// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // See the corresponding header file for description of the functions in this // file. #include "chrome/installer/util/install_util.h" #include <shellapi.h> #include <shlobj.h> #include <shlwapi.h> #include <algorithm> #include "base/command_line.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/process/launch.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/sys_info.h" #include "base/values.h" #include "base/version.h" #include "base/win/metro.h" #include "base/win/registry.h" #include "base/win/windows_version.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/installer/util/browser_distribution.h" #include "chrome/installer/util/google_update_constants.h" #include "chrome/installer/util/helper.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/l10n_string_util.h" #include "chrome/installer/util/util_constants.h" #include "chrome/installer/util/work_item_list.h" using base::win::RegKey; using installer::ProductState; namespace { const wchar_t kStageBinaryPatching[] = L"binary_patching"; const wchar_t kStageBuilding[] = L"building"; const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch"; const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs"; const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts"; const wchar_t kStageEnsemblePatching[] = L"ensemble_patching"; const wchar_t kStageExecuting[] = L"executing"; const wchar_t kStageFinishing[] = L"finishing"; const wchar_t kStagePreconditions[] = L"preconditions"; const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy"; const wchar_t kStageRegisteringChrome[] = L"registering_chrome"; const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver"; const wchar_t kStageRollingback[] = L"rollingback"; const wchar_t kStageUncompressing[] = L"uncompressing"; const wchar_t kStageUnpacking[] = L"unpacking"; const wchar_t kStageUpdatingChannels[] = L"updating_channels"; const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest"; const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version"; const wchar_t kStageUninstallingBinaries[] = L"uninstalling_binaries"; const wchar_t kStageUninstallingChromeFrame[] = L"uninstalling_chrome_frame"; const wchar_t* const kStages[] = { NULL, kStagePreconditions, kStageUncompressing, kStageEnsemblePatching, kStageBinaryPatching, kStageUnpacking, kStageBuilding, kStageExecuting, kStageRollingback, kStageRefreshingPolicy, kStageUpdatingChannels, kStageCopyingPreferencesFile, kStageCreatingShortcuts, kStageRegisteringChrome, kStageRemovingOldVersions, kStageFinishing, kStageConfiguringAutoLaunch, kStageCreatingVisualManifest, kStageDeferringToHigherVersion, kStageUninstallingBinaries, kStageUninstallingChromeFrame, }; COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages), kStages_disagrees_with_Stage_comma_they_must_match_bang); // Creates a zero-sized non-decorated foreground window that doesn't appear // in the taskbar. This is used as a parent window for calls to ShellExecuteEx // in order for the UAC dialog to appear in the foreground and for focus // to be returned to this process once the UAC task is dismissed. Returns // NULL on failure, a handle to the UAC window on success. HWND CreateUACForegroundWindow() { HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW, L"STATIC", NULL, WS_POPUP | WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, ::GetModuleHandle(NULL), NULL); if (foreground_window) { HMONITOR monitor = ::MonitorFromWindow(foreground_window, MONITOR_DEFAULTTONEAREST); if (monitor) { MONITORINFO mi = {0}; mi.cbSize = sizeof(mi); ::GetMonitorInfo(monitor, &mi); RECT screen_rect = mi.rcWork; int x_offset = (screen_rect.right - screen_rect.left) / 2; int y_offset = (screen_rect.bottom - screen_rect.top) / 2; ::MoveWindow(foreground_window, screen_rect.left + x_offset, screen_rect.top + y_offset, 0, 0, FALSE); } else { NOTREACHED() << "Unable to get default monitor"; } ::SetForegroundWindow(foreground_window); } return foreground_window; } } // namespace base::string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) { static const wchar_t kInstalledComponentsPath[] = L"Software\\Microsoft\\Active Setup\\Installed Components\\"; return kInstalledComponentsPath + dist->GetActiveSetupGuid(); } void InstallUtil::TriggerActiveSetupCommand() { base::string16 active_setup_reg( GetActiveSetupPath(BrowserDistribution::GetDistribution())); base::win::RegKey active_setup_key( HKEY_LOCAL_MACHINE, active_setup_reg.c_str(), KEY_QUERY_VALUE); base::string16 cmd_str; LONG read_status = active_setup_key.ReadValue(L"StubPath", &cmd_str); if (read_status != ERROR_SUCCESS) { LOG(ERROR) << active_setup_reg << ", " << read_status; // This should never fail if Chrome is registered at system-level, but if it // does there is not much else to be done. return; } CommandLine cmd(CommandLine::FromString(cmd_str)); // Force creation of shortcuts as the First Run beacon might land between now // and the time setup.exe checks for it. cmd.AppendSwitch(installer::switches::kForceConfigureUserSettings); base::LaunchOptions launch_options; if (base::win::IsMetroProcess()) launch_options.force_breakaway_from_job_ = true; if (!base::LaunchProcess(cmd.GetCommandLineString(), launch_options, NULL)) PLOG(ERROR) << cmd.GetCommandLineString(); } bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) { base::FilePath::StringType program(cmd.GetProgram().value()); DCHECK(!program.empty()); DCHECK_NE(program[0], L'\"'); CommandLine::StringType params(cmd.GetCommandLineString()); if (params[0] == '"') { DCHECK_EQ('"', params[program.length() + 1]); DCHECK_EQ(program, params.substr(1, program.length())); params = params.substr(program.length() + 2); } else { DCHECK_EQ(program, params.substr(0, program.length())); params = params.substr(program.length()); } base::TrimWhitespace(params, base::TRIM_ALL, ¶ms); HWND uac_foreground_window = CreateUACForegroundWindow(); SHELLEXECUTEINFO info = {0}; info.cbSize = sizeof(SHELLEXECUTEINFO); info.fMask = SEE_MASK_NOCLOSEPROCESS; info.hwnd = uac_foreground_window; info.lpVerb = L"runas"; info.lpFile = program.c_str(); info.lpParameters = params.c_str(); info.nShow = SW_SHOW; bool success = false; if (::ShellExecuteEx(&info) == TRUE) { ::WaitForSingleObject(info.hProcess, INFINITE); DWORD ret_val = 0; if (::GetExitCodeProcess(info.hProcess, &ret_val)) { success = true; if (exit_code) *exit_code = ret_val; } } if (uac_foreground_window) { DestroyWindow(uac_foreground_window); } return success; } CommandLine InstallUtil::GetChromeUninstallCmd( bool system_install, BrowserDistribution::Type distribution_type) { ProductState state; if (state.Initialize(system_install, distribution_type)) { return state.uninstall_command(); } return CommandLine(CommandLine::NO_PROGRAM); } void InstallUtil::GetChromeVersion(BrowserDistribution* dist, bool system_install, Version* version) { DCHECK(dist); RegKey key; HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE | KEY_WOW64_32KEY); base::string16 version_str; if (result == ERROR_SUCCESS) result = key.ReadValue(google_update::kRegVersionField, &version_str); *version = Version(); if (result == ERROR_SUCCESS && !version_str.empty()) { VLOG(1) << "Existing " << dist->GetDisplayName() << " version found " << version_str; *version = Version(base::UTF16ToASCII(version_str)); } else { DCHECK_EQ(ERROR_FILE_NOT_FOUND, result); VLOG(1) << "No existing " << dist->GetDisplayName() << " install found."; } } void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist, bool system_install, Version* version) { DCHECK(dist); RegKey key; HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE | KEY_WOW64_32KEY); base::string16 version_str; if (result == ERROR_SUCCESS) result = key.ReadValue(google_update::kRegCriticalVersionField, &version_str); *version = Version(); if (result == ERROR_SUCCESS && !version_str.empty()) { VLOG(1) << "Critical Update version for " << dist->GetDisplayName() << " found " << version_str; *version = Version(base::UTF16ToASCII(version_str)); } else { DCHECK_EQ(ERROR_FILE_NOT_FOUND, result); VLOG(1) << "No existing " << dist->GetDisplayName() << " install found."; } } bool InstallUtil::IsOSSupported() { // We do not support Win2K or older, or XP without service pack 2. VLOG(1) << base::SysInfo::OperatingSystemName() << ' ' << base::SysInfo::OperatingSystemVersion(); base::win::Version version = base::win::GetVersion(); return (version > base::win::VERSION_XP) || ((version == base::win::VERSION_XP) && (base::win::OSInfo::GetInstance()->service_pack().major >= 2)); } void InstallUtil::AddInstallerResultItems( bool system_install, const base::string16& state_key, installer::InstallStatus status, int string_resource_id, const base::string16* const launch_cmd, WorkItemList* install_list) { DCHECK(install_list); const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; DWORD installer_result = (GetInstallReturnCode(status) == 0) ? 0 : 1; install_list->AddCreateRegKeyWorkItem(root, state_key, KEY_WOW64_32KEY); install_list->AddSetRegValueWorkItem(root, state_key, KEY_WOW64_32KEY, installer::kInstallerResult, installer_result, true); install_list->AddSetRegValueWorkItem(root, state_key, KEY_WOW64_32KEY, installer::kInstallerError, static_cast<DWORD>(status), true); if (string_resource_id != 0) { base::string16 msg = installer::GetLocalizedString(string_resource_id); install_list->AddSetRegValueWorkItem(root, state_key, KEY_WOW64_32KEY, installer::kInstallerResultUIString, msg, true); } if (launch_cmd != NULL && !launch_cmd->empty()) { install_list->AddSetRegValueWorkItem( root, state_key, KEY_WOW64_32KEY, installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true); } } void InstallUtil::UpdateInstallerStage(bool system_install, const base::string16& state_key_path, installer::InstallerStage stage) { DCHECK_LE(static_cast<installer::InstallerStage>(0), stage); DCHECK_GT(installer::NUM_STAGES, stage); const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; RegKey state_key; LONG result = state_key.Open(root, state_key_path.c_str(), KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_32KEY); if (result == ERROR_SUCCESS) { if (stage == installer::NO_STAGE) { result = state_key.DeleteValue(installer::kInstallerExtraCode1); LOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) << "Failed deleting installer stage from " << state_key_path << "; result: " << result; } else { const DWORD extra_code_1 = static_cast<DWORD>(stage); result = state_key.WriteValue(installer::kInstallerExtraCode1, extra_code_1); LOG_IF(ERROR, result != ERROR_SUCCESS) << "Failed writing installer stage to " << state_key_path << "; result: " << result; } // TODO(grt): Remove code below here once we're convinced that our use of // Google Update's new InstallerExtraCode1 value is good. installer::ChannelInfo channel_info; // This will return false if the "ap" value isn't present, which is fine. channel_info.Initialize(state_key); if (channel_info.SetStage(kStages[stage]) && !channel_info.Write(&state_key)) { LOG(ERROR) << "Failed writing installer stage to " << state_key_path; } } else { LOG(ERROR) << "Failed opening " << state_key_path << " to update installer stage; result: " << result; } } bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) { wchar_t program_files_path[MAX_PATH] = {0}; if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, program_files_path))) { return !StartsWith(exe_path, program_files_path, false); } else { NOTREACHED(); } return true; } bool InstallUtil::IsMultiInstall(BrowserDistribution* dist, bool system_install) { DCHECK(dist); ProductState state; return state.Initialize(system_install, dist) && state.is_multi_install(); } bool CheckIsChromeSxSProcess() { CommandLine* command_line = CommandLine::ForCurrentProcess(); CHECK(command_line); if (command_line->HasSwitch(installer::switches::kChromeSxS)) return true; // Also return true if we are running from Chrome SxS installed path. base::FilePath exe_dir; PathService::Get(base::DIR_EXE, &exe_dir); base::string16 chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2); chrome_sxs_dir.append(installer::kSxSSuffix); // This is SxS if current EXE is in or under (possibly multiple levels under) // |chrome_sxs_dir|\|installer::kInstallBinaryDir| std::vector<base::FilePath::StringType> components; exe_dir.GetComponents(&components); // We need at least 1 element in the array for the behavior of the following // loop to be defined. This should always be true, since we're splitting the // path to our executable and one of the components will be the drive letter. DCHECK(!components.empty()); typedef std::vector<base::FilePath::StringType>::const_reverse_iterator ComponentsIterator; for (ComponentsIterator current = components.rbegin(), parent = current + 1; parent != components.rend(); current = parent++) { if (base::FilePath::CompareEqualIgnoreCase( *current, installer::kInstallBinaryDir) && base::FilePath::CompareEqualIgnoreCase(*parent, chrome_sxs_dir)) { return true; } } return false; } bool InstallUtil::IsChromeSxSProcess() { static bool sxs = CheckIsChromeSxSProcess(); return sxs; } // static bool InstallUtil::IsFirstRunSentinelPresent() { // TODO(msw): Consolidate with first_run::internal::IsFirstRunSentinelPresent. base::FilePath user_data_dir; return !PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) || base::PathExists(user_data_dir.Append(chrome::kFirstRunSentinel)); } // static bool InstallUtil::GetEULASentinelFilePath(base::FilePath* path) { base::FilePath user_data_dir; if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) return false; *path = user_data_dir.Append(installer::kEULASentinelFile); return true; } // This method tries to delete a registry key and logs an error message // in case of failure. It returns true if deletion is successful (or the key did // not exist), otherwise false. bool InstallUtil::DeleteRegistryKey(HKEY root_key, const base::string16& key_path, REGSAM wow64_access) { VLOG(1) << "Deleting registry key " << key_path; RegKey target_key; LONG result = target_key.Open(root_key, key_path.c_str(), KEY_READ | KEY_WRITE | wow64_access); if (result == ERROR_FILE_NOT_FOUND) return true; if (result == ERROR_SUCCESS) result = target_key.DeleteKey(L""); if (result != ERROR_SUCCESS) { LOG(ERROR) << "Failed to delete registry key: " << key_path << " error: " << result; return false; } return true; } // This method tries to delete a registry value and logs an error message // in case of failure. It returns true if deletion is successful (or the key did // not exist), otherwise false. bool InstallUtil::DeleteRegistryValue(HKEY reg_root, const base::string16& key_path, REGSAM wow64_access, const base::string16& value_name) { RegKey key; LONG result = key.Open(reg_root, key_path.c_str(), KEY_SET_VALUE | wow64_access); if (result == ERROR_SUCCESS) result = key.DeleteValue(value_name.c_str()); if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { LOG(ERROR) << "Failed to delete registry value: " << value_name << " error: " << result; return false; } return true; } // static InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf( HKEY root_key, const base::string16& key_to_delete_path, const base::string16& key_to_test_path, const REGSAM wow64_access, const wchar_t* value_name, const RegistryValuePredicate& predicate) { DCHECK(root_key); ConditionalDeleteResult delete_result = NOT_FOUND; RegKey key; base::string16 actual_value; if (key.Open(root_key, key_to_test_path.c_str(), KEY_QUERY_VALUE | wow64_access) == ERROR_SUCCESS && key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS && predicate.Evaluate(actual_value)) { key.Close(); delete_result = DeleteRegistryKey(root_key, key_to_delete_path, wow64_access) ? DELETED : DELETE_FAILED; } return delete_result; } // static InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf( HKEY root_key, const wchar_t* key_path, REGSAM wow64_access, const wchar_t* value_name, const RegistryValuePredicate& predicate) { DCHECK(root_key); DCHECK(key_path); ConditionalDeleteResult delete_result = NOT_FOUND; RegKey key; base::string16 actual_value; if (key.Open(root_key, key_path, KEY_QUERY_VALUE | KEY_SET_VALUE | wow64_access) == ERROR_SUCCESS && key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS && predicate.Evaluate(actual_value)) { LONG result = key.DeleteValue(value_name); if (result != ERROR_SUCCESS) { LOG(ERROR) << "Failed to delete registry value: " << (value_name ? value_name : L"(Default)") << " error: " << result; delete_result = DELETE_FAILED; } delete_result = DELETED; } return delete_result; } bool InstallUtil::ValueEquals::Evaluate(const base::string16& value) const { return value == value_to_match_; } // static int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) { switch (status) { case installer::FIRST_INSTALL_SUCCESS: case installer::INSTALL_REPAIRED: case installer::NEW_VERSION_UPDATED: case installer::IN_USE_UPDATED: case installer::UNUSED_BINARIES_UNINSTALLED: return 0; default: return status; } } // static void InstallUtil::MakeUninstallCommand(const base::string16& program, const base::string16& arguments, CommandLine* command_line) { *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments); } // static base::string16 InstallUtil::GetCurrentDate() { static const wchar_t kDateFormat[] = L"yyyyMMdd"; wchar_t date_str[arraysize(kDateFormat)] = {0}; int len = GetDateFormatW(LOCALE_INVARIANT, 0, NULL, kDateFormat, date_str, arraysize(date_str)); if (len) { --len; // Subtract terminating \0. } else { PLOG(DFATAL) << "GetDateFormat"; } return base::string16(date_str, len); } // Open |path| with minimal access to obtain information about it, returning // true and populating |file| on success. // static bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath& path, base::File* file) { DCHECK(file); file->Initialize(path, base::File::FLAG_OPEN); return file->IsValid(); } // Populate |info| for |file|, returning true on success. // static bool InstallUtil::ProgramCompare::GetInfo(const base::File& file, BY_HANDLE_FILE_INFORMATION* info) { DCHECK(file.IsValid()); return GetFileInformationByHandle(file.GetPlatformFile(), info) != 0; } InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath& path_to_match) : path_to_match_(path_to_match), file_info_() { DCHECK(!path_to_match_.empty()); if (!OpenForInfo(path_to_match_, &file_)) { PLOG(WARNING) << "Failed opening " << path_to_match_.value() << "; falling back to path string comparisons."; } else if (!GetInfo(file_, &file_info_)) { PLOG(WARNING) << "Failed getting information for " << path_to_match_.value() << "; falling back to path string comparisons."; file_.Close(); } } InstallUtil::ProgramCompare::~ProgramCompare() { } bool InstallUtil::ProgramCompare::Evaluate(const base::string16& value) const { // Suss out the exe portion of the value, which is expected to be a command // line kinda (or exactly) like: // "c:\foo\bar\chrome.exe" -- "%1" base::FilePath program(CommandLine::FromString(value).GetProgram()); if (program.empty()) { LOG(WARNING) << "Failed to parse an executable name from command line: \"" << value << "\""; return false; } return EvaluatePath(program); } bool InstallUtil::ProgramCompare::EvaluatePath( const base::FilePath& path) const { // Try the simple thing first: do the paths happen to match? if (base::FilePath::CompareEqualIgnoreCase(path_to_match_.value(), path.value())) return true; // If the paths don't match and we couldn't open the expected file, we've done // our best. if (!file_.IsValid()) return false; // Open the program and see if it references the expected file. base::File file; BY_HANDLE_FILE_INFORMATION info = {}; return (OpenForInfo(path, &file) && GetInfo(file, &info) && info.dwVolumeSerialNumber == file_info_.dwVolumeSerialNumber && info.nFileIndexHigh == file_info_.nFileIndexHigh && info.nFileIndexLow == file_info_.nFileIndexLow); }