Autoupdate: implement updating bootstrapper utility (#5204)

This commit is contained in:
Andrey Nekrasov
2020-07-27 19:53:29 +03:00
committed by GitHub
parent 5a48376a77
commit 3796a5ef97
34 changed files with 1016 additions and 296 deletions

View File

@@ -0,0 +1,64 @@
#include "pch.h"
#include <common/common.h>
#include "http_client.h"
#include "dotnet_installation.h"
namespace fs = std::filesystem;
namespace updating
{
bool dotnet_is_installed()
{
auto runtimes = exec_and_read_output(LR"(dotnet --list-runtimes)");
if (!runtimes)
{
return false;
}
const char DESKTOP_DOTNET_RUNTIME_STRING[] = "Microsoft.WindowsDesktop.App 3.1.";
return runtimes->find(DESKTOP_DOTNET_RUNTIME_STRING) != std::string::npos;
}
bool install_dotnet()
{
const wchar_t DOTNET_DESKTOP_DOWNLOAD_LINK[] = L"https://download.visualstudio.microsoft.com/download/pr/3eb7efa1-96c6-4e97-bb9f-563ecf595f8a/7efd9c1cdd74df8fb0a34c288138a84f/windowsdesktop-runtime-3.1.6-win-x64.exe";
const wchar_t DOTNET_DESKTOP_FILENAME[] = L"windowsdesktop-runtime.exe";
auto dotnet_download_path = fs::temp_directory_path() / DOTNET_DESKTOP_FILENAME;
winrt::Windows::Foundation::Uri download_link{ DOTNET_DESKTOP_DOWNLOAD_LINK };
const size_t max_attempts = 3;
bool download_success = false;
for (size_t i = 0; i < max_attempts; ++i)
{
try
{
http::HttpClient client;
client.download(download_link, dotnet_download_path).wait();
download_success = true;
break;
}
catch (...)
{
// couldn't download
}
}
if (!download_success)
{
return false;
}
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE };
sei.lpFile = dotnet_download_path.c_str();
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = L"/install /passive";
if (ShellExecuteExW(&sei) != TRUE)
{
return false;
}
WaitForSingleObject(sei.hProcess, INFINITE);
CloseHandle(sei.hProcess);
return true;
}
}

View File

@@ -0,0 +1,7 @@
#pragma once
namespace updating
{
bool dotnet_is_installed();
bool install_dotnet();
}

View File

@@ -13,5 +13,7 @@
#include <Shobjidl.h>
#include <Knownfolders.h>
#include <ShlObj_core.h>
#include <shellapi.h>
#include <filesystem>
#endif //PCH_H

View File

