chromium/chrome/updater/test/integration_tests_win.cc

// Copyright 2020 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/test/integration_tests_win.h"

#include <regstr.h>
#include <shlobj.h>
#include <wrl/client.h>
#include <wrl/implements.h>

#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/format_macros.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_variant.h"
#include "base/win/win_util.h"
#include "base/win/window_enumerator.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/updater/activity.h"
#include "chrome/updater/app/server/win/com_classes.h"
#include "chrome/updater/app/server/win/updater_idl.h"
#include "chrome/updater/app/server/win/updater_internal_idl.h"
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/external_constants_builder.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/test/integration_tests_impl.h"
#include "chrome/updater/test/unit_test_util.h"
#include "chrome/updater/test/unit_test_util_win.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.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/task_scheduler.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/test/test_strings.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/resources.grh"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#include "components/crx_file/crx_verifier.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace updater::test {
namespace {

constexpr wchar_t kDidRun[] = L"dr";

enum class CheckInstallationStatus {
  kCheckIsNotInstalled = 0,
  kCheckIsInstalled = 1,
};

enum class CheckInstallationVersions {
  kCheckSxSOnly = 0,
  kCheckActiveAndSxS = 1,
};

// Creates an instance of the class specified by `clsid` in a local server.
template <typename ComInterface>
HRESULT CreateLocalServer(GUID clsid,
                          Microsoft::WRL::ComPtr<ComInterface>& server) {
  return ::CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER,
                            IID_PPV_ARGS(&server));
}

[[nodiscard]] bool RegKeyExists(HKEY root, const std::wstring& path) {
  return base::win::RegKey(root, path.c_str(), Wow6432(KEY_QUERY_VALUE))
      .Valid();
}

[[nodiscard]] bool RegKeyExists64(HKEY root, const std::wstring& path) {
  return base::win::RegKey(root, path.c_str(),
                           KEY_WOW64_64KEY | KEY_QUERY_VALUE)
      .Valid();
}

[[nodiscard]] bool RegKeyExistsCOM(HKEY root, const std::wstring& path) {
  return base::win::RegKey(root, path.c_str(), KEY_QUERY_VALUE).Valid();
}

[[nodiscard]] std::wstring ReadRegValue(HKEY root,
                                        const std::wstring& path,
                                        const std::wstring& value,
                                        REGSAM wow64_access) {
  std::wstring result;
  base::win::RegKey(root, path.c_str(), wow64_access | KEY_QUERY_VALUE)
      .ReadValue(value.c_str(), &result);
  return result;
}

[[nodiscard]] bool DeleteRegKey(HKEY root, const std::wstring& path) {
  LONG result =
      base::win::RegKey(root, L"", Wow6432(DELETE)).DeleteKey(path.c_str());
  return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}

[[nodiscard]] bool DeleteRegKey64(HKEY root, const std::wstring& path) {
  LONG result = base::win::RegKey(root, L"", KEY_WOW64_64KEY | DELETE)
                    .DeleteKey(path.c_str());
  return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}

[[nodiscard]] bool DeleteRegKeyCOM(HKEY root, const std::wstring& path) {
  LONG result = base::win::RegKey(root, L"", DELETE).DeleteKey(path.c_str());
  return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}

[[nodiscard]] bool IsServiceGone(const std::wstring& service_name) {
  ScopedScHandle scm(::OpenSCManager(
      nullptr, nullptr, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE));
  if (!scm.IsValid()) {
    return false;
  }

  ScopedScHandle service(
      ::OpenService(scm.Get(), service_name.c_str(),
                    SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG));
  bool is_service_gone = !service.IsValid();
  if (!is_service_gone) {
    if (!::ChangeServiceConfig(service.Get(), SERVICE_NO_CHANGE,
                               SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, nullptr,
                               nullptr, nullptr, nullptr, nullptr, nullptr,
                               nullptr)) {
      is_service_gone = ::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE;
    }
  }

  return is_service_gone &&
         !base::win::RegKey(HKEY_LOCAL_MACHINE, UPDATER_KEY, Wow6432(KEY_READ))
              .HasValue(service_name.c_str());
}

// Checks the installation states (installed or uninstalled) and versions (SxS
// only, or both active and SxS). The installation state includes
// Client/ClientState registry, COM server registration, COM service
// registration, COM interfaces, wake tasks, and files on the file system.
void CheckInstallation(UpdaterScope scope,
                       CheckInstallationStatus check_installation_status,
                       CheckInstallationVersions check_installation_versions) {
  const bool is_installed =
      check_installation_status == CheckInstallationStatus::kCheckIsInstalled;
  const bool is_active_and_sxs = check_installation_versions ==
                                 CheckInstallationVersions::kCheckActiveAndSxS;

  const HKEY root = UpdaterScopeToHKeyRoot(scope);

  if (is_active_and_sxs) {
    EXPECT_EQ(is_installed, base::PathExists(*GetGoogleUpdateExePath(scope)));
    EXPECT_EQ(is_installed,
              RegKeyExists(UpdaterScopeToHKeyRoot(scope),
                           base::StrCat({CLIENT_STATE_KEY,
                                         base::UTF8ToWide(kUpdaterAppId)})));

    if (is_installed) {
      for (const wchar_t* key : {CLIENTS_KEY, CLIENT_STATE_KEY, UPDATER_KEY}) {
        EXPECT_TRUE(RegKeyExists(root, key)) << key;
      }

      std::wstring pv;
      EXPECT_EQ(ERROR_SUCCESS,
                base::win::RegKey(
                    root, GetAppClientsKey(kLegacyGoogleUpdateAppID).c_str(),
                    Wow6432(KEY_READ))
                    .ReadValue(kRegValuePV, &pv));
      EXPECT_STREQ(kUpdaterVersionUtf16, pv.c_str());
      EXPECT_EQ(
          ERROR_SUCCESS,
          base::win::RegKey(
              root, GetAppClientStateKey(kLegacyGoogleUpdateAppID).c_str(),
              Wow6432(KEY_READ))
              .ReadValue(kRegValuePV, &pv));
      EXPECT_STREQ(kUpdaterVersionUtf16, pv.c_str());

      std::wstring uninstall_cmd_line_string;
      EXPECT_EQ(ERROR_SUCCESS,
                base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
                    .ReadValue(kRegValueUninstallCmdLine,
                               &uninstall_cmd_line_string));
      EXPECT_TRUE(base::CommandLine::FromString(uninstall_cmd_line_string)
                      .HasSwitch(kWakeSwitch));

      EXPECT_EQ(ERROR_SUCCESS,
                base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
                    .HasValue(kRegValueVersion));

      if (!IsSystemInstall(scope)) {
        std::wstring run_updater_wake_command;
        EXPECT_EQ(ERROR_SUCCESS,
                  base::win::RegKey(root, REGSTR_PATH_RUN, KEY_READ)
                      .ReadValue(GetTaskNamePrefix(scope).c_str(),
                                 &run_updater_wake_command));
        EXPECT_TRUE(base::CommandLine::FromString(run_updater_wake_command)
                        .HasSwitch(kWakeSwitch));
      }
    } else {
      if (::IsUserAnAdmin()) {
        for (const wchar_t* key :
             {kRegKeyCompanyCloudManagement, UPDATER_POLICIES_KEY}) {
          EXPECT_FALSE(RegKeyExists(HKEY_LOCAL_MACHINE, key));
        }
      }
      if (!IsSystemInstall(scope)) {
        ForEachRegistryRunValueWithPrefix(
            base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
            [](const std::wstring& run_name) {
              ADD_FAILURE() << "Unexpected Run key found: " << run_name;
            });
      }
    }
  }

  for (const CLSID& clsid :
       JoinVectors(GetSideBySideServers(scope), is_active_and_sxs
                                                    ? GetActiveServers(scope)
                                                    : std::vector<CLSID>())) {
    EXPECT_EQ(is_installed,
              RegKeyExistsCOM(root, GetComServerClsidRegistryPath(clsid)));
    if (IsSystemInstall(scope)) {
      EXPECT_EQ(is_installed,
                RegKeyExistsCOM(root, GetComServerAppidRegistryPath(clsid)));
    }

    const std::wstring progid(GetProgIdForClsid(clsid));
    if (!progid.empty()) {
      EXPECT_EQ(is_installed,
                RegKeyExistsCOM(root, GetComProgIdRegistryPath(progid)));
    }
  }

  for (const auto& [iid, expected_interface_name] : JoinVectors(
           GetSideBySideInterfaces(scope),
           is_active_and_sxs ? GetActiveInterfaces(scope)
                             : std::vector<std::pair<IID, std::wstring>>())) {
    EXPECT_EQ(is_installed, RegKeyExists(root, GetComIidRegistryPath(iid)));
    EXPECT_EQ(is_installed, RegKeyExists64(root, GetComIidRegistryPath(iid)));
    if (is_installed) {
      for (const auto& key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
        EXPECT_EQ(ReadRegValue(root, GetComIidRegistryPath(iid), L"", key_flag),
                  expected_interface_name);
      }
    }
    EXPECT_EQ(is_installed,
              RegKeyExistsCOM(root, GetComTypeLibRegistryPath(iid)));
  }

  if (IsSystemInstall(scope)) {
    for (const bool is_internal_service : {false, true}) {
      if (!is_active_and_sxs && !is_internal_service) {
        continue;
      }

      const std::wstring service_name(GetServiceName(is_internal_service));
      EXPECT_EQ(is_installed,
                !IsServiceGone(GetServiceName(is_internal_service)))
          << ": " << service_name << ": " << is_internal_service;

      if (!is_installed) {
        ForEachServiceWithPrefix(
            base::StrCat({base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
                          is_internal_service ? kWindowsInternalServiceName
                                              : kWindowsServiceName}),
            base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
            [](const std::wstring& service_name) {
              ADD_FAILURE() << "Unexpected service found: " << service_name;
            });
      }
    }
  }

  scoped_refptr<TaskScheduler> task_scheduler =
      TaskScheduler::CreateInstance(scope);
  if (is_installed) {
    const std::wstring task_name =
        task_scheduler->FindFirstTaskName(GetTaskNamePrefix(scope));
    EXPECT_TRUE(!task_name.empty());
    EXPECT_TRUE(task_scheduler->IsTaskRegistered(task_name));

    TaskScheduler::TaskInfo task_info;
    ASSERT_TRUE(task_scheduler->GetTaskInfo(task_name, task_info));
    ASSERT_EQ(task_info.exec_actions.size(), 1u);
    EXPECT_EQ(
        task_info.exec_actions[0].arguments,
        base::StrCat({L"--wake", IsSystemInstall(scope) ? L" --system" : L""}));

    EXPECT_EQ(task_info.trigger_types,
              TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY |
                  TaskScheduler::TriggerType::TRIGGER_TYPE_LOGON);
  } else {
    task_scheduler->ForEachTaskWithPrefix(
        base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
        [](const std::wstring& task_name) {
          ADD_FAILURE() << "Unexpected task found: " << task_name;
        });
  }

  const std::optional<base::FilePath> path =
      GetVersionedInstallDirectory(scope, base::Version(kUpdaterVersion));
  ASSERT_TRUE(path);
  EXPECT_TRUE(WaitFor([&] { return is_installed == base::PathExists(*path); },
                      [&] {
                        VLOG(0) << "Still waiting for " << *path
                                << " where is_installed=" << is_installed;
                      }))
      << base::JoinString(
             [&path] {
               std::vector<base::FilePath::StringType> files;
               base::FileEnumerator(*path, true,
                                    base::FileEnumerator::FILES |
                                        base::FileEnumerator::DIRECTORIES)
                   .ForEach([&files](const base::FilePath& name) {
                     files.push_back(name.value());
                   });

               return files;
             }(),
             FILE_PATH_LITERAL(","));
}

