/ src / runner / main.cpp
main.cpp
  1  #include "pch.h"
  2  #include <ShellScalingApi.h>
  3  #include <lmcons.h>
  4  #include <filesystem>
  5  #include <sstream>
  6  #include "tray_icon.h"
  7  #include "powertoy_module.h"
  8  #include "trace.h"
  9  #include "general_settings.h"
 10  #include "restart_elevated.h"
 11  #include "RestartManagement.h"
 12  #include "Generated files/resource.h"
 13  #include "settings_telemetry.h"
 14  
 15  #include <common/comUtils/comUtils.h>
 16  #include <common/display/dpi_aware.h>
 17  #include <common/Telemetry/EtwTrace/EtwTrace.h>
 18  #include <common/notifications/notifications.h>
 19  #include <common/notifications/dont_show_again.h>
 20  #include <common/updating/installer.h>
 21  #include <common/updating/updating.h>
 22  #include <common/updating/updateState.h>
 23  #include <common/utils/appMutex.h>
 24  #include <common/utils/elevation.h>
 25  #include <common/utils/os-detect.h>
 26  #include <common/utils/processApi.h>
 27  #include <common/utils/resources.h>
 28  #include <common/utils/clean_video_conference.h>
 29  
 30  #include "UpdateUtils.h"
 31  #include "ActionRunnerUtils.h"
 32  
 33  #include <winrt/Windows.System.h>
 34  
 35  #include <Psapi.h>
 36  #include <RestartManager.h>
 37  #include <shellapi.h>
 38  #include "centralized_kb_hook.h"
 39  #include "centralized_hotkeys.h"
 40  #include "quick_access_host.h"
 41  #include "ai_detection.h"
 42  #include <common/utils/package.h>
 43  
 44  #if _DEBUG && _WIN64
 45  #include "unhandled_exception_handler.h"
 46  #endif
 47  #include <common/logger/logger.h>
 48  #include <common/SettingsAPI/settings_helpers.h>
 49  #include <runner/settings_window.h>
 50  #include <common/utils/process_path.h>
 51  #include <common/utils/winapi_error.h>
 52  #include <common/utils/window.h>
 53  #include <common/version/version.h>
 54  #include <common/utils/string_utils.h>
 55  #include <common/utils/gpo.h>
 56  
 57  // disabling warning 4458 - declaration of 'identifier' hides class member
 58  // to avoid warnings from GDI files - can't add winRT directory to external code
 59  // in the Cpp.Build.props
 60  #pragma warning(push)
 61  #pragma warning(disable : 4458)
 62  #include <gdiplus.h>
 63  #pragma warning(pop)
 64  
 65  namespace
 66  {
 67      const wchar_t PT_URI_PROTOCOL_SCHEME[] = L"powertoys://";
 68      const wchar_t POWER_TOYS_MODULE_LOAD_FAIL[] = L"Failed to load "; // Module name will be appended on this message and it is not localized.
 69  }
 70  
 71  void chdir_current_executable()
 72  {
 73      // Change current directory to the path of the executable.
 74      WCHAR executable_path[MAX_PATH];
 75      GetModuleFileName(NULL, executable_path, MAX_PATH);
 76      PathRemoveFileSpec(executable_path);
 77      if (!SetCurrentDirectory(executable_path))
 78      {
 79          show_last_error_message(L"Change Directory to Executable Path", GetLastError(), L"PowerToys - runner");
 80      }
 81  }
 82  
 83  // Detect AI capabilities by calling ImageResizer in detection mode.
 84  // This runs in a background thread to avoid blocking the main startup.
 85  // ImageResizer writes the result to a cache file that it reads on normal startup.
 86  void DetectAiCapabilitiesAsync(bool skipSettingsCheck)
 87  {
 88      std::thread([skipSettingsCheck]() {
 89          try
 90          {
 91              // Check if ImageResizer module is enabled (skip if called from apply_general_settings)
 92              if (!skipSettingsCheck)
 93              {
 94                  auto settings = PTSettingsHelper::load_general_settings();
 95                  if (json::has(settings, L"enabled", json::JsonValueType::Object))
 96                  {
 97                      auto enabledModules = settings.GetNamedObject(L"enabled");
 98                      if (json::has(enabledModules, L"Image Resizer", json::JsonValueType::Boolean))
 99                      {
100                          bool isEnabled = enabledModules.GetNamedBoolean(L"Image Resizer", false);
101                          if (!isEnabled)
102                          {
103                              Logger::info(L"ImageResizer module is disabled, skipping AI detection");
104                              return;
105                          }
106                      }
107                  }
108              }
109  
110              // Get ImageResizer.exe path (located in WinUI3Apps folder)
111              std::wstring imageResizerPath = get_module_folderpath();
112              imageResizerPath += L"\\WinUI3Apps\\PowerToys.ImageResizer.exe";
113  
114              if (!std::filesystem::exists(imageResizerPath))
115              {
116                  Logger::warn(L"ImageResizer.exe not found at {}, skipping AI detection", imageResizerPath);
117                  return;
118              }
119  
120              Logger::info(L"Starting AI capability detection via ImageResizer");
121  
122              // Call ImageResizer --detect-ai
123              SHELLEXECUTEINFO sei = { sizeof(sei) };
124              sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
125              sei.lpFile = imageResizerPath.c_str();
126              sei.lpParameters = L"--detect-ai";
127              sei.nShow = SW_HIDE;
128  
129              if (ShellExecuteExW(&sei))
130              {
131                  // Wait for detection to complete (with timeout)
132                  DWORD waitResult = WaitForSingleObject(sei.hProcess, 30000); // 30 second timeout
133                  CloseHandle(sei.hProcess);
134  
135                  if (waitResult == WAIT_OBJECT_0)
136                  {
137                      Logger::info(L"AI capability detection completed successfully");
138                  }
139                  else if (waitResult == WAIT_TIMEOUT)
140                  {
141                      Logger::warn(L"AI capability detection timed out");
142                  }
143                  else
144                  {
145                      Logger::warn(L"AI capability detection wait failed");
146                  }
147              }
148              else
149              {
150                  Logger::warn(L"Failed to launch ImageResizer for AI detection, error: {}", GetLastError());
151              }
152          }
153          catch (const std::exception& e)
154          {
155              Logger::error("Exception during AI capability detection: {}", e.what());
156          }
157          catch (...)
158          {
159              Logger::error("Unknown exception during AI capability detection");
160          }
161      }).detach();
162  }
163  
164  inline wil::unique_mutex_nothrow create_msi_mutex()
165  {
166      return createAppMutex(POWERTOYS_MSI_MUTEX_NAME);
167  }
168  
169  void open_menu_from_another_instance(std::optional<std::string> settings_window)
170  {
171      const HWND hwnd_main = FindWindowW(L"PToyTrayIconWindow", nullptr);
172      LPARAM msg = static_cast<LPARAM>(ESettingsWindowNames::Dashboard);
173      if (settings_window.has_value() && settings_window.value() != "")
174      {
175          msg = static_cast<LPARAM>(ESettingsWindowNames_from_string(settings_window.value()));
176      }
177      PostMessageW(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, msg);
178      SetForegroundWindow(hwnd_main); // Bring the settings window to the front
179  }
180  
181  int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow, bool openOobe, bool openScoobe, bool showRestartNotificationAfterUpdate)
182  {
183      Logger::info("Runner is starting. Elevated={} openOobe={} openScoobe={} showRestartNotificationAfterUpdate={}", isProcessElevated, openOobe, openScoobe, showRestartNotificationAfterUpdate);
184      DPIAware::EnableDPIAwarenessForThisProcess();
185  
186  #if _DEBUG && _WIN64
187  //Global error handlers to diagnose errors.
188  //We prefer this not to show any longer until there's a bug to diagnose.
189  //init_global_error_handlers();
190  #endif
191      Trace::RegisterProvider();
192  
193      // Load settings from file before reading them
194      load_general_settings();
195      auto const settings = get_general_settings();
196      start_tray_icon(isProcessElevated, settings.showThemeAdaptiveTrayIcon);
197  
198      if (settings.enableQuickAccess)
199      {
200          QuickAccessHost::start();
201      }
202      update_quick_access_hotkey(settings.enableQuickAccess, settings.quickAccessShortcut);
203      set_tray_icon_visible(settings.showSystemTrayIcon);
204      CentralizedKeyboardHook::Start();
205  
206      int result = -1;
207      try
208      {
209          if (!openOobe && showRestartNotificationAfterUpdate)
210          {
211              std::thread{
212                  [] {
213                      // Wait a bit, because Windows has a delay until it picks up toast notification registration in the registry
214                      Sleep(10000);
215                      Logger::info("Showing toast notification asking to restart PC");
216                      notifications::show_toast(GET_RESOURCE_STRING(IDS_PT_VERSION_CHANGE_ASK_FOR_COMPUTER_RESTART).c_str(), L"PowerToys");
217                  }
218              }.detach();
219          }
220  
221          std::thread{ [] {
222              PeriodicUpdateWorker();
223          } }.detach();
224  
225          // Start AI capability detection in background (Windows 11+ only)
226          // AI Super Resolution is not supported on Windows 10
227          // This calls ImageResizer --detect-ai which writes result to cache file
228          if (package::IsWin11OrGreater())
229          {
230              DetectAiCapabilitiesAsync();
231          }
232          else
233          {
234              Logger::info(L"AI capability detection skipped: Windows 10 does not support AI Super Resolution");
235          }
236  
237          std::thread{ [] {
238              if (updating::uninstall_previous_msix_version_async().get())
239              {
240                  notifications::show_toast(GET_RESOURCE_STRING(IDS_OLDER_MSIX_UNINSTALLED).c_str(), L"PowerToys");
241              }
242          } }.detach();
243  
244          chdir_current_executable();
245  
246          // We deprecated a utility called Video Conference Mute, which registered itself as a video input device.
247          // When running elevated, we try to clean up the device registration from previous installations.
248          // This is done here too because a user-scope installer won't be able to remove the driver registration due to lack of permissions.
249          if (isProcessElevated)
250          {
251              clean_video_conference();
252          }
253  
254          // Load PowerToys DLLs
255  
256          std::vector<std::wstring_view> knownModules = {
257              L"PowerToys.FancyZonesModuleInterface.dll",
258              L"PowerToys.powerpreview.dll",
259              L"WinUI3Apps/PowerToys.ImageResizerExt.dll",
260              L"PowerToys.KeyboardManager.dll",
261              L"PowerToys.Launcher.dll",
262              L"WinUI3Apps/PowerToys.PowerRenameExt.dll",
263              L"PowerToys.ShortcutGuideModuleInterface.dll",
264              L"PowerToys.ColorPicker.dll",
265              L"PowerToys.AwakeModuleInterface.dll",
266              L"PowerToys.FindMyMouse.dll",
267              L"PowerToys.MouseHighlighter.dll",
268              L"PowerToys.MouseJump.dll",
269              L"PowerToys.AlwaysOnTopModuleInterface.dll",
270              L"PowerToys.MousePointerCrosshairs.dll",
271              L"PowerToys.CursorWrap.dll",
272              L"PowerToys.PowerAccentModuleInterface.dll",
273              L"PowerToys.PowerOCRModuleInterface.dll",
274              L"PowerToys.AdvancedPasteModuleInterface.dll",
275              L"WinUI3Apps/PowerToys.FileLocksmithExt.dll",
276              L"WinUI3Apps/PowerToys.RegistryPreviewExt.dll",
277              L"WinUI3Apps/PowerToys.MeasureToolModuleInterface.dll",
278              L"WinUI3Apps/PowerToys.NewPlus.ShellExtension.dll",
279              L"WinUI3Apps/PowerToys.HostsModuleInterface.dll",
280              L"WinUI3Apps/PowerToys.Peek.dll",
281              L"WinUI3Apps/PowerToys.EnvironmentVariablesModuleInterface.dll",
282              L"PowerToys.MouseWithoutBordersModuleInterface.dll",
283              L"PowerToys.CropAndLockModuleInterface.dll",
284              L"PowerToys.CmdNotFoundModuleInterface.dll",
285              L"PowerToys.WorkspacesModuleInterface.dll",
286              L"PowerToys.CmdPalModuleInterface.dll",
287              L"PowerToys.ZoomItModuleInterface.dll",
288              L"PowerToys.LightSwitchModuleInterface.dll",
289              L"PowerToys.PowerDisplayModuleInterface.dll",
290          };
291  
292          for (auto moduleSubdir : knownModules)
293          {
294              try
295              {
296                  auto pt_module = load_powertoy(moduleSubdir);
297                  modules().emplace(pt_module->get_key(), std::move(pt_module));
298              }
299              catch (...)
300              {
301                  std::wstring errorMessage = POWER_TOYS_MODULE_LOAD_FAIL;
302                  errorMessage += moduleSubdir;
303                  
304  #ifdef _DEBUG
305                  // In debug mode, simply log the warning and continue execution.
306                  // This contrasts with the past approach where developers had to build all modules
307                  // without errors before debugging—slowing down quick clone-and-fix iterations.
308                  Logger::warn(L"Debug mode: {}", errorMessage);
309  #else
310                  // In release mode, show error dialog as before
311                  MessageBoxW(NULL,
312                              errorMessage.c_str(),
313                              L"PowerToys",
314                              MB_OK | MB_ICONERROR);
315  #endif
316              }
317          }
318          // Start initial powertoys
319          start_enabled_powertoys();
320          std::wstring product_version = get_product_version();
321          Trace::EventLaunch(product_version, isProcessElevated);
322          PTSettingsHelper::save_last_version_run(product_version);
323  
324          if (openSettings)
325          {
326              std::optional<std::wstring> window;
327              if (!settingsWindow.empty())
328              {
329                  window = winrt::to_hstring(settingsWindow);
330              }
331              open_settings_window(window);
332          }
333  
334          if (openOobe)
335          {
336              PTSettingsHelper::save_oobe_opened_state();
337              open_oobe_window();
338          }
339          else if (openScoobe)
340          {
341              open_scoobe_window();
342          }
343  
344          settings_telemetry::init();
345          result = run_message_loop();
346      }
347      catch (std::runtime_error& err)
348      {
349          std::string err_what = err.what();
350          MessageBoxW(nullptr, std::wstring(err_what.begin(), err_what.end()).c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR | MB_SETFOREGROUND);
351          result = -1;
352      }
353      Trace::UnregisterProvider();
354      QuickAccessHost::stop();
355      return result;
356  }
357  
358  // If the PT runner is launched as part of some action and manually by a user, e.g. being activated as a COM server
359  // for background toast notification handling, we should execute corresponding code flow instead of the main code flow.
360  enum class SpecialMode
361  {
362      None,
363      Win32ToastNotificationCOMServer,
364      ToastNotificationHandler,
365      ReportSuccessfulUpdate
366  };
367  
368  SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_list)
369  {
370      for (size_t i = 1; i < n_cmd_args; ++i)
371      {
372          if (!wcscmp(notifications::TOAST_ACTIVATED_LAUNCH_ARG, cmd_arg_list[i]))
373          {
374              return SpecialMode::Win32ToastNotificationCOMServer;
375          }
376          else if (n_cmd_args == 2 && !wcsncmp(PT_URI_PROTOCOL_SCHEME, cmd_arg_list[i], wcslen(PT_URI_PROTOCOL_SCHEME)))
377          {
378              return SpecialMode::ToastNotificationHandler;
379          }
380          else if (n_cmd_args == 2 && !wcscmp(cmdArg::UPDATE_REPORT_SUCCESS, cmd_arg_list[i]))
381          {
382              return SpecialMode::ReportSuccessfulUpdate;
383          }
384      }
385  
386      return SpecialMode::None;
387  }
388  
389  int win32_toast_notification_COM_server_mode()
390  {
391      notifications::run_desktop_app_activator_loop();
392      return 0;
393  }
394  
395  enum class toast_notification_handler_result
396  {
397      exit_success,
398      exit_error
399  };
400  
401  toast_notification_handler_result toast_notification_handler(const std::wstring_view param)
402  {
403      const std::wstring_view cant_drag_elevated_disable = L"cant_drag_elevated_disable/";
404      const std::wstring_view couldnt_toggle_powerpreview_modules_disable = L"couldnt_toggle_powerpreview_modules_disable/";
405      const std::wstring_view open_settings = L"open_settings/";
406      const std::wstring_view open_overview = L"open_overview/";
407      const std::wstring_view update_now = L"update_now/";
408  
409      if (param == cant_drag_elevated_disable)
410      {
411          return notifications::disable_toast(notifications::ElevatedDontShowAgainRegistryPath) ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
412      }
413      else if (param.starts_with(update_now))
414      {
415          std::wstring args{ cmdArg::UPDATE_NOW_LAUNCH_STAGE1 };
416          LaunchPowerToysUpdate(args.c_str());
417          return toast_notification_handler_result::exit_success;
418      }
419      else if (param == couldnt_toggle_powerpreview_modules_disable)
420      {
421          return notifications::disable_toast(notifications::PreviewModulesDontShowAgainRegistryPath) ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
422      }
423      else if (param == open_settings)
424      {
425          open_menu_from_another_instance(std::nullopt);
426          return toast_notification_handler_result::exit_success;
427      }
428      else if (param == open_overview)
429      {
430          open_menu_from_another_instance("Overview");
431          return toast_notification_handler_result::exit_success;
432      }
433      else
434      {
435          return toast_notification_handler_result::exit_error;
436      }
437  }
438  
439  int WINAPI WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR lpCmdLine, int /*nCmdShow*/)
440  {
441      Shared::Trace::ETWTrace trace{};
442      trace.UpdateState(true);
443  
444      Gdiplus::GdiplusStartupInput gpStartupInput;
445      ULONG_PTR gpToken;
446      GdiplusStartup(&gpToken, &gpStartupInput, NULL);
447  
448      winrt::init_apartment();
449  
450      const wchar_t* securityDescriptor =
451          L"O:BA" // Owner: Builtin (local) administrator
452          L"G:BA" // Group: Builtin (local) administrator
453          L"D:"
454          L"(A;;0x7;;;PS)" // Access allowed on COM_RIGHTS_EXECUTE, _LOCAL, & _REMOTE for Personal self
455          L"(A;;0x7;;;IU)" // Access allowed on COM_RIGHTS_EXECUTE for Interactive Users
456          L"(A;;0x3;;;SY)" // Access allowed on COM_RIGHTS_EXECUTE, & _LOCAL for Local system
457          L"(A;;0x7;;;BA)" // Access allowed on COM_RIGHTS_EXECUTE, _LOCAL, & _REMOTE for Builtin (local) administrator
458          L"(A;;0x3;;;S-1-15-3-1310292540-1029022339-4008023048-2190398717-53961996-4257829345-603366646)" // Access allowed on COM_RIGHTS_EXECUTE, & _LOCAL for Win32WebViewHost package capability
459          L"S:"
460          L"(ML;;NX;;;LW)"; // Integrity label on No execute up for Low mandatory level
461      initializeCOMSecurity(securityDescriptor);
462  
463      int n_cmd_args = 0;
464      LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args);
465      switch (should_run_in_special_mode(n_cmd_args, cmd_arg_list))
466      {
467      case SpecialMode::Win32ToastNotificationCOMServer:
468          return win32_toast_notification_COM_server_mode();
469      case SpecialMode::ToastNotificationHandler:
470          switch (toast_notification_handler(cmd_arg_list[1] + wcslen(PT_URI_PROTOCOL_SCHEME)))
471          {
472          case toast_notification_handler_result::exit_error:
473              return 1;
474          case toast_notification_handler_result::exit_success:
475              return 0;
476          }
477          [[fallthrough]];
478      case SpecialMode::ReportSuccessfulUpdate:
479      {
480          notifications::remove_toasts_by_tag(notifications::UPDATING_PROCESS_TOAST_TAG);
481          notifications::remove_all_scheduled_toasts();
482          notifications::show_toast(GET_RESOURCE_STRING(IDS_PT_UPDATE_MESSAGE_BOX_TEXT),
483                                    L"PowerToys",
484                                    notifications::toast_params{ notifications::UPDATING_PROCESS_TOAST_TAG });
485          break;
486      }
487  
488      case SpecialMode::None:
489          // continue as usual
490          break;
491      }
492  
493      std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
494      logFilePath.append(LogSettings::runnerLogPath);
495      Logger::init(LogSettings::runnerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
496  
497      const std::string cmdLine{ lpCmdLine };
498      Logger::info("Running powertoys with cmd args: {}", cmdLine);
499  
500      auto open_settings_it = cmdLine.find("--open-settings");
501      const bool open_settings = open_settings_it != std::string::npos;
502      // Check if opening specific settings window
503      open_settings_it = cmdLine.find("--open-settings=");
504      std::string settings_window;
505      if (open_settings_it != std::string::npos)
506      {
507          std::string rest_of_cmd_line{ cmdLine, open_settings_it + std::string{ "--open-settings=" }.size() };
508          std::istringstream iss(rest_of_cmd_line);
509          iss >> settings_window;
510      }
511  
512      // Check if another instance is already running.
513      wil::unique_mutex_nothrow msi_mutex = create_msi_mutex();
514      if (!msi_mutex)
515      {
516          open_menu_from_another_instance(settings_window);
517          return 0;
518      }
519  
520      bool openOobe = false;
521      try
522      {
523          openOobe = !PTSettingsHelper::get_oobe_opened_state();
524      }
525      catch (const std::exception& e)
526      {
527          Logger::error("Failed to get or save OOBE state with an exception: {}", e.what());
528      }
529  
530      bool openScoobe = false;
531      bool showRestartNotificationAfterUpdate = false;
532      try
533      {
534          std::wstring last_version_run = PTSettingsHelper::get_last_version_run();
535          const auto product_version = get_product_version();
536          openScoobe = product_version != last_version_run;
537          showRestartNotificationAfterUpdate = openScoobe;
538          Logger::info(L"Scoobe: product_version={} last_version_run={}", product_version, last_version_run);
539      }
540      catch (const std::exception& e)
541      {
542          Logger::error("Failed to get last version with an exception: {}", e.what());
543      }
544  
545      int result = 0;
546      try
547      {
548          // Singletons initialization order needs to be preserved, first events and
549          // then modules to guarantee the reverse destruction order.
550          modules();
551  
552          std::thread{ [] {
553              auto state = UpdateState::read();
554              if (state.state == UpdateState::upToDate)
555              {
556                  updating::cleanup_updates();
557              }
558          } }.detach();
559  
560          auto general_settings = load_general_settings();
561  
562          // Apply the general settings but don't save it as the modules() variable has not been loaded yet
563          apply_general_settings(general_settings, false);
564          const bool elevated = is_process_elevated();
565          const bool with_dont_elevate_arg = cmdLine.find("--dont-elevate") != std::string::npos;
566          const bool run_elevated_setting = general_settings.GetNamedBoolean(L"run_elevated", false);
567          const bool with_restartedElevated_arg = cmdLine.find("--restartedElevated") != std::string::npos;
568  
569          // Update scoobe behavior based on setting and gpo
570          bool scoobeSettingDisabled = general_settings.GetNamedBoolean(L"show_whats_new_after_updates", true) == false;
571          bool scoobeDisabledByGpo = powertoys_gpo::getDisableShowWhatsNewAfterUpdatesValue() == powertoys_gpo::gpo_rule_configured_enabled;
572          if (openScoobe && (scoobeSettingDisabled || scoobeDisabledByGpo))
573          {
574              // Scoobe should show after an update, but is disabled by policy or setting
575              Logger::info(L"Scoobe: Showing scoobe after updates is disabled by setting or by GPO.");
576              openScoobe = false;
577          }
578  
579          bool dataDiagnosticsDisabledByGpo = powertoys_gpo::getAllowDataDiagnosticsValue() == powertoys_gpo::gpo_rule_configured_disabled;
580          if (dataDiagnosticsDisabledByGpo)
581          {
582              Logger::info(L"Data diagnostics: Data diagnostics is disabled by GPO.");
583              PTSettingsHelper::save_data_diagnostics(false);
584          }
585  
586          if (elevated && with_dont_elevate_arg && !run_elevated_setting)
587          {
588              Logger::info("Scheduling restart as non elevated");
589              schedule_restart_as_non_elevated();
590              result = 0;
591          }
592          else if (elevated || !run_elevated_setting || with_dont_elevate_arg || (!elevated && with_restartedElevated_arg))
593          {
594              // The condition (!elevated && with_restartedElevated_arg) solves issue #19307. Restart elevated loop detected, running non-elevated
595              if (!elevated && with_restartedElevated_arg)
596              {
597                  Logger::info("Restart as elevated failed. Running non-elevated.");
598              }
599  
600              result = runner(elevated, open_settings, settings_window, openOobe, openScoobe, showRestartNotificationAfterUpdate);
601  
602              if (result == 0)
603              {
604                  // Save settings on closing, if closed 'normal'
605                  PTSettingsHelper::save_general_settings(get_general_settings().to_json());
606              }
607          }
608          else
609          {
610              Logger::info("Scheduling restart as elevated");
611              schedule_restart_as_elevated(open_settings);
612              result = 0;
613          }
614      }
615      catch (std::runtime_error& err)
616      {
617          std::string err_what = err.what();
618          MessageBoxW(nullptr, std::wstring(err_what.begin(), err_what.end()).c_str(), GET_RESOURCE_STRING(IDS_ERROR).c_str(), MB_OK | MB_ICONERROR);
619          result = -1;
620      }
621  
622      trace.Flush();
623      trace.UpdateState(false);
624  
625      // We need to release the mutexes to be able to restart the application
626      if (msi_mutex)
627      {
628          msi_mutex.reset(nullptr);
629      }
630  
631      if (is_restart_scheduled())
632      {
633          modules().clear();
634          if (!restart_if_scheduled())
635          {
636              // If it's not possible to restart non-elevated due to some condition in the user's configuration, user should start PowerToys manually.
637              Logger::warn("Scheduled restart failed. Couldn't restart non-elevated. PowerToys exits here because retrying it would just mean failing in a loop.");
638          }
639      }
640      stop_tray_icon();
641  
642      return result;
643  }