@@ -13,6 +13,7 @@ namespace
{
const wchar_t UPDATE_NOTIFY_TOAST_TAG[] = L"PTUpdateNotifyTag";
const wchar_t UPDATE_READY_TOAST_TAG[] = L"PTUpdateReadyTag";
const wchar_t TOAST_TITLE[] = L"PowerToys Update";
}
namespace localized_strings
@@ -23,7 +24,7 @@ namespace localized_strings
const wchar_t GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR[] = L"Error: couldn't download PowerToys installer. Visit our GitHub page to update.\n";
const wchar_t GITHUB_NEW_VERSION_UPDATE_NOW[] = L"Update now";
const wchar_t GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART[] = L"At next launch";
const wchar_t UNINSTALLATION_SUCCESS[] = L"Previous version of PowerToys was uninstalled successfully.";
const wchar_t UNINSTALLATION_UNKNOWN_ERROR[] = L"Error: please uninstall the previous version of PowerToys manually.";
@@ -35,6 +36,9 @@ namespace localized_strings
const wchar_t GITHUB_NEW_VERSION_SNOOZE_TITLE[] = L"Click Snooze to be reminded in:";
const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D[] = L"1 day";
const wchar_t GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D[] = L"5 days";
const wchar_t DOWNLOAD_IN_PROGRESS[] = L"Downloading...";
const wchar_t DOWNLOAD_COMPLETE[] = L"Download complete";
}
namespace updating
@@ -55,7 +59,7 @@ namespace updating
{
::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false };
std::wstring contents = GITHUB_NEW_VERSION_UNAVAILABLE;
::notifications::show_toast(std::move(contents), std::move(toast_params));
::notifications::show_toast(std::move(contents), TOAST_TITLE, std::move(toast_params));
}
void show_available(const updating::new_version_download_info& info)
@@ -64,18 +68,28 @@ namespace updating
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE;
contents += current_version_to_next_version(info);
::notifications::show_toast_with_activations(std::move(contents), {},
{
::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://download_and_install_update/" },
::notifications::link_button{ GITHUB_NEW_VERSION_MORE_INFO, info.release_page_uri.ToString().c_str() }
},
std::move(toast_params));
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://download_and_install_update/" }, ::notifications::link_button{ GITHUB_NEW_VERSION_MORE_INFO, info.release_page_uri.ToString().c_str() } },
std::move(toast_params));
}
void show_download_start(const updating::new_version_download_info& info)
{
::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false, 0.0f, info.version_string };
::notifications::show_toast_with_activations(localized_strings::GITHUB_NEW_VERSION_DOWNLOAD_STARTED, {}, {}, std::move(toast_params));
::notifications::progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version_string };
progress_title += L' ';
progress_title += localized_strings::DOWNLOAD_IN_PROGRESS;
progress_bar_params.progress_title = progress_title;
progress_bar_params.progress = .0f;
::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false, std::move(progress_bar_params) };
::notifications::show_toast_with_activations(localized_strings::GITHUB_NEW_VERSION_DOWNLOAD_STARTED,
TOAST_TITLE,
{},
{},
std::move(toast_params));
}
void show_visit_github(const updating::new_version_download_info& info)
@@ -83,7 +97,11 @@ namespace updating
::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false };
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
contents += current_version_to_next_version(info);
::notifications::show_toast_with_activations(std::move(contents), {}, { ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } }, std::move(toast_params));
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } },
std::move(toast_params));
}
void show_install_error(const updating::new_version_download_info& info)
@@ -91,7 +109,11 @@ namespace updating
::notifications::toast_params toast_params{ UPDATE_NOTIFY_TOAST_TAG, false };
std::wstring contents = GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR;
contents += current_version_to_next_version(info);
::notifications::show_toast_with_activations(std::move(contents), {}, { ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } }, std::move(toast_params));
::notifications::show_toast_with_activations(std::move(contents),
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_VISIT, info.release_page_uri.ToString().c_str() } },
std::move(toast_params));
}
void show_version_ready(const updating::new_version_download_info& info)
@@ -101,27 +123,34 @@ namespace updating
new_version_ready += current_version_to_next_version(info);
::notifications::show_toast_with_activations(std::move(new_version_ready),
{},
TOAST_TITLE,
{},
{ ::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_NOW, L"powertoys://update_now/" + info.installer_filename },
::notifications::link_button{ GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART, L"powertoys://schedule_update/" + info.installer_filename },
::notifications::snooze_button{ GITHUB_NEW_VERSION_SNOOZE_TITLE, { { GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 }, { GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } } } },
std::move(toast_params));
std::move(toast_params));
}
void show_uninstallation_success()
{
::notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS);
::notifications::show_toast(localized_strings::UNINSTALLATION_SUCCESS, TOAST_TITLE);
}
void show_uninstallation_error()
{
::notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR);
::notifications::show_toast(localized_strings::UNINSTALLATION_UNKNOWN_ERROR, TOAST_TITLE);
}
void update_download_progress(float progress)
void update_download_progress(const updating::new_version_download_info& info, float progress)
{
::notifications::toast_params toast_params { UPDATE_NOTIFY_TOAST_TAG, false, progress };
::notifications::update_progress_bar_toast(localized_strings::GITHUB_NEW_VERSION_DOWNLOAD_STARTED, std::move(toast_params));
::notifications::progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version_string };
progress_title += L' ';
progress_title += progress < 1 ? localized_strings::DOWNLOAD_IN_PROGRESS : localized_strings::DOWNLOAD_COMPLETE;
progress_bar_params.progress_title = progress_title;
progress_bar_params.progress = progress;
::notifications::update_progress_bar_toast(UPDATE_NOTIFY_TOAST_TAG, progress_bar_params);
}
}
}

View File

@@ -15,6 +15,6 @@ namespace updating
void show_uninstallation_success();
void show_uninstallation_error();
void update_download_progress(float progress);
void update_download_progress(const updating::new_version_download_info& info, float progress);
}
}

View File

