| // Copyright 2012 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/installer/util/installation_state.h" |
| |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| |
| #include "base/check.h" |
| #include "base/strings/cstring_view.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/version.h" |
| #include "base/win/registry.h" |
| #include "build/build_config.h" |
| #include "chrome/install_static/install_util.h" |
| #include "chrome/installer/util/app_commands.h" |
| #include "chrome/installer/util/google_update_constants.h" |
| #include "chrome/installer/util/helper.h" |
| #include "chrome/installer/util/install_util.h" |
| #include "chrome/installer/util/util_constants.h" |
| |
| namespace installer { |
| |
| namespace { |
| |
| // Initializes |commands| from the "Commands" subkey of |version_key|. Returns |
| // false if there is no "Commands" subkey or on error. |
| bool InitializeCommands(const base::win::RegKey& clients_key, |
| AppCommands* commands) { |
| static const DWORD kAccess = |
| KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_WOW64_32KEY; |
| base::win::RegKey commands_key; |
| |
| if (commands_key.Open(clients_key.Handle(), google_update::kRegCommandsKey, |
| kAccess) == ERROR_SUCCESS) { |
| return commands->Initialize(commands_key, KEY_WOW64_32KEY); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| ProductState::ProductState() |
| : uninstall_command_(base::CommandLine::NO_PROGRAM), |
| eula_accepted_(0), |
| usagestats_(0), |
| msi_(false), |
| has_eula_accepted_(false), |
| has_oem_install_(false), |
| has_usagestats_(false) {} |
| |
| ProductState::~ProductState() = default; |
| |
| bool ProductState::Initialize(bool system_install) { |
| static const DWORD kAccess = KEY_QUERY_VALUE | KEY_WOW64_32KEY; |
| const std::wstring clients_key(install_static::GetClientsKeyPath()); |
| const std::wstring state_key(install_static::GetClientStateKeyPath()); |
| const HKEY root_key = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; |
| base::win::RegKey key; |
| |
| // Clear the runway. |
| Clear(); |
| |
| // Read from the Clients key. |
| if (key.Open(root_key, clients_key.c_str(), kAccess) == ERROR_SUCCESS) { |
| std::wstring version_str; |
| if (key.ReadValue(google_update::kRegVersionField, &version_str) == |
| ERROR_SUCCESS) { |
| version_ = |
| std::make_unique<base::Version>(base::WideToASCII(version_str)); |
| if (!version_->IsValid()) |
| version_.reset(); |
| } |
| |
| // Attempt to read the other values even if the "pv" version value was |
| // absent. Note that ProductState instances containing these values will |
| // only be accessible via InstallationState::GetNonVersionedProductState. |
| if (key.ReadValue(google_update::kRegOldVersionField, &version_str) == |
| ERROR_SUCCESS) { |
| old_version_ = |
| std::make_unique<base::Version>(base::WideToASCII(version_str)); |
| if (!old_version_->IsValid()) |
| old_version_.reset(); |
| } |
| |
| if (key.ReadValue(google_update::kRegChannelField, &channel_) != |
| ERROR_SUCCESS) { |
| channel_.clear(); |
| } |
| |
| if (!InitializeCommands(key, &commands_)) |
| commands_.Clear(); |
| } |
| |
| // Read from the ClientState key. |
| if (key.Open(root_key, state_key.c_str(), kAccess) == ERROR_SUCCESS) { |
| std::wstring setup_path; |
| std::wstring uninstall_arguments; |
| |
| // Read in the brand code, it may be absent |
| key.ReadValue(google_update::kRegBrandField, &brand_); |
| |
| key.ReadValue(kUninstallStringField, &setup_path); |
| key.ReadValue(kUninstallArgumentsField, &uninstall_arguments); |
| InstallUtil::ComposeCommandLine(setup_path, uninstall_arguments, |
| &uninstall_command_); |
| |
| // "usagestats" may be absent, 0 (false), or 1 (true). On the chance that |
| // different values are permitted in the future, we'll simply hold whatever |
| // we find. |
| has_usagestats_ = (key.ReadValueDW(google_update::kRegUsageStatsField, |
| &usagestats_) == ERROR_SUCCESS); |
| // "oeminstall" may be present with any value or absent. |
| has_oem_install_ = (key.ReadValue(google_update::kRegOemInstallField, |
| &oem_install_) == ERROR_SUCCESS); |
| // "eulaaccepted" may be absent, 0 or 1. |
| has_eula_accepted_ = (key.ReadValueDW(google_update::kRegEulaAceptedField, |
| &eula_accepted_) == ERROR_SUCCESS); |
| // "msi" may be absent, 0 or 1 |
| DWORD dw_value = 0; |
| msi_ = (key.ReadValueDW(google_update::kRegMSIField, &dw_value) == |
| ERROR_SUCCESS) && |
| (dw_value != 0); |
| |
| constexpr base::wcstring_view kEnterpriseProductPrefix( |
| L"EnterpriseProduct"); |
| for (base::win::RegistryValueIterator iter(root_key, state_key.c_str(), |
| KEY_WOW64_32KEY); |
| iter.Valid(); ++iter) { |
| std::wstring_view value_name(iter.Name()); |
| if (base::StartsWith(value_name, kEnterpriseProductPrefix, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| product_guid_ = value_name.substr(kEnterpriseProductPrefix.size()); |
| break; |
| } |
| } |
| } |
| |
| // Read from the ClientStateMedium key. Values here override those in |
| // ClientState. |
| if (system_install && |
| key.Open(root_key, install_static::GetClientStateMediumKeyPath().c_str(), |
| kAccess) == ERROR_SUCCESS) { |
| DWORD dword_value = 0; |
| |
| if (key.ReadValueDW(google_update::kRegUsageStatsField, &dword_value) == |
| ERROR_SUCCESS) { |
| has_usagestats_ = true; |
| usagestats_ = dword_value; |
| } |
| |
| if (key.ReadValueDW(google_update::kRegEulaAceptedField, &dword_value) == |
| ERROR_SUCCESS) { |
| has_eula_accepted_ = true; |
| eula_accepted_ = dword_value; |
| } |
| } |
| |
| if (version_.get() && uninstall_command_.GetProgram().empty()) { |
| // The product has a "pv" in its Clients key, but is missing the |
| // "UninstallString" value expected in its ClientState key. Manufacture the |
| // expected uninstall command on the basis of what appears to be present on |
| // the machine. |
| |
| // FindInstallPath returns the path to a version directory if one exists in |
| // any of the standard install locations. |
| if (const auto version_dir = FindInstallPath(system_install, *version_); |
| !version_dir.empty()) { |
| uninstall_command_ = base::CommandLine( |
| version_dir.Append(kInstallerDir).Append(kSetupExe)); |
| uninstall_command_.AppendSwitch(switches::kUninstall); |
| InstallUtil::AppendModeAndChannelSwitches(&uninstall_command_); |
| |
| // Note that `AppendModeAndChannelSwitches` will use the current process's |
| // channel. When called from within a browser process, this will be |
| // correct. When called from within the installer, it will not be. To |
| // ensure that the browser's notion of the channel is always used, strip |
| // off a value added above and explicitly add the value from the Clients |
| // key. |
| uninstall_command_.RemoveSwitch(switches::kChannel); |
| if (!channel_.empty()) { |
| uninstall_command_.AppendSwitchNative(switches::kChannel, channel_); |
| } |
| |
| if (system_install) { |
| uninstall_command_.AppendSwitch(switches::kSystemLevel); |
| } |
| uninstall_command_.AppendSwitch(switches::kVerboseLogging); |
| } |
| } |
| |
| if (version_.get() && system_install && (!msi_ || product_guid_.empty())) { |
| // A system-level install may be missing the "msi" and/or |
| // "EnterpriseProduct" values in ClientState. To repair from this, search |
| // for an ARP entry under a product guid with a display name matching this |
| // product. |
| if (auto product_guid = |
| FindProductGuid(InstallUtil::GetDisplayName(), product_guid_); |
| !product_guid.empty()) { |
| msi_ = true; |
| product_guid_ = std::move(product_guid); |
| // Add `--msi` to the uninstall args if it is missing. |
| if (!uninstall_command_.HasSwitch(switches::kMsi)) { |
| uninstall_command_.AppendSwitch(switches::kMsi); |
| } |
| } |
| } |
| |
| return version_.get() != nullptr; |
| } |
| |
| base::FilePath ProductState::GetSetupPath() const { |
| return uninstall_command_.GetProgram(); |
| } |
| |
| const base::Version& ProductState::version() const { |
| DCHECK(version_); |
| return *version_; |
| } |
| |
| ProductState& ProductState::CopyFrom(const ProductState& other) { |
| version_.reset(other.version_.get() ? new base::Version(*other.version_) |
| : nullptr); |
| old_version_.reset(other.old_version_.get() |
| ? new base::Version(*other.old_version_) |
| : nullptr); |
| channel_ = other.channel_; |
| brand_ = other.brand_; |
| uninstall_command_ = other.uninstall_command_; |
| product_guid_ = other.product_guid_; |
| oem_install_ = other.oem_install_; |
| commands_.CopyFrom(other.commands_); |
| eula_accepted_ = other.eula_accepted_; |
| usagestats_ = other.usagestats_; |
| msi_ = other.msi_; |
| has_eula_accepted_ = other.has_eula_accepted_; |
| has_oem_install_ = other.has_oem_install_; |
| has_usagestats_ = other.has_usagestats_; |
| |
| return *this; |
| } |
| |
| void ProductState::Clear() { |
| version_.reset(); |
| old_version_.reset(); |
| channel_.clear(); |
| brand_.clear(); |
| oem_install_.clear(); |
| uninstall_command_ = base::CommandLine(base::CommandLine::NO_PROGRAM); |
| product_guid_.clear(); |
| commands_.Clear(); |
| eula_accepted_ = 0; |
| usagestats_ = 0; |
| msi_ = false; |
| has_eula_accepted_ = false; |
| has_oem_install_ = false; |
| has_usagestats_ = false; |
| } |
| |
| bool ProductState::GetEulaAccepted(DWORD* eula_accepted) const { |
| DCHECK(eula_accepted); |
| if (!has_eula_accepted_) |
| return false; |
| *eula_accepted = eula_accepted_; |
| return true; |
| } |
| |
| bool ProductState::GetOemInstall(std::wstring* oem_install) const { |
| DCHECK(oem_install); |
| if (!has_oem_install_) |
| return false; |
| *oem_install = oem_install_; |
| return true; |
| } |
| |
| bool ProductState::GetUsageStats(DWORD* usagestats) const { |
| DCHECK(usagestats); |
| if (!has_usagestats_) |
| return false; |
| *usagestats = usagestats_; |
| return true; |
| } |
| |
| // static |
| std::wstring ProductState::FindProductGuid(std::wstring_view display_name, |
| std::wstring_view hint) { |
| constexpr base::wcstring_view kUninstallRootKey( |
| L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"); |
| constexpr size_t kGuidLength = 36; // Does not include braces. |
| constexpr REGSAM kViews[] = { |
| 0, // The default view for this bitness. |
| #if defined(ARCH_CPU_64_BITS) |
| KEY_WOW64_32KEY, // 32-bit view. |
| #else |
| KEY_WOW64_64KEY, // 64-bit view. |
| #endif |
| }; |
| // Returns true if the DisplayName value for the uninstall entry for `name` in |
| // `view` of HKLM equals `display_name`. |
| auto display_name_is = [&kUninstallRootKey, storage = std::wstring()]( |
| REGSAM view, std::wstring_view name, |
| std::wstring_view display_name) mutable { |
| return base::win::RegKey(HKEY_LOCAL_MACHINE, |
| base::StrCat({kUninstallRootKey, name}).c_str(), |
| KEY_QUERY_VALUE | view) |
| .ReadValue(kUninstallDisplayNameField, &storage) == |
| ERROR_SUCCESS && |
| storage == display_name; |
| }; |
| |
| // If the caller provided a hint, look first for an entry for it. |
| if (!hint.empty()) { |
| const std::wstring name = base::StrCat({L"{", hint, L"}"}); |
| for (const REGSAM view : kViews) { |
| if (display_name_is(view, name, display_name)) { |
| return std::wstring(hint); |
| } |
| } |
| } |
| |
| // Otherwise, search through all subkeys named with GUIDs looking for a hit. |
| for (const REGSAM view : kViews) { |
| for (base::win::RegistryKeyIterator iter(HKEY_LOCAL_MACHINE, |
| kUninstallRootKey.c_str(), view); |
| iter.Valid(); ++iter) { |
| const std::wstring_view key_name(iter.Name()); |
| // Skip this key if it doesn't plausibly look like a product guid. |
| if (key_name.size() != kGuidLength + 2 || key_name.front() != L'{' || |
| key_name.back() != L'}') { |
| continue; |
| } |
| if (display_name_is(view, key_name, display_name)) { |
| return std::wstring(key_name.substr(1, kGuidLength)); |
| } |
| } |
| } |
| |
| return {}; |
| } |
| |
| InstallationState::InstallationState() = default; |
| |
| void InstallationState::Initialize() { |
| user_chrome_.Initialize(false); |
| system_chrome_.Initialize(true); |
| } |
| |
| const ProductState* InstallationState::GetProductState( |
| bool system_install) const { |
| const ProductState* product_state = |
| GetNonVersionedProductState(system_install); |
| return product_state->version_.get() ? product_state : nullptr; |
| } |
| |
| const ProductState* InstallationState::GetNonVersionedProductState( |
| bool system_install) const { |
| return system_install ? &system_chrome_ : &user_chrome_; |
| } |
| |
| } // namespace installer |