Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions Samples/MultiHeadedPackage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
page_type: sample
languages:
- csharp
products:
- windows
- windows-app-sdk
name: "Multi-headed MSIX package sample"
urlFragment: multi-headed-package
description: "Shows how to package multiple executables in a single MSIX or sparse package"
extendedZipContent:
- path: LICENSE
target: LICENSE
---
# Multi-headed MSIX package sample

These samples demonstrate how to create **multi-headed packages** — whether MSIX or sparse-packaged — by defining multiple `<Application>` elements in a single `Package.appxmanifest`. Each application entry gets its own Start menu tile and can be launched independently, while sharing the same package identity and installation lifecycle.

## Samples

### MSIX sample (`cs/cs-winui-msix/`)

A WinUI 3 solution using single-project MSIX packaging with two projects:

- **PrimaryApp** — The main WinUI app that owns the package manifest. Uses `$targetnametoken$` tokens so the build system fills in the correct executable name.
- **SecondaryApp** — A console app included in the same MSIX package. Its executable name is hardcoded in the manifest as `SecondaryApp.exe` with `EntryPoint="Windows.FullTrustApplication"`.

After deployment, both apps appear in the Start menu as separate entries.

### Sparse sample (`cs/cs-wpf-sparse/`)

A WPF solution demonstrating multi-headed sparse packages with runtime registration:

- **PrimaryApp** — A WPF app with `WindowsPackageType=Sparse` that registers the sparse package at runtime using the `PackageManager` API. After registration, it uses `IApplicationActivationManager` to relaunch with package identity via the app's AUMID (Application User Model ID).
- **SecondaryApp** — A minimal WPF app that detects whether it has package identity. Once the sparse package is registered, the primary app can launch this secondary app with identity using the `SecondaryApp` AUMID entry point.

Key implementation details:
- The manifest uses `uap10:AllowExternalContent="true"` to enable sparse packaging.
- Application entries use `uap10:RuntimeBehavior="win32App"` instead of `EntryPoint` (these are mutually exclusive).
- Restart/launch uses `IApplicationActivationManager.ActivateApplication()` with the package AUMID to ensure the process receives package identity. Launching the exe directly with `Process.Start` would bypass the package activation and the process would not have identity.
- A test certificate (`PrimaryApp_TemporaryKey.pfx`) is included because sparse packages must be signed to be registered.

## Prerequisites

