This guide should work for most .NET projects types. The steps have been tested with both console and UI-based projects like WPF. For working examples, check out the dotnet-app (console) and wpf-app (WPF) samples in the samples folder.
This guide demonstrates how to use winappcli with a .NET application to debug with package identity and package your application as an MSIX.
Package identity is a core concept in the Windows app model. It allows your application to access specific Windows APIs (like Notifications, Security, AI APIs, etc), have a clean install/uninstall experience, and more.
A standard executable (like one created with dotnet build) does not have package identity. This guide shows how to add it for debugging and then package it for distribution.
-
.NET SDK: Install the .NET SDK (requires a restart after installation):
winget install Microsoft.DotNet.SDK.10 --source winget
-
winapp CLI: Install the
winapptool via winget (or update if already installed):winget install Microsoft.winappcli --source winget
Start by creating a simple .NET console application:
dotnet new console -n dotnet-app
cd dotnet-appRun it to make sure everything is working:
dotnet runOutput should be "Hello, World!"
We'll update the app to check if it's running with package identity. We'll use the Windows Runtime API to access the Package APIs.
First, update your project file to target a specific Windows SDK version. Open dotnet-app.csproj and change the TargetFramework to include the Windows SDK version:
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>This gives you access to Windows Runtime APIs without needing additional packages.
Now replace the contents of Program.cs with the following code. This code attempts to retrieve the current package identity using the Windows Runtime API. If it succeeds, it prints the Package Family Name; otherwise, it prints "Not packaged".
using Windows.ApplicationModel;
try
{
var package = Package.Current;
var familyName = package.Id.FamilyName;
Console.WriteLine($"Package Family Name: {familyName}");
}
catch (InvalidOperationException)
{
// Thrown when app doesn't have package identity
Console.WriteLine("Not packaged");
}Now, run the app as usual:
dotnet runYou should see the output "Not packaged". This confirms that the standard executable is running without any package identity.
The winapp init command automatically detects .csproj files and runs a .NET-specific setup. It sets up everything you need in one go: validates your TargetFramework, adds required NuGet packages, generates the app manifest, and assets.
Run the following command and follow the prompts:
winapp initWhen prompted:
- Package name: Press Enter to accept the default (dotnet-app)
- Publisher name: Press Enter to accept the default or enter your name
- Version: Press Enter to accept 1.0.0.0
- Description: Press Enter to accept the default (Windows Application) or enter a description
- Windows App SDK setup: Select Stable, Preview, or Experimental (determines which Windows App SDK version is added)
- TargetFramework update: If your
TargetFrameworkdoesn't include a supported Windows SDK version, you'll be prompted to update it (e.g., tonet10.0-windows10.0.26100.0) - Developer Mode: If you are prompted about "Developer Mode", you can turn it on if you would like, but be aware that it requires administrative privileges
This command will:
- Update the
TargetFrameworkin your.csprojto a supported Windows TFM (if needed) - Add
Microsoft.WindowsAppSDK,Microsoft.Windows.SDK.BuildTools, andMicrosoft.Windows.SDK.BuildTools.WinAppNuGet package references to your.csproj - Create
Package.appxmanifestandAssetsfolder for your app identity
Note: Unlike native/C++ projects, the .NET flow does not create a
winapp.yamlfile. NuGet packages are managed directly via your.csproj. Usedotnet restoreto restore packages after cloning.
You can open Package.appxmanifest to further customize properties like the display name, publisher, and capabilities.
To verify the packages were added to your project:
dotnet list packageYou should see Microsoft.WindowsAppSDK and Microsoft.Windows.SDK.BuildTools in the output.
Because we're building a console app, we need to make sure dotnet run keeps console output in the current terminal. By default, dotnet run launches the packaged app via AUMID activation, which opens a new window — and the window closes immediately when the console app finishes, swallowing any output.
To fix this, you'll add an execution alias to the manifest and tell the run integration to launch via that alias instead.
Skip this step if you're building a UI app (WPF, WinForms, WinUI). Those apps render their own window, so the default AUMID launch is what you want.
-
Add the execution alias to your manifest:
winapp manifest add-aliasThis adds a
uap5:ExecutionAliastoPackage.appxmanifest(defaulting to your project's exe name) so the app can be launched by name from a terminal. -
Tell the
dotnet runintegration to use the alias. Opendotnet-app.csprojand add the following inside any<PropertyGroup>(or create a new<PropertyGroup>if needed):<WinAppRunUseExecutionAlias>true</WinAppRunUseExecutionAlias>
With this property set,
dotnet runlaunches the app via its execution alias and inherits the current terminal's stdin/stdout/stderr so you see console output inline.
Since winapp init added the Microsoft.Windows.SDK.BuildTools.WinApp NuGet package to your project, you can simply run:
dotnet runThis automatically invokes winapp run under the hood — creating a loose layout package, registering it with Windows, and launching your app with full package identity.
Note: You may see NuGet vulnerability warnings (NU1900) about package sources. These are safe to ignore — they don't affect your build.
You should see output similar to:
Package Family Name: dotnet-app_12345abcde
This confirms your app is running with a valid package identity!
If you didn't use winapp init (or removed the NuGet package), you can build and run manually:
dotnet build -c Debug
winapp run .\bin\Debug\net10.0-windows10.0.26100.0To add the NuGet package back: dotnet add package Microsoft.Windows.SDK.BuildTools.WinApp --prerelease
Tip: To disable the automatic
dotnet runintegration, add<EnableWinAppRunSupport>false</EnableWinAppRunSupport>to your.csproj. See dotnet run support docs for customization options.
If you need sparse package behavior specifically (identity without copying files), you can use create-debug-identity instead. This registers a sparse package pointing to your exe rather than creating a loose layout:
winapp create-debug-identity .\bin\Debug\net10.0-windows10.0.26100.0\dotnet-app.exeThen run the executable directly (do not use dotnet run as it might rebuild/overwrite the file):
.\bin\Debug\net10.0-windows10.0.26100.0\dotnet-app.exeIf you prefer not to use the NuGet package, you can add a custom MSBuild target that runs create-debug-identity after Debug builds. Add this to your .csproj file at the end, just before the closing </Project> tag:
<!-- Automatically apply debug identity after Debug builds -->
<Target Name="ApplyDebugIdentity" AfterTargets="Build" Condition="'$(Configuration)' == 'Debug'">
<Exec Command="winapp create-debug-identity "$(TargetDir)$(TargetName).exe""
WorkingDirectory="$(ProjectDir)"
IgnoreExitCode="false" />
</Target>With this configuration, dotnet build applies the debug identity and you can run the executable directly. Note that dotnet run may rebuild and overwrite the identity, so run the exe manually after building.
Tip: For advanced debugging workflows (attaching debuggers, IDE setup, startup debugging), see the Debugging Guide.
When to skip this: If you prefer explicit control over when identity is applied, or if you're working on code that doesn't need identity for most of your development cycle, the manual approach above may be simpler.
The Windows App SDK gives you access to modern Windows APIs beyond what the base Windows SDK provides — things like the notification system, windowing APIs, app lifecycle management, and on-device AI. If your app needs any of these capabilities, this step is for you. If you just need package identity for distribution, you can skip to step 7.
If you ran winapp init (Step 4), Microsoft.WindowsAppSDK was already added as a NuGet package reference to your .csproj. You can verify with dotnet list package. If you skipped SDK setup during init, or need to add it manually, run:
dotnet add package Microsoft.WindowsAppSDKReplace the entire contents of Program.cs with the following code, which adds a Windows App Runtime version check:
using Windows.ApplicationModel;
class Program
{
static void Main(string[] args)
{
try
{
var package = Package.Current;
var familyName = package.Id.FamilyName;
Console.WriteLine($"Package Family Name: {familyName}");
// Get Windows App Runtime version using the API
var runtimeVersion = Microsoft.Windows.ApplicationModel.WindowsAppRuntime.RuntimeInfo.AsString;
Console.WriteLine($"Windows App Runtime Version: {runtimeVersion}");
}
catch (InvalidOperationException)
{
// Thrown when app doesn't have package identity
Console.WriteLine("Not packaged");
}
}
}Rebuild and run the application with Windows App SDK. Since we've added the WinAppSDK, we need to re-register with identity so winapp adds the runtime dependency. If you added the WinApp NuGet package (recommended), simply run dotnet run. Otherwise (replace dotnet-app with your project name):
dotnet build -c Debug
winapp run .\bin\Debug\net10.0-windows10.0.26100.0You should now see output like:
Package Family Name: dotnet-app.debug_12345abcde
Windows App Runtime Version: 8000.770.947.0
The Windows App SDK NuGet package includes all the necessary assemblies for accessing modern Windows APIs including:
- Notifications and live tiles
- Windowing and app lifecycle
- Push notifications
- And many more Windows App SDK components
For more advanced Windows App SDK usage, check out the Windows App SDK documentation.
Once you're ready to distribute your app, you can package it as an MSIX using the same manifest.
First, build your application in release mode for optimal performance:
dotnet build -c ReleaseNote: You may see NuGet vulnerability warnings (NU1900). These are safe to ignore and don't affect your build output.
Before packaging, you need a development certificate for signing. Generate one if you haven't already:
winapp cert generate --if-exists skipNow you can package and sign. Point the pack command to your build output folder (replace dotnet-app and the TFM path with your project's values):
# package and sign the app with the generated certificate
winapp pack .\bin\Release\net10.0-windows10.0.26100.0 --manifest .\Package.appxmanifest --cert .\devcert.pfx Note: The
packcommand automatically uses the Package.appxmanifest from your current directory and copies it to the target folder before packaging. The generated .msix file will be in the current directory.
Before you can install the MSIX package, you need to install the development certificate. Run this command as administrator:
winapp cert install .\devcert.pfxInstall the package by double-clicking the generated *.msix file.
Now you can run your app from anywhere in the terminal by typing:
dotnet-appYou should see the "Package Family Name" output, confirming it's installed and running with identity.
Tip: If you need to repackage your app (e.g., after code changes), increment the
Versionin yourPackage.appxmanifestbefore runningwinapp packagain. Windows requires a higher version number to update an installed package.
- Once you are ready for distribution, you can sign your MSIX with a code signing certificate from a Certificate Authority so your users don't have to install a self-signed certificate.
- The Microsoft Store will sign the MSIX for you, no need to sign before submission.
- You might need to create multiple MSIX packages, one for each architecture you support (x64, Arm64). Use the
-rflag withdotnet buildto target specific architectures:dotnet build -c Release -r win-x64ordotnet build -c Release -r win-arm64.
To automate MSIX packaging as part of your Release builds, add this target to your .csproj file (you can add it alongside the debug identity target):
<!-- Automatically package as MSIX after Release builds -->
<Target Name="PackageMsix" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<!-- Package and sign directly from build output -->
<Exec Command="winapp pack "$(TargetDir.TrimEnd('\'))" --cert "$(ProjectDir)devcert.pfx""
WorkingDirectory="$(ProjectDir)"
IgnoreExitCode="false" />
</Target>With this configuration:
- Building in Release mode (
dotnet build -c Release) will automatically create the MSIX package - The MSIX is packaged and signed with your development certificate
- The final
.msixfile will be in the root of the project
You can also create a custom configuration (e.g., PackagedRelease) by modifying the condition to '$(Configuration)' == 'PackagedRelease'.
- Distribute via winget: Submit your MSIX to the Windows Package Manager Community Repository
- Publish to the Microsoft Store: Use
winapp storeto submit your package - Set up CI/CD: Use the
setup-WinAppCliGitHub Action to automate packaging in your pipeline - Explore Windows APIs: With package identity, you can now use Notifications, on-device AI, and other identity-dependent APIs