@@ -17,16 +17,19 @@
#include <winrt/Windows.Networking.Connectivity.h>
#include "VersionHelper.h"
#include <PathCch.h>
namespace
{
const wchar_t POWER_TOYS_UPGRADE_CODE[] = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const wchar_t POWERTOYS_EXE_COMPONENT[] = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}";
const wchar_t DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH[] = L"delete_previous_powertoys_confirm";
const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t MSIX_PACKAGE_NAME[] = L"Microsoft.PowerToys";
const wchar_t MSIX_PACKAGE_PUBLISHER[] = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US";
const size_t MAX_DOWNLOAD_ATTEMPTS = 3;
const wchar_t TOAST_TITLE[] = L"PowerToys";
}
namespace localized_strings
@@ -88,7 +91,7 @@ namespace updating
{
try
{
::notifications::show_toast(*system_message);
::notifications::show_toast(*system_message, TOAST_TITLE);
}
catch (...)
{
@@ -114,7 +117,7 @@ namespace updating
if (github_version > current_version)
{
const std::wstring_view required_architecture = get_architecture_string(get_current_architecture());
constexpr const std::wstring_view required_filename_pattern = updating::installer_filename_pattern;
constexpr const std::wstring_view required_filename_pattern = updating::INSTALLER_FILENAME_PATTERN;
// Desc-sorted by its priority
const std::array<std::wstring_view, 2> asset_extensions = { L".exe", L".msi" };
for (const auto asset_extension : asset_extensions)
@@ -202,7 +205,7 @@ namespace updating
{
co_return;
}
if (download_updates_automatically && !could_be_costly_connection())
{
auto installer_download_dst = create_download_path() / new_version->installer_filename;
@@ -260,8 +263,8 @@ namespace updating
try
{
auto progressUpdateHandle = [](float progress) {
updating::notifications::update_download_progress(progress);
auto progressUpdateHandle = [&](float progress) {
updating::notifications::update_download_progress(new_version.value(), progress);
};
http::HttpClient client;
@@ -275,4 +278,84 @@ namespace updating
co_return new_version->installer_filename;
}
std::optional<std::wstring> get_msi_package_installed_path()
{
constexpr size_t guid_length = 39;
wchar_t product_ID[guid_length];
if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(POWER_TOYS_UPGRADE_CODE, 0, 0, product_ID); !found)
{
return std::nullopt;
}
if (const bool installed = INSTALLSTATE_DEFAULT == MsiQueryProductStateW(product_ID); !installed)
{
return std::nullopt;
}
DWORD buf_size = MAX_PATH;
wchar_t buf[MAX_PATH];
if (ERROR_SUCCESS == MsiGetProductInfoW(product_ID, INSTALLPROPERTY_INSTALLLOCATION, buf, &buf_size) && buf_size)
{
return buf;
}
DWORD package_path_size = 0;
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, nullptr, &package_path_size))
{
return std::nullopt;
}
std::wstring package_path(++package_path_size, L'\0');
if (ERROR_SUCCESS != MsiGetProductInfoW(product_ID, INSTALLPROPERTY_LOCALPACKAGE, package_path.data(), &package_path_size))
{
return std::nullopt;
}
package_path.resize(size(package_path) - 1); // trim additional \0 which we got from MsiGetProductInfoW
wchar_t path[MAX_PATH];
DWORD path_size = MAX_PATH;
MsiGetComponentPathW(product_ID, POWERTOYS_EXE_COMPONENT, path, &path_size);
if (!path_size)
{
return std::nullopt;
}
PathCchRemoveFileSpec(path, path_size);
return path;
}
std::optional<VersionHelper> get_installed_powertoys_version()
{
auto installed_path = get_msi_package_installed_path();
if (!installed_path)
{
return std::nullopt;
}
*installed_path += L"\\PowerToys.exe";
// Get the version information for the file requested
const DWORD fvSize = GetFileVersionInfoSizeW(installed_path->c_str(), nullptr);
if (!fvSize)
{
return std::nullopt;
}
auto pbVersionInfo = std::make_unique<BYTE[]>(fvSize);
if (!GetFileVersionInfoW(installed_path->c_str(), 0, fvSize, pbVersionInfo.get()))
{
return std::nullopt;
}
VS_FIXEDFILEINFO* fileInfo = nullptr;
UINT fileInfoLen = 0;
if (!VerQueryValueW(pbVersionInfo.get(), L"\\", reinterpret_cast<LPVOID*>(&fileInfo), &fileInfoLen))
{
return std::nullopt;
}
return VersionHelper{ (fileInfo->dwFileVersionMS >> 16) & 0xffff,
(fileInfo->dwFileVersionMS >> 0) & 0xffff,
(fileInfo->dwFileVersionLS >> 16) & 0xffff };
}
}

View File

@@ -4,14 +4,17 @@
#include <string>
#include <future>
#include <filesystem>
#include <winrt/Windows.Foundation.h>
#include "../VersionHelper.h"
namespace updating
{
std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();
std::optional<std::wstring> get_msi_package_installed_path();
std::optional<VersionHelper> get_installed_powertoys_version();
std::future<bool> uninstall_previous_msix_version_async();
@@ -30,5 +33,6 @@ namespace updating
std::future<void> check_new_version_available();
std::future<std::wstring> download_update();
constexpr inline std::wstring_view installer_filename_pattern = L"powertoyssetup";
// non-localized
constexpr inline std::wstring_view INSTALLER_FILENAME_PATTERN = L"powertoyssetup";
}

View File

@@ -118,6 +118,7 @@
<Lib>
<AdditionalLibraryDirectories>
</AdditionalLibraryDirectories>
<AdditionalDependencies>Pathcch.lib;Version.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
@@ -150,6 +151,7 @@
<Lib>
<AdditionalLibraryDirectories>
</AdditionalLibraryDirectories>
<AdditionalDependencies>Pathcch.lib;Version.lib</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@@ -169,12 +171,14 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="dotnet_installation.h" />
<ClInclude Include="http_client.h" />
<ClInclude Include="toast_notifications_helper.h" />
<ClInclude Include="updating.h" />
<ClInclude Include="pch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="dotnet_installation.cpp" />
<ClCompile Include="http_client.cpp" />
<ClCompile Include="toast_notifications_helper.cpp" />
<ClCompile Include="updating.cpp" />

View File

@@ -27,6 +27,9 @@
<ClInclude Include="http_client.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dotnet_installation.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
@@ -41,9 +44,11 @@
<ClCompile Include="http_client.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dotnet_installation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="..\UnitTests-CommonLib\packages.config" />
</ItemGroup>
</Project>