chromium/chrome/updater/util/win_util.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/updater/util/win_util.h"

#include <windows.h>

#include <aclapi.h>
#include <combaseapi.h>
#include <objidl.h>
#include <regstr.h>
#include <shellapi.h>
#include <shlobj.h>
#include <winhttp.h>
#include <wrl/client.h>
#include <wtsapi32.h>

#include <cstdlib>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/base_paths_win.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/debug/alias.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/functional/function_ref.h"
#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/process/process_iterator.h"
#include "base/ranges/algorithm.h"
#include "base/scoped_native_library.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.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/system/sys_info.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "base/win/atl.h"
#include "base/win/registry.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_handle.h"
#include "base/win/scoped_localalloc.h"
#include "base/win/scoped_process_information.h"
#include "base/win/scoped_variant.h"
#include "base/win/startup_information.h"
#include "base/win/win_util.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/registration_data.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/win/scoped_handle.h"
#include "chrome/updater/win/user_info.h"
#include "chrome/updater/win/win_constants.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace updater {

namespace {

HResultOr<bool> IsUserRunningSplitToken() {
  HANDLE token = NULL;
  if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) {
    return base::unexpected(HRESULTFromLastError());
  }
  base::win::ScopedHandle token_holder(token);
  TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
  DWORD size_returned = 0;
  if (!::GetTokenInformation(token_holder.Get(), TokenElevationType,
                             &elevation_type, sizeof(elevation_type),
                             &size_returned)) {
    return base::unexpected(HRESULTFromLastError());
  }
  bool is_split_token = elevation_type == TokenElevationTypeFull ||
                        elevation_type == TokenElevationTypeLimited;
  CHECK(is_split_token || elevation_type == TokenElevationTypeDefault);
  return base::ok(is_split_token);
}

HRESULT GetSidIntegrityLevel(PSID sid, MANDATORY_LEVEL* level) {
  if (!::IsValidSid(sid)) {
    return E_FAIL;
  }
  SID_IDENTIFIER_AUTHORITY* authority = ::GetSidIdentifierAuthority(sid);
  if (!authority) {
    return E_FAIL;
  }
  constexpr SID_IDENTIFIER_AUTHORITY kMandatoryLabelAuth =
      SECURITY_MANDATORY_LABEL_AUTHORITY;
  if (std::memcmp(authority, &kMandatoryLabelAuth,
                  sizeof(SID_IDENTIFIER_AUTHORITY))) {
    return E_FAIL;
  }
  PUCHAR count = ::GetSidSubAuthorityCount(sid);
  if (!count || *count != 1) {
    return E_FAIL;
  }
  DWORD* rid = ::GetSidSubAuthority(sid, 0);
  if (!rid) {
    return E_FAIL;
  }
  if ((*rid & 0xFFF) != 0 || *rid > SECURITY_MANDATORY_PROTECTED_PROCESS_RID) {
    return E_FAIL;
  }
  *level = static_cast<MANDATORY_LEVEL>(*rid >> 12);
  return S_OK;
}

// Gets the mandatory integrity level of a process.
HRESULT GetProcessIntegrityLevel(DWORD process_id, MANDATORY_LEVEL* level) {
  HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, process_id);
  if (!process) {
    return HRESULTFromLastError();
  }
  base::win::ScopedHandle process_holder(process);
  HANDLE token = NULL;
  if (!::OpenProcessToken(process_holder.Get(),
                          TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) {
    return HRESULTFromLastError();
  }
  base::win::ScopedHandle token_holder(token);
  DWORD label_size = 0;
  if (::GetTokenInformation(token_holder.Get(), TokenIntegrityLevel, nullptr, 0,
                            &label_size) ||
      ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
    return E_FAIL;
  }
  std::unique_ptr<TOKEN_MANDATORY_LABEL, base::FreeDeleter> label(
      static_cast<TOKEN_MANDATORY_LABEL*>(std::malloc(label_size)));
  if (!::GetTokenInformation(token_holder.Get(), TokenIntegrityLevel,
                             label.get(), label_size, &label_size)) {
    return HRESULTFromLastError();
  }
  return GetSidIntegrityLevel(label->Label.Sid, level);
}

bool IsExplorerRunningAtMediumOrLower() {
  base::NamedProcessIterator iter(L"EXPLORER.EXE", nullptr);
  while (const base::ProcessEntry* process_entry = iter.NextProcessEntry()) {
    MANDATORY_LEVEL level = MandatoryLevelUntrusted;
    if (SUCCEEDED(GetProcessIntegrityLevel(process_entry->pid(), &level)) &&
        level <= MandatoryLevelMedium) {
      return true;
    }
  }
  return false;
}

// Creates a WS_POPUP | WS_VISIBLE with zero
// size, of the STATIC WNDCLASS. It uses the default running EXE module
// handle for creation.
//
// A visible centered foreground window is needed as the parent in Windows 7 and
// above, to allow the UAC prompt to come up in the foreground, centered.
// Otherwise, the elevation prompt will be minimized on the taskbar. A zero size
// window works. A plain vanilla WS_POPUP allows the window to be free of
// adornments. WS_EX_TOOLWINDOW prevents the task bar from showing the
// zero-sized window.
//
// Returns NULL on failure. Call ::GetLastError() to get extended error
// information on failure.
HWND CreateForegroundParentWindowForUAC() {
  CWindow foreground_parent;
  if (foreground_parent.Create(L"STATIC", NULL, NULL, NULL,
                               WS_POPUP | WS_VISIBLE, WS_EX_TOOLWINDOW)) {
    foreground_parent.CenterWindow(NULL);
    ::SetForegroundWindow(foreground_parent);
  }
  return foreground_parent.Detach();
}

// Compares the OS, service pack, and build numbers using `::VerifyVersionInfo`,
// in accordance with `type_mask` and `oper`.
bool CompareOSVersionsInternal(const OSVERSIONINFOEX& os,
                               DWORD type_mask,
                               BYTE oper) {
  CHECK(type_mask);
  CHECK(oper);

  ULONGLONG cond_mask = 0;
  cond_mask = ::VerSetConditionMask(cond_mask, VER_MAJORVERSION, oper);
  cond_mask = ::VerSetConditionMask(cond_mask, VER_MINORVERSION, oper);
  cond_mask = ::VerSetConditionMask(cond_mask, VER_SERVICEPACKMAJOR, oper);
  cond_mask = ::VerSetConditionMask(cond_mask, VER_SERVICEPACKMINOR, oper);
  cond_mask = ::VerSetConditionMask(cond_mask, VER_BUILDNUMBER, oper);

  // `::VerifyVersionInfo` could return `FALSE` due to an error other than
  // `ERROR_OLD_WIN_VERSION`. We do not handle that case here.
  // https://msdn.microsoft.com/ms725492.
  OSVERSIONINFOEX os_in = os;
  return ::VerifyVersionInfo(&os_in, type_mask, cond_mask);
}

std::optional<int> DaynumFromDWORD(DWORD value) {
  const int daynum = static_cast<int>(value);

  // When daynum is positive, it is the number of days since January 1, 2007.
  // It's reasonable to only accept value between 3000 (maps to Mar 20, 2015)
  // and 50000 (maps to Nov 24, 2143).
  // -1 is special value for first install.
  return daynum == -1 || (daynum >= 3000 && daynum <= 50000)
             ? std::make_optional(daynum)
             : std::nullopt;
}

}  // namespace