void SleepFor(const base::TimeDelta& interval) {
  VLOG(2) << "Sleeping " << interval.InSecondsF() << " seconds...";
  base::PlatformThread::Sleep(interval);
  VLOG(2) << "Sleep complete.";
}

void SetupAppCommand(UpdaterScope scope,
                     const std::wstring& app_id,
                     const std::wstring& command_id,
                     const std::wstring& parameters,
                     base::ScopedTempDir& temp_dir) {
  base::CommandLine cmd_exe_command_line(base::CommandLine::NO_PROGRAM);
  SetupCmdExe(scope, cmd_exe_command_line, temp_dir);
  CreateAppCommandRegistry(
      scope, app_id, command_id,
      base::StrCat({cmd_exe_command_line.GetCommandLineString(), parameters}));
}

base::Process LaunchOfflineInstallProcess(bool is_legacy_install,
                                          const base::FilePath& exe_path,
                                          UpdaterScope install_scope,
                                          const std::wstring& app_id,
                                          const std::wstring& offline_dir_guid,
                                          bool is_silent_install) {
  auto launch_legacy_offline_install = [&] {
    auto build_legacy_switch =
        [](const std::string& switch_name) -> std::wstring {
      return base::ASCIIToWide(base::StrCat({"/", switch_name}));
    };
    std::vector<std::wstring> install_cmd_args = {
        base::CommandLine::QuoteForCommandLineToArgvW(exe_path.value()),

        IsSystemInstall(install_scope)
            ? build_legacy_switch(updater::kSystemSwitch)
            : L"",

        build_legacy_switch(updater::kHandoffSwitch),
        base::StrCat({L"\"appguid=", app_id, L"&lang=en\""}),

        build_legacy_switch(updater::kSessionIdSwitch),
        L"{E85204C6-6F2F-40BF-9E6C-4952208BB977}",

        build_legacy_switch(updater::kOfflineDirSwitch),
        base::CommandLine::QuoteForCommandLineToArgvW(offline_dir_guid),

        is_silent_install ? build_legacy_switch(updater::kSilentSwitch) : L"",
    };

    return base::LaunchProcess(base::JoinString(install_cmd_args, L" "), {});
  };

  auto launch_offline_install = [&] {
    base::CommandLine install_cmd(exe_path);
    if (IsSystemInstall(install_scope)) {
      install_cmd.AppendSwitch(kSystemSwitch);
    }

    install_cmd.AppendSwitchNative(
        updater::kHandoffSwitch,
        base::StrCat({L"appguid=", app_id, L"&lang=en"}));
    install_cmd.AppendSwitchASCII(updater::kSessionIdSwitch,
                                  "{E85204C6-6F2F-40BF-9E6C-4952208BB977}");
    install_cmd.AppendSwitchNative(updater::kOfflineDirSwitch,
                                   offline_dir_guid);
    if (is_silent_install) {
      install_cmd.AppendSwitch(updater::kSilentSwitch);
    }

    return base::LaunchProcess(install_cmd, {});
  };

  return is_legacy_install ? launch_legacy_offline_install()
                           : launch_offline_install();
}

DISPID GetDispId(Microsoft::WRL::ComPtr<IDispatch> dispatch,
                 std::wstring name) {
  DISPID id = 0;
  LPOLESTR name_ptr = &name[0];
  EXPECT_HRESULT_SUCCEEDED(dispatch->GetIDsOfNames(IID_NULL, &name_ptr, 1,
                                                   LOCALE_USER_DEFAULT, &id));
  VLOG(2) << __func__ << ": " << name << ": " << id;
  return id;
}

void CallDispatchMethod(
    Microsoft::WRL::ComPtr<IDispatch> dispatch,
    const std::wstring& method_name,
    const std::vector<base::win::ScopedVariant>& variant_params) {
  std::vector<VARIANT> params;
  params.reserve(variant_params.size());

  // IDispatch::Invoke() expects the parameters in reverse order.
  base::ranges::transform(base::Reversed(variant_params),
                          std::back_inserter(params),
                          &base::win::ScopedVariant::Copy);

  DISPPARAMS dp = {};
  if (!params.empty()) {
    dp.rgvarg = &params[0];
    dp.cArgs = params.size();
  }

  EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke(
      GetDispId(dispatch, method_name), IID_NULL, LOCALE_USER_DEFAULT,
      DISPATCH_METHOD, &dp, nullptr, nullptr, nullptr));

  base::ranges::for_each(params, [](auto& param) { ::VariantClear(&param); });
  return;
}

base::win::ScopedVariant GetDispatchProperty(
    Microsoft::WRL::ComPtr<IDispatch> dispatch,
    const std::wstring& property_name) {
  DISPPARAMS dp = {};
  base::win::ScopedVariant result;

  EXPECT_HRESULT_SUCCEEDED(dispatch->Invoke(
      GetDispId(dispatch, property_name), IID_NULL, LOCALE_USER_DEFAULT,
      DISPATCH_PROPERTYGET, &dp, result.Receive(), nullptr, nullptr));

  return result;
}

std::wstring GetAppVersionWebString(
    Microsoft::WRL::ComPtr<IDispatch> version_web_dispatch) {
  Microsoft::WRL::ComPtr<IAppVersionWeb> version_web;
  EXPECT_HRESULT_SUCCEEDED(version_web_dispatch.As(&version_web));

  base::win::ScopedBstr version;
  EXPECT_HRESULT_SUCCEEDED(version_web->get_version(version.Receive()));

  return version.Get();
}

bool BuildTestAppInstaller(const base::FilePath& installer_script,
                           const base::FilePath& output_installer) {
  base::FilePath exe_path;
  if (!base::PathService::Get(base::DIR_EXE, &exe_path)) {
    return false;
  }
  const base::FilePath installer_dir = exe_path.AppendASCII("test_installer");

  base::CommandLine command(
      installer_dir.AppendASCII("embed_install_scripts.py"));
  command.AppendSwitchPath(
      "--installer", installer_dir.AppendASCII("test_meta_installer.exe"));
  command.AppendSwitchPath("--output", output_installer);
  command.AppendSwitchPath("--script", installer_script);
  return RunVPythonCommand(command) == 0;
}

void RunOfflineInstallWithManifest(UpdaterScope scope,
                                   bool is_legacy_install,
                                   bool is_silent_install,
                                   const std::string& manifest_format,
                                   int string_resource_id_to_find,
                                   bool expect_success) {
  static constexpr wchar_t kTestAppID[] =
      L"{CDABE316-39CD-43BA-8440-6D1E0547AEE6}";
  static constexpr char kAppInstallerName[] = "TestAppSetup.exe";
  const base::Version kTestPV("1.2.3.4");
  const std::wstring manifest_filename(L"OfflineManifest.gup");
  const std::wstring offline_dir_guid(
      L"{7B3A5597-DDEA-409B-B900-4C3D2A94A75C}");
  const HKEY root = UpdaterScopeToHKeyRoot(scope);
  const std::wstring app_clients_key = GetAppClientsKey(kTestAppID);
  const std::wstring app_client_state_key = GetAppClientStateKey(kTestAppID);

  EXPECT_TRUE(DeleteRegKey(root, app_clients_key));
  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));

  const std::optional<base::FilePath> updater_exe =
      GetUpdaterExecutablePath(scope);
  ASSERT_TRUE(updater_exe.has_value());

  const base::FilePath exe_dir(updater_exe->DirName());
  const base::FilePath offline_dir(
      exe_dir.Append(L"Offline").Append(offline_dir_guid));
  const base::FilePath offline_app_dir(offline_dir.Append(kTestAppID));
  const base::FilePath offline_app_scripts_dir(
      offline_app_dir.Append(L"Scripts"));
  ASSERT_TRUE(base::CreateDirectory(offline_app_scripts_dir));

  // Create a batch file as the installer script, which creates some registry
  // values as the installation artifacts.
  const base::FilePath batch_script_path(
      offline_app_scripts_dir.AppendASCII("AppSetup.bat"));

  // Create a shared event to be waited for in this process and signaled in the
  // test process. If the test is running elevated with UAC on, the test will
  // also confirm that the test process is launched at medium integrity, by
  // creating an event with a security descriptor that allows the medium
  // integrity process to signal it.
  test::EventHolder event_holder(IsElevatedWithUACOn()
                                     ? CreateEveryoneWaitableEventForTest()
                                     : test::CreateWaitableEventForTest());
  EXPECT_TRUE(base::WriteFile(batch_script_path, [&] {
    const std::string reg_hive = IsSystemInstall(scope) ? "HKLM" : "HKCU";
    const std::string app_client_state_key_utf8 =
        base::WideToUTF8(app_client_state_key);

    base::CommandLine post_install_cmd(
        GetTestProcessCommandLine(scope, GetTestName()));
    post_install_cmd.AppendSwitchNative(
        IsElevatedWithUACOn() ? kTestEventToSignalIfMediumIntegrity
                              : kTestEventToSignal,
        event_holder.name);
    std::vector<std::string> commands;
    const struct {
      const std::string subkey;
      const char* value_name;
      const char* type;
      const std::string value;
    } reg_items[] = {
        {base::WideToUTF8(app_clients_key), "pv", "REG_SZ",
         kTestPV.GetString()},
        {app_client_state_key_utf8, "InstallerResult", "REG_DWORD", "0"},
        {app_client_state_key_utf8, "InstallerError", "REG_DWORD", "0"},
        {app_client_state_key_utf8, "InstallerExtraCode1", "REG_DWORD", "0"},
        {app_client_state_key_utf8, "InstallerResultUIString", "REG_SZ",
         "CoolApp"},
        {app_client_state_key_utf8, "InstallerSuccessLaunchCmdLine", "REG_SZ",
         base::WideToASCII(post_install_cmd.GetCommandLineString())},
    };
    for (const auto& reg_item : reg_items) {
      commands.push_back(base::StringPrintf(
          "REG.exe ADD \"%s\\%s\" /v %s /t %s /d %s /f /reg:32",
          reg_hive.c_str(), reg_item.subkey.c_str(), reg_item.value_name,
          reg_item.type,
          base::WideToASCII(base::CommandLine::QuoteForCommandLineToArgvW(
                                base::ASCIIToWide(reg_item.value)))
              .c_str()));
    }
    return base::JoinString(commands, "\n");
  }()));

  const base::FilePath& app_installer =
      offline_app_dir.AppendASCII(kAppInstallerName);
  EXPECT_TRUE(BuildTestAppInstaller(batch_script_path, app_installer));
  base::FilePath manifest_path = offline_dir.Append(manifest_filename);
  int64_t app_installer_size = 0;
  EXPECT_TRUE(base::GetFileSize(app_installer, &app_installer_size));
  const std::string manifest = base::StringPrintfNonConstexpr(
      manifest_format.c_str(), kTestAppID, /*pv=*/"", kAppInstallerName,
      app_installer_size, kAppInstallerName);
  EXPECT_TRUE(base::WriteFile(manifest_path, manifest));

  // Trigger offline install.
  ASSERT_TRUE(LaunchOfflineInstallProcess(
                  is_legacy_install, updater_exe.value(), scope, kTestAppID,
                  offline_dir_guid, is_silent_install)
                  .IsValid());

  // * Silent installs do not show any UI.
  // * Successful interactive installs show a progress UI, but once the install
  //   completes, the `InstallerSuccessLaunchCmdLine` is launched and the UI
  //   closes automatically.
  // * Unsuccessful interactive installs show an install error dialog that needs
  //   to be explicitly closed via `CloseInstallCompleteDialog`.
  if (is_silent_install || expect_success) {
    EXPECT_TRUE(WaitForUpdaterExit());
  } else {
    CloseInstallCompleteDialog(GetLocalizedString(string_resource_id_to_find));
  }

  scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
  ASSERT_TRUE(global_prefs);
  const base::Version pv =
      base::MakeRefCounted<PersistedData>(scope, global_prefs->GetPrefService(),
                                          nullptr)
          ->GetProductVersion(base::WideToASCII(kTestAppID));

  base::win::RegKey key;
  LONG registry_result =
      key.Open(root, app_client_state_key.c_str(), Wow6432(KEY_QUERY_VALUE));

  if (!expect_success) {
    EXPECT_EQ(registry_result, ERROR_FILE_NOT_FOUND);
    EXPECT_FALSE(pv.IsValid());
    return;
  }

  EXPECT_EQ(registry_result, ERROR_SUCCESS);

  // Updater should have written "pv".
  ASSERT_TRUE(pv.IsValid());
  EXPECT_EQ(pv, kTestPV);

  // Check for expected installer result API reg values.
  base::win::RegKey updater_key(root, UPDATER_KEY, Wow6432(KEY_QUERY_VALUE));
  ASSERT_TRUE(updater_key.Valid());
  for (const base::win::RegKey& regkey :
       {std::cref(key), std::cref(updater_key)}) {
    std::wstring value;
    EXPECT_EQ(regkey.ReadValue(kRegValueLastInstallerResultUIString, &value),
              ERROR_SUCCESS);
    EXPECT_EQ(value, L"CoolApp");
  }

  if (!is_silent_install) {
    // Silent install does not run post-install command. For other cases the
    // event should have been signaled by the post-install command via the
    // installer result API.
    EXPECT_TRUE(
        event_holder.event.TimedWait(TestTimeouts::action_max_timeout()));
  }

  EXPECT_TRUE(DeleteRegKey(root, app_client_state_key));
}

}  // namespace

