From 1aff7ffc83eae6b5097e8d5136142cc6b86719a4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Jun 2025 10:55:36 +0000
Subject: [PATCH 1/3] Initial plan
From efb09b7f46183d70a18ccffc82655f2ac3af4204 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Jun 2025 11:05:10 +0000
Subject: [PATCH 2/3] Implement fix for per-user installer directory
permissions issue
Co-authored-by: lei9444 <39758135+lei9444@users.noreply.github.com>
---
installer/PowerToysSetup/Product.wxs | 18 +++-
.../CustomAction.cpp | 101 +++++++++++++++++-
.../CustomAction.def | 1 +
3 files changed, 118 insertions(+), 2 deletions(-)
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index f15b8a471422..ae7ed21509d4 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -140,6 +140,7 @@
+
@@ -149,7 +150,10 @@
NOT Installed
-
+
+ NOT Installed
+
+
NOT Installed
@@ -252,6 +256,10 @@
Property="UpgradeCommandNotFound"
Value="[INSTALLFOLDER]" />
+
+
@@ -423,6 +431,14 @@
DllEntry="ApplyModulesRegistryChangeSetsCA"
/>
+
+
#include
#include
+#include
+#include
using namespace std;
@@ -1153,7 +1155,104 @@ UINT __stdcall UnRegisterContextMenuPackagesCA(MSIHANDLE hInstall)
return WcaFinalize(er);
}
-UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall)
+UINT __stdcall FixPerUserInstallDirectoryPermissionsCA(MSIHANDLE hInstall)
+{
+ HRESULT hr = S_OK;
+ UINT er = ERROR_SUCCESS;
+ std::wstring installationFolder;
+
+ hr = WcaInitialize(hInstall, "FixPerUserInstallDirectoryPermissions");
+ ExitOnFailure(hr, "Failed to initialize");
+
+ // Only apply this fix for per-user installations
+ LPWSTR currentScope = nullptr;
+ hr = WcaGetProperty(L"InstallScope", ¤tScope);
+ ExitOnFailure(hr, "Failed to get InstallScope property");
+
+ if (!currentScope || std::wstring{currentScope} != L"perUser")
+ {
+ Logger::info(L"Not a per-user installation, skipping directory permission fix");
+ goto LExit;
+ }
+
+ hr = getInstallFolder(hInstall, installationFolder);
+ ExitOnFailure(hr, "Failed to get installFolder.");
+
+ Logger::info(L"Fixing directory permissions for per-user installation at: {}", installationFolder);
+
+ // Get the current user's SID
+ HANDLE hToken = NULL;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ ExitOnFailure(hr, "Failed to open process token");
+ }
+
+ DWORD dwSize = 0;
+ GetTokenInformation(hToken, TokenUser, NULL, 0, &dwSize);
+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ CloseHandle(hToken);
+ ExitOnFailure(hr, "Failed to get token information size");
+ }
+
+ std::vector tokenUserBuffer(dwSize);
+ PTOKEN_USER pTokenUser = reinterpret_cast(tokenUserBuffer.data());
+
+ if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwSize, &dwSize))
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ CloseHandle(hToken);
+ ExitOnFailure(hr, "Failed to get token information");
+ }
+
+ CloseHandle(hToken);
+
+ // Create an ACL that allows full control for the current user
+ PACL pACL = NULL;
+ EXPLICIT_ACCESS ea = {};
+ ea.grfAccessPermissions = GENERIC_ALL;
+ ea.grfAccessMode = SET_ACCESS;
+ ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
+ ea.Trustee.ptstrName = (LPTSTR)pTokenUser->User.Sid;
+
+ DWORD dwRes = SetEntriesInAcl(1, &ea, NULL, &pACL);
+ if (ERROR_SUCCESS != dwRes)
+ {
+ hr = HRESULT_FROM_WIN32(dwRes);
+ ExitOnFailure(hr, "Failed to create ACL");
+ }
+
+ // Apply the ACL to the installation directory
+ dwRes = SetNamedSecurityInfo(
+ const_cast(installationFolder.c_str()),
+ SE_FILE_OBJECT,
+ DACL_SECURITY_INFORMATION,
+ NULL,
+ NULL,
+ pACL,
+ NULL);
+
+ if (pACL)
+ {
+ LocalFree(pACL);
+ }
+
+ if (ERROR_SUCCESS != dwRes)
+ {
+ hr = HRESULT_FROM_WIN32(dwRes);
+ ExitOnFailure(hr, "Failed to set directory permissions");
+ }
+
+ Logger::info(L"Successfully fixed directory permissions for per-user installation");
+
+LExit:
+ er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
+ return WcaFinalize(er);
+}
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def
index 6e42da27c581..1dd4bc3d5a66 100644
--- a/installer/PowerToysSetupCustomActions/CustomAction.def
+++ b/installer/PowerToysSetupCustomActions/CustomAction.def
@@ -28,3 +28,4 @@ EXPORTS
UninstallCommandNotFoundModuleCA
UpgradeCommandNotFoundModuleCA
UnsetAdvancedPasteAPIKeyCA
+ FixPerUserInstallDirectoryPermissionsCA
From e9400e2df8f4bc6d96d28275a30f360e9985be19 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 29 Jun 2025 11:08:12 +0000
Subject: [PATCH 3/3] Improve permission fix to include Interactive Users group
for better preview handler compatibility
Co-authored-by: lei9444 <39758135+lei9444@users.noreply.github.com>
---
.../CustomAction.cpp | 31 +++++++++++++------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp
index 484e68afbb4c..70b3e944ffff 100644
--- a/installer/PowerToysSetupCustomActions/CustomAction.cpp
+++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp
@@ -1209,17 +1209,28 @@ UINT __stdcall FixPerUserInstallDirectoryPermissionsCA(MSIHANDLE hInstall)
CloseHandle(hToken);
- // Create an ACL that allows full control for the current user
+ // Create ACL entries for both the current user and Interactive Users
+ // This ensures preview handlers work properly in different security contexts
PACL pACL = NULL;
- EXPLICIT_ACCESS ea = {};
- ea.grfAccessPermissions = GENERIC_ALL;
- ea.grfAccessMode = SET_ACCESS;
- ea.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
- ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
- ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
- ea.Trustee.ptstrName = (LPTSTR)pTokenUser->User.Sid;
-
- DWORD dwRes = SetEntriesInAcl(1, &ea, NULL, &pACL);
+ EXPLICIT_ACCESS ea[2] = {};
+
+ // Entry for current user
+ ea[0].grfAccessPermissions = FILE_ALL_ACCESS;
+ ea[0].grfAccessMode = SET_ACCESS;
+ ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
+ ea[0].Trustee.ptstrName = (LPTSTR)pTokenUser->User.Sid;
+
+ // Entry for Interactive Users (this helps with preview handlers running in different contexts)
+ ea[1].grfAccessPermissions = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
+ ea[1].grfAccessMode = SET_ACCESS;
+ ea[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
+ ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
+ ea[1].Trustee.ptstrName = const_cast(L"INTERACTIVE");
+
+ DWORD dwRes = SetEntriesInAcl(2, ea, NULL, &pACL);
if (ERROR_SUCCESS != dwRes)
{
hr = HRESULT_FROM_WIN32(dwRes);