From 8536d7b1cd6853e129cef06d6f31b9f5fbc31b74 Mon Sep 17 00:00:00 2001 From: Boliang Zhang <122517415+LegendaryBlair@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:37:59 +0800 Subject: [PATCH] Fix MSIX sparse package DACL contamination breaking File Explorer preview handlers (#47177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Moves the MSIX sparse package's `ExternalLocation` from the PowerToys root install folder to the `WinUI3Apps\` subfolder, isolating DACL contamination from preview handler DLLs. ## Problem On Windows 23H2/24H2/25H2, MSIX sparse package registration adds AppContainer SIDs (`S-1-15-2-*` / `S-1-15-3-*`) to the DACL of the `ExternalLocation` folder. Since `ExternalLocation` pointed to the PowerToys root install folder, this broke File Explorer preview handlers — `prevhost.exe` (running at LOW integrity) could no longer load preview handler DLLs (`.txt`, `.md`, `.pdf`, `.svg`, etc.). ## Fix - **`CustomAction.cpp`**: Changed `ExternalLocation` from `installFolderPath` → `installFolderPath + L"WinUI3Apps\\"` - **`AppxManifest.xml`**: Removed unused PowerOCR `` entry; stripped `WinUI3Apps\` prefix from `Executable` paths (now relative to new ExternalLocation) - **`CmdPal.Ext.PowerToys.csproj`**: Moved `OutputPath` to `WinUI3Apps\` so the AOT-compiled extension EXE resolves correctly under the new ExternalLocation - **WiX installer files**: Updated source/install paths for KBM assets, CmdPal satellite assemblies, and `CommandPalette.Extensions.winmd` that moved with the CmdPal output - **ESRP signing**: Updated CmdPal dll/exe paths to include `WinUI3Apps\` prefix ## Validation | Test | 25H2 | 23H2 | |------|------|------| | DACL isolation (no S-1-15-* on root) | ✅ | ✅ | | Preview handlers (.txt, .md, .pdf, .svg) | ✅ | ✅ | | Peek | ✅ | ✅ | | Context menus (PowerRename, FileLocksmith, ImageResizer, New+) | ✅ | ✅ | | Upgrade path (old → new) | ✅ | ✅ | ## Files changed (12) - `installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp` — core fix - `src/PackageIdentity/AppxManifest.xml` — manifest cleanup - `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj` — output path - `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs` — icon path - `installer/PowerToysSetupVNext/BaseApplications.wxs` — winmd source path - `installer/PowerToysSetupVNext/KeyboardManager.wxs` — KBM assets path - `installer/PowerToysSetupVNext/Resources.wxs` — CmdPal satellite paths - `installer/PowerToysSetupVNext/generateAllFileComponents.ps1` — scan path - `.pipelines/ESRPSigning_core.json` — signing paths - `src/PackageIdentity/BuildSparsePackage.ps1` — dev script hint - `src/PackageIdentity/readme.md` — docs - `doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md` — docs --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .pipelines/ESRPSigning_core.json | 4 ++-- .../powertoys-extension-local-development.md | 6 +++--- .../CustomAction.cpp | 2 +- installer/PowerToysSetupVNext/BaseApplications.wxs | 2 +- installer/PowerToysSetupVNext/KeyboardManager.wxs | 4 ++-- installer/PowerToysSetupVNext/Resources.wxs | 7 ++++--- .../generateAllFileComponents.ps1 | 2 +- src/PackageIdentity/AppxManifest.xml | 14 ++------------ src/PackageIdentity/BuildSparsePackage.ps1 | 5 +++-- src/PackageIdentity/readme.md | 13 +++++++------ .../Helpers/PowerToysResourcesHelper.cs | 2 +- .../Microsoft.CmdPal.Ext.PowerToys.csproj | 6 +++--- 12 files changed, 30 insertions(+), 37 deletions(-) diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 5ae6a931cc..4beb999538 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -264,8 +264,8 @@ "Workspaces.ModuleServices.dll", "Microsoft.CommandPalette.Extensions.dll", "Microsoft.CommandPalette.Extensions.Toolkit.dll", - "Microsoft.CmdPal.Ext.PowerToys.dll", - "Microsoft.CmdPal.Ext.PowerToys.exe", + "WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.dll", + "WinUI3Apps\\Microsoft.CmdPal.Ext.PowerToys.exe", "*Microsoft.CmdPal.UI_*.msix", "PowerToys.DSC.dll", diff --git a/doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md b/doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md index 506adfb61e..f933b7e6db 100644 --- a/doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md +++ b/doc/devdocs/modules/cmdpal/powertoys-extension-local-development.md @@ -2,7 +2,7 @@ This guide is for iterating on `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj`. -The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` at the sparse package root, so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`. +The extension is registered through the shared sparse package defined in `src/PackageIdentity/AppxManifest.xml`. That manifest declares `Microsoft.CmdPal.Ext.PowerToys.exe` relative to the sparse package's ExternalLocation (`WinUI3Apps\` subfolder), so the sparse package and the extension must be built for the same platform and configuration, for example `x64\Debug`. ## Local development loop @@ -30,12 +30,12 @@ The extension is registered through the shared sparse package defined in `src/Pa The command will look like this: ```powershell - Add-AppxPackage -Path "\\\PowerToysSparse.msix" -ExternalLocation "\\" + Add-AppxPackage -Path "\\\PowerToysSparse.msix" -ExternalLocation "\\\WinUI3Apps" ``` 4. Build `src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj` in the same platform and configuration. - This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` directly into the sparse package root, such as `x64\Debug` or `ARM64\Debug`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml`. + This project writes `Microsoft.CmdPal.Ext.PowerToys.exe` into the `WinUI3Apps\` subfolder of the output root, such as `x64\Debug\WinUI3Apps` or `ARM64\Debug\WinUI3Apps`. That matches the `Executable="Microsoft.CmdPal.Ext.PowerToys.exe"` entry in `src/PackageIdentity/AppxManifest.xml` resolved relative to the sparse package's ExternalLocation. 5. Restart Command Palette. diff --git a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp index bb491e4588..890a9fdf6e 100644 --- a/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActionsVNext/CustomAction.cpp @@ -639,7 +639,7 @@ UINT __stdcall InstallPackageIdentityMSIXCA(MSIHANDLE hInstall) try { - std::wstring externalLocation = installFolderPath; // External content location (PowerToys install folder) + std::wstring externalLocation = installFolderPath + L"WinUI3Apps\\"; // External content location (WinUI3Apps subfolder to isolate DACL changes from preview handler DLLs) Uri externalUri{ externalLocation }; // External location URI for sparse package content Uri packageUri{ msixPath }; // The MSIX file URI diff --git a/installer/PowerToysSetupVNext/BaseApplications.wxs b/installer/PowerToysSetupVNext/BaseApplications.wxs index 57a9c71637..4de71b10df 100644 --- a/installer/PowerToysSetupVNext/BaseApplications.wxs +++ b/installer/PowerToysSetupVNext/BaseApplications.wxs @@ -11,7 +11,7 @@ - + diff --git a/installer/PowerToysSetupVNext/KeyboardManager.wxs b/installer/PowerToysSetupVNext/KeyboardManager.wxs index d6948d6598..12efd1b2be 100644 --- a/installer/PowerToysSetupVNext/KeyboardManager.wxs +++ b/installer/PowerToysSetupVNext/KeyboardManager.wxs @@ -4,11 +4,11 @@ - + - + diff --git a/installer/PowerToysSetupVNext/Resources.wxs b/installer/PowerToysSetupVNext/Resources.wxs index 808883920b..f781f336b9 100644 --- a/installer/PowerToysSetupVNext/Resources.wxs +++ b/installer/PowerToysSetupVNext/Resources.wxs @@ -9,7 +9,7 @@ - + @@ -361,11 +361,11 @@ - + - + @@ -433,6 +433,7 @@ + diff --git a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 index ea5d143941..048f587def 100644 --- a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 +++ b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 @@ -191,7 +191,7 @@ Generate-FileList -fileDepsJson "" -fileListName ImageResizerAssetsFiles -wxsFil Generate-FileComponents -fileListName "ImageResizerAssetsFiles" -wxsFilePath $PSScriptRoot\ImageResizer.wxs #KeyboardManager -Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\KeyboardManager" +Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsFiles -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManager" Generate-FileList -fileDepsJson "" -fileListName KeyboardManagerAssetsWinUI3Files -wxsFilePath $PSScriptRoot\KeyboardManager.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\KeyboardManagerEditor" Generate-FileComponents -fileListName "KeyboardManagerAssetsFiles" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs Generate-FileComponents -fileListName "KeyboardManagerAssetsWinUI3Files" -wxsFilePath $PSScriptRoot\KeyboardManager.wxs diff --git a/src/PackageIdentity/AppxManifest.xml b/src/PackageIdentity/AppxManifest.xml index 502cc33ff0..edfa0af5b7 100644 --- a/src/PackageIdentity/AppxManifest.xml +++ b/src/PackageIdentity/AppxManifest.xml @@ -38,17 +38,7 @@ - - - - - + - + The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the output folder that hosts the Win32 binaries (for example `x64\Release`). +> The MSIX contains only metadata. When the package is registered you must point `-ExternalLocation` to the `WinUI3Apps` subfolder of the output folder that hosts the Win32 binaries (for example `x64\Release\WinUI3Apps`). This isolates the DACL changes that MSIX registration applies on Windows 23H2/24H2 to the `WinUI3Apps` folder, keeping the root install folder clean for preview handler DLLs. ## Building the sparse package locally @@ -53,16 +53,17 @@ After `PowerToysSparse.msix` is generated: # First time registration $repoRoot = "C:/git/PowerToys" $outputRoot = Join-Path $repoRoot "x64/Release" -Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $outputRoot +$externalLocation = Join-Path $outputRoot "WinUI3Apps" +Add-AppxPackage -Path (Join-Path $outputRoot "PowerToysSparse.msix") -ExternalLocation $externalLocation # Re-register after manifest tweaks only -Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -ForceApplicationShutdown +Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $externalLocation -ForceApplicationShutdown # Remove the sparse identity Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage ``` -`-ExternalLocation` should match the output folder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes. +`-ExternalLocation` should match the `WinUI3Apps` subfolder that contains the Win32 executables declared in the manifest. Re-run registration whenever the manifest or executable layout changes. ## CI-specific guidance @@ -72,7 +73,7 @@ Get-AppxPackage -Name Microsoft.PowerToys.SparseApp | Remove-AppxPackage ## Consuming the identity from other components -1. Add a new `` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` root. +1. Add a new `` entry inside `src/PackageIdentity/AppxManifest.xml`. Use a unique `Id` (for example `PowerToys.MyModuleUI`) and set `Executable` to the Win32 binary relative to the `-ExternalLocation` (`WinUI3Apps` subfolder). 2. Ensure the binary is copied into the platform/configuration output folder (`x64\Release`, `ARM64\Debug`, etc.) so the sparse package can locate it. 3. Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an `` element with `packageName="Microsoft.PowerToys.SparseApp"`, `applicationId` matching the ``, and a `publisher` that matches the sparse package. Keep the manifest’s publisher in sync with `src/PackageIdentity/.user/PowerToysSparse.publisher.txt` (emitted by `BuildSparsePackage.ps1`). See `src/modules/imageresizer/ui/ImageResizerUI.csproj` for an example that points `ApplicationManifest` to `ImageResizerUI.dev.manifest` for local builds and switches to `ImageResizerUI.prod.manifest` when `$(CIBuild)` is `true`. 4. Register or re-register the sparse package so Windows learns about the new application Id. diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs index bdceae9058..f1ea3002d9 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysResourcesHelper.cs @@ -10,7 +10,7 @@ namespace PowerToysExtension.Helpers; internal static class PowerToysResourcesHelper { private const string AssetsRoot = "Assets\\"; - private const string SettingsIconRoot = "WinUI3Apps\\Assets\\Settings\\Icons\\"; + private const string SettingsIconRoot = "Assets\\Settings\\Icons\\"; internal static IconInfo IconFromSettingsIcon(string fileName) => IconHelpers.FromRelativePath($"{SettingsIconRoot}{fileName}"); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj index 1e8fd040a0..03617e9748 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj @@ -12,7 +12,7 @@ false None false - $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\ false false enable @@ -27,11 +27,11 @@ - + PreserveNewest - + PreserveNewest