base::FilePath GetSetupExecutablePath() {
  base::FilePath out_dir;
  if (!base::PathService::Get(base::DIR_EXE, &out_dir)) {
    return base::FilePath();
  }
  return out_dir.AppendASCII("UpdaterSetup_test.exe");
}

void Clean(UpdaterScope scope) {
  VLOG(0) << __func__;

  CleanProcesses();

  const HKEY root = UpdaterScopeToHKeyRoot(scope);
  for (const wchar_t* key : {CLIENT_STATE_KEY, CLIENTS_KEY, UPDATER_KEY}) {
    EXPECT_TRUE(DeleteRegKey(root, key));
  }

  if (::IsUserAnAdmin()) {
    for (const wchar_t* key :
         {kRegKeyCompanyCloudManagement, kRegKeyCompanyEnrollment,
          UPDATER_POLICIES_KEY}) {
      EXPECT_TRUE(DeleteRegKey(HKEY_LOCAL_MACHINE, key));
    }
  }

  for (const CLSID& clsid :
       JoinVectors(GetSideBySideServers(scope), GetActiveServers(scope))) {
    EXPECT_TRUE(DeleteRegKeyCOM(root, GetComServerClsidRegistryPath(clsid)));
    if (IsSystemInstall(scope)) {
      EXPECT_TRUE(DeleteRegKeyCOM(root, GetComServerAppidRegistryPath(clsid)));
    }

    const std::wstring progid(GetProgIdForClsid(clsid));
    if (!progid.empty()) {
      EXPECT_TRUE(DeleteRegKeyCOM(root, GetComProgIdRegistryPath(progid)));
    }
  }

  // To avoid `TYPE_E_CANTLOADLIBRARY` errors due to a failed cleanup of a
  // previous user test run, this code cleans up both system and user
  // interface/typelib entries when running system tests.
  for (const UpdaterScope interface_scope : [&]() -> std::vector<UpdaterScope> {
         if (IsSystemInstall(scope)) {
           return {scope, UpdaterScope::kUser};
         } else {
           return {scope};
         }
       }()) {
    for (const auto& [iid, interface_name] :
         JoinVectors(GetSideBySideInterfaces(interface_scope),
                     GetActiveInterfaces(interface_scope))) {
      const HKEY interface_root = UpdaterScopeToHKeyRoot(interface_scope);
      EXPECT_TRUE(DeleteRegKey(interface_root, GetComIidRegistryPath(iid)));
      EXPECT_TRUE(DeleteRegKey64(interface_root, GetComIidRegistryPath(iid)));
      EXPECT_TRUE(
          DeleteRegKeyCOM(interface_root, GetComTypeLibRegistryPath(iid)));
    }
  }

  if (!IsSystemInstall(scope)) {
    ForEachRegistryRunValueWithPrefix(
        base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
        [](const std::wstring& run_name) {
          base::win::RegKey(HKEY_CURRENT_USER, REGSTR_PATH_RUN, KEY_WRITE)
              .DeleteValue(run_name.c_str());
        });
  }

  if (IsSystemInstall(scope)) {
    ForEachServiceWithPrefix(base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
                             base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
                             [](const std::wstring& service_name) {
                               EXPECT_TRUE(DeleteService(service_name));
                             });
  }

  scoped_refptr<TaskScheduler> task_scheduler =
      TaskScheduler::CreateInstance(scope);
  task_scheduler->ForEachTaskWithPrefix(
      base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
      [&task_scheduler](const std::wstring& task_name) {
        EXPECT_TRUE(task_scheduler->DeleteTask(task_name));
      });

  const std::optional<base::FilePath> target_path =
      GetGoogleUpdateExePath(scope);
  if (target_path) {
    base::DeleteFile(*target_path);
  }

  std::optional<base::FilePath> path = GetInstallDirectory(scope);
  ASSERT_TRUE(path);
  ASSERT_TRUE(base::DeletePathRecursively(*path)) << *path;

  // Delete any updater logs in %TMP%.
  for (const auto& file : GetUpdaterLogFilesInTmp()) {
    ASSERT_TRUE(base::DeleteFile(file));
  }
}

void EnterTestMode(const GURL& update_url,
                   const GURL& crash_upload_url,
                   const GURL& device_management_url,
                   const GURL& app_logo_url,
                   const base::TimeDelta& idle_timeout) {
  ASSERT_TRUE(ExternalConstantsBuilder()
                  .SetUpdateURL(std::vector<std::string>{update_url.spec()})
                  .SetCrashUploadURL(crash_upload_url.spec())
                  .SetDeviceManagementURL(device_management_url.spec())
                  .SetAppLogoURL(app_logo_url.spec())
                  .SetUseCUP(false)
                  .SetInitialDelay(base::Milliseconds(100))
                  .SetServerKeepAliveTime(base::Seconds(1))
                  .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3)
                  .SetOverinstallTimeout(base::Seconds(11))
                  .SetIdleCheckPeriod(idle_timeout)
                  .Modify());
}

void ExpectInstalled(UpdaterScope scope) {
  CheckInstallation(scope, CheckInstallationStatus::kCheckIsInstalled,
                    CheckInstallationVersions::kCheckSxSOnly);
}

void ExpectClean(UpdaterScope scope) {
  ExpectCleanProcesses();
  CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled,
                    CheckInstallationVersions::kCheckActiveAndSxS);

  // Check that the caches have been removed.
  const std::optional<base::FilePath> path = GetCacheBaseDirectory(scope);
  ASSERT_TRUE(path);
  EXPECT_TRUE(
      WaitFor([&] { return !base::PathExists(*path); },
              [&] { VLOG(0) << "Still waiting for cache removal: " << *path; }))
      << base::JoinString(
             [&path] {
               std::vector<base::FilePath::StringType> files;
               base::FileEnumerator(*path, true,
                                    base::FileEnumerator::FILES |
                                        base::FileEnumerator::DIRECTORIES)
                   .ForEach([&files](const base::FilePath& name) {
                     files.push_back(name.value());
                   });

               return files;
             }(),
             FILE_PATH_LITERAL(","));
}

void ExpectCandidateUninstalled(UpdaterScope scope) {
  CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled,
                    CheckInstallationVersions::kCheckSxSOnly);
}

void Uninstall(UpdaterScope scope) {
  // Note: the updater uninstall is run from the build dir, not the install dir,
  // because it is useful for tests to be able to run it to clean the system
  // even if installation has failed or the installed binaries have already been
  // removed.

  // The updater setup executable is used instead of `updater` because setup
  // knows how to de-elevate when run at high integrity to uninstall a per-user
  // install, which is what is done in the
  // `IntegrationTestUserInSystem.ElevatedInstallOfUserUpdaterAndApp` test.
  base::CommandLine command_line(GetSetupExecutablePath());
  ASSERT_FALSE(command_line.GetProgram().empty());
  command_line.AppendSwitch(kUninstallSwitch);
  int exit_code = -1;
  Run(scope, command_line, &exit_code);

  // Uninstallation involves a race with the uninstall.cmd script and the
  // process exit. Sleep to allow the script to complete its work.
  SleepFor(base::Seconds(5));
  ASSERT_EQ(0, exit_code);
}

void SetActive(UpdaterScope /*scope*/, const std::string& id) {
  base::win::RegKey key;
  ASSERT_EQ(key.Create(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
                       Wow6432(KEY_WRITE)),
            ERROR_SUCCESS);
  EXPECT_EQ(key.WriteValue(kDidRun, L"1"), ERROR_SUCCESS);
}

