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/process/launch.h"
22 #include "base/strings/string_util.h"
23 #include "base/sys_info.h"
24 #include "base/values.h"
25 #include "base/version.h"
26 #include "base/win/metro.h"
27 #include "base/win/registry.h"
28 #include "base/win/windows_version.h"
29 #include "chrome/installer/util/browser_distribution.h"
30 #include "chrome/installer/util/google_update_constants.h"
31 #include "chrome/installer/util/helper.h"
32 #include "chrome/installer/util/installation_state.h"
33 #include "chrome/installer/util/l10n_string_util.h"
34 #include "chrome/installer/util/util_constants.h"
35 #include "chrome/installer/util/work_item_list.h"
37 using base::win::RegKey
;
38 using installer::ProductState
;
42 const wchar_t kStageBinaryPatching
[] = L
"binary_patching";
43 const wchar_t kStageBuilding
[] = L
"building";
44 const wchar_t kStageConfiguringAutoLaunch
[] = L
"configuring_auto_launch";
45 const wchar_t kStageCopyingPreferencesFile
[] = L
"copying_prefs";
46 const wchar_t kStageCreatingShortcuts
[] = L
"creating_shortcuts";
47 const wchar_t kStageEnsemblePatching
[] = L
"ensemble_patching";
48 const wchar_t kStageExecuting
[] = L
"executing";
49 const wchar_t kStageFinishing
[] = L
"finishing";
50 const wchar_t kStagePreconditions
[] = L
"preconditions";
51 const wchar_t kStageRefreshingPolicy
[] = L
"refreshing_policy";
52 const wchar_t kStageRegisteringChrome
[] = L
"registering_chrome";
53 const wchar_t kStageRemovingOldVersions
[] = L
"removing_old_ver";
54 const wchar_t kStageRollingback
[] = L
"rollingback";
55 const wchar_t kStageUncompressing
[] = L
"uncompressing";
56 const wchar_t kStageUnpacking
[] = L
"unpacking";
57 const wchar_t kStageUpdatingChannels
[] = L
"updating_channels";
58 const wchar_t kStageCreatingVisualManifest
[] = L
"creating_visual_manifest";
59 const wchar_t kStageDeferringToHigherVersion
[] = L
"deferring_to_higher_version";
61 const wchar_t* const kStages
[] = {
65 kStageEnsemblePatching
,
71 kStageRefreshingPolicy
,
72 kStageUpdatingChannels
,
73 kStageCopyingPreferencesFile
,
74 kStageCreatingShortcuts
,
75 kStageRegisteringChrome
,
76 kStageRemovingOldVersions
,
78 kStageConfiguringAutoLaunch
,
79 kStageCreatingVisualManifest
,
80 kStageDeferringToHigherVersion
,
83 COMPILE_ASSERT(installer::NUM_STAGES
== arraysize(kStages
),
84 kStages_disagrees_with_Stage_comma_they_must_match_bang
);
86 // Creates a zero-sized non-decorated foreground window that doesn't appear
87 // in the taskbar. This is used as a parent window for calls to ShellExecuteEx
88 // in order for the UAC dialog to appear in the foreground and for focus
89 // to be returned to this process once the UAC task is dismissed. Returns
90 // NULL on failure, a handle to the UAC window on success.
91 HWND
CreateUACForegroundWindow() {
92 HWND foreground_window
= ::CreateWindowEx(WS_EX_TOOLWINDOW
,
95 WS_POPUP
| WS_VISIBLE
,
98 ::GetModuleHandle(NULL
),
100 if (foreground_window
) {
101 HMONITOR monitor
= ::MonitorFromWindow(foreground_window
,
102 MONITOR_DEFAULTTONEAREST
);
104 MONITORINFO mi
= {0};
105 mi
.cbSize
= sizeof(mi
);
106 ::GetMonitorInfo(monitor
, &mi
);
107 RECT screen_rect
= mi
.rcWork
;
108 int x_offset
= (screen_rect
.right
- screen_rect
.left
) / 2;
109 int y_offset
= (screen_rect
.bottom
- screen_rect
.top
) / 2;
110 ::MoveWindow(foreground_window
,
111 screen_rect
.left
+ x_offset
,
112 screen_rect
.top
+ y_offset
,
115 NOTREACHED() << "Unable to get default monitor";
117 ::SetForegroundWindow(foreground_window
);
119 return foreground_window
;
124 string16
InstallUtil::GetActiveSetupPath(BrowserDistribution
* dist
) {
125 static const wchar_t kInstalledComponentsPath
[] =
126 L
"Software\\Microsoft\\Active Setup\\Installed Components\\";
127 return kInstalledComponentsPath
+ dist
->GetActiveSetupGuid();
130 void InstallUtil::TriggerActiveSetupCommand() {
131 string16
active_setup_reg(
132 GetActiveSetupPath(BrowserDistribution::GetDistribution()));
133 base::win::RegKey
active_setup_key(
134 HKEY_LOCAL_MACHINE
, active_setup_reg
.c_str(), KEY_QUERY_VALUE
);
136 LONG read_status
= active_setup_key
.ReadValue(L
"StubPath", &cmd_str
);
137 if (read_status
!= ERROR_SUCCESS
) {
138 LOG(ERROR
) << active_setup_reg
<< ", " << read_status
;
139 // This should never fail if Chrome is registered at system-level, but if it
140 // does there is not much else to be done.
144 CommandLine
cmd(CommandLine::FromString(cmd_str
));
145 // Force creation of shortcuts as the First Run beacon might land between now
146 // and the time setup.exe checks for it.
147 cmd
.AppendSwitch(installer::switches::kForceConfigureUserSettings
);
149 base::LaunchOptions launch_options
;
150 if (base::win::IsMetroProcess())
151 launch_options
.force_breakaway_from_job_
= true;
152 if (!base::LaunchProcess(cmd
.GetCommandLineString(), launch_options
, NULL
))
153 PLOG(ERROR
) << cmd
.GetCommandLineString();
156 bool InstallUtil::ExecuteExeAsAdmin(const CommandLine
& cmd
, DWORD
* exit_code
) {
157 base::FilePath::StringType
program(cmd
.GetProgram().value());
158 DCHECK(!program
.empty());
159 DCHECK_NE(program
[0], L
'\"');
161 CommandLine::StringType
params(cmd
.GetCommandLineString());
162 if (params
[0] == '"') {
163 DCHECK_EQ('"', params
[program
.length() + 1]);
164 DCHECK_EQ(program
, params
.substr(1, program
.length()));
165 params
= params
.substr(program
.length() + 2);
167 DCHECK_EQ(program
, params
.substr(0, program
.length()));
168 params
= params
.substr(program
.length());
171 TrimWhitespace(params
, TRIM_ALL
, ¶ms
);
173 HWND uac_foreground_window
= CreateUACForegroundWindow();
175 SHELLEXECUTEINFO info
= {0};
176 info
.cbSize
= sizeof(SHELLEXECUTEINFO
);
177 info
.fMask
= SEE_MASK_NOCLOSEPROCESS
;
178 info
.hwnd
= uac_foreground_window
;
179 info
.lpVerb
= L
"runas";
180 info
.lpFile
= program
.c_str();
181 info
.lpParameters
= params
.c_str();
182 info
.nShow
= SW_SHOW
;
184 bool success
= false;
185 if (::ShellExecuteEx(&info
) == TRUE
) {
186 ::WaitForSingleObject(info
.hProcess
, INFINITE
);
188 if (::GetExitCodeProcess(info
.hProcess
, &ret_val
)) {
191 *exit_code
= ret_val
;
195 if (uac_foreground_window
) {
196 DestroyWindow(uac_foreground_window
);
202 CommandLine
InstallUtil::GetChromeUninstallCmd(
203 bool system_install
, BrowserDistribution::Type distribution_type
) {
205 if (state
.Initialize(system_install
, distribution_type
)) {
206 return state
.uninstall_command();
208 return CommandLine(CommandLine::NO_PROGRAM
);
211 void InstallUtil::GetChromeVersion(BrowserDistribution
* dist
,
216 HKEY reg_root
= (system_install
) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
217 LONG result
= key
.Open(reg_root
, dist
->GetVersionKey().c_str(),
220 string16 version_str
;
221 if (result
== ERROR_SUCCESS
)
222 result
= key
.ReadValue(google_update::kRegVersionField
, &version_str
);
224 *version
= Version();
225 if (result
== ERROR_SUCCESS
&& !version_str
.empty()) {
226 VLOG(1) << "Existing " << dist
->GetAppShortCutName() << " version found "
228 *version
= Version(WideToASCII(version_str
));
230 DCHECK_EQ(ERROR_FILE_NOT_FOUND
, result
);
231 VLOG(1) << "No existing " << dist
->GetAppShortCutName()
232 << " install found.";
236 void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution
* dist
,
241 HKEY reg_root
= (system_install
) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
243 key
.Open(reg_root
, dist
->GetVersionKey().c_str(), KEY_QUERY_VALUE
);
245 string16 version_str
;
246 if (result
== ERROR_SUCCESS
)
247 result
= key
.ReadValue(google_update::kRegCriticalVersionField
,
250 *version
= Version();
251 if (result
== ERROR_SUCCESS
&& !version_str
.empty()) {
252 VLOG(1) << "Critical Update version for " << dist
->GetAppShortCutName()
253 << " found " << version_str
;
254 *version
= Version(WideToASCII(version_str
));
256 DCHECK_EQ(ERROR_FILE_NOT_FOUND
, result
);
257 VLOG(1) << "No existing " << dist
->GetAppShortCutName()
258 << " install found.";
262 bool InstallUtil::IsOSSupported() {
263 // We do not support Win2K or older, or XP without service pack 2.
264 VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
265 << base::SysInfo::OperatingSystemVersion();
266 base::win::Version version
= base::win::GetVersion();
267 return (version
> base::win::VERSION_XP
) ||
268 ((version
== base::win::VERSION_XP
) &&
269 (base::win::OSInfo::GetInstance()->service_pack().major
>= 2));
272 void InstallUtil::AddInstallerResultItems(bool system_install
,
273 const string16
& state_key
,
274 installer::InstallStatus status
,
275 int string_resource_id
,
276 const string16
* const launch_cmd
,
277 WorkItemList
* install_list
) {
278 DCHECK(install_list
);
279 const HKEY root
= system_install
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
280 DWORD installer_result
= (GetInstallReturnCode(status
) == 0) ? 0 : 1;
281 install_list
->AddCreateRegKeyWorkItem(root
, state_key
);
282 install_list
->AddSetRegValueWorkItem(root
, state_key
,
283 installer::kInstallerResult
,
284 installer_result
, true);
285 install_list
->AddSetRegValueWorkItem(root
, state_key
,
286 installer::kInstallerError
,
287 static_cast<DWORD
>(status
), true);
288 if (string_resource_id
!= 0) {
289 string16 msg
= installer::GetLocalizedString(string_resource_id
);
290 install_list
->AddSetRegValueWorkItem(root
, state_key
,
291 installer::kInstallerResultUIString
, msg
, true);
293 if (launch_cmd
!= NULL
&& !launch_cmd
->empty()) {
294 install_list
->AddSetRegValueWorkItem(root
, state_key
,
295 installer::kInstallerSuccessLaunchCmdLine
, *launch_cmd
, true);
299 void InstallUtil::UpdateInstallerStage(bool system_install
,
300 const string16
& state_key_path
,
301 installer::InstallerStage stage
) {
302 DCHECK_LE(static_cast<installer::InstallerStage
>(0), stage
);
303 DCHECK_GT(installer::NUM_STAGES
, stage
);
304 const HKEY root
= system_install
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
306 LONG result
= state_key
.Open(root
, state_key_path
.c_str(),
307 KEY_QUERY_VALUE
| KEY_SET_VALUE
);
308 if (result
== ERROR_SUCCESS
) {
309 if (stage
== installer::NO_STAGE
) {
310 result
= state_key
.DeleteValue(installer::kInstallerExtraCode1
);
311 LOG_IF(ERROR
, result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
)
312 << "Failed deleting installer stage from " << state_key_path
313 << "; result: " << result
;
315 const DWORD extra_code_1
= static_cast<DWORD
>(stage
);
316 result
= state_key
.WriteValue(installer::kInstallerExtraCode1
,
318 LOG_IF(ERROR
, result
!= ERROR_SUCCESS
)
319 << "Failed writing installer stage to " << state_key_path
320 << "; result: " << result
;
322 // TODO(grt): Remove code below here once we're convinced that our use of
323 // Google Update's new InstallerExtraCode1 value is good.
324 installer::ChannelInfo channel_info
;
325 // This will return false if the "ap" value isn't present, which is fine.
326 channel_info
.Initialize(state_key
);
327 if (channel_info
.SetStage(kStages
[stage
]) &&
328 !channel_info
.Write(&state_key
)) {
329 LOG(ERROR
) << "Failed writing installer stage to " << state_key_path
;
332 LOG(ERROR
) << "Failed opening " << state_key_path
333 << " to update installer stage; result: " << result
;
337 bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path
) {
338 wchar_t program_files_path
[MAX_PATH
] = {0};
339 if (SUCCEEDED(SHGetFolderPath(NULL
, CSIDL_PROGRAM_FILES
, NULL
,
340 SHGFP_TYPE_CURRENT
, program_files_path
))) {
341 return !StartsWith(exe_path
, program_files_path
, false);
348 bool InstallUtil::IsMultiInstall(BrowserDistribution
* dist
,
349 bool system_install
) {
352 return state
.Initialize(system_install
, dist
->GetType()) &&
353 state
.is_multi_install();
356 bool CheckIsChromeSxSProcess() {
357 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
360 if (command_line
->HasSwitch(installer::switches::kChromeSxS
))
363 // Also return true if we are running from Chrome SxS installed path.
364 base::FilePath exe_dir
;
365 PathService::Get(base::DIR_EXE
, &exe_dir
);
366 string16
chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2
);
367 chrome_sxs_dir
.append(installer::kSxSSuffix
);
368 return base::FilePath::CompareEqualIgnoreCase(
369 exe_dir
.BaseName().value(), installer::kInstallBinaryDir
) &&
370 base::FilePath::CompareEqualIgnoreCase(
371 exe_dir
.DirName().BaseName().value(), chrome_sxs_dir
);
374 bool InstallUtil::IsChromeSxSProcess() {
375 static bool sxs
= CheckIsChromeSxSProcess();
379 bool InstallUtil::GetSentinelFilePath(const base::FilePath::CharType
* file
,
380 BrowserDistribution
* dist
,
381 base::FilePath
* path
) {
382 base::FilePath exe_path
;
383 if (!PathService::Get(base::DIR_EXE
, &exe_path
))
386 if (IsPerUserInstall(exe_path
.value().c_str())) {
387 const base::FilePath
maybe_product_dir(exe_path
.DirName().DirName());
388 if (base::PathExists(exe_path
.Append(installer::kChromeExe
))) {
389 // DIR_EXE is most likely Chrome's directory in which case |exe_path| is
390 // the user-level sentinel path.
392 } else if (base::PathExists(
393 maybe_product_dir
.Append(installer::kChromeExe
))) {
394 // DIR_EXE can also be the Installer directory if this is called from a
395 // setup.exe running from Application\<version>\Installer (see
396 // InstallerState::GetInstallerDirectory) in which case Chrome's directory
398 *path
= maybe_product_dir
;
404 std::vector
<base::FilePath
> user_data_dir_paths
;
405 installer::GetChromeUserDataPaths(dist
, &user_data_dir_paths
);
407 if (!user_data_dir_paths
.empty())
408 *path
= user_data_dir_paths
[0];
413 *path
= path
->Append(file
);
417 // This method tries to delete a registry key and logs an error message
418 // in case of failure. It returns true if deletion is successful (or the key did
419 // not exist), otherwise false.
420 bool InstallUtil::DeleteRegistryKey(HKEY root_key
,
421 const string16
& key_path
) {
422 VLOG(1) << "Deleting registry key " << key_path
;
423 LONG result
= ::SHDeleteKey(root_key
, key_path
.c_str());
424 if (result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
) {
425 LOG(ERROR
) << "Failed to delete registry key: " << key_path
426 << " error: " << result
;
432 // This method tries to delete a registry value and logs an error message
433 // in case of failure. It returns true if deletion is successful (or the key did
434 // not exist), otherwise false.
435 bool InstallUtil::DeleteRegistryValue(HKEY reg_root
,
436 const string16
& key_path
,
437 const string16
& value_name
) {
439 LONG result
= key
.Open(reg_root
, key_path
.c_str(), KEY_SET_VALUE
);
440 if (result
== ERROR_SUCCESS
)
441 result
= key
.DeleteValue(value_name
.c_str());
442 if (result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
) {
443 LOG(ERROR
) << "Failed to delete registry value: " << value_name
444 << " error: " << result
;
451 InstallUtil::ConditionalDeleteResult
InstallUtil::DeleteRegistryKeyIf(
453 const string16
& key_to_delete_path
,
454 const string16
& key_to_test_path
,
455 const wchar_t* value_name
,
456 const RegistryValuePredicate
& predicate
) {
458 ConditionalDeleteResult delete_result
= NOT_FOUND
;
460 string16 actual_value
;
461 if (key
.Open(root_key
, key_to_test_path
.c_str(),
462 KEY_QUERY_VALUE
) == ERROR_SUCCESS
&&
463 key
.ReadValue(value_name
, &actual_value
) == ERROR_SUCCESS
&&
464 predicate
.Evaluate(actual_value
)) {
466 delete_result
= DeleteRegistryKey(root_key
, key_to_delete_path
)
467 ? DELETED
: DELETE_FAILED
;
469 return delete_result
;
473 InstallUtil::ConditionalDeleteResult
InstallUtil::DeleteRegistryValueIf(
475 const wchar_t* key_path
,
476 const wchar_t* value_name
,
477 const RegistryValuePredicate
& predicate
) {
480 ConditionalDeleteResult delete_result
= NOT_FOUND
;
482 string16 actual_value
;
483 if (key
.Open(root_key
, key_path
,
484 KEY_QUERY_VALUE
| KEY_SET_VALUE
) == ERROR_SUCCESS
&&
485 key
.ReadValue(value_name
, &actual_value
) == ERROR_SUCCESS
&&
486 predicate
.Evaluate(actual_value
)) {
487 LONG result
= key
.DeleteValue(value_name
);
488 if (result
!= ERROR_SUCCESS
) {
489 LOG(ERROR
) << "Failed to delete registry value: "
490 << (value_name
? value_name
: L
"(Default)")
491 << " error: " << result
;
492 delete_result
= DELETE_FAILED
;
494 delete_result
= DELETED
;
496 return delete_result
;
499 bool InstallUtil::ValueEquals::Evaluate(const string16
& value
) const {
500 return value
== value_to_match_
;
504 int InstallUtil::GetInstallReturnCode(installer::InstallStatus status
) {
506 case installer::FIRST_INSTALL_SUCCESS
:
507 case installer::INSTALL_REPAIRED
:
508 case installer::NEW_VERSION_UPDATED
:
509 case installer::IN_USE_UPDATED
:
517 void InstallUtil::MakeUninstallCommand(const string16
& program
,
518 const string16
& arguments
,
519 CommandLine
* command_line
) {
520 *command_line
= CommandLine::FromString(L
"\"" + program
+ L
"\" " + arguments
);
524 string16
InstallUtil::GetCurrentDate() {
525 static const wchar_t kDateFormat
[] = L
"yyyyMMdd";
526 wchar_t date_str
[arraysize(kDateFormat
)] = {0};
527 int len
= GetDateFormatW(LOCALE_INVARIANT
, 0, NULL
, kDateFormat
,
528 date_str
, arraysize(date_str
));
530 --len
; // Subtract terminating \0.
532 PLOG(DFATAL
) << "GetDateFormat";
535 return string16(date_str
, len
);
538 // Open |path| with minimal access to obtain information about it, returning
539 // true and populating |handle| on success.
541 bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath
& path
,
542 base::win::ScopedHandle
* handle
) {
544 handle
->Set(base::CreatePlatformFile(path
, base::PLATFORM_FILE_OPEN
, NULL
,
546 return handle
->IsValid();
549 // Populate |info| for |handle|, returning true on success.
551 bool InstallUtil::ProgramCompare::GetInfo(const base::win::ScopedHandle
& handle
,
552 BY_HANDLE_FILE_INFORMATION
* info
) {
553 DCHECK(handle
.IsValid());
554 return GetFileInformationByHandle(
555 const_cast<base::win::ScopedHandle
&>(handle
), info
) != 0;
558 InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath
& path_to_match
)
559 : path_to_match_(path_to_match
),
560 file_handle_(base::kInvalidPlatformFileValue
),
562 DCHECK(!path_to_match_
.empty());
563 if (!OpenForInfo(path_to_match_
, &file_handle_
)) {
564 PLOG(WARNING
) << "Failed opening " << path_to_match_
.value()
565 << "; falling back to path string comparisons.";
566 } else if (!GetInfo(file_handle_
, &file_info_
)) {
567 PLOG(WARNING
) << "Failed getting information for "
568 << path_to_match_
.value()
569 << "; falling back to path string comparisons.";
570 file_handle_
.Close();
574 InstallUtil::ProgramCompare::~ProgramCompare() {
577 bool InstallUtil::ProgramCompare::Evaluate(const string16
& value
) const {
578 // Suss out the exe portion of the value, which is expected to be a command
579 // line kinda (or exactly) like:
580 // "c:\foo\bar\chrome.exe" -- "%1"
581 base::FilePath
program(CommandLine::FromString(value
).GetProgram());
582 if (program
.empty()) {
583 LOG(WARNING
) << "Failed to parse an executable name from command line: \""
588 return EvaluatePath(program
);
591 bool InstallUtil::ProgramCompare::EvaluatePath(
592 const base::FilePath
& path
) const {
593 // Try the simple thing first: do the paths happen to match?
594 if (base::FilePath::CompareEqualIgnoreCase(path_to_match_
.value(),
598 // If the paths don't match and we couldn't open the expected file, we've done
600 if (!file_handle_
.IsValid())
603 // Open the program and see if it references the expected file.
604 base::win::ScopedHandle handle
;
605 BY_HANDLE_FILE_INFORMATION info
= {};
607 return (OpenForInfo(path
, &handle
) &&
608 GetInfo(handle
, &info
) &&
609 info
.dwVolumeSerialNumber
== file_info_
.dwVolumeSerialNumber
&&
610 info
.nFileIndexHigh
== file_info_
.nFileIndexHigh
&&
611 info
.nFileIndexLow
== file_info_
.nFileIndexLow
);