[Auto-update] Auto-update improvements (#11356)

* [Updating] Refactor autoupdate mechanism to use Settings window buttons

* [Updating] Don't use underscores in update_state (#11029)

* [Updating] Rename action_runner to be consisent with accepted format

* [Updating] Make UpdateState values explicit

* [Setup] Set default bootstrapper log severity to debug

* [BugReport] Include all found bootstrapper logs

* [Setup] Use capital letter for ActionRunner

* [Updating] Simple UI to test UpdateState

* [Action Runner]  cleanup and coding style

* [BugReportTool] fix coding convension

* [Auto-update][PT Settings] Updated general page in the Settings (#11227)

* [Auto-update][PT Settings] File watcher monitoring UpdateState.json (#11282)

* Handle button clicks (#11288)

* [Updating] Document ActionRunner cmd flags

* [Auto-update][PT Settings] Updated UI (#11335)

* [Updating] Do not reset update state when msi cancellation detected

* [Updating] Directly launch update_now PT action instead of using custom URI scheme

* Checking for updates UI (#11354)

* [Updating] Fix cannotDownload state in action runner

* [Updating] Reset update state to CannotDownload if action runner encountered an error

* [Updating][PT Settings] downloading label, disable button in error state

* Changed error message

* [Updating rename CannotDownload to ErrorDownloading

* [Updating] Add trace logging for Check for updates callback

* [Updating][PT Settings] simplify downloading checks

* [Updating][PT Settings] Updated text labels

* [Updating][PT Settings] Retry to load settings if failed

* [Updating][PT Settings] Text fix

* [Updating][PT Settings] Installed version links removed

* [Updating][PT Settings] Error text updated

* [Updating][PT Settings] Show label after version checked

* [Updating][PT Settings] Text foreground fix

* [Updating][PT Settings] Clean up

* [Updating] Do not reset releasePageUrl in case of error/cancellation

* [Updating][PT Settings] fixed missing string

* [Updating][PT Settings] checked for updates state fix

Co-authored-by: yuyoyuppe <a.yuyoyuppe@gmail.com>
Co-authored-by: Andrey Nekrasov <yuyoyuppe@users.noreply.github.com>
Co-authored-by: Enrico Giordani <enrico.giordani@gmail.com>
This commit is contained in:
Seraphima Zykova 2021-05-21 13:32:34 +03:00 committed by GitHub
parent d55badf14a
commit b96b4fcb0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1065 additions and 761 deletions

View file

@ -64,7 +64,7 @@ build:
- from: 'x64/Release'
to: 'Build_Output'
include:
- 'action_runner.exe'
- 'PowerToys.ActionRunner.exe'
- 'BugReportTool\BugReportTool.exe'
- 'modules\ColorPicker\ColorPicker.dll'
- 'modules\ColorPicker\ColorPickerUI.dll'

View file

@ -100,7 +100,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageResizerUITest", "src\modules\imageresizer\tests\ImageResizerUITest.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action_runner\action_runner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ActionRunner", "src\ActionRunner\ActionRunner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}"
ProjectSection(ProjectDependencies) = postProject
{17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E}
EndProjectSection

View file

@ -70,54 +70,18 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve">
<value>An update to PowerToys is available.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_STARTED" xml:space="preserve">
<value>PowerToys download started.</value>
</data>
<data name="GITHUB_NEW_VERSION_READY_TO_INSTALL" xml:space="preserve">
<value>An update to PowerToys is ready to install.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR" xml:space="preserve">
<value>Error: couldn't download PowerToys installer. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_NOW" xml:space="preserve">
<value>Update now</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART" xml:space="preserve">
<value>At next launch</value>
</data>
<data name="UNINSTALLATION_UNKNOWN_ERROR" xml:space="preserve">
<value>Error: please uninstall the previous version of PowerToys manually.</value>
</data>
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
<value>Visit</value>
</data>
<data name="GITHUB_NEW_VERSION_MORE_INFO" xml:space="preserve">
<value>More info...</value>
</data>
<data name="GITHUB_NEW_VERSION_ABORT" xml:space="preserve">
<value>Abort</value>
</data>
<data name="GITHUB_NEW_VERSION_SNOOZE_TITLE" xml:space="preserve">
<value>Click Snooze to be reminded in:</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D" xml:space="preserve">
<value>1 day</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D" xml:space="preserve">
<value>5 days</value>
</data>
<data name="DOWNLOAD_IN_PROGRESS" xml:space="preserve">
<value>Downloading...</value>
</data>
<data name="DOWNLOAD_COMPLETE" xml:space="preserve">
<value>Download complete</value>
</data>
<data name="TOAST_TITLE" xml:space="preserve">
<value>PowerToys Update</value>
</data>
@ -144,16 +108,6 @@
</data>
<data name="NEW_VERSION_INSTALLATION_ERROR" xml:space="preserve">
<value>Couldn't install new PowerToys version.</value>
</data>
<data name="SNOOZE_BUTTON" xml:space="preserve">
<value>Snooze</value>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
<data name="NEWER_VERSION_ERROR" xml:space="preserve">
<value>A newer version is already installed.</value>

View file

@ -207,12 +207,8 @@ int Bootstrapper(HINSTANCE hInstance)
{
}
spdlog::level::level_enum severity = spdlog::level::off;
if (logLevel == "debug")
{
severity = spdlog::level::debug;
}
else if (logLevel == "error")
spdlog::level::level_enum severity = spdlog::level::debug;
if (logLevel == "error")
{
severity = spdlog::level::err;
}
@ -359,6 +355,7 @@ int Bootstrapper(HINSTANCE hInstance)
{
spdlog::error("Couldn't install the existing MSI package ({})", GetLastError());
ShowMessageBoxError(IDS_UNINSTALL_PREVIOUS_VERSION_ERROR);
return 1;
}
const bool installDotnet = !skipDotnetInstall;

View file

@ -71,7 +71,7 @@
<WixVariable Id="WixUILicenseRtf" Value="$(var.RepoDir)\installer\License.rtf" />
<Property Id="INSTALLSTARTMENUSHORTCUT" Value="1"/>
<Property Id="CREATESCHEDULEDTASK" Value="1"/>
<Property Id="WixShellExecTarget" Value="[#action_runner.exe]" />
<Property Id="WixShellExecTarget" Value="[#PowerToys_ActionRunner.exe]" />
<Property Id ="EXISTINGPOWERRENAMEEXTPATH">
<RegistrySearch Id="ExistingExtPath" Root="HKCR" Key="CLSID\{0440049F-D1DC-4E46-B27B-98393D79486B}\InprocServer32" Type="raw"/>
@ -324,8 +324,8 @@
<Component Id="BackgroundActivator_dll" Guid="23B25EE4-BCA2-45DF-BBCD-82FBDF01C5AB" Win64="yes">
<File Id="BackgroundActivatorDLL.dll" KeyPath="yes" Checksum="yes" />
</Component>
<Component Id="action_runner_exe" Guid="626ABB17-16F0-4007-9A58-6998724A5E14" Win64="yes">
<File Id="action_runner.exe" KeyPath="yes" Checksum="yes" />
<Component Id="PowerToys_ActionRunner_exe" Guid="626ABB17-16F0-4007-9A58-6998724A5E14" Win64="yes">
<File Id="PowerToys.ActionRunner.exe" KeyPath="yes" Checksum="yes" />
</Component>
<Component Id="License_rtf" Guid="3E5AE43B-CFB4-449B-A346-94CAAFF3312E" Win64="yes">
<File Source="$(var.RepoDir)\installer\License.rtf" Id="License.rtf" KeyPath="yes" />
@ -792,7 +792,7 @@
<ComponentRef Id="powertoys_exe" />
<ComponentRef Id="PowerToysStartMenuShortcut"/>
<ComponentRef Id="BackgroundActivator_dll" />
<ComponentRef Id="action_runner_exe" />
<ComponentRef Id="PowerToys_ActionRunner_exe" />
<ComponentRef Id="powertoys_toast_clsid" />
<ComponentRef Id="License_rtf" />
<ComponentRef Id="Notice_md" />

View file

@ -67,54 +67,18 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve">
<value>An update to PowerToys is available.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_STARTED" xml:space="preserve">
<value>PowerToys download started.</value>
</data>
<data name="GITHUB_NEW_VERSION_READY_TO_INSTALL" xml:space="preserve">
<value>An update to PowerToys is ready to install.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR" xml:space="preserve">
<value>Error: couldn't download PowerToys installer. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_NOW" xml:space="preserve">
<value>Update now</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART" xml:space="preserve">
<value>At next launch</value>
</data>
<data name="UNINSTALLATION_UNKNOWN_ERROR" xml:space="preserve">
<value>Error: please uninstall the previous version of PowerToys manually.</value>
</data>
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
<value>Visit</value>
</data>
<data name="GITHUB_NEW_VERSION_MORE_INFO" xml:space="preserve">
<value>More info...</value>
</data>
<data name="GITHUB_NEW_VERSION_ABORT" xml:space="preserve">
<value>Abort</value>
</data>
<data name="GITHUB_NEW_VERSION_SNOOZE_TITLE" xml:space="preserve">
<value>Click Snooze to be reminded in:</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D" xml:space="preserve">
<value>1 day</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D" xml:space="preserve">
<value>5 days</value>
</data>
<data name="DOWNLOAD_IN_PROGRESS" xml:space="preserve">
<value>Downloading...</value>
</data>
<data name="DOWNLOAD_COMPLETE" xml:space="preserve">
<value>Download complete</value>
</data>
<data name="TOAST_TITLE" xml:space="preserve">
<value>PowerToys Update</value>
</data>
@ -124,14 +88,4 @@
<data name="OFFER_UNINSTALL_MSI_TITLE" xml:space="preserve">
<value>PowerToys: uninstall previous version?</value>
</data>
<data name="SNOOZE_BUTTON" xml:space="preserve">
<value>Snooze</value>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</root>

View file

@ -1,3 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#define WIN32_LEAN_AND_MEAN
#include "Generated Files/resource.h"
@ -8,6 +12,7 @@
#include <string_view>
#include <common/updating/updating.h>
#include <common/updating/updateState.h>
#include <common/updating/installer.h>
#include <common/updating/http_client.h>
#include <common/updating/dotnet_installation.h>
@ -15,6 +20,7 @@
#include <common/utils/elevation.h>
#include <common/utils/process_path.h>
#include <common/utils/resources.h>
#include <common/utils/timeutil.h>
#include <common/SettingsAPI/settings_helpers.h>
@ -29,13 +35,16 @@
auto Strings = create_notifications_strings();
int uninstall_msi_action()
using namespace cmdArg;
int UninstallMsiAction()
{
const auto package_path = updating::get_msi_package_path();
if (package_path.empty())
{
return 0;
}
if (!updating::uninstall_msi_version(package_path, Strings))
{
return -1;
@ -55,44 +64,92 @@ int uninstall_msi_action()
namespace fs = std::filesystem;
std::optional<fs::path> copy_self_to_temp_dir()
std::optional<fs::path> CopySelfToTempDir()
{
std::error_code error;
auto dst_path = fs::temp_directory_path() / "action_runner.exe";
auto dst_path = fs::temp_directory_path() / "PowerToys.ActionRunner.exe";
fs::copy_file(get_module_filename(), dst_path, fs::copy_options::overwrite_existing, error);
if (error)
{
return std::nullopt;
}
return std::move(dst_path);
}
bool install_new_version_stage_1(const std::wstring_view installer_filename, const bool must_restart = false)
std::optional<fs::path> ObtainInstallerPath()
{
const fs::path installer{ updating::get_pending_updates_path() / installer_filename };
using namespace updating;
if (!fs::is_regular_file(installer))
auto state = UpdateState::read();
if (state.state == UpdateState::readyToDownload || state.state == UpdateState::errorDownloading)
{
const auto new_version_info = get_github_version_info_async(Strings).get();
if (!new_version_info)
{
Logger::error(L"Couldn't obtain github version info: {}", new_version_info.error());
return std::nullopt;
}
if (!std::holds_alternative<new_version_download_info>(*new_version_info))
{
Logger::error("Invoked with -update_now argument, but no update was available");
return std::nullopt;
}
auto downloaded_installer = download_new_version(std::get<new_version_download_info>(*new_version_info)).get();
if (!downloaded_installer)
{
Logger::error("Couldn't download new installer");
}
return downloaded_installer;
}
else if (state.state == UpdateState::readyToInstall)
{
fs::path installer{ get_pending_updates_path() / state.downloadedInstallerFilename };
if (fs::is_regular_file(installer))
{
return std::move(installer);
}
else
{
Logger::error(L"Couldn't find a downloaded installer {}", installer.native());
return std::nullopt;
}
}
else
{
Logger::error("Invoked with -update_now argument, but update state was invalid");
return std::nullopt;
}
}
bool InstallNewVersionStage1()
{
const auto installer = ObtainInstallerPath();
if (!installer)
{
return false;
}
if (auto copy_in_temp = copy_self_to_temp_dir())
if (auto copy_in_temp = CopySelfToTempDir())
{
// detect if PT was running
// Detect if PT was running
const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr);
const bool launch_powertoys = must_restart || pt_main_window != nullptr;
const bool launch_powertoys = pt_main_window != nullptr;
if (pt_main_window != nullptr)
{
SendMessageW(pt_main_window, WM_CLOSE, 0, 0);
}
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2_CMDARG };
std::wstring arguments{ UPDATE_NOW_LAUNCH_STAGE2 };
arguments += L" \"";
arguments += installer.c_str();
arguments += installer->c_str();
arguments += L"\" \"";
arguments += get_module_folderpath();
arguments += L"\" ";
arguments += launch_powertoys ? UPDATE_STAGE2_RESTART_PT_CMDARG : UPDATE_STAGE2_DONT_START_PT_CMDARG;
arguments += launch_powertoys ? UPDATE_STAGE2_RESTART_PT : UPDATE_STAGE2_DONT_START_PT;
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC };
sei.lpFile = copy_in_temp->c_str();
@ -107,7 +164,7 @@ bool install_new_version_stage_1(const std::wstring_view installer_filename, con
}
}
bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view install_path, bool launch_powertoys)
bool InstallNewVersionStage2(std::wstring installer_path, std::wstring_view install_path, bool launch_powertoys)
{
std::transform(begin(installer_path), end(installer_path), begin(installer_path), ::towlower);
@ -134,25 +191,36 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view
{
parameters += L"--no_start_pt";
}
sei.lpParameters = parameters.c_str();
success = ShellExecuteExW(&sei) == TRUE;
// Wait for the install completion
if (success)
{
WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exitCode = 0;
GetExitCodeProcess(sei.hProcess, &exitCode);
success = exitCode == 0;
CloseHandle(sei.hProcess);
}
}
std::error_code _;
fs::remove(installer_path, _);
if (!success)
{
return false;
}
std::error_code _;
fs::remove(installer_path, _);
UpdateState::store([&](UpdateState& state) {
state = {};
state.githubUpdateLastCheckedDate.emplace(timeutil::now());
state.state = UpdateState::upToDate;
});
if (launch_powertoys)
{
std::wstring new_pt_path{ install_path };
@ -164,6 +232,7 @@ bool install_new_version_stage_2(std::wstring installer_path, std::wstring_view
sei.lpParameters = UPDATE_REPORT_SUCCESS;
return ShellExecuteExW(&sei) == TRUE;
}
return true;
}
@ -175,13 +244,14 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
return 1;
}
std::wstring_view action{ args[1] };
std::filesystem::path logFilePath(PTSettingsHelper::get_root_save_folder_location());
logFilePath.append(LogSettings::actionRunnerLogPath);
Logger::init(LogSettings::actionRunnerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location());
if (action == RUN_NONELEVATED_CMDARG)
if (action == RUN_NONELEVATED)
{
int nextArg = 2;
@ -227,7 +297,6 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
run_same_elevation(target.data(), params, pidBuffer);
// cleanup
if (!pidFile.empty())
{
if (pidBuffer)
@ -243,24 +312,36 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
}
}
}
else if (action == UNINSTALL_MSI_CMDARG)
else if (action == UNINSTALL_MSI)
{
return uninstall_msi_action();
return UninstallMsiAction();
}
else if (action == UPDATE_NOW_LAUNCH_STAGE1_CMDARG)
else if (action == UPDATE_NOW_LAUNCH_STAGE1)
{
std::wstring_view installerFilename{ args[2] };
return !install_new_version_stage_1(installerFilename);
const bool failed = !InstallNewVersionStage1();
if (failed)
{
UpdateState::store([&](UpdateState& state) {
state.downloadedInstallerFilename = {};
state.githubUpdateLastCheckedDate.emplace(timeutil::now());
state.state = UpdateState::errorDownloading;
});
}
return failed;
}
else if (action == UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG)
{
std::wstring_view installerFilename{ args[2] };
return !install_new_version_stage_1(installerFilename, true);
}
else if (action == UPDATE_NOW_LAUNCH_STAGE2_CMDARG)
else if (action == UPDATE_NOW_LAUNCH_STAGE2)
{
using namespace std::string_view_literals;
return !install_new_version_stage_2(args[2], args[3], args[4] == std::wstring_view{ UPDATE_STAGE2_RESTART_PT_CMDARG });
const bool failed = !InstallNewVersionStage2(args[2], args[3], args[4] == std::wstring_view{ UPDATE_STAGE2_RESTART_PT });
if (failed)
{
UpdateState::store([&](UpdateState& state) {
state.downloadedInstallerFilename = {};
state.githubUpdateLastCheckedDate.emplace(timeutil::now());
state.state = UpdateState::errorDownloading;
});
}
return failed;
}
return 0;

View file

@ -2,13 +2,13 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h action_runner.base.rc action_runner.rc" />
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h actionRunner.base.rc actionRunner.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}</ProjectGuid>
<RootNamespace>action_runner</RootNamespace>
<ProjectName>action_runner</ProjectName>
<RootNamespace>actionRunner</RootNamespace>
<ProjectName>PowerToys.ActionRunner</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\deps\expected.props" />
@ -20,16 +20,7 @@
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ImportGroup>
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
@ -44,7 +35,7 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="action_runner.cpp" />
<ClCompile Include="ActionRunner.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common\logger\logger.vcxproj">
@ -61,12 +52,11 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\runner\updating.h" />
<ClInclude Include="resource.h" />
</ItemGroup>
<ItemGroup>
<None Include="action_runner.base.rc" />
<ResourceCompile Include="Generated Files/action_runner.rc" />
<None Include="ActionRunner.base.rc" />
<ResourceCompile Include="Generated Files\ActionRunner.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@ -83,4 +73,4 @@
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.200729.8\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>
</Project>

View file

@ -1,11 +1,11 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by action_runner.rc
// Used by PowerToys.ActionRunner.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys ActionRunner"
#define INTERNAL_NAME "action_runner"
#define ORIGINAL_FILENAME "action_runner.exe"
#define INTERNAL_NAME "PowerToys.ActionRunner"
#define ORIGINAL_FILENAME "PowerToys.ActionRunner.exe"

View file

@ -57,6 +57,5 @@ namespace notifications
}
RegCloseKey(key);
return timeutil::diff::in_days(timeutil::now(), last_disabled_time) < disable_interval_in_days;
return false;
}
}

