MSIX で配布した .NET Framework 4.8 の WinForms アプリを、別の WinForms アプリから確実に起動したい──本記事はそのための実践ガイドです。
UWP 専用の Launcher.LaunchUriAsync に頼らず、Win32/WinForms 側だけで完結する「A. AUMID を使った直接起動(推奨)」と「B. カスタム URI での起動(代替)」の2手法を、設計のポイント、落とし穴、実装コード、検証方法まで一気通貫で解説します。
背景とゴール
前提条件は次のとおりです。
- 2つの WinForms アプリ(.NET Framework 4.8)を MSIX パッケージとして配布済み(Microsoft Store またはサイドロード)。
- 起動元アプリから、もう一方のアプリをコードで起動したい。
- UWP API
Windows.System.Launcher系は使わず、Win32/COM/シェルの標準機能で解決したい。
この記事のゴールは「MSIX 同士のアプリ連携を、AUMID またはカスタム URI のいずれかで確実に実装すること」です。
本記事の結論
| 目的 | 手順・ポイント | 補足 |
|---|---|---|
| A. アプリを直接起動する(推奨) | 対象アプリの AUMID (AppUserModelId) を取得する。AUMID は PackageFamilyName + "!" + ApplicationId で構成。 起動元アプリで IApplicationActivationManager(COM)を呼び出して ActivateApplication する。 | 追加のマニフェスト変更は不要。 AUMID は「アプリのバージョン」に依存しないが、Publisher が変わると PackageFamilyName が変わるため、その場合は AUMID も変わる点に注意。 |
| B. カスタム URI で起動する(代替案) | 対象アプリの MSIX マニフェストに <uap:Protocol Name="myapp" /> を追加し、URI を受け付けるようにする。 起動元アプリで Process.Start("myapp://...") または ShellExecuteEx を使って起動。 | プロトコル名は英小文字推奨。Win32 側だけで完結。URI 経由でパラメータ受け渡しも容易。 |
A. AUMID を使ってアプリを直接起動(推奨)
AUMID とは何か
AUMID(AppUserModelId)は「スタートメニューに並ぶ 1 つのアプリ」を一意に表す ID です。MSIX でパッケージ化されたアプリの AUMID は次の式で決まります。
AUMID = PackageFamilyName + "!" + ApplicationId
- PackageFamilyName: パッケージ名(Name)と Publisher(署名者)から導出されるファミリ名。
- ApplicationId: パッケージ マニフェストの
<Application Id="..." />属性。
したがって、アプリの バージョンが変わっても AUMID は不変ですが、Publisher が変わる(例: Store 署名 → 社内証明書)と PackageFamilyName が変わり、AUMID も変化します。配布経路(Store/サイドロード)が同じ Publisher による署名であれば AUMID は同一です。
AUMID を取得する(3つの方法)
1) Visual Studio のパッケージ プロジェクトから読み取る(最も正確)
- Windows Application Packaging Project(または MSIX パッケージ化プロジェクト)の
Package.appxmanifestを開く。 <Identity Name="Acme.MyApp" Publisher="CN=ACME, O=..." />の Name と Publisher を確認。<Applications><Application Id="App" ... /></Applications>のIdを確認。- 実機でインストールした後、PowerShell で
PackageFamilyNameを取得し、!Idを連結する。
2) PowerShell で取得(手早い)
# 例: Name = Acme.MyApp のパッケージ
$pkg = Get-AppxPackage -Name "Acme.MyApp"
$pfm = $pkg.PackageFamilyName
$aumid = "$pfm!App" # Application Id が "App" の場合
$aumid
補足: Application Id はプロジェクトのマニフェストで確認してください。既定では「App」のことが多いですが、複数アプリを含むパッケージでは Id が複数存在します。
3) スタートメニュー経由で検証(現地確認)
- Win + R →
shell:AppsFolderを開く。 - 対象アプリを右クリック → 「ショートカットの作成」などからプロパティを確認すると、
...AppsFolder\PackageFamilyName!ApplicationIdの形で参照されていることが分かります。 - テストとして
explorer.exe shell:AppsFolder\{PackageFamilyName}!{ApplicationId}を実行して起動できるか確認可能。
IApplicationActivationManager の定義(C# / VB)
WinForms (.NET Framework 4.8) からは COM を使って Shell にアクティベーションを依頼します。必要なのは インターフェース定義 と CLSID の 2 つだけです。
C# の定義とユーティリティ
using System;
using System.Runtime.InteropServices;
[ComImport]
[Guid("2e941141-7f97-4756-ba1d-9decde894a3d")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IApplicationActivationManager
{
int ActivateApplication(
[MarshalAs(UnmanagedType.LPWStr)] string appUserModelId,
[MarshalAs(UnmanagedType.LPWStr)] string arguments,
ActivateOptions options,
out uint processId);
// 参考:必要に応じて追加
int ActivateForFile(
[MarshalAs(UnmanagedType.LPWStr)] string appUserModelId,
IntPtr /* IShellItemArray */ itemArray,
[MarshalAs(UnmanagedType.LPWStr)] string verb,
out uint processId);
int ActivateForProtocol(
[MarshalAs(UnmanagedType.LPWStr)] string appUserModelId,
IntPtr /* IShellItemArray */ itemArray,
out uint processId);
}
[Flags]
enum ActivateOptions : uint
{
None = 0x00000000
}
[ComImport]
[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
class ApplicationActivationManager { }
public static class MsixActivator
{
public static uint ActivateByAumid(string aumid, string args = "")
{
var mgr = (IApplicationActivationManager)new ApplicationActivationManager();
var hr = mgr.ActivateApplication(aumid, args ?? string.Empty, ActivateOptions.None, out var pid);
if (hr < 0) Marshal.ThrowExceptionForHR(hr);
return pid;
}
}
VB.NET の定義と呼び出し
Imports System.Runtime.InteropServices
Interface IApplicationActivationManager
Function ActivateApplication(
appUserModelId As String,
arguments As String,
options As ActivateOptions,
ByRef processId As UInteger) As Integer
Function ActivateForFile(
<MarshalAs(UnmanagedType.LPWStr)> appUserModelId As String,
itemArray As IntPtr, ' IShellItemArray
<MarshalAs(UnmanagedType.LPWStr)> verb As String,
ByRef processId As UInteger) As Integer
Function ActivateForProtocol(
<MarshalAs(UnmanagedType.LPWStr)> appUserModelId As String,
itemArray As IntPtr, ' IShellItemArray
ByRef processId As UInteger) As Integer
End Interface
Enum ActivateOptions As UInteger
None = 0UI
End Enum
Class ApplicationActivationManager
End Class
Public Module MsixActivator
Public Function ActivateByAumid(aumid As String, Optional args As String = "") As UInteger
Dim mgr = CType(Activator.CreateInstance(GetType(ApplicationActivationManager)), IApplicationActivationManager)
Dim pid As UInteger = 0
Dim hr = mgr.ActivateApplication(aumid, args, ActivateOptions.None, pid)
If hr < 0 Then Marshal.ThrowExceptionForHR(hr)
Return pid
End Function
End Module
実行例(C# / VB)
// C#
string aumid = "Acme.MyApp_1234567890abc!App"; // 例
uint pid = MsixActivator.ActivateByAumid(aumid, "--from=Launcher --mode=Viewer");
Console.WriteLine($"Launched PID: {pid}");
' VB.NET
Dim aumid = "Acme.MyApp_1234567890abc!App" ' 例
Dim pid = MsixActivator.ActivateByAumid(aumid, "--from=Launcher --mode=Viewer")
Console.WriteLine($"Launched PID: {pid}")
引数の受け取り(起動先アプリ)
起動先の WinForms アプリでは、通常のコマンドラインとして受け取れます。
// Program.cs(C#)
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// ここで args を解釈
Application.Run(new MainForm());
}
URI と同様に扱いたい場合は --key=value 形式を推奨します。空白や日本語は URL エンコードまたは Base64 で安全に搬送できます。
エラーと対処(AUMID 直接起動)
| 現象/コード | 原因 | 対処 |
|---|---|---|
| 0x80270254 / 例外「The app didn’t start」 | AUMID が誤り(パッケージ完全名を連結している等)。 | PackageFamilyName + "!" + ApplicationId を厳守。PowerShell で再確認。 |
| 0x80073CF9 等(パッケージ未登録) | 対象アプリがインストールされていない、またはユーザー プロファイルが異なる。 | 対象ユーザーでインストールしているか確認。マルチユーザー環境ではユーザーごとに検証。 |
| 管理者(高い整合性)からの起動が不安定 | パッケージ アプリは中整合性で動作するため、トークンの違いが影響。 | 起動元を通常権限で実行するか、ブローカー プロセスを用いる。 |
| サービス(Session 0)から起動不可 | インタラクティブ セッション外からの UI 起動はサポート外。 | ユーザー セッション内のブリッジ プロセス経由で行う。 |
B. カスタム URI で起動する(代替)
マニフェスト設定(MSIX 側)
起動先アプリの Package.appxmanifest にプロトコル拡張を追加します。Win32(FullTrust)アプリでも MSIX での登録は可能です。
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
<Applications>
<Application Id="App"
Executable="MyApp.exe"
EntryPoint="Windows.FullTrustApplication">
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="myapp">
<uap:DisplayName>My App Protocol</uap:DisplayName>
<uap:Logo>Assets\Square44x44Logo.png</uap:Logo>
</uap:Protocol>
</uap:Extension>
</Extensions>
</Application>
</Applications>
</Package>
複数アプリを含む場合は、対象アプリ(Id)の <Application> 内に設定してください。
起動元アプリ(WinForms)からの呼び出し
// C#
System.Diagnostics.Process.Start("myapp://open?file=abc&mode=view");
' VB.NET
Process.Start("myapp://open?file=abc&mode=view")
内部的には ShellExecute が実行され、プロトコル ハンドラとして登録された MSIX アプリが起動します。
失敗時は既定ブラウザーや他アプリに奪われていないか(同一スキーム競合)を確認してください。
受け取り側の引数処理
パッケージ化した WinForms アプリでは、起動時のコマンドラインに URI が渡されます。通常は args[0] または Environment.GetCommandLineArgs() から取得できます。
// C#
static void Main(string[] args)
{
// args 例: ["MyApp.exe", "myapp://open?file=abc&mode=view"]
var uriArg = args.FirstOrDefault(a => a.StartsWith("myapp://", StringComparison.OrdinalIgnoreCase));
if (uriArg != null)
{
var uri = new Uri(uriArg);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
var file = query["file"];
var mode = query["mode"];
// TODO: パラメータに応じた画面遷移
}
Application.Run(new MainForm());
}
利点と欠点(AUMID 直接起動 vs URI)
| 項目 | AUMID 直接起動 | カスタム URI |
|---|---|---|
| 実装コスト | COM 呼び出しが必要だが追加マニフェスト不要 | マニフェスト追加が必要だが呼び出しは簡単 |
| 引数の柔軟性 | 自由文字列(コマンドライン) | URI で標準化しやすい |
| バージョン安定性 | バージョンに依存せず安定(Publisher 変更時は要更新) | スキーム名が同じなら安定(競合管理は必要) |
| 外部連携 | Windows 内の起動に特化 | 他アプリ・ブラウザー等からも起動可能 |
| デバッグ容易性 | explorer.exe shell:AppsFolder\AUMIDで容易 | ブラウザーのアドレスバー等でもテスト可 |
よくあるハマりどころと対策
- パッケージ完全名(FullName)を AUMID と誤認
AUMID は PackageFamilyName を使います。PowerShell のGet-AppxPackageでPackageFamilyNameを確認しましょう。FullName を連結すると起動失敗(0x80270254)になります。 - Publisher が異なるビルドの混在
Store 版と社内署名版で Publisher が異なるとPackageFamilyNameが変わります。AUMID も変わるため、起動元アプリに どのビルドの相手を想定しているか を明示するか、設定で上書きできるようにしておきましょう。 ActivateForProtocolで 0x800706BE(RPC エラー)
第2引数にはIShellItemArrayを渡す必要があり、単なる文字列では動きません。URI を気軽に渡したい場合は、B. カスタム URI 方式を利用する方がシンプルです。- 管理者権限プロセスからの起動
パッケージ アプリは中整合性で実行されるため、Elevated → Non-Elevated の橋渡しで制約が出ることがあります。起動専用の中整合性ブローカーを用意するか、起動元自体を通常権限で動かしましょう。 - サービスやスケジュール タスクからの直接起動
ユーザー インタラクション前提のため、Session 0 などからの起動は不可です。ユーザー セッションのブリッジ経由で行います。
堅牢性のための設計パターン
起動手段のフォールバック
- まず AUMID 直接起動を試みる。
- 失敗したら(Publisher 差異や未インストールの可能性)カスタム URI にフォールバック。
- それでも失敗したら、ユーザーにインストール状態の確認ダイアログを出す。
// C#
public static bool TryLaunchOtherApp()
{
try
{
MsixActivator.ActivateByAumid("Acme.MyApp_123456!App", "--mode=Viewer");
return true;
}
catch
{
try
{
System.Diagnostics.Process.Start("myapp://open?mode=Viewer");
return true;
}
catch { return false; }
}
}
引数の安全な受け渡し
- スペース・日本語・記号は URL エンコード か Base64 で運ぶ。
- 受け取り側は 許可リスト(Allowlist) でコマンドを解釈する(例:
modeはview/edit以外拒否)。 - ログにはデコード後の見やすい形と、原文(エスケープ済み)を併記してデバッグ容易性を確保。
検証・デバッグのチートシート
| 操作 | コマンド/手順 | 期待結果 |
|---|---|---|
| PackageFamilyName の取得 | (Get-AppxPackage -Name Acme.MyApp).PackageFamilyName | 例: Acme.MyApp_1234567890abc |
| AUMID テスト起動 | explorer.exe shell:AppsFolder\Acme.MyApp_1234567890abc!App | 対象アプリが起動する |
| URI テスト起動 | ファイル名入力欄に myapp://open?mode=view | 対象アプリが起動、引数を認識 |
| 起動失敗の切り分け | イベント ビューアー → アプリケーションとサービス ログ → Microsoft → Windows → AppXDeployment-Server | エラーログで原因(登録・署名・競合)を特定 |
「ActivateForFile / ActivateForProtocol」を選ばない理由
IApplicationActivationManager には ActivateForFile と ActivateForProtocol もありますが、これらは IShellItemArray を構築して引き渡す必要があります。WinForms から簡便に扱うにはハードルが高く、エラー(0x800706BE など)になりやすいのが実情です。URI を使いたいだけであれば、マニフェストでプロトコル登録 + Process.Start の方がはるかに簡単・堅牢です。
ストア版 / サイドロード版の違いと運用
- AUMID の安定性: バージョンは無関係。ただし Publisher が異なると PackageFamilyName が変わるため、AUMID も変化します。社内配布と Store を併用する場合は注意。
- カスタム URI の安定性: スキーム名が同じであれば安定。インストールが複数あると競合の可能性はありますが、実運用では同時共存を避けるポリシーで解決するのが定石です。
- 設定で吸収: 起動先識別子(AUMID / URI スキーム)を設定ファイルに持たせ、環境差を吸収する設計が実用的です。
実装テンプレート(そのままコピペ可)
C#: AUMID/URI 両対応のヘルパー
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace MsixLaunch
{
[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IApplicationActivationManager
{
int ActivateApplication(string appUserModelId, string arguments,
ActivateOptions options, out uint processId);
int ActivateForFile(string appUserModelId, IntPtr itemArray,
string verb, out uint processId);
int ActivateForProtocol(string appUserModelId, IntPtr itemArray,
out uint processId);
}
[Flags] enum ActivateOptions : uint { None = 0 }
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
class ApplicationActivationManager { }
public static class MsixLauncher
{
public static bool TryLaunchByAumid(string aumid, string args, out uint pid, out Exception error)
{
pid = 0; error = null;
try
{
var mgr = (IApplicationActivationManager)new ApplicationActivationManager();
var hr = mgr.ActivateApplication(aumid, args ?? string.Empty, ActivateOptions.None, out pid);
if (hr < 0) Marshal.ThrowExceptionForHR(hr);
return true;
}
catch (Exception ex) { error = ex; return false; }
}
public static bool TryLaunchByUri(string uri, out Exception error)
{
error = null;
try
{
Process.Start(uri);
return true;
}
catch (Exception ex) { error = ex; return false; }
}
}
}
VB.NET: AUMID 起動のミニマム実装
Imports System.Runtime.InteropServices
Module MsixAumidLaunch
Interface IApplicationActivationManager
Function ActivateApplication(appUserModelId As String,
arguments As String,
options As ActivateOptions,
ByRef processId As UInteger) As Integer
End Interface
Enum ActivateOptions As UInteger
None = 0UI
End Enum
<ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")>
Class ApplicationActivationManager
End Class
Public Function LaunchAumid(aumid As String, Optional args As String = "") As UInteger
Dim mgr = CType(Activator.CreateInstance(GetType(ApplicationActivationManager)),
IApplicationActivationManager)
Dim pid As UInteger = 0
Dim hr = mgr.ActivateApplication(aumid, args, ActivateOptions.None, pid)
If hr < 0 Then Marshal.ThrowExceptionForHR(hr)
Return pid
End Function
End Module
品質を上げる運用 Tips
- ロギング: AUMID、渡した引数、戻り PID、例外(HRESULT)を Info レベルで記録。サポート窓口の解決速度が上がります。
- ヘルスチェック: 初回起動時や設定画面に「相手アプリをテスト起動」ボタンを用意。AUMID/URI の誤りを早期に発見。
- ユーザー通知: 起動に失敗したら「インストールされていない可能性」と「期待するエディション(Store/Enterprise)」をガイド。
- スキーム設計: URI は
myapp://action?key=valueのように 動詞+名詞 を基本形に。意味が一意になるように命名。
FAQ
Q. AUMID とパッケージ完全名(FullName)の違いは?
A. AUMID は PackageFamilyName!ApplicationId。FullName はバージョンやアーキテクチャを含む完全識別子で、AUMID には使いません。
Q. 相手が複数バージョン共存しているときは?
A. AUMID はバージョンに依存しないのでそのまま機能します。URI の場合は最新登録に関連付くため、事前ポリシー(共存不可)で回避するのが無難です。
Q. Launcher.LaunchUriAsync を WinForms から直接使えないの?
A. Windows ランタイム API を .NET Framework から扱うには追加参照や制約が多く、MSIX/Win32 の文脈では COM(AUMID)か URI の二択が実用的です。
まとめ
MSIX で配布した .NET Framework 4.8 の WinForms アプリを、別の WinForms アプリから起動する最短ルートは 2 つです。(A)AUMID + IApplicationActivationManager による直接起動は追加のマニフェストを必要とせず、パラメータ渡しも自由度が高い王道解です。(B)カスタム URI はマニフェスト変更が必要ですが、呼び出しは極めて簡単で、他アプリ・ブラウザーからの統一的なエントリとしても使えます。
いずれの方式でも、「AUMID の正確な把握」「Publisher 変更時の識別子差異」「引数の安全設計(エンコード/Allowlist)」の 3 点を押さえれば、.NET Framework 4.8 環境でも MSIX 同士のアプリ連携は十分に堅牢に実現できます。
付録:コピペ用 PowerShell スニペット集
指定 Name の AUMID を生成(ApplicationId=App 前提)
$name = "Acme.MyApp"
$pkg = Get-AppxPackage -Name $name
$aumid = "$($pkg.PackageFamilyName)!App"
$aumid
インストール済み自社アプリ(Name に ‘Acme’ を含む)の AUMID 一覧
Get-AppxPackage | Where-Object { $_.Name -like "*Acme*" } |
ForEach-Object {
"{0}!App" -f $_.PackageFamilyName
}
AUMID の妥当性をその場で検証
$aumid = "Acme.MyApp_1234567890abc!App"
Start-Process explorer.exe "shell:AppsFolder\$aumid"
付録:エラーコード早見表(実務で遭遇しやすいもの)
| HRESULT / 状態 | 意味 | 主な対処 |
|---|---|---|
| 0x80270254 | アプリの起動に失敗(AUMID 誤り等) | AUMID を再確認。PFN ではなく PackageFamilyName を使用。 |
| 0x80073CF9 | パッケージ関連の一般的なエラー | 再登録/再インストール。イベントログで原因特定。 |
| 0x800706BE | RPC の呼び出しに失敗(ActivateForXXX 系) | IShellItemArray の生成要件を満たすか、URI 方式に切替。 |
付録:セキュリティとコンプライアンスの注意
- プロトコル ハンドラは OS 全体に影響する登録です。スキーム名は社内で予約・管理し、第三者アプリと衝突しないようにしてください。
- 外部から URI を投げ込まれる可能性を考慮し、必ず入力検証を行ってください。
- ログに URI(含むトークン等)を残す場合はマスキングポリシーを設け、個人情報や機微情報に配慮しましょう。
実装チェックリスト
- [ ] AUMID =>
PackageFamilyName!ApplicationIdの形で取得できている - [ ]
IApplicationActivationManagerを使って正常に起動できる - [ ] 引数のエンコード/デコードと Allowlist を実装済み
- [ ] フォールバックとしてカスタム URI も機能する
- [ ] Publisher 変更時の AUMID 更新手順がドキュメント化されている
- [ ] 検証スクリプト(PowerShell)がリポジトリに同梱されている
サンプル:ヘルパーを使った起動 UI(C# WinForms)
// ボタン 1: AUMID で直接起動
private void btnAumid_Click(object sender, EventArgs e)
{
var aumid = txtAumid.Text.Trim();
try
{
var pid = MsixLaunch.MsixLauncher.TryLaunchByAumid(aumid, txtArgs.Text, out var outPid, out var ex)
? outPid : 0;
lblResult.Text = pid > 0 ? $"OK: PID={pid}" : $"NG: {ex?.Message}";
}
catch (Exception ex)
{
lblResult.Text = ex.Message;
}
}
// ボタン 2: URI で起動
private void btnUri_Click(object sender, EventArgs e)
{
try
{
var ok = MsixLaunch.MsixLauncher.TryLaunchByUri(txtUri.Text, out var ex);
lblResult.Text = ok ? "OK" : $"NG: {ex?.Message}";
}
catch (Exception ex)
{
lblResult.Text = ex.Message;
}
}
最後に
「AUMID で直接起動」か「カスタム URI で起動」か。どちらの方式も十分に実戦投入できます。まずは AUMID を用いた直接起動をベースラインに採用し、環境差や外部連携の要件が濃い場合は URI を併用する──この二刀流が現場での正解パターンです。本記事のテンプレートとチェックリストをそのまま組み込み、今日から MSIX のアプリ連携を堅牢にしていきましょう。

コメント