install_util.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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/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"
36
37using base::win::RegKey;
38using installer::ProductState;
39
40namespace {
41
42const wchar_t kStageBinaryPatching[] = L"binary_patching";
43const wchar_t kStageBuilding[] = L"building";
44const wchar_t kStageConfiguringAutoLaunch[] = L"configuring_auto_launch";
45const wchar_t kStageCopyingPreferencesFile[] = L"copying_prefs";
46const wchar_t kStageCreatingShortcuts[] = L"creating_shortcuts";
47const wchar_t kStageEnsemblePatching[] = L"ensemble_patching";
48const wchar_t kStageExecuting[] = L"executing";
49const wchar_t kStageFinishing[] = L"finishing";
50const wchar_t kStagePreconditions[] = L"preconditions";
51const wchar_t kStageRefreshingPolicy[] = L"refreshing_policy";
52const wchar_t kStageRegisteringChrome[] = L"registering_chrome";
53const wchar_t kStageRemovingOldVersions[] = L"removing_old_ver";
54const wchar_t kStageRollingback[] = L"rollingback";
55const wchar_t kStageUncompressing[] = L"uncompressing";
56const wchar_t kStageUnpacking[] = L"unpacking";
57const wchar_t kStageUpdatingChannels[] = L"updating_channels";
58const wchar_t kStageCreatingVisualManifest[] = L"creating_visual_manifest";
59const wchar_t kStageDeferringToHigherVersion[] = L"deferring_to_higher_version";
60
61const wchar_t* const kStages[] = {
62  NULL,
63  kStagePreconditions,
64  kStageUncompressing,
65  kStageEnsemblePatching,
66  kStageBinaryPatching,
67  kStageUnpacking,
68  kStageBuilding,
69  kStageExecuting,
70  kStageRollingback,
71  kStageRefreshingPolicy,
72  kStageUpdatingChannels,
73  kStageCopyingPreferencesFile,
74  kStageCreatingShortcuts,
75  kStageRegisteringChrome,
76  kStageRemovingOldVersions,
77  kStageFinishing,
78  kStageConfiguringAutoLaunch,
79  kStageCreatingVisualManifest,
80  kStageDeferringToHigherVersion,
81};
82
83COMPILE_ASSERT(installer::NUM_STAGES == arraysize(kStages),
84               kStages_disagrees_with_Stage_comma_they_must_match_bang);
85
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.
91HWND CreateUACForegroundWindow() {
92  HWND foreground_window = ::CreateWindowEx(WS_EX_TOOLWINDOW,
93                                            L"STATIC",
94                                            NULL,
95                                            WS_POPUP | WS_VISIBLE,
96                                            0, 0, 0, 0,
97                                            NULL, NULL,
98                                            ::GetModuleHandle(NULL),
99                                            NULL);
100  if (foreground_window) {
101    HMONITOR monitor = ::MonitorFromWindow(foreground_window,
102                                           MONITOR_DEFAULTTONEAREST);
103    if (monitor) {
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,
113                   0, 0, FALSE);
114    } else {
115      NOTREACHED() << "Unable to get default monitor";
116    }
117    ::SetForegroundWindow(foreground_window);
118  }
119  return foreground_window;
120}
121
122}  // namespace
123
124string16 InstallUtil::GetActiveSetupPath(BrowserDistribution* dist) {
125  static const wchar_t kInstalledComponentsPath[] =
126      L"Software\\Microsoft\\Active Setup\\Installed Components\\";
127  return kInstalledComponentsPath + dist->GetActiveSetupGuid();
128}
129
130void 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);
135  string16 cmd_str;
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.
141    return;
142  }
143
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);
148
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();
154}
155
156bool 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'\"');
160
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);
166  } else {
167    DCHECK_EQ(program, params.substr(0, program.length()));
168    params = params.substr(program.length());
169  }
170
171  TrimWhitespace(params, TRIM_ALL, &params);
172
173  HWND uac_foreground_window = CreateUACForegroundWindow();
174
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;
183
184  bool success = false;
185  if (::ShellExecuteEx(&info) == TRUE) {
186    ::WaitForSingleObject(info.hProcess, INFINITE);
187    DWORD ret_val = 0;
188    if (::GetExitCodeProcess(info.hProcess, &ret_val)) {
189      success = true;
190      if (exit_code)
191        *exit_code = ret_val;
192    }
193  }
194
195  if (uac_foreground_window) {
196    DestroyWindow(uac_foreground_window);
197  }
198
199  return success;
200}
201
202CommandLine InstallUtil::GetChromeUninstallCmd(
203    bool system_install, BrowserDistribution::Type distribution_type) {
204  ProductState state;
205  if (state.Initialize(system_install, distribution_type)) {
206    return state.uninstall_command();
207  }
208  return CommandLine(CommandLine::NO_PROGRAM);
209}
210
211void InstallUtil::GetChromeVersion(BrowserDistribution* dist,
212                                   bool system_install,
213                                   Version* version) {
214  DCHECK(dist);
215  RegKey key;
216  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
217  LONG result = key.Open(reg_root, dist->GetVersionKey().c_str(),
218                         KEY_QUERY_VALUE);
219
220  string16 version_str;
221  if (result == ERROR_SUCCESS)
222    result = key.ReadValue(google_update::kRegVersionField, &version_str);
223
224  *version = Version();
225  if (result == ERROR_SUCCESS && !version_str.empty()) {
226    VLOG(1) << "Existing " << dist->GetAppShortCutName() << " version found "
227            << version_str;
228    *version = Version(WideToASCII(version_str));
229  } else {
230    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
231    VLOG(1) << "No existing " << dist->GetAppShortCutName()
232            << " install found.";
233  }
234}
235
236void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution* dist,
237                                           bool system_install,
238                                           Version* version) {
239  DCHECK(dist);
240  RegKey key;
241  HKEY reg_root = (system_install) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
242  LONG result =
243      key.Open(reg_root, dist->GetVersionKey().c_str(), KEY_QUERY_VALUE);
244
245  string16 version_str;
246  if (result == ERROR_SUCCESS)
247    result = key.ReadValue(google_update::kRegCriticalVersionField,
248                           &version_str);
249
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));
255  } else {
256    DCHECK_EQ(ERROR_FILE_NOT_FOUND, result);
257    VLOG(1) << "No existing " << dist->GetAppShortCutName()
258            << " install found.";
259  }
260}
261
262bool 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));
270}
271
272void 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);
292  }
293  if (launch_cmd != NULL && !launch_cmd->empty()) {
294    install_list->AddSetRegValueWorkItem(root, state_key,
295        installer::kInstallerSuccessLaunchCmdLine, *launch_cmd, true);
296  }
297}
298
299void 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;
305  RegKey state_key;
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;
314    } else {
315      const DWORD extra_code_1 = static_cast<DWORD>(stage);
316      result = state_key.WriteValue(installer::kInstallerExtraCode1,
317                                    extra_code_1);
318      LOG_IF(ERROR, result != ERROR_SUCCESS)
319          << "Failed writing installer stage to " << state_key_path
320          << "; result: " << result;
321    }
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;
330    }
331  } else {
332    LOG(ERROR) << "Failed opening " << state_key_path
333               << " to update installer stage; result: " << result;
334  }
335}
336
337bool 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);
342  } else {
343    NOTREACHED();
344  }
345  return true;
346}
347
348bool InstallUtil::IsMultiInstall(BrowserDistribution* dist,
349                                 bool system_install) {
350  DCHECK(dist);
351  ProductState state;
352  return state.Initialize(system_install, dist->GetType()) &&
353         state.is_multi_install();
354}
355
356bool CheckIsChromeSxSProcess() {
357  CommandLine* command_line = CommandLine::ForCurrentProcess();
358  CHECK(command_line);
359
360  if (command_line->HasSwitch(installer::switches::kChromeSxS))
361    return true;
362
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);
372}
373
374bool InstallUtil::IsChromeSxSProcess() {
375  static bool sxs = CheckIsChromeSxSProcess();
376  return sxs;
377}
378
379bool 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))
384    return false;
385
386  if (IsPerUserInstall(exe_path.value().c_str())) {
387    const base::FilePath maybe_product_dir(exe_path.DirName().DirName());
388    if (file_util::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.
391      *path = exe_path;
392    } else if (file_util::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
397      // is two levels up.
398      *path = maybe_product_dir;
399    } else {
400      NOTREACHED();
401      return false;
402    }
403  } else {
404    std::vector<base::FilePath> user_data_dir_paths;
405    installer::GetChromeUserDataPaths(dist, &user_data_dir_paths);
406
407    if (!user_data_dir_paths.empty())
408      *path = user_data_dir_paths[0];
409    else
410      return false;
411  }
412
413  *path = path->Append(file);
414  return true;
415}
416
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.
420bool 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;
427    return false;
428  }
429  return true;
430}
431
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.
435bool InstallUtil::DeleteRegistryValue(HKEY reg_root,
436                                      const string16& key_path,
437                                      const string16& value_name) {
438  RegKey key;
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;
445    return false;
446  }
447  return true;
448}
449
450// static
451InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryKeyIf(
452    HKEY root_key,
453    const string16& key_to_delete_path,
454    const string16& key_to_test_path,
455    const wchar_t* value_name,
456    const RegistryValuePredicate& predicate) {
457  DCHECK(root_key);
458  ConditionalDeleteResult delete_result = NOT_FOUND;
459  RegKey key;
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)) {
465    key.Close();
466    delete_result = DeleteRegistryKey(root_key, key_to_delete_path)
467        ? DELETED : DELETE_FAILED;
468  }
469  return delete_result;
470}
471
472// static
473InstallUtil::ConditionalDeleteResult InstallUtil::DeleteRegistryValueIf(
474    HKEY root_key,
475    const wchar_t* key_path,
476    const wchar_t* value_name,
477    const RegistryValuePredicate& predicate) {
478  DCHECK(root_key);
479  DCHECK(key_path);
480  ConditionalDeleteResult delete_result = NOT_FOUND;
481  RegKey key;
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;
493    }
494    delete_result = DELETED;
495  }
496  return delete_result;
497}
498
499bool InstallUtil::ValueEquals::Evaluate(const string16& value) const {
500  return value == value_to_match_;
501}
502
503// static
504int InstallUtil::GetInstallReturnCode(installer::InstallStatus status) {
505  switch (status) {
506    case installer::FIRST_INSTALL_SUCCESS:
507    case installer::INSTALL_REPAIRED:
508    case installer::NEW_VERSION_UPDATED:
509    case installer::IN_USE_UPDATED:
510      return 0;
511    default:
512      return status;
513  }
514}
515
516// static
517void InstallUtil::MakeUninstallCommand(const string16& program,
518                                       const string16& arguments,
519                                       CommandLine* command_line) {
520  *command_line = CommandLine::FromString(L"\"" + program + L"\" " + arguments);
521}
522
523// static
524string16 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));
529  if (len) {
530    --len;  // Subtract terminating \0.
531  } else {
532    PLOG(DFATAL) << "GetDateFormat";
533  }
534
535  return string16(date_str, len);
536}
537
538// Open |path| with minimal access to obtain information about it, returning
539// true and populating |handle| on success.
540// static
541bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath& path,
542                                              base::win::ScopedHandle* handle) {
543  DCHECK(handle);
544  handle->Set(base::CreatePlatformFile(path, base::PLATFORM_FILE_OPEN, NULL,
545                                       NULL));
546  return handle->IsValid();
547}
548
549// Populate |info| for |handle|, returning true on success.
550// static
551bool 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;
556}
557
558InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath& path_to_match)
559    : path_to_match_(path_to_match),
560      file_handle_(base::kInvalidPlatformFileValue),
561      file_info_() {
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();
571  }
572}
573
574InstallUtil::ProgramCompare::~ProgramCompare() {
575}
576
577bool 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: \""
584                 << value << "\"";
585    return false;
586  }
587
588  return EvaluatePath(program);
589}
590
591bool 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(),
595                                             path.value()))
596    return true;
597
598  // If the paths don't match and we couldn't open the expected file, we've done
599  // our best.
600  if (!file_handle_.IsValid())
601    return false;
602
603  // Open the program and see if it references the expected file.
604  base::win::ScopedHandle handle;
605  BY_HANDLE_FILE_INFORMATION info = {};
606
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);
612}
613