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