npx skills add ...
npx skills add microsoft/winappcli --skill winapp-setup
Set up a Windows app project for MSIX packaging, Windows SDK access, or Windows API usage, or scaffold a brand-new WinUI 3 app. Use when creating or scaffolding a new WinUI app from scratch, adding Windows support to an Electron, .NET, C++, Rust, Flutter, or Tauri project, or restoring SDK packages after cloning.
npx skills add microsoft/winappcli --skill winapp-setup
Use this skill when:
winapp new)Install the winapp CLI before running any commands:
You need an existing app project — winapp init does not create new projects, it adds Windows platform files to your existing codebase.
Already have a
Package.appxmanifest? .NET projects that already have a packaging manifest (e.g., WinUI 3 apps or projects with an existing MSIX packaging setup) likely don't needwinapp init. Ensure your.csprojreferences theMicrosoft.WindowsAppSDKNuGet package and has the right properties for packaged builds (e.g.,<WindowsPackageType>MSIX</WindowsPackageType>). WinUI 3 apps created from Visual Studio templates are typically already fully configured — you can go straight to building and usingwinapp runorwinapp package.
Package.appxmanifest is the most important file winapp creates — it declares your app's identity, capabilities, and visual assets. Most winapp commands require it (package, run, cert generate --manifest).
winapp.yaml is only needed for SDK version management via restore/update. Projects that already reference Windows SDK packages (e.g., via NuGet in a .csproj) can use winapp commands without it.
.winapp/ is the local folder where SDK packages and generated projections (e.g., CppWinRT headers) are stored. This folder is .gitignored — team members recreate it via winapp restore.
To start a brand-new WinUI app (rather than adding Windows support to an existing project), use winapp new. It verifies the .NET SDK, installs the official WinUI dotnet new template pack on demand (grabbing the latest, or offering to update a stale one), and scaffolds the app against your installed SDK's target framework. Most WinUI templates already include packaging/identity, so no winapp init step is needed afterward — follow the template-specific next step winapp new prints when it finishes. App templates go straight to winapp run (which builds and launches the app); the winui-lib (class library) and winui-unittest templates differ (reference the library from an app project, or winapp run the packaged test app to run its tests). The template list is read live from the installed pack — run winapp new --list to see the current set.
The pack ships two styles of app. XAML templates (winui, winui-navview, winui-tabview, winui-mvvm) define the UI in markup with a C# code-behind. Reactor templates (reactor, reactor-mvu, reactor-navview, reactor-tabview) are pure C# with no XAML, using an MVU pattern.
Reactor templates are experimental. They reference the prerelease
Microsoft.UI.Reactorpackages, whose APIs can change or be removed in a future release — don't pick one unless the user explicitly asks for Reactor.winapp newmarks them (Experimental) in--listand in the picker, reports"Experimental": truein--json, and never selects one as the default. They also require the .NET 10 SDK or newer; on an older SDKwinapp newfails up front naming the version it needs.
A first run, template-pack update, or newly published Windows App SDK version may take longer while missing NuGet packages download and restore. If scaffolding continues beyond 10 seconds,
winapp newupdates its status message rather than silently waiting.
After init, your project will contain:
Package.appxmanifest — package identity and capabilitiesAssets/ — default app icons (Square44x44Logo, Square150x150Logo, etc.)winapp.yaml — SDK version pinning for restore/update.winapp/ — downloaded SDK packages and generated projections.gitignore update — excludes .winapp/ and devcert.pfxWhen JS bindings are enabled (via --add-js-bindings or by answering yes in interactive init), npm/Electron projects also get:
.winapp/bindings/ — generated JS bindings for Windows App SDK APIs (npm-only, Node / Electron)package.json update — adds the winapp.jsBindings namespace and @microsoft/dynwinrt dependency (npm-only)Use --sparse when you have an already-built desktop exe (WPF, WinForms, Win32, Electron, etc.) and only want to give it package identity — without repackaging the whole app into the MSIX. The app's files stay where they are and are resolved from an external content location at runtime.
--sparse requires --exe. It skips all SDK/package installation (sparse identity packages have no SDK dependencies) and, by default, writes to a dedicated sparse/ folder in the current directory (override with --output-dir) so the manifest and its Assets/ stay out of a build-output folder that a rebuild would wipe:
appxmanifest.xml — identity-only sparse manifest (declares uap10:AllowExternalContent)Assets/ — placeholder visual assets (extracted from the exe's icon when possible), resolved from the external location at runtime — not bundled into the .msixIf an appxmanifest.xml already exists in the target directory, init fails instead of overwriting it; re-run with --force to regenerate.
This is step 1 of the sparse packaging workflow. Continue with:
winapp pack ./sparse/appxmanifest.xml --cert ./devcert.pfx — build the signed identity .msixwinapp embed-identity ./bin/Release/MyApp.exe — connect the exe to the identity package (re-sign the exe afterward)Add-AppxPackage -Path <msix> -ExternalLocation <install-dir>For the full walkthrough, see the Sparse packaging guide.
Use restore when you clone a repo that already has winapp.yaml but no .winapp/ folder. For a .NET project there is no winapp.yaml, so restore runs dotnet restore instead.
init, restore, and update download the SDK packages through NuGet, honoring your standard nuget.config hierarchy. Private feeds and mirrors, feed credentials (including credential providers), and a custom globalPackagesFolder all work as they do for dotnet restore. To use only your feed, <clear /> the inherited sources first:
Security note: For native projects winapp resolves
nuget.configfrom the directory it operates on: theinit/restoredirectory argument,--config-dirwhen given, otherwise the current directory. For .NET projects the sources come from the project's ownnuget.confighierarchy instead, because that is whatdotnet add packageanddotnet restoreuse — so put a private feed's config in the project directory or an ancestor, not in a sibling passed via--config-dir(that is reported and ignored). Run these commands only against directories you trust, the same asdotnet restore. Use<packageSourceMapping>to pin packages to specific feeds when more than one source is configured.
This updates winapp.yaml with the latest versions and reinstalls packages.
Use winapp run during iterative development — it creates a loose layout package, registers a debug identity, and launches the app in one step. For identity-only registration without loose layout, use winapp create-debug-identity instead.
winapp run on a .csproj (.NET / WinUI)For .NET SDK projects you can point winapp run at the project instead of the build output — it builds the .csproj and launches it in one step, so there's no separate dotnet build and no need to know the output path:
Project mode supports both packaged and unpackaged WinUI apps, detected from the project's effective WindowsPackageType (MSIX ⇒ loose-layout register + AUMID launch; None ⇒ launch the built .exe), and installs the matching-architecture Windows App Runtime before launching. RID-only remains the default; when the effective configuration requires a self-contained profile, winapp selects the architecture-matching profile without forcing that platform onto referenced AnyCPU libraries. Requires .NET SDK 8.0.100+.
-c/--configuration, --arch, -r/--runtime, -f/--framework, --no-build, --no-restore, -p/--property (repeat for multiple properties; use %3B or %2C for a literal semicolon or comma in a value).--manifest, --no-launch, --with-alias, --clean, --unregister-on-exit, --output-appx-directory, --executable — rejected for unpackaged apps.--json sends invocations and child output to stderr so stdout stays valid JSON. --quiet suppresses invocations and sends dotnet's quiet restore/build output to stderr so stdout stays clean.winapp run on a .cs file-based appA .NET 10 file-based app is a single .cs file configured by #: directives, with no project file. Point winapp run at it and the app builds and launches with package identity — no hand-written manifest:
Describe the package with #:property directives in the file. All are optional:
| Property | Sets | Default |
|---|---|---|
WinAppPackageName | Identity/@Name | file name (sanitized) + a short hash of its path |
WinAppDisplayName | Start/Settings name | file name |
WinAppPublisher | Identity/@Publisher | CN=<current user>; a bare name is wrapped as CN= |
WinAppVersion | Identity/@Version | $(Version), normalized to four 0–65535 parts (1.2.3-preview.4 ⇒ 1.2.3.0) |
WinAppDescription | Install/Settings description | the display name |
WinAppCapabilities | Capabilities to declare, separated by ; or , | none |
WinAppRunUseExecutionAlias | Launch via execution alias instead of AUMID, so console output stays in the terminal | true for OutputType=Exe, else false |
Capabilities for gated APIs. Running full trust with identity is enough for APIs that only require a packaged identity, but some are gated on a declared capability regardless — the Windows AI APIs most notably. (Shell integrations like protocol handlers and file associations are a separate thing again: those need authored <Extensions> entries, not a capability.) #:property WinAppCapabilities=systemAIModels is all Phi Silica needs from the manifest. winapp writes each name into the element and namespace it actually requires (<systemai:Capability>, <Capability>, <DeviceCapability> — they differ), declares that namespace, and raises MaxVersionTested when required. An unknown bare name is rejected rather than guessed; qualify it yourself with rescap:, uap:, systemai:, device: or app: (the app: set is closed at the five foundation capabilities).
Bring your own manifest with --manifest, #:property WinAppManifestPath=…, or a manifest next to the .cs named <filename>.appxmanifest. Only that per-file name is auto-detected — a shared Package.appxmanifest in the folder is ignored, since several .cs files can live together. Otherwise one is generated into the build output (with default assets) and refreshed each run.
Packaged and unpackaged both work, from the effective WindowsPackageType — same as project mode. None builds, installs the Windows App Runtime, and launches the .exe directly; identity options apply to packaged apps only.
Console apps print to the terminal by default. An app with OutputType=Exe is launched through an execution alias rather than AUMID activation, because an AUMID-launched packaged app has no console and would print nothing. Pass --without-alias (or set #:property WinAppRunUseExecutionAlias=false) to force AUMID; pass --with-alias to get one for a windowed app. The alias is named from the package family name with a winapp- prefix (com.contoso.counter → winapp-com.contoso.counter_<publisherhash>.exe), so it can never contend with a real command on PATH or with another publisher's same-named app; winapp run prints the name it registered. An authored manifest's own alias is used as-is. If another package already owns the name, winapp reports it — falling back to AUMID when it inferred the alias for you, and failing the run when you asked for one explicitly with --with-alias or WinAppRunUseExecutionAlias=true, rather than launching the wrong app.
Rejected options (the file configures itself): -f ⇒ #:property TargetFramework=…; --project ⇒ not applicable. --arch/-r work as in project mode and default to the current winapp process architecture — required for self-contained WinAppSDK apps, which fail as AnyCPU. Everything else works as usual.
The package outlives the run. winapp says so the first time it registers an app. Remove it with winapp unregister counter.cs (no manifest path needed — it resolves the same identity), or run with --unregister-on-exit.
Requires .NET SDK 10.0.300+.
The default identity includes a short hash of the file's path (
counter.cs→counter-a1b2c3d4), so twocounter.csfiles in different folders are separate apps with separateLocalState. It is stable across edits and re-runs, and changes only if the file moves. SetWinAppPackageNameto pick a stable identity yourself. The Start menu showsWinAppDisplayName, not the identity.
run and create-debug-identitywinapp run | create-debug-identity | |
|---|---|---|
| Registers | Full loose layout package (entire folder) | Sparse package (single exe) |
| App launch | Winapp launches via AUMID or alias | You launch the exe yourself |
| Simulates MSIX | Yes — closest to production | No — identity only |
| Files | Copied to AppX layout dir | Exe stays in place |
| Best for | Most frameworks (.NET, C++, Rust, Flutter, Tauri) | Electron, or F5 startup debugging |
Default to winapp run. Use create-debug-identity when you need your IDE to launch and debug the exe directly (startup debugging), or when the exe is separate from your source (Electron).
Console apps keep stdin/stdout in the current terminal automatically: winapp reads the app's output type — from the project or .cs file where it can, and from the built binary's PE subsystem when running a build-output folder. Pass --with-alias only to force it for a windowed app, or when a folder holds several executables and detection can't pick one.
--debug-outputcaveat: CapturesOutputDebugStringand crash diagnostics (minidump + automatic analysis for both managed and native crashes) but attaches winapp as the debugger — you cannot also attach VS Code or WinDbg. Use--no-launchif you need your own debugger. Add--symbolsto download PDB symbols for richer native crash analysis. For WinUI 3 apps, a stowed-exception triage pass runs automatically (surfacing the originating HRESULT and native XAML dispatch stack); the debugger components it needs are downloaded on first use, or setWINAPP_DBGTOOLS_DIRto a directory containingdbgeng.dllandJsProvider.dllfor offline/locked-down environments.
For full debugging scenarios and IDE setup, see the Debugging Guide.
winapp init . --use-defaults in your existing projectPackage.appxmanifest to add capabilities your app needs (e.g., runFullTrust, internetClient)winapp run ./bin/Debug to register identity and launch for debuggingwinapp package ./bin/Release --cert ./devcert.pfx to create MSIX--use-defaults (alias: --no-prompt) in CI/CD pipelines and scripts to avoid interactive prompts. Non-interactive environments (piped stdin, CI runners) are auto-detected and will use defaults automatically with a warning.Package.appxmanifest without SDK setup, use winapp manifest generate instead of initwinapp init is idempotent for the config file — re-running it won't overwrite an existing winapp.yaml unless you use --config-onlynpm install --save-dev @microsoft/winappcli and use npx winapp init instead of the standalone CLIwinapp-manifest to customize your Package.appxmanifestwinapp-package to create an MSIX installerwinapp-signing for certificate generationwinapp-troubleshoot for a command selection flowchart| Error | Cause | Solution |
|---|---|---|
| "winapp.yaml not found" | Running restore/update without config | Run winapp init first, or ensure you're in the right directory |
| "Directory not found" | Target directory doesn't exist | Create the directory first or check the path |
| SDK download fails | Network issue or firewall | Ensure internet access; check proxy settings |
| SDK download fails with 401/403 | Private feed requires authentication | Store credentials in nuget.config (<packageSourceCredentials>) or configure a credential provider / feed environment credentials before running in CI |
| SDK package not found on private feed | Feed doesn't mirror the SDK packages, or the wrong source is configured | Ensure the feed serves Microsoft.WindowsAppSDK, Microsoft.Windows.SDK.CPP, Microsoft.Windows.CppWinRT, etc.; keep nuget.org enabled if the feed only supplements it |
init prompts unexpectedly in CI | Missing --use-defaults flag | Add --use-defaults to skip all prompts (note: non-interactive shells are now auto-detected) |
winapp new fails during scaffolding | A dotnet new post-creation action (restore, package add) failed | Re-run with --verbose to stream the live dotnet output and see the underlying error |
Run winapp <command> --help for current command options, or winapp --cli-schema for the complete machine-readable command schema.