void ExpectActive(UpdaterScope /*scope*/, const std::string& id) {
  base::win::RegKey key;
  ASSERT_EQ(key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
                     Wow6432(KEY_READ)),
            ERROR_SUCCESS);
  std::wstring value;
  ASSERT_EQ(key.ReadValue(kDidRun, &value), ERROR_SUCCESS);
  EXPECT_EQ(value, L"1");
}

void ExpectNotActive(UpdaterScope /*scope*/, const std::string& id) {
  base::win::RegKey key;
  if (key.Open(HKEY_CURRENT_USER, GetAppClientStateKey(id).c_str(),
               Wow6432(KEY_READ)) == ERROR_SUCCESS) {
    std::wstring value;
    if (key.ReadValue(kDidRun, &value) == ERROR_SUCCESS) {
      EXPECT_EQ(value, L"0");
    }
  }
}

// Waits for all updater processes to end, including the server process holding
// the prefs lock.
bool WaitForUpdaterExit() {
  VersionProcessFilter filter;
  return WaitFor(
      [&] {
        return !test::IsProcessRunning(GetExecutableRelativePath().value(),
                                       &filter);
      },
      [&] {
        VLOG(0) << "Still waiting for updater to exit. "
                << test::PrintProcesses(GetExecutableRelativePath().value(),
                                        &filter);
      });
}

// Verify registry entries for all interfaces.
// IID entries under `Software\Classes\Interface`:
// * ProxyStubClsid32 entry should point to the OLE automation marshaler
// * TypeLib entry should be equal to the IID.
//
// TypeLib entries under `Software\Classes\TypeLib`:
// * Read the typelib path under both `win32` and `win64`.
// * Confirm that the typelib can be loaded using ::LoadTypeLib.
// * Confirm that the typeinfo for each interface can be loaded from the
// typelib.
void VerifyInterfacesRegistryEntries(UpdaterScope scope) {
  for (const auto is_internal : {true, false}) {
    for (const auto& [iid, interface_name] :
         GetInterfaces(is_internal, scope)) {
      const HKEY root = UpdaterScopeToHKeyRoot(scope);
      const std::wstring iid_reg_path = GetComIidRegistryPath(iid);
      const std::wstring typelib_reg_path = GetComTypeLibRegistryPath(iid);
      const std::wstring iid_string = StringFromGuid(iid);

      for (const auto& key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
        std::wstring val;
        EXPECT_EQ(ReadRegValue(root, iid_reg_path, L"", key_flag),
                  interface_name);
        EXPECT_EQ(ReadRegValue(root, iid_reg_path + L"\\ProxyStubClsid32", L"",
                               key_flag),
                  L"{00020424-0000-0000-C000-000000000046}");
        EXPECT_EQ(
            ReadRegValue(root, iid_reg_path + L"\\TypeLib", L"", key_flag),
            iid_string);
        EXPECT_EQ(ReadRegValue(root, iid_reg_path + L"\\TypeLib", L"Version",
                               key_flag),
                  L"1.0");
      }

      EXPECT_EQ(ReadRegValue(root, typelib_reg_path + L"\\1.0", L"", 0),
                base::StrCat({PRODUCT_FULLNAME_STRING L" TypeLib for ",
                              interface_name}));
      const std::wstring typelib_reg_path_win32 =
          typelib_reg_path + L"\\1.0\\0\\win32";
      const std::wstring typelib_reg_path_win64 =
          typelib_reg_path + L"\\1.0\\0\\win64";

      for (const auto& path :
           {typelib_reg_path_win32, typelib_reg_path_win64}) {
        std::wstring typelib_path;
        EXPECT_EQ(base::win::RegKey(root, path.c_str(), KEY_READ)
                      .ReadValue(L"", &typelib_path),
                  ERROR_SUCCESS)
            << ": " << root << ": " << path << ": " << iid_string;

        Microsoft::WRL::ComPtr<ITypeLib> type_lib;
        EXPECT_HRESULT_SUCCEEDED(::LoadTypeLib(typelib_path.c_str(), &type_lib))
            << ": Typelib path: " << typelib_path;

        Microsoft::WRL::ComPtr<ITypeInfo> type_info;
        EXPECT_HRESULT_SUCCEEDED(type_lib->GetTypeInfoOfGuid(iid, &type_info))
            << ": Typelib path: " << typelib_path << ": IID: " << iid_string;
      }
    }
  }
}

void ExpectNoLegacyEntriesPerUser() {
  // The IProcessLauncher and IProcessLauncher2 interfaces are now only
  // registered for system since r1154562. So verify that these do not exist in
  // the user hive.
  for (const auto& iid :
       {__uuidof(IProcessLauncher), __uuidof(IProcessLauncher2)}) {
    for (const auto& reg_path :
         {GetComIidRegistryPath(iid), GetComTypeLibRegistryPath(iid)}) {
      EXPECT_FALSE(RegKeyExistsCOM(HKEY_CURRENT_USER, reg_path));
    }
  }
}

// Tests if the typelibs and some of the public, internal, and
// legacy interfaces are available. Failure to query these interfaces indicates
// an issue with typelib registration.
void ExpectInterfacesRegistered(UpdaterScope scope) {
  {
    // IUpdater, IGoogleUpdate3Web and IAppBundleWeb.
    // The block is necessary so that updater_server goes out of scope and
    // releases the prefs lock before updater_internal_server tries to acquire
    // it to mode-check.
    Microsoft::WRL::ComPtr<IUnknown> updater_server;
    ASSERT_HRESULT_SUCCEEDED(
        CreateLocalServer(IsSystemInstall(scope) ? __uuidof(UpdaterSystemClass)
                                                 : __uuidof(UpdaterUserClass),
                          updater_server));
    Microsoft::WRL::ComPtr<IUpdater> updater;
    EXPECT_HRESULT_SUCCEEDED(
        updater_server.CopyTo(IsSystemInstall(scope) ? __uuidof(IUpdaterSystem)
                                                     : __uuidof(IUpdaterUser),
                              IID_PPV_ARGS_Helper(&updater)));

    // Verifies that the progid for the legacy clsid is registered.
    CLSID expected_clsid = {};
    EXPECT_HRESULT_SUCCEEDED(::CLSIDFromProgID(
        IsSystemInstall(scope) ? kGoogleUpdate3WebSystemClassProgId
                               : kGoogleUpdate3WebUserClassProgId,
        &expected_clsid));
    EXPECT_EQ(expected_clsid, IsSystemInstall(scope)
                                  ? __uuidof(GoogleUpdate3WebSystemClass)
                                  : __uuidof(GoogleUpdate3WebUserClass));

    for (const CLSID& clsid : [&scope]() -> std::vector<CLSID> {
           if (IsSystemInstall(scope)) {
             return {__uuidof(GoogleUpdate3WebSystemClass),
                     __uuidof(GoogleUpdate3WebServiceClass)};
           } else {
             return {__uuidof(GoogleUpdate3WebUserClass)};
           }
         }()) {
      Microsoft::WRL::ComPtr<IUnknown> updater_legacy_server;
      ASSERT_HRESULT_SUCCEEDED(CreateLocalServer(clsid, updater_legacy_server));

      // The non-user/system-specialized interfaces are registered for all
      // installs for backward compatibility.
      Microsoft::WRL::ComPtr<IGoogleUpdate3Web> google_update;
      ASSERT_HRESULT_SUCCEEDED(updater_legacy_server.As(&google_update));
      google_update.Reset();
      EXPECT_HRESULT_SUCCEEDED(updater_legacy_server.CopyTo(
          IsSystemInstall(scope) ? __uuidof(IGoogleUpdate3WebSystem)
                                 : __uuidof(IGoogleUpdate3WebUser),
          IID_PPV_ARGS_Helper(&google_update)));
      Microsoft::WRL::ComPtr<IAppBundleWeb> app_bundle;
      Microsoft::WRL::ComPtr<IDispatch> dispatch;
      ASSERT_HRESULT_SUCCEEDED(google_update->createAppBundleWeb(&dispatch));
      EXPECT_HRESULT_SUCCEEDED(dispatch.As(&app_bundle));
      app_bundle.Reset();
      EXPECT_HRESULT_SUCCEEDED(
          dispatch.CopyTo(IsSystemInstall(scope) ? __uuidof(IAppBundleWebSystem)
                                                 : __uuidof(IAppBundleWebUser),
                          IID_PPV_ARGS_Helper(&app_bundle)));
    }
  }

  {
    // IUpdaterInternal.
    Microsoft::WRL::ComPtr<IUnknown> updater_internal_server;
    ASSERT_HRESULT_SUCCEEDED(CreateLocalServer(
        IsSystemInstall(scope) ? __uuidof(UpdaterInternalSystemClass)
                               : __uuidof(UpdaterInternalUserClass),
        updater_internal_server));
    Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
    EXPECT_HRESULT_SUCCEEDED(updater_internal_server.CopyTo(
        IsSystemInstall(scope) ? __uuidof(IUpdaterInternalSystem)
                               : __uuidof(IUpdaterInternalUser),
        IID_PPV_ARGS_Helper(&updater_internal)));
  }

  VerifyInterfacesRegistryEntries(scope);
  if (!IsSystemInstall(scope)) {
    ExpectNoLegacyEntriesPerUser();
  }
}

void ExpectMarshalInterfaceSucceeds(UpdaterScope scope) {
  // Create proxy/stubs for the IUpdaterInternal interface.
  // Look up the ProxyStubClsid32.
  CLSID psclsid = {};
  REFIID iupdaterinternal_iid = IsSystemInstall(scope)
                                    ? __uuidof(IUpdaterInternalSystem)
                                    : __uuidof(IUpdaterInternalUser);
  EXPECT_HRESULT_SUCCEEDED(::CoGetPSClsid(iupdaterinternal_iid, &psclsid));
  EXPECT_EQ(base::ToUpperASCII(StringFromGuid(psclsid)),
            L"{00020424-0000-0000-C000-000000000046}");

  // Get the proxy/stub factory buffer.
  Microsoft::WRL::ComPtr<IPSFactoryBuffer> psfb;
  EXPECT_HRESULT_SUCCEEDED(
      ::CoGetClassObject(psclsid, CLSCTX_INPROC, 0, IID_PPV_ARGS(&psfb)));

  // Create the interface proxy.
  Microsoft::WRL::ComPtr<IRpcProxyBuffer> proxy_buffer;
  Microsoft::WRL::ComPtr<IUpdaterInternal> object;
  EXPECT_HRESULT_SUCCEEDED(psfb->CreateProxy(nullptr, iupdaterinternal_iid,
                                             &proxy_buffer,
                                             IID_PPV_ARGS_Helper(&object)));

  // Create the interface stub.
  Microsoft::WRL::ComPtr<IRpcStubBuffer> stub_buffer;
  EXPECT_HRESULT_SUCCEEDED(
      psfb->CreateStub(iupdaterinternal_iid, nullptr, &stub_buffer));

  // Marshal and unmarshal an IUpdaterInternal object.
  Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
  EXPECT_HRESULT_SUCCEEDED(
      MakeAndInitializeComObject<UpdaterInternalImpl>(updater_internal));

  Microsoft::WRL::ComPtr<IStream> stream;
  EXPECT_HRESULT_SUCCEEDED(::CoMarshalInterThreadInterfaceInStream(
      iupdaterinternal_iid, updater_internal.Get(), &stream));

  base::WaitableEvent unmarshal_complete_event;

  base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})
      ->PostTask(
          FROM_HERE,
          base::BindOnce(
              [](Microsoft::WRL::ComPtr<IStream> stream,
                 REFIID iupdaterinternal_iid, base::WaitableEvent& event) {
                const base::ScopedClosureRunner signal_event(base::BindOnce(
                    [](base::WaitableEvent& event) { event.Signal(); },
                    std::ref(event)));

                Microsoft::WRL::ComPtr<IUpdaterInternal> updater_internal;
                EXPECT_HRESULT_SUCCEEDED(::CoUnmarshalInterface(
                    stream.Get(), iupdaterinternal_iid,
                    IID_PPV_ARGS_Helper(&updater_internal)));
              },
              stream, iupdaterinternal_iid,
              std::ref(unmarshal_complete_event)));

  EXPECT_TRUE(
      unmarshal_complete_event.TimedWait(TestTimeouts::action_max_timeout()));
}

