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

40
src/common/RcResource.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include "pch.h"
#include "RcResource.h"
#include <fstream>
std::optional<RcResource> RcResource::create(int resource_id, const std::wstring_view resource_class)
{
const HRSRC resHandle = FindResourceW(nullptr, MAKEINTRESOURCEW(resource_id), resource_class.data());
if (!resHandle)
{
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<const std::byte*>(LockResource(memHandle));
if (!res)
{
return std::nullopt;
}
return RcResource{ { res, resSize } };
}
bool RcResource::saveAsFile(const std::filesystem::path destination)
{
std::fstream installerFile{ destination, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc };
if (!installerFile.is_open())
{
return false;
}
installerFile.write(reinterpret_cast<const char*>(_memory.data()), _memory.size());
return true;
}

22
src/common/RcResource.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <string_view>
#include <optional>
#include <span>
#include <filesystem>
class RcResource
{
public:
std::span<const std::byte> _memory;
static std::optional<RcResource> create(int resource_id, const std::wstring_view resource_class);
bool saveAsFile(const std::filesystem::path destination);
private:
RcResource() = delete;
RcResource(std::span<const std::byte> memory) :
_memory{ std::move(memory) }
{
}
};

View File

@@ -4,45 +4,7 @@
#include <RestartManager.h>
#include <Psapi.h>
std::vector<RM_UNIQUE_PROCESS> GetProcessInfoByName(const std::wstring& processName)
{
DWORD bytesReturned{};
std::vector<DWORD> processIds{};
processIds.resize(1024);
DWORD processIdSize{ (DWORD)processIds.size() * sizeof(DWORD) };
EnumProcesses(processIds.data(), processIdSize, &bytesReturned);
while (bytesReturned == processIdSize)
{
processIdSize *= 2;
processIds.resize(processIdSize / sizeof(DWORD));
EnumProcesses(processIds.data(), processIdSize, &bytesReturned);
}
std::vector<RM_UNIQUE_PROCESS> pInfos{};
for (const DWORD& processId : processIds)
{
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
if (hProcess)
{
wchar_t name[MAX_PATH];
if (GetProcessImageFileName(hProcess, name, MAX_PATH) > 0)
{
if (processName == PathFindFileName(name))
{
FILETIME creationTime{};
FILETIME exitTime{};
FILETIME kernelTime{};
FILETIME userTime{};
if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime))
{
pInfos.push_back({ processId, creationTime });
}
}
}
CloseHandle(hProcess);
}
}
return pInfos;
}
#include "processApi.h"
void RestartProcess(const std::wstring& processName)
{
@@ -52,7 +14,18 @@ void RestartProcess(const std::wstring& processName)
{
return;
}
std::vector<RM_UNIQUE_PROCESS> pInfo = GetProcessInfoByName(processName);
auto processHandles = getProcessHandlesByName(processName, PROCESS_QUERY_INFORMATION);
std::vector<RM_UNIQUE_PROCESS> pInfo;
for (const auto& hProcess : processHandles)
{
FILETIME creationTime{};
FILETIME _{};
if (GetProcessTimes(hProcess.get(), &creationTime, &_, &_, &_))
{
pInfo.emplace_back(RM_UNIQUE_PROCESS{ GetProcessId(hProcess.get()), creationTime });
}
}
if (pInfo.empty() ||
RmRegisterResources(sessionHandle, 0, nullptr, sizeof(pInfo), pInfo.data(), 0, nullptr) != ERROR_SUCCESS)
{

27
src/common/appMutex.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <string>
#include "wil/resource.h"
#include <lmcons.h>
namespace
{
constexpr inline wchar_t POWERTOYS_MSI_MUTEX_NAME[] = L"Local\\PowerToyRunMutex";
constexpr inline wchar_t POWERTOYS_MSIX_MUTEX_NAME[] = L"Local\\PowerToyMSIXRunMutex";
constexpr inline wchar_t POWERTOYS_BOOTSTRAPPER_MUTEX_NAME[] = L"PowerToysBootstrapperMutex";
}
inline wil::unique_mutex_nothrow createAppMutex(std::wstring mutexName)
{
wchar_t username[UNLEN + 1];
DWORD username_length = UNLEN + 1;
GetUserNameW(username, &username_length);
mutexName += username;
wil::unique_mutex_nothrow result{ CreateMutexW(nullptr, TRUE, mutexName.c_str()) };
return GetLastError() == ERROR_ALREADY_EXISTS ? wil::unique_mutex_nothrow{} : std::move(result);
}

View File

@@ -268,13 +268,26 @@ RECT keep_rect_inside_rect(const RECT& small_rect, const RECT& big_rect)
return result;
}
int run_message_loop()
int run_message_loop(const bool until_idle, const std::optional<uint32_t> timeout_seconds)
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
bool stop = false;
UINT_PTR timerId = 0;
if (timeout_seconds.has_value())
{
timerId = SetTimer(nullptr, 0, *timeout_seconds * 1000, nullptr);
}
while (!stop && GetMessageW(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
DispatchMessageW(&msg);
stop = until_idle && !PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
stop = stop || (msg.message == WM_TIMER && msg.wParam == timerId);
}
if (timeout_seconds.has_value())
{
KillTimer(nullptr, timerId);
}
return static_cast<int>(msg.wParam);
}
@@ -537,7 +550,7 @@ bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWOR
CloseHandle(pi.hThread);
}
}
return succeeded;
}
@@ -548,7 +561,7 @@ bool run_same_elevation(const std::wstring& file, const std::wstring& params, DW
{
executable_args += L" " + params;
}
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
auto succeeded = CreateProcessW(file.c_str(),
@@ -656,7 +669,7 @@ std::wstring get_module_filename(HMODULE mod)
return { buffer, actual_length };
}
std::wstring get_module_folderpath(HMODULE mod)
std::wstring get_module_folderpath(HMODULE mod, const bool removeFilename)
{
wchar_t buffer[MAX_PATH + 1];
DWORD actual_length = GetModuleFileNameW(mod, buffer, MAX_PATH);
@@ -671,7 +684,10 @@ std::wstring get_module_folderpath(HMODULE mod)
return long_filename;
}
PathRemoveFileSpecW(buffer);
if (removeFilename)
{
PathRemoveFileSpecW(buffer);
}
return { buffer, (UINT)lstrlenW(buffer) };
}

