Skip to content

Commit

Permalink
bootstrapper: use WinAPI progress bar window instead of toast notific… (
Browse files Browse the repository at this point in the history
#8210)

* bootstrapper: use WinAPI progress bar window instead of toast notifications

* remove obsolete msi action
  • Loading branch information
yuyoyuppe committed Nov 26, 2020
1 parent 9757a45 commit 9735459
Show file tree
Hide file tree
Showing 13 changed files with 246 additions and 135 deletions.
10 changes: 10 additions & 0 deletions .github/actions/spell-check/expect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Acceleratorkeys
ACCEPTFILES
accessibile
accessibilityinsights
acf
Acl
aclapi
AColumn
Expand Down Expand Up @@ -154,6 +155,7 @@ bc
bcc
bck
Bcl
bdaa
bddac
BEGINLABELEDIT
betadele
Expand Down Expand Up @@ -955,6 +957,7 @@ IBackground
IBeam
IBind
icase
iccex
IClass
ico
ICollection
Expand Down Expand Up @@ -1045,6 +1048,7 @@ Infotip
ingbuffer
inheritdoc
ini
INITCOMMONCONTROLSEX
INITDIALOG
INITGUID
inl
Expand Down Expand Up @@ -1328,6 +1332,7 @@ makeappx
MAKEINTRESOURCE
MAKEINTRESOURCEW
MAKELANGID
MAKELPARAM
makepri
malloc
manifestdependency
Expand Down Expand Up @@ -1424,6 +1429,7 @@ MSIRESTARTMANAGERCONTROL
msix
msixbundle
MSIXVERSION
MSGBOX
MSLLHOOKSTRUCT
Mso
msp
Expand All @@ -1450,6 +1456,7 @@ NAMECHANGE
nameof
NAMEONLY
namespace
NATIVEFNTCTL
nc
NCACTIVATE
ncc
Expand Down Expand Up @@ -1979,8 +1986,10 @@ SETICON
setings
setlocal
setnt
SETRANGE
Setrect
SETREDRAW
SETSTEP
SETTEXT
SETTINGCHANGE
settingsheader
Expand Down Expand Up @@ -2130,6 +2139,7 @@ STDMETHODIMP
stdout
Stdout
stefan
STEPIT
stgm
STGMEDIUM
stoi
Expand Down
18 changes: 6 additions & 12 deletions installer/PowerToysBootstrapper/bootstrapper/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BOOTSTRAPPER_PROGRESS_TITLE" xml:space="preserve">
<value>PowerToys installer</value>
</data>
<data name="DOTNET_CORE_DOWNLOAD_FAILURE" xml:space="preserve">
<value>Couldn't download .NET Core Desktop Runtime 3.1, please install it manually.</value>
</data>
Expand Down Expand Up @@ -130,24 +133,15 @@
<data name="EXTRACTING_INSTALLER" xml:space="preserve">
<value>Extracting PowerToys MSI...</value>
</data>
<data name="UNINSTALLING_PREVIOUS_VERSION" xml:space="preserve">
<value>Uninstalling previous PowerToys version...</value>
</data>
<data name="UNINSTALL_PREVIOUS_VERSION_ERROR" xml:space="preserve">
<value>Couldn't uninstall previous PowerToys version!</value>
</data>
<data name="INSTALLING_DOTNET" xml:space="preserve">
<value>Installing dotnet...</value>
<data name="DOWNLOADING_DOTNET" xml:space="preserve">
<value>Downloading .NET Core Runtime...</value>
</data>
<data name="DOTNET_INSTALL_ERROR" xml:space="preserve">
<value>Couldn't install dotnet!</value>
</data>
<data name="INSTALLING_NEW_VERSION" xml:space="preserve">
<value>Installing new PowerToys version...</value>
</data>
<data name="NEW_VERSION_INSTALLATION_DONE" xml:space="preserve">
<value>PowerToys installation complete!</value>
</data>
<data name="NEW_VERSION_INSTALLATION_ERROR" xml:space="preserve">
<value>Couldn't install new PowerToys version.</value>
</data>
Expand All @@ -160,5 +154,5 @@
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</data>
</root>
136 changes: 49 additions & 87 deletions installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include "Generated Files/resource.h"

#include <common/common.h>
#include <common/notifications.h>
#include <common/RcResource.h>
#include <common/updating/dotnet_installation.h>
#include <common/updating/installer.h>
Expand All @@ -12,6 +11,8 @@

#include <runner/action_runner_utils.h>

#include "progressbar_window.h"

extern "C" IMAGE_DOS_HEADER __ImageBase;

auto Strings = create_notifications_strings();
Expand All @@ -22,7 +23,7 @@ auto Strings = create_notifications_strings();
namespace // Strings in this namespace should not be localized
{
const wchar_t APPLICATION_ID[] = L"PowerToysInstaller";
const wchar_t INSTALLATION_TOAST_TITLE[] = L"PowerToys Installation";
const wchar_t INSTALLATION_MSGBOX_TITLE[] = L"PowerToys Installation";
const wchar_t TOAST_TAG[] = L"PowerToysInstallerProgress";
const char LOG_FILENAME[] = "powertoys-bootstrapper-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
const char MSI_LOG_FILENAME[] = "powertoys-bootstrapper-msi-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
Expand All @@ -44,17 +45,6 @@ std::optional<fs::path> extractEmbeddedInstaller()
return executableRes->saveAsFile(installerPath) ? std::make_optional(std::move(installerPath)) : std::nullopt;
}

std::optional<fs::path> extractIcon()
{
auto iconRes = RcResource::create(IDR_BIN_ICON, L"BIN");
if (!iconRes)
{
return std::nullopt;
}
auto icoPath = fs::temp_directory_path() / L"PowerToysBootstrappedInstaller.ico";
return iconRes->saveAsFile(icoPath) ? std::make_optional(std::move(icoPath)) : std::nullopt;
}

void setup_log(fs::path directory, const spdlog::level::level_enum severity)
{
try
Expand Down Expand Up @@ -84,7 +74,15 @@ void setup_log(fs::path directory, const spdlog::level::level_enum severity)
}
}

