dotnet run Support for Packaged WinUI Apps
July 16, 2026 · View on GitHub
This document describes the implementation of dotnet run support for packaged WinUI applications using a custom NuGet package.
Overview
The solution enables developers to run packaged WinUI (WinAppSDK) applications using just the .NET CLI:
winapp init
dotnet run
Architecture
Components
-
Microsoft.Windows.SDK.BuildTools.WinApp (NuGet Package)
- Contains the WinAppCLI binary in
tools/folder - Provides MSBuild targets that hook into
dotnet run - Automatically detects packaged WinUI apps and handles launch
- Contains the WinAppCLI binary in
-
WinAppCLI
- Handles debug identity registration
- Launches packaged apps via Windows Application Activation Manager
How It Works
dotnet run
│
▼
MSBuild Build Target
│
▼
_WinAppValidateRunSupport (validates prerequisites; gated by _WinAppRunSupportActive)
│
▼
_WinAppPrepareRunArguments (overrides RunCommand with CLI path)
│
▼
Run Target (invokes: winapp run --manifest ...)
│
▼
WinAppCLI
├── Creates loose-layout package
├── Registers debug identity
└── Launches via Application Activation Manager
File Structure
src/
├── winapp-NuGet/ # BuildTools.WinApp NuGet package
│ ├── Microsoft.Windows.SDK.BuildTools.WinApp.csproj
│ ├── README.md
│ ├── build/
│ │ ├── Microsoft.Windows.SDK.BuildTools.WinApp.props
│ │ └── Microsoft.Windows.SDK.BuildTools.WinApp.targets
│ └── tools/ # CLI binaries (copied by build script)
│ ├── win-x64/
│ └── win-arm64/
│
samples/
└── winui-app/ # Sample WinUI app for testing
MSBuild Integration Details
Properties (Microsoft.Windows.SDK.BuildTools.WinApp.props)
| Property | Default | Description |
|---|---|---|
EnableWinAppRunSupport | true | Enable/disable the run support functionality |
WinAppManifestPath | Auto-detected | Path to the AppxManifest file |
WinAppLooseLayoutPath | $(OutputPath)AppX\ | Output directory for loose-layout package |
WinAppLaunchArgs | (empty) | Arguments to pass to the app on launch |
WinAppCliPath | (in package) | Path to the winapp.exe CLI |
WinAppRunUseExecutionAlias | false | Launch via execution alias instead of AUMID. Keeps console I/O in the current terminal. Requires uap5:ExecutionAlias in the manifest. Cannot be combined with WinAppRunNoLaunch. |
WinAppRunNoLaunch | false | Only register package identity without launching the app. Cannot be combined with WinAppRunUseExecutionAlias. |
WinAppRunDebugOutput | false | Attach as a debugger to capture OutputDebugString messages and first-chance exceptions. Only one debugger can attach at a time, so Visual Studio or VS Code cannot debug simultaneously. Use WinAppRunNoLaunch instead to attach a different debugger. Cannot be combined with WinAppRunNoLaunch. |
Targets (Microsoft.Windows.SDK.BuildTools.WinApp.targets)
| Target | Description |
|---|---|
_WinAppValidateRunSupport | Validates prerequisites (CLI exists, manifest exists) |
_WinAppBuildRunArgs | Builds CLI command arguments (shared by run targets) |
_WinAppPrepareRunArguments | Overrides RunCommand to use CLI |
RunPackagedApp | Direct target to run packaged app |
WinAppRunSupportInfo | Diagnostic target showing all properties |
Detection Logic
The package only activates when all of the following are true (gated by the internal _WinAppRunSupportActive property):
EnableWinAppRunSupportistrue(the default). Set it tofalseto explicitly disable.WindowsPackageTypeis not set toNone(absence of the property means packaged).OutputTypeis notLibrary— bothExe(packaged console apps via execution alias) andWinExe(WinUI apps) are supported.- The target platform identifier is
windows(derived from$(TargetPlatformIdentifier)if set, else from$(TargetFramework)). In multi-targeted projects (e.g. MAUInet*-android;net*-ios;net*-windows10.0.19041.0), the targets are inert for non-Windows TFMs. WinAppManifestPathresolves to an existing file. The targets auto-detect the manifest by checking the output directory first ($(OutputPath)AppxManifest.xml,$(OutputPath)Package.appxmanifest,$(OutputPath)appxmanifest.xml) and then the project directory (AppxManifest.xml,Package.appxmanifest,appxmanifest.xml); a consumer-suppliedWinAppManifestPathis honored as-is. Output-directory paths are accepted because frameworks like MAUI generate the manifest at build time into$(OutputPath)from platform / msbuild props; without that the gate could never activate for transitive MAUI head apps.
This gating ensures the package is safe to consume transitively (e.g. when re-exported by a library): unrelated projects (libraries, test projects, console apps without manifests, non-Windows TFMs) see no winapp activity and no impact on dotnet run.
Build Scripts
package-nuget.ps1
Creates both NuGet packages:
.\scripts\package-nuget.ps1 # Prerelease version
.\scripts\package-nuget.ps1 -Version 1.0.0 -Stable # Stable version
Integration with build-cli.ps1
The main build script now includes NuGet packaging:
.\scripts\build-cli.ps1 # Full build including NuGet
.\scripts\build-cli.ps1 -SkipNuGet # Skip NuGet packages
Usage
Customization
Disable run support for a project:
<PropertyGroup>
<EnableWinAppRunSupport>false</EnableWinAppRunSupport>
</PropertyGroup>
Specify manifest path:
<PropertyGroup>
<WinAppManifestPath>$(MSBuildProjectDirectory)\custom\Package.appxmanifest</WinAppManifestPath>
</PropertyGroup>
Pass launch arguments:
<PropertyGroup>
<WinAppLaunchArgs>--debug --verbose</WinAppLaunchArgs>
</PropertyGroup>
Launch via execution alias (for console apps):
<PropertyGroup>
<WinAppRunUseExecutionAlias>true</WinAppRunUseExecutionAlias>
</PropertyGroup>
Register identity without launching:
<PropertyGroup>
<WinAppRunNoLaunch>true</WinAppRunNoLaunch>
</PropertyGroup>
Capture OutputDebugString messages and first-chance exceptions:
<PropertyGroup>
<WinAppRunDebugOutput>true</WinAppRunDebugOutput>
</PropertyGroup>
Production Blockers
1. CLI AOT Build Issues (BLOCKING)
The CLI currently has NativeAOT compilation errors related to Newtonsoft.Json and NuGet.Protocol. These must be resolved before the NuGet package can include the CLI binaries.
Error summary:
- 146 trim/AOT analysis errors
- Related to reflection-heavy code in Newtonsoft.Json
- Related to dynamic code generation in NuGet.Protocol
Resolution:
- Wait until https://github.com/NuGet/Home/issues/14408
2. Developer Mode Requirement
Running packaged apps requires Developer Mode enabled on Windows. The solution should:
- Detect when Developer Mode is disabled
- Provide clear error messages
- Consider documenting this requirement prominently
3. First-run Experience
On first dotnet run, the CLI needs to:
- Download Windows SDK Build Tools (if not cached)
- This can take time on slow connections
Consider pre-caching or documenting this.
4. Platform Detection
The current implementation defaults to x64. For ARM64 machines, the targets correctly detect architecture, but the default Platform may need adjustment.
Testing
Local Testing (without published NuGet)
The sample project imports the MSBuild targets directly:
<Import Project="..\..\src\winapp-NuGet\build\Microsoft.Windows.SDK.BuildTools.WinApp.props" />
<Import Project="..\..\src\winapp-NuGet\build\Microsoft.Windows.SDK.BuildTools.WinApp.targets" />
Diagnostic Commands
# Show MSBuild property values
dotnet msbuild -t:WinAppRunSupportInfo
# Verbose build output
dotnet run -v:detailed
Future Enhancements
- Hot Reload Support: Integrate with
dotnet watchfor live reloading - Debug Attachment: Return process ID for debugger attachment in IDEs
- Unpackaged Mode: Auto-detect and use unpackaged mode when appropriate
- Multiple Apps: Support projects with multiple Application entries in manifest