// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/win/setup/uninstall.h"
#include <windows.h>
#include <shlobj.h>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/strings/strcat_win.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/scoped_com_initializer.h"
#include "chrome/installer/util/install_service_work_item.h"
#include "chrome/installer/util/registry_util.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/setup/setup_util.h"
#include "chrome/updater/win/win_constants.h"
namespace updater {
namespace {
void DeleteComServer(UpdaterScope scope, bool uninstall_all) {
for (const CLSID& clsid : JoinVectors(
GetSideBySideServers(scope),
uninstall_all ? GetActiveServers(scope) : std::vector<CLSID>())) {
installer::DeleteRegistryKey(UpdaterScopeToHKeyRoot(scope),
GetComServerClsidRegistryPath(clsid),
WorkItem::kWow64Default);
const std::wstring progid(GetProgIdForClsid(clsid));
if (!progid.empty()) {
installer::DeleteRegistryKey(UpdaterScopeToHKeyRoot(scope),
GetComProgIdRegistryPath(progid),
WorkItem::kWow64Default);
}
}
}
void DeleteComService(bool uninstall_all) {
CHECK(::IsUserAnAdmin());
for (const GUID& appid :
JoinVectors(GetSideBySideServers(UpdaterScope::kSystem),
uninstall_all ? GetActiveServers(UpdaterScope::kSystem)
: std::vector<CLSID>())) {
installer::DeleteRegistryKey(HKEY_LOCAL_MACHINE,
GetComServerAppidRegistryPath(appid),
WorkItem::kWow64Default);
}
for (const bool is_internal_service : {true, false}) {
const std::wstring service_name = GetServiceName(is_internal_service);
if (!installer::InstallServiceWorkItem::DeleteService(
service_name.c_str(), UPDATER_KEY, {}, {})) {
LOG(WARNING) << "DeleteService [" << service_name << "] failed.";
} else {
VLOG(1) << "DeleteService [" << service_name << "] succeeded.";
}
}
}
void DeleteComInterfaces(UpdaterScope scope, bool uninstall_all) {
for (const auto& [iid, interface_name] : JoinVectors(
GetSideBySideInterfaces(scope),
uninstall_all ? GetActiveInterfaces(scope)
: std::vector<std::pair<IID, std::wstring>>())) {
{
const std::wstring reg_path = GetComIidRegistryPath(iid);
for (const auto& key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
installer::DeleteRegistryKey(UpdaterScopeToHKeyRoot(scope), reg_path,
key_flag);
}
}
{
const std::wstring reg_path = GetComTypeLibRegistryPath(iid);
installer::DeleteRegistryKey(UpdaterScopeToHKeyRoot(scope), reg_path,
WorkItem::kWow64Default);
}
}
}
void DeleteClientStateKey(UpdaterScope scope) {
base::win::RegKey client_state;
if (client_state.Open(UpdaterScopeToHKeyRoot(scope), CLIENT_STATE_KEY,
Wow6432(KEY_QUERY_VALUE)) == ERROR_SUCCESS) {
// Delete the entire `ClientState` key only if all the apps are uninstalled
// already, as evidenced by the `--uninstall-if-unused` switch.
client_state.DeleteKey(base::CommandLine::ForCurrentProcess()->HasSwitch(
kUninstallIfUnusedSwitch)
? L""
: base::UTF8ToWide(kUpdaterAppId).c_str());
}
}
void DeleteGoogleUpdateFilesAndKeys(UpdaterScope scope) {
DeleteClientStateKey(scope);
const std::optional<base::FilePath> target_path =
GetGoogleUpdateExePath(scope);
if (target_path) {
base::DeletePathRecursively(target_path->DirName());
}
}
int RunUninstallScript(UpdaterScope scope, bool uninstall_all) {
const std::optional<base::FilePath> versioned_dir =
GetVersionedInstallDirectory(scope);
if (!versioned_dir) {
LOG(ERROR) << "GetVersionedInstallDirectory failed.";
return kErrorNoVersionedDirectory;
}
const std::optional<base::FilePath> base_dir = GetInstallDirectory(scope);
if (IsSystemInstall(scope) && !base_dir) {
LOG(ERROR) << "GetInstallDirectory failed.";
return kErrorNoBaseDirectory;
}
base::FilePath cmd_exe_path;
if (!base::PathService::Get(base::DIR_SYSTEM, &cmd_exe_path)) {
return kErrorPathServiceFailed;
}
cmd_exe_path = cmd_exe_path.Append(L"cmd.exe");
const base::FilePath script_path =
versioned_dir->AppendASCII(kUninstallScript);
const std::wstring cmdline = base::StrCat(
{L"\"", cmd_exe_path.value(), L"\" /Q /C \"\"", script_path.value(),
L"\" --dir=\"", (uninstall_all ? base_dir : versioned_dir)->value(),
L"\"\""});
base::LaunchOptions options;
options.start_hidden = true;
VLOG(1) << "Running " << cmdline;
base::Process process = base::LaunchProcess(cmdline, options);
if (!process.IsValid()) {
LOG(ERROR) << "Failed to create process " << cmdline;
return kErrorProcessLaunchFailed;
}
return kErrorOk;
}
// Reverses the changes made by setup. This is a best effort uninstall:
// 1. Deletes the scheduled task.
// 2. Deletes the ClientState key.
// 3. Runs the uninstall script in the install directory of the updater.
// The execution of this function and the script race each other but the script
// loops and waits in between iterations trying to delete the install directory.
// If `uninstall_all` is set to `true`, the function uninstalls both the
// internal as well as the active updater. If `uninstall_all` is set to `false`,
// the function uninstalls only the internal updater.
int UninstallImpl(UpdaterScope scope, bool uninstall_all) {
VLOG(1) << __func__ << ", scope: " << scope;
CHECK(!IsSystemInstall(scope) || ::IsUserAnAdmin());
auto scoped_com_initializer =
std::make_unique<base::win::ScopedCOMInitializer>(
base::win::ScopedCOMInitializer::kMTA);
updater::UnregisterWakeTask(scope);
if (uninstall_all) {
DeleteGoogleUpdateFilesAndKeys(scope);
}
DeleteComInterfaces(scope, uninstall_all);
if (IsSystemInstall(scope)) {
DeleteComService(uninstall_all);
}
DeleteComServer(scope, uninstall_all);
if (!IsSystemInstall(scope)) {
UnregisterUserRunAtStartup(GetTaskNamePrefix(scope));
}
if (uninstall_all) {
// Preserve the log file in %TMP%.
std::optional<base::FilePath> log_file = GetLogFilePath(scope);
if (log_file) {
std::optional<base::FilePath> temp_file =
GetUniqueTempFilePath(*log_file);
if (temp_file) {
base::CopyFile(*log_file, *temp_file);
}
}
}
return RunUninstallScript(scope, uninstall_all);
}
} // namespace
int Uninstall(UpdaterScope scope) {
return UninstallImpl(scope, true);
}
// Uninstalls this version of the updater, without uninstalling any other
// versions. This version is assumed to not be the active version.
int UninstallCandidate(UpdaterScope scope) {
return UninstallImpl(scope, false);
}
} // namespace updater