| // 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 <shlobj.h> |
| #include <wrl/client.h> |
| #include <wrl/implements.h> |
| |
| #include <regstr.h> |
| |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #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/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_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/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 "build/branding_buildflags.h" |
| #include "build/build_config.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/test/integration_tests_impl.h" |
| #include "chrome/updater/updater_branding.h" |
| #include "chrome/updater/updater_scope.h" |
| #include "chrome/updater/updater_version.h" |
| #include "chrome/updater/util/unittest_util.h" |
| #include "chrome/updater/util/unittest_util_win.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/updater_installer_strings.h" |
| #include "chrome/updater/win/win_constants.h" |
| #include "components/crx_file/crx_verifier.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.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 RegKeyExistsCOM(HKEY root, const std::wstring& path) { |
| return base::win::RegKey(root, path.c_str(), KEY_QUERY_VALUE).Valid(); |
| } |
| |
| [[nodiscard]] bool DeleteRegKey(HKEY root, const std::wstring& path) { |
| LONG result = |
| base::win::RegKey(root, L"", Wow6432(KEY_READ)).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"", KEY_READ).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) { |
| for (const wchar_t* key : {CLIENTS_KEY, UPDATER_KEY}) { |
| EXPECT_EQ(is_installed, RegKeyExists(root, key)); |
| } |
| |
| EXPECT_EQ(is_installed, base::PathExists(*GetGoogleUpdateExePath(scope))); |
| |
| if (is_installed) { |
| EXPECT_TRUE(RegKeyExists(root, CLIENT_STATE_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()); |
| |
| 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, kRegKeyCompanyEnrollment, |
| UPDATER_POLICIES_KEY}) { |
| EXPECT_FALSE(RegKeyExists(HKEY_LOCAL_MACHINE, key)); |
| } |
| } |
| |
| EXPECT_FALSE(RegKeyExists(root, UPDATER_KEY)); |
| |
| if (!IsSystemInstall(scope)) { |
| ForEachRegistryRunValueWithPrefix( |
| base::ASCIIToWide(PRODUCT_FULLNAME_STRING), |
| base::BindRepeating([](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 IID& iid : |
| JoinVectors(GetSideBySideInterfaces(scope), |
| is_active_and_sxs ? GetActiveInterfaces(scope) |
| : std::vector<IID>())) { |
| EXPECT_EQ(is_installed, RegKeyExistsCOM(root, GetComIidRegistryPath(iid))); |
| 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), |
| base::BindRepeating([](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.c_str())); |
| |
| TaskScheduler::TaskInfo task_info; |
| ASSERT_TRUE(task_scheduler->GetTaskInfo(task_name.c_str(), &task_info)); |
| ASSERT_EQ(task_info.exec_actions.size(), 1u); |
| EXPECT_STREQ( |
| task_info.exec_actions[0].arguments.c_str(), |
| base::StrCat({L"--wake ", IsSystemInstall(scope) ? L"--system " : L"", |
| L"--enable-logging " |
| L"--vmodule=*/components/winhttp/*=2," |
| L"*/components/update_client/*=2," |
| L"*/chrome/updater/*=2"}) |
| .c_str()); |
| |
| EXPECT_EQ(task_info.trigger_types, |
| TaskScheduler::TriggerType::TRIGGER_TYPE_HOURLY | |
| (IsSystemInstall(scope) |
| ? TaskScheduler::TriggerType::TRIGGER_TYPE_POST_REBOOT |
| : 0)); |
| } else { |
| task_scheduler->ForEachTaskWithPrefix( |
| base::ASCIIToWide(PRODUCT_FULLNAME_STRING), |
| base::BindRepeating([](const std::wstring& task_name) { |
| ADD_FAILURE() << "Unexpected task found: " << task_name; |
| })); |
| } |
| |
| const absl::optional<base::FilePath> path = |
| GetVersionedInstallDirectory(scope, base::Version(kUpdaterVersion)); |
| ASSERT_TRUE(path); |
| EXPECT_TRUE(WaitFor(base::BindLambdaForTesting([&]() { |
| return is_installed == base::PathExists(*path); |
| }), |
| base::BindLambdaForTesting([&]() { |
| VLOG(0) << "Still waiting for " << *path |
| << " where is_installed=" << is_installed; |
| }))) |
| << base::JoinString( |
| [&path]() { |
| base::FileEnumerator it(*path, true, |
| base::FileEnumerator::FILES | |
| base::FileEnumerator::DIRECTORIES); |
| std::vector<base::FilePath::StringType> files; |
| for (base::FilePath name = it.Next(); !name.empty(); |
| name = it.Next()) { |
| files.push_back(name.value()); |
| } |
| |
| return files; |
| }(), |
| FILE_PATH_LITERAL(",")); |
| } |
| |
| // Returns true if any updater process is found running in any session in the |
| // system, regardless of its path. |
| bool IsUpdaterRunning() { |
| return test::IsProcessRunning(GetExecutableRelativePath().value()); |
| } |
| |
| 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 = [&]() -> base::Process { |
| 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()), |
| |
| build_legacy_switch(updater::kEnableLoggingSwitch), |
| |
| // This switch and its value must be connected by '=' because logging |
| // switch does not support legacy format. |
| base::StrCat({build_legacy_switch(updater::kLoggingModuleSwitch), L"=", |
| base::ASCIIToWide(updater::kLoggingModuleSwitchValue)}), |
| |
| 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::Process { |
| base::CommandLine install_cmd(exe_path); |
| |
| install_cmd.AppendSwitch(kEnableLoggingSwitch); |
| install_cmd.AppendSwitchASCII(kLoggingModuleSwitch, |
| kLoggingModuleSwitchValue); |
| 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(); |
| } |
| |
| class WindowEnumerator { |
| public: |
| WindowEnumerator(HWND parent, |
| base::RepeatingCallback<bool(HWND hwnd)> filter, |
| base::RepeatingCallback<void(HWND hwnd)> action) |
| : parent_(parent), filter_(filter), action_(action) {} |
| |
| WindowEnumerator(const WindowEnumerator&) = delete; |
| WindowEnumerator& operator=(const WindowEnumerator&) = delete; |
| |
| void Run() const { |
| ::EnumChildWindows(parent_, &OnWindowProc, reinterpret_cast<LPARAM>(this)); |
| } |
| |
| static std::wstring GetWindowClass(HWND hwnd) { |
| constexpr int kMaxWindowClassNameLength = 256; |
| wchar_t buffer[kMaxWindowClassNameLength + 1] = {0}; |
| int name_len = ::GetClassName(hwnd, buffer, std::size(buffer)); |
| if (name_len <= 0 || name_len > kMaxWindowClassNameLength) |
| return std::wstring(); |
| |
| return std::wstring(&buffer[0], name_len); |
| } |
| |
| static bool IsSystemDialog(HWND hwnd) { |
| constexpr wchar_t kSystemDialogClass[] = L"#32770"; |
| return GetWindowClass(hwnd) == kSystemDialogClass; |
| } |
| |
| static std::wstring GetWindowText(HWND hwnd) { |
| const int num_chars = ::GetWindowTextLength(hwnd); |
| if (!num_chars) |
| return std::wstring(); |
| std::vector<wchar_t> text(num_chars + 1); |
| if (!::GetWindowText(hwnd, &text.front(), text.size())) |
| return std::wstring(); |
| return std::wstring(text.begin(), text.end()); |
| } |
| |
| private: |
| bool OnWindow(HWND hwnd) const { |
| if (filter_.Run(hwnd)) |
| action_.Run(hwnd); |
| |
| // Returns true to keep enumerating. |
| return true; |
| } |
| |
| static BOOL CALLBACK OnWindowProc(HWND hwnd, LPARAM lparam) { |
| return reinterpret_cast<WindowEnumerator*>(lparam)->OnWindow(hwnd); |
| } |
| |
| const HWND parent_; |
| base::RepeatingCallback<bool(HWND hwnd)> filter_; |
| base::RepeatingCallback<void(HWND hwnd)> action_; |
| }; |
| |
| 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 = ¶ms[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(¶m); }); |
| 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(); |
| } |
| |
| } // 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))); |
| } |
| } |
| |
| for (const IID& iid : JoinVectors(GetSideBySideInterfaces(scope), |
| GetActiveInterfaces(scope))) { |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComIidRegistryPath(iid))); |
| EXPECT_TRUE(DeleteRegKeyCOM(root, GetComTypeLibRegistryPath(iid))); |
| } |
| |
| if (!IsSystemInstall(scope)) { |
| ForEachRegistryRunValueWithPrefix( |
| base::ASCIIToWide(PRODUCT_FULLNAME_STRING), |
| base::BindRepeating([](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), |
| base::BindRepeating([](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), |
| base::BindRepeating( |
| [](scoped_refptr<TaskScheduler> task_scheduler, |
| const std::wstring& task_name) { |
| task_scheduler->DeleteTask(task_name.c_str()); |
| }, |
| task_scheduler)); |
| |
| const absl::optional<base::FilePath> target_path = |
| GetGoogleUpdateExePath(scope); |
| if (target_path) |
| base::DeleteFile(*target_path); |
| |
| absl::optional<base::FilePath> path = GetInstallDirectory(scope); |
| ASSERT_TRUE(path); |
| ASSERT_TRUE(base::DeletePathRecursively(*path)) << *path; |
| |
| // TODO(crbug.com/1401759) - this can be removed after the crbug is closed. |
| VLOG(0) << __func__ << " end."; |
| } |
| |
| void EnterTestMode(const GURL& update_url, |
| const GURL& crash_upload_url, |
| const GURL& device_management_url) { |
| ASSERT_TRUE(ExternalConstantsBuilder() |
| .SetUpdateURL(std::vector<std::string>{update_url.spec()}) |
| .SetCrashUploadURL(crash_upload_url.spec()) |
| .SetDeviceManagementURL(device_management_url.spec()) |
| .SetUseCUP(false) |
| .SetInitialDelay(base::Milliseconds(100)) |
| .SetServerKeepAliveTime(base::Seconds(1)) |
| .SetCrxVerifierFormat(crx_file::VerifierFormat::CRX3) |
| .SetOverinstallTimeout(base::Seconds(11)) |
| .Modify()); |
| } |
| |
| void ExpectInstalled(UpdaterScope scope) { |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsInstalled, |
| CheckInstallationVersions::kCheckSxSOnly); |
| } |
| |
| void ExpectClean(UpdaterScope scope) { |
| ExpectCleanProcesses(); |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled, |
| CheckInstallationVersions::kCheckActiveAndSxS); |
| } |
| |
| void ExpectCandidateUninstalled(UpdaterScope scope) { |
| CheckInstallation(scope, CheckInstallationStatus::kCheckIsNotInstalled, |
| CheckInstallationVersions::kCheckSxSOnly); |
| } |
| |
| void Uninstall(UpdaterScope scope) { |
| // Note: "updater.exe --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. |
| base::FilePath path = GetSetupExecutablePath().DirName().Append( |
| FILE_PATH_LITERAL("updater_test.exe")); |
| ASSERT_FALSE(path.empty()); |
| base::CommandLine command_line(path); |
| 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. |
| // TODO(crbug.com/1217765): Figure out a way to replace this. |
| SleepFor(base::Seconds(5)); |
| ASSERT_EQ(0, exit_code); |
| } |
| |
| void SetActive(UpdaterScope /*scope*/, const std::string& id) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| 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) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| 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) { |
| // TODO(crbug.com/1159498): Standardize registry access. |
| 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(UpdaterScope /*scope*/) { |
| return WaitFor( |
| base::BindRepeating([]() { return !IsUpdaterRunning(); }), |
| base::BindLambdaForTesting([]() { |
| VLOG(0) << "Still waiting for updater to exit. " |
| << test::PrintProcesses(GetExecutableRelativePath().value()); |
| })); |
| } |
| |
| // 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 : 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 = base::win::WStringFromGUID(iid); |
| |
| std::wstring val; |
| { |
| const auto& path = iid_reg_path + L"\\ProxyStubClsid32"; |
| EXPECT_EQ(base::win::RegKey(root, path.c_str(), KEY_READ) |
| .ReadValue(L"", &val), |
| ERROR_SUCCESS) |
| << ": " << root << ": " << path << ": " << iid_string; |
| EXPECT_EQ(val, L"{00020424-0000-0000-C000-000000000046}"); |
| } |
| |
| { |
| const auto& path = iid_reg_path + L"\\TypeLib"; |
| EXPECT_EQ(base::win::RegKey(root, path.c_str(), KEY_READ) |
| .ReadValue(L"", &val), |
| ERROR_SUCCESS) |
| << ": " << root << ": " << path << ": " << iid_string; |
| EXPECT_EQ(val, iid_string); |
| } |
| |
| 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; |
| } |
| } |
| } |
| } |
| |
| // 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); |
| } |
| |
| 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(base::win::WStringFromGUID(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) { |
| 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"brand").Get(), |
| base::win::ScopedBstr(L"en").Get(), |
| base::win::ScopedBstr(L"ap").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; |
| while (!done && (timer.Elapsed() < kExpirationTimeout)) { |
| 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))); |
| 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)); |
| |
| std::wstring state_description; |
| std::wstring extra_data; |
| 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(¤t_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->install()); |
| } |
| 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::StringPrintf( |
| L"[Bytes downloaded: %d][Bytes total: %d][Time remaining: %d]", |
| bytes_downloaded, total_bytes_to_download, |
| download_time_remaining_ms); |
| break; |
| } |
| |
| case STATE_DOWNLOAD_COMPLETE: |
| case STATE_EXTRACTING: |
| case STATE_APPLYING_DIFFERENTIAL_PATCH: |
| case STATE_READY_TO_INSTALL: { |
| state_description = L"Download completed!"; |
| 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::StringPrintf(L"[Bytes downloaded: %d][Bytes total: %d]", |
| 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::StringPrintf(L"[Install Progress: %d][Time remaining: %d]", |
| 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!"; |
| EXPECT_HRESULT_SUCCEEDED(state->get_errorCode(&error_code)); |
| 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::StringPrintf( |
| L"[errorCode: %d][completionMessage: %ls][installerResultCode: %d]", |
| error_code, completion_message.Get(), installer_result_code); |
| break; |
| } |
| |
| default: |
| state_description = L"Unhandled state..."; |
| break; |
| } |
| |
| // TODO(crbug.com/1245992): Remove this logging once the code is test |
| // flakiness is eliminated and no further debugging is needed. |
| LOG(ERROR) << base::StringPrintf(L"[State: %d][%ls]%ls", state_value, |
| state_description.c_str(), |
| extra_data.c_str()); |
| base::PlatformThread::Sleep(base::Seconds(1)); |
| } |
| |
| EXPECT_TRUE(done) |
| << "The test timed out, consider increasing kExpirationTimeout which is: " |
| << kExpirationTimeout; |
| EXPECT_EQ(expected_final_state, state_value); |
| EXPECT_EQ(expected_error_code, error_code); |
| 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) { |
| 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)); |
| } |
| |
| 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()})); |
| } |
| |
| void DeleteLaunchCommandElevated(const std::wstring& app_id, |
| const std::wstring& command_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<IProcessLauncher> process_launcher; |
| ASSERT_HRESULT_SUCCEEDED( |
| CreateLocalServer(__uuidof(ProcessLauncherClass), process_launcher)); |
| |
| constexpr wchar_t kAppId1[] = L"{831EF4D0-B729-4F61-AA34-91526481799D}"; |
| 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)); |
| |
| 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(base::BindLambdaForTesting([&]() { |
| 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::BindLambdaForTesting([&]() { |
| 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); |
| } |
| |
| namespace { |
| |
| 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); |
| } |
| |
| } // namespace |
| |
| 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()); |
| } |
| |
| int RunVPythonCommand(const base::CommandLine& command_line) { |
| base::CommandLine python_command = command_line; |
| python_command.PrependWrapper(FILE_PATH_LITERAL("vpython3.bat")); |
| |
| int exit_code = -1; |
| base::Process process = base::LaunchProcess(python_command, {}); |
| EXPECT_TRUE(process.IsValid()); |
| EXPECT_TRUE(process.WaitForExitWithTimeout(TestTimeouts::action_timeout(), |
| &exit_code)); |
| return exit_code; |
| } |
| |
| void RunTestServiceCommand(const std::string& sub_command) { |
| 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("updater_test_service_control.py")); |
| EXPECT_TRUE(base::PathExists(path)); |
| |
| base::CommandLine command(path); |
| command.AppendArg(sub_command); |
| |
| EXPECT_EQ(RunVPythonCommand(command), 0); |
| } |
| |
| 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); |
| } |
| |
| void SetupRealUpdaterLowerVersion(UpdaterScope scope) { |
| base::FilePath exe_path; |
| ASSERT_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_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_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 |
| base::CommandLine command_line( |
| old_updater_path.Append(FILE_PATH_LITERAL("UpdaterSetup_test.exe"))); |
| command_line.AppendSwitch(kInstallSwitch); |
| int exit_code = -1; |
| Run(scope, command_line, &exit_code); |
| ASSERT_EQ(exit_code, 0); |
| } |
| |
| 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 absl::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); |
| } |
| |
| void SetupFakeLegacyUpdater(UpdaterScope scope) { |
| const HKEY root = UpdaterScopeToHKeyRoot(scope); |
| |
| base::win::RegKey key; |
| ASSERT_EQ(key.Create(root, GetAppClientsKey(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, |
| GetAppClientsKey(L"{8A69D345-D564-463C-AFF1-A69D9E530F96}").c_str(), |
| Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| ASSERT_EQ(key.WriteValue(kRegValuePV, L"99.0.0.1"), ERROR_SUCCESS); |
| key.Close(); |
| |
| ASSERT_EQ( |
| key.Create(root, |
| GetAppClientStateKey(L"{8A69D345-D564-463C-AFF1-A69D9E530F96}") |
| .c_str(), |
| 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, |
| GetAppCohortKey(L"{8A69D345-D564-463C-AFF1-A69D9E530F96}").c_str(), |
| 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, |
| GetAppClientsKey(L"{fc54d8f9-b6fd-4274-92eb-c4335cd8761e}").c_str(), |
| 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); |
| |
| const std::wstring task_name_prefix(IsSystemInstall(scope) |
| ? kLegacyTaskNamePrefixSystem |
| : kLegacyTaskNamePrefixUser); |
| for (const std::wstring& task_name : |
| {base::StrCat({task_name_prefix, L"Core"}), |
| base::StrCat({task_name_prefix, L"UA"})}) { |
| ASSERT_TRUE(task_scheduler->RegisterTask( |
| task_name.c_str(), task_name.c_str(), |
| 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 absl::optional<base::FilePath> google_update_exe = |
| GetGoogleUpdateExePath(scope); |
| ASSERT_TRUE(google_update_exe.has_value()); |
| |
| const base::FilePath exe_dir(google_update_exe->DirName()); |
| base::CommandLine command_line = |
| GetTestProcessCommandLine(scope, test::GetTestName()); |
| |
| 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")}) { |
| ASSERT_TRUE(base::CreateDirectory(dir)); |
| |
| for (const std::wstring exe_name : {kLegacyExeName, L"mock.exe"}) { |
| const base::FilePath exe(dir.Append(exe_name)); |
| ASSERT_TRUE(base::CopyFile(command_line.GetProgram(), exe)); |
| } |
| } |
| } |
| |
| void RunFakeLegacyUpdater(UpdaterScope scope) { |
| const absl::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.exe"}) { |
| 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 ExpectLegacyUpdaterMigrated(UpdaterScope scope) { |
| scoped_refptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(scope); |
| auto persisted_data = base::MakeRefCounted<PersistedData>( |
| scope, global_prefs->GetPrefService()); |
| |
| // 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_FALSE(persisted_data->GetDateLastActive(kNoPVAppId)); |
| EXPECT_FALSE(persisted_data->GetDateLastRollcall(kNoPVAppId)); |
| |
| 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).value(), -1); |
| EXPECT_EQ(persisted_data->GetDateLastRollcall(kChromeAppId).value(), 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, |
| base::BindLambdaForTesting( |
| [&count_entries](const std::wstring& /*service_name*/) { |
| ++count_entries; |
| })); |
| } else { |
| // Expect no GoogleUpdate run value. |
| ForEachRegistryRunValueWithPrefix( |
| kLegacyRunValuePrefix, |
| base::BindLambdaForTesting( |
| [&count_entries](const std::wstring& /* run_name*/) { |
| ++count_entries; |
| })); |
| } |
| EXPECT_EQ(count_entries, 0); |
| |
| // Expect no GoogleUpdate tasks. |
| count_entries = 0; |
| const std::wstring task_name_prefix(IsSystemInstall(scope) |
| ? kLegacyTaskNamePrefixSystem |
| : kLegacyTaskNamePrefixUser); |
| scoped_refptr<TaskScheduler> task_scheduler = |
| TaskScheduler::CreateInstance(scope, /*use_task_subfolders=*/false); |
| task_scheduler->ForEachTaskWithPrefix( |
| task_name_prefix, |
| base::BindLambdaForTesting( |
| [&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`. |
| int count_google_update_exe = 0; |
| const absl::optional<base::FilePath> google_update_exe = |
| GetGoogleUpdateExePath(scope); |
| ASSERT_TRUE(google_update_exe.has_value()); |
| ASSERT_TRUE(base::PathExists(*google_update_exe)); |
| |
| const base::FilePath exe_dir(google_update_exe->DirName()); |
| |
| base::FileEnumerator it( |
| exe_dir, false, |
| base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES); |
| for (base::FilePath name = it.Next(); !name.empty(); name = it.Next()) { |
| if (name == google_update_exe) { |
| ++count_google_update_exe; |
| continue; |
| } |
| |
| ADD_FAILURE() << "Unexpected file/directory found: " << name; |
| } |
| |
| EXPECT_EQ(count_google_update_exe, 1); |
| } |
| |
| void InstallApp(UpdaterScope scope, const std::string& app_id) { |
| base::win::RegKey key; |
| ASSERT_EQ(key.Create(UpdaterScopeToHKeyRoot(scope), |
| GetAppClientsKey(app_id).c_str(), Wow6432(KEY_WRITE)), |
| ERROR_SUCCESS); |
| RegisterApp(scope, app_id); |
| } |
| |
| void UninstallApp(UpdaterScope scope, const std::string& app_id) { |
| base::win::RegKey key; |
| ASSERT_EQ( |
| key.Open(UpdaterScopeToHKeyRoot(scope), CLIENTS_KEY, Wow6432(KEY_WRITE)), |
| 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) { |
| constexpr wchar_t kTestAppID[] = L"{CDABE316-39CD-43BA-8440-6D1E0547AEE6}"; |
| const base::Version kTestPV("1.2.3.4"); |
| constexpr char kManifestFormat[] = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| "<response protocol=\"3.0\">\n" |
| " <app appid=\"%ls\" status=\"ok\">\n" |
| " <updatecheck status=\"ok\">\n" |
| " <manifest version=\"%s\">\n" |
| " <packages>\n" |
| " <package hash_sha256=\"sha256hash_foobar\"\n" |
| " name=\"cmd.exe\" required=\"true\" size=\"%lld\"/>\n" |
| " </packages>\n" |
| " <actions>\n" |
| " <action event=\"install\"\n" |
| " run=\"cmd.exe\"\n" |
| " arguments=\"/c "%ls" \"/>\n" |
| " </actions>\n" |
| " </manifest>\n" |
| " </updatecheck>\n" |
| " <data index=\"verboselogging\" name=\"install\" status=\"ok\">\n" |
| " {\"distribution\": { \"verbose_logging\": true}}\n" |
| " </data>\n" |
| " </app>\n" |
| "</response>\n"; |
| |
| const std::wstring manifest_filename(L"OfflineManifest.gup"); |
| const std::wstring cmd_exe_arbitrarily_named(L"arbitrarily_named_cmd.exe"); |
| const std::string script_name("test_installer.bat"); |
| const std::wstring offline_dir_guid( |
| L"{7B3A5597-DDEA-409B-B900-4C3D2A94A75C}"); |
| const HKEY root = UpdaterScopeToHKeyRoot(scope); |
| const std::wstring app_client_state_key = GetAppClientStateKey(kTestAppID); |
| |
| EXPECT_TRUE(DeleteRegKey(root, app_client_state_key)); |
| |
| const absl::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(script_name)); |
| |
| // Create a unique name for a shared event to be waited for in this process |
| // and signaled in the offline installer process to confirm the installer |
| // was run. |
| test::EventHolder event_holder(test::CreateWaitableEventForTest()); |
| |
| EXPECT_TRUE(base::WriteFile( |
| batch_script_path, |
| [](UpdaterScope scope, const std::string& app_client_state_key, |
| const std::wstring& event_name) -> std::string { |
| const std::string reg_hive = IsSystemInstall(scope) ? "HKLM" : "HKCU"; |
| |
| base::CommandLine post_install_cmd( |
| GetTestProcessCommandLine(scope, GetTestName())); |
| post_install_cmd.AppendSwitchNative(kTestEventToSignal, event_name); |
| std::vector<std::string> commands; |
| const struct { |
| const char* value_name; |
| const char* type; |
| const std::string value; |
| } reg_items[5] = { |
| {"InstallerResult", "REG_DWORD", "0"}, |
| {"InstallerError", "REG_DWORD", "0"}, |
| {"InstallerExtraCode1", "REG_DWORD", "0"}, |
| {"InstallerResultUIString", "REG_SZ", "CoolApp"}, |
| {"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(), app_client_state_key.c_str(), |
| reg_item.value_name, reg_item.type, reg_item.value.c_str())); |
| } |
| return base::JoinString(commands, "\n"); |
| }(scope, base::WideToASCII(app_client_state_key), event_holder.name))); |
| |
| // The updater only allows `.exe` or `.msi` to run from the offline directory. |
| // Setup `cmd.exe` as the wrapper installer. |
| base::FilePath cmd_exe_path; |
| ASSERT_TRUE(base::PathService::Get(base::DIR_SYSTEM, &cmd_exe_path)); |
| cmd_exe_path = cmd_exe_path.Append(L"cmd.exe"); |
| ASSERT_TRUE(base::CopyFile( |
| cmd_exe_path, offline_app_dir.Append(cmd_exe_arbitrarily_named))); |
| |
| base::FilePath manifest_path = offline_dir.Append(manifest_filename); |
| int64_t exe_size = 0; |
| EXPECT_TRUE(base::GetFileSize(cmd_exe_path, &exe_size)); |
| const std::string manifest = base::StringPrintf( |
| kManifestFormat, kTestAppID, kTestPV.GetString().c_str(), exe_size, |
| batch_script_path.value().c_str()); |
| 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()); |
| |
| if (is_silent_install) { |
| EXPECT_TRUE(WaitForUpdaterExit(scope)); |
| } else { |
| // Dismiss the installation completion dialog, then wait for the process |
| // exit. |
| EXPECT_TRUE(WaitFor( |
| base::BindRepeating([]() { |
| // Enumerate the top-level dialogs to find the setup dialog. |
| WindowEnumerator( |
| ::GetDesktopWindow(), base::BindRepeating([](HWND hwnd) { |
| return WindowEnumerator::IsSystemDialog(hwnd) && |
| base::Contains(WindowEnumerator::GetWindowText(hwnd), |
| GetLocalizedStringF( |
| IDS_INSTALLER_DISPLAY_NAME_BASE, |
| GetLocalizedString( |
| IDS_FRIENDLY_COMPANY_NAME_BASE))); |
| }), |
| base::BindRepeating([](HWND hwnd) { |
| // Enumerates the dialog items to search for installation |
| // complete message. Once found, close the dialog. |
| WindowEnumerator( |
| hwnd, base::BindRepeating([](HWND hwnd) { |
| return base::Contains( |
| WindowEnumerator::GetWindowText(hwnd), |
| GetLocalizedString( |
| IDS_BUNDLE_INSTALLED_SUCCESSFULLY_BASE)); |
| }), |
| base::BindRepeating([](HWND hwnd) { |
| ::PostMessage(::GetParent(hwnd), WM_CLOSE, 0, 0); |
| })) |
| .Run(); |
| })) |
| .Run(); |
| |
| return !IsUpdaterRunning(); |
| }), |
| base::BindLambdaForTesting( |
| []() { VLOG(0) << "Still waiting for the process exit."; }))); |
| } |
| |
| // Updater should have written "pv". |
| EXPECT_EQ(base::MakeRefCounted<PersistedData>( |
| scope, CreateGlobalPrefs(scope)->GetPrefService()) |
| ->GetProductVersion(base::WideToASCII(kTestAppID)), |
| kTestPV); |
| |
| // App installer should have created the expected reg value. |
| base::win::RegKey key; |
| std::wstring value; |
| EXPECT_EQ( |
| key.Open(root, app_client_state_key.c_str(), Wow6432(KEY_QUERY_VALUE)), |
| ERROR_SUCCESS); |
| EXPECT_EQ(key.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 updater::test |