View File

@@ -40,7 +40,7 @@ bool operator<(const RECT& lhs, const RECT& rhs);
// Moves and/or resizes small_rect to fit inside big_rect.
RECT keep_rect_inside_rect(const RECT& small_rect, const RECT& big_rect);
// Initializes and runs windows message loop
int run_message_loop();
int run_message_loop(const bool until_idle = false, const std::optional<uint32_t> timeout_seconds = {});
std::optional<std::wstring> get_last_error_message(const DWORD dw);
void show_last_error_message(LPCWSTR lpszFunction, DWORD dw);
@@ -89,7 +89,7 @@ std::wstring get_process_path(HWND hwnd) noexcept;
std::wstring get_product_version();
std::wstring get_module_filename(HMODULE mod = nullptr);
std::wstring get_module_folderpath(HMODULE mod = nullptr);
std::wstring get_module_folderpath(HMODULE mod = nullptr, const bool removeFilename = true);
// Get a string from the resource file
std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wchar_t* fallback);
@@ -135,6 +135,6 @@ struct overloaded : Ts...
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...)->overloaded<Ts...>;
overloaded(Ts...) -> overloaded<Ts...>;
#define POWER_LAUNCHER_PID_SHARED_FILE L"Local\\3cbfbad4-199b-4e2c-9825-942d5d3d3c74"

View File

@@ -116,6 +116,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="animation.h" />
<ClInclude Include="appMutex.h" />
<ClInclude Include="async_message_queue.h" />
<ClInclude Include="d2d_svg.h" />
<ClInclude Include="d2d_text.h" />
@@ -126,6 +127,8 @@
<ClInclude Include="keyboard_layout.h" />
<ClInclude Include="keyboard_layout_impl.h" />
<ClInclude Include="notifications.h" />
<ClInclude Include="processApi.h" />
<ClInclude Include="RcResource.h" />
<ClInclude Include="os-detect.h" />
<ClInclude Include="RestartManagement.h" />
<ClInclude Include="shared_constants.h" />
@@ -165,6 +168,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="RcResource.cpp" />
<ClCompile Include="RestartManagement.cpp" />
<ClCompile Include="settings_helpers.cpp" />
<ClCompile Include="settings_objects.cpp" />
@@ -195,4 +199,4 @@
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200703.9\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.200703.9\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200703.9\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.200703.9\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
</Project>