NamedObjectAttributes::NamedObjectAttributes(const std::wstring& name,
                                             const CSecurityDesc& sd)
    : name(name), sa(CSecurityAttributes(sd)) {}
NamedObjectAttributes::~NamedObjectAttributes() = default;

HRESULT HRESULTFromLastError() {
  const auto error_code = ::GetLastError();
  return (error_code != NO_ERROR) ? HRESULT_FROM_WIN32(error_code) : E_FAIL;
}

NamedObjectAttributes GetNamedObjectAttributes(const wchar_t* base_name,
                                               UpdaterScope scope) {
  CHECK(base_name);

  switch (scope) {
    case UpdaterScope::kUser: {
      std::wstring user_sid;
      GetProcessUser(nullptr, nullptr, &user_sid);
      return {
          base::StrCat({kGlobalPrefix, base_name, user_sid}),
          GetCurrentUserDefaultSecurityDescriptor().value_or(CSecurityDesc())};
    }
    case UpdaterScope::kSystem:
      // Grant access to administrators and system.
      return {base::StrCat({kGlobalPrefix, base_name}),
              GetAdminDaclSecurityDescriptor(GENERIC_ALL)};
  }
}

std::optional<CSecurityDesc> GetCurrentUserDefaultSecurityDescriptor() {
  CAccessToken token;
  if (!token.GetProcessToken(TOKEN_QUERY)) {
    return std::nullopt;
  }

  CSecurityDesc security_desc;
  CSid sid_owner;
  if (!token.GetOwner(&sid_owner)) {
    return std::nullopt;
  }

  security_desc.SetOwner(sid_owner);
  CSid sid_group;
  if (!token.GetPrimaryGroup(&sid_group)) {
    return std::nullopt;
  }

  security_desc.SetGroup(sid_group);

  CDacl dacl;
  if (!token.GetDefaultDacl(&dacl)) {
    return std::nullopt;
  }

  CSid sid_user;
  if (!token.GetUser(&sid_user)) {
    return std::nullopt;
  }
  if (!dacl.AddAllowedAce(sid_user, GENERIC_ALL)) {
    return std::nullopt;
  }

  security_desc.SetDacl(dacl);

  return security_desc;
}

CSecurityDesc GetAdminDaclSecurityDescriptor(ACCESS_MASK accessmask) {
  CSecurityDesc sd;
  CDacl dacl;
  dacl.AddAllowedAce(Sids::System(), accessmask);
  dacl.AddAllowedAce(Sids::Admins(), accessmask);

  sd.SetOwner(Sids::Admins());
  sd.SetGroup(Sids::Admins());
  sd.SetDacl(dacl);
  sd.MakeAbsolute();
  return sd;
}

std::wstring GetAppClientsKey(const std::string& app_id) {
  return GetAppClientsKey(base::ASCIIToWide(app_id));
}

std::wstring GetAppClientsKey(const std::wstring& app_id) {
  return base::StrCat({CLIENTS_KEY, app_id});
}

std::wstring GetAppClientStateKey(const std::string& app_id) {
  return GetAppClientStateKey(base::ASCIIToWide(app_id));
}

std::wstring GetAppClientStateKey(const std::wstring& app_id) {
  return base::StrCat({CLIENT_STATE_KEY, app_id});
}

std::wstring GetAppCohortKey(const std::string& app_id) {
  return GetAppCohortKey(base::ASCIIToWide(app_id));
}

std::wstring GetAppCohortKey(const std::wstring& app_id) {
  return base::StrCat({GetAppClientStateKey(app_id), L"\\", kRegKeyCohort});
}

std::wstring GetAppCommandKey(const std::wstring& app_id,
                              const std::wstring& command_id) {
  return base::StrCat(
      {GetAppClientsKey(app_id), L"\\", kRegKeyCommands, L"\\", command_id});
}

std::string GetAppAPValue(UpdaterScope scope, const std::string& app_id) {
  base::win::RegKey client_state_key;
  if (client_state_key.Open(
          UpdaterScopeToHKeyRoot(scope),
          GetAppClientStateKey(base::ASCIIToWide(app_id)).c_str(),
          Wow6432(KEY_READ)) == ERROR_SUCCESS) {
    std::wstring ap;
    if (client_state_key.ReadValue(kRegValueAP, &ap) == ERROR_SUCCESS) {
      return base::WideToASCII(ap);
    }
  }
  return {};
}

std::wstring GetRegistryKeyClientsUpdater() {
  return GetAppClientsKey(kUpdaterAppId);
}

std::wstring GetRegistryKeyClientStateUpdater() {
  return GetAppClientStateKey(kUpdaterAppId);
}

bool SetRegistryKey(HKEY root,
                    const std::wstring& key,
                    const std::wstring& name,
                    const std::wstring& value) {
  base::win::RegKey rkey;
  LONG result = rkey.Create(root, key.c_str(), Wow6432(KEY_WRITE));
  if (result != ERROR_SUCCESS) {
    VLOG(1) << "Failed to open (" << root << ") " << key << ": " << result;
    return false;
  }
  result = rkey.WriteValue(name.c_str(), value.c_str());
  if (result != ERROR_SUCCESS) {
    VLOG(1) << "Failed to write (" << root << ") " << key << " @ " << name
            << ": " << result;
    return false;
  }
  return result == ERROR_SUCCESS;
}

bool SetEulaAccepted(UpdaterScope scope, bool eula_accepted) {
  const HKEY root = UpdaterScopeToHKeyRoot(scope);
  return eula_accepted
             ? DeleteRegValue(root, UPDATER_KEY, L"eulaaccepted")
             : base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_WRITE))
                       .WriteValue(L"eulaaccepted", 0ul) == ERROR_SUCCESS;
}

HResultOr<bool> IsTokenAdmin(HANDLE token) {
  SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
  PSID administrators_group = nullptr;
  if (!::AllocateAndInitializeSid(&nt_authority, 2, SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
                                  &administrators_group)) {
    return base::unexpected(HRESULTFromLastError());
  }
  absl::Cleanup free_sid = [&] { ::FreeSid(administrators_group); };
  BOOL is_member = false;
  if (!::CheckTokenMembership(token, administrators_group, &is_member)) {
    return base::unexpected(HRESULTFromLastError());
  }
  return base::ok(is_member);
}

HResultOr<bool> IsUserAdmin() {
  return IsTokenAdmin(NULL);
}

HResultOr<bool> IsUserNonElevatedAdmin() {
  HANDLE token = NULL;
  if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_READ, &token)) {
    return base::unexpected(HRESULTFromLastError());
  }
  bool is_user_non_elevated_admin = false;
  base::win::ScopedHandle token_holder(token);
  TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
  DWORD size_returned = 0;
  if (::GetTokenInformation(token_holder.Get(), TokenElevationType,
                            &elevation_type, sizeof(elevation_type),
                            &size_returned)) {
    if (elevation_type == TokenElevationTypeLimited) {
      is_user_non_elevated_admin = true;
    }
  }
  return base::ok(is_user_non_elevated_admin);
}