void InitializeBundle(UpdaterScope scope,
                      Microsoft::WRL::ComPtr<IAppBundleWeb>& bundle_web) {
  Microsoft::WRL::ComPtr<IGoogleUpdate3Web> update3web;
  ASSERT_HRESULT_SUCCEEDED(CreateLocalServer(
      IsSystemInstall(scope) ? __uuidof(GoogleUpdate3WebSystemClass)
                             : __uuidof(GoogleUpdate3WebUserClass),
      update3web));

  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
  Microsoft::WRL::ComPtr<IDispatch> dispatch;
  ASSERT_HRESULT_SUCCEEDED(update3web->createAppBundleWeb(&dispatch));
  ASSERT_HRESULT_SUCCEEDED(dispatch.As(&bundle));
  bundle.Reset();
  ASSERT_HRESULT_SUCCEEDED(dispatch.CopyTo(IsSystemInstall(scope)
                                               ? __uuidof(IAppBundleWebSystem)
                                               : __uuidof(IAppBundleWebUser),
                                           IID_PPV_ARGS_Helper(&bundle)));
  EXPECT_HRESULT_SUCCEEDED(bundle->initialize());

  bundle_web = bundle;
}

HRESULT DoUpdate(UpdaterScope scope,
                 const base::win::ScopedBstr& appid,
                 AppBundleWebCreateMode app_bundle_web_create_mode,
                 int expected_final_state,
                 HRESULT expected_error_code,
                 bool cancel_when_downloading) {
  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
  InitializeBundle(scope, bundle);
  EXPECT_TRUE(bundle);
  EXPECT_HRESULT_SUCCEEDED(
      app_bundle_web_create_mode == AppBundleWebCreateMode::kCreateInstalledApp
          ? bundle->createInstalledApp(appid.Get())
          : bundle->createApp(appid.Get(), base::win::ScopedBstr(L"BRND").Get(),
                              base::win::ScopedBstr(L"en").Get(),
                              base::win::ScopedBstr(L"DoUpdateAP").Get()));
  EXPECT_HRESULT_SUCCEEDED(bundle->checkForUpdate());
  bool done = false;
  static const base::TimeDelta kExpirationTimeout =
      2 * TestTimeouts::action_max_timeout();
  base::ElapsedTimer timer;

  LONG state_value = 0;
  LONG error_code = 0;
  std::wstring extra_data;
  Microsoft::WRL::ComPtr<IDispatch> app_dispatch;
  EXPECT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch));
  Microsoft::WRL::ComPtr<IAppWeb> app;
  EXPECT_HRESULT_SUCCEEDED(app_dispatch.As(&app));
  app.Reset();
  EXPECT_HRESULT_SUCCEEDED(app_dispatch.CopyTo(
      IsSystemInstall(scope) ? __uuidof(IAppWebSystem) : __uuidof(IAppWebUser),
      IID_PPV_ARGS_Helper(&app)));

  if (app_bundle_web_create_mode == AppBundleWebCreateMode::kCreateApp) {
    EXPECT_HRESULT_SUCCEEDED(app->put_serverInstallDataIndex(
        base::win::ScopedBstr(L"expected_install_data_index").Get()));
    base::win::ScopedBstr install_data_index;
    EXPECT_HRESULT_SUCCEEDED(
        app->get_serverInstallDataIndex(install_data_index.Receive()));
    EXPECT_STREQ(install_data_index.Get(), L"expected_install_data_index");
  }

  while (!done && (timer.Elapsed() < kExpirationTimeout)) {
    Microsoft::WRL::ComPtr<IDispatch> state_dispatch;
    EXPECT_HRESULT_SUCCEEDED(app->get_currentState(&state_dispatch));
    Microsoft::WRL::ComPtr<ICurrentState> state;
    EXPECT_HRESULT_SUCCEEDED(state_dispatch.As(&state));
    state.Reset();
    EXPECT_HRESULT_SUCCEEDED(state_dispatch.CopyTo(
        IsSystemInstall(scope) ? __uuidof(ICurrentStateSystem)
                               : __uuidof(ICurrentStateUser),
        IID_PPV_ARGS_Helper(&state)));
    EXPECT_HRESULT_SUCCEEDED(state->get_stateValue(&state_value));
    EXPECT_HRESULT_SUCCEEDED(state->get_errorCode(&error_code));

    std::wstring state_description;
    done = state_value == expected_final_state;
    switch (state_value) {
      case STATE_INIT:
        state_description = L"Initializating...";
        break;

      case STATE_WAITING_TO_CHECK_FOR_UPDATE:
      case STATE_CHECKING_FOR_UPDATE: {
        state_description = L"Checking for update...";
        Microsoft::WRL::ComPtr<IDispatch> current_version_web_dispatch;
        EXPECT_HRESULT_SUCCEEDED(
            app->get_currentVersionWeb(&current_version_web_dispatch));
        extra_data = base::StrCat(
            {L"[Current Version: ",
             GetAppVersionWebString(current_version_web_dispatch), L"]"});
        break;
      }

      case STATE_UPDATE_AVAILABLE: {
        state_description = L"Update available!";
        Microsoft::WRL::ComPtr<IDispatch> next_version_web_dispatch;
        EXPECT_HRESULT_SUCCEEDED(
            app->get_nextVersionWeb(&next_version_web_dispatch));
        extra_data = base::StrCat(
            {L"[Next Version: ",
             GetAppVersionWebString(next_version_web_dispatch), L"]"});
        if (!done) {
          EXPECT_HRESULT_SUCCEEDED(bundle->download());
        }

        break;
      }

      case STATE_WAITING_TO_DOWNLOAD:
      case STATE_RETRYING_DOWNLOAD:
        state_description = L"Contacting server...";
        break;

      case STATE_DOWNLOADING: {
        state_description = L"Downloading...";
        ULONG bytes_downloaded = 0;
        state->get_bytesDownloaded(&bytes_downloaded);
        ULONG total_bytes_to_download = 0;
        state->get_totalBytesToDownload(&total_bytes_to_download);
        LONG download_time_remaining_ms = 0;
        state->get_downloadTimeRemainingMs(&download_time_remaining_ms);
        extra_data = base::ASCIIToWide(base::StringPrintf(
            "[Bytes downloaded: %lu][Bytes total: %lu][Time remaining: %ld]",
            bytes_downloaded, total_bytes_to_download,
            download_time_remaining_ms));

        if (cancel_when_downloading) {
          EXPECT_HRESULT_SUCCEEDED(bundle->cancel());
        }

        break;
      }

      case STATE_DOWNLOAD_COMPLETE:
      case STATE_EXTRACTING:
      case STATE_APPLYING_DIFFERENTIAL_PATCH:
      case STATE_READY_TO_INSTALL: {
        state_description = L"Ready to install!";
        ULONG bytes_downloaded = 0;
        state->get_bytesDownloaded(&bytes_downloaded);
        ULONG total_bytes_to_download = 0;
        state->get_totalBytesToDownload(&total_bytes_to_download);
        extra_data = base::ASCIIToWide(
            base::StringPrintf("[Bytes downloaded: %lu][Bytes total: %lu]",
                               bytes_downloaded, total_bytes_to_download));
        EXPECT_HRESULT_SUCCEEDED(bundle->install());
        break;
      }

      case STATE_WAITING_TO_INSTALL:
      case STATE_INSTALLING: {
        state_description = L"Installing...";
        LONG install_progress = 0;
        state->get_installProgress(&install_progress);
        LONG install_time_remaining_ms = 0;
        state->get_installTimeRemainingMs(&install_time_remaining_ms);
        extra_data = base::ASCIIToWide(
            base::StringPrintf("[Install Progress: %ld][Time remaining: %ld]",
                               install_progress, install_time_remaining_ms));
        break;
      }

      case STATE_INSTALL_COMPLETE:
        state_description = L"Done!";
        break;

      case STATE_PAUSED:
        state_description = L"Paused...";
        break;

      case STATE_NO_UPDATE:
        state_description = L"No update available!";
        break;

      case STATE_ERROR: {
        state_description = L"Error!";
        base::win::ScopedBstr completion_message;
        EXPECT_HRESULT_SUCCEEDED(
            state->get_completionMessage(completion_message.Receive()));
        LONG installer_result_code = 0;
        EXPECT_HRESULT_SUCCEEDED(
            state->get_installerResultCode(&installer_result_code));
        extra_data = base::ASCIIToWide(base::StringPrintf(
            "[errorCode: %ld][completionMessage: %ls][installerResultCode: "
            "%ld]",
            error_code, completion_message.Get(), installer_result_code));
        break;
      }

      default:
        state_description = L"Unhandled state...";
        break;
    }

    base::PlatformThread::Sleep(base::Seconds(1));
  }

  EXPECT_TRUE(done)
      << "The test timed out, consider increasing kExpirationTimeout which is: "
      << kExpirationTimeout << ": " << extra_data;
  EXPECT_EQ(expected_final_state, state_value) << extra_data;
  EXPECT_EQ(expected_error_code, error_code) << extra_data;
  return S_OK;
}

void ExpectLegacyUpdate3WebSucceeds(
    UpdaterScope scope,
    const std::string& app_id,
    AppBundleWebCreateMode app_bundle_web_create_mode,
    int expected_final_state,
    int expected_error_code,
    bool cancel_when_downloading) {
  EXPECT_HRESULT_SUCCEEDED(
      DoUpdate(scope, base::win::ScopedBstr(base::UTF8ToWide(app_id).c_str()),
               app_bundle_web_create_mode, expected_final_state,
               expected_error_code, cancel_when_downloading));
}

