diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index f24842bc1f..94950829de 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -47,8 +47,8 @@ WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + Event="EndDialog" + Value="Return">NOT Installed 1 NOT Installed NOT Installed @@ -62,11 +62,8 @@ - - - - - + + @@ -90,8 +87,21 @@ Installed and (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + + + NOT Installed + + + + diff --git a/src/action_runner/action_runner.cpp b/src/action_runner/action_runner.cpp index 222d2506e0..61dba78627 100644 --- a/src/action_runner/action_runner.cpp +++ b/src/action_runner/action_runner.cpp @@ -14,6 +14,10 @@ #include "../runner/tray_icon.h" #include "../runner/action_runner_utils.h" +#include "resource.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + int uninstall_msi_action() { const auto package_path = updating::get_msi_package_path(); @@ -122,6 +126,56 @@ bool install_new_version_stage_2(std::wstring_view installer_path, std::wstring_ return true; } +bool dotnet_is_installed() +{ + auto runtimes = exec_and_read_output(LR"(dotnet --list-runtimes)"); + if (!runtimes) + { + return false; + } + const char DESKTOP_DOTNET_RUNTIME_STRING[] = "Microsoft.WindowsDesktop.App 3.1."; + return runtimes->find(DESKTOP_DOTNET_RUNTIME_STRING) != std::string::npos; +} + +bool install_dotnet() +{ + const wchar_t DOTNET_DESKTOP_DOWNLOAD_LINK[] = L"https://download.visualstudio.microsoft.com/download/pr/a1510e74-b31a-4434-b8a0-8074ff31fb3f/b7de8ecba4a14d8312551cfdc745dea1/windowsdesktop-runtime-3.1.0-win-x64.exe"; + const wchar_t DOTNET_DESKTOP_FILENAME[] = L"windowsdesktop-runtime-3.1.0-win-x64.exe"; + + auto dotnet_download_path = fs::temp_directory_path() / DOTNET_DESKTOP_FILENAME; + winrt::Windows::Foundation::Uri download_link{ DOTNET_DESKTOP_DOWNLOAD_LINK }; + + const size_t max_attempts = 3; + bool download_success = false; + for (size_t i = 0; i < max_attempts; ++i) + { + try + { + updating::try_download_file(dotnet_download_path, download_link).wait(); + download_success = true; + break; + } + catch (...) + { + // couldn't download + } + } + if (!download_success) + { + MessageBoxW(nullptr, + GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE).c_str(), + GET_RESOURCE_STRING(IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE).c_str(), + MB_OK | MB_ICONERROR); + return false; + } + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOASYNC }; + sei.lpFile = dotnet_download_path.c_str(); + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = L"/install /passive"; + return ShellExecuteExW(&sei) == TRUE; +} + int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { int nArgs = 0; @@ -132,7 +186,15 @@ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) } std::wstring_view action{ args[1] }; - if (action == L"-uninstall_msi") + if (action == L"-install_dotnet") + { + if (dotnet_is_installed()) + { + return 0; + } + return !install_dotnet(); + } + else if (action == L"-uninstall_msi") { return uninstall_msi_action(); } diff --git a/src/action_runner/action_runner.rc b/src/action_runner/action_runner.rc new file mode 100644 index 0000000000..7f3543c45a --- /dev/null +++ b/src/action_runner/action_runner.rc @@ -0,0 +1,43 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "../common/version.h" + +STRINGTABLE +BEGIN + IDS_DOTNET_CORE_DOWNLOAD_FAILURE "Couldn't download .NET Core Desktop Runtime 3.1.3, please install it manually." + IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE "PowerToys installation error" +END + +1 VERSIONINFO + FILEVERSION FILE_VERSION + PRODUCTVERSION PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", "PowerToys Action Runner" + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", "PowerToys Action Runner" + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", "action_runner.exe" + VALUE "ProductName", "PowerToys" + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/action_runner/action_runner.vcxproj b/src/action_runner/action_runner.vcxproj index 8543460329..cfd48ad7ee 100644 --- a/src/action_runner/action_runner.vcxproj +++ b/src/action_runner/action_runner.vcxproj @@ -160,10 +160,14 @@ + + + + diff --git a/src/action_runner/resource.h b/src/action_runner/resource.h new file mode 100644 index 0000000000..1f0f0f671d --- /dev/null +++ b/src/action_runner/resource.h @@ -0,0 +1,3 @@ +#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE 101 +#define IDS_DOTNET_CORE_DOWNLOAD_FAILURE_TITLE 102 + diff --git a/src/common/common.cpp b/src/common/common.cpp index c64c7178cb..a7d2192aa7 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -6,6 +6,8 @@ #include #include "version.h" +#include + #pragma comment(lib, "advapi32.lib") #pragma comment(lib, "shlwapi.lib") @@ -735,3 +737,64 @@ bool find_app_name_in_path(const std::wstring& where, const std::vector exec_and_read_output(const std::wstring_view command, const DWORD timeout) +{ + SECURITY_ATTRIBUTES saAttr{ sizeof(saAttr) }; + saAttr.bInheritHandle = true; + + wil::unique_handle childStdoutRead; + wil::unique_handle childStdoutWrite; + if (!CreatePipe(&childStdoutRead, &childStdoutWrite, &saAttr, 0)) + { + return std::nullopt; + } + + if (!SetHandleInformation(childStdoutRead.get(), HANDLE_FLAG_INHERIT, 0)) + { + return std::nullopt; + } + + PROCESS_INFORMATION piProcInfo{}; + STARTUPINFOW siStartInfo{ sizeof(siStartInfo) }; + + siStartInfo.hStdError = childStdoutWrite.get(); + siStartInfo.hStdOutput = childStdoutWrite.get(); + siStartInfo.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + siStartInfo.wShowWindow = SW_HIDE; + + std::wstring cmdLine{ command }; + if (!CreateProcessW(nullptr, + cmdLine.data(), + nullptr, + nullptr, + true, + NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, + nullptr, + nullptr, + &siStartInfo, + &piProcInfo)) + { + return std::nullopt; + } + + WaitForSingleObject(piProcInfo.hProcess, timeout); + + childStdoutWrite.reset(); + CloseHandle(piProcInfo.hThread); + + std::string childOutput; + for (;;) + { + char buffer[4096]; + DWORD gotBytes = 0; + if (!ReadFile(childStdoutRead.get(), buffer, sizeof(buffer), &gotBytes, nullptr) || !gotBytes) + { + break; + } + childOutput += std::string_view{ buffer, gotBytes }; + } + + CloseHandle(piProcInfo.hProcess); + return childOutput; +} \ No newline at end of file diff --git a/src/common/common.h b/src/common/common.h index 17cd475993..8d2e552473 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -99,6 +99,8 @@ std::wstring get_resource_string(UINT resource_id, HINSTANCE instance, const wch // is added to the .cpp file. #define GET_RESOURCE_STRING(resource_id) get_resource_string(resource_id, reinterpret_cast(&__ImageBase), L#resource_id) +std::optional exec_and_read_output(const std::wstring_view command, const DWORD timeout = INFINITE); + // Helper class for various COM-related APIs, e.g working with security descriptors template struct typed_storage diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp index d10ba7e912..dc081acfcc 100644 --- a/src/common/updating/updating.cpp +++ b/src/common/updating/updating.cpp @@ -209,7 +209,7 @@ namespace updating return { std::move(path_str) }; } - std::future attempt_to_download_installer(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url) + std::future try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url) { namespace storage = winrt::Windows::Storage; @@ -218,6 +218,7 @@ namespace updating (void)response.EnsureSuccessStatusCode(); auto msi_installer_file_stream = co_await storage::Streams::FileRandomAccessStream::OpenAsync(destination.c_str(), storage::FileAccessMode::ReadWrite, storage::StorageOpenOptions::AllowReadersAndWriters, storage::Streams::FileOpenDisposition::CreateAlways); co_await response.Content().WriteToStreamAsync(msi_installer_file_stream); + msi_installer_file_stream.Close(); } std::future try_autoupdate(const bool download_updates_automatically) @@ -244,7 +245,7 @@ namespace updating { try { - co_await attempt_to_download_installer(installer_download_dst, new_version->msi_download_url); + co_await try_download_file(installer_download_dst, new_version->msi_download_url); download_success = true; break; } diff --git a/src/common/updating/updating.h b/src/common/updating/updating.h index e9ee25cff8..1f76288a74 100644 --- a/src/common/updating/updating.h +++ b/src/common/updating/updating.h @@ -9,6 +9,8 @@ namespace updating { + std::future try_download_file(const std::filesystem::path& destination, const winrt::Windows::Foundation::Uri& url); + std::wstring get_msi_package_path(); bool uninstall_msi_version(const std::wstring& package_path); bool offer_msi_uninstallation();