* See [System requirements for Windows app development](https://docs.microsoft.com/windows/apps/windows-app-sdk/system-requirements).
* Make sure that your development environment is set up correctly&mdash;see [Install tools for developing apps for Windows 10 and Windows 11](https://docs.microsoft.com/windows/apps/windows-app-sdk/set-up-your-development-environment).

## Building and running

### MSIX sample

1. Open `cs/cs-winui-msix/MultiHeadedMsix.sln` in Visual Studio.
2. Set **PrimaryApp** as the startup project.
3. Press **F5** to build, deploy, and launch the primary app.
Comment on lines +53 to +54
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F5 builds aren't currently supported. When I build and run in VS (2026), I get this error:

{PathToSamplesRepo}\Samples\MultiHeadedPackage\cs\cs-winui-msix\PrimaryApp\Properties\launchSettings.json was not found. To debug a packaged single-project MSIX solution, a profile with command name MsixPackage in launchSettings.json is required. For more information, visit https://aka.ms/winappsdk.singleproj.

This error is in {pathToDotNuget}\.nuget\packages\microsoft.windowsappsdk.base\1.8.250831001\buildTransitive\Microsoft.WindowsAppSDK.SingleProject.targets, so we'll need to update the Single-project MSIX VSIX to support this as well.

4. Check the Start menu for both "Primary App" and "Secondary App" entries.

### Sparse sample

1. Open `cs/cs-wpf-sparse/MultiHeadedSparse.sln` in Visual Studio.
2. Set **PrimaryApp** as the startup project.
3. Trust the test certificate: run `certutil -addstore TrustedPeople cs\cs-wpf-sparse\PrimaryApp\PrimaryApp_TemporaryKey.pfx` from an elevated terminal.
4. Press **Ctrl+F5** to build and run without debugging.
5. Click **Register Package** to register the sparse package with the OS.
6. Click **Restart** to relaunch the app with package identity (the status should now show the package full name).
7. Click **Launch Secondary App** to launch the secondary app entry point with package identity.

To clean up, click **Unregister Package** and restart.

## Related links

- [Windows App SDK](https://docs.microsoft.com/windows/apps/windows-app-sdk/)
- [Single-project MSIX packaging](https://docs.microsoft.com/windows/apps/windows-app-sdk/single-project-msix)
- [Grant package identity by packaging with external location](https://docs.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps)
- [IApplicationActivationManager::ActivateApplication](https://learn.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-iapplicationactivationmanager-activateapplication)
54 changes: 54 additions & 0 deletions Samples/MultiHeadedPackage/cs/cs-winui-msix/MultiHeadedMsix.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31717.71
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrimaryApp", "PrimaryApp\PrimaryApp.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecondaryApp", "SecondaryApp\SecondaryApp.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Build.0 = Debug|ARM64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Deploy.0 = Debug|ARM64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Deploy.0 = Debug|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|x86
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|x86
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Deploy.0 = Debug|x86
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.ActiveCfg = Release|ARM64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.Build.0 = Release|ARM64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|x86
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|x86
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|ARM64.ActiveCfg = Debug|ARM64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|ARM64.Build.0 = Debug|ARM64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x64.ActiveCfg = Debug|x64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x64.Build.0 = Debug|x64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x86.ActiveCfg = Debug|x86
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|x86.Build.0 = Debug|x86
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|ARM64.ActiveCfg = Release|ARM64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|ARM64.Build.0 = Release|ARM64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x64.ActiveCfg = Release|x64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x64.Build.0 = Release|x64
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.ActiveCfg = Release|x86
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D1E2F3A4-B5C6-7890-DEFA-123456789ABC}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<Application
x:Class="PrimaryApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PrimaryApp">
</Application>
23 changes: 23 additions & 0 deletions Samples/MultiHeadedPackage/cs/cs-winui-msix/PrimaryApp/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.UI.Xaml;

namespace PrimaryApp
{
public partial class App : Application
{
private Window mainWindow;

public App()
{
this.InitializeComponent();
}

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
mainWindow = new MainWindow();
mainWindow.Activate();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!-- Copyright (c) Microsoft Corporation.
Licensed under the MIT License. -->
<Window
x:Class="PrimaryApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Primary App - Multi-Headed MSIX">

<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<StackPanel Spacing="16">
<TextBlock
Text="This is the Primary App"
FontSize="28"
HorizontalAlignment="Center" />
<TextBlock
Text="This app is part of a multi-headed MSIX package."
FontSize="16"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
<TextBlock
Text="Check your Start menu for both 'Primary App' and 'Secondary App' entries."
FontSize="14"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap"
TextAlignment="Center" />
</StackPanel>
</Grid>
</Window>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.UI.Xaml;

namespace PrimaryApp
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">

<Identity
Name="MultiHeadedMsixSample"
Publisher="CN=Microsoft Corporation"
Version="1.0.0.0" />

<Properties>
<DisplayName>Multi-Headed MSIX Sample</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Assets\logo.png</Logo>
</Properties>

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

<Resources>
<Resource Language="x-generate"/>
</Resources>

<Applications>
<!-- Primary: tokens replaced by build system -->
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="Primary App"
Description="Primary application in multi-headed package"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>

<!-- Secondary: hardcoded exe name, no tokens -->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the exe name hardcoded here to prevent collisions or some other reason?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is hardcoded because the build system only automatically updates the "header project" values. For the other projects, we need to hardcode the names. This is similar to WAP experience. It is in my backlog to add a feature to fill in the Application fields automatically from the referenced projects.

<Application Id="SecondaryApp"
Executable="SecondaryApp.exe"
EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
DisplayName="Secondary App"
Description="Secondary application in multi-headed package"
BackgroundColor="transparent"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
</uap:VisualElements>
</Application>
</Applications>

<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed .NET 6 is out of support. Do we want to update it to a higher version (here and in SecondaryApp.csproj?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is consistent with the other samples. If we update the dotnet version for this, we should update all of them to the minimum supported version.

<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>PrimaryApp</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding explicit package versions here as well

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The samples will not build with explicit package versions. They are defined by the repository here https://github.com/microsoft/WindowsAppSDK-Samples/blob/release/experimental/Samples/Directory.Packages.props

<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
</ItemGroup>

<ItemGroup>
<Content Include="Assets\SplashScreen.png" />
<Content Include="Assets\Square150x150Logo.png" />
<Content Include="Assets\Square44x44Logo.png" />
<Content Include="Assets\logo.png" />
<Content Include="Assets\Wide310x150Logo.png" />
</ItemGroup>

<ItemGroup>
<!-- Secondary app included in the MSIX package -->
<ProjectReference Include="..\SecondaryApp\SecondaryApp.csproj" />
</ItemGroup>

<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
Tools extension to be activated for this project even if the Windows App SDK Nuget
package has not yet been restored -->
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="PrimaryApp.app"/>

<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
</windowsSettings>
</application>
</assembly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace SecondaryApp
{
class Program
{
static void Main()
{
Console.WriteLine("=== Secondary App - Multi-Headed MSIX ===");
Console.WriteLine();
Console.WriteLine("This is the Secondary App.");
Console.WriteLine("Both this app and the Primary App were installed from the same MSIX package.");
Console.WriteLine();
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<RootNamespace>SecondaryApp</RootNamespace>
<Platforms>x86;x64;ARM64</Platforms>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<!-- No EnableMsixTooling, no Package.appxmanifest — this project is packaged by PrimaryApp -->
</PropertyGroup>
</Project>
Loading