void SetupLaunchCommandElevated(const std::wstring& app_id,
                                const std::wstring& name,
                                const std::wstring& pv,
                                const std::wstring& command_id,
                                const std::wstring& command_parameters,
                                base::ScopedTempDir& temp_dir) {
  base::CommandLine cmd_exe_command_line(base::CommandLine::NO_PROGRAM);
  SetupCmdExe(UpdaterScope::kSystem, cmd_exe_command_line, temp_dir);
  CreateLaunchCmdElevatedRegistry(
      app_id, name, pv, command_id,
      base::StrCat({cmd_exe_command_line.GetCommandLineString(),
                    command_parameters.c_str()}));

  // Enable usage stats to allow this launch command to send an app command
  // ping.
  base::win::RegKey client_state_key(
      CreateAppClientStateKey(UpdaterScope::kSystem, app_id));
  EXPECT_EQ(client_state_key.WriteValue(L"usagestats", 1), ERROR_SUCCESS);
}

void DeleteLaunchCommandElevated(const std::wstring& app_id,
                                 const std::wstring& command_id) {
  DeleteAppClientStateKey(UpdaterScope::kSystem, app_id);
  EXPECT_EQ(CreateAppClientKey(UpdaterScope::kSystem, app_id)
                .DeleteValue(command_id.c_str()),
            ERROR_SUCCESS);
}

HRESULT ProcessLaunchCmdElevated(
    Microsoft::WRL::ComPtr<IProcessLauncher> process_launcher,
    const std::wstring& appid,
    const std::wstring& commandid,
    const int expected_exit_code) {
  ULONG_PTR proc_handle = 0;
  HRESULT hr = process_launcher->LaunchCmdElevated(
      appid.c_str(), commandid.c_str(), ::GetCurrentProcessId(), &proc_handle);
  if (FAILED(hr)) {
    return hr;
  }

  EXPECT_NE(static_cast<ULONG_PTR>(0), proc_handle);

  const base::Process process(reinterpret_cast<HANDLE>(proc_handle));
  int exit_code = 0;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  EXPECT_EQ(exit_code, expected_exit_code);

  return hr;
}

void ExpectLegacyProcessLauncherSucceeds(UpdaterScope scope) {
  // ProcessLauncher is only implemented for kSystem at the moment.
  if (!IsSystemInstall(scope)) {
    return;
  }

  Microsoft::WRL::ComPtr<IUnknown> unknown;
  ASSERT_HRESULT_SUCCEEDED(
      CreateLocalServer(__uuidof(ProcessLauncherClass), unknown));
  Microsoft::WRL::ComPtr<IProcessLauncher> process_launcher;
  EXPECT_HRESULT_SUCCEEDED(unknown.As(&process_launcher));
  process_launcher.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      unknown.CopyTo(__uuidof(IProcessLauncherSystem),
                     IID_PPV_ARGS_Helper(&process_launcher)));

  Microsoft::WRL::ComPtr<IProcessLauncher2> process_launcher2;
  EXPECT_HRESULT_SUCCEEDED(unknown.As(&process_launcher2));
  process_launcher2.Reset();
  EXPECT_HRESULT_SUCCEEDED(
      unknown.CopyTo(__uuidof(IProcessLauncher2System),
                     IID_PPV_ARGS_Helper(&process_launcher2)));

  static constexpr wchar_t kAppId1[] =
      L"{831EF4D0-B729-4F61-AA34-91526481799D}";
  static constexpr wchar_t kCommandId[] = L"cmd";

  // Succeeds when the command is present in the registry.
  base::ScopedTempDir temp_dir;
  SetupLaunchCommandElevated(kAppId1, L"" BROWSER_PRODUCT_NAME_STRING,
                             L"1.0.0.0", kCommandId, L" /c \"exit 5420\"",
                             temp_dir);

  // Succeeds when the command is present in the registry.
  ASSERT_HRESULT_SUCCEEDED(
      ProcessLaunchCmdElevated(process_launcher, kAppId1, kCommandId, 5420));

  // Allows time for the app command ping to be sent.
  base::PlatformThread::Sleep(base::Seconds(1));

  DeleteLaunchCommandElevated(kAppId1, kCommandId);
  EXPECT_EQ(
      HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND),
      ProcessLaunchCmdElevated(process_launcher, kAppId1, kCommandId, 5420));

  base::ScopedTempDir app_command_temp_dir;
  SetupAppCommand(scope, kAppId1, kCommandId, L" /c \"exit 11555\"",
                  app_command_temp_dir);
  ASSERT_HRESULT_SUCCEEDED(
      ProcessLaunchCmdElevated(process_launcher, kAppId1, kCommandId, 11555));

  DeleteAppClientKey(scope, kAppId1);
}

void ExpectLegacyAppCommandWebSucceeds(UpdaterScope scope,
                                       const std::string& app_id,
                                       const std::string& command_id,
                                       const base::Value::List& parameters,
                                       int expected_exit_code) {
  const size_t kMaxParameters = 9;
  ASSERT_LE(parameters.size(), kMaxParameters);

  base::ScopedTempDir temp_dir;
  const std::wstring appid = base::UTF8ToWide(app_id);
  const std::wstring commandid = base::UTF8ToWide(command_id);

  SetupAppCommand(scope, appid, commandid, L" /c \"exit %1\"", temp_dir);

  Microsoft::WRL::ComPtr<IAppBundleWeb> bundle;
  InitializeBundle(scope, bundle);
  ASSERT_HRESULT_SUCCEEDED(
      bundle->createInstalledApp(base::win::ScopedBstr(appid).Get()));

  Microsoft::WRL::ComPtr<IDispatch> app_dispatch;
  ASSERT_HRESULT_SUCCEEDED(bundle->get_appWeb(0, &app_dispatch));
  Microsoft::WRL::ComPtr<IAppWeb> app;
  ASSERT_HRESULT_SUCCEEDED(app_dispatch.As(&app));
  app.Reset();
  ASSERT_HRESULT_SUCCEEDED(app_dispatch.CopyTo(
      IsSystemInstall(scope) ? __uuidof(IAppWebSystem) : __uuidof(IAppWebUser),
      IID_PPV_ARGS_Helper(&app)));

  Microsoft::WRL::ComPtr<IDispatch> command_dispatch;
  ASSERT_HRESULT_SUCCEEDED(app->get_command(
      base::win::ScopedBstr(commandid).Get(), &command_dispatch));
  Microsoft::WRL::ComPtr<IAppCommandWeb> app_command_web;
  ASSERT_HRESULT_SUCCEEDED(command_dispatch.As(&app_command_web));
  app_command_web.Reset();
  ASSERT_HRESULT_SUCCEEDED(command_dispatch.CopyTo(
      IsSystemInstall(scope) ? __uuidof(IAppCommandWebSystem)
                             : __uuidof(IAppCommandWebUser),
      IID_PPV_ARGS_Helper(&app_command_web)));

  std::vector<base::win::ScopedVariant> variant_params;
  variant_params.reserve(kMaxParameters);
  base::ranges::transform(parameters, std::back_inserter(variant_params),
                          [](const auto& param) {
                            return base::win::ScopedVariant(
                                base::UTF8ToWide(param.GetString()).c_str());
                          });
  for (size_t i = parameters.size(); i < kMaxParameters; ++i) {
    variant_params.emplace_back(base::win::ScopedVariant::kEmptyVariant);
  }

  ASSERT_HRESULT_SUCCEEDED(app_command_web->execute(
      variant_params[0], variant_params[1], variant_params[2],
      variant_params[3], variant_params[4], variant_params[5],
      variant_params[6], variant_params[7], variant_params[8]));

  EXPECT_TRUE(WaitFor([&] {
    UINT status = 0;
    EXPECT_HRESULT_SUCCEEDED(app_command_web->get_status(&status));
    return status == COMMAND_STATUS_COMPLETE;
  }));

  DWORD exit_code = 0;
  EXPECT_HRESULT_SUCCEEDED(app_command_web->get_exitCode(&exit_code));
  EXPECT_EQ(exit_code, static_cast<DWORD>(expected_exit_code));

  // Now also run the AppCommand using the IDispatch methods.
  command_dispatch.Reset();
  ASSERT_HRESULT_SUCCEEDED(app->get_command(
      base::win::ScopedBstr(commandid).Get(), &command_dispatch));

  CallDispatchMethod(command_dispatch, L"execute", variant_params);

  EXPECT_TRUE(WaitFor([&] {
    base::win::ScopedVariant status =
        GetDispatchProperty(command_dispatch, L"status");
    return V_UINT(status.ptr()) == COMMAND_STATUS_COMPLETE;
  }));

  base::win::ScopedVariant command_exit_code =
      GetDispatchProperty(command_dispatch, L"exitCode");
  EXPECT_EQ(V_UI4(command_exit_code.ptr()),
            static_cast<DWORD>(expected_exit_code));

  DeleteAppClientKey(scope, appid);
}

void ExpectPolicyStatusValues(
    Microsoft::WRL::ComPtr<IPolicyStatusValue> policy_status_value,
    const std::wstring& expected_source,
    const std::wstring& expected_value,
    VARIANT_BOOL expected_has_conflict) {
  base::win::ScopedBstr source;
  base::win::ScopedBstr value;
  VARIANT_BOOL has_conflict = VARIANT_FALSE;

  ASSERT_NE(policy_status_value.Get(), nullptr);
  EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_source(source.Receive()));
  EXPECT_EQ(source.Get(), expected_source);
  EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_value(value.Receive()));
  EXPECT_EQ(value.Get(), expected_value);
  EXPECT_HRESULT_SUCCEEDED(policy_status_value->get_hasConflict(&has_conflict));
  EXPECT_EQ(has_conflict, expected_has_conflict);
}