HResultOr<bool> IsCOMCallerAdmin() {
  HRESULT hr = ::CoImpersonateClient();
  if (hr == RPC_E_CALL_COMPLETE) {
    // RPC_E_CALL_COMPLETE indicates that the caller is in-proc.
    return base::ok(::IsUserAnAdmin());
  }

  if (FAILED(hr)) {
    return base::unexpected(hr);
  }

  HResultOr<ScopedKernelHANDLE> token = []() -> decltype(token) {
    ScopedKernelHANDLE token;
    absl::Cleanup co_revert_to_self = [] { ::CoRevertToSelf(); };
    if (!::OpenThreadToken(::GetCurrentThread(), TOKEN_QUERY, TRUE,
                           ScopedKernelHANDLE::Receiver(token).get())) {
      HRESULT hr = HRESULTFromLastError();
      LOG(ERROR) << "::OpenThreadToken failed: " << std::hex << hr;
      return base::unexpected(hr);
    }
    return token;
  }();

  if (!token.has_value()) {
    return base::unexpected(token.error());
  }

  return IsTokenAdmin(token.value().get()).transform_error([](HRESULT error) {
    CHECK(FAILED(error));
    LOG(ERROR) << "IsTokenAdmin failed: " << std::hex << error;
    return error;
  });
}

bool IsUACOn() {
  // The presence of a split token definitively indicates that UAC is on. But
  // the absence of the token does not necessarily indicate that UAC is off.
  HResultOr<bool> is_split_token = IsUserRunningSplitToken();
  return (is_split_token.has_value() && is_split_token.value()) ||
         IsExplorerRunningAtMediumOrLower();
}

bool IsElevatedWithUACOn() {
  HResultOr<bool> is_user_admin = IsUserAdmin();
  return (!is_user_admin.has_value() || is_user_admin.value()) && IsUACOn();
}

std::string GetUACState() {
  std::string s;

  HResultOr<bool> is_user_admin = IsUserAdmin();
  if (is_user_admin.has_value()) {
    base::StringAppendF(&s, "IsUserAdmin: %d, ", is_user_admin.value());
  }

  HResultOr<bool> is_user_non_elevated_admin = IsUserNonElevatedAdmin();
  if (is_user_non_elevated_admin.has_value()) {
    base::StringAppendF(&s, "IsUserNonElevatedAdmin: %d, ",
                        is_user_non_elevated_admin.value());
  }

  base::StringAppendF(&s, "IsUACOn: %d, IsElevatedWithUACOn: %d, ", IsUACOn(),
                      IsElevatedWithUACOn());

  base::StringAppendF(&s, "LUA: %d", base::win::UserAccountControlIsEnabled());
  return s;
}

std::wstring GetServiceName(bool is_internal_service) {
  std::wstring service_name = GetServiceDisplayName(is_internal_service);
  std::erase_if(service_name, base::IsAsciiWhitespace<wchar_t>);
  return service_name;
}

std::wstring GetServiceDisplayName(bool is_internal_service) {
  return base::StrCat(
      {base::ASCIIToWide(PRODUCT_FULLNAME_STRING), L" ",
       is_internal_service ? kWindowsInternalServiceName : kWindowsServiceName,
       L" ", kUpdaterVersionUtf16});
}

REGSAM Wow6432(REGSAM access) {
  CHECK(access);

  return KEY_WOW64_32KEY | access;
}

HResultOr<DWORD> ShellExecuteAndWait(const base::FilePath& file_path,
                                     const std::wstring& parameters,
                                     const std::wstring& verb) {
  VLOG(1) << __func__ << ": path: " << file_path
          << ", parameters:" << parameters << ", verb:" << verb;
  CHECK(!file_path.empty());

  const HWND hwnd = CreateForegroundParentWindowForUAC();
  const absl::Cleanup destroy_window = [&] {
    if (hwnd) {
      ::DestroyWindow(hwnd);
    }
  };

  SHELLEXECUTEINFO shell_execute_info = {};
  shell_execute_info.cbSize = sizeof(SHELLEXECUTEINFO);
  shell_execute_info.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS |
                             SEE_MASK_NOZONECHECKS | SEE_MASK_NOASYNC;
  shell_execute_info.hProcess = NULL;
  shell_execute_info.hwnd = hwnd;
  shell_execute_info.lpVerb = verb.c_str();
  shell_execute_info.lpFile = file_path.value().c_str();
  shell_execute_info.lpParameters = parameters.c_str();
  shell_execute_info.lpDirectory = NULL;
  shell_execute_info.nShow = SW_SHOW;
  shell_execute_info.hInstApp = NULL;

  if (!::ShellExecuteEx(&shell_execute_info)) {
    const HRESULT hr = HRESULTFromLastError();
    VLOG(1) << __func__ << ": ::ShellExecuteEx failed: " << std::hex << hr;
    return base::unexpected(hr);
  }

  if (!shell_execute_info.hProcess) {
    VLOG(1) << __func__ << ": Started process, PID unknown";
    return base::ok(0);
  }

  const base::Process process(shell_execute_info.hProcess);
  const DWORD pid = process.Pid();
  VLOG(1) << __func__ << ": Started process, PID: " << pid;

  // Allow the spawned process to show windows in the foreground.
  if (!::AllowSetForegroundWindow(pid)) {
    VLOG(1) << __func__
            << ": ::AllowSetForegroundWindow failed: " << ::GetLastError();
  }

  int ret_val = 0;
  if (!process.WaitForExit(&ret_val)) {
    return base::unexpected(HRESULTFromLastError());
  }

  return base::ok(static_cast<DWORD>(ret_val));
}

HResultOr<DWORD> RunElevated(const base::FilePath& file_path,
                             const std::wstring& parameters) {
  return ShellExecuteAndWait(file_path, parameters, L"runas");
}