View File

@@ -120,6 +120,15 @@
<ClInclude Include="debug_control.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RcResource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="appMutex.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="processApi.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="d2d_svg.cpp">
@@ -195,6 +204,9 @@
<ClCompile Include="RestartManagement.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RcResource.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -3,6 +3,7 @@
#include "common.h"
#include "com_object_factory.h"
#include "notifications.h"
#include "winstore.h"
#include <unknwn.h>
#include <winrt/base.h>
@@ -11,35 +12,37 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.ApplicationModel.Background.h>
#include "winstore.h"
#include <wil/com.h>
#include <propvarutil.h>
#include <propkey.h>
#include <Shobjidl.h>
#include <winerror.h>
#include <NotificationActivationCallback.h>
#include "notifications_winrt/handler_functions.h"
#include <filesystem>
using namespace winrt::Windows::ApplicationModel::Background;
using winrt::Windows::Data::Xml::Dom::XmlDocument;
using winrt::Windows::UI::Notifications::ToastNotification;
using winrt::Windows::UI::Notifications::ToastNotificationManager;
namespace fs = std::filesystem;
namespace
{
constexpr std::wstring_view TASK_NAME = L"PowerToysBackgroundNotificationsHandler";
constexpr std::wstring_view TASK_ENTRYPOINT = L"PowerToysNotifications.BackgroundHandler";
constexpr std::wstring_view APPLICATION_ID = L"PowerToys";
constexpr std::wstring_view PACKAGED_APPLICATION_ID = L"PowerToys";
constexpr std::wstring_view APPIDS_REGISTRY = LR"(Software\Classes\AppUserModelId\)";
constexpr std::wstring_view WIN32_AUMID = L"Microsoft.PowerToysWin32";
std::wstring APPLICATION_ID;
}
namespace localized_strings
namespace localized_strings
{
constexpr std::wstring_view SNOOZE_BUTTON = L"Snooze";
constexpr std::wstring_view PT_UPDATE = L"PowerToys update";
constexpr std::wstring_view DOWNLOAD_IN_PROGRESS = L"Downloading...";
constexpr std::wstring_view DOWNLOAD_COMPLETE = L"Download complete";
}
static DWORD loop_thread_id()
@@ -119,6 +122,74 @@ void notifications::run_desktop_app_activator_loop()
CoRevokeClassObject(token);
}
bool notifications::register_application_id(const std::wstring_view appName, const std::wstring_view iconPath)
{
std::wstring aumidPath{ APPIDS_REGISTRY };
aumidPath += APPLICATION_ID;
wil::unique_hkey aumidKey;
if (FAILED(RegCreateKeyW(HKEY_CURRENT_USER, aumidPath.c_str(), &aumidKey)))
{
return false;
}
if (FAILED(RegSetKeyValueW(aumidKey.get(),
nullptr,
L"DisplayName",
REG_SZ,
appName.data(),
static_cast<DWORD>((size(appName) + 1) * sizeof(wchar_t)))))
{
return false;
}
if (FAILED(RegSetKeyValueW(aumidKey.get(),
nullptr,
L"IconUri",
REG_SZ,
iconPath.data(),
static_cast<DWORD>((size(iconPath) + 1) * sizeof(wchar_t)))))
{
return false;
}
const std::wstring_view iconColor = L"FFDDDDDD";
if (FAILED(RegSetKeyValueW(aumidKey.get(),
nullptr,
L"IconBackgroundColor",
REG_SZ,
iconColor.data(),
static_cast<DWORD>((size(iconColor) + 1) * sizeof(wchar_t)))))
{
return false;
}
return true;
}
void notifications::unregister_application_id()
{
std::wstring aumidPath{ APPIDS_REGISTRY };
aumidPath += APPLICATION_ID;
wil::unique_hkey registryRoot;
RegOpenKeyW(HKEY_CURRENT_USER, aumidPath.c_str(), &registryRoot);
if (!registryRoot)
{
return;
}
RegDeleteTreeW(registryRoot.get(), nullptr);
registryRoot.reset();
RegOpenKeyW(HKEY_CURRENT_USER, APPIDS_REGISTRY.data(), &registryRoot);
if (!registryRoot)
{
return;
}
RegDeleteKeyW(registryRoot.get(), APPLICATION_ID.data());
}
void notifications::set_application_id(const std::wstring_view appID)
{
APPLICATION_ID = appID;
SetCurrentProcessExplicitAppUserModelID(APPLICATION_ID.c_str());
}
void notifications::register_background_toast_handler()
{
if (!winstore::running_as_packaged())
@@ -133,7 +204,7 @@ void notifications::register_background_toast_handler()
BackgroundExecutionManager::RequestAccessAsync().get();
BackgroundTaskBuilder builder;
ToastNotificationActionTrigger trigger{ APPLICATION_ID };
ToastNotificationActionTrigger trigger{ PACKAGED_APPLICATION_ID };
builder.SetTrigger(trigger);
builder.TaskEntryPoint(TASK_ENTRYPOINT);
builder.Name(TASK_NAME);
@@ -154,10 +225,10 @@ void notifications::register_background_toast_handler()
}
}
void notifications::show_toast(std::wstring message, toast_params params)
void notifications::show_toast(std::wstring message, std::wstring title, toast_params params)
{
// The toast won't be actually activated in the background, since it doesn't have any buttons
show_toast_with_activations(std::move(message), {}, {}, std::move(params));
show_toast_with_activations(std::move(message), std::move(title), {}, {}, std::move(params));
}
inline void xml_escape(std::wstring data)
@@ -191,34 +262,26 @@ inline void xml_escape(std::wstring data)
data.swap(buffer);
}
void notifications::show_toast_with_activations(std::wstring message, std::wstring_view background_handler_id, std::vector<action_t> actions, toast_params params)
void notifications::show_toast_with_activations(std::wstring message,
std::wstring title,
std::wstring_view background_handler_id,
std::vector<action_t> actions,
toast_params params)
{
// DO NOT LOCALIZE any string in this function, because they're XML tags and a subject to
// https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/toast-xml-schema
std::wstring toast_xml;
toast_xml.reserve(2048);
std::wstring title{ L"PowerToys" };
if (winstore::running_as_packaged())
{
title += L" (Experimental)";
}
toast_xml += LR"(<?xml version="1.0"?><toast><visual><binding template="ToastGeneric"><text>)";
toast_xml += LR"(<?xml version="1.0"?><toast><visual><binding template="ToastGeneric"><text id="1">)";
toast_xml += title;
toast_xml += L"</text><text>";
toast_xml += LR"(</text><text id="2">)";
toast_xml += message;
toast_xml += L"</text>";
if (params.progress)
if (params.progress_bar.has_value())
{
toast_xml += LR"(<progress title=")";
toast_xml += localized_strings::PT_UPDATE;
if (params.subtitle)
{
toast_xml += L" ";
toast_xml += *params.subtitle;
}
toast_xml += LR"(" value="{progressValue}" valueStringOverride="{progressValueString}" status="{progressStatus}"/>)";
toast_xml += LR"(<progress title="{progressTitle}" value="{progressValue}" valueStringOverride="{progressValueString}" status="" />)";
}
toast_xml += L"</binding></visual><actions>";
for (size_t i = 0; i < size(actions); ++i)
@@ -310,19 +373,19 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri
toast_xml_doc.LoadXml(toast_xml);
ToastNotification notification{ toast_xml_doc };
if (params.progress)
if (params.progress_bar.has_value())
{
float progress = std::clamp(params.progress.value(), 0.0f, 1.0f);
float progress = std::clamp(params.progress_bar->progress, 0.0f, 1.0f);
winrt::Windows::Foundation::Collections::StringMap map;
map.Insert(L"progressValue", std::to_wstring(progress));
map.Insert(L"progressValueString", std::to_wstring(static_cast<int>(progress * 100)) + std::wstring(L"%"));
map.Insert(L"progressStatus", localized_strings::DOWNLOAD_IN_PROGRESS);
map.Insert(L"progressTitle", params.progress_bar->progress_title);
winrt::Windows::UI::Notifications::NotificationData data(map);
notification.Data(data);
}
const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() :
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID);
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(APPLICATION_ID);
// Set a tag-related params if it has a valid length
if (params.tag.has_value() && params.tag->length() < 64)
@@ -343,29 +406,18 @@ void notifications::show_toast_with_activations(std::wstring message, std::wstri
notifier.Show(notification);
}
void notifications::update_progress_bar_toast(std::wstring plaintext_message, toast_params params)
void notifications::update_progress_bar_toast(std::wstring_view tag, progress_bar_params params)
{
if (!params.progress.has_value())
{
return;
}
const auto notifier = winstore::running_as_packaged() ?
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() :
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(APPLICATION_ID);
const auto notifier = winstore::running_as_packaged() ? ToastNotificationManager::ToastNotificationManager::CreateToastNotifier() :
ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(WIN32_AUMID);
float progress = std::clamp(params.progress.value(), 0.0f, 1.0f);
float progress = std::clamp(params.progress, 0.0f, 1.0f);
winrt::Windows::Foundation::Collections::StringMap map;
map.Insert(L"progressValue", std::to_wstring(progress));
map.Insert(L"progressValueString", std::to_wstring(static_cast<int>(progress * 100)) + std::wstring(L"%"));
map.Insert(L"progressStatus", progress < 1 ? localized_strings::DOWNLOAD_IN_PROGRESS : localized_strings::DOWNLOAD_COMPLETE);
map.Insert(L"progressTitle", params.progress_title);
winrt::Windows::UI::Notifications::NotificationData data(map);
std::wstring tag = L"";
if (params.tag.has_value() && params.tag->length() < 64)
{
tag = *params.tag;
}
winrt::Windows::UI::Notifications::NotificationUpdateResult res = notifier.Update(data, tag);
}

