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** — MSIX or sparse packages that contain 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.
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 -->
<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>
<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" />
<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