int bootstrapper()
void show_error_box(const wchar_t* message, const wchar_t* title)
{
MessageBoxW(nullptr,
message,
title,
MB_OK | MB_ICONERROR);
}

int bootstrapper(HINSTANCE hInstance)
{
winrt::init_apartment();
cxxopts::Options options{ "PowerToysBootstrapper" };
Expand Down Expand Up @@ -233,19 +231,6 @@ int bootstrapper()
spdlog::error("Couldn't acquire PowerToys global mutex. That means setup couldn't kill PowerToys.exe process");
return 1;
}
notifications::override_application_id(APPLICATION_ID);
spdlog::debug("Extracting icon for toast notifications");
fs::path iconPath{ L"C:\\" };
if (auto extractedIcon = extractIcon())
{
iconPath = std::move(*extractedIcon);
}
spdlog::debug("Registering app id for toast notifications");
notifications::register_application_id(INSTALLATION_TOAST_TITLE, iconPath.c_str());

auto removeShortcut = wil::scope_exit([&] {
notifications::unregister_application_id();
});

// Check if there's a newer version installed, and launch its installer if so.
const VersionHelper myVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
Expand All @@ -260,58 +245,13 @@ int bootstrapper()
}
}

std::mutex progressLock;
notifications::progress_bar_params progressParams;
progressParams.progress = 0.0f;
progressParams.progress_title = GET_RESOURCE_STRING(IDS_EXTRACTING_INSTALLER);
notifications::toast_params params{ TOAST_TAG, false, std::move(progressParams) };
if (!silent)
{
spdlog::debug("Launching progress toast notification");
notifications::show_toast_with_activations({}, INSTALLATION_TOAST_TITLE, {}, {}, std::move(params));
}

auto processToasts = wil::scope_exit([&] {
spdlog::debug("Processing HWND messages for 2s so toast have time to show up");
run_message_loop(true, 2);
});

if (!silent)
{
// Worker thread to periodically increase progress and keep the progress toast from losing focus
std::thread{ [&] {
spdlog::debug("Started worker thread for progress bar update");
for (;; Sleep(3000))
{
std::scoped_lock lock{ progressLock };
if (progressParams.progress == 1.f)
{
break;
}
progressParams.progress = std::min(0.99f, progressParams.progress + 0.001f);
notifications::update_toast_progress_bar(TOAST_TAG, progressParams);
}
} }.detach();
}

auto updateProgressBar = [&](const float value, const wchar_t* title) {
if (silent)
{
return;
}
std::scoped_lock lock{ progressLock };
progressParams.progress = value;
progressParams.progress_title = title;
notifications::update_toast_progress_bar(TOAST_TAG, progressParams);
};

