This document describes the implementation of dotnet run support for packaged WinUI applications using a custom NuGet package.
The solution enables developers to run packaged WinUI (WinAppSDK) applications using just the .NET CLI:
winapp init
dotnet run-
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
dotnet run
│
▼
MSBuild Build Target
│
▼
_WinAppValidateRunSupport (validates prerequisites, WindowsPackageType != None)
│
▼
_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
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
| 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. |
| 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 |
The package detects a packaged app when:
WindowsPackageTypeis not set toNone(absence of the property means packaged)
Creates both NuGet packages:
.\scripts\package-nuget.ps1 # Prerelease version
.\scripts\package-nuget.ps1 -Version 1.0.0 -Stable # Stable versionThe main build script now includes NuGet packaging:
.\scripts\build-cli.ps1 # Full build including NuGet
.\scripts\build-cli.ps1 -SkipNuGet # Skip NuGet packages
.\scripts\build-cli.ps1 -SkipVsc # Skip VS Code extensionDisable 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>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 NuGet/Home#14408
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
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.
The current implementation defaults to x64. For ARM64 machines, the targets correctly detect architecture, but the default Platform may need adjustment.
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" /># Show MSBuild property values
dotnet msbuild -t:WinAppRunSupportInfo
# Verbose build output
dotnet run -v:detailed- 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