Skip to content

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

Open
shmuelie wants to merge 7 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 7 commits into
microsoft:mainfrom
shmuelie:user/senglard/sdk-packaging-project

Conversation

@shmuelie
Copy link
Copy Markdown

@shmuelie shmuelie commented Jun 4, 2026

Summary

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

SDK-tool discovery, MSIX packing, and signing 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:

Default (CLI/CI): Publish -> Merge -> Validate -> CopyAssets -> ResourceIndex -> Pack -> Sign

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

# Target Description Backed by
1 PublishToLayout Publishes each ProjectReference with LayoutDir dotnet publish
2 MergeAppxManifest Multi-section fragment merge + version/arch stamping MergeAppxFragments task
3 ValidateAppxManifest XML well-formedness + required elements + duplicate IDs ValidateAppxManifest task
4 CopyMsixAssets Images + MsixContent items MSBuild Copy
5 GenerateResourceIndex MakePri.exe for .resw (auto/true/false) WinAppSdkGetSdkFileFullPath + MakePri
6 PackMsix Packs the layout into the .msix WinAppSdkMakeAppxPack
7 SignMsix Optional signing (+ timestamp / Azure) WinAppSdkSignAppxPackage

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):

  • WinAppSdkGetSdkFileFullPath — locates MakeAppx / SignTool / MakePri in the installed Windows SDK
  • WinAppSdkMakeAppxPack — packs the layout into the .msix from a generated [Files] mapping file
  • WinAppSdkSignAppxPackage — signs the .msix, with timestamp and Azure Code Signing / Key Vault support

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 small RoslynCodeTaskFactory inline task resolves the latest installed Windows SDK version for TargetPlatformVersion (honoring MsixWindowsSdkVersion when set).

Key Features

  • Compiled tasks (netstandard2.0) — MergeAppxFragments, ValidateAppxManifest; SDK-tool discovery, packing, and signing come from the Microsoft.Windows.SDK.BuildTools.MSIX package
  • 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 files
  • Deploy-on-build — auto-registers layout when building in VS (layout mode skips Pack/Sign)
  • VS Property Page — XAML Rule file with 10 properties across 4 categories (no VSIX needed)
  • Clean integration — unregisters package + removes layout on clean
  • 21 configurable properties (incl. MsixSdkBuildToolsVersion, MsixWindowsSdkVersion, MsixHashAlgorithmId)

What's Included

SDK Project (src/MsixPackaging/)

  • Microsoft.Build.MsixPackaging.csproj — netstandard2.0, follows 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.targets — BuildMsix orchestrator + targets, using the build-tools package tasks
  • Sdk/Microsoft.Build.MsixPackaging.PropertyPage.xaml — VS Project Properties page
  • 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, indentation
  • ValidateAppxManifestTests — valid manifest, missing elements, duplicate IDs, malformed XML, invalid version

Sample (samples/MsixPackaging/)

  • SampleConsoleApp/ — trivial console app with AppxFragment.xml and MsixImages/
  • SamplePackaging.msbuildproj — consumes the SDK from the source tree (builds a real .msix)
  • Package.base.appxmanifest — minimal manifest template

Repo Conventions

  • Naming — package Microsoft.Build.MsixPackaging, task namespace Microsoft.Build.MsixPackaging.Tasks
  • SDK tool discovery / pack / signMicrosoft.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)

Testing

  • dotnet build --no-incremental — SDK compiles with 0 warnings, 0 errors (StyleCop clean)
  • dotnet test — 14/14 tests pass across all 4 TFMs
  • Sample builds end-to-end and produces a valid .msix (publish -> merge -> validate -> copy -> pack)

shmuelie and others added 7 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>
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