void ExpectLegacyPolicyStatusSucceeds(UpdaterScope scope) {
  Microsoft::WRL::ComPtr<IUnknown> policy_status_server;
  ASSERT_HRESULT_SUCCEEDED(CreateLocalServer(
      IsSystemInstall(scope) ? __uuidof(PolicyStatusSystemClass)
                             : __uuidof(PolicyStatusUserClass),
      policy_status_server));
  Microsoft::WRL::ComPtr<IPolicyStatus2> policy_status2;
  ASSERT_HRESULT_SUCCEEDED(policy_status_server.As(&policy_status2));
  policy_status2.Reset();
  ASSERT_HRESULT_SUCCEEDED(policy_status_server.CopyTo(
      IsSystemInstall(scope) ? __uuidof(IPolicyStatus2System)
                             : __uuidof(IPolicyStatus2User),
      IID_PPV_ARGS_Helper(&policy_status2)));

  base::win::ScopedBstr updater_version;
  ASSERT_HRESULT_SUCCEEDED(
      policy_status2->get_updaterVersion(updater_version.Receive()));
  EXPECT_STREQ(updater_version.Get(), kUpdaterVersionUtf16);

  DATE last_checked = 0;
  EXPECT_HRESULT_SUCCEEDED(policy_status2->get_lastCheckedTime(&last_checked));
  EXPECT_GT(static_cast<int>(last_checked), 0);

  Microsoft::WRL::ComPtr<IPolicyStatusValue> policy_status_value;
  ASSERT_HRESULT_SUCCEEDED(
      policy_status2->get_lastCheckPeriodMinutes(&policy_status_value));
  ExpectPolicyStatusValues(policy_status_value, L"Default", L"270",
                           VARIANT_FALSE);

  const base::win::ScopedBstr test_app(L"test1");
  policy_status_value.Reset();
  ASSERT_HRESULT_SUCCEEDED(policy_status2->get_effectivePolicyForAppInstalls(
      test_app.Get(), &policy_status_value));
  ExpectPolicyStatusValues(policy_status_value, L"Default", L"1",
                           VARIANT_FALSE);

  policy_status_value.Reset();
  ASSERT_HRESULT_SUCCEEDED(policy_status2->get_effectivePolicyForAppUpdates(
      test_app.Get(), &policy_status_value));
  ExpectPolicyStatusValues(policy_status_value, L"Default", L"1",
                           VARIANT_FALSE);

  policy_status_value.Reset();
  ASSERT_HRESULT_SUCCEEDED(policy_status2->get_isRollbackToTargetVersionAllowed(
      test_app.Get(), &policy_status_value));
  ExpectPolicyStatusValues(policy_status_value, L"Default", L"false",
                           VARIANT_FALSE);

  ASSERT_HRESULT_SUCCEEDED(policy_status2->refreshPolicies());
}

void InvokeTestServiceFunction(const std::string& function_name,
                               const base::Value::Dict& arguments) {
  std::string arguments_json_string;
  EXPECT_TRUE(base::JSONWriter::Write(arguments, &arguments_json_string));

  base::FilePath path(base::CommandLine::ForCurrentProcess()->GetProgram());
  path = path.DirName();
  path = MakeAbsoluteFilePath(path);
  path = path.Append(FILE_PATH_LITERAL("test_service"))
             .Append(FILE_PATH_LITERAL("service_client.py"));
  EXPECT_TRUE(base::PathExists(path));

  base::CommandLine command(path);
  command.AppendSwitchASCII("--function", function_name);
  command.AppendSwitchASCII("--args", arguments_json_string);
  EXPECT_EQ(RunVPythonCommand(command), 0);
}

base::FilePath GetRealUpdaterLowerVersionPath() {
  base::FilePath exe_path;
  EXPECT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_path));
  base::FilePath old_updater_path =
      exe_path.Append(FILE_PATH_LITERAL("old_updater"));

#if BUILDFLAG(CHROMIUM_BRANDING)
#if defined(ARCH_CPU_ARM64)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chromium_win_arm64"));
#elif defined(ARCH_CPU_X86_64)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chromium_win_x86_64"));
#elif defined(ARCH_CPU_X86)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chromium_win_x86"));
#endif
#elif BUILDFLAG(GOOGLE_CHROME_BRANDING)
#if defined(ARCH_CPU_ARM64)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chrome_win_arm64"));
#elif defined(ARCH_CPU_X86_64)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chrome_win_x86_64"));
#elif defined(ARCH_CPU_X86)
  old_updater_path =
      old_updater_path.Append(FILE_PATH_LITERAL("chrome_win_x86"));
#endif
#endif

#if BUILDFLAG(CHROMIUM_BRANDING) || BUILDFLAG(GOOGLE_CHROME_BRANDING)
  old_updater_path = old_updater_path.Append(FILE_PATH_LITERAL("cipd"));
#endif
  return old_updater_path.Append(FILE_PATH_LITERAL("UpdaterSetup_test.exe"));
}

void RunUninstallCmdLine(UpdaterScope scope) {
  std::wstring uninstall_cmd_line_string;
  EXPECT_EQ(ERROR_SUCCESS, base::win::RegKey(UpdaterScopeToHKeyRoot(scope),
                                             UPDATER_KEY, Wow6432(KEY_READ))
                               .ReadValue(kRegValueUninstallCmdLine,
                                          &uninstall_cmd_line_string));
  base::CommandLine command_line =
      base::CommandLine::FromString(uninstall_cmd_line_string);

  base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;

  base::Process process = base::LaunchProcess(command_line, {});
  EXPECT_TRUE(process.IsValid());

  int exit_code = 0;
  EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_timeout(),
                                             &exit_code));
  EXPECT_EQ(0, exit_code);
}

void RunHandoff(UpdaterScope scope, const std::string& app_id) {
  const std::optional<base::FilePath> installed_executable_path =
      GetUpdaterExecutablePath(scope);
  ASSERT_TRUE(installed_executable_path);
  ASSERT_TRUE(base::PathExists(*installed_executable_path));

  base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
  const std::wstring command_line(base::StrCat(
      {base::CommandLine::QuoteForCommandLineToArgvW(
           installed_executable_path->value()),
       L" /handoff \"appguid=", base::ASCIIToWide(app_id), L"&needsadmin=",
       IsSystemInstall(scope) ? L"Prefers" : L"False", L"\" /silent"}));
  VLOG(0) << " RunHandoff: " << command_line;
  const base::Process process = base::LaunchProcess(command_line, {});
  ASSERT_TRUE(process.IsValid());

  int exit_code = 0;
  ASSERT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                             &exit_code));
  ASSERT_EQ(exit_code, 0);
}

std::wstring GetFakeUpdaterTaskName(UpdaterScope scope,
                                    const std::wstring& type) {
  return base::StrCat({IsSystemInstall(scope) ? kLegacyTaskNamePrefixSystem
                                              : kLegacyTaskNamePrefixUser,
                       type, base::NumberToWString(::GetCurrentProcessId())});
}

void SetupFakeLegacyUpdater(UpdaterScope scope) {
  const HKEY root = UpdaterScopeToHKeyRoot(scope);

  base::win::RegKey key;
  ASSERT_EQ(key.Create(root,
                       base::StrCat(
                           {UPDATER_KEY L"Clients\\", kLegacyGoogleUpdateAppID})
                           .c_str(),
                       Wow6432(KEY_WRITE)),
            ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValuePV, L"1.1.1.1"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GOOG"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS);
  key.Close();

  ASSERT_EQ(
      key.Create(
          root,
          UPDATER_KEY L"\\Clients\\{8A69D345-D564-463C-AFF1-A69D9E530F96}",
          Wow6432(KEY_WRITE)),
      ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValuePV, L"99.0.0.1"), ERROR_SUCCESS);
  key.Close();

  ASSERT_EQ(
      key.Create(
          root,
          UPDATER_KEY L"\\ClientState\\{8A69D345-D564-463C-AFF1-A69D9E530F96}",
          Wow6432(KEY_WRITE)),
      ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GGLS"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueDateOfLastActivity, 0xFFFFFFFF),
            ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueDateOfLastRollcall, 5929), ERROR_SUCCESS);
  key.Close();

  ASSERT_EQ(key.Create(
                root,
                UPDATER_KEY L"\\ClientState"
                            L"\\{8A69D345-D564-463C-AFF1-A69D9E530F96}\\cohort",
                Wow6432(KEY_WRITE)),
            ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(nullptr, L"TestCohort"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueCohortName, L"TestCohortName"),
            ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueCohortHint, L"TestCohortHint"),
            ERROR_SUCCESS);
  key.Close();

  ASSERT_EQ(
      key.Create(
          root,
          UPDATER_KEY L"\\Clients\\{fc54d8f9-b6fd-4274-92eb-c4335cd8761e}",
          Wow6432(KEY_WRITE)),
      ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueBrandCode, L"GGLS"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueAP, L"TestAP"), ERROR_SUCCESS);
  ASSERT_EQ(key.WriteValue(kRegValueDateOfLastActivity, L"5900"),
            ERROR_SUCCESS);
  key.Close();

  if (IsSystemInstall(scope)) {
    // Install mock GoogleUpdate services "gupdate" and "gupdatem".
    EXPECT_TRUE(CreateService(kLegacyServiceNamePrefix,
                              kLegacyServiceDisplayNamePrefix,
                              L"C:\\temp\\temp.exe"));
    EXPECT_TRUE(CreateService(base::StrCat({kLegacyServiceNamePrefix, L"m"}),
                              kLegacyServiceDisplayNamePrefix,
                              L"C:\\temp\\temp.exe"));
  } else {
    // Install mock GoogleUpdate run value.
    base::win::RegKey run_key;
    ASSERT_EQ(
        run_key.Open(HKEY_CURRENT_USER, REGSTR_PATH_RUN, KEY_READ | KEY_WRITE),
        ERROR_SUCCESS);
    ASSERT_EQ(run_key.WriteValue(kLegacyRunValuePrefix, L"C:\\temp\\temp.exe"),
              ERROR_SUCCESS);
  }

  // Install mock GoogleUpdate tasks.
  scoped_refptr<TaskScheduler> task_scheduler =
      TaskScheduler::CreateInstance(scope, /*use_task_subfolders=*/false);
  ASSERT_TRUE(task_scheduler);

  for (const std::wstring& task_name : {GetFakeUpdaterTaskName(scope, L"Core"),
                                        GetFakeUpdaterTaskName(scope, L"UA")}) {
    ASSERT_TRUE(task_scheduler->RegisterTask(
        task_name, task_name,
        base::CommandLine::FromString(L"C:\\temp\\temp.exe"),
        TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY, false));
  }

  // Set up a mock `GoogleUpdate.exe`, and the following mock directories:
  // `Download`, `Install`, and a versioned `1.2.3.4` directory.
  const std::optional<base::FilePath> google_update_exe =
      GetGoogleUpdateExePath(scope);
  ASSERT_TRUE(google_update_exe.has_value());
  SetupMockUpdater(google_update_exe.value());
}

void RunFakeLegacyUpdater(UpdaterScope scope) {
  const std::optional<base::FilePath> google_update_exe =
      GetGoogleUpdateExePath(scope);
  ASSERT_TRUE(base::PathExists(*google_update_exe));

  const base::FilePath exe_dir(google_update_exe->DirName());
  base::CommandLine command_line =
      GetTestProcessCommandLine(scope, test::GetTestName());
  command_line.AppendSwitchASCII(
      updater::kTestSleepSecondsSwitch,
      base::NumberToString(TestTimeouts::action_timeout().InSeconds() / 4));

  for (const base::FilePath& dir :
       {exe_dir, exe_dir.Append(L"1.2.3.4"), exe_dir.Append(L"Download"),
        exe_dir.Append(L"Install")}) {
    for (const std::wstring exe_name : {kLegacyExeName, L"mock.executable"}) {
      const base::FilePath exe(dir.Append(exe_name));
      ASSERT_TRUE(base::PathExists(exe));

      base::Process process = base::LaunchProcess(
          base::StrCat(
              {base::CommandLine::QuoteForCommandLineToArgvW(exe.value()), L" ",
               command_line.GetArgumentsString()}),
          {});
      ASSERT_TRUE(process.IsValid());
    }
  }
}

