diff --git a/installer/PowerToysBootstrapper/bootstrapper/RcResource.cpp b/installer/PowerToysBootstrapper/bootstrapper/RcResource.cpp index b4793968ff..d4214446e5 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/RcResource.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/RcResource.cpp @@ -10,21 +10,25 @@ std::optional RcResource::create(int resource_id, const std::wstring { return std::nullopt; } + const HGLOBAL memHandle = LoadResource(nullptr, resHandle); if (!memHandle) { return std::nullopt; } + const size_t resSize = SizeofResource(nullptr, resHandle); if (!resSize) { return std::nullopt; } + auto res = static_cast(LockResource(memHandle)); if (!res) { return std::nullopt; } + return RcResource{ res, resSize }; } @@ -35,6 +39,7 @@ bool RcResource::saveAsFile(const std::filesystem::path destination) { return false; } + installerFile.write(reinterpret_cast(_memory), _size); return true; } diff --git a/installer/PowerToysBootstrapper/bootstrapper/Resources.resx b/installer/PowerToysBootstrapper/bootstrapper/Resources.resx index 7d5dc48c2a..b7d9947e39 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/Resources.resx +++ b/installer/PowerToysBootstrapper/bootstrapper/Resources.resx @@ -59,7 +59,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - PowerToys installer + PowerToys Installer Couldn't download .NET Core Desktop Runtime 3.1, please install it manually. diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp index 7f118100f3..ef88a33536 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp @@ -36,18 +36,19 @@ namespace // Strings in this namespace should not be localized namespace fs = std::filesystem; -std::optional extractEmbeddedInstaller(const fs::path extractPath) +std::optional ExtractEmbeddedInstaller(const fs::path extractPath) { auto executableRes = RcResource::create(IDR_BIN_MSIINSTALLER, L"BIN"); if (!executableRes) { return std::nullopt; } + auto installerPath = extractPath / L"PowerToysBootstrappedInstaller-" PRODUCT_VERSION_STRING L".msi"; return executableRes->saveAsFile(installerPath) ? std::make_optional(std::move(installerPath)) : std::nullopt; } -void setup_log(fs::path directory, const spdlog::level::level_enum severity) +void SetupLogger(fs::path directory, const spdlog::level::level_enum severity) { try { @@ -65,6 +66,7 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity) { logger = spdlog::null_logger_mt("null"); } + logger->set_pattern("[%L][%d-%m-%C-%T] %v"); logger->set_level(severity); spdlog::set_default_logger(std::move(logger)); @@ -76,7 +78,7 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity) } } -void show_error_box(const wchar_t* message, const wchar_t* title) +void ShowMessageBoxError(const wchar_t* message, const wchar_t* title) { MessageBoxW(nullptr, message, @@ -84,18 +86,21 @@ void show_error_box(const wchar_t* message, const wchar_t* title) MB_OK | MB_ICONERROR); } -int bootstrapper(HINSTANCE hInstance) +int Bootstrapper(HINSTANCE hInstance) { winrt::init_apartment(); char* programFilesDir = nullptr; size_t size = 0; std::string defaultInstallDir; + if (!_dupenv_s(&programFilesDir, &size, "PROGRAMFILES")) { defaultInstallDir += programFilesDir; defaultInstallDir += "\\PowerToys"; } + cxxopts::Options options{ "PowerToysBootstrapper" }; + // clang-format off options.add_options() ("h,help", "Show help") @@ -108,6 +113,7 @@ int bootstrapper(HINSTANCE hInstance) ("install_dir", "Installation directory", cxxopts::value()->default_value(defaultInstallDir)) ("extract_msi", "Extract MSI to the working directory and exit. Use only if you must access MSI directly."); // clang-format on + cxxopts::ParseResult cmdArgs; bool showHelp = false; try @@ -178,13 +184,14 @@ int bootstrapper(HINSTANCE hInstance) { severity = spdlog::level::err; } - setup_log(logDir, severity); - spdlog::debug("PowerToys Bootstrapper is launched!\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}\ninstall_dir: {}\nextract_msi: {}\n", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel, installDirArg, extract_msi_only); + + SetupLogger(logDir, severity); + spdlog::debug("PowerToys Bootstrapper is launched\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}\ninstall_dir: {}\nextract_msi: {}\n", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel, installDirArg, extract_msi_only); // If a user requested an MSI -> extract it and exit if (extract_msi_only) { - if (const auto installerPath = extractEmbeddedInstaller(fs::current_path())) + if (const auto installerPath = ExtractEmbeddedInstaller(fs::current_path())) { spdlog::info("MSI installer was extracted to {}", installerPath->string()); } @@ -200,6 +207,7 @@ int bootstrapper(HINSTANCE hInstance) { MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr); } + if (silent) { if (is_process_elevated()) @@ -232,12 +240,14 @@ int bootstrapper(HINSTANCE hInstance) params += L' '; } } + const auto processHandle = run_elevated(argList[0], params.c_str()); if (!processHandle) { spdlog::error("Couldn't restart elevated to enable silent mode! ({})", GetLastError()); return 1; } + if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0) { DWORD exitCode = 0; @@ -259,6 +269,7 @@ int bootstrapper(HINSTANCE hInstance) { TerminateProcess(handle.get(), 0); } + auto powerToysMutex = createAppMutex(POWERTOYS_MSI_MUTEX_NAME); auto instanceMutex = createAppMutex(POWERTOYS_BOOTSTRAPPER_MUTEX_NAME); if (!instanceMutex) @@ -281,16 +292,18 @@ int bootstrapper(HINSTANCE hInstance) } spdlog::debug("Extracting embedded MSI installer"); - const auto installerPath = extractEmbeddedInstaller(fs::temp_directory_path()); + const auto installerPath = ExtractEmbeddedInstaller(fs::temp_directory_path()); if (!installerPath) { if (!silent) { - show_error_box(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); + ShowMessageBoxError(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); } + spdlog::error("Couldn't install the MSI installer ({})", GetLastError()); return 1; } + auto removeExtractedInstaller = wil::scope_exit([&] { std::error_code _; fs::remove(*installerPath, _); @@ -306,18 +319,20 @@ int bootstrapper(HINSTANCE hInstance) { spdlog::debug("Existing MSI package path not found"); } + if (!package_path.empty() && !updating::uninstall_msi_version(package_path, Strings)) { spdlog::error("Couldn't install the existing MSI package ({})", GetLastError()); if (!silent) { - show_error_box(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); + ShowMessageBoxError(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); } } + const bool installDotnet = !skipDotnetInstall; if (!silent) { - open_progressbar_window(hInstance, 0, GET_RESOURCE_STRING(IDS_BOOTSTRAPPER_PROGRESS_TITLE).c_str(), GET_RESOURCE_STRING(IDS_DOWNLOADING_DOTNET).c_str()); + OpenProgressBarDialog(hInstance, 0, GET_RESOURCE_STRING(IDS_BOOTSTRAPPER_PROGRESS_TITLE).c_str(), GET_RESOURCE_STRING(IDS_DOWNLOADING_DOTNET).c_str()); } try @@ -329,13 +344,13 @@ int bootstrapper(HINSTANCE hInstance) spdlog::debug("Dotnet is already installed: {}", dotnetInstalled); if (!dotnetInstalled) { - bool installed_successfully = false; + bool installedSuccessfully = false; if (const auto dotnet_installer_path = updating::download_dotnet()) { // Dotnet installer has its own progress bar - close_progressbar_window(); - installed_successfully = updating::install_dotnet(*dotnet_installer_path, silent); - if (!installed_successfully) + CloseProgressBarDialog(); + installedSuccessfully = updating::install_dotnet(*dotnet_installer_path, silent); + if (!installedSuccessfully) { spdlog::error("Couldn't install dotnet"); } @@ -345,11 +360,11 @@ int bootstrapper(HINSTANCE hInstance) spdlog::error("Couldn't download dotnet"); } - if (!installed_successfully) + if (!installedSuccessfully) { if (!silent) { - show_error_box(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); + ShowMessageBoxError(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE); } } } @@ -362,7 +377,7 @@ int bootstrapper(HINSTANCE hInstance) } // At this point, there's no reason to show progress bar window, since MSI installers have their own - close_progressbar_window(); + CloseProgressBarDialog(); const std::wstring msiProps = installFolderProp; spdlog::debug("Launching MSI installation for new package {}", installerPath->string()); @@ -384,6 +399,7 @@ int bootstrapper(HINSTANCE hInstance) spdlog::error("Couldn't determine new MSI package install location ({})", GetLastError()); return 1; } + *newPTPath += L"\\PowerToys.exe"; SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE }; @@ -399,7 +415,7 @@ int WINAPI WinMain(HINSTANCE hi, HINSTANCE, LPSTR, int) { try { - return bootstrapper(hi); + return Bootstrapper(hi); } catch (const std::exception& ex) { diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj index ceeb93ec0e..1da5c3c2b5 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj +++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj @@ -119,7 +119,6 @@ - diff --git a/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.cpp b/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.cpp index 6b44e2fdb0..1270da086a 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.cpp @@ -6,28 +6,29 @@ #include "progressbar_window.h" #include "Generated Files/resource.h" -const int label_height = 20; +const int labelHeight = 18; -const int progress_bar_height = 15; -const int progress_bar_margin = 10; +const int progressBarHeight = 20; +const int margin = 10; -const int window_width = 450; -const int title_bar_height = 32; -const int window_height = progress_bar_margin * 3 + progress_bar_height + label_height + title_bar_height; +const int windowWidth = 480; +const int titleBarHeight = 32; +const int windowHeight = margin * 4 + progressBarHeight + labelHeight + titleBarHeight; -int progressbar_steps = 0; +int progressBarSteps = 0; -HWND progress_bar; -HWND main_window; -HWND label; +HWND hDialog = nullptr; +HWND hLabel = nullptr; +HWND hProgressBar = nullptr; +HBRUSH hBrush = nullptr; -std::wstring initial_label; -std::mutex ui_thread_is_running; +std::wstring labelText; +std::mutex uiThreadIsRunning; namespace nonlocalized { - const wchar_t window_class[] = L"PTBProgressBarWnd"; - const wchar_t label_class[] = L"static"; + const wchar_t windowClass[] = L"PTBProgressBarWnd"; + const wchar_t labelClass[] = L"static"; } #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") @@ -38,55 +39,87 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { case WM_CREATE: { - ui_thread_is_running.lock(); - label = CreateWindowW(nonlocalized::label_class, initial_label.c_str(), WS_CHILD | WS_VISIBLE | WS_TABSTOP, progress_bar_margin, 0, window_width - progress_bar_margin * 4, label_height, hWnd, (HMENU)(501), (HINSTANCE)GetWindowLongPtrW(hWnd, GWLP_HINSTANCE), nullptr); + uiThreadIsRunning.lock(); - progress_bar = CreateWindowExW(0, - PROGRESS_CLASS, - nullptr, - WS_VISIBLE | WS_CHILD | PBS_SMOOTH, - progress_bar_margin, - progress_bar_margin + label_height, - window_width - progress_bar_margin * 4, - progress_bar_height, - hWnd, - (HMENU)(IDR_PROGRESS_BAR), - (HINSTANCE)GetWindowLongPtrW(hWnd, GWLP_HINSTANCE), - nullptr); + hLabel = CreateWindowW(nonlocalized::labelClass, + labelText.c_str(), + WS_CHILD | WS_VISIBLE | WS_TABSTOP, + margin, + margin, + windowWidth - (margin * 4), + labelHeight, + hWnd, + (HMENU)(501), + (HINSTANCE)GetWindowLongPtrW(hWnd, GWLP_HINSTANCE), nullptr); - bool filled_on_start = false; - if (progressbar_steps == 0) + hProgressBar = CreateWindowExW(0, + PROGRESS_CLASS, + nullptr, + WS_VISIBLE | WS_CHILD | PBS_SMOOTH, + margin, + (margin * 2) + labelHeight, + windowWidth - (margin * 4), + progressBarHeight, + hWnd, + (HMENU)(IDR_PROGRESS_BAR), + (HINSTANCE)GetWindowLongPtrW(hWnd, GWLP_HINSTANCE), + nullptr); + + bool filledOnStart = false; + if (progressBarSteps == 0) { - progressbar_steps = 1; - filled_on_start = true; + progressBarSteps = 1; + filledOnStart = true; } - SendMessageW(progress_bar, PBM_SETRANGE, 0, MAKELPARAM(0, progressbar_steps)); - SendMessageW(progress_bar, PBM_SETSTEP, 1, 0); - if (filled_on_start) + + SendMessageW(hProgressBar, PBM_SETRANGE, 0, MAKELPARAM(0, progressBarSteps)); + SendMessageW(hProgressBar, PBM_SETSTEP, 1, 0); + + if (filledOnStart) { - SendMessageW(progress_bar, PBM_STEPIT, 0, 0); + SendMessageW(hProgressBar, PBM_STEPIT, 0, 0); } break; } + case WM_CTLCOLORSTATIC: + { + if (lParam == (LPARAM)hLabel) + { + if (!hBrush) + { + HDC hdcStatic = (HDC)wParam; + SetTextColor(hdcStatic, RGB(0, 0, 0)); + SetBkColor(hdcStatic, RGB(255, 255, 255)); + hBrush = CreateSolidBrush(RGB(255, 255, 255)); + } + + return (LRESULT)hBrush; + } + break; + } case WM_CLOSE: + { DestroyWindow(hWnd); PostQuitMessage(0); break; + } default: + { return DefWindowProcW(hWnd, Msg, wParam, lParam); } + } return 0; } -void open_progressbar_window(HINSTANCE hInstance, const int n_progressbar_steps, const wchar_t* title, const wchar_t* init_label) +void OpenProgressBarDialog(HINSTANCE hInstance, const int nProgressbarSteps, const wchar_t* title, const wchar_t* label) { - initial_label = init_label; - progressbar_steps = n_progressbar_steps; + labelText = label; + progressBarSteps = nProgressbarSteps; std::wstring window_title{ title }; std::thread{ [hInstance, window_title = std::move(window_title)] { - INITCOMMONCONTROLSEX iccex{ .dwSize = sizeof(iccex), .dwICC = ICC_NATIVEFNTCTL_CLASS | ICC_PROGRESS_CLASS }; + INITCOMMONCONTROLSEX iccex{.dwSize = sizeof(iccex), .dwICC = ICC_NATIVEFNTCTL_CLASS | ICC_PROGRESS_CLASS }; InitCommonControlsEx(&iccex); WNDCLASSEX wc{}; @@ -95,54 +128,58 @@ void open_progressbar_window(HINSTANCE hInstance, const int n_progressbar_steps, wc.hInstance = hInstance; wc.hIcon = LoadIconW(hInstance, MAKEINTRESOURCE(IDR_BIN_ICON)); wc.hIconSm = LoadIconW(hInstance, MAKEINTRESOURCE(IDR_BIN_ICON)); - wc.lpszClassName = nonlocalized::window_class; + wc.lpszClassName = nonlocalized::windowClass; + if (!RegisterClassExW(&wc)) { spdlog::warn("Couldn't register main_window class for progress bar."); return; } + RECT rect{}; GetClientRect(GetDesktopWindow(), &rect); - rect.left = rect.right / 2 - window_width / 2; - rect.top = rect.bottom / 4 - window_height / 2; - main_window = CreateWindowExW(WS_EX_CLIENTEDGE, - nonlocalized::window_class, - window_title.c_str(), - WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX, - rect.left, - rect.top, - window_width, - window_height, - nullptr, - nullptr, - hInstance, - nullptr); + rect.left = rect.right / 2 - windowWidth / 2; + rect.top = rect.bottom / 4 - windowHeight / 2; + hDialog = CreateWindowExW(WS_EX_CLIENTEDGE, + nonlocalized::windowClass, + window_title.c_str(), + WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX, + rect.left, + rect.top, + windowWidth, + windowHeight, + nullptr, + nullptr, + hInstance, + nullptr); - if (!main_window) + if (!hDialog) { spdlog::warn("Couldn't create progress bar main_window"); return; } - ShowWindow(main_window, SW_SHOW); - UpdateWindow(main_window); + + ShowWindow(hDialog, SW_SHOW); + UpdateWindow(hDialog); run_message_loop(); - ui_thread_is_running.unlock(); + uiThreadIsRunning.unlock(); } }.detach(); } -void tick_progressbar_window(const wchar_t* new_status) +void UpdateProgressBarDialog(const wchar_t* label) { - SetWindowTextW(label, new_status); - SendMessageW(progress_bar, PBM_STEPIT, 0, 0); + SetWindowTextW(hLabel, label); + SendMessageW(hProgressBar, PBM_STEPIT, 0, 0); } -void close_progressbar_window() +void CloseProgressBarDialog() { - SendMessageW(main_window, WM_CLOSE, {}, {}); + SendMessageW(hDialog, WM_CLOSE, {}, {}); { - std::unique_lock wait_for_ui_to_exit{ui_thread_is_running}; + std::unique_lock waitForUIToExit{ uiThreadIsRunning }; } + // Return focus to the current process, since it was lost due to progress bar closing (?) INPUT i = {INPUT_MOUSE, {}}; SendInput(1, &i, sizeof(i)); diff --git a/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.h b/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.h index 6b24aa2b21..22aa90e358 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.h +++ b/installer/PowerToysBootstrapper/bootstrapper/progressbar_window.h @@ -4,6 +4,6 @@ #define WIN32_LEAN_AND_MEAN #include -void open_progressbar_window(HINSTANCE hInstance, const int n_progressbar_steps, const wchar_t* title, const wchar_t* init_label); -void tick_progressbar_window(const wchar_t* new_status); -void close_progressbar_window(); \ No newline at end of file +void OpenProgressBarDialog(HINSTANCE hInstance, const int nProgressbarSteps, const wchar_t* title, const wchar_t* label); +void UpdateProgressBarDialog(const wchar_t* label); +void CloseProgressBarDialog();