View file

@ -81,7 +81,7 @@ namespace updating
}
catch (...)
{
updating::notifications::show_uninstallation_error(strings);
MessageBoxW(nullptr, strings.UNINSTALLATION_UNKNOWN_ERROR.c_str(), strings.NOTIFICATION_TITLE.c_str(), MB_OK | MB_ICONERROR);
}
}
return false;

View file

@ -14,7 +14,7 @@ namespace updating
namespace notifications
{
using namespace ::notifications;
std::wstring current_version_to_next_version(const updating::new_version_download_info& info)
std::wstring current_version_to_next_version(const new_version_download_info& info)
{
auto current_version_to_next_version = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring();
current_version_to_next_version += L" -> ";
@ -22,15 +22,7 @@ namespace updating
return current_version_to_next_version;
}
void show_unavailable(const notifications::strings& strings, std::wstring reason)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
show_toast(std::move(reason), strings.TOAST_TITLE, std::move(toast_params));
}
void show_available(const updating::new_version_download_info& info, const notifications::strings& strings)
void show_new_version_available(const new_version_download_info& info, const strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
@ -40,106 +32,30 @@ namespace updating
contents += current_version_to_next_version(info);
show_toast_with_activations(std::move(contents),
strings.TOAST_TITLE,
strings.NOTIFICATION_TITLE,
{},
{ link_button{ strings.GITHUB_NEW_VERSION_UPDATE_NOW,
L"powertoys://download_and_install_update/" },
L"powertoys://update_now/" },
link_button{ strings.GITHUB_NEW_VERSION_MORE_INFO,
info.release_page_uri.ToString().c_str() } },
L"powertoys://open_settings/" } },
std::move(toast_params));
}
void show_download_start(const updating::new_version_download_info& info, const notifications::strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version.toWstring() };
progress_title += L' ';
progress_title += strings.DOWNLOAD_IN_PROGRESS;
progress_bar_params.progress_title = progress_title;
progress_bar_params.progress = .0f;
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false, std::move(progress_bar_params) };
show_toast_with_activations(strings.GITHUB_NEW_VERSION_DOWNLOAD_STARTED,
strings.TOAST_TITLE,
{},
{},
std::move(toast_params));
}
void show_visit_github(const updating::new_version_download_info& info, const notifications::strings& strings)
void show_open_settings_for_update(const strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
std::wstring contents = strings.GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
contents += L'\n';
contents += current_version_to_next_version(info);
show_toast_with_activations(std::move(contents),
strings.TOAST_TITLE,
std::vector<action_t> actions = {
link_button{ strings.GITHUB_NEW_VERSION_MORE_INFO,
L"powertoys://open_settings/" },
};
show_toast_with_activations(strings.GITHUB_NEW_VERSION_AVAILABLE,
strings.NOTIFICATION_TITLE,
{},
{ link_button{ strings.GITHUB_NEW_VERSION_VISIT,
info.release_page_uri.ToString().c_str() } },
std::move(actions),
std::move(toast_params));
}
void show_install_error(const updating::new_version_download_info& info, const notifications::strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
std::wstring contents = strings.GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR;
contents += L'\n';
contents += current_version_to_next_version(info);
show_toast_with_activations(std::move(contents),
strings.TOAST_TITLE,
{},
{ link_button{ strings.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, const notifications::strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
std::wstring new_version_ready{ strings.GITHUB_NEW_VERSION_READY_TO_INSTALL };
new_version_ready += L'\n';
new_version_ready += current_version_to_next_version(info);
show_toast_with_activations(std::move(new_version_ready),
strings.TOAST_TITLE,
{},
{ link_button{ strings.GITHUB_NEW_VERSION_UPDATE_NOW,
L"powertoys://update_now/" + info.installer_filename },
link_button{ strings.GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART,
L"powertoys://schedule_update/" + info.installer_filename },
snooze_button{
strings.GITHUB_NEW_VERSION_SNOOZE_TITLE,
{ { strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D, 24 * 60 },
{ strings.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D, 120 * 60 } },
strings.SNOOZE_BUTTON } },
std::move(toast_params));
}
void show_uninstallation_error(const notifications::strings& strings)
{
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
show_toast(strings.UNINSTALLATION_UNKNOWN_ERROR, strings.TOAST_TITLE);
}
void update_download_progress(const updating::new_version_download_info& info, float progress, const notifications::strings& strings)
{
progress_bar_params progress_bar_params;
std::wstring progress_title{ info.version.toWstring() };
progress_title += L' ';
progress_title += progress < 1 ? strings.DOWNLOAD_IN_PROGRESS : strings.DOWNLOAD_COMPLETE;
progress_bar_params.progress_title = progress_title;
progress_bar_params.progress = progress;
update_toast_progress_bar(UPDATING_PROCESS_TOAST_TAG, progress_bar_params);
}
}
}

View file

@ -10,71 +10,31 @@ namespace updating
{
struct strings
{
std::wstring DOWNLOAD_COMPLETE;
std::wstring DOWNLOAD_IN_PROGRESS;
std::wstring GITHUB_NEW_VERSION_ABORT;
std::wstring GITHUB_NEW_VERSION_AVAILABLE;
std::wstring GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
std::wstring GITHUB_NEW_VERSION_CHECK_ERROR;
std::wstring GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR;
std::wstring GITHUB_NEW_VERSION_DOWNLOAD_STARTED;
std::wstring GITHUB_NEW_VERSION_MORE_INFO;
std::wstring GITHUB_NEW_VERSION_READY_TO_INSTALL;
std::wstring GITHUB_NEW_VERSION_SNOOZE_TITLE;
std::wstring GITHUB_NEW_VERSION_UP_TO_DATE;
std::wstring GITHUB_NEW_VERSION_UPDATE_NOW;
std::wstring GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART;
std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D;
std::wstring GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D;
std::wstring GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR;
std::wstring GITHUB_NEW_VERSION_VISIT;
std::wstring OFFER_UNINSTALL_MSI;
std::wstring OFFER_UNINSTALL_MSI_TITLE;
std::wstring SNOOZE_BUTTON;
std::wstring TOAST_TITLE;
std::wstring NOTIFICATION_TITLE;
std::wstring UNINSTALLATION_UNKNOWN_ERROR;
};
void show_unavailable(const notifications::strings& strings, std::wstring reason);
void show_available(const updating::new_version_download_info& info, const strings&);
void show_download_start(const updating::new_version_download_info& info, const strings&);
void show_visit_github(const updating::new_version_download_info& info, const strings&);
void show_install_error(const updating::new_version_download_info& info, const strings&);
void show_version_ready(const updating::new_version_download_info& info, const strings&);
void show_uninstallation_error(const notifications::strings& strings);
void update_download_progress(const updating::new_version_download_info& info, float progress, const notifications::strings& strings);
void show_new_version_available(const new_version_download_info& info, const strings& strings);
void show_open_settings_for_update(const strings& strings);
}
}
#define create_notifications_strings() \
::updating::notifications::strings \
{ \
.DOWNLOAD_COMPLETE = GET_RESOURCE_STRING(IDS_DOWNLOAD_COMPLETE), \
.DOWNLOAD_IN_PROGRESS = GET_RESOURCE_STRING(IDS_DOWNLOAD_IN_PROGRESS), \
.GITHUB_NEW_VERSION_ABORT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_ABORT), \
.GITHUB_NEW_VERSION_AVAILABLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE), \
.GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT), \
.GITHUB_NEW_VERSION_CHECK_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_CHECK_ERROR), \
.GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR), \
.GITHUB_NEW_VERSION_DOWNLOAD_STARTED = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_DOWNLOAD_STARTED), \
.GITHUB_NEW_VERSION_MORE_INFO = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_MORE_INFO), \
.GITHUB_NEW_VERSION_READY_TO_INSTALL = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_READY_TO_INSTALL), \
.GITHUB_NEW_VERSION_SNOOZE_TITLE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_SNOOZE_TITLE), \
.GITHUB_NEW_VERSION_UP_TO_DATE = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UP_TO_DATE), \
.GITHUB_NEW_VERSION_UPDATE_NOW = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_NOW), \
.GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D), \
.GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D), \
.GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR), \
.GITHUB_NEW_VERSION_VISIT = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_VISIT), \
.OFFER_UNINSTALL_MSI = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI), \
.OFFER_UNINSTALL_MSI_TITLE = GET_RESOURCE_STRING(IDS_OFFER_UNINSTALL_MSI_TITLE), \
.SNOOZE_BUTTON = GET_RESOURCE_STRING(IDS_SNOOZE_BUTTON), \
.TOAST_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \
.NOTIFICATION_TITLE = GET_RESOURCE_STRING(IDS_TOAST_TITLE), \
.UNINSTALLATION_UNKNOWN_ERROR = GET_RESOURCE_STRING(IDS_UNINSTALLATION_UNKNOWN_ERROR) \
}