View File

@@ -10,10 +10,13 @@ namespace notifications
{
constexpr inline const wchar_t TOAST_ACTIVATED_LAUNCH_ARG[] = L"-ToastActivated";
void set_application_id(const std::wstring_view appID);
void register_background_toast_handler();
void run_desktop_app_activator_loop();
bool register_application_id(const std::wstring_view appName, const std::wstring_view iconPath);
void unregister_application_id();
struct snooze_duration
{
std::wstring label;
@@ -39,17 +42,22 @@ namespace notifications
bool context_menu = false;
};
struct progress_bar_params
{
std::wstring_view progress_title;
float progress = 0.f;
};
struct toast_params
{
std::optional<std::wstring_view> tag;
bool resend_if_scheduled = true;
std::optional<float> progress;
std::optional<std::wstring_view> subtitle;
std::optional<progress_bar_params> progress_bar;
};
using action_t = std::variant<link_button, background_activated_button, snooze_button>;
void show_toast(std::wstring plaintext_message, toast_params params = {});
void show_toast_with_activations(std::wstring plaintext_message, std::wstring_view background_handler_id, std::vector<action_t> actions, toast_params params = {});
void update_progress_bar_toast(std::wstring plaintext_message, toast_params params);
void show_toast(std::wstring plaintext_message, std::wstring title, toast_params params = {});
void show_toast_with_activations(std::wstring plaintext_message, std::wstring title, std::wstring_view background_handler_id, std::vector<action_t> actions, toast_params params = {});
void update_progress_bar_toast(std::wstring_view tag, progress_bar_params params);
}

