Skip to content

Microsoft.Build.MsixPackaging — reusable MSBuild SDK for multi-app MSIX packaging#672

Open
shmuelie wants to merge 18 commits into
microsoft:mainfrom
shmuelie:user/senglard/sdk-packaging-project
Open

Microsoft.Build.MsixPackaging — reusable MSBuild SDK for multi-app MSIX packaging#672
shmuelie wants to merge 18 commits into
microsoft:mainfrom
shmuelie:user/senglard/sdk-packaging-project

Conversation

@shmuelie

@shmuelie shmuelie commented Jun 4, 2026

Copy link
Copy Markdown

Summary

New SDK project: Microsoft.Build.MsixPackaging — a reusable MSBuild SDK that packages multiple .NET projects into a sideloadable MSIX (single package or multi-architecture bundle) using per-project AppxFragment.xml manifest entries. Replaces WAP/DesktopBridge with a transparent, SDK-style workflow.

SDK-tool discovery, packing, signing, symbol packaging, and Store packaging use the compiled MSBuild tasks in the Microsoft.Windows.SDK.BuildTools.MSIX package, so the SDK relies on supported Windows SDK tooling.

Consumer Experience

<Project Sdk="Microsoft.Build.MsixPackaging/1.0.0">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <MsixFileName>MyAppBundle</MsixFileName>
    <MsixPackageVersion>1.0.0.0</MsixPackageVersion>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\App1\App1.csproj" LayoutDir="App1" />
    <MsixContent Include="config.json" PackagePath="Assets\config.json" />
  </ItemGroup>
</Project>

Pipeline

BuildMsix orchestrator with a conditional pipeline (opt-in stages no-op unless enabled):

Single package (default): Publish -> Merge -> Validate -> CopyAssets -> ResourceIndex -> Pack -> Symbol -> Sign -> AppInstaller

Bundle (MsixBundleEnabled): per-architecture Publish/Merge/Validate/CopyAssets/ResourceIndex/Pack -> makeappx bundle -> Symbol -> Sign -> AppInstaller -> StoreUpload

VS dev-loop (layout mode): Publish -> Merge -> Validate -> CopyAssets -> ResourceIndex -> DeployLayout (skips Pack/Sign)

Target Description Backed by
PublishToLayout Publishes each ProjectReference with LayoutDir (per-architecture RID in bundle mode) dotnet publish
MergeAppxManifest Multi-section fragment merge + version/arch stamping MergeAppxFragments task
ValidateAppxManifest XML well-formedness + required elements + duplicate IDs ValidateAppxManifest task
CopyMsixAssets Images + MsixContent items MSBuild Copy
GenerateResourceIndex MakePri.exe for .resw (auto/true/false) WinAppSdkGetSdkFileFullPath + MakePri
PackMsix Packs the layout into the .msix WinAppSdkMakeAppxPack
BundleMsix Builds each architecture and combines them into a .msixbundle per-arch pack + makeappx bundle
GenerateMsixSymbolPackage Produces a .msixsym from the layout PDBs WinAppSdkGenerateAppxSymbolPackage
SignMsix Signs the .msix/.msixbundle (cert file, test cert, timestamp, or Azure) WinAppSdkSignAppxPackage (+ validation)
GenerateMsixAppInstaller Writes an .appinstaller for sideload auto-update direct XML generation
CreateMsixUpload Wraps the bundle (and symbol) into a .msixupload WinAppSdkCreateAppStoreContainer

Plus opt-in targets: CleanMsixLayout, InstallMsix, RegisterMsixLayout, UninstallMsix.

Build tooling (Microsoft.Windows.SDK.BuildTools.MSIX)

The SDK uses the package's compiled tasks (namespace Microsoft.Build.Msix): SDK-tool discovery (WinAppSdkGetSdkFileFullPath), packing (WinAppSdkMakeAppxPack), signing (WinAppSdkSignAppxPackage), publisher/certificate validation (WinAppSdkValidatePublisherName, WinAppSdkValidateSigningCertificate), symbol packaging (WinAppSdkGenerateAppxSymbolPackage), and Store container creation (WinAppSdkCreateAppStoreContainer).

