install_util.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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.
4//
5// See the corresponding header file for description of the functions in this
6// file.
7
8#include "chrome/installer/util/install_util.h"
9
10#include <shellapi.h>
11#include <shlobj.h>
12#include <shlwapi.h>
13
14#include <algorithm>
15
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_util.h"
22#include "base/string_util.h"
23#include "base/sys_info.h"
24#include "base/values.h"
25#include "base/version.h"
26#include "base/win/registry.h"
27#include "base/win/windows_version.h"
28#include "chrome/installer/util/browser_distribution.h"
29#include "chrome/installer/util/google_update_constants.h"
30#include "chrome/installer/util/helper.h"
31#include "chrome/installer/util/installation_state.h"
32#include "chrome/installer/util/l10n_string_util.h"
33#include "chrome/installer/util/util_constants.h"
34#include "chrome/installer/util/work_item_list.h"
35
36using base::win::RegKey;
37using installer::ProductState;
38
39namespace {
40
41const wchar_t kStageBinaryPatching[] = L"binary_patching";
42const wchar_t kStageBuilding[] = L"building";
43const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch";
44const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs";
45const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts";
46const wchar_t kStageEnsemblePatching[] = L"ensemble_patching";
47const wchar_t kStageExecuting[] = L"executing";
48const wchar_t kStageFinishing[] = L"finishing";
49const wchar_t kStagePreconditions[] = L"preconditions";
50const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy";
51const wchar_t kStageRegisteringChrome[] = L"registering_chrome";
52const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver";
53const wchar_t kStageRollingback[] = L"rollingback";
54const wchar_t kStageUncompressing[] = L"uncompressing";
55const wchar_t kStageUnpacking[] = L"unpacking";
56const wchar_t kStageUpdatingChannels[] = L"updating_channels";
57const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest";
58const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version";
59
60const wchar_t* const kStages[] = {
61  NULL,
62  kStagePreconditions,
63  kStageUncompressing,
64  kStageEnsemblePatching,
65  kStageBinaryPatching,
66  kStageUnpacking,
67  kStageBuilding,
68  kStageExecuting,
69  kStageRollingback,
70  kStageRefreshingPolicy,
71  kStageUpdatingChannels,
72  kStageCopyingPreferencesFile,
73  kStageCreatingShortcuts,
74  kStageRegisteringChrome,
75  kStageRemovingOldVersions,
76  kStageFinishing,
77  kStageConfiguringAutoLaunch,
78  kStageCreatingVisualManifest,
79  kStageDeferringToHigherVersion,
80};
81
82COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages),
83               kStages_disagrees_with_Stage_comma_they_must_match_bang);
84
85// Creates a zero-sized non-decorated foreground window that doesn't appear
86// in the taskbar. This is used as a parent window for calls to ShellExecuteEx
87// in order for the UAC dialog to appear in the foreground and for focus
88// to be returned to this process once the UAC task is dismissed. Returns
89// NULL on failure, a handle to the UAC window on success.
90HWND CreateUACForegroundWindow() {
91  HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW,
92                                            L"STATIC",
93                                            NULL,
94                                            WS_POPUP | WS_VISIBLE,
95                                            0, 0, 0, 0,
96                                            NULL, NULL,
97                                            ::GetModuleHandle(NULL),
98                                            NULL);
99  if (foreground_window) {
100    HMONITOR monitor = ::MonitorFromWindow(foreground_window,
101                                           MONITOR_DEFAULTTONEAREST);
102    if (monitor) {
103      MONITORINFO mi = {0};
104      mi.cbSize = sizeof(mi);
105      ::GetMonitorInfo(monitor, &mi);
106      RECT screen_rect = mi.rcWork;
107      int x_offset = (screen_rect.right - screen_rect.left) / 2;
108      int y_offset = (screen_rect.bottom - screen_rect.top) / 2;
109      ::MoveWindow(foreground_window,
110                   screen_rect.left + x_offset,
111                   screen_rect.top + y_offset,
112                   0, 0, FALSE);
113    } else {
114      NOTREACHED() << "Unable to get default monitor";
115    }
116    ::SetForegroundWindow(foreground_window);
117  }
118  return foreground_window;
119}
120
121}  // namespace
122
123string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) {
124  static const wchar_t kInstalledComponentsPath[] =
125      L"Software\\Microsoft\\Active Setup\\Installed Components\\";
126  return kInstalledComponentsPath + dist->GetAppGuid();
127}
128
129void InstallUtil::TriggerActiveSetupCommandIfNeeded() {
130  FilePath chrome_exe;
131  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
132    NOTREACHED();
133  } else if (InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())) {
134    return;
135  }
136
137  string16 active_setup_reg(
138      GetActiveSetupPath(BrowserDistribution::GetDistribution()));
139  base::win::RegKey active_setup_key(
140      HKEY_LOCAL_MACHINE, active_setup_reg.c_str(), KEY_QUERY_VALUE);
141  string16 cmd_str;
142  LONG read_status = active_setup_key.ReadValue(L"StubPath", &cmd_str);
143  if (read_status != ERROR_SUCCESS) {
144    LOG(ERROR) << active_setup_reg << ", " << read_status;
145    // This should never fail if Chrome is registered at system-level, but if it
146    // does there is not much else to be done.
147    return;
148  }
149
150  CommandLine cmd(CommandLine::FromString(cmd_str));
151  // Force creation of shortcuts as the First Run beacon might land between now
152  // and the time setup.exe checks for it.
153  cmd.AppendSwitch(installer::switches::kForceConfigureUserSettings);
154
155  base::LaunchOptions default_options;
156  if (!base::LaunchProcess(cmd.GetCommandLineString(), default_options, NULL))
157    PLOG(ERROR) << cmd.GetCommandLineString();
158}
159
160bool InstallUtil::ExecuteExeAsAdmin(const CommandLine& cmd, DWORD* exit_code) {
161  FilePath::StringType program(cmd.GetProgram().value());
162  DCHECK(!program.empty());
163  DCHECK_NE(program[0], L'\"');
164
165  CommandLine::StringType params(cmd.GetCommandLineString());
166  if (params[0] == '"') {
167    DCHECK_EQ('"', params[program.length() + 1]);
168    DCHECK_EQ(program, params.substr(1, program.length()));
169    params = params.substr(program.length() + 2);
170  } else {
171    DCHECK_EQ(program, params.substr(0, program.length()));
172    params = params.substr(program.length());
173  }
174
175  TrimWhitespace(params, TRIM_ALL, &params);
176
177  HWND uac_foreground_window = CreateUACForegroundWindow();
178
179  SHELLEXECUTEINFO info = {0};
180  info.cbSize = sizeof(SHELLEXECUTEINFO);
181  info.fMask = SEE_MASK_NOCLOSEPROCESS;
182  info.hwnd = uac_foreground_window;
183  info.lpVerb = L"runas";
184  info.lpFile = program.c_str();
185  info.lpParameters = params.c_str();
186  info.nShow = SW_SHOW;
187
188  bool success = false;
189  if (::ShellExecuteEx(&info) == TRUE) {
190    ::WaitForSingleObject(info.hProcess, INFINITE);
191    DWORD ret_val = 0;
192    if (::GetExitCodeProcess(info.hProcess, &ret_val)) {
193      success = true;
194      if (exit_code)
195        *exit_code = ret_val;
196    }
197  }
198
199  if (uac_foreground_window) {
200    DestroyWindow(uac_foreground_window);
201  }
202
203  return success;
204}
205
206CommandLine InstallUtil::GetChromeUninstallCmd(
207    bool system_install, BrowserDistribution::Type distribution_type) {
208  ProductState state;
209  if (state.Initialize(system_install, distribution_type)) {
210    return state.uninstall_command();
211  }
212  return CommandLine(CommandLine::NO_PROGRAM);
213}
214
215void InstallUtil::GetChromeVersion(BrowserDistribution* dist,
216                                   bool system_install,
217                                   Version* version) {
218  DCHECK(dist);
219  RegKey key;
220  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
221  LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(),
222                         KEY_QUERY_VALUE);
223
224  string16 version_str;
225  if (result == ERROR_SUCCESS)
226    result = key.ReadValue(google_update::kRegVersionField, &version_str);
227
228  *version = Version();
229  if (result == ERROR_SUCCESS && !version_str.empty()) {
230    VLOG(1) << "Existing " << dist->GetAppShortCutName() << " version found "
231            << version_str;
232    *version = Version(WideToASCII(version_str));
233  } else {
234    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
235    VLOG(1) << "No existing " << dist->GetAppShortCutName()
236            << " install found.";
237  }
238}
239
240void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist,
241                                           bool system_install,
242                                           Version* version) {
243  DCHECK(dist);
244  RegKey key;
245  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
246  LONG result =
247      key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE);
248
249  string16 version_str;
250  if (result == ERROR_SUCCESS)
251    result = key.ReadValue(google_update::kRegCriticalVersionField,
252                           &version_str);
253
254  *version = Version();
255  if (result == ERROR_SUCCESS && !version_str.empty()) {
256    VLOG(1) << "Critical Update version for " << dist->GetAppShortCutName()
257            << " found " << version_str;
258    *version = Version(WideToASCII(version_str));
259  } else {
260    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
261    VLOG(1) << "No existing " << dist->GetAppShortCutName()
262            << " install found.";
263  }
264}
265
266bool InstallUtil::IsOSSupported() {
267  // We do not support Win2K or older, or XP without service pack 2.
268  VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
269          << base::SysInfo::OperatingSystemVersion();
270  base::win::Version version = base::win::GetVersion();
271  return (version > base::win::VERSION_XP) ||
272      ((version == base::win::VERSION_XP) &&
273       (base::win::OSInfo::GetInstance()->service_pack().major >= 2));
274}
275
276void InstallUtil::AddInstallerResultItems(bool system_install,
277                                          const string16& state_key,
278                                          installer::InstallStatus status,
279                                          int string_resource_id,
280                                          const string16* const launch_cmd,
281                                          WorkItemList* install_list) {
282  DCHECK(install_list);
283  const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
284  DWORD installer_result = (GetInstallReturnCode(status) == 0) ? 0 : 1;
285  install_list->AddCreateRegKeyWorkItem(root, state_key);
286  install_list->AddSetRegValueWorkItem(root, state_key,
287                                       installer::kInstallerResult,
288                                       installer_result, true);
289  install_list->AddSetRegValueWorkItem(root, state_key,
290                                       installer::kInstallerError,
291                                       static_cast<DWORD>(status), true);
292  if (string_resource_id != 0) {
293    string16 msg = installer::GetLocalizedString(string_resource_id);
294    install_list->AddSetRegValueWorkItem(root, state_key,
295        installer::kInstallerResultUIString, msg, true);
296  }
297  if (launch_cmd != NULL && !launch_cmd->empty()) {
298    install_list->AddSetRegValueWorkItem(root, state_key,
299        installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true);
300  }
301}
302
303void InstallUtil::UpdateInstallerStage(bool system_install,
304                                       const string16& state_key_path,
305                                       installer::InstallerStage stage) {
306  DCHECK_LE(static_cast<installer::InstallerStage>(0), stage);
307  DCHECK_GT(installer::NUM_STAGES, stage);
308  const HKEY root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
309  RegKey state_key;
310  LONG result = state_key.Open(root, state_key_path.c_str(),
311                               KEY_QUERY_VALUE | KEY_SET_VALUE);
312  if (result == ERROR_SUCCESS) {
313    if (stage == installer::NO_STAGE) {
314      result = state_key.DeleteValue(installer::kInstallerExtraCode1);
315      LOG_IF(ERROR, result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND)
316          << "Failed deleting installer stage from " << state_key_path
317          << "; result: " << result;
318    } else {
319      const DWORD extra_code_1 = static_cast<DWORD>(stage);
320      result = state_key.WriteValue(installer::kInstallerExtraCode1,
321                                    extra_code_1);
322      LOG_IF(ERROR, result != ERROR_SUCCESS)
323          << "Failed writing installer stage to " << state_key_path
324          << "; result: " << result;
325    }
326    // TODO(grt): Remove code below here once we're convinced that our use of
327    // Google Update's new InstallerExtraCode1 value is good.
328    installer::ChannelInfo channel_info;
329    // This will return false if the "ap" value isn't present, which is fine.
330    channel_info.Initialize(state_key);
331    if (channel_info.SetStage(kStages[stage]) &&
332        !channel_info.Write(&state_key)) {
333      LOG(ERROR) << "Failed writing installer stage to " << state_key_path;
334    }
335  } else {
336    LOG(ERROR) << "Failed opening " << state_key_path
337               << " to update installer stage; result: " << result;
338  }
339}
340
341bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path) {
342  wchar_t program_files_path[MAX_PATH] = {0};
343  if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL,
344                                SHGFP_TYPE_CURRENT, program_files_path))) {
345    return !StartsWith(exe_path, program_files_path, false);
346  } else {
347    NOTREACHED();
348  }
349  return true;
350}
351
352bool InstallUtil::IsMultiInstall(BrowserDistribution* dist,
353                                 bool system_install) {
354  DCHECK(dist);
355  ProductState state;
356  return state.Initialize(system_install, dist->GetType()) &&
357         state.is_multi_install();
358}
359
360bool CheckIsChromeSxSProcess() {
361  CommandLine* command_line = CommandLine::ForCurrentProcess();
362  CHECK(command_line);
363
364  if (command_line->HasSwitch(installer::switches::kChromeSxS))
365    return true;
366
367  // Also return true if we are running from Chrome SxS installed path.
368  FilePath exe_dir;
369  PathService::Get(base::DIR_EXE, &exe_dir);
370  string16 chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2);
371  chrome_sxs_dir.append(installer::kSxSSuffix);
372  return FilePath::CompareEqualIgnoreCase(exe_dir.BaseName().value(),
373                                          installer::kInstallBinaryDir) &&
374         FilePath::CompareEqualIgnoreCase(exe_dir.DirName().BaseName().value(),
375                                          chrome_sxs_dir);
376}
377
378bool InstallUtil::IsChromeSxSProcess() {
379  static bool sxs = CheckIsChromeSxSProcess();
380  return sxs;
381}
382
383bool InstallUtil::GetSentinelFilePath(const FilePath::CharType* file,
384                                      BrowserDistribution* dist,
385                                      FilePath* path) {
386  FilePath exe_path;
387  if (!PathService::Get(base::DIR_EXE, &exe_path))
388    return false;
389
390  if (IsPerUserInstall(exe_path.value().c_str())) {
391    *path = exe_path;
392  } else {
393    std::vector<FilePath> user_data_dir_paths;
394    installer::GetChromeUserDataPaths(dist, &user_data_dir_paths);
395
396    if (!user_data_dir_paths.empty())
397      *path = user_data_dir_paths[0];
398    else
399      return false;
400  }
401
402  *path = path->Append(file);
403  return true;
404}
405
406// This method tries to delete a registry key and logs an error message
407// in case of failure. It returns true if deletion is successful (or the key did
408// not exist), otherwise false.
409bool InstallUtil::DeleteRegistryKey(HKEY root_key,
410                                    const string16& key_path) {
411  VLOG(1) << "Deleting registry key " << key_path;
412  LONG result = ::SHDeleteKey(root_key, key_path.c_str());
413  if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
414    LOG(ERROR) << "Failed to delete registry key: " << key_path
415               << " error: " << result;
416    return false;
417  }
418  return true;
419}
420
421// This method tries to delete a registry value 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.
424bool InstallUtil::DeleteRegistryValue(HKEY reg_root,
425                                      const string16& key_path,
426                                      const string16& value_name) {
427  RegKey key;
428  LONG result = key.Open(reg_root, key_path.c_str(), KEY_SET_VALUE);
429  if (result == ERROR_SUCCESS)
430    result = key.DeleteValue(value_name.c_str());
431  if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) {
432    LOG(ERROR) << "Failed to delete registry value: " << value_name
433               << " error: " << result;
434    return false;
435  }
436  return true;
437}
438
439// static
440InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf(
441    HKEY root_key,
442    const string16& key_to_delete_path,
443    const string16& key_to_test_path,
444    const wchar_t* value_name,
445    const RegistryValuePredicate& predicate) {
446  DCHECK(root_key);
447  ConditionalDeleteResult delete_result = NOT_FOUND;
448  RegKey key;
449  string16 actual_value;
450  if (key.Open(root_key, key_to_test_path.c_str(),
451               KEY_QUERY_VALUE) == ERROR_SUCCESS &&
452      key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
453      predicate.Evaluate(actual_value)) {
454    key.Close();
455    delete_result = DeleteRegistryKey(root_key, key_to_delete_path)
456        ? DELETED : DELETE_FAILED;
457  }
458  return delete_result;
459}
460
461// static
462InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf(
463    HKEY root_key,
464    const wchar_t* key_path,
465    const wchar_t* value_name,
466    const RegistryValuePredicate& predicate) {
467  DCHECK(root_key);
468  DCHECK(key_path);
469  ConditionalDeleteResult delete_result = NOT_FOUND;
470  RegKey key;
471  string16 actual_value;
472  if (key.Open(root_key, key_path,
473               KEY_QUERY_VALUE | KEY_SET_VALUE) == ERROR_SUCCESS &&
474      key.ReadValue(value_name, &actual_value) == ERROR_SUCCESS &&
475      predicate.Evaluate(actual_value)) {
476    LONG result = key.DeleteValue(value_name);
477    if (result != ERROR_SUCCESS) {
478      LOG(ERROR) << "Failed to delete registry value: "
479                 << (value_name ? value_name : L"(Default)")
480                 << " error: " << result;
481      delete_result = DELETE_FAILED;
482    }
483    delete_result = DELETED;
484  }
485  return delete_result;
486}
487
488bool InstallUtil::ValueEquals::Evaluate(const string16& value) const {
489  return value == value_to_match_;
490}
491
492// static
493int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) {
494  switch (status) {
495    case installer::FIRST_INSTALL_SUCCESS:
496    case installer::INSTALL_REPAIRED:
497    case installer::NEW_VERSION_UPDATED:
498    case installer::IN_USE_UPDATED:
499      return 0;
500    default:
501      return status;
502  }
503}
504
505// static
506void InstallUtil::MakeUninstallCommand(const string16& program,
507                                       const string16& arguments,
508                                       CommandLine* command_line) {
509  *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments);
510}
511
512string16 InstallUtil::GetCurrentDate() {
513  static const wchar_t kDateFormat[] = L"yyyyMMdd";
514  wchar_t date_str[arraysize(kDateFormat)] = {0};
515  int len = GetDateFormatW(LOCALE_INVARIANT, 0, NULL, kDateFormat,
516                           date_str, arraysize(date_str));
517  if (len) {
518    --len;  // Subtract terminating \0.
519  } else {
520    PLOG(DFATAL) << "GetDateFormat";
521  }
522
523  return string16(date_str, len);
524}
525
526// Open |path| with minimal access to obtain information about it, returning
527// true and populating |handle| on success.
528// static
529bool InstallUtil::ProgramCompare::OpenForInfo(const FilePath& path,
530                                              base::win::ScopedHandle* handle) {
531  DCHECK(handle);
532  handle->Set(base::CreatePlatformFile(path, base::PLATFORM_FILE_OPEN, NULL,
533                                       NULL));
534  return handle->IsValid();
535}
536
537// Populate |info| for |handle|, returning true on success.
538// static
539bool InstallUtil::ProgramCompare::GetInfo(const base::win::ScopedHandle& handle,
540                                          BY_HANDLE_FILE_INFORMATION* info) {
541  DCHECK(handle.IsValid());
542  return GetFileInformationByHandle(
543      const_cast<base::win::ScopedHandle&>(handle), info) != 0;
544}
545
546InstallUtil::ProgramCompare::ProgramCompare(const FilePath& path_to_match)
547    : path_to_match_(path_to_match),
548      file_handle_(base::kInvalidPlatformFileValue),
549      file_info_() {
550  DCHECK(!path_to_match_.empty());
551  if (!OpenForInfo(path_to_match_, &file_handle_)) {
552    PLOG(WARNING) << "Failed opening " << path_to_match_.value()
553                  << "; falling back to path string comparisons.";
554  } else if (!GetInfo(file_handle_, &file_info_)) {
555    PLOG(WARNING) << "Failed getting information for "
556                  << path_to_match_.value()
557                  << "; falling back to path string comparisons.";
558    file_handle_.Close();
559  }
560}
561
562InstallUtil::ProgramCompare::~ProgramCompare() {
563}
564
565bool InstallUtil::ProgramCompare::Evaluate(const string16& value) const {
566  // Suss out the exe portion of the value, which is expected to be a command
567  // line kinda (or exactly) like:
568  // "c:\foo\bar\chrome.exe" -- "%1"
569  FilePath program(CommandLine::FromString(value).GetProgram());
570  if (program.empty()) {
571    LOG(WARNING) << "Failed to parse an executable name from command line: \""
572                 << value << "\"";
573    return false;
574  }
575
576  // Try the simple thing first: do the paths happen to match?
577  if (FilePath::CompareEqualIgnoreCase(path_to_match_.value(), program.value()))
578    return true;
579
580  // If the paths don't match and we couldn't open the expected file, we've done
581  // our best.
582  if (!file_handle_.IsValid())
583    return false;
584
585  // Open the program and see if it references the expected file.
586  base::win::ScopedHandle handle;
587  BY_HANDLE_FILE_INFORMATION info = {};
588
589  return (OpenForInfo(program, &handle) &&
590          GetInfo(handle, &info) &&
591          info.dwVolumeSerialNumber == file_info_.dwVolumeSerialNumber &&
592          info.nFileIndexHigh == file_info_.nFileIndexHigh &&
593          info.nFileIndexLow == file_info_.nFileIndexLow);
594}
595