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,115 @@ 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 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[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); + 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