diff --git a/.pipelines/build-bootstrapper.cmd b/.pipelines/build-bootstrapper.cmd new file mode 100644 index 0000000000..e738c7b190 --- /dev/null +++ b/.pipelines/build-bootstrapper.cmd @@ -0,0 +1,10 @@ +cd /D "%~dp0" + +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=amd64 -host_arch=amd64 -winsdk=10.0.18362.0 +pushd . +cd .. +set SolutionDir=%cd% +popd +SET IsPipeline=1 +call msbuild ../src/bootstrapper/bootstrapper.vcxproj /p:Configuration=Release /p:Platform=x64 /p:CIBuild=true /p:BuildProjectReferences=false /p:SolutionDir=%SolutionDir%\ || exit /b 1 + diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 5c2f32c2ca..cef106046a 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -132,6 +132,17 @@ build: - 'PowerToysSetup-*.msi' signing_options: sign_inline: true # This does signing a soon as this command completes + - !!buildcommand + name: 'Build Power Toys Bootstrapper' + command: '.pipelines\build-bootstrapper.cmd' + artifacts: + - from: 'x64/Release' + to: 'Build_Installer_Output' + include: + - 'PowerToysSetup-*.exe' + signing_options: + sign_inline: true # This does signing a soon as this command completes + #package: # commands: diff --git a/PowerToys.sln b/PowerToys.sln index 258cfe8c75..eb45d5b047 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -263,6 +263,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPickerUI", "src\module EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "colorpicker", "colorpicker", "{1D78B84B-CA39-406C-98F4-71F7EC266CC0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bootstrapper", "src\bootstrapper\bootstrapper.vcxproj", "{D194E3AA-F824-4CA9-9A58-034DD6B7D022}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -521,6 +523,8 @@ Global {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64 + {D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Debug|x64.ActiveCfg = Debug|x64 + {D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Release|x64.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 7f55813851..99d915d55d 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -2,6 +2,8 @@ #include +#include "../../src/common/updating/updating.h" + using namespace std; TRACELOGGING_DEFINE_PROVIDER( @@ -14,7 +16,6 @@ TRACELOGGING_DEFINE_PROVIDER( const DWORD USERNAME_DOMAIN_LEN = DNLEN + UNLEN + 2; // Domain Name + '\' + User Name + '\0' const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' -static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}"; static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; // Creates a Scheduled Task to run at logon for the current user. @@ -563,52 +564,6 @@ LExit: return WcaFinalize(er); } -std::optional getMsiPackageInstalledPath(const wchar_t* product_upgrade_code, const wchar_t* file_component) -{ - constexpr size_t guid_length = 39; - wchar_t product_ID[guid_length]; - if (const bool found = ERROR_SUCCESS == MsiEnumRelatedProductsW(product_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[256]; - DWORD path_size = 256; - MsiGetComponentPathW(product_ID, file_component, path, &path_size); - if (!path_size) - { - return std::nullopt; - } - PathCchRemoveFileSpec(path, path_size); - return path; -} - UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; @@ -617,7 +572,7 @@ UINT __stdcall DetectPrevInstallPathCA(MSIHANDLE hInstall) try { - if (auto install_path = getMsiPackageInstalledPath(POWERTOYS_UPGRADE_CODE, POWERTOYS_EXE_COMPONENT)) + if (auto install_path = updating::get_msi_package_installed_path()) { MsiSetPropertyW(hInstall, L"INSTALLFOLDER", install_path->data()); } diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj index 4a9e2e892a..e0a23ec46d 100644 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -60,7 +60,7 @@ true - Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies) + Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies) $(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories) CustomAction.def true @@ -82,7 +82,7 @@ true - Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;%(AdditionalDependencies) + Psapi.lib;Pathcch.lib;comsupp.lib;taskschd.lib;Secur32.lib;msi.lib;dutil.lib;wcautil.lib;Version.lib;..\..\$(PlatformShortName)\$(Configuration)\common.lib;..\..\$(PlatformShortName)\$(Configuration)\updating.lib;%(AdditionalDependencies) $(WIX)sdk\$(WixPlatformToolset)\lib\x64;$(SolutionDir)\packages\WiX.3.11.2\tools\sdk\vs2017\lib\x64;%(AdditionalLibraryDirectories) CustomAction.def true @@ -93,12 +93,12 @@ HighestAvailable - + Use stdafx.h - + diff --git a/src/action_runner/action_runner.cpp b/src/action_runner/action_runner.cpp index 07a9a1365f..c26177a391 100644 --- a/src/action_runner/action_runner.cpp +++ b/src/action_runner/action_runner.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -114,6 +115,7 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE}; sei.lpFile = installer_path.c_str(); sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = L"-silent"; success = ShellExecuteExW(&sei) == TRUE; // Wait for the install completion @@ -146,57 +148,6 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view return true; } -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/d8cf1fe3-21c2-4baf-988f-f0152996135e/0c00b94713ee93e7ad5b4f82e2b86607/windowsdesktop-runtime-3.1.4-win-x64.exe"; - const wchar_t DOTNET_DESKTOP_FILENAME[] = L"windowsdesktop-runtime-3.1.4-win-x64.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) - { - MessageBoxW(nullptr, - GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE).c_str(), - GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE).c_str(), - MB_OK | MB_ICONERROR); - return false; - } - SHELLEXECUTEINFOW sei{ sizeof(sei) }; - sei.fMask = { SEE_MASK_NOASYNC }; - sei.lpFile = dotnet_download_path.c_str(); - sei.nShow = SW_SHOWNORMAL; - sei.lpParameters = L"/install /passive"; - return ShellExecuteExW(&sei) == TRUE; -} - int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { int nArgs = 0; @@ -270,11 +221,18 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) } else if (action == L"-install_dotnet") { - if (dotnet_is_installed()) + if (updating::dotnet_is_installed()) { return 0; } - return !install_dotnet(); + const bool success = updating::install_dotnet(); + + MessageBoxW(nullptr, + GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE).c_str(), + GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE).c_str(), + MB_OK | MB_ICONERROR); + + return !success; } else if (action == L"-uninstall_msi") { diff --git a/src/action_runner/service_runner.vcxproj.filters b/src/action_runner/service_runner.vcxproj.filters deleted file mode 100644 index 3f4b3d6ad9..0000000000 --- a/src/action_runner/service_runner.vcxproj.filters +++ /dev/null @@ -1,27 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Header Files - - - \ No newline at end of file diff --git a/src/bootstrapper/bootstrapper.cpp b/src/bootstrapper/bootstrapper.cpp new file mode 100644 index 0000000000..04ab6ab610 --- /dev/null +++ b/src/bootstrapper/bootstrapper.cpp @@ -0,0 +1,208 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include "resource.h" +#include + +#include + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace +{ + const wchar_t APPLICATION_ID[] = L"PowerToysInstaller"; + const wchar_t TOAST_TAG[] = L"PowerToysInstallerProgress"; +} + +namespace localized_strings +{ + const wchar_t INSTALLER_EXTRACT_ERROR[] = L"Couldn't extract MSI installer!"; + const wchar_t TOAST_TITLE[] = L"PowerToys Installation"; + const wchar_t EXTRACTING_INSTALLER[] = L"Extracting PowerToys MSI..."; + const wchar_t UNINSTALLING_PREVIOUS_VERSION[] = L"Uninstalling previous PowerToys version..."; + const wchar_t UNINSTALL_PREVIOUS_VERSION_ERROR[] = L"Couldn't uninstall previous PowerToys version!"; + const wchar_t INSTALLING_DOTNET[] = L"Installing dotnet..."; + const wchar_t DOTNET_INSTALL_ERROR[] = L"Couldn't install dotnet!"; + const wchar_t INSTALLING_NEW_VERSION[] = L"Installing new PowerToys version..."; + const wchar_t NEW_VERSION_INSTALLATION_DONE[] = L"PowerToys installation complete!"; + const wchar_t NEW_VERSION_INSTALLATION_ERROR[] = L"Couldn't install new PowerToys version."; +} + +namespace fs = std::filesystem; + +std::optional extractEmbeddedInstaller() +{ + auto executableRes = RcResource::create(IDR_BIN_MSIINSTALLER, L"BIN"); + if (!executableRes) + { + return std::nullopt; + } + auto installerPath = fs::temp_directory_path() / L"PowerToysBootstrappedInstaller-" PRODUCT_VERSION_STRING L".msi"; + return executableRes->saveAsFile(installerPath) ? std::make_optional(std::move(installerPath)) : std::nullopt; +} + +std::optional 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; +} + +int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) +{ + using namespace localized_strings; + winrt::init_apartment(); + + // Try killing PowerToys and prevent future processes launch + for (auto& handle : getProcessHandlesByName(L"PowerToys.exe", PROCESS_TERMINATE)) + { + TerminateProcess(handle.get(), 0); + } + auto powerToysMutex = createAppMutex(POWERTOYS_MSI_MUTEX_NAME); + + int n_cmd_args = 0; + LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args); + const bool silent = n_cmd_args > 1 && std::wstring_view{ L"-silent" } == cmd_arg_list[1]; + + auto instanceMutex = createAppMutex(POWERTOYS_BOOTSTRAPPER_MUTEX_NAME); + if (!instanceMutex) + { + return 1; + } + notifications::set_application_id(APPLICATION_ID); + + fs::path iconPath{ L"C:\\" }; + if (auto extractedIcon = extractIcon()) + { + iconPath = std::move(*extractedIcon); + } + + notifications::register_application_id(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); + if (const auto installedVersion = updating::get_installed_powertoys_version(); installedVersion && *installedVersion >= myVersion) + { + auto msi_path = updating::get_msi_package_path(); + if (!msi_path.empty()) + { + MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr); + MsiInstallProductW(msi_path.c_str(), nullptr); + return 0; + } + } + + std::mutex progressLock; + notifications::progress_bar_params progressParams; + progressParams.progress = 0.0f; + progressParams.progress_title = EXTRACTING_INSTALLER; + notifications::toast_params params{ TOAST_TAG, false, std::move(progressParams) }; + notifications::show_toast_with_activations({}, TOAST_TITLE, {}, {}, std::move(params)); + + auto processToasts = wil::scope_exit([&] { + run_message_loop(true, 2); + }); + + // Worker thread to periodically increase progress and keep the progress toast from losing focus + std::thread{ [&] { + for (;; Sleep(3000)) + { + std::scoped_lock lock{ progressLock }; + if (progressParams.progress == 1.f) + { + break; + } + progressParams.progress = min(0.99f, progressParams.progress + 0.001f); + notifications::update_progress_bar_toast(TOAST_TAG, progressParams); + } + } }.detach(); + + auto updateProgressBar = [&](const float value, const wchar_t* title) { + std::scoped_lock lock{ progressLock }; + progressParams.progress = value; + progressParams.progress_title = title; + notifications::update_progress_bar_toast(TOAST_TAG, progressParams); + }; + + const auto installerPath = extractEmbeddedInstaller(); + if (!installerPath) + { + notifications::show_toast(INSTALLER_EXTRACT_ERROR, TOAST_TITLE); + return 1; + } + auto removeExtractedInstaller = wil::scope_exit([&] { + std::error_code _; + fs::remove(*installerPath, _); + }); + + updateProgressBar(.25f, UNINSTALLING_PREVIOUS_VERSION); + const auto package_path = updating::get_msi_package_path(); + if (!package_path.empty() && !updating::uninstall_msi_version(package_path)) + { + notifications::show_toast(UNINSTALL_PREVIOUS_VERSION_ERROR, TOAST_TITLE); + } + + updateProgressBar(.5f, INSTALLING_DOTNET); + if (!updating::dotnet_is_installed() && !updating::install_dotnet()) + { + notifications::show_toast(DOTNET_INSTALL_ERROR, TOAST_TITLE); + } + + updateProgressBar(.75f, INSTALLING_NEW_VERSION); + if (!silent) + { + MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr); + } + const bool installationDone = MsiInstallProductW(installerPath->c_str(), nullptr) == ERROR_SUCCESS; + + updateProgressBar(1.f, installationDone ? NEW_VERSION_INSTALLATION_DONE : NEW_VERSION_INSTALLATION_ERROR); + + if (!installationDone) + { + return 1; + } + auto newPTPath = updating::get_msi_package_installed_path(); + if (!newPTPath) + { + return 1; + } + // Do not launch PowerToys, if we're launched from the action_runner + if (!silent) + { + *newPTPath += L"\\PowerToys.exe"; + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NO_CONSOLE }; + sei.lpFile = newPTPath->c_str(); + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = UPDATE_REPORT_SUCCESS; + ShellExecuteExW(&sei); + } + + return 0; +} diff --git a/src/bootstrapper/bootstrapper.rc b/src/bootstrapper/bootstrapper.rc new file mode 100644 index 0000000000..44885243e8 --- /dev/null +++ b/src/bootstrapper/bootstrapper.rc @@ -0,0 +1,48 @@ +#include +#include "resource.h" +#include "../common/version.h" + +MAINICON ICON "../runner/svgs/icon.ico" +IDR_BIN_ICON BIN "../runner/svgs/icon.ico" + + +STRINGTABLE +BEGIN + IDS_DOTNET_CORE_DOWNLOAD_FAILURE "Couldn't download .NET Core Desktop Runtime 3.1.3, please install it manually." + IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE "PowerToys installation error" +END + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END + +#include "Generated Files\installer_resource.rc" \ No newline at end of file diff --git a/src/bootstrapper/bootstrapper.vcxproj b/src/bootstrapper/bootstrapper.vcxproj new file mode 100644 index 0000000000..72ddfe35be --- /dev/null +++ b/src/bootstrapper/bootstrapper.vcxproj @@ -0,0 +1,139 @@ + + + + + + + + + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {D194E3AA-F824-4CA9-9A58-034DD6B7D022} + bootstrapper + 10.0.17134.0 + bootstrapper + + + + Application + true + v142 + Unicode + Spectre + + + Application + false + v142 + true + Unicode + Spectre + + + + + + + + + + + + + + PowerToysSetup-$(Version)-$(PlatformShortName) + + + false + ../;$(IncludePath) + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + + + true + ../;$(IncludePath) + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + stdcpplatest + true + MultiThreaded + + + Windows + true + true + true + WindowsApp.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies) + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + stdcpplatest + true + MultiThreadedDebug + + + Windows + true + WindowsApp.lib;Msi.lib;Shlwapi.lib;%(AdditionalDependencies) + + + + + + + + {74485049-c722-400f-abe5-86ac52d929b3} + + + {17da04df-e393-4397-9cf0-84dabe11032e} + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/bootstrapper/packages.config b/src/bootstrapper/packages.config new file mode 100644 index 0000000000..730d206108 --- /dev/null +++ b/src/bootstrapper/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/bootstrapper/resource.h b/src/bootstrapper/resource.h new file mode 100644 index 0000000000..d372099cd0 --- /dev/null +++ b/src/bootstrapper/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by bootstrapper.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Bootstrapper" +#define INTERNAL_NAME "bootstrapper" +#define ORIGINAL_FILENAME "bootstrapper.exe" + +// Non-localizable +////////////////////////////// + +#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE 101 +#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE 102 + +#define IDR_BIN_MSIINSTALLER 103 +#define IDR_BIN_ICON 104 diff --git a/src/common/RcResource.cpp b/src/common/RcResource.cpp new file mode 100644 index 0000000000..bde1edc94c --- /dev/null +++ b/src/common/RcResource.cpp @@ -0,0 +1,40 @@ +#include "pch.h" +#include "RcResource.h" + +#include + +std::optional 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(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(_memory.data()), _memory.size()); + return true; +} diff --git a/src/common/RcResource.h b/src/common/RcResource.h new file mode 100644 index 0000000000..54b9b732ef --- /dev/null +++ b/src/common/RcResource.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include + +class RcResource +{ +public: + std::span _memory; + + static std::optional create(int resource_id, const std::wstring_view resource_class); + bool saveAsFile(const std::filesystem::path destination); + +private: + RcResource() = delete; + RcResource(std::span memory) : + _memory{ std::move(memory) } + { + } +}; diff --git a/src/common/RestartManagement.cpp b/src/common/RestartManagement.cpp index 5dda4d7016..18bad28580 100644 --- a/src/common/RestartManagement.cpp +++ b/src/common/RestartManagement.cpp @@ -4,45 +4,7 @@ #include #include -std::vector GetProcessInfoByName(const std::wstring& processName) -{ - DWORD bytesReturned{}; - std::vector 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 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 pInfo = GetProcessInfoByName(processName); + auto processHandles = getProcessHandlesByName(processName, PROCESS_QUERY_INFORMATION); + std::vector 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) { diff --git a/src/common/appMutex.h b/src/common/appMutex.h new file mode 100644 index 0000000000..61db26f144 --- /dev/null +++ b/src/common/appMutex.h @@ -0,0 +1,27 @@ +#pragma once +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include + +#include "wil/resource.h" +#include + +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); +} diff --git a/src/common/common.cpp b/src/common/common.cpp index 1da9aec7cf..d87c867a8c 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -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 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(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) }; } diff --git a/src/common/common.h b/src/common/common.h index 642f3f67eb..556bc0e1a1 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -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 timeout_seconds = {}); std::optional 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 -overloaded(Ts...)->overloaded; +overloaded(Ts...) -> overloaded; #define POWER_LAUNCHER_PID_SHARED_FILE L"Local\\3cbfbad4-199b-4e2c-9825-942d5d3d3c74" diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 81f3e89024..30d2e89f9e 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -116,6 +116,7 @@ + @@ -126,6 +127,8 @@ + + @@ -165,6 +168,7 @@ Create + @@ -195,4 +199,4 @@ - \ No newline at end of file + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 0a1661fa7a..91427520e7 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -120,6 +120,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -195,6 +204,9 @@ Source Files + + Source Files + diff --git a/src/common/notifications.cpp b/src/common/notifications.cpp index 06e9d52643..e2776cbe93 100644 --- a/src/common/notifications.cpp +++ b/src/common/notifications.cpp @@ -3,6 +3,7 @@ #include "common.h" #include "com_object_factory.h" #include "notifications.h" +#include "winstore.h" #include #include @@ -11,35 +12,37 @@ #include #include #include - -#include "winstore.h" +#include +#include +#include +#include #include #include #include "notifications_winrt/handler_functions.h" +#include 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((size(appName) + 1) * sizeof(wchar_t))))) + { + return false; + } + + if (FAILED(RegSetKeyValueW(aumidKey.get(), + nullptr, + L"IconUri", + REG_SZ, + iconPath.data(), + static_cast((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((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(), ®istryRoot); + if (!registryRoot) + { + return; + } + RegDeleteTreeW(registryRoot.get(), nullptr); + registryRoot.reset(); + RegOpenKeyW(HKEY_CURRENT_USER, APPIDS_REGISTRY.data(), ®istryRoot); + 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 actions, toast_params params) +void notifications::show_toast_with_activations(std::wstring message, + std::wstring title, + std::wstring_view background_handler_id, + std::vector 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"()"; + toast_xml += LR"()"; toast_xml += title; - toast_xml += L""; + toast_xml += LR"()"; toast_xml += message; toast_xml += L""; - if (params.progress) + if (params.progress_bar.has_value()) { - toast_xml += LR"()"; + toast_xml += LR"()"; } toast_xml += L""; 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(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(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); } diff --git a/src/common/notifications.h b/src/common/notifications.h index 5e2b57918d..99eea438d0 100644 --- a/src/common/notifications.h +++ b/src/common/notifications.h @@ -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 tag; bool resend_if_scheduled = true; - std::optional progress; - std::optional subtitle; + std::optional progress_bar; }; using action_t = std::variant; - 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 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 actions, toast_params params = {}); + void update_progress_bar_toast(std::wstring_view tag, progress_bar_params params); } diff --git a/src/common/processApi.h b/src/common/processApi.h new file mode 100644 index 0000000000..3396728a3f --- /dev/null +++ b/src/common/processApi.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +#pragma comment(lib, "Shlwapi.lib") + +inline std::vector getProcessHandlesByName(const std::wstring_view processName, DWORD handleAccess) +{ + std::vector result; + DWORD bytesRequired; + std::vector processIds; + processIds.resize(4096 / sizeof(processIds[0])); + auto processIdSize = static_cast(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; +} diff --git a/src/common/updating/dotnet_installation.cpp b/src/common/updating/dotnet_installation.cpp new file mode 100644 index 0000000000..8cb3222c8e --- /dev/null +++ b/src/common/updating/dotnet_installation.cpp @@ -0,0 +1,64 @@ +#include "pch.h" + +#include + +#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; + } +} \ No newline at end of file diff --git a/src/common/updating/dotnet_installation.h b/src/common/updating/dotnet_installation.h new file mode 100644 index 0000000000..b27bdd9591 --- /dev/null +++ b/src/common/updating/dotnet_installation.h @@ -0,0 +1,7 @@ +#pragma once + +namespace updating +{ + bool dotnet_is_installed(); + bool install_dotnet(); +} \ No newline at end of file diff --git a/src/common/updating/pch.h b/src/common/updating/pch.h index 6dcc374983..77175d7e73 100644 --- a/src/common/updating/pch.h +++ b/src/common/updating/pch.h @@ -13,5 +13,7 @@ #include #include #include +#include +#include #endif //PCH_H diff --git a/src/common/updating/toast_notifications_helper.cpp b/src/common/updating/toast_notifications_helper.cpp index c6d6de2fce..bcde3142a9 100644 --- a/src/common/updating/toast_notifications_helper.cpp +++ b/src/common/updating/toast_notifications_helper.cpp @@ -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); } } } \ No newline at end of file diff --git a/src/common/updating/toast_notifications_helper.h b/src/common/updating/toast_notifications_helper.h index f582d36e1f..1d143f506a 100644 --- a/src/common/updating/toast_notifications_helper.h +++ b/src/common/updating/toast_notifications_helper.h @@ -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); } } \ No newline at end of file diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index d2587fa7e0..7f7d66c01c 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -17,16 +17,19 @@ #include #include "VersionHelper.h" +#include 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 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 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 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(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(&fileInfo), &fileInfoLen)) + { + return std::nullopt; + } + return VersionHelper{ (fileInfo->dwFileVersionMS >> 16) & 0xffff, + (fileInfo->dwFileVersionMS >> 0) & 0xffff, + (fileInfo->dwFileVersionLS >> 16) & 0xffff }; + } } \ No newline at end of file diff --git a/src/common/updating/updating.h b/src/common/updating/updating.h index 53ae0e6b3e..26c670ace4 100644 --- a/src/common/updating/updating.h +++ b/src/common/updating/updating.h @@ -4,14 +4,17 @@ #include #include #include - #include +#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 get_msi_package_installed_path(); + std::optional get_installed_powertoys_version(); std::future uninstall_previous_msix_version_async(); @@ -30,5 +33,6 @@ namespace updating std::future check_new_version_available(); std::future download_update(); - constexpr inline std::wstring_view installer_filename_pattern = L"powertoyssetup"; + // non-localized + constexpr inline std::wstring_view INSTALLER_FILENAME_PATTERN = L"powertoyssetup"; } \ No newline at end of file diff --git a/src/common/updating/updating.vcxproj b/src/common/updating/updating.vcxproj index 7b6281a2c6..4b3f123424 100644 --- a/src/common/updating/updating.vcxproj +++ b/src/common/updating/updating.vcxproj @@ -118,6 +118,7 @@ + Pathcch.lib;Version.lib @@ -150,6 +151,7 @@ + Pathcch.lib;Version.lib @@ -169,12 +171,14 @@ + + diff --git a/src/common/updating/updating.vcxproj.filters b/src/common/updating/updating.vcxproj.filters index 9d39ecb0c7..dc5c1b089a 100644 --- a/src/common/updating/updating.vcxproj.filters +++ b/src/common/updating/updating.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + @@ -41,9 +44,11 @@ Source Files + + Source Files + - \ No newline at end of file diff --git a/src/modules/fancyzones/lib/WindowMoveHandler.cpp b/src/modules/fancyzones/lib/WindowMoveHandler.cpp index 12176c49eb..a450ff64b7 100644 --- a/src/modules/fancyzones/lib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/lib/WindowMoveHandler.cpp @@ -16,6 +16,11 @@ extern "C" IMAGE_DOS_HEADER __ImageBase; +namespace +{ + const wchar_t TOAST_TITLE[] = L"FancyZones"; +} + namespace WindowMoveHandlerUtils { bool IsCursorTypeIndicatingSizeEvent() @@ -399,7 +404,10 @@ void WindowMoveHandlerPrivate::WarnIfElevationIsRequired(HWND window) noexcept notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), L"https://aka.ms/powertoysDetectedElevatedHelp" }, notifications::link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), L"powertoys://cant_drag_elevated_disable/" } }; - notifications::show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), {}, std::move(actions)); + notifications::show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), + TOAST_TITLE, + {}, + std::move(actions)); warning_shown = true; } } diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 04ccc9bee6..c1abf7d333 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "update_state.h" #include "update_utils.h" @@ -39,16 +40,13 @@ namespace localized_strings { const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running."; const wchar_t OLDER_MSIX_UNINSTALLED[] = L"An older MSIX version of PowerToys was uninstalled."; - const wchar_t PT_UPDATE_MESSAGE_BOX_TITLE[] = L"PowerToys"; const wchar_t PT_UPDATE_MESSAGE_BOX_TEXT[] = L"PowerToys was updated and some components require Windows Explorer to restart. Do you want to restart Windows Explorer now?"; - } namespace { - const wchar_t MSI_VERSION_MUTEX_NAME[] = L"Local\\PowerToyRunMutex"; - const wchar_t MSIX_VERSION_MUTEX_NAME[] = L"Local\\PowerToyMSIXRunMutex"; const wchar_t PT_URI_PROTOCOL_SCHEME[] = L"powertoys://"; + const wchar_t APPLICATION_ID[] = L"Microsoft.PowerToysWin32"; } void chdir_current_executable() @@ -63,30 +61,20 @@ void chdir_current_executable() } } -wil::unique_mutex_nothrow create_runner_mutex(const bool msix_version) +inline wil::unique_mutex_nothrow create_msi_mutex() { - wchar_t username[UNLEN + 1]; - DWORD username_length = UNLEN + 1; - GetUserNameW(username, &username_length); - wil::unique_mutex_nothrow result{ CreateMutexW(nullptr, TRUE, (std::wstring(msix_version ? MSIX_VERSION_MUTEX_NAME : MSI_VERSION_MUTEX_NAME) + username).c_str()) }; - - return GetLastError() == ERROR_ALREADY_EXISTS ? wil::unique_mutex_nothrow{} : std::move(result); + return createAppMutex(POWERTOYS_MSI_MUTEX_NAME); } -wil::unique_mutex_nothrow create_msi_mutex() +inline wil::unique_mutex_nothrow create_msix_mutex() { - return create_runner_mutex(false); -} - -wil::unique_mutex_nothrow create_msix_mutex() -{ - return create_runner_mutex(true); + return createAppMutex(POWERTOYS_MSIX_MUTEX_NAME); } void open_menu_from_another_instance() { - HWND hwnd_main = FindWindow(L"PToyTrayIconWindow", NULL); - PostMessage(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, NULL); + const HWND hwnd_main = FindWindowW(L"PToyTrayIconWindow", nullptr); + PostMessageW(hwnd_main, WM_COMMAND, ID_SETTINGS_MENU_COMMAND, 0); } int runner(bool isProcessElevated) @@ -119,11 +107,12 @@ int runner(bool isProcessElevated) std::thread{ [] { if (updating::uninstall_previous_msix_version_async().get()) { - notifications::show_toast(localized_strings::OLDER_MSIX_UNINSTALLED); + notifications::show_toast(localized_strings::OLDER_MSIX_UNINSTALLED, L"PowerToys"); } } }.detach(); } + notifications::set_application_id(APPLICATION_ID); notifications::register_background_toast_handler(); chdir_current_executable(); @@ -140,7 +129,7 @@ int runner(bool isProcessElevated) L"modules/ColorPicker/ColorPicker.dll", }; - for (const auto & moduleSubdir : knownModules) + for (const auto& moduleSubdir : knownModules) { try { @@ -211,7 +200,6 @@ enum class toast_notification_handler_result exit_error }; - toast_notification_handler_result toast_notification_handler(const std::wstring_view param) { const std::wstring_view cant_drag_elevated_disable = L"cant_drag_elevated_disable/"; @@ -245,7 +233,7 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ else if (param.starts_with(download_and_install_update)) { std::wstring installer_filename = updating::download_update().get(); - + std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_CMDARG }; args += L' '; args += installer_filename; @@ -261,10 +249,10 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_ void RequestExplorerRestart() { - if (MessageBox(nullptr, - localized_strings::PT_UPDATE_MESSAGE_BOX_TEXT, - localized_strings::PT_UPDATE_MESSAGE_BOX_TITLE, - MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1) == IDYES) + if (MessageBoxW(nullptr, + localized_strings::PT_UPDATE_MESSAGE_BOX_TEXT, + L"PowerToys", + MB_ICONINFORMATION | MB_YESNO | MB_DEFBUTTON1) == IDYES) { RestartProcess(EXPLORER_PROCESS_NAME); }