MSIX Packaging & Signing

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Shipping a Windows desktop app is two decisions: how the files are laid out on the user’s machine and how the user (and Windows) can trust them. MSIX answers the first with a container format that installs and removes an app cleanly, and it answers the second by requiring a digital signature. This page follows Bookshelf Desktop, the running scenario of this section, from a project in Visual Studio to a signed .msix file: the shape of the package, the manifest that describes it, the four ways to produce one (single-project MSIX, the Windows Application Packaging Project, packaging with external location, and the MSIX Packaging Tool), and the signing story that goes with it, from a throw-away test certificate to Azure Artifact Signing.

The page was written against these versions, current in October 2026:

  • .NET 10 LTS, latest patch 10.0.13, with C# 14. .NET 11 is at release candidate 2, with general availability expected at .NET Conf, 10 to 13 November 2026. Nothing on this page changes with .NET 11.

  • Visual Studio 2026 18.11.1. The single-project MSIX packaging tools are built into Visual Studio 2026 and later; earlier Visual Studio versions need a separate extension.

  • Windows App SDK 2.5.1 for WinUI 3 projects.

  • Windows 11 and Windows 10. Windows 10 reached the end of support on 14 October 2025. The manifest examples target 10.0.19041.0 (Windows 10 version 2004) as the minimum, which is also the minimum that packaging with external location needs.

  • Microsoft Store Policies 7.20 and the Store behaviour described on the Microsoft Learn code-signing pages, as of October 2026.

Distribution (the Microsoft Store, App Installer, WinGet, ClickOnce) is the subject of the Distribution Channels page of this section, the publish options of .NET itself are on .NET Publishing Options, and doing all of this in a pipeline is on CI/CD with GitHub Actions. This page stops where a signed package exists. The security consequences of packaging (the trust tiers, AppContainer, capabilities) are on Security & App Isolation, and what package identity unlocks for an unpackaged app is on Windows Platform Integration.

What MSIX Is

MSIX is the modern Windows app packaging format. It gives any Windows app a reliable install and uninstall, updates, and access to the Windows features that require package identity. Package identity is the key idea: when an app is packaged, Windows assigns it an identity built from manifest information (name, publisher, version, architecture and a resource identifier), and that identity is what push notifications, background tasks, the Windows AI APIs, Store distribution and enterprise management attach to.

Capability What it means for Bookshelf Desktop

Reliable install and uninstall

The OS installs the package as one unit and removes everything it registered on uninstall, so no files or registry entries are left behind.

Differential updates

The package carries a hash of each 64 KB block of every file, so an update downloads only the blocks that changed.

Disk space efficiency

Files shared between packages are managed by Windows, while each app stays independent so one app’s update does not affect another.

Containerized execution

The app runs in a lightweight container with a virtual file system and registry. Windows virtualizes or redirects certain writes to reduce the impact on the system.

Enterprise readiness

Deployment through Intune, Configuration Manager and the Enterprise Modern App Management CSP.

Mandatory signature

An MSIX package must be signed before Windows will install it.

Source: What is MSIX?

The container is a security and compatibility mechanism, not necessarily a sandbox. A packaged desktop app normally runs in full trust, with the same authority as the user, and the virtualization exists so that old applications keep working and uninstall stays clean. The difference between full trust and an AppContainer app is explained on Security & App Isolation.

Package types and file extensions

MSIX defines four package types and one distribution container. A desktop app is almost always a Main package.

Type Purpose Manifest indicator

Main

The primary package of an application. It declares applications, capabilities, dependencies and extensions.

No declaration of another type.

Framework

Shared libraries or runtime content, with side-by-side versions. The Windows App SDK framework package is one.

The Framework element is true.

Resource

Language-, scale- or other qualifier-specific resources, installed only when they apply to the device or user.

The ResourcePackage element is true.

Optional

Extra content tied to one Main package.

A uap3:MainPackageDependency element.

Bundle (.msixbundle)

One distribution artifact containing several MSIX packages of the same package family, so Windows installs only the architecture and resource packages that apply. A bundle is a container, not a fifth deployable type.

A bundle manifest and a ResourceId of ~.

In practice you meet these files. The .msixupload extension is the upload file for Microsoft Store submissions; it is produced by the Visual Studio Create App Packages wizard or by building with the StoreUpload build mode shown later on this page.

File Use

.msix

One package for one architecture. The artifact you sign and sideload.

.msixbundle

Several architectures (for example x64 and ARM64) in one file.

.msixupload

The upload file for Partner Center. The Store signs the package after certification.

.appinstaller

Not a package: an XML file that points to a package and describes update behaviour. See the Distribution Channels page.

.cer / .pfx

A public certificate / a certificate with its private key, used to trust or to sign.

Inside a Package

An .msix file is a ZIP-based container. Windows separates the files a package holds into payload files (the app itself: executables, libraries, assets, configuration, resources) and footprint files (metadata that describes, validates or supports the payload).

Diagram of the contents of a Bookshelf Desktop MSIX package: footprint files such as AppxManifest.xml, AppxBlockMap.xml and AppxSignature.p7x on one side, and payload files on the other
Footprint file Role

AppxManifest.xml

Declares the package identity, dependencies, capabilities, visual elements and extension points. You author it as Package.appxmanifest in the project, and the build writes AppxManifest.xml into the package.

AppxBlockMap.xml

Lists every file with a cryptographic hash for each 64 KB block. Windows uses it for differential updates and for integrity verification. Its HashMethod records the hash algorithm the package was made with.

AppxSignature.p7x

Holds the package signature. All MSIX packages must be signed before installation.

Other files

Present only when a feature needs them, such as AppxContentGroupMap.xml for a streaming install or AppxMetadata\CodeIntegrity.cat for code-integrity metadata.

Source: What is MSIX?

Two consequences follow from the block map. First, changing a single byte of a payload file after the package is built invalidates the package, which is a security benefit but also the reason you sign after packaging and never edit a package by hand. Second, SignTool must use the same hash algorithm that built the package. The default of the packaging tools is SHA-256, and SignTool’s own default is not, so you always pass the algorithm explicitly (see below).

