PowerToys.Update.cpp
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 #define WIN32_LEAN_AND_MEAN 6 #include "Generated Files/resource.h" 7 8 #include <Windows.h> 9 #include <shellapi.h> 10 11 #include <filesystem> 12 #include <string_view> 13 14 #include <common/updating/updating.h> 15 #include <common/updating/updateState.h> 16 #include <common/updating/installer.h> 17 18 #include <common/utils/elevation.h> 19 #include <common/utils/HttpClient.h> 20 #include <common/utils/process_path.h> 21 #include <common/utils/resources.h> 22 #include <common/utils/timeutil.h> 23 24 #include <common/SettingsAPI/settings_helpers.h> 25 26 #include <common/logger/logger.h> 27 28 #include <winrt/Windows.ApplicationModel.h> 29 #include <winrt/Windows.Storage.h> 30 #include <Msi.h> 31 32 #include "../runner/tray_icon.h" 33 #include "../runner/UpdateUtils.h" 34 35 using namespace cmdArg; 36 37 namespace fs = std::filesystem; 38 39 std::optional<fs::path> CopySelfToTempDir() 40 { 41 std::error_code error; 42 auto dst_path = fs::temp_directory_path() / "PowerToys.Update.exe"; 43 fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error); 44 if (error) 45 { 46 return std::nullopt; 47 } 48 49 return std::move(dst_path); 50 } 51 52 std::optional<fs::path> ObtainInstaller(bool& isUpToDate) 53 { 54 using namespace updating; 55 56 isUpToDate = false; 57 58 auto state = UpdateState::read(); 59 60 const auto new_version_info = get_github_version_info_async().get(); 61 if (std::holds_alternative<version_up_to_date>(*new_version_info)) 62 { 63 isUpToDate = true; 64 Logger::error("Invoked with -update_now argument, but no update was available"); 65 return std::nullopt; 66 } 67 68 if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading) 69 { 70 if (!new_version_info) 71 { 72 Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error()); 73 return std::nullopt; 74 } 75 76 // Cleanup old updates before downloading the latest 77 updating::cleanup_updates(); 78 79 auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get(); 80 if (!downloaded_installer) 81 { 82 Logger::error("Couldn't download new installer"); 83 } 84 85 return downloaded_installer; 86 } 87 else if (state.state == UpdateState::readyToInstall) 88 { 89 fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename }; 90 if (fs::is_regular_file(installer)) 91 { 92 return std::move(installer); 93 } 94 else 95 { 96 Logger::error(L"Couldn't find a downloaded installer {}", installer.native()); 97 return std::nullopt; 98 } 99 } 100 else if (state.state == UpdateState::upToDate) 101 { 102 isUpToDate = true; 103 return std::nullopt; 104 } 105 106 Logger::error("Invoked with -update_now argument, but update state was invalid"); 107 return std::nullopt; 108 } 109 110 bool InstallNewVersionStage1(fs::path installer) 111 { 112 if (auto copy_in_temp = CopySelfToTempDir()) 113 { 114 // Detect if PT was running 115 const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr); 116 117 if (pt_main_window != nullptr) 118 { 119 SendMessageW(pt_main_window, WM_CLOSE, 0, 0); 120 } 121 122 std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 }; 123 arguments += L" \""; 124 arguments += installer.c_str(); 125 arguments += L"\""; 126 SHELLEXECUTEINFOW sei{ sizeof(sei) }; 127 sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC }; 128 sei.lpFile = copy_in_temp->c_str(); 129 sei.nShow = SW_SHOWNORMAL; 130 131 sei.lpParameters = arguments.c_str(); 132 return ShellExecuteExW(&sei) == TRUE; 133 } 134 else 135 { 136 return false; 137 } 138 } 139 140 bool InstallNewVersionStage2(std::wstring installer_path) 141 { 142 std::transform(begin(installer_path), end(installer_path), begin(installer_path), ::towlower); 143 144 bool success = true; 145 146 if (installer_path.ends_with(L".msi")) 147 { 148 success = MsiInstallProductW(installer_path.data(), nullptr) == ERROR_SUCCESS; 149 } 150 else 151 { 152 // If it's not .msi, then it's a wix bootstrapper 153 SHELLEXECUTEINFOW sei{ sizeof(sei) }; 154 sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE }; 155 sei.lpFile = installer_path.c_str(); 156 sei.nShow = SW_SHOWNORMAL; 157 std::wstring parameters = L"/passive /norestart"; 158 sei.lpParameters = parameters.c_str(); 159 160 success = ShellExecuteExW(&sei) == TRUE; 161 162 // Wait for the install completion 163 if (success) 164 { 165 WaitForSingleObject(sei.hProcess, INFINITE); 166 DWORD exitCode = 0; 167 GetExitCodeProcess(sei.hProcess, &exitCode); 168 success = exitCode == 0; 169 CloseHandle(sei.hProcess); 170 } 171 } 172 173 if (!success) 174 { 175 return false; 176 } 177 178 UpdateState::store([&](UpdateState& state) { 179 state = {}; 180 state.githubUpdateLastCheckedDate.emplace(timeutil::now()); 181 state.state = UpdateState::upToDate; 182 }); 183 184 return true; 185 } 186 187 int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) 188 { 189 int nArgs = 0; 190 LPWSTR* args = CommandLineToArgvW(GetCommandLineW(), &nArgs); 191 if (!args || nArgs < 2) 192 { 193 return 1; 194 } 195 196 std::wstring_view action{ args[1] }; 197 198 std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location()); 199 logFilePath.append(LogSettings::updateLogPath); 200 Logger::init(LogSettings::updateLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); 201 202 if (action == UPDATE_NOW_LAUNCH_STAGE1) 203 { 204 bool isUpToDate = false; 205 auto installerPath = ObtainInstaller(isUpToDate); 206 bool failed = !installerPath.has_value(); 207 failed = failed || !InstallNewVersionStage1(std::move(*installerPath)); 208 if (failed) 209 { 210 UpdateState::store([&](UpdateState& state) { 211 state = {}; 212 state.githubUpdateLastCheckedDate.emplace(timeutil::now()); 213 state.state = isUpToDate ? UpdateState::upToDate : UpdateState::errorDownloading; 214 }); 215 } 216 return failed; 217 } 218 else if (action == UPDATE_NOW_LAUNCH_STAGE2) 219 { 220 using namespace std::string_view_literals; 221 const bool failed = !InstallNewVersionStage2(args[2]); 222 if (failed) 223 { 224 UpdateState::store([&](UpdateState& state) { 225 state = {}; 226 state.githubUpdateLastCheckedDate.emplace(timeutil::now()); 227 state.state = UpdateState::errorDownloading; 228 }); 229 } 230 return failed; 231 } 232 233 return 0; 234 }