Tutorials

MSIX Packaging

10 min read · Prerequisite: Getting Started · Wraps demo/15-msix-basic and demo/52-msix-advanced

What you'll build

MSIX is Microsoft's modern packaging format. It provides container-based isolation, automatic updates, and Windows Store distribution. FalkForge can produce both MSI and MSIX from the same codebase.

By the end of this tutorial you will have a working MSIX package, understand the AppxManifest, VFS mapping, code signing, and know how to create multi-architecture bundles.

When to use MSIX vs MSI

Both formats install software on Windows. They serve different audiences.

MSIMSIX
IsolationShared system stateContainer-based — each app gets its own virtualized filesystem and registry
UpdatesManual or via management toolsBuilt-in auto-update via .appinstaller
DistributionSCCM, Group Policy, web downloadMicrosoft Store, web download, SCCM
UninstallClean when authored correctlyAlways clean — container is removed entirely
ServicesFull Windows service supportLimited — background tasks only
Admin rightsTypically requiredPer-user install without elevation
OS supportWindows XP and laterWindows 10 1809 and later

Rule of thumb: Use MSIX for end-user desktop apps. Use MSI for server software, services, or environments that require Group Policy deployment.

Deep dive: MSIX container isolation

MSIX apps run inside a lightweight container. The container virtualizes the filesystem and registry so the app believes it writes to C:\Program Files or HKLM, but changes stay inside the package's private storage.

This means:

  • Uninstall removes everything — no leftover files or registry keys.
  • Multiple versions can coexist side-by-side.
  • Apps cannot interfere with each other's files.

The container uses the MSIX Virtual File System (VFS). FalkForge maps your KnownFolder targets to VFS paths automatically.

Prerequisites

Creating a basic MSIX package

Open demo/15-msix-basic/msix-basic.csx. This is the entire MSIX installer — about 28 lines.

Imports

using FalkForge;
using FalkForge.Compiler.Msix;
using FalkForge.Compiler.Msix.Builders;

FalkForge provides the entry point. Compiler.Msix contains the MSIX compiler and model types. Builders provides the fluent API.

The entry point

InstallerMsix.BuildMsix(args, msix =>
{
    // ... MSIX configuration ...
}, (model, outputPath) =>
{
    var compiler = new MsixCompiler();
    return compiler.Compile(model, outputPath);
});

InstallerMsix.BuildMsix takes three arguments: command-line args, a builder lambda, and a compile function. The builder lambda configures the package. The compile function receives the validated model and produces the .msix file.

Package identity

msix
    .Name("FalkForge.Demo.MsixBasic")
    .Publisher("CN=FalkForge Demo")
    .DisplayName("MSIX Basic Demo")
    .PublisherDisplayName("FalkForge")
    .Version(new Version(1, 0, 0, 0))
    .Architecture(ProcessorArchitecture.X64)

Every MSIX package has an identity: Name, Publisher, Version, and Architecture. The Publisher must match the subject of your signing certificate and start with CN=. The version requires four parts (Major.Minor.Build.Revision).

DisplayName and PublisherDisplayName are human-readable labels shown in Settings > Apps.

Deep dive: MSIX vs MSI identity

MSI uses a GUID-based UpgradeCode to link product versions. MSIX uses a string-based Name + Publisher pair instead. The combination uniquely identifies your app across all versions.

Like the MSI UpgradeCode, never change the Name or Publisher after you ship. Doing so creates a new, unrelated package from Windows' perspective.

Declaring an application

    .Application("App", "hello.exe", app => app
        .DisplayName("Hello World")
        .Square44x44Logo("assets/Square44x44Logo.png")
        .Square150x150Logo("assets/Square150x150Logo.png"))

An MSIX package must declare at least one application. Each application has an Id, an Executable path, and visual elements. The logos appear on the Start menu tile and taskbar.

Required logo sizes: 44x44 (taskbar) and 150x150 (Start menu). An optional 310x150 wide logo enables the wide tile layout.

Capabilities and signing

    .Capability("internetClient")
    .MinWindowsVersion("10.0.17763.0")
    .Signing(s => s.CertificatePath("demo-cert.pfx"));

Capabilities declare what the app can access. Common values: internetClient, privateNetworkClientServer, webcam, microphone. Restricted capabilities like runFullTrust require Store approval.

MinWindowsVersion sets the minimum OS version. The default is 10.0.17763.0 (Windows 10 1809).

