<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This pull request adds support for building, installing, and managing a shared sparse MSIX package to grant package identity to select Win32 components in PowerToys. It introduces a new `PackageIdentity` project, updates the installer to handle the new MSIX package during install/uninstall, and provides developer documentation for working with the sparse package. Additionally, new dependencies and signing rules are included to support these changes. **Sparse Package Identity Support** * Added new `PackageIdentity` project to the solution for building the sparse MSIX package, and included it in solution/project build configurations (`PowerToys.sln`). [[1]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R29) [[2]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R54-R55) [[3]](diffhunk://#diff-ca837ce490070b91656ffffe31cbad8865ba9174e0f020231f77baf35ff3f811R873-R880) * Added developer documentation (`sparse-package.md`) and updated documentation indexes to describe how to build, register, and consume the sparse MSIX package. [[1]](diffhunk://#diff-b4e39fb55a49c6de336d5847d75a55dd1d14840578da0ed9130f0130b61b34aaR1-R87) [[2]](diffhunk://#diff-d0f204e503506a26ef2aa3605a8d64ac353393526fb5dcf48d4287c821f3edbcR31) [[3]](diffhunk://#diff-430296c8d28f70d8a0164b44d7dfc30ffb1fb32466dad181947f35885b7f28d1R13) **Installer Enhancements** * Implemented new custom actions in the installer to install and uninstall the `PowerToysSparse.msix` package, supporting both per-user and machine-level scenarios (`CustomAction.cpp`, `CustomAction.def`, `Product.wxs`). [[1]](diffhunk://#diff-a7680a20bf0315cff463a95588a100c99d2afc53030f6e947f1f1dcaca5eefd7R597-R806) [[2]](diffhunk://#diff-79daec0ccfcea63a2f3acb7d811b8b508529921123c754111bbccbea98b2bd74R36-R37) [[3]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR115) [[4]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR127) [[5]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR149) [[6]](diffhunk://#diff-c12203517db7cde9ad34df9e6611457d1d3c7bc8eb7d58e06739887d3c1034afR205-R210) **Build and Dependency Updates** * Added new NuGet package dependencies for Windows App SDK AI and Runtime to support MSIX and sparse package features (`Directory.Packages.props`). * Updated signing pipeline to include the new `PowerToysSparse.msix` artifact (`.pipelines/ESRPSigning_core.json`). <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com>
PowerToys sparse package identity
This document describes how to build, sign, register, and consume the shared sparse MSIX package that grants package identity to select Win32 components of PowerToys.
Package overview
The sparse package lives under src/PackageIdentity. It produces a payload-free MSIX whose Identity matches Microsoft.PowerToys.SparseApp. The manifest contains one entry per Win32 surface that should run with identity (for example Settings, PowerOCR, Image Resizer).
The MSIX contains only metadata. When the package is registered you must point
-ExternalLocationto the output folder that hosts the Win32 binaries (for examplex64\Release).
Building the sparse package locally
Two options are available:
- Build the utility project from Visual Studio:
PackageIdentity.vcxprojdefines aGenerateSparsePackagetarget that runs beforePrepareForBuildand invokes the helper script automatically. - Invoke the helper script directly from PowerShell:
$repoRoot = "C:/git/PowerToys"
pwsh "$repoRoot/src/PackageIdentity/BuildSparsePackage.ps1" -Platform x64 -Configuration Release
Supported switches:
-Cleanremoves previousbin/objoutputs and uninstalls existing installation.-ForceCertregenerates the local dev certificate (.pfx/.cer/.pwd/.thumbprint) undersrc/PackageIdentity/.user.-NoSignskips signing. The MSIX still builds but must be signed before deployment.-CIBuild(or setting$env:CIBuild = 'true') keeps the manifest publisher intact and skips the local cert substitution.
The script determines the proper makeappx.exe for the host build machine (x64 on typical developer boxes) and creates PowerToysSparse.msix in {repo}\<Platform>\<Configuration>.
After packaging finishes, the helper also emits
src/PackageIdentity/.user/PowerToysSparse.publisher.txt. This file mirrors the publisher string Windows will see once the sparse package is registered, which downstream projects can read to stay in sync when generating their own manifests.
Local signing basics
When -NoSign is not used the script generates (or reuses) a development certificate and signs the package via signtool.exe:
-
Artifacts are stored in
src/PackageIdentity/.user/PowerToysSparse.certificate.sample.*(.cerand.thumbprint). -
Install the
.cerintoCurrentUser→TrustedPeople(andTrustedRoot, if necessary) so Windows trusts the signature:$repoRoot = "C:/git/PowerToys" Import-Certificate -FilePath "$repoRoot/src/PackageIdentity/.user/PowerToysSparse.certificate.sample.cer" -CertStoreLocation Cert:\CurrentUser\TrustedPeople -
The private key stays in the current user's personal certificate store.
Registering or unregistering the package
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
# Re-register after manifest tweaks only
Add-AppxPackage -Register (Join-Path $repoRoot "src/PackageIdentity/AppxManifest.xml") -ExternalLocation $outputRoot -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.
CI-specific guidance
- Pass
-CIBuildtoBuildSparsePackage.ps1(or build withmsbuild PackageIdentity.vcxproj /p:CIBuild=true). This prevents the script from rewriting the manifest publisher to the local dev certificate subject. - The project automatically adds
-NoSignonly when$(CIBuild)istrue. Local Debug and Release builds are signed with the development certificate. - Make sure the agent trusts whichever certificate signs the package. If the package remains unsigned (
-NoSign) it cannot be installed on test machines until it is signed.
Consuming the identity from other components
-
Add a new
<Application>entry insidesrc/PackageIdentity/AppxManifest.xml. Use a uniqueId(for examplePowerToys.MyModuleUI) and setExecutableto the Win32 binary relative to the-ExternalLocationroot. -
Ensure the binary is copied into the platform/configuration output folder (
x64\Release,ARM64\Debug, etc.) so the sparse package can locate it. -
Embed a sparse identity manifest in the Win32 binary so it binds to the MSIX identity at runtime. The manifest must declare an
<msix>element withpackageName="Microsoft.PowerToys.SparseApp",applicationIdmatching the<Application Id>, and apublisherthat matches the sparse package. Keep the manifest’s publisher in sync withsrc/PackageIdentity/.user/PowerToysSparse.publisher.txt(emitted byBuildSparsePackage.ps1). Seesrc/modules/imageresizer/ui/ImageResizerUI.csprojfor an example that pointsApplicationManifesttoImageResizerUI.dev.manifestfor local builds and switches toImageResizerUI.prod.manifestwhen$(CIBuild)istrue. -
Register or re-register the sparse package so Windows learns about the new application Id.
-
To launch the Win32 surface with identity, use the
shell:AppsFolderactivation form (for example:shell:AppsFolder\Microsoft.PowerToys.SparseApp_<PackageFamilyName>!PowerToys.MyModuleUI) or activate it viaIApplicationActivationManager::ActivateApplicationusing the same AppUserModelID.- For locally built packages, resolve the
<PackageFamilyName>withGet-AppxPackage -Name Microsoft.PowerToys.SparseApp | Select-Object -ExpandProperty PackageFamilyName. - Store-distributed builds use
Microsoft.PowerToys.SparseApp_8wekyb3d8bbwe. Local developer builds created by this script typically use a different family name derived from the dev certificate.
- For locally built packages, resolve the
-
Context menu handlers or other launchers should fall back to the unpackaged executable path for environments where the sparse package is not present.
Troubleshooting tips
Program 'makeappx.exe' failed to run: make sure you are running an x64 PowerShell host. The script now chooses the appropriate makeappx automatically; update your repo if the log still points to an ARM64 binary.HRESULT 0x800B0109 (trust failure): install the development certificate into bothTrustedPeopleandTrustedRootstores for the current user.- Stale registration: remove the package with
Remove-AppxPackageand re-run the script with-Cleanto rebuild from scratch.