Integration (not bundled): the package is injected as a PackageReference from Sdk.props with GeneratePathProperty and ExcludeAssets="build;buildTransitive", so only the task assembly is used and the package's heavyweight WinAppSDK pipeline is not imported. It is restored automatically (consumers do not add it manually). VersionOverride is used under Central Package Management so no central PackageVersion entry is required, and the task assembly is resolved per runtime (net6.0 for Core, net472 for desktop MSBuild). A RoslynCodeTaskFactory inline task resolves the latest installed Windows SDK version for TargetPlatformVersion (honoring MsixWindowsSdkVersion when set).

Key Features

  • Multi-section fragment merge — 4 manifest markers (Applications, Capabilities, Extensions, Dependencies)
  • Manifest validation at build time — catches errors before install
  • Version stamping via MsixPackageVersion (4-part numeric); MsixContent items for arbitrary content
  • Signing — certificate file, auto-generated test certificate (in-memory, matching the manifest Publisher), RFC 3161 timestamping, and Azure Code Signing / Azure Key Vault; the Publisher is validated against the certificate before signing
  • Symbol package (.msixsym) for Partner Center crash analysis
  • App Installer (.appinstaller) for sideload auto-update (references the bundle or the .msix)
  • Multi-architecture bundle (.msixbundle) across MsixBundlePlatforms (e.g. x64|x86|arm64)
  • Store upload (.msixupload) wrapping the bundle and symbol for Partner Center
  • Deploy-on-build — auto-registers the layout when building in VS (layout mode skips Pack/Sign)
  • VS Property Page — XAML Rule file across Package / Deployment / Signing / Bundle / Distribution / Resources categories (no VSIX needed)
  • Clean integration — unregisters package + removes layout on clean

All signing, symbol, App Installer, bundle, and Store-upload features are opt-in and off by default; the default build produces a single unsigned .msix.

What's Included

SDK Project (src/MsixPackaging/)

  • Microsoft.Build.MsixPackaging.csproj — netstandard2.0, follows the Artifacts pattern
  • Tasks/ — 2 compiled MSBuild tasks (MergeAppxFragments, ValidateAppxManifest)
  • Sdk/Sdk.props — NoTargets import, property defaults, build-tools package injection, VS property page
  • Sdk/Sdk.targetsBuildMsix orchestrator and all targets, using the build-tools package tasks
  • Sdk/Microsoft.Build.MsixPackaging.PropertyPage.xaml — VS Project Properties page
  • Sdk/New-MsixTestCertificate.ps1 — generates an in-memory self-signed test certificate
  • build/.props/.targets for traditional NuGet package consumption
  • version.json (1.0), README.md

Unit Tests (src/MsixPackaging.UnitTests/)

  • 14 tests across net472, net8.0, net9.0, net10.0
  • MergeAppxFragmentsTests — version validation, fragment detection, attribute patching (version/architecture), indentation
  • ValidateAppxManifestTests — valid manifest, missing elements, duplicate IDs, malformed XML, invalid version

Sample (samples/MsixPackaging/)

  • SampleConsoleApp/ — console app with AppxFragment.xml, MsixImages/, and <RuntimeIdentifiers> for bundling
  • SamplePackaging.msbuildproj — consumes the SDK; the default build demonstrates test-certificate signing, the symbol package, and the App Installer, with the multi-architecture bundle and Store upload available via documented opt-in properties
  • Package.base.appxmanifest — minimal manifest template

Repo Conventions

  • Naming — package Microsoft.Build.MsixPackaging, task namespace Microsoft.Build.MsixPackaging.Tasks
  • SDK tool discovery / pack / sign / symbol / uploadMicrosoft.Windows.SDK.BuildTools.MSIX tasks
  • Package versions — Central Package Management
  • NoTargets version — from global.json msbuild-sdks
  • SigningFilesToSign (Authenticode + StrongName) for the task DLL
  • Versioning — Nerdbank.GitVersioning (1.0)
  • Task packagingBuildOutputTargetFolder=build\ (Artifacts pattern)

Notes

  • Referenced apps must declare the bundle architectures in <RuntimeIdentifiers> so restore covers them; MsixStoreUploadEnabled requires a bundle.
  • MsixToolArchitecture is a no-op (the build-tools package resolves tool architecture automatically); retained for backward compatibility.

Testing

  • dotnet build --no-incremental — SDK compiles with 0 warnings, 0 errors (StyleCop clean) and packs (task DLL + Sdk/ content)
  • dotnet test — 14/14 tests pass across all 4 TFMs
  • Sample default build — signed .msix + .msixsym + .appinstaller
  • Sample bundle build (x64|x86|arm64) — signed .msixbundle (all architectures) + .msixupload + .msixsym + .appinstaller

