1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // See the corresponding header file for description of the functions in this
8 #include "chrome/installer/util/install_util.h"
16 #include "base/command_line.h"
17 #include "base/file_util.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_ptr.h"
20 #include "base/path_service.h"
21 #include "base/platform_file.h"
22 #include "base/process/launch.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/sys_info.h"
26 #include "base/values.h"
27 #include "base/version.h"
28 #include "base/win/metro.h"
29 #include "base/win/registry.h"
30 #include "base/win/windows_version.h"
31 #include "chrome/common/chrome_constants.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/installer/util/browser_distribution.h"
34 #include "chrome/installer/util/google_update_constants.h"
35 #include "chrome/installer/util/helper.h"
36 #include "chrome/installer/util/installation_state.h"
37 #include "chrome/installer/util/l10n_string_util.h"
38 #include "chrome/installer/util/util_constants.h"
39 #include "chrome/installer/util/work_item_list.h"
41 using base::win::RegKey;
42 using installer::ProductState;
46 const wchar_t kStageBinaryPatching[] = L"binary_patching";
47 const wchar_t kStageBuilding[] = L"building";
48 const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch";
49 const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs";
50 const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts";
51 const wchar_t kStageEnsemblePatching[] = L"ensemble_patching";
52 const wchar_t kStageExecuting[] = L"executing";
53 const wchar_t kStageFinishing[] = L"finishing";
54 const wchar_t kStagePreconditions[] = L"preconditions";
55 const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy";
56 const wchar_t kStageRegisteringChrome[] = L"registering_chrome";
57 const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver";
58 const wchar_t kStageRollingback[] = L"rollingback";
59 const wchar_t kStageUncompressing[] = L"uncompressing";
60 const wchar_t kStageUnpacking[] = L"unpacking";
61 const wchar_t kStageUpdatingChannels[] = L"updating_channels";
62 const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest";
63 const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version";
64 const wchar_t kStageUninstallingBinaries[] = L"uninstalling_binaries";
65 const wchar_t kStageUninstallingChromeFrame[] = L"uninstalling_chrome_frame";
67 const wchar_t* const kStages[] = {
71 kStageEnsemblePatching,
77 kStageRefreshingPolicy,
78 kStageUpdatingChannels,
79 kStageCopyingPreferencesFile,
80 kStageCreatingShortcuts,
81 kStageRegisteringChrome,
82 kStageRemovingOldVersions,
84 kStageConfiguringAutoLaunch,
85 kStageCreatingVisualManifest,
86 kStageDeferringToHigherVersion,
87 kStageUninstallingBinaries,
88 kStageUninstallingChromeFrame,
91 COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages),
92 kStages_disagrees_with_Stage_comma_they_must_match_bang);
94 // Creates a zero-sized non-decorated foreground window that doesn't appear
95 // in the taskbar. This is used as a parent window for calls to ShellExecuteEx
96 // in order for the UAC dialog to appear in the foreground and for focus
97 // to be returned to this process once the UAC task is dismissed. Returns
98 // NULL on failure, a handle to the UAC window on success.
99 HWND CreateUACForegroundWindow() {
100 HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW,
103 WS_POPUP | WS_VISIBLE,
106 ::GetModuleHandle(NULL),
108 if (foreground_window) {
109 HMONITOR monitor = ::MonitorFromWindow(foreground_window,
110 MONITOR_DEFAULTTONEAREST);
112 MONITORINFO mi = {0};
113 mi.cbSize = sizeof(mi);
114 ::GetMonitorInfo(monitor, &mi);
115 RECT screen_rect = mi.rcWork;
116 int x_offset = (screen_rect.right - screen_rect.left) / 2;
117 int y_offset = (screen_rect.bottom - screen_rect.top) / 2;
118 ::MoveWindow(foreground_window,
119 screen_rect.left + x_offset,
120 screen_rect.top + y_offset,
123 NOTREACHED() << "Unable to get default monitor";
125 ::SetForegroundWindow(foreground_window);
127 return foreground_window;
132 base::string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) {
133 static const wchar_t kInstalledComponentsPath[] =
134 L"Software\\Microsoft\\Active Setup\\Installed Components\\";
135 return kInstalledComponentsPath + dist->GetActiveSetupGuid();
138 void InstallUtil::TriggerActiveSetupCommand() {
139 base::string16 active_setup_reg(
140 GetActiveSetupPath(BrowserDistribution::GetDistribution()));
141 base::win::RegKey active_setup_key(
142 HKEY_LOCAL_MACHINE, active_setup_reg.c_str(), KEY_QUERY_VALUE);
143 base::string16 cmd_str;
144 LONG read_status = active_setup_key.ReadValue(L"StubPath", &cmd_str);
145 if (read_status != ERROR_SUCCESS) {
146 LOG(ERROR) << active_setup_reg << ", " << read_status;
147 // This should never fail if Chrome is registered at system-level, but if it
148 // does there is not much else to be done.
152 CommandLine cmd(CommandLine::FromString(cmd_str));
153 // Force creation of shortcuts as the First Run beacon might land between now
154 // and the time setup.exe checks for it.
155 cmd.AppendSwitch(installer::switches::kForceConfigureUserSettings);
157 base::LaunchOptions launch_options;
158 if (base::win::IsMetroProcess())
159 launch_options.force_breakaway_from_job_ = true;
160 if (!base::LaunchProcess(cmd.GetCommandLineString(), launch_options, NULL))
161 PLOG(ERROR) << cmd.GetCommandLineString();
164 bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) {
165 base::FilePath::StringType program(cmd.GetProgram().value());
166 DCHECK(!program.empty());
167 DCHECK_NE(program[0], L'\"');
169 CommandLine::StringType params(cmd.GetCommandLineString());
170 if (params[0] == '"') {
171 DCHECK_EQ('"', params[program.length() + 1]);
172 DCHECK_EQ(program, params.substr(1, program.length()));
173 params = params.substr(program.length() + 2);
175 DCHECK_EQ(program, params.substr(0, program.length()));
176 params = params.substr(program.length());
179 base::TrimWhitespace(params, base::TRIM_ALL, ¶ms);
181 HWND uac_foreground_window = CreateUACForegroundWindow();
183 SHELLEXECUTEINFO info = {0};
184 info.cbSize = sizeof(SHELLEXECUTEINFO);
185 info.fMask = SEE_MASK_NOCLOSEPROCESS;
186 info.hwnd = uac_foreground_window;
187 info.lpVerb = L"runas";
188 info.lpFile = program.c_str();
189 info.lpParameters = params.c_str();
190 info.nShow = SW_SHOW;
192 bool success = false;
193 if (::ShellExecuteEx(&info) == TRUE) {
194 ::WaitForSingleObject(info.hProcess, INFINITE);
196 if (::GetExitCodeProcess(info.hProcess, &ret_val)) {
199 *exit_code = ret_val;
203 if (uac_foreground_window) {
204 DestroyWindow(uac_foreground_window);
210 CommandLine InstallUtil::GetChromeUninstallCmd(
211 bool system_install, BrowserDistribution::Type distribution_type) {
213 if (state.Initialize(system_install, distribution_type)) {
214 return state.uninstall_command();
216 return CommandLine(CommandLine::NO_PROGRAM);
219 void InstallUtil::GetChromeVersion(BrowserDistribution* dist,
224 HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
225 LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(),
228 base::string16 version_str;
229 if (result == ERROR_SUCCESS)
230 result = key.ReadValue(google_update::kRegVersionField, &version_str);
232 *version = Version();
233 if (result == ERROR_SUCCESS && !version_str.empty()) {
234 VLOG(1) << "Existing " << dist->GetDisplayName() << " version found "
236 *version = Version(base::UTF16ToASCII(version_str));
238 DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
239 VLOG(1) << "No existing " << dist->GetDisplayName()
240 << " install found.";
244 void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist,
249 HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
251 key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE);
253 base::string16 version_str;
254 if (result == ERROR_SUCCESS)
255 result = key.ReadValue(google_update::kRegCriticalVersionField,
258 *version = Version();
259 if (result == ERROR_SUCCESS && !version_str.empty()) {
260 VLOG(1) << "Critical Update version for " << dist->GetDisplayName()
261 << " found " << version_str;
262 *version = Version(base::UTF16ToASCII(version_str));
264 DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
265 VLOG(1) << "No existing " << dist->GetDisplayName()
266 << " install found.";
270 bool InstallUtil::IsOSSupported() {
271 // We do not support Win2K or older, or XP without service pack 2.
272 VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
273 << base::SysInfo::OperatingSystemVersion();
274 base::win::Version version = base::win::GetVersion();
275 return (version > base::win::VERSION_XP) ||
276 ((version == base::win::VERSION_XP) &&
277 (base::win::OSInfo::GetInstance()->service_pack().major >= 2));
280 void InstallUtil::AddInstallerResultItems(
282 const base::string16& state_key,
283 installer::InstallStatus status,
284 int string_resource_id,
285 const base::string16* const launch_cmd,
286 WorkItemList* install_list) {
287 DCHECK(install_list);
288 const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
289 DWORD installer_result = (GetInstallReturnCode(status) == 0) ? 0 : 1;
290 install_list->AddCreateRegKeyWorkItem(root, state_key);
291 install_list->AddSetRegValueWorkItem(root, state_key,
292 installer::kInstallerResult,
293 installer_result, true);
294 install_list->AddSetRegValueWorkItem(root, state_key,
295 installer::kInstallerError,
296 static_cast<DWORD>(status), true);
297 if (string_resource_id != 0) {
298 base::string16 msg = installer::GetLocalizedString(string_resource_id);
299 install_list->AddSetRegValueWorkItem(root, state_key,
300 installer::kInstallerResultUIString, msg, true);
302 if (launch_cmd != NULL && !launch_cmd->empty()) {
303 install_list->AddSetRegValueWorkItem(root, state_key,
304 installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true);
308 void InstallUtil::UpdateInstallerStage(bool system_install,
309 const base::string16& state_key_path,
310 installer::InstallerStage stage) {
311 DCHECK_LE(static_cast<installer::InstallerStage>(0), stage);
312 DCHECK_GT(installer::NUM_STAGES, stage);
313 const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
315 LONG result = state_key.Open(root, state_key_path.c_str(),
316 KEY_QUERY_VALUE | KEY_SET_VALUE);
317 if (result == ERROR_SUCCESS) {
318 if (stage == installer::NO_STAGE) {
319 result = state_key.DeleteValue(installer::kInstallerExtraCode1);
320 LOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND)
321 << "Failed deleting installer stage from " << state_key_path
322 << "; result: " << result;
324 const DWORD extra_code_1 = static_cast<DWORD>(stage);
325 result = state_key.WriteValue(installer::kInstallerExtraCode1,
327 LOG_IF(ERROR, result != ERROR_SUCCESS)
328 << "Failed writing installer stage to " << state_key_path
329 << "; result: " << result;
331 // TODO(grt): Remove code below here once we're convinced that our use of
332 // Google Update's new InstallerExtraCode1 value is good.
333 installer::ChannelInfo channel_info;
334 // This will return false if the "ap" value isn't present, which is fine.
335 channel_info.Initialize(state_key);
336 if (channel_info.SetStage(kStages[stage]) &&
337 !channel_info.Write(&state_key)) {
338 LOG(ERROR) << "Failed writing installer stage to " << state_key_path;
341 LOG(ERROR) << "Failed opening " << state_key_path
342 << " to update installer stage; result: " << result;
346 bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) {
347 wchar_t program_files_path[MAX_PATH] = {0};
348 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
349 SHGFP_TYPE_CURRENT, program_files_path))) {
350 return !StartsWith(exe_path, program_files_path, false);
357 bool InstallUtil::IsMultiInstall(BrowserDistribution* dist,
358 bool system_install) {
361 return state.Initialize(system_install, dist) && state.is_multi_install();
364 bool CheckIsChromeSxSProcess() {
365 CommandLine* command_line = CommandLine::ForCurrentProcess();
368 if (command_line->HasSwitch(installer::switches::kChromeSxS))
371 // Also return true if we are running from Chrome SxS installed path.
372 base::FilePath exe_dir;
373 PathService::Get(base::DIR_EXE, &exe_dir);
374 base::string16 chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2);
375 chrome_sxs_dir.append(installer::kSxSSuffix);
377 // This is SxS if current EXE is in or under (possibly multiple levels under)
378 // |chrome_sxs_dir|\|installer::kInstallBinaryDir|
379 std::vector<base::FilePath::StringType> components;
380 exe_dir.GetComponents(&components);
381 // We need at least 1 element in the array for the behavior of the following
382 // loop to be defined. This should always be true, since we're splitting the
383 // path to our executable and one of the components will be the drive letter.
384 DCHECK(!components.empty());
385 typedef std::vector<base::FilePath::StringType>::const_reverse_iterator
387 for (ComponentsIterator current = components.rbegin(), parent = current + 1;
388 parent != components.rend(); current = parent++) {
389 if (base::FilePath::CompareEqualIgnoreCase(
390 *current, installer::kInstallBinaryDir) &&
391 base::FilePath::CompareEqualIgnoreCase(*parent, chrome_sxs_dir)) {
399 bool InstallUtil::IsChromeSxSProcess() {
400 static bool sxs = CheckIsChromeSxSProcess();
405 bool InstallUtil::IsFirstRunSentinelPresent() {
406 // TODO(msw): Consolidate with first_run::internal::IsFirstRunSentinelPresent.
407 base::FilePath user_data_dir;
408 return !PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) ||
409 base::PathExists(user_data_dir.Append(chrome::kFirstRunSentinel));
413 bool InstallUtil::GetEULASentinelFilePath(base::FilePath* path) {
414 base::FilePath user_data_dir;
415 if (!PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
417 *path = user_data_dir.Append(installer::kEULASentinelFile);
421 // This method tries to delete a registry key and logs an error message
422 // in case of failure. It returns true if deletion is successful (or the key did
423 // not exist), otherwise false.
424 bool InstallUtil::DeleteRegistryKey(HKEY root_key,
425 const base::string16& key_path) {
426 VLOG(1) << "Deleting registry key " << key_path;
427 LONG result = ::SHDeleteKey(root_key, key_path.c_str());
428 if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
429 LOG(ERROR) << "Failed to delete registry key: " << key_path
430 << " error: " << result;
436 // This method tries to delete a registry value and logs an error message
437 // in case of failure. It returns true if deletion is successful (or the key did
438 // not exist), otherwise false.
439 bool InstallUtil::DeleteRegistryValue(HKEY reg_root,
440 const base::string16& key_path,
441 const base::string16& value_name) {
443 LONG result = key.Open(reg_root, key_path.c_str(), KEY_SET_VALUE);
444 if (result == ERROR_SUCCESS)
445 result = key.DeleteValue(value_name.c_str());
446 if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
447 LOG(ERROR) << "Failed to delete registry value: " << value_name
448 << " error: " << result;
455 InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf(
457 const base::string16& key_to_delete_path,
458 const base::string16& key_to_test_path,
459 const wchar_t* value_name,
460 const RegistryValuePredicate& predicate) {
462 ConditionalDeleteResult delete_result = NOT_FOUND;
464 base::string16 actual_value;
465 if (key.Open(root_key, key_to_test_path.c_str(),
466 KEY_QUERY_VALUE) == ERROR_SUCCESS &&
467 key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
468 predicate.Evaluate(actual_value)) {
470 delete_result = DeleteRegistryKey(root_key, key_to_delete_path)
471 ? DELETED : DELETE_FAILED;
473 return delete_result;
477 InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf(
479 const wchar_t* key_path,
480 const wchar_t* value_name,
481 const RegistryValuePredicate& predicate) {
484 ConditionalDeleteResult delete_result = NOT_FOUND;
486 base::string16 actual_value;
487 if (key.Open(root_key, key_path,
488 KEY_QUERY_VALUE | KEY_SET_VALUE) == ERROR_SUCCESS &&
489 key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
490 predicate.Evaluate(actual_value)) {
491 LONG result = key.DeleteValue(value_name);
492 if (result != ERROR_SUCCESS) {
493 LOG(ERROR) << "Failed to delete registry value: "
494 << (value_name ? value_name : L"(Default)")
495 << " error: " << result;
496 delete_result = DELETE_FAILED;
498 delete_result = DELETED;
500 return delete_result;
503 bool InstallUtil::ValueEquals::Evaluate(const base::string16& value) const {
504 return value == value_to_match_;
508 int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) {
510 case installer::FIRST_INSTALL_SUCCESS:
511 case installer::INSTALL_REPAIRED:
512 case installer::NEW_VERSION_UPDATED:
513 case installer::IN_USE_UPDATED:
514 case installer::UNUSED_BINARIES_UNINSTALLED:
522 void InstallUtil::MakeUninstallCommand(const base::string16& program,
523 const base::string16& arguments,
524 CommandLine* command_line) {
525 *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments);
529 base::string16 InstallUtil::GetCurrentDate() {
530 static const wchar_t kDateFormat[] = L"yyyyMMdd";
531 wchar_t date_str[arraysize(kDateFormat)] = {0};
532 int len = GetDateFormatW(LOCALE_INVARIANT, 0, NULL, kDateFormat,
533 date_str, arraysize(date_str));
535 --len; // Subtract terminating \0.
537 PLOG(DFATAL) << "GetDateFormat";
540 return base::string16(date_str, len);
543 // Open |path| with minimal access to obtain information about it, returning
544 // true and populating |file| on success.
546 bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath& path,
549 file->Initialize(path, base::File::FLAG_OPEN);
550 return file->IsValid();
553 // Populate |info| for |file|, returning true on success.
555 bool InstallUtil::ProgramCompare::GetInfo(const base::File& file,
556 BY_HANDLE_FILE_INFORMATION* info) {
557 DCHECK(file.IsValid());
558 return GetFileInformationByHandle(file.GetPlatformFile(), info) != 0;
561 InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath& path_to_match)
562 : path_to_match_(path_to_match),
564 DCHECK(!path_to_match_.empty());
565 if (!OpenForInfo(path_to_match_, &file_)) {
566 PLOG(WARNING) << "Failed opening " << path_to_match_.value()
567 << "; falling back to path string comparisons.";
568 } else if (!GetInfo(file_, &file_info_)) {
569 PLOG(WARNING) << "Failed getting information for "
570 << path_to_match_.value()
571 << "; falling back to path string comparisons.";
576 InstallUtil::ProgramCompare::~ProgramCompare() {
579 bool InstallUtil::ProgramCompare::Evaluate(const base::string16& value) const {
580 // Suss out the exe portion of the value, which is expected to be a command
581 // line kinda (or exactly) like:
582 // "c:\foo\bar\chrome.exe" -- "%1"
583 base::FilePath program(CommandLine::FromString(value).GetProgram());
584 if (program.empty()) {
585 LOG(WARNING) << "Failed to parse an executable name from command line: \""
590 return EvaluatePath(program);
593 bool InstallUtil::ProgramCompare::EvaluatePath(
594 const base::FilePath& path) const {
595 // Try the simple thing first: do the paths happen to match?
596 if (base::FilePath::CompareEqualIgnoreCase(path_to_match_.value(),
600 // If the paths don't match and we couldn't open the expected file, we've done
602 if (!file_.IsValid())
605 // Open the program and see if it references the expected file.
607 BY_HANDLE_FILE_INFORMATION info = {};
609 return (OpenForInfo(path, &file) &&
610 GetInfo(file, &info) &&
611 info.dwVolumeSerialNumber == file_info_.dwVolumeSerialNumber &&
612 info.nFileIndexHigh == file_info_.nFileIndexHigh &&
613 info.nFileIndexLow == file_info_.nFileIndexLow);