blob: a55f28d3d13addd54620b76e2766fe854dfde8f7 [file] [log] [blame]
// Copyright (c) 2012 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.
//
// This file defines the methods useful for uninstalling Chrome.
#include "chrome/installer/setup/uninstall.h"
#include <windows.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/shortcut.h"
#include "base/win/windows_version.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_result_codes.h"
#include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/setup/installer_state.h"
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/setup/setup_util.h"
#include "chrome/installer/setup/user_hive_visitor.h"
#include "chrome/installer/util/auto_launch_util.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/channel_info.h"
#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/firewall_manager_win.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/logging_installer.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/installer/util/work_item.h"
#include "chrome_elf/chrome_elf_constants.h"
#include "content/public/common/result_codes.h"
#include "rlz/lib/rlz_lib.h"
using base::win::RegKey;
namespace installer {
namespace {
// Avoid leaving behind a Temp dir. If one exists, ask SelfCleaningTempDir to
// clean it up for us. This may involve scheduling it for deletion after
// reboot. Don't report that a reboot is required in this case, however.
// TODO(erikwright): Shouldn't this still lead to
// ScheduleParentAndGrandparentForDeletion?
void DeleteInstallTempDir(const base::FilePath& target_path) {
base::FilePath temp_path(target_path.DirName().Append(
installer::kInstallTempDir));
if (base::DirectoryExists(temp_path)) {
SelfCleaningTempDir temp_dir;
if (!temp_dir.Initialize(target_path.DirName(),
installer::kInstallTempDir) ||
!temp_dir.Delete()) {
LOG(ERROR) << "Failed to delete temp dir " << temp_path.value();
}
}
}
// Processes uninstall WorkItems from install_worker in no-rollback-list.
void ProcessChromeWorkItems(const InstallerState& installer_state,
const Product& product) {
std::unique_ptr<WorkItemList> work_item_list(WorkItem::CreateWorkItemList());
work_item_list->set_log_message(
"Cleanup OS upgrade command and deprecated per-user registrations");
work_item_list->set_best_effort(true);
work_item_list->set_rollback_enabled(false);
AddOsUpgradeWorkItems(installer_state, base::FilePath(), base::Version(),
product, work_item_list.get());
// Perform a best-effort cleanup of per-user keys. On system-level installs
// this will only cleanup keys for the user running the uninstall but it was
// considered that this was good enough (better than triggering Active Setup
// for all users solely for this cleanup).
AddCleanupDeprecatedPerUserRegistrationsWorkItems(product,
work_item_list.get());
work_item_list->Do();
}
void ClearRlzProductState() {
const rlz_lib::AccessPoint points[] = {rlz_lib::CHROME_OMNIBOX,
rlz_lib::CHROME_HOME_PAGE,
rlz_lib::CHROME_APP_LIST,
rlz_lib::NO_ACCESS_POINT};
rlz_lib::ClearProductState(rlz_lib::CHROME, points);
// If chrome has been reactivated, clear all events for this brand as well.
base::string16 reactivation_brand_wide;
if (GoogleUpdateSettings::GetReactivationBrand(&reactivation_brand_wide)) {
std::string reactivation_brand(base::UTF16ToASCII(reactivation_brand_wide));
rlz_lib::SupplementaryBranding branding(reactivation_brand.c_str());
rlz_lib::ClearProductState(rlz_lib::CHROME, points);
}
}
// Removes all files from the installer directory. Returns false in case of an
// error.
bool RemoveInstallerFiles(const base::FilePath& installer_directory) {
base::FileEnumerator file_enumerator(
installer_directory,
false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
bool success = true;
for (base::FilePath to_delete = file_enumerator.Next(); !to_delete.empty();
to_delete = file_enumerator.Next()) {
VLOG(1) << "Deleting installer path " << to_delete.value();
if (!base::DeleteFile(to_delete, true)) {
LOG(ERROR) << "Failed to delete path: " << to_delete.value();
success = false;
}
}
return success;
}
// Kills all Chrome processes, immediately.
void CloseAllChromeProcesses() {
base::CleanupProcesses(installer::kChromeExe, base::TimeDelta(),
content::RESULT_CODE_HUNG, NULL);
base::CleanupProcesses(installer::kNaClExe, base::TimeDelta(),
content::RESULT_CODE_HUNG, NULL);
}
// Updates shortcuts to |old_target_exe| that have non-empty args, making them
// target |new_target_exe| instead. The non-empty args requirement is a
// heuristic to determine whether a shortcut is "user-generated". This routine
// can only be called for user-level installs.
void RetargetUserShortcutsWithArgs(const InstallerState& installer_state,
const Product& product,
const base::FilePath& old_target_exe,
const base::FilePath& new_target_exe) {
if (installer_state.system_install()) {
NOTREACHED();
return;
}
BrowserDistribution* dist = product.distribution();
ShellUtil::ShellChange install_level = ShellUtil::CURRENT_USER;
// Retarget all shortcuts that point to |old_target_exe| from all
// ShellUtil::ShortcutLocations.
VLOG(1) << "Retargeting shortcuts.";
for (int location = ShellUtil::SHORTCUT_LOCATION_FIRST;
location < ShellUtil::NUM_SHORTCUT_LOCATIONS; ++location) {
if (!ShellUtil::RetargetShortcutsWithArgs(
static_cast<ShellUtil::ShortcutLocation>(location), dist,
install_level, old_target_exe, new_target_exe)) {
LOG(WARNING) << "Failed to retarget shortcuts in ShortcutLocation: "
<< location;
}
}
}
// Deletes shortcuts at |install_level| from Start menu, Desktop,
// Quick Launch, taskbar, and secondary tiles on the Start Screen (Win8+).
// Only shortcuts pointing to |target_exe| will be removed.
void DeleteShortcuts(const InstallerState& installer_state,
const Product& product,
const base::FilePath& target_exe) {
BrowserDistribution* dist = product.distribution();
// The per-user shortcut for this user, if present on a system-level install,
// has already been deleted in chrome_browser_main_win.cc::DoUninstallTasks().
ShellUtil::ShellChange install_level = installer_state.system_install() ?
ShellUtil::SYSTEM_LEVEL : ShellUtil::CURRENT_USER;
// Delete and unpin all shortcuts that point to |target_exe| from all
// ShellUtil::ShortcutLocations.
VLOG(1) << "Deleting shortcuts.";
for (int location = ShellUtil::SHORTCUT_LOCATION_FIRST;
location < ShellUtil::NUM_SHORTCUT_LOCATIONS; ++location) {
if (!ShellUtil::RemoveShortcuts(
static_cast<ShellUtil::ShortcutLocation>(location), dist,
install_level, target_exe)) {
LOG(WARNING) << "Failed to delete shortcuts in ShortcutLocation: "
<< location;
}
}
}
bool ScheduleParentAndGrandparentForDeletion(const base::FilePath& path) {
base::FilePath parent_dir = path.DirName();
bool ret = ScheduleFileSystemEntityForDeletion(parent_dir);
if (!ret) {
LOG(ERROR) << "Failed to schedule parent dir for deletion: "
<< parent_dir.value();
} else {
base::FilePath grandparent_dir(parent_dir.DirName());
ret = ScheduleFileSystemEntityForDeletion(grandparent_dir);
if (!ret) {
LOG(ERROR) << "Failed to schedule grandparent dir for deletion: "
<< grandparent_dir.value();
}
}
return ret;
}
// Deletes the given directory if it is empty. Returns DELETE_SUCCEEDED if the
// directory is deleted, DELETE_NOT_EMPTY if it is not empty, and DELETE_FAILED
// otherwise.
DeleteResult DeleteEmptyDir(const base::FilePath& path) {
if (!base::IsDirectoryEmpty(path))
return DELETE_NOT_EMPTY;
if (base::DeleteFile(path, true))
return DELETE_SUCCEEDED;
LOG(ERROR) << "Failed to delete folder: " << path.value();
return DELETE_FAILED;
}
// Get the user data directory.
base::FilePath GetUserDataDir(const Product& product) {
base::FilePath path;
if (!PathService::Get(chrome::DIR_USER_DATA, &path))
return base::FilePath();
return path;
}
// Creates a copy of the local state file and returns a path to the copy.
base::FilePath BackupLocalStateFile(const base::FilePath& user_data_dir) {
base::FilePath backup;
base::FilePath state_file(user_data_dir.Append(chrome::kLocalStateFilename));
if (!base::CreateTemporaryFile(&backup))
LOG(ERROR) << "Failed to create temporary file for Local State.";
else
base::CopyFile(state_file, backup);
return backup;
}
// Deletes a given user data directory as well as the containing product
// directories if they are empty (e.g., "Google\Chrome").
DeleteResult DeleteUserDataDir(const base::FilePath& user_data_dir) {
if (user_data_dir.empty())
return DELETE_SUCCEEDED;
DeleteResult result = DELETE_SUCCEEDED;
VLOG(1) << "Deleting user profile " << user_data_dir.value();
if (!base::DeleteFile(user_data_dir, true)) {
LOG(ERROR) << "Failed to delete user profile dir: "
<< user_data_dir.value();
result = DELETE_FAILED;
}
const base::FilePath product_dir1(user_data_dir.DirName());
if (!product_dir1.empty() &&
DeleteEmptyDir(product_dir1) == DELETE_SUCCEEDED) {
const base::FilePath product_dir2(product_dir1.DirName());
if (!product_dir2.empty())
DeleteEmptyDir(product_dir2);
}
return result;
}
// Moves setup to a temporary file, outside of the install folder. Also attempts
// to change the current directory to the TMP directory. On Windows, each
// process has a handle to its CWD. If setup.exe's CWD happens to be within the
// install directory, deletion will fail as a result of the open handle.
bool MoveSetupOutOfInstallFolder(const InstallerState& installer_state,
const base::FilePath& setup_exe) {
// The list of files which setup.exe depends on at runtime. Typically this is
// solely setup.exe itself, but in component builds this also includes the
// DLLs installed by setup.exe.
std::vector<base::FilePath> setup_files;
setup_files.push_back(setup_exe);
#if defined(COMPONENT_BUILD)
base::FileEnumerator file_enumerator(
setup_exe.DirName(), false, base::FileEnumerator::FILES, L"*.dll");
for (base::FilePath setup_dll = file_enumerator.Next(); !setup_dll.empty();
setup_dll = file_enumerator.Next()) {
setup_files.push_back(setup_dll);
}
#endif // defined(COMPONENT_BUILD)
base::FilePath tmp_dir;
base::FilePath temp_file;
if (!PathService::Get(base::DIR_TEMP, &tmp_dir)) {
NOTREACHED();
return false;
}
// Change the current directory to the TMP directory. See method comment above
// for details.
VLOG(1) << "Changing current directory to: " << tmp_dir.value();
if (!base::SetCurrentDirectory(tmp_dir))
PLOG(ERROR) << "Failed to change the current directory.";
for (std::vector<base::FilePath>::const_iterator it = setup_files.begin();
it != setup_files.end(); ++it) {
const base::FilePath& setup_file = *it;
if (!base::CreateTemporaryFileInDir(tmp_dir, &temp_file)) {
LOG(ERROR) << "Failed to create temporary file for "
<< setup_file.BaseName().value();
return false;
}
VLOG(1) << "Attempting to move " << setup_file.BaseName().value() << " to: "
<< temp_file.value();
if (!base::Move(setup_file, temp_file)) {
PLOG(ERROR) << "Failed to move " << setup_file.BaseName().value()
<< " to " << temp_file.value();
return false;
}
// We cannot delete the file right away, but try to delete it some other
// way. Either with the help of a different process or the system.
if (!base::DeleteFileAfterReboot(temp_file)) {
const uint32_t kDeleteAfterMs = 10 * 1000;
installer::DeleteFileFromTempProcess(temp_file, kDeleteAfterMs);
}
}
return true;
}
DeleteResult DeleteChromeFilesAndFolders(const InstallerState& installer_state,
const base::FilePath& setup_exe) {
const base::FilePath& target_path = installer_state.target_path();
if (target_path.empty()) {
LOG(ERROR) << "DeleteChromeFilesAndFolders: no installation destination "
<< "path.";
return DELETE_FAILED; // Nothing else we can do to uninstall, so we return.
}
DeleteInstallTempDir(target_path);
DeleteResult result = DELETE_SUCCEEDED;
base::FilePath installer_directory;
if (target_path.IsParent(setup_exe))
installer_directory = setup_exe.DirName();
// Enumerate all the files in target_path recursively (breadth-first).
// We delete a file or folder unless it is a parent/child of the installer
// directory. For parents of the installer directory, we will later recurse
// and delete all the children (that are not also parents/children of the
// installer directory).
base::FileEnumerator file_enumerator(target_path, true,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (base::FilePath to_delete = file_enumerator.Next(); !to_delete.empty();
to_delete = file_enumerator.Next()) {
if (!installer_directory.empty() &&
(to_delete == installer_directory ||
installer_directory.IsParent(to_delete) ||
to_delete.IsParent(installer_directory))) {
continue;
}
VLOG(1) << "Deleting install path " << to_delete.value();
if (!base::DeleteFile(to_delete, true)) {
LOG(ERROR) << "Failed to delete path (1st try): " << to_delete.value();
// Try closing any running Chrome processes and deleting files once again.
CloseAllChromeProcesses();
if (!base::DeleteFile(to_delete, true)) {
LOG(ERROR) << "Failed to delete path (2nd try): " << to_delete.value();
result = DELETE_FAILED;
break;
}
}
}
return result;
}
// This method checks if Chrome is currently running or if the user has
// cancelled the uninstall operation by clicking Cancel on the confirmation
// box that Chrome pops up.
InstallStatus IsChromeActiveOrUserCancelled(
const InstallerState& installer_state,
const Product& product) {
int32_t exit_code = content::RESULT_CODE_NORMAL_EXIT;
base::CommandLine options(base::CommandLine::NO_PROGRAM);
options.AppendSwitch(installer::switches::kUninstall);
// Here we want to save user from frustration (in case of Chrome crashes)
// and continue with the uninstallation as long as chrome.exe process exit
// code is NOT one of the following:
// - UNINSTALL_CHROME_ALIVE - chrome.exe is currently running
// - UNINSTALL_USER_CANCEL - User cancelled uninstallation
// - HUNG - chrome.exe was killed by HuntForZombieProcesses() (until we can
// give this method some brains and not kill chrome.exe launched
// by us, we will not uninstall if we get this return code).
VLOG(1) << "Launching Chrome to do uninstall tasks.";
if (product.LaunchChromeAndWait(installer_state.target_path(), options,
&exit_code)) {
VLOG(1) << "chrome.exe launched for uninstall confirmation returned: "
<< exit_code;
if ((exit_code == chrome::RESULT_CODE_UNINSTALL_CHROME_ALIVE) ||
(exit_code == chrome::RESULT_CODE_UNINSTALL_USER_CANCEL) ||
(exit_code == content::RESULT_CODE_HUNG))
return installer::UNINSTALL_CANCELLED;
if (exit_code == chrome::RESULT_CODE_UNINSTALL_DELETE_PROFILE)
return installer::UNINSTALL_DELETE_PROFILE;
} else {
PLOG(ERROR) << "Failed to launch chrome.exe for uninstall confirmation.";
}
return installer::UNINSTALL_CONFIRMED;
}
bool ShouldDeleteProfile(const base::CommandLine& cmd_line,
InstallStatus status) {
return status == installer::UNINSTALL_DELETE_PROFILE ||
cmd_line.HasSwitch(installer::switches::kDeleteProfile);
}
// Removes XP-era filetype registration making Chrome the default browser.
// MSDN (see http://msdn.microsoft.com/library/windows/desktop/cc144148.aspx)
// tells us not to do this, but certain applications break following
// uninstallation if we don't.
void RemoveFiletypeRegistration(const InstallerState& installer_state,
HKEY root,
const base::string16& browser_entry_suffix) {
base::string16 classes_path(ShellUtil::kRegClasses);
classes_path.push_back(base::FilePath::kSeparators[0]);
BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
const base::string16 prog_id(
distribution->GetBrowserProgIdPrefix() + browser_entry_suffix);
// Delete each filetype association if it references this Chrome. Take care
// not to delete the association if it references a system-level install of
// Chrome (only a risk if the suffix is empty). Don't delete the whole key
// since other apps may have stored data there.
std::vector<const wchar_t*> cleared_assocs;
if (installer_state.system_install() ||
!browser_entry_suffix.empty() ||
!base::win::RegKey(HKEY_LOCAL_MACHINE, (classes_path + prog_id).c_str(),
KEY_QUERY_VALUE).Valid()) {
InstallUtil::ValueEquals prog_id_pred(prog_id);
for (const wchar_t* const* filetype =
&ShellUtil::kPotentialFileAssociations[0]; *filetype != NULL;
++filetype) {
if (InstallUtil::DeleteRegistryValueIf(
root, (classes_path + *filetype).c_str(), WorkItem::kWow64Default,
NULL, prog_id_pred) == InstallUtil::DELETED) {
cleared_assocs.push_back(*filetype);
}
}
}
// For all filetype associations in HKLM that have just been removed, attempt
// to restore some reasonable value. We have no definitive way of knowing
// what handlers are the most appropriate, so we use a fixed mapping based on
// the default values for a fresh install of Windows.
if (root == HKEY_LOCAL_MACHINE) {
base::string16 assoc;
base::win::RegKey key;
for (size_t i = 0; i < cleared_assocs.size(); ++i) {
const wchar_t* replacement_prog_id = NULL;
assoc.assign(cleared_assocs[i]);
// Inelegant, but simpler than a pure data-driven approach.
if (assoc == L".htm" || assoc == L".html")
replacement_prog_id = L"htmlfile";
else if (assoc == L".xht" || assoc == L".xhtml")
replacement_prog_id = L"xhtmlfile";
if (!replacement_prog_id) {
LOG(WARNING) << "No known replacement ProgID for " << assoc
<< " files.";
} else if (key.Open(HKEY_LOCAL_MACHINE,
(classes_path + replacement_prog_id).c_str(),
KEY_QUERY_VALUE) == ERROR_SUCCESS &&
(key.Open(HKEY_LOCAL_MACHINE, (classes_path + assoc).c_str(),
KEY_SET_VALUE) != ERROR_SUCCESS ||
key.WriteValue(NULL, replacement_prog_id) != ERROR_SUCCESS)) {
// The replacement ProgID is registered on the computer but the attempt
// to set it for the filetype failed.
LOG(ERROR) << "Failed to restore system-level filetype association "
<< assoc << " = " << replacement_prog_id;
}
}
}
}
bool DeleteUserRegistryKeys(const std::vector<const base::string16*>* key_paths,
const wchar_t* user_sid,
base::win::RegKey* key) {
for (const auto* key_path : *key_paths) {
LONG result = key->DeleteKey(key_path->c_str());
if (result == ERROR_SUCCESS) {
VLOG(1) << "Deleted " << user_sid << "\\" << *key_path;
} else if (result != ERROR_FILE_NOT_FOUND) {
::SetLastError(result);
PLOG(ERROR) << "Failed deleting " << user_sid << "\\" << *key_path;
}
}
return true;
}
// Removes Active Setup entries from the registry. This cannot be done through
// a work items list as usual because of different paths based on conditionals,
// but otherwise respects the no rollback/best effort uninstall mentality.
// This will only apply for system-level installs of Chrome/Chromium and will be
// a no-op for all other types of installs.
void UninstallActiveSetupEntries(const InstallerState& installer_state,
const Product& product) {
VLOG(1) << "Uninstalling registry entries for Active Setup.";
BrowserDistribution* distribution = product.distribution();
if (!installer_state.system_install()) {
VLOG(1) << "No Active Setup processing to do for user-level "
<< distribution->GetDisplayName();
return;
}
const base::string16 active_setup_path(
InstallUtil::GetActiveSetupPath(distribution));
InstallUtil::DeleteRegistryKey(HKEY_LOCAL_MACHINE, active_setup_path,
WorkItem::kWow64Default);
// Windows leaves keys behind in HKCU\\Software\\(Wow6432Node\\)?Microsoft\\
// Active Setup\\Installed Components\\{guid}
// for every user that logged in since system-level Chrome was installed. This
// is a problem because Windows compares the value of the Version subkey in
// there with the value of the Version subkey in the matching HKLM entries
// before running Chrome's Active Setup so if Chrome was to be
// uninstalled/reinstalled by an admin, some users may not go through Active
// Setup again as desired.
//
// It is however very hard to delete those values as the registry hives for
// other users are not loaded by default under HKEY_USERS (unless a user is
// logged on or has a process impersonating them).
//
// Following our best effort uninstall practices, try to delete the value in
// all users hives. If a given user's hive is not loaded, try to load it to
// proceed with the deletion (failure to do so is ignored).
// Windows automatically adds Wow6432Node when creating/deleting the HKLM key,
// but doesn't seem to do so when manually deleting the user-level keys it
// created.
base::string16 alternate_active_setup_path(active_setup_path);
alternate_active_setup_path.insert(arraysize("Software\\") - 1,
L"Wow6432Node\\");
VLOG(1) << "Uninstall per-user Active Setup keys.";
std::vector<const base::string16*> paths = {&active_setup_path,
&alternate_active_setup_path};
VisitUserHives(base::Bind(&DeleteUserRegistryKeys, base::Unretained(&paths)));
}
// Removes the persistent blacklist state for the current user. Note: this will
// not remove the state for users other than the one uninstalling Chrome on a
// system-level install (http://crbug.com/388725). Doing so would require
// extracting the per-user registry hive iteration from
// UninstallActiveSetupEntries so that it could service multiple tasks.
void RemoveBlacklistState() {
InstallUtil::DeleteRegistryKey(HKEY_CURRENT_USER,
blacklist::kRegistryBeaconPath,
0); // wow64_access
// The following key is no longer used (https://crbug.com/631771).
// This cleanup is being left in for a time though.
#if defined(GOOGLE_CHROME_BUILD)
const wchar_t kRegistryFinchListPath[] =
L"SOFTWARE\\Google\\Chrome\\BLFinchList";
#else
const wchar_t kRegistryFinchListPath[] =
L"SOFTWARE\\Chromium\\BLFinchList";
#endif
InstallUtil::DeleteRegistryKey(HKEY_CURRENT_USER,
kRegistryFinchListPath,
0); // wow64_access
}
// Removes the persistent state for |distribution| for the current user. Note:
// this will not remove the state for users other than the one uninstalling
// Chrome on a system-level install; see RemoveBlacklistState for details.
void RemoveDistributionRegistryState(BrowserDistribution* distribution) {
static const base::char16* const kKeysToPreserve[] = {
L"Extensions", L"NativeMessagingHosts",
};
// Delete the contents of the distribution key except for those parts used by
// outsiders to configure Chrome.
DeleteRegistryKeyPartial(
HKEY_CURRENT_USER, distribution->GetRegistryPath(),
std::vector<base::string16>(
&kKeysToPreserve[0],
&kKeysToPreserve[arraysize(kKeysToPreserve) - 1]));
}
} // namespace
DeleteResult DeleteChromeDirectoriesIfEmpty(
const base::FilePath& application_directory) {
DeleteResult result(DeleteEmptyDir(application_directory));
if (result == DELETE_SUCCEEDED) {
// Now check and delete if the parent directories are empty
// For example Google\Chrome or Chromium
const base::FilePath product_directory(application_directory.DirName());
if (!product_directory.empty()) {
result = DeleteEmptyDir(product_directory);
if (result == DELETE_SUCCEEDED) {
const base::FilePath vendor_directory(product_directory.DirName());
if (!vendor_directory.empty())
result = DeleteEmptyDir(vendor_directory);
}
}
}
if (result == DELETE_NOT_EMPTY)
result = DELETE_SUCCEEDED;
return result;
}
bool DeleteChromeRegistrationKeys(const InstallerState& installer_state,
BrowserDistribution* dist,
HKEY root,
const base::string16& browser_entry_suffix,
InstallStatus* exit_code) {
DCHECK(exit_code);
if (dist->GetDefaultBrowserControlPolicy() ==
BrowserDistribution::DEFAULT_BROWSER_UNSUPPORTED) {
// We should have never set those keys.
return true;
}
base::FilePath chrome_exe(installer_state.target_path().Append(kChromeExe));
// Delete Software\Classes\ChromeHTML.
const base::string16 prog_id(
dist->GetBrowserProgIdPrefix() + browser_entry_suffix);
base::string16 reg_prog_id(ShellUtil::kRegClasses);
reg_prog_id.push_back(base::FilePath::kSeparators[0]);
reg_prog_id.append(prog_id);
InstallUtil::DeleteRegistryKey(root, reg_prog_id, WorkItem::kWow64Default);
// Delete Software\Classes\Chrome.
base::string16 reg_app_id(ShellUtil::kRegClasses);
reg_app_id.push_back(base::FilePath::kSeparators[0]);
// Append the requested suffix manually here (as ShellUtil::GetBrowserModelId
// would otherwise try to figure out the currently installed suffix).
reg_app_id.append(dist->GetBaseAppId() + browser_entry_suffix);
InstallUtil::DeleteRegistryKey(root, reg_app_id, WorkItem::kWow64Default);
// Delete all Start Menu Internet registrations that refer to this Chrome.
{
using base::win::RegistryKeyIterator;
InstallUtil::ProgramCompare open_command_pred(chrome_exe);
base::string16 client_name;
base::string16 client_key;
base::string16 open_key;
for (RegistryKeyIterator iter(root, ShellUtil::kRegStartMenuInternet);
iter.Valid(); ++iter) {
client_name.assign(iter.Name());
client_key.assign(ShellUtil::kRegStartMenuInternet)
.append(1, L'\\')
.append(client_name);
open_key.assign(client_key).append(ShellUtil::kRegShellOpen);
if (InstallUtil::DeleteRegistryKeyIf(root, client_key, open_key,
WorkItem::kWow64Default, NULL, open_command_pred)
!= InstallUtil::NOT_FOUND) {
// Delete the default value of SOFTWARE\Clients\StartMenuInternet if it
// references this Chrome (i.e., if it was made the default browser).
InstallUtil::DeleteRegistryValueIf(
root, ShellUtil::kRegStartMenuInternet, WorkItem::kWow64Default,
NULL, InstallUtil::ValueEquals(client_name));
// Also delete the value for the default user if we're operating in
// HKLM.
if (root == HKEY_LOCAL_MACHINE) {
InstallUtil::DeleteRegistryValueIf(
HKEY_USERS,
base::string16(L".DEFAULT\\").append(
ShellUtil::kRegStartMenuInternet).c_str(),
WorkItem::kWow64Default, NULL,
InstallUtil::ValueEquals(client_name));
}
}
}
}
// Delete Software\RegisteredApplications\Chromium
InstallUtil::DeleteRegistryValue(
root, ShellUtil::kRegRegisteredApplications,
WorkItem::kWow64Default,
dist->GetBaseAppName() + browser_entry_suffix);
// Delete the App Paths and Applications keys that let Explorer find Chrome:
// http://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
base::string16 app_key(ShellUtil::kRegClasses);
app_key.push_back(base::FilePath::kSeparators[0]);
app_key.append(L"Applications");
app_key.push_back(base::FilePath::kSeparators[0]);
app_key.append(installer::kChromeExe);
InstallUtil::DeleteRegistryKey(root, app_key, WorkItem::kWow64Default);
base::string16 app_path_key(ShellUtil::kAppPathsRegistryKey);
app_path_key.push_back(base::FilePath::kSeparators[0]);
app_path_key.append(installer::kChromeExe);
InstallUtil::DeleteRegistryKey(root, app_path_key, WorkItem::kWow64Default);
// Cleanup OpenWithList and OpenWithProgids:
// http://msdn.microsoft.com/en-us/library/bb166549
base::string16 file_assoc_key;
base::string16 open_with_list_key;
base::string16 open_with_progids_key;
for (int i = 0; ShellUtil::kPotentialFileAssociations[i] != NULL; ++i) {
file_assoc_key.assign(ShellUtil::kRegClasses);
file_assoc_key.push_back(base::FilePath::kSeparators[0]);
file_assoc_key.append(ShellUtil::kPotentialFileAssociations[i]);
file_assoc_key.push_back(base::FilePath::kSeparators[0]);
open_with_list_key.assign(file_assoc_key);
open_with_list_key.append(L"OpenWithList");
open_with_list_key.push_back(base::FilePath::kSeparators[0]);
open_with_list_key.append(installer::kChromeExe);
InstallUtil::DeleteRegistryKey(
root, open_with_list_key, WorkItem::kWow64Default);
open_with_progids_key.assign(file_assoc_key);
open_with_progids_key.append(ShellUtil::kRegOpenWithProgids);
InstallUtil::DeleteRegistryValue(root, open_with_progids_key,
WorkItem::kWow64Default, prog_id);
}
// Cleanup in case Chrome had been made the default browser.
// Delete the default value of SOFTWARE\Clients\StartMenuInternet if it
// references this Chrome. Do this explicitly here for the case where HKCU is
// being processed; the iteration above will have no hits since registration
// lives in HKLM.
InstallUtil::DeleteRegistryValueIf(
root, ShellUtil::kRegStartMenuInternet, WorkItem::kWow64Default, NULL,
InstallUtil::ValueEquals(dist->GetBaseAppName() + browser_entry_suffix));
// Delete each protocol association if it references this Chrome.
InstallUtil::ProgramCompare open_command_pred(chrome_exe);
base::string16 parent_key(ShellUtil::kRegClasses);
parent_key.push_back(base::FilePath::kSeparators[0]);
const base::string16::size_type base_length = parent_key.size();
base::string16 child_key;
for (const wchar_t* const* proto =
&ShellUtil::kPotentialProtocolAssociations[0];
*proto != NULL;
++proto) {
parent_key.resize(base_length);
parent_key.append(*proto);
child_key.assign(parent_key).append(ShellUtil::kRegShellOpen);
InstallUtil::DeleteRegistryKeyIf(root, parent_key, child_key,
WorkItem::kWow64Default, NULL,
open_command_pred);
}
RemoveFiletypeRegistration(installer_state, root, browser_entry_suffix);
*exit_code = installer::UNINSTALL_SUCCESSFUL;
return true;
}
void RemoveChromeLegacyRegistryKeys(BrowserDistribution* dist,
const base::FilePath& chrome_exe) {
// We used to register Chrome to handle crx files, but this turned out
// to be not worth the hassle. Remove these old registry entries if
// they exist. See: http://codereview.chromium.org/210007
#if defined(GOOGLE_CHROME_BUILD)
const wchar_t kChromeExtProgId[] = L"ChromeExt";
#else
const wchar_t kChromeExtProgId[] = L"ChromiumExt";
#endif
HKEY roots[] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
for (size_t i = 0; i < arraysize(roots); ++i) {
base::string16 suffix;
if (roots[i] == HKEY_LOCAL_MACHINE)
suffix = ShellUtil::GetCurrentInstallationSuffix(dist, chrome_exe);
// Delete Software\Classes\ChromeExt,
base::string16 ext_prog_id(ShellUtil::kRegClasses);
ext_prog_id.push_back(base::FilePath::kSeparators[0]);
ext_prog_id.append(kChromeExtProgId);
ext_prog_id.append(suffix);
InstallUtil::DeleteRegistryKey(roots[i], ext_prog_id,
WorkItem::kWow64Default);
// Delete Software\Classes\.crx,
base::string16 ext_association(ShellUtil::kRegClasses);
ext_association.append(L"\\");
ext_association.append(L".crx");
InstallUtil::DeleteRegistryKey(roots[i], ext_association,
WorkItem::kWow64Default);
}
}
void UninstallFirewallRules(BrowserDistribution* dist,
const base::FilePath& chrome_exe) {
std::unique_ptr<FirewallManager> manager =
FirewallManager::Create(dist, chrome_exe);
if (manager)
manager->RemoveFirewallRules();
}
InstallStatus UninstallProduct(const InstallationState& original_state,
const InstallerState& installer_state,
const base::FilePath& setup_exe,
const Product& product,
bool remove_all,
bool force_uninstall,
const base::CommandLine& cmd_line) {
InstallStatus status = installer::UNINSTALL_CONFIRMED;
BrowserDistribution* browser_dist = product.distribution();
const base::FilePath chrome_exe(
installer_state.target_path().Append(installer::kChromeExe));
VLOG(1) << "UninstallProduct: " << browser_dist->GetDisplayName();
if (force_uninstall) {
// Since --force-uninstall command line option is used, we are going to
// do silent uninstall. Try to close all running Chrome instances.
CloseAllChromeProcesses();
} else {
// no --force-uninstall so lets show some UI dialog boxes.
status = IsChromeActiveOrUserCancelled(installer_state, product);
if (status != installer::UNINSTALL_CONFIRMED &&
status != installer::UNINSTALL_DELETE_PROFILE)
return status;
const base::string16 suffix(
ShellUtil::GetCurrentInstallationSuffix(browser_dist, chrome_exe));
// Check if we need admin rights to cleanup HKLM (the conditions for
// requiring a cleanup are the same as the conditions to do the actual
// cleanup where DeleteChromeRegistrationKeys() is invoked for
// HKEY_LOCAL_MACHINE below). If we do, try to launch another uninstaller
// (silent) in elevated mode to do HKLM cleanup.
// And continue uninstalling in the current process also to do HKCU cleanup.
if (remove_all &&
ShellUtil::QuickIsChromeRegisteredInHKLM(
browser_dist, chrome_exe, suffix) &&
!::IsUserAnAdmin() &&
base::win::GetVersion() >= base::win::VERSION_VISTA &&
!cmd_line.HasSwitch(installer::switches::kRunAsAdmin)) {
base::CommandLine new_cmd(base::CommandLine::NO_PROGRAM);
new_cmd.AppendArguments(cmd_line, true);
// Append --run-as-admin flag to let the new instance of setup.exe know
// that we already tried to launch ourselves as admin.
new_cmd.AppendSwitch(installer::switches::kRunAsAdmin);
// Append --remove-chrome-registration to remove registry keys only.
new_cmd.AppendSwitch(installer::switches::kRemoveChromeRegistration);
if (!suffix.empty()) {
new_cmd.AppendSwitchNative(
installer::switches::kRegisterChromeBrowserSuffix, suffix);
}
DWORD exit_code = installer::UNKNOWN_STATUS;
InstallUtil::ExecuteExeAsAdmin(new_cmd, &exit_code);
}
}
// Chrome is not in use so lets uninstall Chrome by deleting various files
// and registry entries. Here we will just make best effort and keep going
// in case of errors.
ClearRlzProductState();
auto_launch_util::DisableBackgroundStartAtLogin();
// If user-level chrome is self-destructing as a result of encountering a
// system-level chrome, retarget owned non-default shortcuts (app shortcuts,
// profile shortcuts, etc.) to the system-level chrome.
if (cmd_line.HasSwitch(installer::switches::kSelfDestruct) &&
!installer_state.system_install()) {
const base::FilePath system_chrome_path(
GetChromeInstallPath(true, browser_dist).Append(installer::kChromeExe));
VLOG(1) << "Retargeting user-generated Chrome shortcuts.";
if (base::PathExists(system_chrome_path)) {
RetargetUserShortcutsWithArgs(installer_state, product, chrome_exe,
system_chrome_path);
} else {
LOG(ERROR) << "Retarget failed: system-level Chrome not found.";
}
}
DeleteShortcuts(installer_state, product, chrome_exe);
// Delete the registry keys (Uninstall key and Version key).
HKEY reg_root = installer_state.root_key();
// Note that we must retrieve the distribution-specific data before deleting
// product.GetVersionKey().
base::string16 distribution_data(browser_dist->GetDistributionData(reg_root));
// Remove Control Panel uninstall link.
InstallUtil::DeleteRegistryKey(reg_root, browser_dist->GetUninstallRegPath(),
KEY_WOW64_32KEY);
// Remove Omaha product key.
InstallUtil::DeleteRegistryKey(
reg_root, browser_dist->GetVersionKey(), KEY_WOW64_32KEY);
// Also try to delete the MSI value in the ClientState key (it might not be
// there). This is due to a Google Update behaviour where an uninstall and a
// rapid reinstall might result in stale values from the old ClientState key
// being picked up on reinstall.
product.SetMsiMarker(installer_state.system_install(), false);
InstallStatus ret = installer::UNKNOWN_STATUS;
const base::string16 suffix(
ShellUtil::GetCurrentInstallationSuffix(browser_dist, chrome_exe));
// Remove all Chrome registration keys.
// Registration data is put in HKCU for both system level and user level
// installs.
DeleteChromeRegistrationKeys(installer_state, browser_dist, HKEY_CURRENT_USER,
suffix, &ret);
// If the user's Chrome is registered with a suffix: it is possible that old
// unsuffixed registrations were left in HKCU (e.g. if this install was
// previously installed with no suffix in HKCU (old suffix rules if the user
// is not an admin (or declined UAC at first run)) and later had to be
// suffixed when fully registered in HKLM (e.g. when later making Chrome
// default through the UI)).
// Remove remaining HKCU entries with no suffix if any.
if (!suffix.empty()) {
DeleteChromeRegistrationKeys(installer_state, browser_dist,
HKEY_CURRENT_USER, base::string16(), &ret);
// For similar reasons it is possible in very few installs (from
// 21.0.1180.0 and fixed shortly after) to be installed with the new-style
// suffix, but have some old-style suffix registrations left behind.
base::string16 old_style_suffix;
if (ShellUtil::GetOldUserSpecificRegistrySuffix(&old_style_suffix) &&
suffix != old_style_suffix) {
DeleteChromeRegistrationKeys(installer_state, browser_dist,
HKEY_CURRENT_USER, old_style_suffix, &ret);
}
// Chrome is registered in HKLM for all system-level installs and for
// user-level installs for which Chrome has been made the default browser.
// Always remove the HKLM registration for system-level installs. For
// user-level installs, only remove it if both: 1) this uninstall isn't a
// self destruct following the installation of a system-level Chrome
// (because the system-level Chrome owns the HKLM registration now), and 2)
// this user has made Chrome their default browser (i.e. has shell
// integration entries registered with |suffix| (note: |suffix| will be the
// empty string if required as it is obtained by
// GetCurrentInstallationSuffix() above)).
// TODO(gab): This can still leave parts of a suffixed install behind. To be
// able to remove them we would need to be able to remove only suffixed
// entries (as it is now some of the registry entries (e.g. App Paths) are
// unsuffixed; thus removing suffixed installs is prohibited in HKLM if
// !|remove_all| for now).
if (installer_state.system_install() ||
(remove_all &&
ShellUtil::QuickIsChromeRegisteredInHKLM(
browser_dist, chrome_exe, suffix))) {
DeleteChromeRegistrationKeys(installer_state, browser_dist,
HKEY_LOCAL_MACHINE, suffix, &ret);
}
ProcessChromeWorkItems(installer_state, product);
UninstallActiveSetupEntries(installer_state, product);
UninstallFirewallRules(browser_dist, chrome_exe);
RemoveBlacklistState();
// Notify the shell that associations have changed since Chrome was likely
// unregistered.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
}
// Get the state of the installed product (if any)
const ProductState* product_state =
original_state.GetProductState(installer_state.system_install());
// Remove the event log provider registration as we are going to delete
// the file which serves the resources anyways.
DeRegisterEventLogProvider();
// Delete shared registry keys as well (these require admin rights) if
// remove_all option is specified.
if (remove_all) {
if (!InstallUtil::IsChromeSxSProcess()) {
// Delete media player registry key that exists only in HKLM. We don't
// delete this key in SxS uninstall as we never set the key for it.
base::string16 reg_path(installer::kMediaPlayerRegPath);
reg_path.push_back(base::FilePath::kSeparators[0]);
reg_path.append(installer::kChromeExe);
InstallUtil::DeleteRegistryKey(HKEY_LOCAL_MACHINE, reg_path,
WorkItem::kWow64Default);
}
}
// Finally delete all the files from Chrome folder after moving setup.exe
// and the user's Local State to a temp location.
bool delete_profile = ShouldDeleteProfile(cmd_line, status);
ret = installer::UNINSTALL_SUCCESSFUL;
base::FilePath user_data_dir(GetUserDataDir(product));
base::FilePath backup_state_file;
if (!user_data_dir.empty()) {
backup_state_file = BackupLocalStateFile(user_data_dir);
} else {
LOG(ERROR) << "Could not retrieve the user's profile directory.";
ret = installer::UNINSTALL_FAILED;
delete_profile = false;
}
DeleteResult delete_result = DeleteChromeFilesAndFolders(
installer_state, base::MakeAbsoluteFilePath(setup_exe));
if (delete_result == DELETE_FAILED)
ret = installer::UNINSTALL_FAILED;
else if (delete_result == DELETE_REQUIRES_REBOOT)
ret = installer::UNINSTALL_REQUIRES_REBOOT;
if (delete_profile) {
DeleteUserDataDir(user_data_dir);
RemoveDistributionRegistryState(browser_dist);
}
if (!force_uninstall && product_state) {
VLOG(1) << "Uninstallation complete. Launching post-uninstall operations.";
browser_dist->DoPostUninstallOperations(product_state->version(),
backup_state_file, distribution_data);
}
// Try and delete the preserved local state once the post-install
// operations are complete.
if (!backup_state_file.empty())
base::DeleteFile(backup_state_file, false);
return ret;
}
void CleanUpInstallationDirectoryAfterUninstall(
const InstallationState& original_state,
const InstallerState& installer_state,
const base::FilePath& setup_exe,
InstallStatus* uninstall_status) {
if (*uninstall_status != UNINSTALL_SUCCESSFUL &&
*uninstall_status != UNINSTALL_REQUIRES_REBOOT) {
return;
}
const base::FilePath target_path(installer_state.target_path());
if (target_path.empty()) {
LOG(ERROR) << "No installation destination path.";
*uninstall_status = UNINSTALL_FAILED;
return;
}
if (!target_path.IsParent(base::MakeAbsoluteFilePath(setup_exe))) {
VLOG(1) << "setup.exe is not in target path. Skipping installer cleanup.";
return;
}
base::FilePath install_directory(setup_exe.DirName());
VLOG(1) << "Removing all installer files.";
// In order to be able to remove the folder in which we're running, we need to
// move setup.exe out of the install folder.
// TODO(tommi): What if the temp folder is on a different volume?
MoveSetupOutOfInstallFolder(installer_state, setup_exe);
// Remove files from "...\<product>\Application\<version>\Installer"
if (!RemoveInstallerFiles(install_directory)) {
*uninstall_status = UNINSTALL_FAILED;
return;
}
// Try to remove the empty directory hierarchy.
// Delete "...\<product>\Application\<version>\Installer"
if (DeleteEmptyDir(install_directory) != DELETE_SUCCEEDED) {
*uninstall_status = UNINSTALL_FAILED;
return;
}
// Delete "...\<product>\Application\<version>"
DeleteResult delete_result = DeleteEmptyDir(install_directory.DirName());
if (delete_result == DELETE_FAILED ||
(delete_result == DELETE_NOT_EMPTY &&
*uninstall_status != UNINSTALL_REQUIRES_REBOOT)) {
*uninstall_status = UNINSTALL_FAILED;
return;
}
if (*uninstall_status == UNINSTALL_REQUIRES_REBOOT) {
// Delete the Application directory at reboot if empty.
ScheduleFileSystemEntityForDeletion(target_path);
// If we need a reboot to continue, schedule the parent directories for
// deletion unconditionally. If they are not empty, the session manager
// will not delete them on reboot.
ScheduleParentAndGrandparentForDeletion(target_path);
} else if (DeleteChromeDirectoriesIfEmpty(target_path) == DELETE_FAILED) {
*uninstall_status = UNINSTALL_FAILED;
}
}
} // namespace installer