shmuelie and others added 18 commits June 4, 2026 15:00
New MSBuild SDK that packages multiple .NET projects into a single
sideloadable MSIX using per-project AppxFragment.xml manifest merging.
Replaces WAP/DesktopBridge with a transparent, SDK-style workflow.

Includes:
- 3 compiled MSBuild tasks (netstandard2.0):
  - MergeAppxFragments: multi-section fragment merge + version/arch stamping
  - ValidateAppxManifest: XML well-formedness + required elements + duplicate IDs
  - FindWindowsSdkTool: locates MakeAppx/SignTool/MakePri from Windows SDK
- Sdk.props: imports NoTargets, defines 19 configurable properties
- Sdk.targets: BuildMsix orchestrator with 7-stage pipeline + 4 opt-in targets
- VS Property Page (XAML Rule) with 10 properties across 4 categories
- build/ files for traditional NuGet package consumption

Ported from SEnglard Ideas/MsixPackagingSdk prototype and adapted to
MSBuildSdks repo conventions (Microsoft.Build.* naming, central package
management, signing, Nerdbank.GitVersioning).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
15 xunit tests covering the 3 compiled MSBuild tasks:
- MergeAppxFragmentsTests: version validation, structured fragment detection,
  Identity attribute patching, indentation formatting
- ValidateAppxManifestTests: valid manifest, missing Identity, duplicate
  Application IDs, malformed XML, missing file, invalid version
- FindWindowsSdkToolTests: host architecture detection

All tests pass across net472, net8.0, net9.0, and net10.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Minimal consumer project demonstrating the SDK:
- SampleConsoleApp: trivial console app with AppxFragment.xml
- SamplePackaging.msbuildproj: imports SDK from source tree
- Package.base.appxmanifest: manifest template with fragment marker
- Directory.Build.props: overrides task assembly path for local dev

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MergeAppxFragments.cs and ValidateAppxManifest.cs had StyleCop violations
(missing file headers, using order, omitted braces, member ordering, blank
lines) that were masked by incremental analyzer caching and only surfaced on
a clean build. Rewrap to satisfy the analyzers with no behavioral change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… signing

Replace the SDK's hand-rolled Windows SDK tool discovery and the raw Exec
calls to MakeAppx/SignTool with the compiled MSBuild tasks from the
Microsoft.Windows.SDK.BuildTools.MSIX package (namespace Microsoft.Build.Msix):