void CloseInstallCompleteDialog(const std::wstring& child_window_text_to_find,
                                bool verify_app_logo_loaded) {
  const std::wstring window_title =
      GetLocalizedStringF(IDS_INSTALLER_DISPLAY_NAME_BASE,
                          GetLocalizedString(IDS_FRIENDLY_COMPANY_NAME_BASE));
  bool found = false;
  base::Process process;
  ASSERT_TRUE(WaitFor(
      [&] {
        if (!found) {
          // Enumerate the top-level dialogs to find the setup dialog.
          base::win::EnumerateChildWindows(
              ::GetDesktopWindow(), base::BindLambdaForTesting([&](HWND hwnd) {
                if (!base::win::IsSystemDialog(hwnd) ||
                    !base::Contains(base::win::GetWindowTextString(hwnd),
                                    window_title)) {
                  return false;
                }
                // Enumerate the child windows to search for
                // `child_window_text_to_find`. If found, close the dialog.
                base::win::EnumerateChildWindows(
                    hwnd, base::BindLambdaForTesting([&](HWND hwnd) {
                      if (!base::Contains(base::win::GetWindowTextString(hwnd),
                                          child_window_text_to_find)) {
                        return false;
                      }
                      const HWND parent_hwnd = ::GetParent(hwnd);
                      if (verify_app_logo_loaded &&
                          !::SendDlgItemMessage(parent_hwnd, IDC_APP_BITMAP,
                                                STM_GETIMAGE, IMAGE_BITMAP,
                                                0)) {
                        return false;
                      }
                      found = true;
                      DWORD pid = 0;
                      EXPECT_TRUE(
                          ::GetWindowThreadProcessId(parent_hwnd, &pid));
                      process = base::Process::Open(pid);
                      EXPECT_TRUE(process.IsValid());
                      ::PostMessage(parent_hwnd, WM_CLOSE, 0, 0);
                      return found;
                    }));
                return found;
              }));
        }
        return found && !process.IsRunning();
      },
      [&] {
        VLOG(0) << "Still waiting, `found`: " << found
                << ": `process.IsRunning()`: " << process.IsRunning();
      }));
}

void ExpectLegacyUpdaterMigrated(UpdaterScope scope) {
  scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope);
  auto persisted_data = base::MakeRefCounted<PersistedData>(
      scope, global_prefs->GetPrefService(), nullptr);

  // Legacy updater itself should not be migrated.
  const std::string kLegacyUpdaterAppId =
      base::SysWideToUTF8(kLegacyGoogleUpdateAppID);
  EXPECT_FALSE(
      persisted_data->GetProductVersion(kLegacyUpdaterAppId).IsValid());
  EXPECT_TRUE(persisted_data->GetAP(kLegacyUpdaterAppId).empty());
  EXPECT_TRUE(persisted_data->GetBrandCode(kLegacyUpdaterAppId).empty());
  EXPECT_TRUE(persisted_data->GetFingerprint(kLegacyUpdaterAppId).empty());

  // App without 'pv' should not be migrated.
  const std::string kNoPVAppId("{fc54d8f9-b6fd-4274-92eb-c4335cd8761e}");
  EXPECT_FALSE(persisted_data->GetProductVersion(kNoPVAppId).IsValid());
  EXPECT_TRUE(persisted_data->GetAP(kNoPVAppId).empty());
  EXPECT_TRUE(persisted_data->GetBrandCode(kNoPVAppId).empty());
  EXPECT_TRUE(persisted_data->GetFingerprint(kNoPVAppId).empty());
  EXPECT_EQ(persisted_data->GetDateLastActive(kNoPVAppId), -2);
  EXPECT_EQ(persisted_data->GetDateLastRollCall(kNoPVAppId), -2);

  EXPECT_EQ(persisted_data->GetProductVersion(kChromeAppId),
            base::Version("99.0.0.1"));
  EXPECT_EQ(persisted_data->GetAP(kChromeAppId), "TestAP");
  EXPECT_EQ(persisted_data->GetBrandCode(kChromeAppId), "GGLS");
  EXPECT_TRUE(persisted_data->GetFingerprint(kChromeAppId).empty());
  EXPECT_EQ(persisted_data->GetDateLastActive(kChromeAppId), -1);
  EXPECT_EQ(persisted_data->GetDateLastRollCall(kChromeAppId), 5929);
  EXPECT_EQ(persisted_data->GetCohort(kChromeAppId), "TestCohort");
  EXPECT_EQ(persisted_data->GetCohortName(kChromeAppId), "TestCohortName");
  EXPECT_EQ(persisted_data->GetCohortHint(kChromeAppId), "TestCohortHint");

  int count_entries = 0;
  if (IsSystemInstall(scope)) {
    // Expect no GoogleUpdate services.
    ForEachServiceWithPrefix(
        kLegacyServiceNamePrefix, kLegacyServiceDisplayNamePrefix,
        [&count_entries](const std::wstring& /*service_name*/) {
          ++count_entries;
        });
  } else {
    // Expect no GoogleUpdate run value.
    ForEachRegistryRunValueWithPrefix(
        kLegacyRunValuePrefix,
        [&count_entries](const std::wstring& /* run_name*/) {
          ++count_entries;
        });
  }
  EXPECT_EQ(count_entries, 0);

  // Expect no GoogleUpdate tasks.
  scoped_refptr<TaskScheduler> task_scheduler =
      TaskScheduler::CreateInstance(scope, /*use_task_subfolders=*/false);
  ASSERT_TRUE(task_scheduler);
  for (const std::wstring& task_name : {GetFakeUpdaterTaskName(scope, L"Core"),
                                        GetFakeUpdaterTaskName(scope, L"UA")}) {
    count_entries = 0;
    task_scheduler->ForEachTaskWithPrefix(
        task_name, [&count_entries](const std::wstring& /*task_name*/) {
          ++count_entries;
        });

    EXPECT_EQ(count_entries, 0);
  }

  // Expect only a single file `GoogleUpdate.exe` and nothing else under
  // `\Google\Update`.
  const std::optional<base::FilePath> google_update_exe =
      GetGoogleUpdateExePath(scope);
  ASSERT_TRUE(google_update_exe.has_value());
  ExpectOnlyMockUpdater(google_update_exe.value());
}

void InstallApp(UpdaterScope scope,
                const std::string& app_id,
                const base::Version& version) {
  base::win::RegKey key;
  ASSERT_EQ(key.Create(UpdaterScopeToHKeyRoot(scope),
                       GetAppClientsKey(app_id).c_str(), Wow6432(KEY_WRITE)),
            ERROR_SUCCESS);
  RegistrationRequest registration;
  registration.app_id = app_id;
  registration.version = version;
  RegisterApp(scope, registration);
}

void UninstallApp(UpdaterScope scope, const std::string& app_id) {
  base::win::RegKey key;
  ASSERT_EQ(
      key.Open(UpdaterScopeToHKeyRoot(scope), CLIENTS_KEY, Wow6432(DELETE)),
      ERROR_SUCCESS);
  ASSERT_EQ(key.DeleteKey(base::SysUTF8ToWide(app_id).c_str()), ERROR_SUCCESS);
}

void RunOfflineInstall(UpdaterScope scope,
                       bool is_legacy_install,
                       bool is_silent_install) {
  static constexpr char kManifestFormat[] =
      R"(<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0">
  <systemrequirements platform="win"/>
  <app appid="%ls" status="ok">
    <updatecheck status="ok">
      <manifest version="%s">
        <packages>
          <package hash_sha256="sha256hash_foobar"
            name="%s" required="true" size="%)" PRId64 R"("/>
        </packages>
        <actions>
          <action event="install"
            run="%s"/>
        </actions>
      </manifest>
    </updatecheck>
    <data index="verboselogging" name="install" status="ok">
      {"distribution": { "verbose_logging": true}}
    </data>
  </app>
</response>)";
  RunOfflineInstallWithManifest(scope, is_legacy_install, is_silent_install,
                                kManifestFormat,
                                IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE, true);
}

void RunOfflineInstallOsNotSupported(UpdaterScope scope,
                                     bool is_legacy_install,
                                     bool is_silent_install) {
  static constexpr char kManifestFormat[] =
      R"(<?xml version="1.0" encoding="UTF-8"?>
<response protocol="3.0">
  <systemrequirements platform="minix"/>
  <app appid="%ls" status="ok">
    <updatecheck status="ok">
      <manifest version="%s">
        <packages>
          <package hash_sha256="sha256hash_foobar"
            name="%s" required="true" size="%)" PRId64 R"("/>
        </packages>
        <actions>
          <action event="install"
            run="%s"/>
        </actions>
      </manifest>
    </updatecheck>
    <data index="verboselogging" name="install" status="ok">
      {"distribution": { "verbose_logging": true}}
    </data>
  </app>
</response>)";
  RunOfflineInstallWithManifest(scope, is_legacy_install, is_silent_install,
                                kManifestFormat,
                                IDS_UPDATER_OS_NOT_SUPPORTED_BASE, false);
}

base::CommandLine MakeElevated(base::CommandLine command_line) {
  return command_line;
}

void SetPlatformPolicies(const base::Value::Dict& values) {
  base::win::RegKey policy_key;
  ASSERT_EQ(ERROR_SUCCESS,
            policy_key.Create(HKEY_LOCAL_MACHINE, UPDATER_POLICIES_KEY,
                              KEY_SET_VALUE));

  for (const auto [app_id, policies] : values) {
    ASSERT_TRUE(policies.is_dict());
    for (const auto [name, value] : policies.GetDict()) {
      const std::wstring& key = base::ASCIIToWide(
          base::StringPrintf("%s%s", name.c_str(), app_id.c_str()));
      if (value.is_string()) {
        policy_key.WriteValue(key.c_str(),
                              base::ASCIIToWide(value.GetString()).c_str());
      } else if (value.is_int()) {
        policy_key.WriteValue(key.c_str(), static_cast<DWORD>(value.GetInt()));
      } else if (value.is_bool()) {
        policy_key.WriteValue(key.c_str(), static_cast<DWORD>(value.GetBool()));
      }
    }
  }
}

void ExpectAppVersion(UpdaterScope scope,
                      const std::string& app_id,
                      const base::Version& version) {
  const base::Version app_version =
      base::MakeRefCounted<PersistedData>(
          scope, CreateGlobalPrefsForTesting(scope)->GetPrefService(), nullptr)
          ->GetProductVersion(app_id);
  EXPECT_TRUE(app_version.IsValid());
  EXPECT_EQ(version, app_version);

  std::wstring pv;
  EXPECT_EQ(
      ERROR_SUCCESS,
      base::win::RegKey(UpdaterScopeToHKeyRoot(scope),
                        GetAppClientStateKey(app_id).c_str(), Wow6432(KEY_READ))
          .ReadValue(kRegValuePV, &pv));
  EXPECT_EQ(base::SysUTF8ToWide(version.GetString()), pv);
}

}  // namespace updater::test