-
Notifications
You must be signed in to change notification settings - Fork 313
Add multi-headed MSIX package sample apps - experimental #601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/experimental
Are you sure you want to change the base?
Changes from all commits
bcfc9db
c5fcd5a
9f40b3d
03466aa
3f70eab
e41d390
2c2596d
d041c47
3305e5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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—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) | ||
| 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> |
| 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(); | ||
| } | ||
| } | ||
| } |
| 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 --> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding explicit package versions here as well
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
There was a problem hiding this comment.
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:
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.