Skip to content

BuildTools.WinApp: auto-install framework MSIX before dotnet run#531

Draft
yeelam-gordon wants to merge 1 commit into
mainfrom
gordon/dotnet-run-auto-install-framework
Draft

BuildTools.WinApp: auto-install framework MSIX before dotnet run#531
yeelam-gordon wants to merge 1 commit into
mainfrom
gordon/dotnet-run-auto-install-framework

Conversation

@yeelam-gordon
Copy link
Copy Markdown

@yeelam-gordon yeelam-gordon commented May 12, 2026

Problem

The Microsoft.Windows.SDK.BuildTools.WinApp targets currently intercept dotnet run for packaged apps and route execution through winapp run (via _WinAppPrepareRunArguments hooking ComputeRunArguments). However, neither layer installs the WinAppSDK framework MSIX that the app''s AppxManifest.xml depends on. On a clean machine, dotnet run therefore fails at PackageManager.RegisterPackageAsync inside PackageRegistrationService.RegisterLooseLayoutAsync with 0x80073CF3:

Failed to register package: Package failed updates, dependency or conflict validation.
Windows cannot install package ... because this package depends on a framework that
could not be found. Provide the framework "Microsoft.WindowsAppRuntime.N" ... to install.

Users are then forced to fall back to:

  • running WindowsAppRuntimeInstall-x64.exe manually, or
  • setting <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> and accepting the size cost.

Neither matches the "winapp init && dotnet run just works" UX the support doc promises.

Why "I can''t repro this": any typical developer machine already has the WinAppSDK runtime pinned by a system/Store-managed Main package (MicrosoftCorporationII.WinAppRuntime.Main.N), which makes the framework un-removable from a normal user prompt. The bug only manifests on machines that never had WinAppSDK installed system-wide — clean CI runners, fresh VMs, end-user boxes outside the WinAppSDK install pipeline. That is exactly the audience winapp init && dotnet run is supposed to serve. See repro instructions below if you want to verify locally.

Root cause

The framework MSIX is already in the user''s NuGet cache. Microsoft.WindowsAppSDK.Runtime ships the framework MSIXes at:

tools/MSIX/win10-{x86,x64,arm64,arm64ec}/Microsoft.WindowsAppRuntime.{N}.msix

…and exposes them to MSBuild as @(AppxPackageRegistration) items via buildTransitive/Microsoft.WindowsAppSDK.AppXReference.props. That is the same item group Visual Studio''s Deploy action consumes — it''s why F5 in VS doesn''t need a separate runtime install. We just weren''t reading it.

Fix

Add a new _WinAppInstallFrameworkDependencies target that:

  • runs before _WinAppBuildRunArgs (and therefore before both _WinAppPrepareRunArguments for dotnet run and RunPackagedApp for the standalone target),
  • filters @(AppxPackageRegistration) to entries whose Architecture metadata matches the current $(Platform) (lower-cased),
  • shells out to powershell -Command Add-AppxPackage with a Get-AppxPackage precheck so already-installed frameworks no-op.

Total change: +48 / -1 in one file.

Scope clarification — we install the framework MSIX only, by design

The Runtime NuGet ships four MSIXes per architecture: Microsoft.WindowsAppRuntime.{N}.msix (the framework), .Main.{N}.msix, .DDLM.{N}.msix, and .Singleton.{N}.msix. This patch installs only the framework, and that is deliberate:

  • An app''s AppxManifest.xml declares <PackageDependency Name="Microsoft.WindowsAppRuntime.N"/> — never Main/DDLM/Singleton.
  • Add-AppxPackage -Register only fails when a <PackageDependency> can''t be resolved. Missing Main/DDLM/Singleton does not cause registration failure.
  • The framework MSIX carries the actual runtime DLLs the app loads. Main/DDLM/Singleton are for end-user deployment scenarios driven by WindowsAppRuntimeInstall.exe, not for app activation.
  • The Runtime NuGet itself reflects this: it lists only the framework MSIX as <AppxPackageRegistration> in AppXReference.props. We follow the NuGet''s own contract — installing the siblings would be an unsolicited surprise install on the user''s machine and is not what VS Deploy does either.

Empirical verification

Scaffolded dotnet new winui -n HelloWinUI against a machine where the matching framework was not registered (verified via Get-AppxPackage -Name Microsoft.WindowsAppRuntime.N).

Before (without this patch):

> winapp run .\bin\x64\Debug\net10.0-windows10.0.26100.0\win-x64 --json
{ "Error": "Failed to register package: ... package depends on a framework that could not be found.
   Provide the framework \"Microsoft.WindowsAppRuntime.2\" ... minimum version 2.0.1.0 ..." }
ExitCode: 1

After installing the framework MSIX from the NuGet cache (which is exactly what this patch automates):

> Add-AppxPackage -Path ...microsoft.windowsappsdk.runtime\2.0.1\tools\MSIX\win10-x64\Microsoft.WindowsAppRuntime.2.msix
> winapp run .\bin\x64\Debug\net10.0-windows10.0.26100.0\win-x64 --json
{ "AUMID": "9798BBED-8919-4607-9CFC-C54F133DDF83_1z32rh13vfry6!App", "ProcessId": 82868 }
ExitCode: 0

App launched successfully.

Reproducing locally

If you want to repro on a dev box, you have to first remove whatever is pinning the framework. On most dev machines that is a Store-managed Main package. Working repro shape:

# 1. Confirm what (if anything) holds the framework hostage:
Get-AppxPackage | Where-Object { $_.Dependencies.PackageFullName -like "Microsoft.WindowsAppRuntime.N*" } |
    Format-Table Name, SignatureKind, PackageFullName

# 2. From an ELEVATED prompt, remove the pinning packages:
Remove-AppxPackage -AllUsers <pinning-package-full-name>

# 3. Now remove the framework itself:
Remove-AppxPackage -Package <framework-full-name>

# 4. Verify it''s gone:
Get-AppxPackage -Name "Microsoft.WindowsAppRuntime.N"   # expect: empty

# 5. Run the app — expect 0x80073CF3 without this PR, success with it:
dotnet run -c Debug -p:Platform=x64

Easier alternative: spin up a fresh Windows VM or a windows-latest GitHub Actions runner — those don''t have the Store pinning packages preinstalled.

Implementation choice — inline PowerShell vs. new CLI command

I went with inline powershell -Command Add-AppxPackage because it ships today without any CLI changes. A cleaner long-term shape is a new winapp install-package <msix> [--if-missing] subcommand, since PackageRegistrationService.InstallPackageAsync already exists in the source — it just needs a CLI surface. Happy to follow up with that as a separate PR if maintainers prefer that direction; let me know and I''ll swap the <Exec> over.

Caveats

  • Requires Developer Mode (already validated by _WinAppValidateRunSupport).
  • Architecture filter assumes $(Platform) is one of x86 / x64 / ARM64 / arm64ec (lower-cased to match the item metadata). AnyCPU is already rejected upstream by the MSIX targets (APPX0504), so no extra guard is needed here.
  • Version pinning comes from the NuGet itself (MicrosoftWindowsAppSDKFoundationAppXVersion.props), so users always get the exact framework build the NuGet was authored against.

Doc follow-up (not in this PR)

docs/dotnet-run-support.md "Production Blockers" section doesn''t list framework provisioning as a gap today. Worth an update after this lands.


Marked as draft for maintainer review of the implementation choice (inline PowerShell vs. promote to CLI command) before final review.

The BuildTools.WinApp targets currently intercept `dotnet run` for
packaged apps and route execution through `winapp run`. However,
neither layer installs the WinAppSDK framework MSIX that the app's
AppxManifest depends on, so `dotnet run` fails on a clean machine
with 0x80073CF3:

  "Windows cannot install package ... because this package depends
   on a framework that could not be found. Provide the framework
   'Microsoft.WindowsAppRuntime.N' ... to install."

The framework MSIX is already shipped inside the
Microsoft.WindowsAppSDK.Runtime NuGet (tools\MSIX\win10-{arch}\) and
exposed to MSBuild as @(AppxPackageRegistration) items via
buildTransitive\Microsoft.WindowsAppSDK.AppXReference.props -- the
same item group Visual Studio Deploy consumes. We just weren't
reading it.

This change adds a new `_WinAppInstallFrameworkDependencies` target
that:
  * runs before _WinAppBuildRunArgs (and therefore before
    _WinAppPrepareRunArguments and RunPackagedApp)
  * filters @(AppxPackageRegistration) to entries whose Architecture
    metadata matches the current $(Platform)
  * shells out to `powershell -Command Add-AppxPackage` with a
    Get-AppxPackage precheck so already-installed frameworks no-op

After this patch, `winapp init && dotnet run` truly works end-to-end
on a clean box -- no WindowsAppRuntimeInstall.exe and no
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
workaround required.

Empirical verification (HelloWinUI scaffolded from `dotnet new winui`,
depending on Microsoft.WindowsAppRuntime.2 v2.0.1.0, on a machine
without that framework installed):

  Before: winapp run -> exit 1, "package depends on a framework
          that could not be found".
  Manual Add-AppxPackage of the MSIX from the NuGet cache, then
  re-run: winapp run -> exit 0, AUMID + PID returned, app launched.

Follow-ups (not in this PR):
  * Promote the inline powershell Exec to a `winapp install-package
    <msix> [--if-missing]` subcommand
    (PackageRegistrationService.InstallPackageAsync already exists
    in the CLI source -- just needs a CLI surface).
  * Consider iterating Main / Singleton MSIXes from
    tools\MSIX\win10-<arch>\ for full parity with VS Deploy.
  * Update docs/dotnet-run-support.md "Production Blockers" --
    framework runtime provisioning is no longer a blocker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 30.87 MB 30.87 MB ✅ 0.0 KB (0.00%)
CLI (x64) 31.23 MB 31.23 MB ✅ 0.0 KB (0.00%)
MSIX (ARM64) 13.03 MB 13.03 MB 📈 +0.1 KB (+0.00%)
MSIX (x64) 13.83 MB 13.83 MB 📈 +0.1 KB (+0.00%)
NPM Package 27.09 MB 27.09 MB 📉 -0.2 KB (-0.00%)
NuGet Package 27.17 MB 27.17 MB 📈 +1.2 KB (+0.00%)
VS Code Extension 19.91 MB 19.91 MB 📉 -0.5 KB (-0.00%)

Test Results

993 passed, 1 skipped out of 994 tests in 429.6s (-20.6s vs. baseline)

Test Coverage

23.7% line coverage, 39.8% branch coverage · ✅ no change vs. baseline

CLI Startup Time

43ms median (x64, winapp --version) · ✅ no change vs. baseline


Updated 2026-05-12 00:29:55 UTC · commit 011c891 · workflow run

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