- Tool discovery: FindWindowsSdkTool (custom C# task) -> WinAppSdkGetSdkFileFullPath,
  used to locate MakeAppx.exe, SignTool.exe, and MakePri.exe.
- Packing: PackMsix Exec(makeappx /d) -> WinAppSdkMakeAppxPack, packing from a
  generated [Files] mapping file built from the layout directory.
- Signing: SignMsix Exec(signtool) -> WinAppSdkSignAppxPackage (adds timestamp
  and Azure Code Signing / Key Vault support).

Integration (no bundling): the package is injected as a package reference from
Sdk.props with GeneratePathProperty and ExcludeAssets=build;buildTransitive, so
only the task assembly is used and the package's heavyweight WinAppSDK pipeline
is not imported. VersionOverride is used under Central Package Management so no
central PackageVersion entry is required. The task assembly is resolved per
runtime (net6.0 for Core, net472 for desktop MSBuild).

A small RoslynCodeTaskFactory inline task resolves the latest installed Windows
SDK version for TargetPlatformVersion (honoring MsixWindowsSdkVersion when set).

Removes FindWindowsSdkTool and its unit test. New properties:
MsixSdkBuildToolsVersion, MsixWindowsSdkVersion, MsixHashAlgorithmId.
MsixToolArchitecture is now a no-op (architecture resolved by the package).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sample packaging project was nested a level too deep, so its SDK imports,
ProjectReference, and default Package.base.appxmanifest/Images paths did not
resolve (the project was never built in the original change). Flatten it to
samples/MsixPackaging/ to match the sibling manifest, images, and app project,
and point the dev-time _MsixSdkTasksAssembly at the artifacts output.

Verified end-to-end: the sample now publishes, merges fragments, validates,
and packs a valid .msix via the new build-tools pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Surface the timestamping, Azure Code Signing (Trusted Signing), and Azure
Key Vault parameters that the WinAppSdkSignAppxPackage task already accepts:

- MsixTimestampUrl / MsixTimestampDigestAlgorithm
- MsixAzureCodeSigningEnabled + Dlib/Endpoint/AccountName/CertificateProfileName
- MsixAzureKeyVaultEnabled + Dlib/Url/CertificateId

SignMsix now runs when signing is enabled and either a certificate file is
supplied or an Azure signing mode is enabled (Azure modes need no .pfx).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Before signing with a certificate file, run WinAppSdkValidateSigningCertificate
and WinAppSdkValidatePublisherName so a mismatch between the manifest Publisher
and the signing certificate fails the build early with a clear message.

Gated by MsixValidateSigningCertificate (default true) and skipped for the
Azure signing modes, which do not use a local certificate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When MsixSigningEnabled=true, MsixGenerateTestCertificate=true, and no
certificate is supplied, generate a throwaway self-signed code-signing
certificate whose subject matches the manifest Publisher, sign with it, and
delete the temporary .pfx afterward.

The certificate is created in memory via .NET CertificateRequest in a shipped
PowerShell script (New-MsixTestCertificate.ps1) — nothing is written to the
certificate store, so no store cleanup is required and it works under both
Windows PowerShell and PowerShell 7.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opt-in GenerateMsixSymbolPackage target (MsixSymbolPackageEnabled) collects the
PDBs from the layout and produces a .msixsym via WinAppSdkGenerateAppxSymbolPackage
for Partner Center crash analysis. Output defaults to the .msix path with a
.msixsym extension and can be overridden with MsixSymbolPackageOutput. Runs after
PackMsix in the build pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opt-in GenerateMsixAppInstaller target (MsixAppInstallerEnabled) writes an
.appinstaller file for sideload auto-update. Identity (Name/Publisher/Version/
ProcessorArchitecture) is read from the merged manifest; the hosted package URL
is derived from MsixAppInstallerUri or set explicitly via MsixAppInstallerPackageUri.
Update cadence is controlled by MsixAppInstallerUpdateCheckHours (OnLaunch).
References a MainBundle when MsixBundleEnabled=true, otherwise a MainPackage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opt-in MsixBundleEnabled builds one MSIX per architecture in MsixBundlePlatforms
(e.g. x64|x86|arm64) and combines them into a .msixbundle:

- BundleMsix drives a per-architecture loop (target-batched) that re-runs the
  pack chain with MsixTargetArchitecture and a win-<arch> RuntimeIdentifier,
  producing one arch-stamped .msix each into a clean packages directory, then
  runs 'makeappx bundle'.
- A representative architecture layout is copied to MsixLayoutDir so the
  signing, symbol, and App Installer targets can read identity and PDBs.
- Downstream targets act on _MsixPrimaryPackage, which is the bundle in bundle
  mode and the .msix otherwise, so signing/App Installer work for both.

Referenced apps must declare the target architectures in <RuntimeIdentifiers>
so restore covers them. The default single-architecture pipeline is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Opt-in MsixStoreUploadEnabled wraps the .msixbundle and, when present, the
.msixsym symbol package into a .msixupload container for Partner Center via
WinAppSdkCreateAppStoreContainer. Requires MsixBundleEnabled (a .msixupload
wraps a bundle); errors clearly otherwise. Runs at the end of the bundle pipeline.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Bundle and Distribution categories and surface the new opt-in properties in
the Visual Studio Project Properties UI: test-certificate generation, certificate
validation, and timestamping (Signing); bundle build, bundle platforms, and Store
upload (Bundle); symbol package and App Installer (Distribution).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sample build now exercises test-certificate signing, certificate validation,
the symbol package, and the App Installer end-to-end. SampleConsoleApp declares
<RuntimeIdentifiers> so the multi-architecture bundle and Store-upload paths can
be enabled via documented opt-in properties.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add property tables for signing (test certificate, validation, timestamp,
Azure) and distribution/bundling (symbol package, App Installer, bundle, Store
upload), plus a Signing section and a Multi-architecture bundles & distribution
section, and update the VS property page category listing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Sdk\New-MsixTestCertificate.ps1 helper triggers NuGet's legacy
PowerShell-install-script validation (NU5110/NU5111), which the repo promotes
to errors. The script is invoked by the SDK targets, not by NuGet install, so
suppress those warnings for this package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant