1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // See the corresponding header file for description of the functions in this
8 #include "chrome/installer/util/install_util.h"
16 #include "base/command_line.h"
17 #include "base/file_util.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_ptr.h"
20 #include "base/path_service.h"
21 #include "base/platform_file.h"
22 #include "base/process/launch.h"
23 #include "base/strings/string_util.h"
24 #include "base/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/installer/util/browser_distribution.h"
31 #include "chrome/installer/util/google_update_constants.h"
32 #include "chrome/installer/util/helper.h"
33 #include "chrome/installer/util/installation_state.h"
34 #include "chrome/installer/util/l10n_string_util.h"
35 #include "chrome/installer/util/util_constants.h"
36 #include "chrome/installer/util/work_item_list.h"
38 using base::win::RegKey
;
39 using installer::ProductState
;
43 const wchar_t kStageBinaryPatching
[] = L
"binary_patching";
44 const wchar_t kStageBuilding
[] = L
"building";
45 const wchar_t kStageConfiguringAutoLaunch
[] = L
"configuring_auto_launch";
46 const wchar_t kStageCopyingPreferencesFile
[] = L
"copying_prefs";
47 const wchar_t kStageCreatingShortcuts
[] = L
"creating_shortcuts";
48 const wchar_t kStageEnsemblePatching
[] = L
"ensemble_patching";
49 const wchar_t kStageExecuting
[] = L
"executing";
50 const wchar_t kStageFinishing
[] = L
"finishing";
51 const wchar_t kStagePreconditions
[] = L
"preconditions";
52 const wchar_t kStageRefreshingPolicy
[] = L
"refreshing_policy";
53 const wchar_t kStageRegisteringChrome
[] = L
"registering_chrome";
54 const wchar_t kStageRemovingOldVersions
[] = L
"removing_old_ver";
55 const wchar_t kStageRollingback
[] = L
"rollingback";
56 const wchar_t kStageUncompressing
[] = L
"uncompressing";
57 const wchar_t kStageUnpacking
[] = L
"unpacking";
58 const wchar_t kStageUpdatingChannels
[] = L
"updating_channels";
59 const wchar_t kStageCreatingVisualManifest
[] = L
"creating_visual_manifest";
60 const wchar_t kStageDeferringToHigherVersion
[] = L
"deferring_to_higher_version";
61 const wchar_t kStageUninstallingBinaries
[] = L
"uninstalling_binaries";
62 const wchar_t kStageUninstallingChromeFrame
[] = L
"uninstalling_chrome_frame";
64 const wchar_t* const kStages
[] = {
68 kStageEnsemblePatching
,
74 kStageRefreshingPolicy
,
75 kStageUpdatingChannels
,
76 kStageCopyingPreferencesFile
,
77 kStageCreatingShortcuts
,
78 kStageRegisteringChrome
,
79 kStageRemovingOldVersions
,
81 kStageConfiguringAutoLaunch
,
82 kStageCreatingVisualManifest
,
83 kStageDeferringToHigherVersion
,
84 kStageUninstallingBinaries
,
85 kStageUninstallingChromeFrame
,
88 COMPILE_ASSERT(installer::NUM_STAGES
== arraysize(kStages
),
89 kStages_disagrees_with_Stage_comma_they_must_match_bang
);
91 // Creates a zero-sized non-decorated foreground window that doesn't appear
92 // in the taskbar. This is used as a parent window for calls to ShellExecuteEx
93 // in order for the UAC dialog to appear in the foreground and for focus
94 // to be returned to this process once the UAC task is dismissed. Returns
95 // NULL on failure, a handle to the UAC window on success.
96 HWND
CreateUACForegroundWindow() {
97 HWND foreground_window
= ::CreateWindowEx(WS_EX_TOOLWINDOW
,
100 WS_POPUP
| WS_VISIBLE
,
103 ::GetModuleHandle(NULL
),
105 if (foreground_window
) {
106 HMONITOR monitor
= ::MonitorFromWindow(foreground_window
,
107 MONITOR_DEFAULTTONEAREST
);
109 MONITORINFO mi
= {0};
110 mi
.cbSize
= sizeof(mi
);
111 ::GetMonitorInfo(monitor
, &mi
);
112 RECT screen_rect
= mi
.rcWork
;
113 int x_offset
= (screen_rect
.right
- screen_rect
.left
) / 2;
114 int y_offset
= (screen_rect
.bottom
- screen_rect
.top
) / 2;
115 ::MoveWindow(foreground_window
,
116 screen_rect
.left
+ x_offset
,
117 screen_rect
.top
+ y_offset
,
120 NOTREACHED() << "Unable to get default monitor";
122 ::SetForegroundWindow(foreground_window
);
124 return foreground_window
;
129 base::string16
InstallUtil::GetActiveSetupPath(BrowserDistribution
* dist
) {
130 static const wchar_t kInstalledComponentsPath
[] =
131 L
"Software\\Microsoft\\Active Setup\\Installed Components\\";
132 return kInstalledComponentsPath
+ dist
->GetActiveSetupGuid();
135 void InstallUtil::TriggerActiveSetupCommand() {
136 base::string16
active_setup_reg(
137 GetActiveSetupPath(BrowserDistribution::GetDistribution()));
138 base::win::RegKey
active_setup_key(
139 HKEY_LOCAL_MACHINE
, active_setup_reg
.c_str(), KEY_QUERY_VALUE
);
140 base::string16 cmd_str
;
141 LONG read_status
= active_setup_key
.ReadValue(L
"StubPath", &cmd_str
);
142 if (read_status
!= ERROR_SUCCESS
) {
143 LOG(ERROR
) << active_setup_reg
<< ", " << read_status
;
144 // This should never fail if Chrome is registered at system-level, but if it
145 // does there is not much else to be done.
149 CommandLine
cmd(CommandLine::FromString(cmd_str
));
150 // Force creation of shortcuts as the First Run beacon might land between now
151 // and the time setup.exe checks for it.
152 cmd
.AppendSwitch(installer::switches::kForceConfigureUserSettings
);
154 base::LaunchOptions launch_options
;
155 if (base::win::IsMetroProcess())
156 launch_options
.force_breakaway_from_job_
= true;
157 if (!base::LaunchProcess(cmd
.GetCommandLineString(), launch_options
, NULL
))
158 PLOG(ERROR
) << cmd
.GetCommandLineString();
161 bool InstallUtil::ExecuteExeAsAdmin(const CommandLine
& cmd
, DWORD
* exit_code
) {
162 base::FilePath::StringType
program(cmd
.GetProgram().value());
163 DCHECK(!program
.empty());
164 DCHECK_NE(program
[0], L
'\"');
166 CommandLine::StringType
params(cmd
.GetCommandLineString());
167 if (params
[0] == '"') {
168 DCHECK_EQ('"', params
[program
.length() + 1]);
169 DCHECK_EQ(program
, params
.substr(1, program
.length()));
170 params
= params
.substr(program
.length() + 2);
172 DCHECK_EQ(program
, params
.substr(0, program
.length()));
173 params
= params
.substr(program
.length());
176 TrimWhitespace(params
, TRIM_ALL
, ¶ms
);
178 HWND uac_foreground_window
= CreateUACForegroundWindow();
180 SHELLEXECUTEINFO info
= {0};
181 info
.cbSize
= sizeof(SHELLEXECUTEINFO
);
182 info
.fMask
= SEE_MASK_NOCLOSEPROCESS
;
183 info
.hwnd
= uac_foreground_window
;
184 info
.lpVerb
= L
"runas";
185 info
.lpFile
= program
.c_str();
186 info
.lpParameters
= params
.c_str();
187 info
.nShow
= SW_SHOW
;
189 bool success
= false;
190 if (::ShellExecuteEx(&info
) == TRUE
) {
191 ::WaitForSingleObject(info
.hProcess
, INFINITE
);
193 if (::GetExitCodeProcess(info
.hProcess
, &ret_val
)) {
196 *exit_code
= ret_val
;
200 if (uac_foreground_window
) {
201 DestroyWindow(uac_foreground_window
);
207 CommandLine
InstallUtil::GetChromeUninstallCmd(
208 bool system_install
, BrowserDistribution::Type distribution_type
) {
210 if (state
.Initialize(system_install
, distribution_type
)) {
211 return state
.uninstall_command();
213 return CommandLine(CommandLine::NO_PROGRAM
);
216 void InstallUtil::GetChromeVersion(BrowserDistribution
* dist
,
221 HKEY reg_root
= (system_install
) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
222 LONG result
= key
.Open(reg_root
, dist
->GetVersionKey().c_str(),
225 base::string16 version_str
;
226 if (result
== ERROR_SUCCESS
)
227 result
= key
.ReadValue(google_update::kRegVersionField
, &version_str
);
229 *version
= Version();
230 if (result
== ERROR_SUCCESS
&& !version_str
.empty()) {
231 VLOG(1) << "Existing " << dist
->GetDisplayName() << " version found "
233 *version
= Version(WideToASCII(version_str
));
235 DCHECK_EQ(ERROR_FILE_NOT_FOUND
, result
);
236 VLOG(1) << "No existing " << dist
->GetDisplayName()
237 << " install found.";
241 void InstallUtil::GetCriticalUpdateVersion(BrowserDistribution
* dist
,
246 HKEY reg_root
= (system_install
) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
248 key
.Open(reg_root
, dist
->GetVersionKey().c_str(), KEY_QUERY_VALUE
);
250 base::string16 version_str
;
251 if (result
== ERROR_SUCCESS
)
252 result
= key
.ReadValue(google_update::kRegCriticalVersionField
,
255 *version
= Version();
256 if (result
== ERROR_SUCCESS
&& !version_str
.empty()) {
257 VLOG(1) << "Critical Update version for " << dist
->GetDisplayName()
258 << " found " << version_str
;
259 *version
= Version(WideToASCII(version_str
));
261 DCHECK_EQ(ERROR_FILE_NOT_FOUND
, result
);
262 VLOG(1) << "No existing " << dist
->GetDisplayName()
263 << " install found.";
267 bool InstallUtil::IsOSSupported() {
268 // We do not support Win2K or older, or XP without service pack 2.
269 VLOG(1) << base::SysInfo::OperatingSystemName() << ' '
270 << base::SysInfo::OperatingSystemVersion();
271 base::win::Version version
= base::win::GetVersion();
272 return (version
> base::win::VERSION_XP
) ||
273 ((version
== base::win::VERSION_XP
) &&
274 (base::win::OSInfo::GetInstance()->service_pack().major
>= 2));
277 void InstallUtil::AddInstallerResultItems(
279 const base::string16
& state_key
,
280 installer::InstallStatus status
,
281 int string_resource_id
,
282 const base::string16
* const launch_cmd
,
283 WorkItemList
* install_list
) {
284 DCHECK(install_list
);
285 const HKEY root
= system_install
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
286 DWORD installer_result
= (GetInstallReturnCode(status
) == 0) ? 0 : 1;
287 install_list
->AddCreateRegKeyWorkItem(root
, state_key
);
288 install_list
->AddSetRegValueWorkItem(root
, state_key
,
289 installer::kInstallerResult
,
290 installer_result
, true);
291 install_list
->AddSetRegValueWorkItem(root
, state_key
,
292 installer::kInstallerError
,
293 static_cast<DWORD
>(status
), true);
294 if (string_resource_id
!= 0) {
295 base::string16 msg
= installer::GetLocalizedString(string_resource_id
);
296 install_list
->AddSetRegValueWorkItem(root
, state_key
,
297 installer::kInstallerResultUIString
, msg
, true);
299 if (launch_cmd
!= NULL
&& !launch_cmd
->empty()) {
300 install_list
->AddSetRegValueWorkItem(root
, state_key
,
301 installer::kInstallerSuccessLaunchCmdLine
, *launch_cmd
, true);
305 void InstallUtil::UpdateInstallerStage(bool system_install
,
306 const base::string16
& state_key_path
,
307 installer::InstallerStage stage
) {
308 DCHECK_LE(static_cast<installer::InstallerStage
>(0), stage
);
309 DCHECK_GT(installer::NUM_STAGES
, stage
);
310 const HKEY root
= system_install
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
312 LONG result
= state_key
.Open(root
, state_key_path
.c_str(),
313 KEY_QUERY_VALUE
| KEY_SET_VALUE
);
314 if (result
== ERROR_SUCCESS
) {
315 if (stage
== installer::NO_STAGE
) {
316 result
= state_key
.DeleteValue(installer::kInstallerExtraCode1
);
317 LOG_IF(ERROR
, result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
)
318 << "Failed deleting installer stage from " << state_key_path
319 << "; result: " << result
;
321 const DWORD extra_code_1
= static_cast<DWORD
>(stage
);
322 result
= state_key
.WriteValue(installer::kInstallerExtraCode1
,
324 LOG_IF(ERROR
, result
!= ERROR_SUCCESS
)
325 << "Failed writing installer stage to " << state_key_path
326 << "; result: " << result
;
328 // TODO(grt): Remove code below here once we're convinced that our use of
329 // Google Update's new InstallerExtraCode1 value is good.
330 installer::ChannelInfo channel_info
;
331 // This will return false if the "ap" value isn't present, which is fine.
332 channel_info
.Initialize(state_key
);
333 if (channel_info
.SetStage(kStages
[stage
]) &&
334 !channel_info
.Write(&state_key
)) {
335 LOG(ERROR
) << "Failed writing installer stage to " << state_key_path
;
338 LOG(ERROR
) << "Failed opening " << state_key_path
339 << " to update installer stage; result: " << result
;
343 bool InstallUtil::IsPerUserInstall(const wchar_t* const exe_path
) {
344 wchar_t program_files_path
[MAX_PATH
] = {0};
345 if (SUCCEEDED(SHGetFolderPath(NULL
, CSIDL_PROGRAM_FILES
, NULL
,
346 SHGFP_TYPE_CURRENT
, program_files_path
))) {
347 return !StartsWith(exe_path
, program_files_path
, false);
354 bool InstallUtil::IsMultiInstall(BrowserDistribution
* dist
,
355 bool system_install
) {
358 return state
.Initialize(system_install
, dist
->GetType()) &&
359 state
.is_multi_install();
362 bool CheckIsChromeSxSProcess() {
363 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
366 if (command_line
->HasSwitch(installer::switches::kChromeSxS
))
369 // Also return true if we are running from Chrome SxS installed path.
370 base::FilePath exe_dir
;
371 PathService::Get(base::DIR_EXE
, &exe_dir
);
372 base::string16
chrome_sxs_dir(installer::kGoogleChromeInstallSubDir2
);
373 chrome_sxs_dir
.append(installer::kSxSSuffix
);
375 // This is SxS if current EXE is in or under (possibly multiple levels under)
376 // |chrome_sxs_dir|\|installer::kInstallBinaryDir|
377 std::vector
<base::FilePath::StringType
> components
;
378 exe_dir
.GetComponents(&components
);
379 // We need at least 1 element in the array for the behavior of the following
380 // loop to be defined. This should always be true, since we're splitting the
381 // path to our executable and one of the components will be the drive letter.
382 DCHECK(!components
.empty());
383 typedef std::vector
<base::FilePath::StringType
>::const_reverse_iterator
385 for (ComponentsIterator current
= components
.rbegin(), parent
= current
+ 1;
386 parent
!= components
.rend(); current
= parent
++) {
387 if (base::FilePath::CompareEqualIgnoreCase(
388 *current
, installer::kInstallBinaryDir
) &&
389 base::FilePath::CompareEqualIgnoreCase(*parent
, chrome_sxs_dir
)) {
397 bool InstallUtil::IsChromeSxSProcess() {
398 static bool sxs
= CheckIsChromeSxSProcess();
402 bool InstallUtil::GetSentinelFilePath(const base::FilePath::CharType
* file
,
403 BrowserDistribution
* dist
,
404 base::FilePath
* path
) {
405 base::FilePath exe_path
;
406 if (!PathService::Get(base::DIR_EXE
, &exe_path
))
409 if (IsPerUserInstall(exe_path
.value().c_str())) {
410 const base::FilePath
maybe_product_dir(exe_path
.DirName().DirName());
411 if (base::PathExists(exe_path
.Append(installer::kChromeExe
))) {
412 // DIR_EXE is most likely Chrome's directory in which case |exe_path| is
413 // the user-level sentinel path.
415 } else if (base::PathExists(
416 maybe_product_dir
.Append(installer::kChromeExe
))) {
417 // DIR_EXE can also be the Installer directory if this is called from a
418 // setup.exe running from Application\<version>\Installer (see
419 // InstallerState::GetInstallerDirectory) in which case Chrome's directory
421 *path
= maybe_product_dir
;
427 std::vector
<base::FilePath
> user_data_dir_paths
;
428 installer::GetChromeUserDataPaths(dist
, &user_data_dir_paths
);
430 if (!user_data_dir_paths
.empty())
431 *path
= user_data_dir_paths
[0];
436 *path
= path
->Append(file
);
440 // This method tries to delete a registry key and logs an error message
441 // in case of failure. It returns true if deletion is successful (or the key did
442 // not exist), otherwise false.
443 bool InstallUtil::DeleteRegistryKey(HKEY root_key
,
444 const base::string16
& key_path
) {
445 VLOG(1) << "Deleting registry key " << key_path
;
446 LONG result
= ::SHDeleteKey(root_key
, key_path
.c_str());
447 if (result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
) {
448 LOG(ERROR
) << "Failed to delete registry key: " << key_path
449 << " error: " << result
;
455 // This method tries to delete a registry value and logs an error message
456 // in case of failure. It returns true if deletion is successful (or the key did
457 // not exist), otherwise false.
458 bool InstallUtil::DeleteRegistryValue(HKEY reg_root
,
459 const base::string16
& key_path
,
460 const base::string16
& value_name
) {
462 LONG result
= key
.Open(reg_root
, key_path
.c_str(), KEY_SET_VALUE
);
463 if (result
== ERROR_SUCCESS
)
464 result
= key
.DeleteValue(value_name
.c_str());
465 if (result
!= ERROR_SUCCESS
&& result
!= ERROR_FILE_NOT_FOUND
) {
466 LOG(ERROR
) << "Failed to delete registry value: " << value_name
467 << " error: " << result
;
474 InstallUtil::ConditionalDeleteResult
InstallUtil::DeleteRegistryKeyIf(
476 const base::string16
& key_to_delete_path
,
477 const base::string16
& key_to_test_path
,
478 const wchar_t* value_name
,
479 const RegistryValuePredicate
& predicate
) {
481 ConditionalDeleteResult delete_result
= NOT_FOUND
;
483 base::string16 actual_value
;
484 if (key
.Open(root_key
, key_to_test_path
.c_str(),
485 KEY_QUERY_VALUE
) == ERROR_SUCCESS
&&
486 key
.ReadValue(value_name
, &actual_value
) == ERROR_SUCCESS
&&
487 predicate
.Evaluate(actual_value
)) {
489 delete_result
= DeleteRegistryKey(root_key
, key_to_delete_path
)
490 ? DELETED
: DELETE_FAILED
;
492 return delete_result
;
496 InstallUtil::ConditionalDeleteResult
InstallUtil::DeleteRegistryValueIf(
498 const wchar_t* key_path
,
499 const wchar_t* value_name
,
500 const RegistryValuePredicate
& predicate
) {
503 ConditionalDeleteResult delete_result
= NOT_FOUND
;
505 base::string16 actual_value
;
506 if (key
.Open(root_key
, key_path
,
507 KEY_QUERY_VALUE
| KEY_SET_VALUE
) == ERROR_SUCCESS
&&
508 key
.ReadValue(value_name
, &actual_value
) == ERROR_SUCCESS
&&
509 predicate
.Evaluate(actual_value
)) {
510 LONG result
= key
.DeleteValue(value_name
);
511 if (result
!= ERROR_SUCCESS
) {
512 LOG(ERROR
) << "Failed to delete registry value: "
513 << (value_name
? value_name
: L
"(Default)")
514 << " error: " << result
;
515 delete_result
= DELETE_FAILED
;
517 delete_result
= DELETED
;
519 return delete_result
;
522 bool InstallUtil::ValueEquals::Evaluate(const base::string16
& value
) const {
523 return value
== value_to_match_
;
527 int InstallUtil::GetInstallReturnCode(installer::InstallStatus status
) {
529 case installer::FIRST_INSTALL_SUCCESS
:
530 case installer::INSTALL_REPAIRED
:
531 case installer::NEW_VERSION_UPDATED
:
532 case installer::IN_USE_UPDATED
:
533 case installer::UNUSED_BINARIES_UNINSTALLED
:
541 void InstallUtil::MakeUninstallCommand(const base::string16
& program
,
542 const base::string16
& arguments
,
543 CommandLine
* command_line
) {
544 *command_line
= CommandLine::FromString(L
"\"" + program
+ L
"\" " + arguments
);
548 base::string16
InstallUtil::GetCurrentDate() {
549 static const wchar_t kDateFormat
[] = L
"yyyyMMdd";
550 wchar_t date_str
[arraysize(kDateFormat
)] = {0};
551 int len
= GetDateFormatW(LOCALE_INVARIANT
, 0, NULL
, kDateFormat
,
552 date_str
, arraysize(date_str
));
554 --len
; // Subtract terminating \0.
556 PLOG(DFATAL
) << "GetDateFormat";
559 return base::string16(date_str
, len
);
562 // Open |path| with minimal access to obtain information about it, returning
563 // true and populating |handle| on success.
565 bool InstallUtil::ProgramCompare::OpenForInfo(const base::FilePath
& path
,
566 base::win::ScopedHandle
* handle
) {
568 handle
->Set(base::CreatePlatformFile(path
, base::PLATFORM_FILE_OPEN
, NULL
,
570 return handle
->IsValid();
573 // Populate |info| for |handle|, returning true on success.
575 bool InstallUtil::ProgramCompare::GetInfo(const base::win::ScopedHandle
& handle
,
576 BY_HANDLE_FILE_INFORMATION
* info
) {
577 DCHECK(handle
.IsValid());
578 return GetFileInformationByHandle(
579 const_cast<base::win::ScopedHandle
&>(handle
), info
) != 0;
582 InstallUtil::ProgramCompare::ProgramCompare(const base::FilePath
& path_to_match
)
583 : path_to_match_(path_to_match
),
584 file_handle_(base::kInvalidPlatformFileValue
),
586 DCHECK(!path_to_match_
.empty());
587 if (!OpenForInfo(path_to_match_
, &file_handle_
)) {
588 PLOG(WARNING
) << "Failed opening " << path_to_match_
.value()
589 << "; falling back to path string comparisons.";
590 } else if (!GetInfo(file_handle_
, &file_info_
)) {
591 PLOG(WARNING
) << "Failed getting information for "
592 << path_to_match_
.value()
593 << "; falling back to path string comparisons.";
594 file_handle_
.Close();
598 InstallUtil::ProgramCompare::~ProgramCompare() {
601 bool InstallUtil::ProgramCompare::Evaluate(const base::string16
& value
) const {
602 // Suss out the exe portion of the value, which is expected to be a command
603 // line kinda (or exactly) like:
604 // "c:\foo\bar\chrome.exe" -- "%1"
605 base::FilePath
program(CommandLine::FromString(value
).GetProgram());
606 if (program
.empty()) {
607 LOG(WARNING
) << "Failed to parse an executable name from command line: \""
612 return EvaluatePath(program
);
615 bool InstallUtil::ProgramCompare::EvaluatePath(
616 const base::FilePath
& path
) const {
617 // Try the simple thing first: do the paths happen to match?
618 if (base::FilePath::CompareEqualIgnoreCase(path_to_match_
.value(),
622 // If the paths don't match and we couldn't open the expected file, we've done
624 if (!file_handle_
.IsValid())
627 // Open the program and see if it references the expected file.
628 base::win::ScopedHandle handle
;
629 BY_HANDLE_FILE_INFORMATION info
= {};
631 return (OpenForInfo(path
, &handle
) &&
632 GetInfo(handle
, &info
) &&
633 info
.dwVolumeSerialNumber
== file_info_
.dwVolumeSerialNumber
&&
634 info
.nFileIndexHigh
== file_info_
.nFileIndexHigh
&&
635 info
.nFileIndexLow
== file_info_
.nFileIndexLow
);