spdlog::debug("Extracting embedded MSI installer");
const auto installerPath = extractEmbeddedInstaller();
if (!installerPath)
{
if (!silent)
{
notifications::show_toast(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR), INSTALLATION_TOAST_TITLE);
show_error_box(GET_RESOURCE_STRING(IDS_INSTALLER_EXTRACT_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}
spdlog::error("Couldn't install the MSI installer ({})", GetLastError());
return 1;
Expand All @@ -321,7 +261,6 @@ int bootstrapper()
fs::remove(*installerPath, _);
});

updateProgressBar(.25f, GET_RESOURCE_STRING(IDS_UNINSTALLING_PREVIOUS_VERSION).c_str());
spdlog::debug("Acquiring existing MSI package path");
const auto package_path = updating::get_msi_package_path();
if (!package_path.empty())
Expand All @@ -332,15 +271,18 @@ int bootstrapper()
{
spdlog::debug("Existing MSI package path not found");
}
if (!package_path.empty() && !updating::uninstall_msi_version(package_path, Strings) && !silent)
if (!package_path.empty() && !updating::uninstall_msi_version(package_path, Strings))
{
spdlog::error("Couldn't install the existing MSI package ({})", GetLastError());
notifications::show_toast(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR), INSTALLATION_TOAST_TITLE);
if (!silent)
{
show_error_box(GET_RESOURCE_STRING(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}
}
const bool installDotnet = !skipDotnetInstall;
if (installDotnet)
if (!silent)
{
updateProgressBar(.5f, GET_RESOURCE_STRING(IDS_INSTALLING_DOTNET).c_str());
open_progressbar_window(hInstance, 0, GET_RESOURCE_STRING(IDS_BOOTSTRAPPER_PROGRESS_TITLE).c_str(), GET_RESOURCE_STRING(IDS_DOWNLOADING_DOTNET).c_str());
}

try
Expand All @@ -350,11 +292,31 @@ int bootstrapper()
spdlog::debug("Detecting if dotnet is installed");
const bool dotnetInstalled = updating::dotnet_is_installed();
spdlog::debug("Dotnet is installed: {}", dotnetInstalled);
if (!dotnetInstalled &&
!updating::install_dotnet(silent) &&
!silent)
if (!dotnetInstalled)
{
notifications::show_toast(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR), INSTALLATION_TOAST_TITLE);
bool installed_successfully = 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)
{
spdlog::error("Couldn't install dotnet");
}
}
else
{
spdlog::error("Couldn't download dotnet");
}

if (!installed_successfully)
{
if (!silent)
{
show_error_box(GET_RESOURCE_STRING(IDS_DOTNET_INSTALL_ERROR).c_str(), INSTALLATION_MSGBOX_TITLE);
}
}
}
}
}
Expand All @@ -364,19 +326,19 @@ int bootstrapper()
MessageBoxW(nullptr, L".NET Core installation", L"Unknown exception encountered!", MB_OK | MB_ICONERROR);
}

updateProgressBar(.75f, GET_RESOURCE_STRING(IDS_INSTALLING_NEW_VERSION).c_str());
// At this point, there's no reason to show progress bar window, since MSI installers have their own
close_progressbar_window();

// Always skip dotnet install, because we should've installed it from here earlier
std::wstring msiProps = L"SKIPDOTNETINSTALL=1 ";
spdlog::debug("Launching MSI installation for new package {}", installerPath->string());
const bool installationDone = MsiInstallProductW(installerPath->c_str(), msiProps.c_str()) == ERROR_SUCCESS;
updateProgressBar(1.f,
installationDone ? GET_RESOURCE_STRING(IDS_NEW_VERSION_INSTALLATION_DONE).c_str() : GET_RESOURCE_STRING(IDS_NEW_VERSION_INSTALLATION_ERROR).c_str());
if (!installationDone)
{
spdlog::error("Couldn't install new MSI package ({})", GetLastError());
return 1;
}

spdlog::debug("Installation completed");

if (!noStartPT && !silent)
Expand All @@ -399,11 +361,11 @@ int bootstrapper()
return 0;
}

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
int WINAPI WinMain(HINSTANCE hi, HINSTANCE, LPSTR, int)
{
try
{
return bootstrapper();
return bootstrapper(hi);
}
catch (const std::exception& ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>WindowsApp.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>WindowsApp.lib;Msi.lib;Shlwapi.lib;Comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
Expand All @@ -110,7 +110,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>WindowsApp.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>WindowsApp.lib;Msi.lib;Shlwapi.lib;Comctl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
Expand All @@ -119,10 +119,12 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="progressbar_window.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\runner\updating.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="progressbar_window.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down

0 comments on commit 9735459

Please sign in to comment.