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();