// Based on https://learn.microsoft.com/en-us/archive/blogs/aaron_margosis/
// faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app.
HResultOr<DWORD> RunDeElevated(const base::CommandLine& command_line) {
  const HWND hwnd = ::GetShellWindow();
  if (!hwnd) {
    return base::unexpected(HRESULTFromLastError());
  }

  DWORD pid = 0;
  if (!::GetWindowThreadProcessId(hwnd, &pid)) {
    return base::unexpected(HRESULTFromLastError());
  }

  base::win::ScopedHandle shell_process(
      ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid));
  if (!shell_process.IsValid()) {
    return base::unexpected(HRESULTFromLastError());
  }

  CAccessToken token;
  if (!token.GetProcessToken(MAXIMUM_ALLOWED)) {
    return base::unexpected(HRESULTFromLastError());
  }
  CTokenPrivileges previous_privileges;
  if (!token.EnablePrivilege(SE_IMPERSONATE_NAME, &previous_privileges)) {
    return base::unexpected(HRESULTFromLastError());
  }
  absl::Cleanup restore_previous_privileges = [&] {
    token.EnableDisablePrivileges(previous_privileges);
  };

  CAccessToken shell_token;
  if (!shell_token.GetProcessToken(TOKEN_DUPLICATE, shell_process.Get())) {
    return base::unexpected(HRESULTFromLastError());
  }

  CAccessToken duplicated_shell_token;
  if (!shell_token.CreatePrimaryToken(
          &duplicated_shell_token, TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY |
                                       TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT |
                                       TOKEN_ADJUST_SESSIONID)) {
    return base::unexpected(HRESULTFromLastError());
  }

  STARTUPINFO startupinfo = {};
  PROCESS_INFORMATION pi = {};
  if (!::CreateProcessWithTokenW(
          duplicated_shell_token.GetHandle(), 0,
          command_line.GetProgram().value().c_str(),
          std::wstring(command_line.GetCommandLineString()).data(), 0, nullptr,
          nullptr, &startupinfo, &pi)) {
    return base::unexpected(HRESULTFromLastError());
  }

  ::CloseHandle(pi.hThread);
  const base::Process process(pi.hProcess);
  pid = process.Pid();
  VLOG(1) << __func__ << ": Started process, PID: " << pid;

  // Allow the spawned process to show windows in the foreground.
  if (!::AllowSetForegroundWindow(pid)) {
    VPLOG(1) << __func__ << ": ::AllowSetForegroundWindow failed";
  }

  int ret_val = 0;
  if (!process.WaitForExit(&ret_val)) {
    return base::unexpected(HRESULTFromLastError());
  }

  return base::ok(static_cast<DWORD>(ret_val));
}

HRESULT RunDeElevatedNoWait(const std::wstring& path,
                            const std::wstring& parameters) {
  Microsoft::WRL::ComPtr<IShellWindows> shell;
  HRESULT hr = ::CoCreateInstance(CLSID_ShellWindows, nullptr,
                                  CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&shell));
  if (FAILED(hr)) {
    return hr;
  }

  long hwnd = 0;
  Microsoft::WRL::ComPtr<IDispatch> dispatch;
  hr = shell->FindWindowSW(base::win::ScopedVariant(CSIDL_DESKTOP).AsInput(),
                           base::win::ScopedVariant().AsInput(), SWC_DESKTOP,
                           &hwnd, SWFO_NEEDDISPATCH, &dispatch);
  if (hr == S_FALSE || FAILED(hr)) {
    return hr == S_FALSE ? E_FAIL : hr;
  }

  Microsoft::WRL::ComPtr<IServiceProvider> service;
  hr = dispatch.As(&service);
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IShellBrowser> browser;
  hr = service->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&browser));
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IShellView> view;
  hr = browser->QueryActiveShellView(&view);
  if (FAILED(hr)) {
    return hr;
  }

  hr = view->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&dispatch));
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IShellFolderViewDual> folder;
  hr = dispatch.As(&folder);
  if (FAILED(hr)) {
    return hr;
  }

  hr = folder->get_Application(&dispatch);
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IShellDispatch2> shell_dispatch;
  hr = dispatch.As(&shell_dispatch);
  if (FAILED(hr)) {
    return hr;
  }

  return shell_dispatch->ShellExecute(
      base::win::ScopedBstr(path).Get(),
      base::win::ScopedVariant(parameters.c_str()),
      base::win::ScopedVariant::kEmptyVariant,
      base::win::ScopedVariant::kEmptyVariant,
      base::win::ScopedVariant::kEmptyVariant);
}

HRESULT RunDeElevatedCmdLine(const std::wstring& cmd_line) {
  if (!IsElevatedWithUACOn()) {
    auto process = base::LaunchProcess(cmd_line, {});
    return process.IsValid() ? S_OK : HRESULTFromLastError();
  }

  std::wstring command_format = cmd_line;
  int num_args = 0;
  base::win::ScopedLocalAllocTyped<wchar_t*> argv(
      ::CommandLineToArgvW(&command_format[0], &num_args));
  if (!argv || num_args < 1) {
    LOG(ERROR) << __func__ << "!argv || num_args < 1: " << num_args;
    return E_INVALIDARG;
  }

  return RunDeElevatedNoWait(
      argv.get()[0],
      base::JoinString(
          [&]() -> std::vector<std::wstring> {
            if (num_args <= 1) {
              return {};
            }

            std::vector<std::wstring> parameters;
            base::ranges::for_each(
                argv.get() + 1, argv.get() + num_args,
                [&](const auto& parameter) {
                  parameters.push_back(
                      base::CommandLine::QuoteForCommandLineToArgvW(parameter));
                });
            return parameters;
          }(),
          L" "));
}

std::optional<base::FilePath> GetGoogleUpdateExePath(UpdaterScope scope) {
  base::FilePath goopdate_base_dir;
  if (!base::PathService::Get(IsSystemInstall(scope)
                                  ? base::DIR_PROGRAM_FILESX86
                                  : base::DIR_LOCAL_APP_DATA,
                              &goopdate_base_dir)) {
    LOG(ERROR) << "Can't retrieve GoogleUpdate base directory.";
    return std::nullopt;
  }

  return goopdate_base_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
      .AppendASCII("Update")
      .Append(kLegacyExeName);
}

HRESULT DisableCOMExceptionHandling() {
  Microsoft::WRL::ComPtr<IGlobalOptions> options;
  HRESULT hr = ::CoCreateInstance(CLSID_GlobalOptions, nullptr,
                                  CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&options));
  if (FAILED(hr)) {
    return hr;
  }
  return hr = options->Set(COMGLB_EXCEPTION_HANDLING,
                           COMGLB_EXCEPTION_DONOT_HANDLE);
}

std::wstring BuildMsiCommandLine(
    const std::wstring& arguments,
    const std::optional<base::FilePath>& installer_data_file,
    const base::FilePath& msi_installer) {
  if (!msi_installer.MatchesExtension(L".msi")) {
    return std::wstring();
  }

  return base::StrCat(
      {L"msiexec ", arguments,
       installer_data_file
           ? base::StrCat(
                 {L" ",
                  base::UTF8ToWide(base::ToUpperASCII(kInstallerDataSwitch)),
                  L"=",
                  base::CommandLine::QuoteForCommandLineToArgvW(
                      installer_data_file->value())})
           : L"",
       L" REBOOT=ReallySuppress /qn /i ",
       base::CommandLine::QuoteForCommandLineToArgvW(msi_installer.value()),
       L" /log ",
       base::CommandLine::QuoteForCommandLineToArgvW(
           msi_installer.AddExtension(L".log").value())});
}

std::wstring BuildExeCommandLine(
    const std::wstring& arguments,
    const std::optional<base::FilePath>& installer_data_file,
    const base::FilePath& exe_installer) {
  if (!exe_installer.MatchesExtension(L".exe")) {
    return std::wstring();
  }

  return base::StrCat(
      {base::CommandLine::QuoteForCommandLineToArgvW(exe_installer.value()),
       L" ", arguments, [&installer_data_file] {
         if (!installer_data_file) {
           return std::wstring();
         }

         base::CommandLine installer_data_args(base::CommandLine::NO_PROGRAM);
         installer_data_args.AppendSwitchPath(kInstallerDataSwitch,
                                              *installer_data_file);
         return base::StrCat({L" ", installer_data_args.GetArgumentsString()});
       }()});
}