View file

@ -27,9 +27,11 @@
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Networking.Connectivity.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Management.Deployment.h>
#include <winrt/Windows.System.h>
#include <wil/resource.h>
#endif //PCH_H

View file

@ -0,0 +1,70 @@
#include "pch.h"
#include "updateState.h"
#include <common/utils/json.h>
#include <common/utils/timeutil.h>
#include <common/SettingsAPI/settings_helpers.h>
namespace
{
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\UpdateState.json";
const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToysRunnerUpdateStateMutex";
}
UpdateState deserialize(const json::JsonObject& json)
{
UpdateState result;
result.state = static_cast<UpdateState::State>(json.GetNamedNumber(L"state", UpdateState::upToDate));
result.releasePageUrl = json.GetNamedString(L"releasePageUrl", L"");
result.githubUpdateLastCheckedDate = timeutil::from_string(json.GetNamedString(L"githubUpdateLastCheckedDate", L"invalid").c_str());
result.downloadedInstallerFilename = json.GetNamedString(L"downloadedInstallerFilename", L"");
return result;
}
json::JsonObject serialize(const UpdateState& state)
{
json::JsonObject json;
if (state.githubUpdateLastCheckedDate.has_value())
{
json.SetNamedValue(L"githubUpdateLastCheckedDate", json::value(timeutil::to_string(*state.githubUpdateLastCheckedDate)));
}
json.SetNamedValue(L"releasePageUrl", json::value(state.releasePageUrl));
json.SetNamedValue(L"state", json::value(static_cast<double>(state.state)));
json.SetNamedValue(L"downloadedInstallerFilename", json::value(state.downloadedInstallerFilename));
return json;
}
UpdateState UpdateState::read()
{
const auto filename = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(filename);
}
return json ? deserialize(*json) : UpdateState{};
}
void UpdateState::store(std::function<void(UpdateState&)> stateModifier)
{
const auto filename = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(filename);
UpdateState state;
if (json)
{
state = deserialize(*json);
}
stateModifier(state);
json.emplace(serialize(state));
json::to_file(filename, *json);
}
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <ctime>
#include <optional>
#include <functional>
// All fields must be default-initialized
struct UpdateState
{
enum State
{
upToDate = 0,
errorDownloading = 1,
readyToDownload = 2,
readyToInstall = 3
} state = upToDate;
std::wstring releasePageUrl;
std::optional<std::time_t> githubUpdateLastCheckedDate;
std::wstring downloadedInstallerFilename;
// To prevent concurrent modification of the file, we enforce this interface, which locks the file while
// the state_modifier is active.
static void store(std::function<void(UpdateState&)> stateModifier);
static UpdateState read();
};

View file