Deep dive: Code signing (required for MSIX)

Unlike MSI, MSIX packages must be signed. Windows refuses to install unsigned MSIX packages. FalkForge validates this at build time (rule MSIX008).

The Publisher field in the manifest must exactly match the certificate subject. A mismatch causes installation failure.

Signing options:

  • Certificate file: .CertificatePath("path/to/cert.pfx") — good for CI/CD pipelines.
  • Certificate store: .CertificateThumbprint("ABC123...") — references a certificate in the Windows certificate store.
  • Timestamp: .TimestampUrl("http://timestamp.digicert.com") — ensures the signature remains valid after the certificate expires.

For development, create a self-signed certificate:

New-SelfSignedCertificate -Type Custom -Subject "CN=FalkForge Demo" `
  -KeyUsage DigitalSignature -FriendlyName "Dev MSIX" `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3")

Try it

dotnet script demo/15-msix-basic/msix-basic.csx

This produces a .msix file in the output directory. You can install it by double-clicking, or use PowerShell:

Add-AppxPackage -Path .\output\MSIX_Basic_Demo-1.0.0.0.msix

AppxManifest configuration

FalkForge generates the AppxManifest.xml automatically from the builder configuration. The manifest is the heart of an MSIX package — it declares identity, applications, capabilities, dependencies, and extensions.

You never write XML directly. The fluent API maps to manifest elements:

Builder methodManifest element
.Name()<Identity Name>
.Publisher()<Identity Publisher>
.Application()<Application>
.Capability()<Capability>
.RestrictedCapability()<rescap:Capability>
.Extension()<uap:Extension>
.Dependency()<PackageDependency>
.MinWindowsVersion()<TargetDeviceFamily MinVersion>
Deep dive: Windows Store requirements

Publishing to the Microsoft Store adds requirements beyond what FalkForge validates:

  • Store-signed certificate: Microsoft signs your package during submission. Use your own certificate for sideloading only.
  • Content policies: No malware, deceptive behavior, or prohibited content.
  • Package identity: Must match the identity reserved in Partner Center.
  • Restricted capabilities: Capabilities like runFullTrust require justification and approval.
  • Privacy policy: Required if the app collects personal data.
  • Age ratings: All Store apps must declare an age rating.

For enterprise sideloading, these Store requirements do not apply. You sign with your own certificate and distribute directly.

VFS (Virtual File System) mapping

MSIX uses a Virtual File System to map traditional install locations into the package container. FalkForge handles this mapping automatically when you target standard folders.

Automatic mapping (default)

When VfsMappingMode is Auto (the default), FalkForge maps KnownFolder targets to their VFS equivalents:

KnownFolderVFS path
ProgramFilesVFS/ProgramFilesX64
CommonAppDataVFS/CommonAppData
AppDataVFS/AppData
LocalAppDataVFS/LocalAppData
SystemVFS/SystemX64
WindowsVFS/Windows
FontsVFS/Fonts

For x86 packages, ProgramFiles maps to VFS/ProgramFilesX86 instead.

Manual mapping

Switch to Manual mode and provide explicit overrides:

msix
    .VfsMapping(VfsMappingMode.Manual)
    .VfsOverride("bin/release", "VFS/ProgramFilesX64/MyApp")

In Manual mode, files without an override are placed at the package root. Use overrides to place specific source directories at specific VFS paths.

Deep dive: How MSIX container isolation works with VFS

When an MSIX app reads C:\Program Files\MyApp\config.json, Windows intercepts the call and redirects it to the package's VFS/ProgramFilesX64/MyApp/config.json. The app never knows the difference.

Writes work the same way. If the app tries to write to C:\Program Files\MyApp\, the write goes to a private copy-on-write layer. The original files remain untouched. On uninstall, the private layer is deleted.

This is why MSIX uninstalls are always clean — there is no shared state to corrupt.

Advanced MSIX packaging

The demo/52-msix-advanced example shows multi-application packages, extensions, dependencies, and auto-update. Here are the key additions.

Multiple applications