bool IsServiceRunning(const std::wstring& service_name) {
  ScopedScHandle scm(::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT));
  if (!scm.IsValid()) {
    LOG(ERROR) << "::OpenSCManager failed. service_name: " << service_name
               << ", error: " << std::hex << HRESULTFromLastError();
    return false;
  }

  ScopedScHandle service(
      ::OpenService(scm.Get(), service_name.c_str(), SERVICE_QUERY_STATUS));
  if (!service.IsValid()) {
    LOG(ERROR) << "::OpenService failed. service_name: " << service_name
               << ", error: " << std::hex << HRESULTFromLastError();
    return false;
  }

  SERVICE_STATUS status = {0};
  if (!::QueryServiceStatus(service.Get(), &status)) {
    LOG(ERROR) << "::QueryServiceStatus failed. service_name: " << service_name
               << ", error: " << std::hex << HRESULTFromLastError();
    return false;
  }

  VLOG(1) << "IsServiceRunning. service_name: " << service_name
          << ", status: " << std::hex << status.dwCurrentState;
  return status.dwCurrentState == SERVICE_RUNNING ||
         status.dwCurrentState == SERVICE_START_PENDING;
}

HKEY UpdaterScopeToHKeyRoot(UpdaterScope scope) {
  return IsSystemInstall(scope) ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
}

std::optional<OSVERSIONINFOEX> GetOSVersion() {
  // `::RtlGetVersion` is being used here instead of `::GetVersionEx`, because
  // the latter function can return the incorrect version if it is shimmed using
  // an app compat shim.
  using RtlGetVersion = LONG(WINAPI*)(OSVERSIONINFOEX*);
  static const RtlGetVersion rtl_get_version = reinterpret_cast<RtlGetVersion>(
      ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), "RtlGetVersion"));
  if (!rtl_get_version) {
    return std::nullopt;
  }

  OSVERSIONINFOEX os_out = {};
  os_out.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

  rtl_get_version(&os_out);
  if (!os_out.dwMajorVersion) {
    return std::nullopt;
  }

  return os_out;
}

bool CompareOSVersions(const OSVERSIONINFOEX& os_version, BYTE oper) {
  CHECK(oper);

  constexpr DWORD kOSTypeMask = VER_MAJORVERSION | VER_MINORVERSION |
                                VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR;
  constexpr DWORD kBuildTypeMask = VER_BUILDNUMBER;

  // If the OS and the service pack match, return the build number comparison.
  return CompareOSVersionsInternal(os_version, kOSTypeMask, VER_EQUAL)
             ? CompareOSVersionsInternal(os_version, kBuildTypeMask, oper)
             : CompareOSVersionsInternal(os_version, kOSTypeMask, oper);
}

bool EnableSecureDllLoading() {
  static const auto set_default_dll_directories =
      reinterpret_cast<decltype(&::SetDefaultDllDirectories)>(::GetProcAddress(
          ::GetModuleHandle(L"kernel32.dll"), "SetDefaultDllDirectories"));

  if (!set_default_dll_directories) {
    return true;
  }

#if defined(COMPONENT_BUILD)
  const DWORD directory_flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS;
#else
  const DWORD directory_flags = LOAD_LIBRARY_SEARCH_SYSTEM32;
#endif

  return set_default_dll_directories(directory_flags);
}

bool EnableProcessHeapMetadataProtection() {
  if (!::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, nullptr,
                            0)) {
    LOG(ERROR) << __func__
               << ": Failed to enable heap metadata protection: " << std::hex
               << HRESULTFromLastError();
    return false;
  }

  return true;
}

std::optional<base::ScopedTempDir> CreateSecureTempDir() {
  // This function uses `base::CreateNewTempDirectory` and then a
  // `base::ScopedTempDir` as owner, instead of just
  // `base::ScopedTempDir::CreateUniqueTempDir`, because the former allows
  // setting a more recognizable prefix of `COMPANY_SHORTNAME_STRING` on the
  // temp directory.
  base::FilePath temp_dir;
  if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL(COMPANY_SHORTNAME_STRING),
                                    &temp_dir)) {
    return std::nullopt;
  }

  base::ScopedTempDir temp_dir_owner;
  if (temp_dir_owner.Set(temp_dir)) {
    return temp_dir_owner;
  }
  return std::nullopt;
}

base::ScopedClosureRunner SignalShutdownEvent(UpdaterScope scope) {
  NamedObjectAttributes attr = GetNamedObjectAttributes(kShutdownEvent, scope);

  base::win::ScopedHandle shutdown_event_handle(
      ::CreateEvent(&attr.sa, true, false, attr.name.c_str()));
  if (!shutdown_event_handle.IsValid()) {
    VLOG(1) << __func__ << "Could not create the shutdown event: " << std::hex
            << HRESULTFromLastError();
    return {};
  }

  auto shutdown_event =
      std::make_unique<base::WaitableEvent>(std::move(shutdown_event_handle));
  shutdown_event->Signal();
  return base::ScopedClosureRunner(
      base::BindOnce(&base::WaitableEvent::Reset, std::move(shutdown_event)));
}

bool IsShutdownEventSignaled(UpdaterScope scope) {
  NamedObjectAttributes attr = GetNamedObjectAttributes(kShutdownEvent, scope);

  base::win::ScopedHandle event_handle(
      ::OpenEvent(EVENT_ALL_ACCESS, false, attr.name.c_str()));
  if (!event_handle.IsValid()) {
    return false;
  }

  base::WaitableEvent event(std::move(event_handle));
  return event.IsSignaled();
}

void StopProcessesUnderPath(const base::FilePath& path,
                            const base::TimeDelta& wait_period) {
  // Filters processes running under `path_prefix`.
  class PathPrefixProcessFilter : public base::ProcessFilter {
   public:
    explicit PathPrefixProcessFilter(const base::FilePath& path_prefix)
        : path_prefix_(path_prefix) {}

    bool Includes(const base::ProcessEntry& entry) const override {
      base::Process process(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
                                          false, entry.th32ProcessID));
      if (!process.IsValid()) {
        return false;
      }

      DWORD path_len = MAX_PATH;
      wchar_t path_string[MAX_PATH];
      if (!::QueryFullProcessImageName(process.Handle(), 0, path_string,
                                       &path_len)) {
        return false;
      }

      return path_prefix_.IsParent(base::FilePath(path_string));
    }

   private:
    const base::FilePath path_prefix_;
  };

  PathPrefixProcessFilter path_prefix_filter(path);

  base::ProcessIterator iter(&path_prefix_filter);
  base::flat_set<std::wstring> process_names_to_cleanup;
  for (const base::ProcessEntry* entry = iter.NextProcessEntry(); entry;
       entry = iter.NextProcessEntry()) {
    process_names_to_cleanup.insert(entry->exe_file());
  }

  const auto deadline = base::TimeTicks::Now() + wait_period;
  for (const auto& exe_file : process_names_to_cleanup) {
    base::CleanupProcesses(
        exe_file, std::max(deadline - base::TimeTicks::Now(), base::Seconds(0)),
        -1, &path_prefix_filter);
  }
}

std::optional<base::CommandLine> CommandLineForLegacyFormat(
    const std::wstring& cmd_string) {
  int num_args = 0;
  base::win::ScopedLocalAllocTyped<wchar_t*> args(
      ::CommandLineToArgvW(cmd_string.c_str(), &num_args));
  if (!args) {
    return std::nullopt;
  }

  auto is_switch = [](const std::wstring& arg) { return arg[0] == L'-'; };

  auto is_legacy_switch = [](const std::wstring& arg) {
    return arg[0] == L'/';
  };

  // First argument is the program.
  base::CommandLine command_line(base::FilePath{args.get()[0]});

  for (int i = 1; i < num_args; ++i) {
    const std::wstring next_arg = i < num_args - 1 ? args.get()[i + 1] : L"";

    if (is_switch(args.get()[i]) || is_switch(next_arg)) {
      // Won't parse Chromium-style command line.
      return std::nullopt;
    }

    if (!is_legacy_switch(args.get()[i])) {
      // This is a bare argument.
      command_line.AppendArg(base::WideToASCII(args.get()[i]));
      continue;
    }

    const std::string switch_name = base::WideToASCII(&args.get()[i][1]);
    if (switch_name.empty()) {
      VLOG(1) << "Empty switch in command line: [" << cmd_string << "]";
      return std::nullopt;
    }
    if (base::StringPairs switch_value_pairs;
        base::SplitStringIntoKeyValuePairs(switch_name, '=', '\n',
                                           &switch_value_pairs)) {
      command_line.AppendSwitchASCII(switch_value_pairs[0].first,
                                     switch_value_pairs[0].second);
      continue;
    }
    if (is_legacy_switch(next_arg) || next_arg.empty()) {
      command_line.AppendSwitch(switch_name);
    } else {
      // Next argument is the value for this switch.
      command_line.AppendSwitchNative(switch_name, next_arg);
      ++i;
    }
  }

  return command_line;
}

std::optional<base::FilePath> GetInstallDirectory(UpdaterScope scope) {
  base::FilePath app_data_dir;
  if (!base::PathService::Get(IsSystemInstall(scope) ? base::DIR_PROGRAM_FILES
                                                     : base::DIR_LOCAL_APP_DATA,
                              &app_data_dir)) {
    LOG(ERROR) << "Can't retrieve app data directory.";
    return std::nullopt;
  }
  return app_data_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
      .AppendASCII(PRODUCT_FULLNAME_STRING);
}

base::FilePath GetExecutableRelativePath() {
  return base::FilePath::FromASCII(kExecutableName);
}

bool IsGuid(const std::wstring& s) {
  CHECK(!s.empty());

  GUID guid = {0};
  return SUCCEEDED(::IIDFromString(&s[0], &guid));
}

void ForEachRegistryRunValueWithPrefix(
    const std::wstring& prefix,
    base::FunctionRef<void(const std::wstring&)> callback) {
  for (base::win::RegistryValueIterator it(HKEY_CURRENT_USER, REGSTR_PATH_RUN,
                                           KEY_WOW64_32KEY);
       it.Valid(); ++it) {
    const std::wstring run_name = it.Name();
    if (base::StartsWith(run_name, prefix)) {
      callback(run_name);
    }
  }
}

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

  LONG result = base::win::RegKey(root, path.c_str(), Wow6432(KEY_WRITE))
                    .DeleteValue(value.c_str());
  return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}

void ForEachServiceWithPrefix(
    const std::wstring& service_name_prefix,
    const std::wstring& display_name_prefix,
    base::FunctionRef<void(const std::wstring&)> callback) {
  for (base::win::RegistryKeyIterator it(HKEY_LOCAL_MACHINE,
                                         L"SYSTEM\\CurrentControlSet\\Services",
                                         KEY_WOW64_32KEY);
       it.Valid(); ++it) {
    const std::wstring service_name = it.Name();
    if (base::StartsWith(service_name, service_name_prefix)) {
      if (display_name_prefix.empty()) {
        callback(service_name);
        continue;
      }

      base::win::RegKey key;
      if (key.Open(HKEY_LOCAL_MACHINE,
                   base::StrCat(
                       {L"SYSTEM\\CurrentControlSet\\Services\\", service_name})
                       .c_str(),
                   Wow6432(KEY_READ)) != ERROR_SUCCESS) {
        continue;
      }

      std::wstring display_name;
      if (key.ReadValue(L"DisplayName", &display_name) != ERROR_SUCCESS) {
        continue;
      }

      const bool display_name_starts_with_prefix =
          base::StartsWith(display_name, display_name_prefix);
      VLOG(1) << __func__ << ": " << service_name
              << " matches: " << service_name_prefix << ": " << display_name
              << ": " << display_name_starts_with_prefix << ": "
              << display_name_prefix;
      if (display_name_starts_with_prefix) {
        callback(service_name);
      }
    }
  }
}

[[nodiscard]] bool DeleteService(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(), DELETE));
  bool is_service_deleted = !service.IsValid();
  if (!is_service_deleted) {
    is_service_deleted =
        ::DeleteService(service.Get())
            ? true
            : ::GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE;
  }

  if (!DeleteRegValue(HKEY_LOCAL_MACHINE, UPDATER_KEY, service_name)) {
    return false;
  }

  VLOG(1) << __func__ << ": " << service_name << ": " << is_service_deleted;
  return is_service_deleted;
}

bool WrongUser(UpdaterScope scope) {
  return IsSystemInstall(scope) ? !::IsUserAnAdmin()
                                : ::IsUserAnAdmin() && IsUACOn();
}

bool EulaAccepted(const std::vector<std::string>& app_ids) {
  for (const auto& app_id : app_ids) {
    DWORD eula_accepted = 0;
    if (base::win::RegKey(
            HKEY_LOCAL_MACHINE,
            base::StrCat({CLIENT_STATE_MEDIUM_KEY, base::ASCIIToWide(app_id)})
                .c_str(),
            Wow6432(KEY_READ))
                .ReadValueDW(L"eulaaccepted", &eula_accepted) ==
            ERROR_SUCCESS &&
        eula_accepted == 1) {
      return true;
    }
  }
  return false;
}

void LogClsidEntries(REFCLSID clsid) {
  const std::wstring local_server32_reg_path(base::StrCat(
      {base::StrCat({L"Software\\Classes\\CLSID\\", StringFromGuid(clsid)}),
       L"\\LocalServer32"}));
  for (const HKEY root : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
    for (const REGSAM key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
      base::win::RegKey key;
      if (ERROR_SUCCESS == key.Open(root, local_server32_reg_path.c_str(),
                                    KEY_QUERY_VALUE | key_flag)) {
        std::wstring val;
        if (ERROR_SUCCESS == key.ReadValue(L"", &val)) {
          LOG(ERROR) << __func__ << ": CLSID entry found: "
                     << (root == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE\\"
                                                    : "HKEY_CURRENT_USER\\")
                     << local_server32_reg_path << ": " << val;
        }
      }
    }
  }
}

std::optional<base::FilePath> GetInstallDirectoryX86(UpdaterScope scope) {
  if (!IsSystemInstall(scope)) {
    return GetInstallDirectory(scope);
  }
  base::FilePath install_dir;
  if (!base::PathService::Get(base::DIR_PROGRAM_FILESX86, &install_dir)) {
    LOG(ERROR) << "Can't retrieve directory for DIR_PROGRAM_FILESX86.";
    return std::nullopt;
  }
  return install_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
      .AppendASCII(PRODUCT_FULLNAME_STRING);
}

std::optional<std::wstring> GetRegKeyContents(const std::wstring& reg_key) {
  base::FilePath system_path;
  if (!base::PathService::Get(base::DIR_SYSTEM, &system_path)) {
    return {};
  }

  std::string output;
  if (!base::GetAppOutput(
          base::StrCat({system_path.Append(L"reg.exe").value(), L" query ",
                        base::CommandLine::QuoteForCommandLineToArgvW(reg_key),
                        L" /s"}),
          &output)) {
    return {};
  }
  return base::ASCIIToWide(output);
}

std::wstring GetTextForSystemError(int error) {
  if (static_cast<HRESULT>(error & 0xFFFF0000) ==
      MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0)) {
    error = HRESULT_CODE(error);
  }

  HMODULE source = nullptr;
  DWORD format_options =
      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
      FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK;

  if (error >= WINHTTP_ERROR_BASE && error <= WINHTTP_ERROR_LAST) {
    source = ::GetModuleHandle(_T("winhttp.dll"));
    if (source) {
      format_options |= FORMAT_MESSAGE_FROM_HMODULE;
    }
  }
  wchar_t* system_allocated_buffer = nullptr;
  const DWORD chars_written = ::FormatMessage(
      format_options, source, error, 0,
      reinterpret_cast<wchar_t*>(&system_allocated_buffer), 0, nullptr);
  base::win::ScopedLocalAllocTyped<wchar_t> free_buffer(
      system_allocated_buffer);
  return chars_written > 0
             ? system_allocated_buffer
             : base::ASCIIToWide(base::StringPrintf("%#x", error));
}

bool MigrateLegacyUpdaters(
    UpdaterScope scope,
    base::RepeatingCallback<void(const RegistrationRequest&)>
        register_callback) {
  const HKEY root = UpdaterScopeToHKeyRoot(scope);
  for (base::win::RegistryKeyIterator it(root, CLIENTS_KEY, KEY_WOW64_32KEY);
       it.Valid(); ++it) {
    const std::wstring app_id = it.Name();

    // Skip importing legacy updater.
    if (base::EqualsCaseInsensitiveASCII(app_id, kLegacyGoogleUpdateAppID)) {
      continue;
    }

    base::win::RegKey key;
    if (key.Open(root, GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ)) !=
        ERROR_SUCCESS) {
      continue;
    }

    RegistrationRequest registration;
    registration.app_id = base::SysWideToUTF8(app_id);
    std::wstring pv;
    if (key.ReadValue(kRegValuePV, &pv) != ERROR_SUCCESS) {
      continue;
    }

    registration.version = base::Version(base::SysWideToUTF8(pv));
    if (!registration.version.IsValid()) {
      continue;
    }

    base::win::RegKey client_state_key;
    if (client_state_key.Open(root, GetAppClientStateKey(app_id).c_str(),
                              Wow6432(KEY_READ)) == ERROR_SUCCESS) {
      std::wstring brand_code;
      if (client_state_key.ReadValue(kRegValueBrandCode, &brand_code) ==
          ERROR_SUCCESS) {
        registration.brand_code = base::SysWideToUTF8(brand_code);
      }

      std::wstring ap;
      if (client_state_key.ReadValue(kRegValueAP, &ap) == ERROR_SUCCESS) {
        registration.ap = base::SysWideToUTF8(ap);
      }

      DWORD date_last_activity = 0;
      if (client_state_key.ReadValueDW(kRegValueDateOfLastActivity,
                                       &date_last_activity) == ERROR_SUCCESS) {
        registration.dla = DaynumFromDWORD(date_last_activity);
      }

      DWORD date_last_rollcall = 0;
      if (client_state_key.ReadValueDW(kRegValueDateOfLastRollcall,
                                       &date_last_rollcall) == ERROR_SUCCESS) {
        registration.dlrc = DaynumFromDWORD(date_last_rollcall);
      }

      DWORD install_date = 0;
      if (client_state_key.ReadValueDW(kRegValueDayOfInstall, &install_date) ==
          ERROR_SUCCESS) {
        registration.install_date = DaynumFromDWORD(install_date);
      }

      base::win::RegKey cohort_key;
      if (cohort_key.Open(root, GetAppCohortKey(app_id).c_str(),
                          Wow6432(KEY_READ)) == ERROR_SUCCESS) {
        std::wstring cohort;
        if (cohort_key.ReadValue(nullptr, &cohort) == ERROR_SUCCESS) {
          registration.cohort = base::SysWideToUTF8(cohort);

          std::wstring cohort_name;
          if (cohort_key.ReadValue(kRegValueCohortName, &cohort_name) ==
              ERROR_SUCCESS) {
            registration.cohort_name = base::SysWideToUTF8(cohort_name);
          }

          std::wstring cohort_hint;
          if (cohort_key.ReadValue(kRegValueCohortHint, &cohort_hint) ==
              ERROR_SUCCESS) {
            registration.cohort_hint = base::SysWideToUTF8(cohort_hint);
          }
          VLOG(2) << "Cohort values: " << registration.cohort << ", "
                  << registration.cohort_name << ", "
                  << registration.cohort_hint;
        }
      }
    }

    register_callback.Run(registration);
  }

  return true;
}

namespace {

struct ScopedWtsConnectStateCloseTraits {
  static WTS_CONNECTSTATE_CLASS* InvalidValue() { return nullptr; }
  static void Free(WTS_CONNECTSTATE_CLASS* memory) { ::WTSFreeMemory(memory); }
};

struct ScopedWtsSessionInfoCloseTraits {
  static PWTS_SESSION_INFO InvalidValue() { return nullptr; }
  static void Free(PWTS_SESSION_INFO memory) { ::WTSFreeMemory(memory); }
};

using ScopedWtsConnectState =
    base::ScopedGeneric<WTS_CONNECTSTATE_CLASS*,
                        ScopedWtsConnectStateCloseTraits>;
using ScopedWtsSessionInfo =
    base::ScopedGeneric<PWTS_SESSION_INFO, ScopedWtsSessionInfoCloseTraits>;

// Returns `true` if there is a user logged on and active in the specified
// session.
bool IsSessionActive(std::optional<DWORD> session_id) {
  if (!session_id) {
    return false;
  }

  ScopedWtsConnectState wts_connect_state;
  DWORD bytes_returned = 0;
  if (::WTSQuerySessionInformation(
          WTS_CURRENT_SERVER_HANDLE, *session_id, WTSConnectState,
          reinterpret_cast<LPTSTR*>(
              ScopedWtsConnectState::Receiver(wts_connect_state).get()),
          &bytes_returned)) {
    CHECK_EQ(bytes_returned, sizeof(WTS_CONNECTSTATE_CLASS));
    return *wts_connect_state.get() == WTSActive;
  }

  return false;
}

// Returns the currently active session.
// `WTSGetActiveConsoleSessionId` retrieves the Terminal Services session
// currently attached to the physical console, so that is attempted first.
// `WTSGetActiveConsoleSessionId` does not work for terminal servers where the
// current active session is always the console. For those, an active session
// is found by enumerating all the sessions that are present on the system, and
// the first active session is returned.
std::optional<DWORD> GetActiveSessionId() {
  if (DWORD active_session_id = ::WTSGetActiveConsoleSessionId();
      IsSessionActive(active_session_id)) {
    return active_session_id;
  }

  ScopedWtsSessionInfo session_info;
  DWORD num_sessions = 0;
  if (::WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,
                             ScopedWtsSessionInfo::Receiver(session_info).get(),
                             &num_sessions)) {
    for (size_t i = 0; i < num_sessions; ++i) {
      if (session_info.get()[i].State == WTSActive) {
        return session_info.get()[i].SessionId;
      }
    }
  }