@ -15,12 +15,27 @@ namespace // Strings in this namespace should not be localized
{
const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases";
const wchar_t LOCAL_BUILD_ERROR[] = L"Local build cannot be updated";
const wchar_t NETWORK_ERROR[] = L"Network error";
const size_t MAX_DOWNLOAD_ATTEMPTS = 3;
}
namespace updating
{
Uri extract_release_page_url(const json::JsonObject& release_object)
{
try
{
return Uri{ release_object.GetNamedString(L"html_url") };
}
catch (...)
{
}
return nullptr;
}
std::optional<VersionHelper> extract_version_from_release_object(const json::JsonObject& release_object)
{
try
@ -66,7 +81,7 @@ namespace updating
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
if (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
{
co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR);
co_return nonstd::make_unexpected(LOCAL_BUILD_ERROR);
}
try
@ -109,24 +124,16 @@ namespace updating
co_return version_up_to_date{};
}
Uri release_page_url{ release_object.GetNamedString(L"html_url") };
auto installer_download_url = extract_installer_asset_download_info(release_object);
co_return new_version_download_info{ std::move(release_page_url),
auto [installer_download_url, installer_filename] = extract_installer_asset_download_info(release_object);
co_return new_version_download_info{ extract_release_page_url(release_object),
std::move(github_version),
std::move(installer_download_url.first),
std::move(installer_download_url.second) };
std::move(installer_download_url),
std::move(installer_filename) };
}
catch (...)
{
}
co_return nonstd::make_unexpected(strings.GITHUB_NEW_VERSION_CHECK_ERROR);
}
bool could_be_costly_connection()
{
using namespace winrt::Windows::Networking::Connectivity;
ConnectionProfile internetConnectionProfile = NetworkInformation::GetInternetConnectionProfile();
return internetConnectionProfile.IsWwanConnectionProfile();
co_return nonstd::make_unexpected(NETWORK_ERROR);
}
std::filesystem::path get_pending_updates_path()
@ -136,86 +143,40 @@ namespace updating
return { std::move(path_str) };
}
std::filesystem::path create_download_path()
std::optional<std::filesystem::path> create_download_path()
{
auto installer_download_dst = get_pending_updates_path();
std::error_code _;
std::filesystem::create_directories(installer_download_dst, _);
return installer_download_dst;
auto installer_download_path = get_pending_updates_path();
std::error_code ec;
std::filesystem::create_directories(installer_download_path, ec);
return !ec ? std::optional{ installer_download_path } : std::nullopt;
}
std::future<bool> try_autoupdate(const bool download_updates_automatically, const notifications::strings& strings)
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version)
{
const auto version_check_result = co_await get_github_version_info_async(strings);
if (!version_check_result)
auto installer_download_path = create_download_path();
if (!installer_download_path)
{
co_return false;
co_return std::nullopt;
}
if (std::holds_alternative<version_up_to_date>(*version_check_result))
{
co_return true;
}
const auto new_version = std::get<new_version_download_info>(*version_check_result);
if (download_updates_automatically && !could_be_costly_connection())
*installer_download_path /= new_version.installer_filename;
bool download_success = false;
for (size_t i = 0; i < MAX_DOWNLOAD_ATTEMPTS; ++i)
{
auto installer_download_dst = create_download_path() / new_version.installer_filename;
bool download_success = false;
for (size_t i = 0; i < MAX_DOWNLOAD_ATTEMPTS; ++i)
try
{
try
{
http::HttpClient client;
co_await client.download(new_version.installer_download_url, installer_download_dst);
download_success = true;
break;
}
catch (...)
{
// reattempt to download or do nothing
}
http::HttpClient client;
co_await client.download(new_version.installer_download_url, *installer_download_path);
download_success = true;
break;
}
if (!download_success)
catch (...)
{
updating::notifications::show_install_error(new_version, strings);
co_return false;
// reattempt to download or do nothing
}
updating::notifications::show_version_ready(new_version, strings);
}
else
{
updating::notifications::show_visit_github(new_version, strings);
}
co_return true;
co_return download_success ? installer_download_path : std::nullopt;
}
std::future<std::wstring> download_update(const notifications::strings& strings)
{
const auto version_check_result = co_await get_github_version_info_async(strings);
if (!version_check_result || std::holds_alternative<version_up_to_date>(*version_check_result))
{
co_return L"";
}
const auto new_version = std::get<new_version_download_info>(*version_check_result);
auto installer_download_dst = create_download_path() / new_version.installer_filename;
updating::notifications::show_download_start(new_version, strings);
try
{
auto progressUpdateHandle = [&](float progress) {
updating::notifications::update_download_progress(new_version, progress, strings);
};
http::HttpClient client;
co_await client.download(new_version.installer_download_url, installer_download_dst, progressUpdateHandle);
}
catch (...)
{
updating::notifications::show_install_error(new_version, strings);
co_return L"";
}
co_return new_version.installer_filename;
}
}

View file

@ -14,9 +14,9 @@
namespace updating
{
using winrt::Windows::Foundation::Uri;
struct version_up_to_date {};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
struct version_up_to_date
{
};
struct new_version_download_info
{
Uri release_page_uri = nullptr;
@ -24,11 +24,10 @@ namespace updating
Uri installer_download_url = nullptr;
std::wstring installer_filename;
};
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
// Returns whether the update check has succeeded
std::future<bool> try_autoupdate(const bool download_updates_automatically, const notifications::strings&);
std::future<std::optional<std::filesystem::path>> download_new_version(const new_version_download_info& new_version);
std::filesystem::path get_pending_updates_path();
std::future<std::wstring> download_update(const notifications::strings&);
std::future<nonstd::expected<github_version_info, std::wstring>> get_github_version_info_async(const notifications::strings& strings, const bool prerelease = false);
// non-localized

View file

@ -40,6 +40,7 @@
<ClInclude Include="installer.h" />
<ClInclude Include="notifications.h" />
<ClInclude Include="updating.h" />
<ClInclude Include="updateState.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="winstore.h" />
</ItemGroup>
@ -49,6 +50,7 @@
<ClCompile Include="installer.cpp" />
<ClCompile Include="notifications.cpp" />
<ClCompile Include="updating.cpp" />
<ClCompile Include="updateState.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
</ClCompile>

View file

@ -225,7 +225,7 @@ public:
params += L" -powerToysPid " + std::to_wstring(powertoys_pid) + L" ";
params += L"--centralized-kb-hook ";
action_runner_path += L"\\action_runner.exe";
action_runner_path += L"\\PowerToys.ActionRunner.exe";
// Set up the shared file from which to retrieve the PID of PowerLauncher
HANDLE hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(DWORD), POWER_LAUNCHER_PID_SHARED_FILE);
if (!hMapFile)

View file

@ -94,54 +94,18 @@
<data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve">
<value>An update to PowerToys is available.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_STARTED" xml:space="preserve">
<value>PowerToys download started.</value>
</data>
<data name="GITHUB_NEW_VERSION_READY_TO_INSTALL" xml:space="preserve">
<value>An update to PowerToys is ready to install.</value>
</data>
<data name="GITHUB_NEW_VERSION_DOWNLOAD_INSTALL_ERROR" xml:space="preserve">
<value>Error: couldn't download PowerToys installer. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_NOW" xml:space="preserve">
<value>Update now</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_AFTER_RESTART" xml:space="preserve">
<value>At next launch</value>
</data>
<data name="UNINSTALLATION_UNKNOWN_ERROR" xml:space="preserve">
<value>Error: please uninstall the previous version of PowerToys manually.</value>
</data>
<data name="GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT" xml:space="preserve">
<value>An update to PowerToys is available. Visit our GitHub page to update.</value>
</data>
<data name="GITHUB_NEW_VERSION_UP_TO_DATE" xml:space="preserve">
<value>PowerToys is up to date.</value>
</data>
<data name="GITHUB_NEW_VERSION_VISIT" xml:space="preserve">
<value>Visit</value>
</data>
<data name="GITHUB_NEW_VERSION_MORE_INFO" xml:space="preserve">
<value>More info...</value>
</data>
<data name="GITHUB_NEW_VERSION_ABORT" xml:space="preserve">
<value>Abort</value>
</data>
<data name="GITHUB_NEW_VERSION_SNOOZE_TITLE" xml:space="preserve">
<value>Click Snooze to be reminded in:</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_1D" xml:space="preserve">
<value>1 day</value>
</data>
<data name="GITHUB_NEW_VERSION_UPDATE_SNOOZE_5D" xml:space="preserve">
<value>5 days</value>
</data>
<data name="DOWNLOAD_IN_PROGRESS" xml:space="preserve">
<value>Downloading...</value>
</data>
<data name="DOWNLOAD_COMPLETE" xml:space="preserve">
<value>Download complete</value>
</data>
<data name="TOAST_TITLE" xml:space="preserve">
<value>PowerToys Update</value>
</data>
@ -151,9 +115,6 @@
<data name="OFFER_UNINSTALL_MSI_TITLE" xml:space="preserve">
<value>PowerToys: uninstall previous version?</value>
</data>
<data name="SNOOZE_BUTTON" xml:space="preserve">
<value>Snooze</value>
</data>
<data name="SETTINGS_MENU_TEXT" xml:space="preserve">
<value>Settings</value>
</data>
@ -167,11 +128,4 @@
<data name="BUGREPORT_SUCCESS" xml:space="preserve">
<value>Bug report .zip file has been created on your Desktop.</value>
</data>
<data name="GITHUB_NEW_VERSION_USING_LOCAL_BUILD_ERROR" xml:space="preserve">
<value>Updating from a local build is not supported.</value>
<comment>User cannot autoupdate from a locally-built PowerToys version</comment>
</data>
<data name="GITHUB_NEW_VERSION_CHECK_ERROR" xml:space="preserve">
<value>Failed to connect to the server. Check your network connection or retry later.</value>
</data>
</root>

View file

@ -17,7 +17,7 @@ SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline)
action_runner_path = get_module_folderpath();
}
action_runner_path += L"\\action_runner.exe";
action_runner_path += L"\\PowerToys.ActionRunner.exe";
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS };
sei.lpFile = action_runner_path.c_str();

View file

@ -5,15 +5,19 @@
SHELLEXECUTEINFOW launch_action_runner(const wchar_t* cmdline);
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG = L"-update_now_and_start_pt";
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1_CMDARG = L"-update_now";
namespace cmdArg
{
// Starts first stage of the PowerToys auto-update process, which involves copying action runner to a temp path and
// restarting it from there, so it doesn't interfere with the installation process.
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE1 = L"-update_now";
// Stage 2 consists of starting the installer and optionally launching newly installed PowerToys binary.
// That's indicated by the following 2 flags.
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE2 = L"-update_now_stage_2";
const inline wchar_t* UPDATE_STAGE2_RESTART_PT = L"restart";
const inline wchar_t* UPDATE_STAGE2_DONT_START_PT = L"dont_start";
const inline wchar_t* UPDATE_NOW_LAUNCH_STAGE2_CMDARG = L"-update_now_stage_2";
const inline wchar_t* UPDATE_STAGE2_RESTART_PT_CMDARG = L"restart";
const inline wchar_t* UPDATE_STAGE2_DONT_START_PT_CMDARG = L"dont_start";
const inline wchar_t * UNINSTALL_MSI_CMDARG = L"-uninstall_msi";
const inline wchar_t * RUN_NONELEVATED_CMDARG = L"-run-non-elevated";
const inline wchar_t* UPDATE_REPORT_SUCCESS = L"-report_update_success";
const inline wchar_t* UNINSTALL_MSI = L"-uninstall_msi";
const inline wchar_t* RUN_NONELEVATED = L"-run-non-elevated";
const inline wchar_t* UPDATE_REPORT_SUCCESS = L"-report_update_success";
}

View file