42
src/common/processApi.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <vector>
#include <wil/resource.h>
#include <Shlwapi.h>
#include <Psapi.h>
#include <string_view>
#pragma comment(lib, "Shlwapi.lib")
inline std::vector<wil::unique_process_handle> getProcessHandlesByName(const std::wstring_view processName, DWORD handleAccess)
{
std::vector<wil::unique_process_handle> result;
DWORD bytesRequired;
std::vector<DWORD> processIds;
processIds.resize(4096 / sizeof(processIds[0]));
auto processIdSize = static_cast<DWORD>(size(processIds) * sizeof(processIds[0]));
EnumProcesses(processIds.data(), processIdSize, &bytesRequired);
while (bytesRequired == processIdSize)
{
processIdSize *= 2;
processIds.resize(processIdSize / sizeof(processIds[0]));
EnumProcesses(processIds.data(), processIdSize, &bytesRequired);
}
processIds.resize(bytesRequired / sizeof(processIds[0]));
handleAccess |= PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ;
for (const DWORD processId : processIds)
{
wil::unique_process_handle hProcess{ OpenProcess(handleAccess, FALSE, processId) };
wchar_t name[MAX_PATH + 1];
if (!hProcess || !GetProcessImageFileNameW(hProcess.get(), name, MAX_PATH))
{
continue;
}
if (processName == PathFindFileNameW(name))
{
result.push_back(std::move(hProcess));
}
}
return result;
}

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>