MSIX Packaging
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.
| MSI | MSIX | |
|---|---|---|
| Isolation | Shared system state | Container-based — each app gets its own virtualized filesystem and registry |
| Updates | Manual or via management tools | Built-in auto-update via .appinstaller |
| Distribution | SCCM, Group Policy, web download | Microsoft Store, web download, SCCM |
| Uninstall | Clean when authored correctly | Always clean — container is removed entirely |
| Services | Full Windows service support | Limited — background tasks only |
| Admin rights | Typically required | Per-user install without elevation |
| OS support | Windows XP and later | Windows 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
- .NET 10 SDK installed.
- Windows 10 SDK (for
signtool.exeand the Appx packaging APIs). - A code-signing certificate (self-signed is fine for development).
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 method | Manifest 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
runFullTrustrequire 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:
| KnownFolder | VFS path |
|---|---|
ProgramFiles | VFS/ProgramFilesX64 |
CommonAppData | VFS/CommonAppData |
AppData | VFS/AppData |
LocalAppData | VFS/LocalAppData |
System | VFS/SystemX64 |
Windows | VFS/Windows |
Fonts | VFS/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:
HoursBetweenUpdateChecks— how often Windows checks for a new version (default: 24).ShowPrompt— show a dialog before updating.AutomaticBackgroundTask— check for updates even when the app is not running.UpdateBlocksActivation— force the update before the app can launch.ForceUpdateFromAnyVersion— allow downgrades (useful for rollbacks).
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:
| Code | Rule |
|---|---|
MSIX001 | Package Name is required. |
MSIX002 | Publisher is required. |
MSIX003 | Publisher must start with CN=. |
MSIX004 | Version must have 4 parts (Major.Minor.Build.Revision). |
MSIX005 | At least one Application is required. |
MSIX006 | DisplayName is required. |
MSIX007 | PublisherDisplayName is required. |
MSIX008 | MSIX packages must be signed. |
MSIX010 | Application Id is required. |
MSIX011 | Application Executable is required. |
MSIX012 | MinWindowsVersion must be a valid version string. |
Deep dive: MSIX compilation pipeline
The MsixCompiler.Compile method runs a seven-step pipeline:
- Validate — checks the model against the MSIX rules.
- Resolve VFS layout — maps files to their VFS paths based on the mapping mode.
- Generate AppxManifest.xml — produces the manifest from the model.
- Build registry hive — if registry entries are declared, creates the virtual registry hive file.
- Create MSIX package — assembles the package using the Windows Appx packaging APIs.
- Sign the package — invokes
signtool.exewith the configured certificate. - Generate .appinstaller — if auto-update is configured, writes the update manifest alongside the package.
What's next
- Need traditional MSI instead? MSI Basics covers shortcuts, services, and features.
- Want EXE bundles that chain multiple MSIs? Bundles covers the bundle compiler.
- Migrating from WiX? Coming from WiX.