@ -17,13 +17,13 @@
#include <common/notifications/dont_show_again.h>
#include <common/updating/installer.h>
#include <common/updating/updating.h>
#include <common/updating/updateState.h>
#include <common/utils/appMutex.h>
#include <common/utils/elevation.h>
#include <common/utils/processApi.h>
#include <common/utils/resources.h>
#include <common/winstore/winstore.h>
#include "update_state.h"
#include "update_utils.h"
#include "action_runner_utils.h"
@ -96,7 +96,7 @@ void debug_verify_launcher_assets()
const auto assetPath = powertoysRoot / asset;
if (!fs::is_regular_file(assetPath))
{
Logger::error("{} couldn't be found.", assetPath.string());
Logger::error("{} couldn't be found.", assetPath.string());
}
}
}
@ -125,7 +125,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe)
debug_verify_launcher_assets();
std::thread{ [] {
github_update_worker();
periodic_update_worker();
} }.detach();
if (winstore::running_as_packaged())
@ -227,7 +227,7 @@ SpecialMode should_run_in_special_mode(const int n_cmd_args, LPWSTR* cmd_arg_lis
{
return SpecialMode::ToastNotificationHandler;
}
else if (n_cmd_args == 2 && !wcscmp(UPDATE_REPORT_SUCCESS, cmd_arg_list[i]))
else if (n_cmd_args == 2 && !wcscmp(cmdArg::UPDATE_REPORT_SUCCESS, cmd_arg_list[i]))
{
return SpecialMode::ReportSuccessfulUpdate;
}
@ -252,9 +252,7 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_
{
const std::wstring_view cant_drag_elevated_disable = L"cant_drag_elevated_disable/";
const std::wstring_view couldnt_toggle_powerpreview_modules_disable = L"couldnt_toggle_powerpreview_modules_disable/";
const std::wstring_view download_and_install_update = L"download_and_install_update/";
const std::wstring_view open_settings = L"open_settings/";
const std::wstring_view schedule_update = L"schedule_update/";
const std::wstring_view update_now = L"update_now/";
if (param == cant_drag_elevated_disable)
@ -263,46 +261,10 @@ toast_notification_handler_result toast_notification_handler(const std::wstring_
}
else if (param.starts_with(update_now))
{
std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_CMDARG };
const auto installerFilename = param.data() + size(update_now);
args += L' ';
args += installerFilename;
std::wstring args{ cmdArg::UPDATE_NOW_LAUNCH_STAGE1 };
launch_action_runner(args.c_str());
return toast_notification_handler_result::exit_success;
}
else if (param.starts_with(schedule_update))
{
const auto installerFilename = param.data() + size(schedule_update);
UpdateState::store([=](UpdateState& state) {
state.pending_update = true;
state.pending_installer_filename = installerFilename;
});
return toast_notification_handler_result::exit_success;
}
else if (param.starts_with(download_and_install_update))
{
try
{
std::wstring installer_filename = updating::download_update(Strings).get();
std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_CMDARG };
args += L' ';
args += installer_filename;
launch_action_runner(args.c_str());
return toast_notification_handler_result::exit_success;
}
catch (...)
{
MessageBoxW(nullptr,
GET_RESOURCE_STRING(IDS_DOWNLOAD_UPDATE_ERROR).c_str(),
L"PowerToys",
MB_ICONWARNING | MB_OK);
return toast_notification_handler_result::exit_error;
}
}
else if (param == couldnt_toggle_powerpreview_modules_disable)
{
return notifications::disable_toast(notifications::PreviewModulesDontShowAgainRegistryPath) ? toast_notification_handler_result::exit_success : toast_notification_handler_result::exit_error;
@ -334,11 +296,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
L"(ML;;NX;;;LW)"; // Integrity label on No execute up for Low mandatory level
initializeCOMSecurity(securityDescriptor);
if (launch_pending_update())
{
return 0;
}
int n_cmd_args = 0;
LPWSTR* cmd_arg_list = CommandLineToArgvW(GetCommandLineW(), &n_cmd_args);
switch (should_run_in_special_mode(n_cmd_args, cmd_arg_list))

View file

@ -28,6 +28,7 @@
#include <ProjectTelemetry.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.Networking.Connectivity.h>
#include <winrt/Windows.Storage.h>
#include <wil/resource.h>

View file

@ -63,7 +63,6 @@
<ClCompile Include="tray_icon.cpp" />
<ClCompile Include="unhandled_exception_handler.cpp" />
<ClCompile Include="update_utils.cpp" />
<ClCompile Include="update_state.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="action_runner_utils.h" />
@ -74,7 +73,6 @@
<ClInclude Include="centralized_kb_hook.h" />
<ClInclude Include="settings_telemetry.h" />
<ClInclude Include="update_utils.h" />
<ClInclude Include="update_state.h" />
<ClInclude Include="powertoy_module.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="restart_elevated.h" />

View file

@ -27,9 +27,6 @@
<ClCompile Include="restart_elevated.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="update_state.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="update_utils.cpp">
<Filter>Utils</Filter>
</ClCompile>
@ -75,9 +72,6 @@
<ClInclude Include="restart_elevated.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="update_state.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="update_utils.h">
<Filter>Utils</Filter>
</ClInclude>

View file

@ -8,9 +8,7 @@
#include <common/interop/two_way_pipe_message_ipc.h>
#include "tray_icon.h"
#include "general_settings.h"
#include <common/themes/windows_colors.h>
#include "restart_elevated.h"
#include "update_state.h"
#include "update_utils.h"
#include "centralized_kb_hook.h"
@ -23,6 +21,8 @@
#include <common/utils/process_path.h>
#include <common/utils/timeutil.h>
#include <common/utils/winapi_error.h>
#include <common/updating/updateState.h>
#include <common/themes/windows_colors.h>
#define BUFSIZE 1024
@ -84,34 +84,16 @@ std::optional<std::wstring> dispatch_json_action_to_module(const json::JsonObjec
}
else if (action == L"check_for_updates")
{
if (auto update_check_result = check_for_updates())
{
VersionHelper latestVersion{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION };
bool isVersionLatest = true;
if (auto new_version = std::get_if<updating::new_version_download_info>(&*update_check_result))
{
latestVersion = new_version->version;
isVersionLatest = false;
}
json::JsonObject json;
json.SetNamedValue(L"version", json::value(latestVersion.toWstring()));
json.SetNamedValue(L"isVersionLatest", json::value(isVersionLatest));
result.emplace(json.Stringify());
UpdateState::store([](UpdateState& state) {
state.github_update_last_checked_date.emplace(timeutil::now());
});
}
check_for_updates_settings_callback();
}
else if (action == L"request_update_state_date")
{
json::JsonObject json;
auto update_state = UpdateState::read();
if (update_state.github_update_last_checked_date)
if (update_state.githubUpdateLastCheckedDate)
{
const time_t date = *update_state.github_update_last_checked_date;
const time_t date = *update_state.githubUpdateLastCheckedDate;
json.SetNamedValue(L"updateStateDate", json::value(std::to_wstring(date)));
}
@ -336,7 +318,7 @@ void run_settings_window(bool showOobeWindow)
// Arg 6: elevated status
bool isElevated{ get_general_settings().isElevated };
std::wstring settings_elevatedStatus = isElevated ? L"true" : L"false";
// Arg 7: is user an admin
bool isAdmin{ get_general_settings().isAdmin };
std::wstring settings_isUserAnAdmin = isAdmin ? L"true" : L"false";
@ -364,14 +346,14 @@ void run_settings_window(bool showOobeWindow)
executable_args.append(settings_isUserAnAdmin);
executable_args.append(L" ");
executable_args.append(settings_showOobe);
BOOL process_created = false;
if (is_process_elevated())
{
// TODO: Revisit this after switching to .NET 5
// Due to a bug in .NET, running the Settings process as non-elevated
// from an elevated process sometimes results in a crash.
// from an elevated process sometimes results in a crash.
// process_created = run_settings_non_elevated(executable_path.c_str(), executable_args.data(), &process_info);
}

View file

@ -1,68 +0,0 @@
#include "pch.h"
#include "update_state.h"
#include <common/utils/json.h>
#include <common/utils/timeutil.h>
#include <common/SettingsAPI/settings_helpers.h>
namespace
{
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json";
const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToys_Runner_UpdateStateMutex";
}
UpdateState deserialize(const json::JsonObject& json)
{
UpdateState result;
result.github_update_last_checked_date = timeutil::from_string(json.GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
result.pending_update = json.GetNamedBoolean(L"pending_update", false);
result.pending_installer_filename = json.GetNamedString(L"pending_installer_filename", L"");
return result;
}
json::JsonObject serialize(const UpdateState& state)
{
json::JsonObject json;
if (state.github_update_last_checked_date.has_value())
{
json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*state.github_update_last_checked_date)));
}
json.SetNamedValue(L"pending_update", json::value(state.pending_update));
json.SetNamedValue(L"pending_installer_filename", json::value(state.pending_installer_filename));
return json;
}
UpdateState UpdateState::read()
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(file_name);
}
return json ? deserialize(*json) : UpdateState{};
}
void UpdateState::store(std::function<void(UpdateState&)> state_modifier)
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
std::optional<json::JsonObject> json;
{
wil::unique_mutex_nothrow mutex{ CreateMutexW(nullptr, FALSE, UPDATE_STATE_MUTEX) };
auto lock = mutex.acquire();
json = json::from_file(file_name);
UpdateState state;
if (json)
{
state = deserialize(*json);
}
state_modifier(state);
json.emplace(serialize(state));
json::to_file(file_name, *json);
}
}

View file

@ -1,18 +0,0 @@
#pragma once
#include <ctime>
#include <optional>
#include <functional>
// All fields must be default-initialized
struct UpdateState
{
std::optional<std::time_t> github_update_last_checked_date;
bool pending_update = false;
std::wstring pending_installer_filename;
// To prevent concurrent modification of the file, we enforce this interface, which locks the file while
// the state_modifier is active.
static void store(std::function<void(UpdateState&)> state_modifier);
static UpdateState read();
};

View file