The Build, Package, Sign, Publish Flow

Whichever packaging route you choose, the same four stages happen, and keeping them separate in your head makes problems easy to locate. A failure in "package" is a manifest or asset problem, a failure in "sign" is almost always a certificate or publisher problem, and a failure in "publish" is a channel rule.

flowchart LR subgraph Build A["dotnet build / MSBuild
Release, one platform"] --> B["Binaries, assets,
Package.appxmanifest"] end subgraph Package B --> C["MSBuild packaging targets
or MakeAppx / winapp CLI"] C --> D["BookshelfDesktop_1.4.0.0_x64.msix
unsigned"] end subgraph Sign D --> E{"Which certificate?"} E -- "Test only" --> F["Self-signed,
Subject equals Publisher"] E -- "Outside the Store" --> G["OV certificate or
Azure Artifact Signing"] E -- "Microsoft Store" --> H["Unsigned upload,
the Store re-signs"] F --> I["SignTool + timestamp"] G --> I end subgraph Publish I --> J["Sideload, App Installer,
WinGet, enterprise"] H --> K["Partner Center
submission"] end

The sections below follow the flow: the manifest (what goes into the package), the four packaging routes, the signing decision, and the install side.

Package.appxmanifest

The manifest is an XML file with the schema of the Windows 10 app package manifest. A project template generates Package.appxmanifest with the placeholders already filled in, and Visual Studio offers a visual designer for it (the Application, Visual Assets, Capabilities, Declarations and Packaging tabs). Edit the XML directly when you need an element the designer does not show.

A complete manifest for a full-trust desktop app

This is the manifest of the Bookshelf Desktop WPF app when it is packaged with the Windows Application Packaging Project. The Identity values are fictitious: CN=Bookshelf Dev is a placeholder publisher that you replace with the subject of the certificate you will really sign with.

<?xml version="1.0" encoding="utf-8"?>
<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
  xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
  IgnorableNamespaces="uap uap5 rescap">

  <Identity Name="Example.BookshelfDesktop"
            Publisher="CN=Bookshelf Dev"
            Version="1.4.0.0"
            ProcessorArchitecture="x64" />

  <Properties>
    <DisplayName>Bookshelf Desktop</DisplayName>
    <PublisherDisplayName>Bookshelf</PublisherDisplayName>
    <Logo>Images\StoreLogo.png</Logo>
  </Properties>

  <Dependencies>
    <TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.19041.0" MaxVersionTested="10.0.26100.0" />
  </Dependencies>

  <Resources>
    <Resource Language="en-US" />
    <Resource Language="es" />
  </Resources>

  <Applications>
    <Application Id="App"
                 Executable="Bookshelf.Desktop.exe"
                 EntryPoint="Windows.FullTrustApplication">
      <uap:VisualElements DisplayName="Bookshelf Desktop"
                          Description="Manage your book collection."
                          BackgroundColor="transparent"
                          Square150x150Logo="Images\Square150x150Logo.png"
                          Square44x44Logo="Images\Square44x44Logo.png" />
      <Extensions>
        <uap5:Extension Category="windows.startupTask" Executable="Bookshelf.Desktop.exe"
                        EntryPoint="Windows.FullTrustApplication">
          <uap5:StartupTask TaskId="BookshelfSync" Enabled="false" DisplayName="Bookshelf sync" />
        </uap5:Extension>
      </Extensions>
    </Application>
  </Applications>

  <Capabilities>
    <rescap:Capability Name="runFullTrust" />
  </Capabilities>
</Package>

What each part does

Element or attribute Meaning and pitfalls

Identity Name

The package name. It is part of the package identity and of the package family name. In a Store submission it must be the identity Partner Center assigns to the reserved app name, and Visual Studio can associate the project with the reserved name so the value is filled in for you.

Identity Publisher

The publisher, written as a certificate subject (CN=<name>, O=<organization>, C=<country>). It must match the Subject of the certificate that signs the package, character for character. See the certificate sections below.

Identity Version

Four-part version major.minor.build.revision. Windows refuses to register a package whose exact version is already registered (the troubleshooting table of the external-location page lists the error), so every build you install over another needs a higher number. Versioning strategy is on the Updates, Versioning & Telemetry page.

Identity ProcessorArchitecture

x64, x86, arm64 or neutral. A packaged .NET app is architecture-specific unless it contains only portable code. A sparse identity package uses neutral.

Properties

Display name, publisher display name and logo, shown in the Store and installer UI.

TargetDeviceFamily

Windows.Desktop with MinVersion and MaxVersionTested. 10.0.19041.0 is the recommended minimum on the Learn external-location page for the widest reach across Windows 10 and Windows 11.

Resources

The languages the package declares. They must agree with the localization of the app; see Globalization & Localization.

Application Executable and EntryPoint

The executable inside the package and, for a full-trust desktop app, the entry point Windows.FullTrustApplication. In a WinUI 3 project the template writes placeholders ($targetnametoken$.exe, $targetentrypoint$) that the build replaces.

uap:VisualElements

Tile name, description, background colour and the logo images. The Square44x44Logo and Square150x150Logo are mandatory for a visible app.

Extensions

Windows features the app registers for: startup tasks, file type associations, protocol activation, share targets, app execution aliases and so on. They live in the manifest and need package identity to work.

Capabilities

What the app may access. runFullTrust is the restricted capability (rescap prefix) that allows a package with a full-trust desktop app to be installed. Restricted capabilities need a justification in Store certification, but not for sideloading.

The runFullTrust capability and the difference between uap10:TrustLevel="mediumIL" and appContainer are covered in detail on Security & App Isolation. The point for packaging is that the manifest, not the project file, decides which tier the app runs in.

Visual assets

A package needs the logo files named in the manifest or the build fails (or, worse, the app installs with a blank tile). The template creates an Images (or Assets) folder; every file must have the build action Content so that it lands in the package. The Windows Application Packaging Project can generate the full set of scaled images from one source image. The sizes you must provide for a desktop app are:

Asset Used for

StoreLogo

The package logo in the Store listing and installer UI (the Logo property).

Square44x44Logo

The app-list entry, the taskbar, and Start search results, with scale and target-size variants.

Square150x150Logo

The medium Start tile.

Wide310x150Logo, Square310x310Logo

Optional larger tiles.

SplashScreen

A splash for WinUI and other apps that declare one.

Localizing the manifest

Strings in the manifest (display name, description) are localized through resource references of the form ms-resource:AppDisplayName and a Strings\en-US\Resources.resw file, so the display name matches the Windows display language. The Resources element must list the languages. The details of resource loading are on Globalization & Localization.

Choosing a Packaging Route

There are four ways to get an MSIX for a desktop app, plus the option of not packaging at all. The right one depends on the stack and on whether you own the source.

Route Applies to Produces Notes

Single-project MSIX

WinUI 3 apps (Windows App SDK)

A full MSIX from the app project alone

One executable per package. The manifest and images live in the app project.

Windows Application Packaging Project

WPF, WinForms and other desktop apps with source

A full MSIX from a separate packaging project that references the app

Can combine several executables in one package. Supported on Windows 10 version 1607 and later; targets Windows 10 Anniversary Update (10.0.14393) or a later release.

Packaging with external location (sparse)

Any existing app that already has an installer

A small identity-only package registered next to the existing installation

Gives package identity without moving the files into a container. Needs Windows 10 version 2004 or later.

MSIX Packaging Tool

An existing installer (MSI, EXE, App-V) without source

A full MSIX converted from the installer

An IT-pro tool: captures what the installer does on a clean machine.

Unpackaged

WPF, WinForms, WinUI

No MSIX

Published with dotnet publish or an installer. No identity, no clean uninstall. See .NET Publishing Options.

Source: Choose a distribution path (the packaged / external-location / unpackaged vocabulary) and the pages cited in each section below.

A good default for new code: WinUI 3 apps use single-project MSIX; WPF and WinForms apps that want a Store or MSIX presence use the Windows Application Packaging Project; a WPF or WinForms app that must keep its own installer but needs a Windows feature that requires identity uses an external-location package.

Single-Project MSIX for WinUI 3

Single-project MSIX lets a WinUI 3 desktop app build into an MSIX without a separate packaging project. It is the default for the WinUI Blank App (Packaged) template that ships with the Windows App SDK. Before it existed, a packaged WinUI app needed an additional Windows Application Packaging Project in the solution; that project is no longer needed, and the selection of a packaging project as the startup project goes away.

  • The single-project MSIX tools are built into Visual Studio 2026 and later. Earlier Visual Studio versions need the Single-project MSIX Packaging Tools extension from the Marketplace.

  • The supported project types are the WinUI templates for C# and C++.

  • Limitation: single-project MSIX supports only one executable in the generated package. If you must combine several executables, keep using a Windows Application Packaging Project.

  • Single-project MSIX does not currently produce .msixbundle files, only a single .msix. A bundle can be created from several .msix files with a separate tool, such as the MSIX Bundler GitHub Action.

The project file

The Bookshelf.WinUI project switches the tooling on with EnableMsixTooling. The other properties are the ones you saw on WinUI 3 & the Windows App SDK.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
    <TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
    <RootNamespace>Bookshelf.WinUI</RootNamespace>
    <Platforms>x64;ARM64</Platforms>
    <RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
    <PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
    <UseWinUI>true</UseWinUI>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- The single-project MSIX switch: the manifest and images live in this project -->
    <EnableMsixTooling>true</EnableMsixTooling>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.WindowsAppSDK" Version="2.5.1" />
    <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
  </ItemGroup>

  <ItemGroup>
    <!-- Every file named in Package.appxmanifest must be packaged as Content -->
    <Content Include="Images\*.png" />
  </ItemGroup>
</Project>

When you convert an existing solution, the documented steps are: add EnableMsixTooling (and a publish profile property) to the application project; move Package.appxmanifest and the Images folder from the old packaging project into the application project and set the images' build action to Content; delete the packaging project; and tick the Deploy check box for every configuration and platform combination in Configuration Manager (using the Active solution configuration and Active solution platform lists at the top). On Visual Studio 2026 and later you also add a Properties\launchSettings.json with a profile whose commandName is MsixPackage, so that F5 deploys and starts the packaged app.

{
  "profiles": {
    "Bookshelf.WinUI (Package)": {
      "commandName": "MsixPackage",
      "alwaysReinstallApp": false,
      "allowLocalNetworkLoopbackProperty": true,
      "nativeDebugging": false
    }
  }
}

Source: Package your app using single-project MSIX (the page lists further optional properties of the profile, such as remote debugging)

Building the package from the command line

Visual Studio builds the package when you deploy or use Package and Publish. From a terminal or CI, call MSBuild with GenerateAppxPackageOnBuild. Without that property the project builds, but no MSIX is produced.

# Sideload package for one architecture, unbundled, written to a Packages folder.
msbuild .\Bookshelf.WinUI\Bookshelf.WinUI.csproj `
    /restore `
    /p:Configuration=Release `
    /p:Platform=x64 `
    /p:AppxPackageDir="Packages\" `
    /p:UapAppxPackageBuildMode=SideloadOnly `
    /p:AppxBundle=Never `
    /p:GenerateAppxPackageOnBuild=true

The properties that decide what kind of package comes out are these. The same properties appear again on the CI/CD page.

MSBuild property Effect

GenerateAppxPackageOnBuild=true

Actually produce the MSIX during the build.

UapAppxPackageBuildMode=SideloadOnly

A package for sideloading, enterprise and App Installer distribution.

UapAppxPackageBuildMode=StoreUpload

A .msixupload file for Partner Center.

AppxBundle=Never / Always

Do not create / create an .msixbundle.

AppxBundlePlatforms="x86|x64"

The architectures in the bundle (the Learn page writes the value with a vertical bar).

AppxPackageDir

The output folder for packages.

AppxPackageSigningEnabled=false

Do not sign during the build. Used for Store uploads, because the Store signs after certification, and for builds that you sign afterwards with SignTool or Artifact Signing.

PackageCertificateKeyFile

The path of the .pfx used to sign during the build.

The Learn page also builds a Store bundle for x86 and x64 in one MSBuild call by setting Platform=x86, which selects the solution configuration that invokes the packaging target, together with AppxBundlePlatforms. For a single-project app that targets x64 and ARM64, the same idea applies with the platforms your project defines.

The Windows Application Packaging Project for WPF and WinForms

WPF and Windows Forms projects have no built-in MSIX tooling, so Visual Studio provides a Windows Application Packaging Project (a .wapproj, often called WAP): a project with no code of its own whose job is to generate and configure the package for the desktop project it references. Despite the name, it works with .NET desktop apps, including .NET 10 WPF and WinForms.

To see the project template, one of these must be installed: the Universal Windows Platform development workload, or the optional component MSIX Packaging Tools in the .NET desktop development workload (or in the ASP.NET and web development / .NET Core workload that offers it). The Visual Studio features the project gives you are an automatic generator of visual assets, a visual designer for the manifest, a wizard to create the package or bundle, and, for the Store, the ability to assign an identity from a name you already reserved in Partner Center.

Adding the packaging project

  1. Open the solution that contains the WPF project (Bookshelf.Desktop).

  2. Add a Windows Application Packaging Project, in the language of the app (C# for a C# app). You do not add code to it.

  3. Choose the Target Version; set the Minimum Version to Windows 10 Anniversary Update or later.

  4. Set the packaging project as the startup project.

  5. Right-click Dependencies under the packaging project, choose Add Project Reference and select the WPF project. If the package holds several desktop apps, right-click the one that should start from the tile under Applications and choose Set as Entry Point.

  6. Build the packaging project. If you get errors, open Configuration Manager and make sure all projects target the same platform. To package x64, the Learn page documents adding the x64 configuration through Configuration Manager before adding the packaging project, or removing the x64 entries from the .sln and adding them again afterwards.

  7. Use the Create App Packages wizard (right-click the packaging project, Package and Publish) to produce the MSIX, a bundle, or an .msixupload for the Store.

The resulting solution layout is easy to recognise:

Bookshelf.sln
|-- Bookshelf.Core               (class library)
|-- Bookshelf.Data               (EF Core, SQLite)
|-- Bookshelf.Desktop            (WPF app, net10.0-windows)
`-- Bookshelf.Desktop.Package    (Windows Application Packaging Project)
      Package.appxmanifest       (identity, capabilities, extensions)
      Images\                    (generated logo assets)
      Dependencies\Applications  (a project reference to Bookshelf.Desktop)

The Learn page for this project was last updated in 2024. It still describes the Visual Studio 2017 15.5 and later template and does not mention .NET 10 by name, so test the packaging project with your own .NET 10 solution rather than assuming a feature list. Every WPF and WinForms desktop app packaged this way is a full-trust app: the manifest carries runFullTrust and the executable runs with the user’s authority.

Packaging with External Location (Sparse Packages)

Sometimes an app already has an installer, an update mechanism and a file layout it cannot change, but it needs a Windows feature that requires package identity. Starting in Windows 10 version 2004, you can grant identity by building and registering a package with external location: a small MSIX that contains the manifest (and little else) and points to the files of the existing installation. You do not move the app into a container, and the app is not virtualized.

Aspect Detail

What the package holds

A manifest with uap10:AllowExternalContent set to true, the identity, the capabilities and the Application entries, plus any logo images the manifest names. The referenced paths in VisualElements do not need to resolve.

Trust level

uap10:TrustLevel="mediumIL" and uap10:RuntimeBehavior="win32App" on each Application, plus the runFullTrust and unvirtualizedResources capabilities, for Win32 compatibility.

AppListEntry

none, so the identity package is not shown as an installed app.

Connection to the exe

The executable carries an application manifest (side-by-side manifest) with an msix element whose packageName, publisher and applicationId match the identity package’s Name, Publisher and Application Id.

Registration

Add-AppxPackage -Path <package> -ExternalLocation <install folder> (per user), or Add-AppxPackage -Stage plus Add-AppxProvisionedPackage (per machine), or the PackageManager API from the installer.

Signing

The package must be signed with a certificate that is trusted on the target machine, exactly like any MSIX.

Visual Studio

The Windows Application Packaging Project and Package with External Location extension builds the identity package for a single app project and adds a certificate wizard and registration scripts.

The manifest, the side-by-side msix element and the registration code for Bookshelf Desktop are shown on Windows Platform Integration, where the features that identity unlocks are the subject. The packaging-specific parts are the build and the registration commands:

# Build the identity package from a folder that contains AppxManifest.xml.
# /nv skips validation of the file paths referenced in the manifest.
MakeAppx.exe pack /o /d .\identity /nv /p .\out\BookshelfIdentity.msix

# Register it next to the existing installation (per user).
Add-AppxPackage -Path .\out\BookshelfIdentity.msix -ExternalLocation "C:\Program Files\Bookshelf Desktop"

# Unregister on uninstall.
Get-AppxPackage Example.BookshelfDesktop | Remove-AppxPackage

The registration errors you will meet, from the same page:

Error Cause Fix

0x800B0109 (CERT_E_UNTRUSTEDROOT)

A self-signed certificate is not in the Trusted People store.

Import the public .cer into Cert:\CurrentUser\TrustedPeople (or the LocalMachine store for machine-wide installs).

0x80073CF9

The exact same version of the package is already registered.

Unregister it first, then register again.

0x80073D54

Registration succeeded but identity is missing at runtime, because publisher, packageName or applicationId in the exe’s msix element do not match the manifest.

Make the three values identical in both manifests.

Identity absent, no error

The ExternalLocation path is not the directory the exe really runs from.

Pass the exact install folder.

0x80073CF6

The manifest is malformed or an attribute is missing.

Run MakeAppx.exe pack to get schema errors; check that uap10:AllowExternalContent is true.

For deeper diagnostics, the deployment log is in Event Viewer under Applications and Services Logs / Microsoft / Windows / AppxDeployment-Server.

The MSIX Packaging Tool

When you have an installer but no source (a vendor MSI, a legacy EXE, an App-V package), the MSIX Packaging Tool repackages it as MSIX. It offers an interactive UI and a command line, and it does not need access to the source code.

  • Application package: runs an installer (MSI, EXE, App-V 5.x or a custom script) on a clean machine while the tool captures what it installs, and writes an MSIX.

  • Modification package: creates a modification package for an existing MSIX package.

  • Package editor: opens an MSIX to view and edit its content and properties.

The tool needs Windows 10 version 1809 or later and administrator rights, and it is available from the Microsoft Store and from WinGet (winget install "MSIX Packaging Tool"). It can also be downloaded for offline use. Conversion is best done in a virtual machine or a clean device, because the tool records everything the installer changes. A converted app sometimes needs compatibility fixes that the Package Support Framework applies without changing the application source.

For Bookshelf Desktop, whose source you own, the MSIX Packaging Tool is not the right route: build the package from the project. The tool is for third-party and legacy software that you must ship as MSIX for a managed environment.

Windows App SDK Runtime Deployment

A WinUI 3 app depends on the Windows App SDK runtime, and how that dependency reaches the machine depends on how you package and publish.

Framework-dependent (default) Self-contained

What ships

Only your app. The Windows App SDK runtime and framework packages are installed on the machine.

The app carries the Windows App SDK with it.

Packaged (MSIX)

The framework packages are installed automatically as dependencies of the packaged app; you call the deployment API to initialize it.

No initialization needed.

External location or unpackaged

Installed by the Windows App SDK runtime installer; initialize with the bootstrapper API.

See the self-contained deployment guide for the automatic reg-free WinRT behaviour.

Servicing

The runtime is serviced centrally, with a small risk that an update introduces a regression.

You control the version; a runtime fix needs a new release of your app.

Size and load time

Smaller.

Larger, and for unpackaged apps slower to load and heavier on memory, because code pages are not shared.

Two rules of thumb from the same page: a packaged WinUI app should normally stay framework-dependent, because the MSIX dependency mechanism installs and shares the runtime for you; and PublishSingleFile works only for apps that are both unpackaged and self-contained, so it does not combine with MSIX. For multiple architectures, create an .msixbundle containing x64 and ARM64 packages; the Store and App Installer pick the right architecture at install time. Native ARM64 builds are recommended over relying on x64 emulation on Windows 11 ARM devices. The .NET publish matrix for WPF, WinForms and WinUI is on the .NET Publishing Options page.

Why Signing Matters and the Publisher Rule

An MSIX package must be signed before Windows will install it, and the signature has two jobs: it proves the package has not been changed since it was signed, and it ties the package to a publisher. The tie is enforced by a single rule that causes most first-time signing failures:

The Subject of the signing certificate must match the Publisher attribute of the Identity element in the manifest.

If the manifest says Publisher="CN=Bookshelf Dev", the certificate’s subject must be exactly CN=Bookshelf Dev. If the subject is CN=Bookshelf Dev, O=Bookshelf, C=US, the manifest must say so with the same attribute values, in the same order. Windows compares the strings, so even extra spaces matter.

Symptom Likely cause

SignTool reports an error that the publisher does not match

The certificate subject differs from the manifest Publisher.

The package signs, but the install says the certificate is not trusted

The certificate was not added to a trusted store on the target machine (self-signed) or the chain does not reach a trusted root.

An update is refused although it is signed

The new package was signed by a different publisher from the installed package. Updates must keep the publisher.

The Store rejects the upload

The manifest identity does not match the identity Partner Center assigned to the reserved app.

For a Store submission the publisher is the one Partner Center assigns (the Publisher in the app identity page), and the Store re-signs the package, so the upload does not need your own certificate.

Certificates for Testing

A self-signed certificate is the quickest way to produce an installable package for your own machines. It is not suitable for public distribution: only users who install and trust your certificate can run the app.

Create the certificate with the subject taken from the manifest (here the fictitious CN=Bookshelf Dev). The extended key usage 1.3.6.1.5.5.7.3.3 marks it as a code-signing certificate, and the empty basic-constraints extension marks it as an end entity, not a certificate authority.

# Elevated PowerShell. The subject must equal Identity Publisher in the manifest.
New-SelfSignedCertificate -Type Custom `
    -KeyUsage DigitalSignature `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") `
    -Subject "CN=Bookshelf Dev" `
    -FriendlyName "Bookshelf Desktop test certificate"

Export it to a .pfx for signing. Export-PfxCertificate requires either -Password or -ProtectTo. The password is read from an environment variable, never typed into a script or committed to the repository.

# The thumbprint and the password come from the environment, not from the script.
$password = ConvertTo-SecureString -String $env:BOOKSHELF_PFX_PASSWORD -Force -AsPlainText
Export-PfxCertificate -Cert "Cert:\CurrentUser\My\$env:BOOKSHELF_CERT_THUMBPRINT" `
    -FilePath .\BookshelfTest.pfx -Password $password

To install an app signed with that certificate, the machine must trust it. Export the public certificate (the .cer file) and import that, and keep the .pfx private because it holds the private key. Importing into LocalMachine affects the trust of all users of the machine, which is why the Learn page recommends removing such certificates when they are no longer needed.

$cert = Get-PfxCertificate -FilePath .\BookshelfTest.pfx
Export-Certificate -Cert $cert -FilePath .\BookshelfTest.cer

# Current user only:
Import-Certificate -FilePath .\BookshelfTest.cer -CertStoreLocation Cert:\CurrentUser\TrustedPeople

# Whole machine (elevated PowerShell):
Import-Certificate -FilePath .\BookshelfTest.cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople

Visual Studio can create this test certificate for you in the Create App Packages wizard (Select signing method, Create). The Learn CI page suggests leaving the password blank for a throw-away CI test certificate and keeping the resulting .pfx out of source control, because git ignores it by default and GitHub secrets are the place for it.

Signing with SignTool

SignTool is the command-line tool of the Windows SDK for signing and for verifying and timestamping signatures. It lives in the bin\<sdk version>\x64 folder of the SDK, with x86 and Arm64 variants. When you build with Visual Studio you do not need it, because the packaging wizard signs for you; you use SignTool in pipelines and when you sign a package someone else built.

Three rules from the documentation matter most:

  1. Name the hash algorithm. It must be the one the package was built with. MakeAppx and the MSBuild packaging targets use SHA-256 by default, and SignTool’s own default is SHA-1, so always pass /fd SHA256. The block map records the algorithm in HashMethod (SHA-256 is the xmlenc#sha256 value).

  2. Sign a bundle once. SignTool signs every package inside an .msixbundle recursively.

  3. Timestamp every signature (next section).

Sign with a PFX file

# The certificate file and its password come from the environment (set by the CI system or the shell).
& "${env:ProgramFiles(x86)}\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe" sign `
    /fd SHA256 `
    /f $env:SIGNING_PFX_PATH `
    /p $env:SIGNING_PFX_PASSWORD `
    /tr http://timestamp.example.com `
    /td SHA256 `
    .\Packages\BookshelfDesktop_1.4.0.0_x64.msix

The command-line options used above:

Option Meaning

/fd SHA256

The file digest algorithm. Must match the package.

/f <file>

The .pfx file. Use /n <subject name> for a certificate in a store, or /sha1 <thumbprint> to pick one exactly. /a lets SignTool choose the best certificate automatically.

/p <password>

The password of the .pfx. Omit it for a certificate that has no password.

/tr <url>

The RFC 3161 timestamp server.

/td SHA256

The digest algorithm of the timestamp request.

The timestamp URL above is a placeholder: use the address your certificate authority publishes (or the Microsoft timestamp service when you sign with Artifact Signing, described below). The password reaches the command line from an environment variable, but remember that a process command line can be visible to other processes on the machine; on a shared build agent prefer a signing service that never exposes a key (Artifact Signing, or a hardware token or cloud HSM driven by your CA’s client), and mask the secret in logs.

Verify

# /pa selects the default Authenticode verification policy, /v prints details.
signtool verify /pa /v .\Packages\BookshelfDesktop_1.4.0.0_x64.msix
Get-AuthenticodeSignature .\Packages\BookshelfDesktop_1.4.0.0_x64.msix | Format-List Status, SignerCertificate, TimeStamperCertificate

Source: SignTool (the verify command)

Sign during the build instead

If you prefer the build to sign, MSBuild accepts PackageCertificateKeyFile (the .pfx), and the password can be passed in as PackageCertificatePassword. The Learn CI workflow decodes a Base64 secret to a temporary .pfx on the runner, passes the file to MSBuild, and deletes it afterwards. This works, but the key sits on the runner’s disk for the length of the build, which is the reason the next section exists.

Timestamping

A timestamp is a countersignature from a timestamp authority that records when the package was signed. Without one, the signature is valid only while the signing certificate is valid: when the certificate expires, Windows treats the binary as unsigned. With a timestamp, the signature stays verifiable after the certificate expires, because the verifier can confirm that the certificate was valid at signing time.

  • Always pass an RFC 3161 timestamp with /tr and /td SHA256. The older /t option uses the legacy Authenticode protocol, and the documentation recommends RFC 3161.

  • Use SHA-256 for both the file digest and the timestamp digest; SHA-1 is deprecated and distrusted for code signing.

  • A previously signed file can be timestamped later with signtool timestamp.

  • Timestamping is critical with Azure Artifact Signing, whose certificates are valid for only three days. Without a timestamp the signature would expire almost immediately.

Azure Artifact Signing

Azure Artifact Signing (formerly Trusted Signing) is Microsoft’s fully managed signing service, and the one Microsoft recommends for apps distributed outside the Store. Some URLs, packages and product pages still carry the old trusted-signing name, and the GitHub Action in the Microsoft documentation used to be called trusted-signing-action.

What it gives you:

  • Zero-touch certificate lifecycle inside FIPS 140-3 level 3 certified hardware security modules. You never hold a private key, so there is no key to lose or leak, and no USB token to plug into a build agent.

  • Short-lived certificates (three days of validity), which is why timestamping is required.

  • Content-confidential signing: your file never leaves the machine; the service signs a digest.

  • Integration with SignTool, GitHub Actions, Azure DevOps, PowerShell for Authenticode and an SDK.

  • Identity validation by Microsoft before any certificate is issued; plan for a few business days.

The resource structure is a resource group in an Azure subscription that contains an Artifact Signing account, which in turn has two resources: an identity validation and a certificate profile. You choose a Basic or Premium SKU for the account. Documented pricing starts at about 9.99 US dollars a month.

Item Detail

Availability

Organizations in the USA, Canada, the European Union and the United Kingdom. Individual developers in the USA and Canada only.

Not eligible

Developers outside those regions use an OV certificate from a certificate authority instead.

Permission

The identity that signs needs the Artifact Signing Certificate Profile Signer role on the account or the certificate profile.

Not for the Store

Artifact Signing is a route for non-Store distribution. Store MSIX submissions are signed by Microsoft after certification.

SmartScreen

Does not give instant trust: reputation builds over time (see below).

Signing a package with SignTool and the Artifact Signing dlib

SignTool talks to the service through a client library (a dlib). The documentation lists the requirements: SignTool.exe from a Windows SDK of version 10.0.2261.755 or later (the 20348 SDK is not supported), the .NET 8 runtime, the Visual C++ Redistributable and the Artifact Signing client dlib, which an installer available through WinGet (winget install -e --id Microsoft.Azure.ArtifactSigningClientTools) sets up. A small JSON file points the dlib at the account:

{
  "Endpoint": "<artifact-signing-regional-endpoint>",
  "CodeSigningAccountName": "<account-name>",
  "CertificateProfileName": "<certificate-profile-name>"
}

The endpoint must be the regional URI that matches the region of the account and the certificate profile; a mismatch commonly produces a 403 Forbidden error. Authentication uses DefaultAzureCredential, so on a developer machine the signed-in Azure CLI or Visual Studio account is used, and in a pipeline the identity is supplied through environment variables or a federated credential.

& "$env:SIGNTOOL_PATH" sign /v /fd SHA256 `
    /tr "http://timestamp.acs.microsoft.com" /td SHA256 `
    /dlib "$env:ARTIFACT_SIGNING_DLIB_PATH\Azure.CodeSigning.Dlib.dll" `
    /dmdf ".\metadata.json" `
    .\Packages\BookshelfDesktop_1.4.0.0_x64.msix

The recommended timestamp authority is the Microsoft one shown above, because the short certificate lifetime makes the timestamp the part of the signature that has to last.

Signing in GitHub Actions

The GitHub Action authenticates with a federated (OIDC) credential, so no key or password is stored in the repository. The three identifiers are not secrets in the cryptographic sense, but the Azure documentation and this page treat them as repository secrets.

name: sign-bookshelf
on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  sign:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4

      # ... build and package the MSIX into .\Packages here (see the CI/CD page) ...

      - name: Azure login (OIDC)
        uses: azure/login@v3
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Sign with Artifact Signing
        uses: azure/artifact-signing-action@v2
        with:
          endpoint: ${{ vars.ARTIFACT_SIGNING_ENDPOINT }}
          signing-account-name: ${{ vars.ARTIFACT_SIGNING_ACCOUNT }}
          certificate-profile-name: ${{ vars.ARTIFACT_SIGNING_PROFILE }}
          files-folder: ${{ github.workspace }}\Packages
          files-folder-filter: msix
          file-digest: SHA256
          timestamp-rfc3161: http://timestamp.acs.microsoft.com
          timestamp-digest: SHA256

The manifest Publisher of the package must equal the subject of the certificate that the certificate profile issues. Read that subject from the certificate profile once identity validation succeeds and put it in Package.appxmanifest; do not guess it. Azure DevOps uses the AzureArtifactSigning task from the Visual Studio Marketplace extension instead of the action. A full pipeline (build, package, sign, release) is on the CI/CD with GitHub Actions page.

Choosing a Certificate: OV, EV and SmartScreen

A signed package from a trusted certificate tells the user and Windows who published it. It does not by itself make SmartScreen quiet, and that is the most common misunderstanding about code signing.

Option Cost (as documented) SmartScreen on first download Use when

Microsoft Store (MSIX)

Free

No warning: Microsoft re-signs and the package carries Microsoft reputation.

The default for most new apps.

Microsoft Store (MSI or EXE)

A certificate chaining to a CA in the Microsoft Trusted Root Program

No SmartScreen prompt during the Store install, though UAC may still appear.

An existing Win32 app submitted as an installer.

Azure Artifact Signing

About 9.99 US dollars a month

Reputation builds over time; initial warnings are expected.

Recommended outside the Store, in the eligible regions.

OV certificate

About 150 to 300 US dollars a year

The same as Artifact Signing.

Outside the eligible regions, or when a customer requires a particular CA. Private keys must be on an HSM or hardware token since June 2023.

EV certificate

About 400 US dollars and up a year

The same as OV since 2024; no longer an instant bypass.

Only for enterprise procurement or trust contexts that ask for EV. Not recommended for SmartScreen.

Self-signed certificate

Free

Blocks installation for the public.

Development and testing, or enterprise with managed certificate trust.

No signature

Free

A strong SmartScreen block; enterprises may block it entirely.

Never for public distribution.

How SmartScreen reputation works

SmartScreen evaluates two signals when a user downloads and runs a file: the publisher reputation (is the file signed, by a known certificate?) and the file-hash reputation (has this exact file been downloaded without signs of malicious behaviour?). Even a signed, brand-new binary can show a warning until its hash or its publisher certificate accumulates enough positive evidence. What follows from the Microsoft documentation:

  • EV certificates no longer bypass SmartScreen. That behaviour was removed in 2024. EV may still matter to enterprise procurement, but paying the premium solely to avoid warnings is no longer justified.

  • Reputation builds organically through download volume. There is no exact threshold; the documentation says it can take several weeks and hundreds of clean installs from a wide audience. There is no mechanism to submit a file for consumer SmartScreen review.

  • Signing every release with a consistent identity lets reputation carry over from one version to the next. Unsigned files start from zero on every version, and changing the signing identity resets the publisher signal.

  • Do not modify signed files after signing, and do not sign potentially unwanted applications, or the certificate can acquire negative reputation.

  • On Windows 11, Smart App Control may supersede SmartScreen application reputation. It blocks unsigned executables unless they have positive reputation, and its signature checks apply to all executables, not just those downloaded from the Internet.

  • The practical way to avoid the warning is the Store: Store-installed apps are never subject to SmartScreen download warnings.

  • Enterprise environments can differ: the bypass can be disabled by policy, and IT administrators may distribute from trusted intranet locations or submit files through the Microsoft Security Intelligence portal.

Tell early adopters of a new, signed app that they may see a SmartScreen prompt, and that they should proceed only after checking the publisher name shown in it.

Sideloading and Developer Mode

Sideloading means installing a package that does not come from the Microsoft Store. Windows allows it for a package whose signature chains to a certificate the device trusts. There are three situations:

Situation What the device needs

A package signed by a publicly trusted CA or by Artifact Signing

Nothing special for the certificate, because it chains to a trusted root. Windows still evaluates SmartScreen. Installation is allowed by default on current Windows.

A package signed with a self-signed or private-CA certificate

The public certificate must be in the Trusted People store (or its root in Trusted Root Certification Authorities) of the machine or user.

A package registered from loose files for debugging

Developer Mode turned on, for the debugging and deployment features that need it.

Developer Mode replaces the older developer license. On a developer machine, toggle it in Settings, System, Advanced, For developers (on Windows 11 25H2 and later; earlier releases show a For developers page). Turning it on requires administrator access, and an organization may disable the option. In addition to sideloading, it enables debugging and additional deployment options; with Device Discovery it also starts SSH services that allow remote deployment to the device. Visual Studio shows a dialog if you build a Windows project while Developer Mode is off.

For automation, Developer Mode and sideloading map to registry values under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock: AllowAllTrustedApps set to 1 enables sideloading, and AllowDevelopmentWithoutDevLicense set to 1 enables Developer Mode. The same policies exist in the Group Policy editor under App Package Deployment. Setting these on user machines is a system-settings change; do it on development and test machines, and leave production machines on the defaults that enterprise management sets.

Installing and removing a signed package

# Trust the test certificate first (see "Certificates for Testing"), then:
Add-AppxPackage -Path .\Packages\BookshelfDesktop_1.4.0.0_x64.msix

# See what is installed, including the package full name and version.
Get-AppxPackage Example.BookshelfDesktop | Format-List Name, Version, Publisher, PackageFullName, InstallLocation

# Remove it. All files and registry entries the package registered go with it.
Get-AppxPackage Example.BookshelfDesktop | Remove-AppxPackage

Source: What is MSIX? (clean uninstall) and Grant package identity by packaging with external location manually (Add-AppxPackage and Remove-AppxPackage)

Double-clicking the .msix opens App Installer, which shows the publisher and an Install button. Distributing updates through an .appinstaller file is a distribution topic, covered on the Distribution Channels and Updates, Versioning & Telemetry pages.

The Same Packaging Decision in WPF, WinUI 3 and Windows Forms

WPF WinUI 3 Windows Forms

Default packaging

Unpackaged (dotnet publish)

Packaged (single-project MSIX) or unpackaged

Unpackaged (dotnet publish)

MSIX route

Windows Application Packaging Project, or external location

Single-project MSIX

Windows Application Packaging Project, or external location

Manifest lives in

The packaging project

The app project

The packaging project

Several executables in one package

Yes (packaging project)

No for single-project MSIX; use a packaging project

Yes (packaging project)

Trust tier of the packaged app

Full trust (runFullTrust)

Full trust by default; AppContainer is opt-in

Full trust (runFullTrust)

Windows App SDK runtime

Only if the app uses Windows App SDK APIs

Required (framework-dependent or self-contained)

Only if the app uses Windows App SDK APIs

Trimming and Native AOT

Disabled (NETSDK1168)

Documented in the Windows App SDK release notes

Disabled

Single-file publish

Unpackaged only

Unpackaged and self-contained only

Unpackaged only

What Changed Since .NET Framework

  • Old model: desktop apps shipped as MSI, ClickOnce or xcopy, with no package identity and no clean uninstall. MSIX is the modern successor of the older AppX package format that UWP used; the footprint files still use the Appx names.

  • .NET 5 and later: a packaging project can reference a .NET (not only .NET Framework) desktop project, so WPF and WinForms on .NET 10 can be packaged the same way as before.

  • Windows App SDK 1.0 and later: the single-project MSIX feature removed the need for a packaging project for WinUI 3 apps. Visual Studio 2026 builds the tools in.

  • Signing in 2023 to 2026: since June 2023 OV private keys must be on an HSM or token; since 2024 EV certificates no longer bypass SmartScreen; and Microsoft introduced Trusted Signing, now named Artifact Signing, as the managed alternative.

  • Windows 10 reached the end of support on 14 October 2025; MSIX features that need Windows 11 are called out in the documentation of each feature.

Bookshelf Desktop: the Whole Pipeline in Short

A sensible release pipeline for the running scenario, from the sections above:

  1. Keep the manifest identity (Name, Publisher) in source control, with the Publisher string taken from the certificate profile (Artifact Signing) or the OV certificate you will sign with. Use a different, test-only identity (for example Example.BookshelfDesktop.Dev with CN=Bookshelf Dev) for local builds.

  2. Build with dotnet build or MSBuild in Release for each architecture. For WPF, build the packaging project; for WinUI, build the app project with GenerateAppxPackageOnBuild.

  3. Package with AppxPackageSigningEnabled=false, so that the unsigned package is a pipeline artifact.

  4. Sign the package once, with Artifact Signing (or SignTool with a certificate from your secret store), with an RFC 3161 timestamp.

  5. Verify the signature with signtool verify /pa /v and Get-AuthenticodeSignature.

  6. Install the package on a clean VM and run the Windows App Certification Kit if you are publishing to the Store (covered on the Distribution Channels page).

  7. Publish: Store upload (the unsigned .msixupload), an App Installer feed, WinGet, or enterprise deployment.

Pitfalls

Pitfall What to do

Publisher does not equal the certificate subject

Copy the subject from the certificate (or certificate profile) into the manifest, and rebuild. Never edit a signed package.

SignTool without /fd

SignTool defaults to SHA-1. Always pass /fd SHA256, matching the package.

No timestamp

The signature dies with the certificate. With Artifact Signing it dies within three days.

Putting the .pfx or its password in the repository or a script

Read both from environment variables or CI secrets ($env:NAME, ${{ secrets.NAME }}), and delete temporary files after the build.

Importing a test certificate into LocalMachine and forgetting it

It changes trust for all users. Remove it when the test is over.

Relying on an EV certificate to avoid SmartScreen

It no longer does. Sign every release with a consistent identity and let reputation build, or publish through the Store.

Same package version installed twice

Registration fails. Increment the version on every build, or unregister first.

Forgetting the Content build action for the logo images

The package misses assets, or the build fails. Set the build action on every image in the manifest.

Several executables in a single-project MSIX

Not supported. Use a Windows Application Packaging Project.

Expecting single-project MSIX to produce a bundle

It produces a single .msix. Bundle separately for multi-architecture releases.

Treating the MSIX container as a sandbox

A full-trust packaged app still runs with the user’s authority. See Security & App Isolation.

Editing the ExternalLocation after registration

The identity package points to one folder. Unregister and register again after a move.

Assuming ms-appinstaller: links work

The protocol is disabled by default in current Windows. See the Distribution Channels page.

Summary

MSIX is a signed, ZIP-based container whose manifest declares the app’s identity, and Windows installs it cleanly and removes it completely. Use single-project MSIX for WinUI 3, a Windows Application Packaging Project for WPF and WinForms, packaging with external location when you must keep an existing installer, and the MSIX Packaging Tool only for software without source. Keep the manifest Publisher identical to the signing certificate’s subject, sign with SHA-256 and an RFC 3161 timestamp, read every secret from the environment or the CI secret store, and prefer Azure Artifact Signing (or the Microsoft Store) to managing keys yourself. Do not count on an EV certificate to satisfy SmartScreen: reputation is earned through consistent signing and download history, and sideloading needs a trusted certificate or Developer Mode.