/ src / Update / PowerToys.Update.cpp
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  }