| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string> |
| #include <windows.h> |
| #include <msi.h> |
| #include <shellapi.h> |
| #include <shlobj.h> |
| |
| #include "base/at_exit.h" |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/path_service.h" |
| #include "base/process_util.h" |
| #include "base/scoped_handle_win.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/win_util.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/installer/setup/install.h" |
| #include "chrome/installer/setup/setup_constants.h" |
| #include "chrome/installer/setup/setup_util.h" |
| #include "chrome/installer/setup/uninstall.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "chrome/installer/util/delete_after_reboot_helper.h" |
| #include "chrome/installer/util/delete_tree_work_item.h" |
| #include "chrome/installer/util/helper.h" |
| #include "chrome/installer/util/html_dialog.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/l10n_string_util.h" |
| #include "chrome/installer/util/logging_installer.h" |
| #include "chrome/installer/util/lzma_util.h" |
| #include "chrome/installer/util/google_update_settings.h" |
| #include "chrome/installer/util/google_update_constants.h" |
| #include "chrome/installer/util/master_preferences.h" |
| #include "chrome/installer/util/shell_util.h" |
| #include "chrome/installer/util/util_constants.h" |
| |
| #include "installer_util_strings.h" |
| |
| namespace { |
| |
| // This method unpacks and uncompresses the given archive file. For Chrome |
| // install we are creating a uncompressed archive that contains all the files |
| // needed for the installer. This uncompressed archive is later compressed. |
| // |
| // This method first uncompresses archive specified by parameter "archive" |
| // and assumes that it will result in an uncompressed full archive file |
| // (chrome.7z) or uncompressed archive patch file (chrome_patch.diff). If it |
| // is patch file, it is applied to the old archive file that should be |
| // present on the system already. As the final step the new archive file |
| // is unpacked in the path specified by parameter "path". |
| DWORD UnPackArchive(const std::wstring& archive, bool system_install, |
| const installer::Version* installed_version, |
| const std::wstring& temp_path, const std::wstring& path, |
| bool& incremental_install) { |
| // First uncompress the payload. This could be a differential |
| // update (patch.7z) or full archive (chrome.7z). If this uncompress fails |
| // return with error. |
| std::wstring unpacked_file; |
| int32 ret = LzmaUtil::UnPackArchive(archive, temp_path, &unpacked_file); |
| if (ret != NO_ERROR) |
| return ret; |
| |
| std::wstring uncompressed_archive(temp_path); |
| file_util::AppendToPath(&uncompressed_archive, installer::kChromeArchive); |
| |
| // Check if this is differential update and if it is, patch it to the |
| // installer archive that should already be on the machine. We assume |
| // it is a differential installer if chrome.7z is not found. |
| if (!file_util::PathExists(FilePath::FromWStringHack(uncompressed_archive))) { |
| incremental_install = true; |
| LOG(INFO) << "Differential patch found. Applying to existing archive."; |
| if (!installed_version) { |
| LOG(ERROR) << "Can not use differential update when Chrome is not " |
| << "installed on the system."; |
| return installer_util::CHROME_NOT_INSTALLED; |
| } |
| std::wstring existing_archive = |
| installer::GetChromeInstallPath(system_install); |
| file_util::AppendToPath(&existing_archive, |
| installed_version->GetString()); |
| file_util::AppendToPath(&existing_archive, installer_util::kInstallerDir); |
| file_util::AppendToPath(&existing_archive, installer::kChromeArchive); |
| if (int i = setup_util::ApplyDiffPatch(existing_archive, unpacked_file, |
| uncompressed_archive)) { |
| LOG(ERROR) << "Binary patching failed with error " << i; |
| return i; |
| } |
| } |
| |
| // Unpack the uncompressed archive. |
| return LzmaUtil::UnPackArchive(uncompressed_archive, path, &unpacked_file); |
| } |
| |
| |
| // This function is called when --rename-chrome-exe option is specified on |
| // setup.exe command line. This function assumes an in-use update has happened |
| // for Chrome so there should be a file called new_chrome.exe on the file |
| // system and a key called 'opv' in the registry. This function will move |
| // new_chrome.exe to chrome.exe and delete 'opv' key in one atomic operation. |
| installer_util::InstallStatus RenameChromeExecutables(bool system_install) { |
| std::wstring chrome_path(installer::GetChromeInstallPath(system_install)); |
| |
| std::wstring chrome_exe(chrome_path); |
| file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); |
| std::wstring chrome_old_exe(chrome_path); |
| file_util::AppendToPath(&chrome_old_exe, installer_util::kChromeOldExe); |
| std::wstring chrome_new_exe(chrome_path); |
| file_util::AppendToPath(&chrome_new_exe, installer_util::kChromeNewExe); |
| |
| scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList()); |
| install_list->AddDeleteTreeWorkItem(chrome_old_exe, std::wstring()); |
| FilePath temp_path; |
| if (!file_util::CreateNewTempDirectory(L"chrome_", &temp_path)) { |
| LOG(ERROR) << "Failed to create Temp directory " << temp_path.value(); |
| return installer_util::RENAME_FAILED; |
| } |
| install_list->AddCopyTreeWorkItem(chrome_new_exe, |
| chrome_exe, |
| temp_path.ToWStringHack(), |
| WorkItem::IF_DIFFERENT, |
| std::wstring()); |
| HKEY reg_root = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
| BrowserDistribution *dist = BrowserDistribution::GetDistribution(); |
| install_list->AddDeleteRegValueWorkItem(reg_root, |
| dist->GetVersionKey(), |
| google_update::kRegOldVersionField, |
| true); |
| install_list->AddDeleteTreeWorkItem(chrome_new_exe, std::wstring()); |
| install_list->AddDeleteRegValueWorkItem(reg_root, |
| dist->GetVersionKey(), |
| google_update::kRegRenameCmdField, |
| true); |
| installer_util::InstallStatus ret = installer_util::RENAME_SUCCESSFUL; |
| if (!install_list->Do()) { |
| LOG(ERROR) << "Renaming of executables failed. Rolling back any changes."; |
| install_list->Rollback(); |
| ret = installer_util::RENAME_FAILED; |
| } |
| file_util::Delete(temp_path, true); |
| return ret; |
| } |
| |
| bool CheckPreInstallConditions(const installer::Version* installed_version, |
| bool system_install, |
| installer_util::InstallStatus& status) { |
| bool is_first_install = (NULL == installed_version); |
| // Check to avoid simultaneous per-user and per-machine installs. |
| scoped_ptr<installer::Version> |
| chrome_version(InstallUtil::GetChromeVersion(!system_install)); |
| if (chrome_version.get()) { |
| LOG(ERROR) << "Already installed version " << chrome_version->GetString() |
| << " conflicts with the current install mode."; |
| if (!system_install && is_first_install) { |
| // This is user-level install and there is a system-level chrome |
| // installation. Instruct omaha to launch the existing one. There |
| // should be no error dialog. |
| std::wstring chrome_exe(installer::GetChromeInstallPath(!system_install)); |
| if (chrome_exe.empty()) { |
| // If we failed to construct install path. Give up. |
| status = installer_util::OS_ERROR; |
| InstallUtil::WriteInstallerResult(system_install, status, |
| IDS_INSTALL_OS_ERROR_BASE, NULL); |
| return false; |
| } else { |
| status = installer_util::EXISTING_VERSION_LAUNCHED; |
| file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); |
| chrome_exe = L"\"" + chrome_exe + L"\" --" |
| + ASCIIToWide(switches::kFirstRun); |
| InstallUtil::WriteInstallerResult(system_install, status, |
| 0, NULL); |
| LOG(INFO) << "Launching existing system-level chrome instead."; |
| base::LaunchApp(chrome_exe, false, false, NULL); |
| return false; |
| } |
| } |
| // If the following compile assert fires it means that the InstallStatus |
| // enumeration changed which will break the contract between the old chrome |
| // installed and the new setup.exe that is trying to upgrade. |
| COMPILE_ASSERT(installer_util::SXS_OPTION_NOT_SUPPORTED == 33, |
| dont_change_enum); |
| |
| // This is an update, not an install. Omaha should know the difference |
| // and not show a dialog. |
| status = system_install ? installer_util::USER_LEVEL_INSTALL_EXISTS : |
| installer_util::SYSTEM_LEVEL_INSTALL_EXISTS; |
| int str_id = system_install ? IDS_INSTALL_USER_LEVEL_EXISTS_BASE : |
| IDS_INSTALL_SYSTEM_LEVEL_EXISTS_BASE; |
| InstallUtil::WriteInstallerResult(system_install, status, str_id, NULL); |
| return false; |
| } |
| // If no previous installation of Chrome, make sure installation directory |
| // either does not exist or can be deleted (i.e. is not locked by some other |
| // process). |
| if (is_first_install) { |
| FilePath install_path = FilePath::FromWStringHack( |
| installer::GetChromeInstallPath(system_install)); |
| if (file_util::PathExists(install_path) && |
| !file_util::Delete(install_path, true)) { |
| LOG(ERROR) << "Installation directory " << install_path.value() |
| << " exists and can not be deleted."; |
| status = installer_util::INSTALL_DIR_IN_USE; |
| int str_id = IDS_INSTALL_DIR_IN_USE_BASE; |
| InstallUtil::WriteInstallerResult(system_install, status, str_id, NULL); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| installer_util::InstallStatus InstallChrome(const CommandLine& cmd_line, |
| const installer::Version* installed_version, const DictionaryValue* prefs) { |
| bool system_level = false; |
| installer_util::GetDistroBooleanPreference(prefs, |
| installer_util::master_preferences::kSystemLevel, &system_level); |
| installer_util::InstallStatus install_status = installer_util::UNKNOWN_STATUS; |
| if (!CheckPreInstallConditions(installed_version, |
| system_level, install_status)) |
| return install_status; |
| |
| // For install the default location for chrome.packed.7z is in current |
| // folder, so get that value first. |
| std::wstring archive = file_util::GetDirectoryFromPath(cmd_line.program()); |
| file_util::AppendToPath(&archive, |
| std::wstring(installer::kChromeCompressedArchive)); |
| |
| // If --install-archive is given, get the user specified value |
| if (cmd_line.HasSwitch(installer_util::switches::kInstallArchive)) { |
| archive = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kInstallArchive); |
| } |
| LOG(INFO) << "Archive found to install Chrome " << archive; |
| |
| // Create a temp folder where we will unpack Chrome archive. If it fails, |
| // then we are doomed, so return immediately and no cleanup is required. |
| FilePath temp_path; |
| if (!file_util::CreateNewTempDirectory(L"chrome_", &temp_path)) { |
| LOG(ERROR) << "Could not create temporary path."; |
| InstallUtil::WriteInstallerResult(system_level, |
| installer_util::TEMP_DIR_FAILED, |
| IDS_INSTALL_TEMP_DIR_FAILED_BASE, |
| NULL); |
| return installer_util::TEMP_DIR_FAILED; |
| } |
| LOG(INFO) << "created path " << temp_path.value(); |
| |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| std::wstring unpack_path(temp_path.ToWStringHack()); |
| file_util::AppendToPath(&unpack_path, |
| std::wstring(installer::kInstallSourceDir)); |
| bool incremental_install = false; |
| if (UnPackArchive(archive, system_level, installed_version, |
| temp_path.ToWStringHack(), unpack_path, |
| incremental_install)) { |
| install_status = installer_util::UNCOMPRESSION_FAILED; |
| InstallUtil::WriteInstallerResult(system_level, install_status, |
| IDS_INSTALL_UNCOMPRESSION_FAILED_BASE, |
| NULL); |
| } else { |
| LOG(INFO) << "unpacked to " << unpack_path; |
| std::wstring src_path(unpack_path); |
| file_util::AppendToPath(&src_path, |
| std::wstring(installer::kInstallSourceChromeDir)); |
| scoped_ptr<installer::Version> |
| installer_version(setup_util::GetVersionFromDir(src_path)); |
| if (!installer_version.get()) { |
| LOG(ERROR) << "Did not find any valid version in installer."; |
| install_status = installer_util::INVALID_ARCHIVE; |
| InstallUtil::WriteInstallerResult(system_level, install_status, |
| IDS_INSTALL_INVALID_ARCHIVE_BASE, NULL); |
| } else { |
| LOG(INFO) << "version to install: " << installer_version->GetString(); |
| if (installed_version && |
| installed_version->IsHigherThan(installer_version.get())) { |
| LOG(ERROR) << "Higher version is already installed."; |
| install_status = installer_util::HIGHER_VERSION_EXISTS; |
| InstallUtil::WriteInstallerResult(system_level, install_status, |
| IDS_INSTALL_HIGHER_VERSION_BASE, |
| NULL); |
| } else { |
| // We want to keep uncompressed archive (chrome.7z) that we get after |
| // uncompressing and binary patching. Get the location for this file. |
| std::wstring archive_to_copy(temp_path.ToWStringHack()); |
| file_util::AppendToPath(&archive_to_copy, installer::kChromeArchive); |
| std::wstring prefs_source_path = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kInstallerData); |
| install_status = installer::InstallOrUpdateChrome( |
| cmd_line.program(), archive_to_copy, temp_path.ToWStringHack(), |
| prefs_source_path, prefs, *installer_version, installed_version); |
| |
| int install_msg_base = IDS_INSTALL_FAILED_BASE; |
| std::wstring chrome_exe; |
| if (install_status == installer_util::SAME_VERSION_REPAIR_FAILED) { |
| install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_BASE; |
| } else if (install_status != installer_util::INSTALL_FAILED) { |
| chrome_exe = installer::GetChromeInstallPath(system_level); |
| if (chrome_exe.empty()) { |
| // If we failed to construct install path, it means the OS call to |
| // get %ProgramFiles% or %AppData% failed. Report this as failure. |
| install_msg_base = IDS_INSTALL_OS_ERROR_BASE; |
| install_status = installer_util::OS_ERROR; |
| } else { |
| file_util::AppendToPath(&chrome_exe, installer_util::kChromeExe); |
| chrome_exe = L"\"" + chrome_exe + L"\""; |
| install_msg_base = 0; |
| } |
| } |
| |
| bool value = false; |
| installer_util::GetDistroBooleanPreference(prefs, |
| installer_util::master_preferences::kDoNotRegisterForUpdateLaunch, |
| &value); |
| bool write_chrome_launch_string = (!value) && |
| (install_status != installer_util::IN_USE_UPDATED); |
| |
| InstallUtil::WriteInstallerResult(system_level, install_status, |
| install_msg_base, write_chrome_launch_string ? &chrome_exe : NULL); |
| |
| if (install_status == installer_util::FIRST_INSTALL_SUCCESS) { |
| LOG(INFO) << "First install successful."; |
| // We never want to launch Chrome in system level install mode. |
| bool do_not_launch_chrome = false; |
| installer_util::GetDistroBooleanPreference(prefs, |
| installer_util::master_preferences::kDoNotLaunchChrome, |
| &do_not_launch_chrome); |
| if (!system_level && !do_not_launch_chrome) |
| installer::LaunchChrome(system_level); |
| } else if ((install_status == installer_util::NEW_VERSION_UPDATED) || |
| (install_status == installer_util::IN_USE_UPDATED)) { |
| installer_setup::RemoveLegacyRegistryKeys(); |
| } |
| } |
| } |
| // There might be an experiment (for upgrade usually) that needs to happen. |
| // An experiment's outcome can include chrome's uninstallation. If that is |
| // the case we would not do that directly at this point but in another |
| // instance of setup.exe |
| // |
| // There is another way to reach this same function if this is a system |
| // level install. See HandleNonInstallCmdLineOptions(). |
| dist->LaunchUserExperiment(install_status, *installer_version, |
| system_level); |
| } |
| |
| // Delete temporary files. These include install temporary directory |
| // and master profile file if present. Note that we do not care about rollback |
| // here and we schedule for deletion on reboot below if the deletes fail. As |
| // such, we do not use DeleteTreeWorkItem. |
| LOG(INFO) << "Deleting temporary directory " << temp_path.value(); |
| bool cleanup_success = file_util::Delete(temp_path, true); |
| if (cmd_line.HasSwitch(installer_util::switches::kInstallerData)) { |
| std::wstring prefs_path = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kInstallerData); |
| cleanup_success = file_util::Delete(prefs_path, true) && cleanup_success; |
| } |
| |
| // The above cleanup has been observed to fail on several users machines. |
| // Specifically, it appears that the temp folder may be locked when we try |
| // to delete it. This is Rather Bad in the case where we have failed updates |
| // as we end up filling users' disks with large-ish temp files. To mitigate |
| // this, if we fail to delete the temp folders, then schedule them for |
| // deletion at next reboot. |
| if (!cleanup_success) { |
| ScheduleDirectoryForDeletion(temp_path.ToWStringHack().c_str()); |
| if (cmd_line.HasSwitch(installer_util::switches::kInstallerData)) { |
| std::wstring prefs_path = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kInstallerData); |
| ScheduleDirectoryForDeletion(prefs_path.c_str()); |
| } |
| } |
| |
| dist->UpdateDiffInstallStatus(system_level, incremental_install, |
| install_status); |
| return install_status; |
| } |
| |
| installer_util::InstallStatus UninstallChrome(const CommandLine& cmd_line, |
| const wchar_t* cmd_params, |
| const installer::Version* version, |
| bool system_install) { |
| LOG(INFO) << "Uninstalling Chome"; |
| bool force = cmd_line.HasSwitch(installer_util::switches::kForceUninstall); |
| if (!version && !force) { |
| LOG(ERROR) << "No Chrome installation found for uninstall."; |
| InstallUtil::WriteInstallerResult(system_install, |
| installer_util::CHROME_NOT_INSTALLED, |
| IDS_UNINSTALL_FAILED_BASE, NULL); |
| return installer_util::CHROME_NOT_INSTALLED; |
| } |
| |
| bool remove_all = !cmd_line.HasSwitch( |
| installer_util::switches::kDoNotRemoveSharedItems); |
| |
| return installer_setup::UninstallChrome(cmd_line.program(), system_install, |
| remove_all, force, |
| cmd_line, cmd_params); |
| } |
| |
| installer_util::InstallStatus ShowEULADialog(const std::wstring& inner_frame) { |
| LOG(INFO) << "About to show EULA"; |
| std::wstring eula_path = installer_util::GetLocalizedEulaResource(); |
| if (eula_path.empty()) { |
| LOG(ERROR) << "No EULA path available"; |
| return installer_util::EULA_REJECTED; |
| } |
| // Newer versions of the caller pass an inner frame parameter that must |
| // be given to the html page being launched. |
| if (!inner_frame.empty()) { |
| eula_path += L"?innerframe="; |
| eula_path += inner_frame; |
| } |
| installer::EulaHTMLDialog dlg(eula_path); |
| installer::EulaHTMLDialog::Outcome outcome = dlg.ShowModal(); |
| if (installer::EulaHTMLDialog::REJECTED == outcome) { |
| LOG(ERROR) << "EULA rejected or EULA failure"; |
| return installer_util::EULA_REJECTED; |
| } |
| if (installer::EulaHTMLDialog::ACCEPTED_OPT_IN == outcome) { |
| LOG(INFO) << "EULA accepted (opt-in)"; |
| return installer_util::EULA_ACCEPTED_OPT_IN; |
| } |
| LOG(INFO) << "EULA accepted (no opt-in)"; |
| return installer_util::EULA_ACCEPTED; |
| } |
| |
| // This method processes any command line options that make setup.exe do |
| // various tasks other than installation (renaming chrome.exe, showing eula |
| // among others). This function returns true if any such command line option |
| // has been found and processed (so setup.exe should exit at that point). |
| bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line, |
| bool system_install, |
| int& exit_code) { |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| if (cmd_line.HasSwitch(installer_util::switches::kUpdateSetupExe)) { |
| installer_util::InstallStatus status = installer_util::SETUP_PATCH_FAILED; |
| // If --update-setup-exe command line option is given, we apply the given |
| // patch to current exe, and store the resulting binary in the path |
| // specified by --new-setup-exe. But we need to first unpack the file |
| // given in --update-setup-exe. |
| FilePath temp_path; |
| if (!file_util::CreateNewTempDirectory(L"chrome_", &temp_path)) { |
| LOG(ERROR) << "Could not create temporary path."; |
| } else { |
| std::wstring setup_patch = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kUpdateSetupExe); |
| LOG(INFO) << "Opening archive " << setup_patch; |
| std::wstring uncompressed_patch; |
| if (LzmaUtil::UnPackArchive(setup_patch, temp_path.ToWStringHack(), |
| &uncompressed_patch) == NO_ERROR) { |
| std::wstring old_setup_exe = cmd_line.program(); |
| std::wstring new_setup_exe = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kNewSetupExe); |
| if (!setup_util::ApplyDiffPatch(old_setup_exe, uncompressed_patch, |
| new_setup_exe)) |
| status = installer_util::NEW_VERSION_UPDATED; |
| } |
| } |
| |
| exit_code = dist->GetInstallReturnCode(status); |
| if (exit_code) { |
| LOG(WARNING) << "setup.exe patching failed."; |
| InstallUtil::WriteInstallerResult(system_install, status, |
| IDS_SETUP_PATCH_FAILED_BASE, NULL); |
| } |
| file_util::Delete(temp_path, true); |
| return true; |
| } else if (cmd_line.HasSwitch(installer_util::switches::kShowEula)) { |
| // Check if we need to show the EULA. If it is passed as a command line |
| // then the dialog is shown and regardless of the outcome setup exits here. |
| std::wstring inner_frame = |
| cmd_line.GetSwitchValueNative(installer_util::switches::kShowEula); |
| exit_code = ShowEULADialog(inner_frame); |
| if (installer_util::EULA_REJECTED != exit_code) |
| GoogleUpdateSettings::SetEULAConsent(true); |
| return true; |
| } else if (cmd_line.HasSwitch( |
| installer_util::switches::kRegisterChromeBrowser)) { |
| // If --register-chrome-browser option is specified, register all |
| // Chrome protocol/file associations as well as register it as a valid |
| // browser for Start Menu->Internet shortcut. This option should only |
| // be used when setup.exe is launched with admin rights. We do not |
| // make any user specific changes in this option. |
| std::wstring chrome_exe(cmd_line.GetSwitchValueNative( |
| installer_util::switches::kRegisterChromeBrowser)); |
| std::wstring suffix; |
| if (cmd_line.HasSwitch( |
| installer_util::switches::kRegisterChromeBrowserSuffix)) { |
| suffix = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kRegisterChromeBrowserSuffix); |
| } |
| exit_code = ShellUtil::RegisterChromeBrowser(chrome_exe, suffix, false); |
| return true; |
| } else if (cmd_line.HasSwitch(installer_util::switches::kRenameChromeExe)) { |
| // If --rename-chrome-exe is specified, we want to rename the executables |
| // and exit. |
| exit_code = RenameChromeExecutables(system_install); |
| return true; |
| } else if (cmd_line.HasSwitch( |
| installer_util::switches::kRemoveChromeRegistration)) { |
| // This is almost reverse of --register-chrome-browser option above. |
| // Here we delete Chrome browser registration. This option should only |
| // be used when setup.exe is launched with admin rights. We do not |
| // make any user specific changes in this option. |
| std::wstring suffix; |
| if (cmd_line.HasSwitch( |
| installer_util::switches::kRegisterChromeBrowserSuffix)) { |
| suffix = cmd_line.GetSwitchValueNative( |
| installer_util::switches::kRegisterChromeBrowserSuffix); |
| } |
| installer_util::InstallStatus tmp = installer_util::UNKNOWN_STATUS; |
| installer_setup::DeleteChromeRegistrationKeys(HKEY_LOCAL_MACHINE, |
| suffix, tmp); |
| exit_code = tmp; |
| return true; |
| } else if (cmd_line.HasSwitch(installer_util::switches::kInactiveUserToast)) { |
| // Launch the inactive user toast experiment. |
| std::string flavor = cmd_line.GetSwitchValueASCII( |
| installer_util::switches::kInactiveUserToast); |
| int flavor_int; |
| base::StringToInt(flavor, &flavor_int); |
| dist->InactiveUserToastExperiment(flavor_int, |
| cmd_line.HasSwitch(installer_util::switches::kSystemLevelToast)); |
| return true; |
| } else if (cmd_line.HasSwitch(installer_util::switches::kSystemLevelToast)) { |
| // We started as system-level and have been re-launched as user level |
| // to continue with the toast experiment. |
| scoped_ptr<installer::Version> |
| installed_version(InstallUtil::GetChromeVersion(system_install)); |
| dist->LaunchUserExperiment(installer_util::REENTRY_SYS_UPDATE, |
| *installed_version, true); |
| return true; |
| } |
| return false; |
| } |
| |
| bool ShowRebootDialog() { |
| // Get a token for this process. |
| HANDLE token; |
| if (!OpenProcessToken(GetCurrentProcess(), |
| TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, |
| &token)) { |
| LOG(ERROR) << "Failed to open token."; |
| return false; |
| } |
| |
| // Use a ScopedHandle to keep track of and eventually close our handle. |
| // TODO(robertshield): Add a Receive() method to base's ScopedHandle. |
| ScopedHandle scoped_handle(token); |
| |
| // Get the LUID for the shutdown privilege. |
| TOKEN_PRIVILEGES tkp = {0}; |
| LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); |
| tkp.PrivilegeCount = 1; |
| tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
| |
| // Get the shutdown privilege for this process. |
| AdjustTokenPrivileges(token, FALSE, &tkp, 0, |
| reinterpret_cast<PTOKEN_PRIVILEGES>(NULL), 0); |
| if (GetLastError() != ERROR_SUCCESS) { |
| LOG(ERROR) << "Unable to get shutdown privileges."; |
| return false; |
| } |
| |
| // Popup a dialog that will prompt to reboot using the default system message. |
| // TODO(robertshield): Add a localized, more specific string to the prompt. |
| RestartDialog(NULL, NULL, EWX_REBOOT | EWX_FORCEIFHUNG); |
| return true; |
| } |
| |
| // Class to manage COM initialization and uninitialization |
| class AutoCom { |
| public: |
| AutoCom() : initialized_(false) { } |
| ~AutoCom() { |
| if (initialized_) CoUninitialize(); |
| } |
| bool Init(bool system_install) { |
| if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) != S_OK) { |
| LOG(ERROR) << "COM initialization failed."; |
| InstallUtil::WriteInstallerResult(system_install, |
| installer_util::OS_ERROR, |
| IDS_INSTALL_OS_ERROR_BASE, NULL); |
| return false; |
| } |
| initialized_ = true; |
| return true; |
| } |
| |
| private: |
| bool initialized_; |
| }; |
| |
| } // namespace |
| |
| int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, |
| wchar_t* command_line, int show_command) { |
| // The exit manager is in charge of calling the dtors of singletons. |
| base::AtExitManager exit_manager; |
| CommandLine::Init(0, NULL); |
| CommandLine* mutable_command_line = CommandLine::ForCurrentProcess(); |
| |
| if (mutable_command_line->HasSwitch(installer_util::switches::kChromeFrame)) { |
| mutable_command_line->AppendSwitch( |
| WideToASCII(installer_util::switches::kDoNotCreateShortcuts)); |
| mutable_command_line->AppendSwitch( |
| WideToASCII(installer_util::switches::kDoNotLaunchChrome)); |
| mutable_command_line->AppendSwitch( |
| WideToASCII(installer_util::switches::kDoNotRegisterForUpdateLaunch)); |
| } |
| |
| const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); |
| |
| installer::InitInstallerLogging(parsed_command_line); |
| scoped_ptr<DictionaryValue> prefs(installer_util::GetInstallPreferences( |
| parsed_command_line)); |
| bool value = false; |
| if (installer_util::GetDistroBooleanPreference(prefs.get(), |
| installer_util::master_preferences::kVerboseLogging, &value) && |
| value) |
| logging::SetMinLogLevel(logging::LOG_INFO); |
| |
| LOG(INFO) << "Command Line: " << parsed_command_line.command_line_string(); |
| |
| bool system_install = false; |
| installer_util::GetDistroBooleanPreference(prefs.get(), |
| installer_util::master_preferences::kSystemLevel, &system_install); |
| LOG(INFO) << "system install is " << system_install; |
| |
| // Check to make sure current system is WinXP or later. If not, log |
| // error message and get out. |
| if (!InstallUtil::IsOSSupported()) { |
| LOG(ERROR) << "Chrome only supports Windows XP or later."; |
| InstallUtil::WriteInstallerResult(system_install, |
| installer_util::OS_NOT_SUPPORTED, |
| IDS_INSTALL_OS_NOT_SUPPORTED_BASE, NULL); |
| return installer_util::OS_NOT_SUPPORTED; |
| } |
| |
| // Initialize COM for use later. |
| AutoCom auto_com; |
| if (!auto_com.Init(system_install)) { |
| return installer_util::OS_ERROR; |
| } |
| |
| // Some command line options don't work with SxS install/uninstall |
| if (InstallUtil::IsChromeSxSProcess()) { |
| if (system_install || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kForceUninstall) || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kMakeChromeDefault) || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kRegisterChromeBrowser) || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kRemoveChromeRegistration) || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kInactiveUserToast) || |
| parsed_command_line.HasSwitch( |
| installer_util::switches::kSystemLevelToast)) { |
| return installer_util::SXS_OPTION_NOT_SUPPORTED; |
| } |
| } |
| |
| int exit_code = 0; |
| if (HandleNonInstallCmdLineOptions(parsed_command_line, system_install, |
| exit_code)) |
| return exit_code; |
| |
| if (system_install && !IsUserAnAdmin()) { |
| if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA && |
| !parsed_command_line.HasSwitch(installer_util::switches::kRunAsAdmin)) { |
| std::wstring exe = parsed_command_line.program(); |
| std::wstring params(command_line); |
| // Append --run-as-admin flag to let the new instance of setup.exe know |
| // that we already tried to launch ourselves as admin. |
| params.append(L" --"); |
| params.append(installer_util::switches::kRunAsAdmin); |
| DWORD exit_code = installer_util::UNKNOWN_STATUS; |
| InstallUtil::ExecuteExeAsAdmin(exe, params, &exit_code); |
| return exit_code; |
| } else { |
| LOG(ERROR) << "Non admin user can not install system level Chrome."; |
| InstallUtil::WriteInstallerResult(system_install, |
| installer_util::INSUFFICIENT_RIGHTS, |
| IDS_INSTALL_INSUFFICIENT_RIGHTS_BASE, |
| NULL); |
| return installer_util::INSUFFICIENT_RIGHTS; |
| } |
| } |
| |
| // Check the existing version installed. |
| scoped_ptr<installer::Version> |
| installed_version(InstallUtil::GetChromeVersion(system_install)); |
| if (installed_version.get()) { |
| LOG(INFO) << "version on the system: " << installed_version->GetString(); |
| } |
| |
| installer_util::InstallStatus install_status = installer_util::UNKNOWN_STATUS; |
| // If --uninstall option is given, uninstall chrome |
| if (parsed_command_line.HasSwitch(installer_util::switches::kUninstall)) { |
| install_status = UninstallChrome(parsed_command_line, |
| command_line, |
| installed_version.get(), |
| system_install); |
| // If --uninstall option is not specified, we assume it is install case. |
| } else { |
| install_status = InstallChrome(parsed_command_line, |
| installed_version.get(), |
| prefs.get()); |
| } |
| |
| BrowserDistribution* dist = BrowserDistribution::GetDistribution(); |
| |
| if (InstallUtil::IsChromeFrameProcess() && |
| !parsed_command_line.HasSwitch( |
| installer_util::switches::kForceUninstall)) { |
| if (install_status == installer_util::UNINSTALL_REQUIRES_REBOOT) { |
| ShowRebootDialog(); |
| } else if (parsed_command_line.HasSwitch( |
| installer_util::switches::kUninstall)) { |
| ::MessageBoxW(NULL, |
| installer_util::GetLocalizedString( |
| IDS_UNINSTALL_COMPLETE_BASE).c_str(), |
| dist->GetApplicationName().c_str(), |
| MB_OK); |
| } |
| } |
| |
| int return_code = 0; |
| // MSI demands that custom actions always return 0 (ERROR_SUCCESS) or it will |
| // rollback the action. If we're uninstalling we want to avoid this, so always |
| // report success, squashing any more informative return codes. |
| if (!(InstallUtil::IsMSIProcess(system_install) && |
| parsed_command_line.HasSwitch(installer_util::switches::kUninstall))) { |
| // Note that we allow the status installer_util::UNINSTALL_REQUIRES_REBOOT |
| // to pass through, since this is only returned on uninstall which is never |
| // invoked directly by Google Update. |
| return_code = dist->GetInstallReturnCode(install_status); |
| } |
| |
| LOG(INFO) << "Installation complete, returning: " << return_code; |
| return return_code; |
| } |