@ -3,14 +3,16 @@
#include "Generated Files/resource.h"
#include "action_runner_utils.h"
#include "update_state.h"
#include "general_settings.h"
#include "update_utils.h"
#include <common/logger/logger.h>
#include <common/updating/installer.h>
#include <common/updating/http_client.h>
#include <common/updating/updating.h>
#include <common/updating/updateState.h>
#include <common/utils/resources.h>
#include <common/utils/timeutil.h>
#include <runner/general_settings.h>
auto Strings = create_notifications_strings();
@ -44,15 +46,80 @@ bool start_msi_uninstallation_sequence()
return exit_code == 0;
}
void github_update_worker()
using namespace updating;
bool could_be_costly_connection()
{
using namespace winrt::Windows::Networking::Connectivity;
ConnectionProfile internetConnectionProfile = NetworkInformation::GetInternetConnectionProfile();
return internetConnectionProfile && internetConnectionProfile.IsWwanConnectionProfile();
}
void process_new_version_info(const github_version_info& version_info,
UpdateState& state,
const bool download_update,
const bool show_notifications)
{
state.githubUpdateLastCheckedDate.emplace(timeutil::now());
if (std::holds_alternative<version_up_to_date>(version_info))
{
state.state = UpdateState::upToDate;
state.releasePageUrl = {};
state.downloadedInstallerFilename = {};
Logger::trace(L"Version is up to date");
return;
}
const auto new_version_info = std::get<new_version_download_info>(version_info);
state.releasePageUrl = new_version_info.release_page_uri.ToString().c_str();
Logger::trace(L"Discovered new version {}", new_version_info.version.toWstring());
const bool already_downloaded = state.state == UpdateState::readyToInstall && state.downloadedInstallerFilename == new_version_info.installer_filename;
if (already_downloaded)
{
Logger::trace(L"New version is already downloaded");
return;
}
if (download_update)
{
Logger::trace(L"Downloading installer for a new version");
if (download_new_version(new_version_info).get())
{
state.state = UpdateState::readyToInstall;
state.downloadedInstallerFilename = new_version_info.installer_filename;
if (show_notifications)
{
notifications::show_new_version_available(new_version_info, Strings);
}
}
else
{
state.state = UpdateState::errorDownloading;
state.downloadedInstallerFilename = {};
Logger::error("Couldn't download new installer");
}
}
else
{
Logger::trace(L"New version is ready to download, showing notification");
state.state = UpdateState::readyToDownload;
state.downloadedInstallerFilename = {};
if (show_notifications)
{
notifications::show_open_settings_for_update(Strings);
}
}
}
void periodic_update_worker()
{
for (;;)
{
auto state = UpdateState::read();
int64_t sleep_minutes_till_next_update = 0;
if (state.github_update_last_checked_date.has_value())
if (state.githubUpdateLastCheckedDate.has_value())
{
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date);
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.githubUpdateLastCheckedDate);
if (last_checked_minutes_ago < 0)
{
last_checked_minutes_ago = UPDATE_CHECK_INTERVAL_MINUTES;
@ -61,22 +128,31 @@ void github_update_worker()
}
std::this_thread::sleep_for(std::chrono::minutes{ sleep_minutes_till_next_update });
const bool download_updates_automatically = get_general_settings().downloadUpdatesAutomatically;
bool update_check_ok = false;
const bool download_update = !could_be_costly_connection() && get_general_settings().downloadUpdatesAutomatically;
bool version_info_obtained = false;
try
{
update_check_ok = updating::try_autoupdate(download_updates_automatically, Strings).get();
const auto new_version_info = get_github_version_info_async(Strings).get();
if (new_version_info.has_value())
{
version_info_obtained = true;
process_new_version_info(*new_version_info, state, download_update, true);
}
else
{
Logger::error(L"Couldn't obtain version info from github: {}", new_version_info.error());
}
}
catch (...)
{
// Couldn't autoupdate
update_check_ok = false;
Logger::error("periodic_update_worker: error while processing version info");
}
if (update_check_ok)
if (version_info_obtained)
{
UpdateState::store([](UpdateState& state) {
state.github_update_last_checked_date.emplace(timeutil::now());
UpdateState::store([&](UpdateState& v) {
v = std::move(state);
});
}
else
@ -86,55 +162,27 @@ void github_update_worker()
}
}
std::optional<updating::github_version_info> check_for_updates()
void check_for_updates_settings_callback()
{
Logger::trace(L"Check for updates callback invoked");
auto state = UpdateState::read();
try
{
auto version_check_result = updating::get_github_version_info_async(Strings).get();
if (!version_check_result)
auto new_version_info = get_github_version_info_async(Strings).get();
if (!new_version_info)
{
updating::notifications::show_unavailable(Strings, std::move(version_check_result.error()));
return std::nullopt;
// If we couldn't get a new version from github for some reason, assume we're up to date, but also log error
new_version_info = version_up_to_date{};
Logger::error(L"Couldn't obtain version info from github: {}", new_version_info.error());
}
if (std::holds_alternative<updating::version_up_to_date>(*version_check_result))
{
updating::notifications::show_unavailable(Strings, Strings.GITHUB_NEW_VERSION_UP_TO_DATE);
return std::move(*version_check_result);
}
auto new_version = std::get<updating::new_version_download_info>(*version_check_result);
updating::notifications::show_available(new_version, Strings);
return std::move(new_version);
const bool download_update = !could_be_costly_connection() && get_general_settings().downloadUpdatesAutomatically;
process_new_version_info(*new_version_info, state, download_update, false);
UpdateState::store([&](UpdateState& v) {
v = std::move(state);
});
}
catch (...)
{
// Couldn't autoupdate
Logger::error("check_for_updates_settings_callback: error while processing version info");
}
return std::nullopt;
}
bool launch_pending_update()
{
try
{
auto update_state = UpdateState::read();
if (update_state.pending_update)
{
UpdateState::store([](UpdateState& state) {
state.pending_update = false;
state.pending_installer_filename = {};
});
std::wstring args{ UPDATE_NOW_LAUNCH_STAGE1_START_PT_CMDARG };
args += L' ';
args += update_state.pending_installer_filename;
launch_action_runner(args.c_str());
return true;
}
}
catch (...)
{
}
return false;
}

View file

@ -3,6 +3,5 @@
#include <common/updating/updating.h>
bool start_msi_uninstallation_sequence();
void github_update_worker();
std::optional<updating::github_version_info> check_for_updates();
bool launch_pending_update();
void periodic_update_worker();
void check_for_updates_settings_callback();

View file

@ -0,0 +1,126 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class UpdatingSettings
{
public enum UpdatingState
{
UpToDate = 0,
ErrorDownloading,
ReadyToDownload,
ReadyToInstall,
}
// Gets or sets a value of the updating state
[JsonPropertyName("state")]
public UpdatingState State { get; set; }
// Gets or sets a value of the release page url
[JsonPropertyName("releasePageUrl")]
public string ReleasePageLink { get; set; }
// Gets or sets a value of the github last checked date
[JsonPropertyName("githubUpdateLastCheckedDate")]
public string LastCheckedDate { get; set; }
// Gets or sets a value of the updating state
[JsonPropertyName("downloadedInstallerFilename")]
public string DownloadedInstallerFilename { get; set; }
// Non-localizable strings: Files
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
public const string SettingsFile = "UpdateState.json";
public string NewVersion
{
get
{
if (ReleasePageLink == null)
{
return string.Empty;
}
try
{
string version = ReleasePageLink.Substring(ReleasePageLink.LastIndexOf('/') + 1);
return version.Trim();
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
return string.Empty;
}
}
public string LastCheckedDateLocalized
{
get
{
try
{
long seconds = long.Parse(LastCheckedDate, CultureInfo.CurrentCulture);
var date = DateTimeOffset.FromUnixTimeSeconds(seconds).UtcDateTime;
return date.ToLocalTime().ToString(CultureInfo.CurrentCulture);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
return string.Empty;
}
}
public UpdatingSettings()
{
State = UpdatingState.UpToDate;
}
public static UpdatingSettings LoadSettings()
{
FileSystem fileSystem = new FileSystem();
var localAppDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var file = localAppDataDir + SettingsFilePath + SettingsFile;
if (fileSystem.File.Exists(file))
{
try
{
Stream inputStream = fileSystem.File.Open(file, FileMode.Open);
StreamReader reader = new StreamReader(inputStream);
string data = reader.ReadToEnd();
inputStream.Close();
reader.Dispose();
return JsonSerializer.Deserialize<UpdatingSettings>(data);
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
}
}
return null;
}
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
}
}
}

View file

@ -75,6 +75,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
}
public static string GetPowerToysInstallationFolder()
{
var settingsPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
return Directory.GetParent(settingsPath).FullName;
}
private static readonly interop.LayoutMapManaged LayoutMap = new interop.LayoutMapManaged();
public static string GetKeyName(uint key)

View file

@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO.Abstractions;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
@ -16,10 +18,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
{
private GeneralSettings GeneralSettingsConfig { get; set; }
private UpdatingSettings UpdatingSettingsConfig { get; set; }
public ButtonClickCommand CheckForUpdatesEventHandler { get; set; }
public ButtonClickCommand RestartElevatedButtonEventHandler { get; set; }
public ButtonClickCommand UpdateNowButtonEventHandler { get; set; }
public Func<string, int> UpdateUIThemeCallBack { get; }
public Func<string, int> SendConfigMSG { get; }
@ -34,10 +40,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private string _settingsConfigFileFolder = string.Empty;
public GeneralViewModel(ISettingsRepository<GeneralSettings> settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func<string, int> updateTheme, Func<string, int> ipcMSGCallBackFunc, Func<string, int> ipcMSGRestartAsAdminMSGCallBackFunc, Func<string, int> ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "")
private IFileSystemWatcher _fileWatcher;
public GeneralViewModel(ISettingsRepository<GeneralSettings> settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func<string, int> updateTheme, Func<string, int> ipcMSGCallBackFunc, Func<string, int> ipcMSGRestartAsAdminMSGCallBackFunc, Func<string, int> ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "", Action dispatcherAction = null)
{
CheckForUpdatesEventHandler = new ButtonClickCommand(CheckForUpdatesClick);
RestartElevatedButtonEventHandler = new ButtonClickCommand(RestartElevated);
UpdateNowButtonEventHandler = new ButtonClickCommand(UpdateNowClick);
// To obtain the general settings configuration of PowerToys if it exists, else to create a new file and return the default configurations.
if (settingsRepository == null)
@ -46,6 +55,11 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
}
GeneralSettingsConfig = settingsRepository.SettingsConfig;
UpdatingSettingsConfig = UpdatingSettings.LoadSettings();
if (UpdatingSettingsConfig == null)
{
UpdatingSettingsConfig = new UpdatingSettings();
}
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
@ -86,6 +100,16 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
RunningAsAdminDefaultText = runAsAdminText;
_isAdmin = isAdmin;
_updatingState = UpdatingSettingsConfig.State;
_newAvailableVersion = UpdatingSettingsConfig.NewVersion;
_newAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
_updateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
if (dispatcherAction != null)
{
_fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction);
}
}
private bool _packaged;
@ -98,9 +122,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private bool _isSystemThemeRadioButtonChecked;
private bool _autoDownloadUpdates;
private string _latestAvailableVersion = string.Empty;
private UpdatingSettings.UpdatingState _updatingState = UpdatingSettings.UpdatingState.UpToDate;
private string _newAvailableVersion = string.Empty;
private string _newAvailableVersionLink = string.Empty;
private string _updateCheckedDate = string.Empty;
private bool _isNewVersionDownloading;
private bool _isNewVersionChecked;
// Gets or sets a value indicating whether packaged.
public bool Packaged
{
@ -360,24 +389,90 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
}
}
// Temp string. Appears when a user clicks "Check for updates" button and shows latest version available on the Github.
public string LatestAvailableVersion
public UpdatingSettings.UpdatingState PowerToysUpdatingState
{
get
{
return _latestAvailableVersion;
return _updatingState;
}
private set
{
if (value != _updatingState)
{
_updatingState = value;
NotifyPropertyChanged();
}
}
}
public string PowerToysNewAvailableVersion
{
get
{
return _newAvailableVersion;
}
private set
{
if (value != _newAvailableVersion)
{
_newAvailableVersion = value;
NotifyPropertyChanged();
}
}
}
public string PowerToysNewAvailableVersionLink
{
get
{
return _newAvailableVersionLink;
}
private set
{
if (value != _newAvailableVersionLink)
{
_newAvailableVersionLink = value;
NotifyPropertyChanged();
}
}
}
public bool IsNewVersionDownloading
{
get
{
return _isNewVersionDownloading;
}
set
{
if (_latestAvailableVersion != value)
if (value != _isNewVersionDownloading)
{
_latestAvailableVersion = value;
_isNewVersionDownloading = value;
NotifyPropertyChanged();
}
}
}
public bool IsNewVersionCheckedAndUpToDate
{
get
{
return _isNewVersionChecked;
}
}
public bool IsDownloadAllowed
{
get
{
return AutoUpdatesEnabled && !IsNewVersionDownloading;
}
}
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
// Notify UI of property change
@ -390,6 +485,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
// callback function to launch the URL to check for updates.
private void CheckForUpdatesClick()
{
IsNewVersionDownloading = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
NotifyPropertyChanged(nameof(IsDownloadAllowed));
if (_isNewVersionChecked)
{
_isNewVersionChecked = !IsNewVersionDownloading;
NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate));
}
GeneralSettingsConfig.CustomActionName = "check_for_updates";
OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfig);
@ -398,6 +502,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
SendCheckForUpdatesConfigMSG(customaction.ToString());
}
private void UpdateNowClick()
{
IsNewVersionDownloading = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
NotifyPropertyChanged(nameof(IsDownloadAllowed));
Process.Start(new ProcessStartInfo(Helper.GetPowerToysInstallationFolder() + "\\PowerToys.exe") { Arguments = "powertoys://update_now/" });
}
public void RequestUpdateCheckedDate()
{
GeneralSettingsConfig.CustomActionName = "request_update_state_date";
@ -417,5 +529,45 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
SendRestartAsAdminConfigMSG(customaction.ToString());
}
public void RefreshUpdatingState()
{
var config = UpdatingSettings.LoadSettings();
// Retry loading if failed
for (int i = 0; i < 3 && config == null; i++)
{
System.Threading.Thread.Sleep(100);
config = UpdatingSettings.LoadSettings();
}
if (config == null || config.ToJsonString() == UpdatingSettingsConfig.ToJsonString())
{
return;
}
UpdatingSettingsConfig = config;
if (PowerToysUpdatingState != config.State)
{
IsNewVersionDownloading = false;
}
else
{
bool dateChanged = UpdateCheckedDate == UpdatingSettingsConfig.LastCheckedDateLocalized;
bool fileDownloaded = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
IsNewVersionDownloading = !(dateChanged || fileDownloaded);
}
PowerToysUpdatingState = UpdatingSettingsConfig.State;
PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion;
PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
UpdateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
_isNewVersionChecked = PowerToysUpdatingState == UpdatingSettings.UpdatingState.UpToDate && !IsNewVersionDownloading;
NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate));
NotifyPropertyChanged(nameof(IsDownloadAllowed));
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class UpdatingStateCannotDownloadToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ErrorDownloading ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class UpdatingStateReadyToDownloadToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ReadyToDownload ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class UpdatingStateReadyToInstallToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.ReadyToInstall ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.PowerToys.Settings.UI.Library;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
namespace Microsoft.PowerToys.Settings.UI.Converters
{
public sealed class UpdatingStateUpToDateToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (UpdatingSettings.UpdatingState)value == UpdatingSettings.UpdatingState.UpToDate ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
}
}