msix
    // Main editor application
    .Application("Editor", "editor.exe", app => app
        .DisplayName("Demo Editor")
        .Description("Document editor with file type associations")
        .BackgroundColor("#1E1E1E")
        .Square44x44Logo("assets/Square44x44Logo.png")
        .Square150x150Logo("assets/Square150x150Logo.png")
        .Wide310x150Logo("assets/Wide310x150Logo.png"))

    // CLI utility
    .Application("Cli", "demotool.exe", app => app
        .DisplayName("Demo CLI Tool")
        .Square44x44Logo("assets/Square44x44Logo.png")
        .Square150x150Logo("assets/Square150x150Logo.png"))

    // Background service
    .Application("SyncService", "sync-service.exe", app => app
        .EntryPoint("DemoApp.SyncService")
        .DisplayName("Demo Sync Service")
        .Square44x44Logo("assets/Square44x44Logo.png")
        .Square150x150Logo("assets/Square150x150Logo.png"))

A single MSIX package can contain multiple executables. Each .Application() call registers a separate entry in the Start menu. The EntryPoint method is used for background tasks and services.

Extensions (file types and protocols)

    // File type associations
    .Extension("windows.fileTypeAssociation", "DemoApp.Editor")

    // Protocol handler (demo:// deep links)
    .Extension("windows.protocol", "DemoApp.Editor")

MSIX extensions register OS integrations. windows.fileTypeAssociation associates file types with your app. windows.protocol registers a custom URI scheme. The second argument is the entry point class.

Restricted capabilities

    .Capability("internetClient")
    .Capability("privateNetworkClientServer")
    .RestrictedCapability("runFullTrust")

Standard capabilities are declared with .Capability(). Restricted capabilities use .RestrictedCapability(). The runFullTrust capability lets the app escape some container restrictions — needed for most desktop apps converted to MSIX.

Package dependencies

    .Dependency(
        "Microsoft.VCLibs.140.00.UWPDesktop",
        "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
        new Version(14, 0, 30704, 0))

Declare framework dependencies that Windows must install before your app. The VCLibs package is the most common — it provides the Visual C++ runtime for UWP/MSIX apps.

Auto-update via .appinstaller

    .UpdateSettings("https://releases.example.com/demo/Demo.appinstaller", update =>
    {
        update.HoursBetweenUpdateChecks(6);
        update.ShowPrompt();
        update.AutomaticBackgroundTask();
        update.ForceUpdateFromAnyVersion();
    })

FalkForge generates a .appinstaller file alongside the .msix. Host it on a web server and Windows handles the rest. Update options:

MSIX bundles for multi-architecture distribution

An MSIX bundle (.msixbundle) combines multiple architecture-specific packages into one download. Windows automatically installs the correct architecture.

InstallerMsix.BuildMsixBundle(args, bundle =>
{
    bundle
        .Name("FalkForge.Demo.MultiArch")
        .Publisher("CN=FalkForge Demo")
        .Version(new Version(1, 0, 0, 0))
        .Package("output/MyApp-x64.msix", ProcessorArchitecture.X64)
        .Package("output/MyApp-arm64.msix", ProcessorArchitecture.Arm64)
        .Signing(s => s.CertificatePath("cert.pfx"));
}, (model, outputPath) =>
{
    var compiler = new MsixBundleCompiler();
    return compiler.Compile(model, outputPath);
});

Build each architecture-specific .msix first, then bundle them. The .Package() method adds a pre-built MSIX file to the bundle. Windows selects the matching architecture at install time.

Validation rules

FalkForge validates your MSIX configuration before compilation. If validation fails, you get a clear error code:

CodeRule
MSIX001Package Name is required.
MSIX002Publisher is required.
MSIX003Publisher must start with CN=.
MSIX004Version must have 4 parts (Major.Minor.Build.Revision).
MSIX005At least one Application is required.
MSIX006DisplayName is required.
MSIX007PublisherDisplayName is required.
MSIX008MSIX packages must be signed.
MSIX010Application Id is required.
MSIX011Application Executable is required.
MSIX012MinWindowsVersion must be a valid version string.
Deep dive: MSIX compilation pipeline

The MsixCompiler.Compile method runs a seven-step pipeline:

  1. Validate — checks the model against the MSIX rules.
  2. Resolve VFS layout — maps files to their VFS paths based on the mapping mode.
  3. Generate AppxManifest.xml — produces the manifest from the model.
  4. Build registry hive — if registry entries are declared, creates the virtual registry hive file.
  5. Create MSIX package — assembles the package using the Windows Appx packaging APIs.
  6. Sign the package — invokes signtool.exe with the configured certificate.
  7. Generate .appinstaller — if auto-update is configured, writes the update manifest alongside the package.

What's next