  return {};
}

std::vector<DWORD> FindProcesses(const std::wstring& process_name) {
  base::NamedProcessIterator iter(process_name, nullptr);
  std::vector<DWORD> pids;
  while (const base::ProcessEntry* process_entry = iter.NextProcessEntry()) {
    pids.push_back(process_entry->pid());
  }
  return pids;
}

// Returns processes running under `session_id`.
std::vector<DWORD> FindProcessesInSession(const std::wstring& process_name,
                                          std::optional<DWORD> session_id) {
  if (!session_id) {
    return {};
  }
  std::vector<DWORD> pids;
  for (const auto pid : FindProcesses(process_name)) {
    DWORD process_session = 0;
    if (::ProcessIdToSessionId(pid, &process_session) &&
        (process_session == *session_id)) {
      pids.push_back(pid);
    }
  }
  return pids;
}

// Returns the first instance found of explorer.exe.
std::optional<DWORD> GetExplorerPid() {
  std::vector<DWORD> pids =
      FindProcessesInSession(L"EXPLORER.EXE", GetActiveSessionId());
  if (pids.empty()) {
    return {};
  }
  return pids[0];
}

// Returns an impersonation token for the user running process_id.
HResultOr<ScopedKernelHANDLE> GetImpersonationToken(
    std::optional<DWORD> process_id) {
  if (!process_id) {
    return base::unexpected(E_UNEXPECTED);
  }
  base::win::ScopedHandle process(::OpenProcess(
      PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, TRUE, *process_id));
  if (!process.IsValid()) {
    return base::unexpected(HRESULTFromLastError());
  }
  ScopedKernelHANDLE process_token;
  if (!::OpenProcessToken(process.Get(), TOKEN_DUPLICATE | TOKEN_QUERY,
                          ScopedKernelHANDLE::Receiver(process_token).get())) {
    return base::unexpected(HRESULTFromLastError());
  }
  ScopedKernelHANDLE user_token;
  if (!::DuplicateTokenEx(process_token.get(),
                          TOKEN_IMPERSONATE | TOKEN_QUERY |
                              TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
                          NULL, SecurityImpersonation, TokenPrimary,
                          ScopedKernelHANDLE::Receiver(user_token).get())) {
    return base::unexpected(HRESULTFromLastError());
  }
  return user_token;
}

}  // namespace

HResultOr<ScopedKernelHANDLE> GetLoggedOnUserToken() {
  return GetImpersonationToken(GetExplorerPid());
}

bool IsAuditMode() {
  base::win::RegKey setup_state_key;
  std::wstring state;
  return setup_state_key.Open(HKEY_LOCAL_MACHINE, kSetupStateKey,
                              KEY_QUERY_VALUE) == ERROR_SUCCESS &&
         setup_state_key.ReadValue(kImageStateValueName, &state) ==
             ERROR_SUCCESS &&
         (base::EqualsCaseInsensitiveASCII(state, kImageStateUnuseableValue) ||
          base::EqualsCaseInsensitiveASCII(state,
                                           kImageStateGeneralAuditValue) ||
          base::EqualsCaseInsensitiveASCII(state,
                                           kImageStateSpecialAuditValue));
}

bool SetOemInstallState() {
  if (!::IsUserAnAdmin() || !IsAuditMode()) {
    return false;
  }

  const base::Time now = base::Time::Now();
  VLOG(1) << "OEM install time set: " << now;
  return base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
                           Wow6432(KEY_SET_VALUE))
             .WriteValue(kRegValueOemInstallTimeMin,
                         now.ToDeltaSinceWindowsEpoch().InMinutes()) ==
         ERROR_SUCCESS;
}

bool ResetOemInstallState() {
  VLOG(1) << "OEM install reset at time: " << base::Time::Now();
  const LONG result =
      base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY, Wow6432(KEY_SET_VALUE))
          .DeleteValue(kRegValueOemInstallTimeMin);
  return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND;
}

bool IsOemInstalling() {
  DWORD oem_install_time_minutes = 0;
  if (base::win::RegKey(HKEY_LOCAL_MACHINE, CLIENTS_KEY,
                        Wow6432(KEY_QUERY_VALUE))
          .ReadValueDW(kRegValueOemInstallTimeMin, &oem_install_time_minutes) !=
      ERROR_SUCCESS) {
    VLOG(2) << "OemInstallTime not found";
    return false;
  }
  const base::Time now = base::Time::Now();
  const base::Time oem_install_time = base::Time::FromDeltaSinceWindowsEpoch(
      base::Minutes(oem_install_time_minutes));
  const base::TimeDelta time_in_oem_mode = now - oem_install_time;
  const bool is_oem_installing = time_in_oem_mode < kMinOemModeTime;
  if (!is_oem_installing) {
    ResetOemInstallState();
  }
  VLOG(1) << "now: " << now << ", OEM install time: " << oem_install_time
          << ", time_in_oem_mode: " << time_in_oem_mode
          << ", is_oem_installing: " << is_oem_installing;
  return is_oem_installing;
}

std::wstring StringFromGuid(const GUID& guid) {
  // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
  constexpr int kGuidStringCharacters =
      1 + 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1 + 1;
  wchar_t guid_string[kGuidStringCharacters] = {0};
  CHECK_NE(::StringFromGUID2(guid, guid_string, kGuidStringCharacters), 0);
  return guid_string;
}

bool StoreRunTimeEnrollmentToken(const std::string& enrollment_token) {
  VLOG(1) << __func__ << ": " << enrollment_token;
  return base::win::RegKey(HKEY_LOCAL_MACHINE,
                           GetAppClientsKey(kUpdaterAppId).c_str(),
                           Wow6432(KEY_SET_VALUE))
             .WriteValue(kRegValueCloudManagementEnrollmentToken,
                         base::SysUTF8ToWide(enrollment_token).c_str()) ==
         ERROR_SUCCESS;
}

std::optional<base::FilePath> GetUniqueTempFilePath(base::FilePath file) {
  base::FilePath temp_dir;
  if (file.empty() || !base::GetTempDir(&temp_dir)) {
    return {};
  }
  return temp_dir.Append(base::StrCat(
      {file.RemoveExtension().BaseName().value(),
       base::UTF8ToWide(base::Uuid::GenerateRandomV4().AsLowercaseString()),
       file.Extension()}));
}

}  // namespace updater