View file

@ -106,6 +106,10 @@
<DependentUpon>ShortcutVisualControl.xaml</DependentUpon>
</Compile>
<Compile Include="Converters\ModuleEnabledToForegroundConverter.cs" />
<Compile Include="Converters\UpdatingStateCannotDownloadToVisibilityConverter.cs" />
<Compile Include="Converters\UpdatingStateReadyToDownloadToVisibilityConverter.cs" />
<Compile Include="Converters\UpdatingStateReadyToInstallToVisibilityConverter.cs" />
<Compile Include="Converters\UpdatingStateUpToDateToVisibilityConverter.cs" />
<Compile Include="Generated Files\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>

View file

@ -400,6 +400,9 @@
<data name="GeneralPage_CheckForUpdates.Content" xml:space="preserve">
<value>Check for updates</value>
</data>
<data name="GeneralPage_UpdateNow.Content" xml:space="preserve">
<value>Update now</value>
</data>
<data name="GeneralPage_PrivacyStatement_URL.Text" xml:space="preserve">
<value>Privacy statement</value>
</data>
@ -706,10 +709,10 @@
<value>Updates</value>
</data>
<data name="General_Version.Text" xml:space="preserve">
<value>Version:</value>
<value>Installed version:</value>
</data>
<data name="General_VersionLastChecked.Text" xml:space="preserve">
<value>Last successfully checked: </value>
<value>Last checked: </value>
</data>
<data name="General_Version.AutomationProperties.Name" xml:space="preserve">
<value>Version</value>
@ -1194,4 +1197,34 @@ From there, simply click on a Markdown file or SVG icon in the File Explorer and
<data name="ShortcutGuide_OpenShortcutGuide.Header" xml:space="preserve">
<value>Open Shortcut Guide</value>
</data>
<data name="General_FailedToDownloadTheNewVersion.Text" xml:space="preserve">
<value>An error occurred trying to update to</value>
</data>
<data name="General_InstallNow.Content" xml:space="preserve">
<value>Install now</value>
</data>
<data name="General_ReadMore.Text" xml:space="preserve">
<value>Read more</value>
</data>
<data name="General_NewVersionAvailable.Text" xml:space="preserve">
<value>A new version is available: </value>
</data>
<data name="General_Downloading.Text" xml:space="preserve">
<value>Downloading...</value>
</data>
<data name="General_TryAgainToDownloadAndInstall.Content" xml:space="preserve">
<value>Try again to download and install</value>
</data>
<data name="General_CheckingForUpdates.Text" xml:space="preserve">
<value>Checking for updates...</value>
</data>
<data name="General_NewVersionReadyToInstall.Text" xml:space="preserve">
<value>A new version is ready to install:</value>
</data>
<data name="General_UpToDate.Text" xml:space="preserve">
<value>PowerToys is up to date</value>
</data>
<data name="General_DownloadAndInstall.Content" xml:space="preserve">
<value>Download and install</value>
</data>
</root>

View file

@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:Microsoft.Toolkit.Uwp.UI.Converters"
xmlns:localConverters="using:Microsoft.PowerToys.Settings.UI.Converters"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
@ -14,6 +15,10 @@
<Page.Resources>
<converters:BoolToObjectConverter x:Key="BoolToVisibilityConverter" TrueValue="Collapsed" FalseValue="Visible"/>
<converters:BoolToVisibilityConverter x:Key="VisibleIfTrueConverter"/>
<localConverters:UpdatingStateUpToDateToVisibilityConverter x:Key="UpdatingStateUpToDateToVisibilityConverter" />
<localConverters:UpdatingStateCannotDownloadToVisibilityConverter x:Key="UpdatingStateCannotDownloadToVisibilityConverter" />
<localConverters:UpdatingStateReadyToDownloadToVisibilityConverter x:Key="UpdatingStateReadyToDownloadToVisibilityConverter" />
<localConverters:UpdatingStateReadyToInstallToVisibilityConverter x:Key="UpdatingStateReadyToInstallToVisibilityConverter" />
</Page.Resources>
<Grid RowSpacing="{StaticResource DefaultRowSpacing}">
@ -62,7 +67,7 @@
AutomationProperties.HeadingLevel="Level2"
Style="{StaticResource SubtitleTextBlockStyle}"/>
<TextBlock Text="{x:Bind Mode=TwoWay, Path=ViewModel.RunningAsText}"
<TextBlock Text="{Binding Mode=TwoWay, Path=RunningAsText}"
TextWrapping="Wrap"
Margin="{StaticResource SmallTopMargin}"/>
@ -116,45 +121,176 @@
IsOn="{Binding Mode=TwoWay, Path=Startup}"/>
<TextBlock x:Uid="General_Updates"
Style="{StaticResource SettingsGroupTitleStyle}"/>
<StackPanel Orientation="Horizontal"
<StackPanel Visibility="{Binding PowerToysUpdatingState, Converter={StaticResource UpdatingStateUpToDateToVisibilityConverter}}">
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_Version_UpToDate"
x:Uid="General_Version"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<TextBlock Text="{Binding PowerToysVersion }"
Margin="4,0,0,0"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_VersionLastChecked}"
Visibility="{Binding AutoUpdatesEnabled, Converter={StaticResource VisibleIfTrueConverter}}">
<TextBlock x:Name="General_VersionLastChecked"
x:Uid="General_VersionLastChecked"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<TextBlock Text="{Binding UpdateCheckedDate, Mode=OneWay}"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"
Margin="6,0" />
</StackPanel>
<TextBlock x:Name="General_CheckingForUpdates"
x:Uid="General_CheckingForUpdates"
Margin="{StaticResource SmallTopMargin}"
Visibility="{Binding IsNewVersionDownloading, Converter={StaticResource VisibleIfTrueConverter}}"/>
<TextBlock x:Name="General_UpToDate"
x:Uid="General_UpToDate"
Margin="{StaticResource SmallTopMargin}"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"
Visibility="{Binding IsNewVersionCheckedAndUpToDate, Converter={StaticResource VisibleIfTrueConverter}}"/>
<Button x:Uid="GeneralPage_CheckForUpdates"
Style="{StaticResource AccentButtonStyle}"
Foreground="White"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_Version" x:Uid="General_Version" />
<HyperlinkButton NavigateUri="https://github.com/microsoft/PowerToys/releases" Margin="4,-6,0,0">
<TextBlock Text="{x:Bind ViewModel.PowerToysVersion }" />
</HyperlinkButton>
<TextBlock Text="{x:Bind ViewModel.LatestAvailableVersion, Mode=OneWay}"
Foreground="{ThemeResource SystemControlErrorTextForegroundBrush}"
Margin="16,0,0,0" />
Command="{Binding CheckForUpdatesEventHandler}"
IsEnabled="{Binding IsDownloadAllowed}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallBottomMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_VersionLastChecked}"
Visibility="{Binding AutoUpdatesEnabled,
Converter={StaticResource VisibleIfTrueConverter}}">
<TextBlock x:Name="General_VersionLastChecked" x:Uid="General_VersionLastChecked" />
<TextBlock Text="{x:Bind ViewModel.UpdateCheckedDate, Mode=OneWay}"
Foreground="{ThemeResource ListViewItemForegroundPointerOver}"
Margin="6,0" />
<StackPanel Visibility="{Binding PowerToysUpdatingState, Converter={StaticResource UpdatingStateReadyToDownloadToVisibilityConverter}}">
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_Version_ReadyToDownload"
x:Uid="General_Version"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<TextBlock Text="{Binding PowerToysVersion }"
Margin="4,0,0,0"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_NewVersionAvailable"
x:Uid="General_NewVersionAvailable"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<HyperlinkButton NavigateUri="{Binding PowerToysNewAvailableVersionLink }" Margin="4,-6,0,0" IsEnabled="{Binding AutoUpdatesEnabled}">
<TextBlock Text="{Binding PowerToysNewAvailableVersion }" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="{Binding PowerToysNewAvailableVersionLink}"
Margin="4,-6,0,0"
IsEnabled="{Binding AutoUpdatesEnabled}">
<TextBlock x:Name="General_ReadMore_ReadyToDownload" x:Uid="General_ReadMore" />
</HyperlinkButton>
</StackPanel>
<TextBlock x:Name="General_Downloading"
x:Uid="General_Downloading"
Visibility="{Binding Mode=OneWay, Path=IsNewVersionDownloading, Converter={StaticResource VisibleIfTrueConverter}}"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<Button x:Uid="General_DownloadAndInstall"
Margin="{StaticResource SmallTopMargin}"
Style="{StaticResource AccentButtonStyle}"
Foreground="White"
Command="{Binding UpdateNowButtonEventHandler}"
IsEnabled="{Binding IsDownloadAllowed}"/>
</StackPanel>
<Button x:Uid="GeneralPage_CheckForUpdates"
Style="{StaticResource AccentButtonStyle}"
Foreground="White"
Command="{Binding CheckForUpdatesEventHandler}"
IsEnabled="{Binding AutoUpdatesEnabled}"
/>
<StackPanel Visibility="{Binding PowerToysUpdatingState, Converter={StaticResource UpdatingStateReadyToInstallToVisibilityConverter}}">
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_Version_ReadyToInstall"
x:Uid="General_Version"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<TextBlock Text="{Binding PowerToysVersion }"
Margin="4,0,0,0"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_NewVersionReadyToInstall"
x:Uid="General_NewVersionReadyToInstall"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<HyperlinkButton NavigateUri="{Binding PowerToysNewAvailableVersionLink }" Margin="4,-6,0,0" IsEnabled="{Binding AutoUpdatesEnabled}">
<TextBlock Text="{Binding PowerToysNewAvailableVersion }" />
</HyperlinkButton>
<HyperlinkButton NavigateUri="{Binding PowerToysNewAvailableVersionLink}"
Margin="4,-6,0,0"
IsEnabled="{Binding AutoUpdatesEnabled}">
<TextBlock x:Name="General_ReadMore_ReadyToInstall" x:Uid="General_ReadMore" />
</HyperlinkButton>
</StackPanel>
<Button x:Uid="General_InstallNow"
Margin="{StaticResource SmallTopMargin}"
Style="{StaticResource AccentButtonStyle}"
Foreground="White"
Command="{Binding UpdateNowButtonEventHandler}"
IsEnabled="{Binding AutoUpdatesEnabled}"/>
</StackPanel>
<StackPanel Visibility="{Binding PowerToysUpdatingState, Converter={StaticResource UpdatingStateCannotDownloadToVisibilityConverter}}">
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_Version_Error"
x:Uid="General_Version"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<TextBlock Text="{Binding PowerToysVersion }"
Margin="4,0,0,0"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
</StackPanel>
<StackPanel Orientation="Horizontal"
Margin="{StaticResource SmallTopMargin}"
AutomationProperties.LabeledBy="{Binding ElementName=General_Version}">
<TextBlock x:Name="General_FailedToDownloadTheNewVersion"
x:Uid="General_FailedToDownloadTheNewVersion"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<HyperlinkButton NavigateUri="{Binding PowerToysNewAvailableVersionLink }" Margin="4,-6,0,0" IsEnabled="{Binding AutoUpdatesEnabled}">
<TextBlock Text="{Binding PowerToysNewAvailableVersion }" />
</HyperlinkButton>
</StackPanel>
<TextBlock x:Name="General_Downloading_TryAgain"
x:Uid="General_Downloading"
Visibility="{Binding Mode=OneWay, Path=IsNewVersionDownloading, Converter={StaticResource VisibleIfTrueConverter}}"
Foreground="{Binding AutoUpdatesEnabled, Converter={StaticResource ModuleEnabledToForegroundConverter}}"/>
<Button x:Uid="General_TryAgainToDownloadAndInstall"
Margin="{StaticResource SmallTopMargin}"
Style="{StaticResource AccentButtonStyle}"
Foreground="White"
Command="{Binding UpdateNowButtonEventHandler}"
IsEnabled="{Binding IsDownloadAllowed}"/>
</StackPanel>
<ToggleSwitch x:Uid="GeneralPage_ToggleSwitch_AutoDownloadUpdates"
Margin="{StaticResource MediumTopMargin}"
Visibility="{Binding Mode=TwoWay, Path=IsAdmin, Converter={StaticResource VisibleIfTrueConverter}}"
IsOn="{Binding Mode=TwoWay, Path=AutoDownloadUpdates}"
IsEnabled="{Binding AutoUpdatesEnabled}" />
</StackPanel>
<RelativePanel x:Name="SidePanel"

View file

@ -10,6 +10,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.Library.ViewModels;
using Windows.ApplicationModel.Resources;
using Windows.Data.Json;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
@ -38,6 +39,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
ResourceLoader loader = ResourceLoader.GetForViewIndependentUse();
var settingsUtils = new SettingsUtils();
Action stateUpdatingAction = async () =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, ViewModel.RefreshUpdatingState);
};
ViewModel = new GeneralViewModel(
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
loader.GetString("GeneralSettings_RunningAsAdminText"),
@ -47,52 +53,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
UpdateUIThemeMethod,
ShellPage.SendDefaultIPCMessage,
ShellPage.SendRestartAdminIPCMessage,
ShellPage.SendCheckForUpdatesIPCMessage);
ShellPage.ShellHandler.IPCResponseHandleList.Add((JsonObject json) =>
{
try
{
string version = json.GetNamedString("version", string.Empty);
bool isLatest = json.GetNamedBoolean("isVersionLatest", false);
if (json.ContainsKey("version"))
{
ViewModel.RequestUpdateCheckedDate();
}
var str = string.Empty;
if (isLatest)
{
str = ResourceLoader.GetForCurrentView().GetString("GeneralSettings_VersionIsLatest");
}
else if (!string.IsNullOrEmpty(version))
{
str = ResourceLoader.GetForCurrentView().GetString("GeneralSettings_NewVersionIsAvailable");
if (!string.IsNullOrEmpty(str))
{
str += ": " + version;
}
}
// Using CurrentCulture since this is user-facing
if (!string.IsNullOrEmpty(str))
{
ViewModel.LatestAvailableVersion = string.Format(CultureInfo.CurrentCulture, str);
}
string updateStateDate = json.GetNamedString("updateStateDate", string.Empty);
if (!string.IsNullOrEmpty(updateStateDate) && long.TryParse(updateStateDate, out var uTCTime))
{
var localTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(uTCTime).ToLocalTime();
ViewModel.UpdateCheckedDate = localTime.ToString(CultureInfo.CurrentCulture);
}
}
catch (Exception e)
{
Logger.LogError("Exception encountered when reading the version.", e);
}
});
ShellPage.SendCheckForUpdatesIPCMessage,
string.Empty,
stateUpdatingAction);
DataContext = ViewModel;
}

View file

@ -142,7 +142,7 @@ void HideUserPrivateInfo(const filesystem::path& dir)
HideForFile(dir, it.first);
}
// delete files
// Delete files
for (auto it : filesToDelete)
{
auto path = dir;
@ -243,6 +243,26 @@ void ReportDotNetInstallationInfo(const filesystem::path& tmpDir)
}
}
void ReportBootstrapperLog(const filesystem::path& targetDir)
{
for (const auto entry : filesystem::directory_iterator{temp_directory_path()})
{
if (!entry.is_regular_file() || !entry.path().has_filename())
{
continue;
}
const std::wstring filename = entry.path().filename().native();
if (!filename.starts_with(L"powertoys-bootstrapper-") || !filename.ends_with(L".log"))
{
continue;
}
std::error_code _;
copy(entry.path(), targetDir, _);
}
}
int wmain(int argc, wchar_t* argv[], wchar_t*)
{
// Get path to save zip
@ -280,6 +300,7 @@ int wmain(int argc, wchar_t* argv[], wchar_t*)
try
{
copy(settingsRootPath, tmpDir, copy_options::recursive);
// Remove updates folder contents
DeleteFolder(tmpDir / "Updates");
}
@ -296,7 +317,7 @@ int wmain(int argc, wchar_t* argv[], wchar_t*)
ReportWindowsSettings(tmpDir);
// Write monitors info to the temporary folder
reportMonitorInfo(tmpDir);
ReportMonitorInfo(tmpDir);
// Write windows version info to the temporary folder
ReportWindowsVersion(tmpDir);
@ -310,6 +331,8 @@ int wmain(int argc, wchar_t* argv[], wchar_t*)
// Write compatibility tab info to the temporary folder
ReportCompatibilityTab(tmpDir);
ReportBootstrapperLog(tmpDir);
// Zip folder
auto zipPath = path::path(saveZipPath);
std::string reportFilename{"PowerToysReport_"};

View file

@ -246,6 +246,7 @@ void ReportRegistry(const filesystem::path& tmpDir)
{
registryReport << "ERROR " << result << "\n";
}
registryReport << "\n";
}
@ -288,12 +289,14 @@ void ReportRegistry(const filesystem::path& tmpDir)
registryReport << "ERROR " << result << "\n";
}
}
RegCloseKey(rootKey);
}
else
{
registryReport << "ERROR " << result << "\n";
}
registryReport << "\n";
}
}

View file

@ -8,4 +8,4 @@
#include <Windows.h>
void ReportRegistry(const std::filesystem::path& tmpDir);
void ReportCompatibilityTab(const std::filesystem::path& tmpDir);
void ReportCompatibilityTab(const std::filesystem::path& tmpDir);

View file

@ -7,7 +7,7 @@ using namespace std;
namespace
{
int buildMonitorInfoReport(std::wostream& os)
int BuildMonitorInfoReport(std::wostream& os)
{
struct capture
{
@ -45,6 +45,7 @@ namespace
}
return TRUE;
};
capture c;
c.os = &os;
if (EnumDisplayMonitors(nullptr, nullptr, callback, (LPARAM)&c))
@ -60,7 +61,7 @@ namespace
}
}
void reportMonitorInfo(const filesystem::path& tmpDir)
void ReportMonitorInfo(const filesystem::path& tmpDir)
{
auto monitorReportPath = tmpDir;
monitorReportPath.append("monitor-report-info.txt");
@ -69,7 +70,7 @@ void reportMonitorInfo(const filesystem::path& tmpDir)
{
wofstream monitorReport(monitorReportPath);
monitorReport << "GetSystemMetrics = " << GetSystemMetrics(SM_CMONITORS) << '\n';
buildMonitorInfoReport(monitorReport);
BuildMonitorInfoReport(monitorReport);
}
catch (std::exception& ex)
{

View file

@ -1,4 +1,4 @@
#pragma once
#include <fstream>
void reportMonitorInfo(const std::filesystem::path& tmpDir);
void ReportMonitorInfo(const std::filesystem::path& tmpDir);