スパース パッケージ: パッケージ化されていないアプリに ID を付与する

実際のエンド ツー エンドの例 (WPF アプリ + Inno セットアップ インストーラー) については、スパース アプリのサンプルを参照してください。

dotnet build、MSBuild、CMake、またはその他のツールチェーンを使用して構築された標準デスクトップ実行可能ファイルには、パッケージ ID がありません。 ID がないと、多くの最新のWindows API (トースト通知、バックグラウンド タスク、共有ターゲット、スタートアップ タスク、アプリ データ API など) を使用することはできません。

スパース パッケージは 、バイナリを MSIX に移動 することなく 、アプリに ID を付与します。 小さな ID 情報のみ.msix (マニフェストのみ) を提供し、外部の場所を使用して、通常どおりインストールされたアプリと一緒に登録します。 .exeは、インストーラーが配置する場所に正確にとどまります。 これは、 winapp create-debug-identityに対応する運用環境です。これは開発者向けデバッグ専用です。

このガイドでは、 パッケージ化されていないアプリに対 する公式の許可 ID ワークフローの最初の 3 つの手順にマップされる 3 つの CLI ステップについて説明します。

Step 命令 Result
1. ID マニフェストを作成する winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. ID パッケージをビルドして署名する winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. アプリに ID を埋め込む winapp embed-identity <exe> <msix> exe の Fusion マニフェスト内の要素

ドキュメントの手順 4 から 5 (パッケージの登録/登録解除) は 、インストーラーの 責任です。 インストーラーの統合を参照してください。

スパース パッケージを使用する場合

  • 既に成熟したインストーラー (Inno Setup、WiX、NSIS、MSI) があり、配布のために MSIX に切り替える必要はありませんが、ID ゲートWindows API が必要です。
  • アプリは、MSIX で許可されていないパスに、または MSIX で許可されていないレイアウトでインストールする必要があります。
  • 最小限の追加の変更が必要です。既存のインストール フローを維持し、登録手順 .msix 1 つ追加します。

初めて開始し、MSIX として配布できる場合は、完全なパッケージ アプリ (winapp init + winapp pack <folder>) の方が簡単です。

前提条件

  1. Windows 10バージョン 2004 (ビルド 19041) 以降。 スパース パッケージは、19041 以降を必要とする uap10:AllowExternalContentに依存します。
  2. winapp CLI — winget を使用してインストールします (既にインストールされている場合は更新)。
    winget install Microsoft.WinApp --source winget
    
  3. ターゲット コンピューターで信頼されているコード署名証明書。 ローカル テストの場合は、 winapp cert generate を使用して開発証明書を生成し、信頼します。 運用パッケージは、マニフェスト Publisherとサブジェクトが一致する証明書で署名する必要があります。

Walkthrough

次の例では、 ./bin/Release/net8.0-windows/MyApp.exeでビルドされた実行可能ファイルを想定しています。

手順 1 - スパース ID マニフェストを作成する

winapp init --exe ./bin/Release/net8.0-windows/MyApp.exe --sparse

これにより、exe のパッケージ名、発行元、バージョン、および説明が (ファイル バージョン情報を介して) 推測され、受け入れるかオーバーライドするように求められます。 CI のプロンプトをスキップする --use-defaults (または --no-prompt) を追加し、特定の値をオーバーライドする --name / --publisher します。

winapp init --exe ./bin/Release/net8.0-windows/MyApp.exe --sparse --use-defaults `
  --name "Contoso.MyApp" --publisher "CN=Contoso"

既定では、現在のディレクトリ内の専用の sparse/ フォルダーに以下を書き込みます ( --output-dirでオーバーライドします)。

  • appxmanifest.xml<uap10:AllowExternalContent>true</uap10:AllowExternalContent> ( <Properties>の下の要素)、 ProcessorArchitecture="neutral"win32App アプリケーション、および Executable に入力された exe 名を含むスパース マニフェスト。
  • Assets/ — プレースホルダーのビジュアル アセット (可能な場合は exe のアイコンから抽出されます)。

なぜexeの横ではなく sparse/ フォルダ? マニフェストとAssets/は、winapp packwinapp embed-identityによって使用されるビルド時の入力です。実行時に exe の横から読み取るものはありません (ランタイム ID は、exe に埋め込まれた<msix>要素と登録済みのパッケージの外部の場所から取得され、マニフェストは名前によって exe を参照するため、その場所は exe が存在する場所とは無関係です)。 これらをソース管理の専用フォルダーに書き込むと、クリーン/リビルドによってワイプされるビルド出力ディレクトリ ( bin/ など) からそれらを保持し、次の手順でクリーンな状態が維持されるように、フォルダーをバイナリから解放します。 winapp pack winapp embed-identity sparse/を自動的に検索するため、パスに名前を付ける必要はほとんどありません。

注: スパース init フローでは、すべての SDK/パッケージのインストール が意図的にスキップされます。ID のみのパッケージには SDK の依存関係はありません。

ターゲット ディレクトリに既に appxmanifest.xml が存在する場合、init は上書き (およびその Assets/) ではなく停止します。 --forceを使用して再実行して再生成します。

生成されたマニフェストの Publisher が、署名に使用する証明書と一致していることを確認します。 必要に応じて appxmanifest.xml を編集するか、生成時に --publisher 渡します。

手順 2 - ID パッケージをビルドして署名する

スパース マニフェスト (フォルダーではなくファイル) で winapp pack をポイントします。

winapp pack ./sparse/appxmanifest.xml --cert ./devcert.pfx

マニフェストは AllowExternalContentを宣言するため、 winapp pack はマニフェストのみを含む ID 専用.msix バイナリもアセットも作成しません。 出力先は既定では現在のディレクトリの <PackageName>.identity.msix です。--output を使用してそれを変更できます。 署名は、 --cert (または --generate-cert) に合格した場合にのみ行われます。

手順 3 - アプリに ID を埋め込む

Windowsが実行中の exe を ID パッケージに接続するように、<msix>要素を埋め込みます。

# EXE mode — modify the built binary in place (uses mt.exe)
winapp embed-identity ./bin/Release/net8.0-windows/MyApp.exe

または、サイド バイ サイド マニフェストをチェックイン ファイルとして維持し、再構築します。

# XML mode — update an external SxS manifest, then rebuild your app
winapp embed-identity ./app.manifest

XML モードでは、 <msix> 要素がターゲット マニフェストに挿入 (または置換) されます。 プロジェクトからそのマニフェストを参照し (.NET、<ApplicationManifest>app.manifest</ApplicationManifest>を設定)、要素が exe に埋め込まれるように再構築します。

どちらのモードでも、スパース appxmanifest.xmlから ID が読み取られます。 --manifest を省略すると、ラッパーは最初にターゲットの横にあるsparse/ フォルダー (既定で winapp init --exe --sparse が書き込む場所) を探し、次に現在のディレクトリでターゲットと現在のディレクトリの横にフォールバックし、--manifest を渡して他の場所を指します。

注: EXE モードでは、バイナリが mt.exe で書き換えられます。これによって、既存の Authenticode 署名が無効になります。 配布する前に、exe (例: winapp sign ./MyApp.exe <cert.pfx>) に再署名します。

手順 4 - 登録 (ローカル テスト用)

マニフェストのロゴは、ID のみの .msix からではなく、実行時に外部の場所から解決されます。 手順 1 ではそれらは ./sparse/Assets 配下に書き出されるため、登録する前に exe ファイルの横(外部の場所)にコピーしてください。そうしないと、Windows はマニフェストが参照しているすべてのロゴが欠けたレイアウトを登録してしまいます。

# Copy the generated assets into the external location (beside your exe)
Copy-Item ./sparse/Assets -Destination .\bin\Release\net8.0-windows\Assets -Recurse -Force

次に、そのフォルダー ( 外部の場所) に対して ID パッケージを登録します。

Add-AppxPackage -Path .\MyApp.identity.msix `
  -ExternalLocation (Resolve-Path .\bin\Release\net8.0-windows)

アプリを起動し、ID が存在することを確認します。たとえば、Windows.ApplicationModel.Package.Current.Id.FamilyName はパッケージ ファミリ名をスローするのではなく、返す必要があります。

クリーンアップするには:

Remove-AppxPackage <full-package-name>

資産の処理

スパース .msixID 専用です。 マニフェストによって参照されるビジュアル アセット (Assets\StoreLogo.png、タイルなど) は、実行時の外部コンテンツの場所 (つまり、アプリのインストール ディレクトリから) から、.msixからではなく解決されます。

つまり、 アプリケーションと共に Assets/ フォルダーを配置 する必要があります (マニフェストが想定するレイアウトと同じで、外部の場所を基準に)。

手順 2 ではマニフェスト ファイル を直接パックします (winapp pack ./sparse/appxmanifest.xml)。このマニフェストから ID のみの .msix をビルドします。兄弟ファイルは無視されるため、アセットやバイナリは含まれていません。 (マニフェストがAllowExternalContent宣言されているフォルダーwinapp packをポイントすると、スパース パッケージの場合は、.msix内ではなく外部の場所に属しているため、見つけたアセットまたはバイナリに関する警告が表示されます)。

インストーラーの統合

登録と登録解除は、インストーラーのジョブです。 このパターンは、インストーラー ツール全体で同じです。

  • インストール: アプリ バイナリ、 Assets/ フォルダー、 .msix をインストール ディレクトリにコピーし、 Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>"実行します。
  • アンインストール: ファイルを削除する前に Remove-AppxPackage <full-package-name> を実行します。

セキュリティ: インストール ディレクトリはインストール時に解決され、PowerShell 文字列リテラルから抜け出す文字 (単一引用符など) を含む場合があります。 パスを -Command 文字列に補間する前に、常にエスケープまたは検証してください。以下の WiX スニペットと NSIS スニペットでは、信頼できるインストール パスが想定されますが、Inno セットアップの例では安全なエスケープが示されています。 インライン -Command補間よりも、パスを-File スクリプトの引数として渡すことをお選びください。

Inno Setup

単一引用符で囲まれた PowerShell リテラルのランタイム インストール パスがエスケープされるように、 [Code] 関数で PowerShell 引数をビルドします ( ' を含むインストール ディレクトリはスクリプトを挿入できません)。

[Files]
Source: "dist\*"; DestDir: "{app}"; Flags: recursesubdirs
Source: "MyApp.identity.msix"; DestDir: "{app}"

[Run]
Filename: "powershell.exe"; Parameters: "{code:RegisterParams}"; Flags: runhidden

[UninstallRun]
Filename: "powershell.exe"; \
  Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Get-AppxPackage -Name 'MyApp' | Remove-AppxPackage"""; \
  Flags: runhidden

[Code]
function EscapePSLiteral(const Value: string): string;
var S: string;
begin
  S := Value; StringChange(S, '''', ''''''); Result := S;
end;

function RegisterParams(Param: string): string;
var AppDir: string;
begin
  AppDir := ExpandConstant('{app}');
  { -ErrorAction Stop + try/catch make a registration failure terminating, so powershell.exe
    exits nonzero and the AfterInstall callback (see the full sample) can abort with rollback. }
  Result := '-NoProfile -ExecutionPolicy Bypass -Command "try { Add-AppxPackage -Path ''' +
    EscapePSLiteral(AppDir + '\MyApp.identity.msix') +
    ''' -ExternalLocation ''' + EscapePSLiteral(AppDir) + ''' -ErrorAction Stop } catch { Write-Error $_; exit 1 }"';
end;

完全で動作するsetup.issについては、スパース アプリのサンプルを参照してください。

次の WiX と NSIS の例では、-Fileを介して小さなregister-sparse.ps1を呼び出して、インストール パスがパラメーターとして渡されるようにします (PowerShell ではデータとしてバインドされます)、-Command文字列に補間されません。 これにより、作成されたインストール ディレクトリ (引用符や $(...)を含むフォルダー名など) によるスクリプトの挿入が回避されます。

# register-sparse.ps1 — ship this alongside your installer
param(
  [Parameter(Mandatory)] [string] $MsixPath,
  [Parameter(Mandatory)] [string] $ExternalLocation,
  [Parameter(Mandatory)] [string] $PackageName
)
$ErrorActionPreference = 'Stop'
try {
  # Add-AppxPackage emits NON-terminating errors by default, so a failure would otherwise leave
  # the process exit code at 0 and let the installer complete without identity. Try the add
  # directly first: a fresh install or a version-bumped upgrade registers/updates in place
  # without touching any existing registration. -ErrorAction Stop + the outer trap make a real
  # failure terminating so the installer (WiX Return="check" / NSIS) sees it.
  try {
    Add-AppxPackage -Path $MsixPath -ExternalLocation $ExternalLocation -ErrorAction Stop
  } catch {
    # Only ONE failure is safe to resolve by unregister+retry: the exact same version is already
    # registered (HRESULT 0x80073CFB, ERROR_PACKAGE_ALREADY_EXISTS — "already installed,
    # reinstallation blocked"), which Add-AppxPackage rejects. Re-throw everything else
    # (untrusted/corrupt .msix, unsupported OS, ...) so a bad new package can NEVER unregister a
    # working prior registration and strip the installed app of the identity it already had.
    if ($_.Exception.HResult -ne 0x80073CFB) { throw }
    Get-AppxPackage -Name $PackageName | Remove-AppxPackage -ErrorAction SilentlyContinue
    Add-AppxPackage -Path $MsixPath -ExternalLocation $ExternalLocation -ErrorAction Stop
  }
} catch {
  Write-Error $_
  exit 1
}

WiX (v3)

Add-AppxPackage)に登録してください。Impersonate="yes" は、それを実行するアカウントに対してパッケージを登録するためです。 Impersonate="no"を含む遅延アクションはLocalSystemとして実行され、インストールしているユーザーに ID は付与されません (一般的には拒否されます)。 コンピューターごとの MSI の場合は、偽装された登録を実行して、呼び出し元のユーザーに適用されるようにします。

遅延カスタム アクションでは、 INSTALLFOLDER を直接読み取ることはできません (遅延アクションは、プロパティにアクセスせずにコンテキストで実行されます)。また、アクション を宣言 するだけでは実行されません。 そのため、CustomActionData (遅延アクションのIdと等しいProperty名を持つ即時タイプ 51 アクション) を使用してパスをマーシャリングし、両方InstallFiles後にスケジュールします。

<!-- Immediate: stash the command line (with the resolved paths) into the deferred action's
     CustomActionData. Windows Installer copies the value of the property named the same as a
     deferred action into that action's CustomActionData. -->
<CustomAction Id="SetRegisterSparseCmd" Property="RegisterSparse" Execute="immediate"
  Value="powershell.exe -NoProfile -ExecutionPolicy Bypass -File &quot;[INSTALLFOLDER]register-sparse.ps1&quot; -MsixPath &quot;[INSTALLFOLDER]MyApp.identity.msix&quot; -ExternalLocation &quot;[INSTALLFOLDER]&quot; -PackageName &quot;MyPackageIdentityName&quot;" />

<!-- Deferred + impersonated: CAQuietExec reads its command line from CustomActionData when run
     deferred, so it registers the package for the invoking user. Return="check" fails the
     install if registration fails. -->
<CustomAction Id="RegisterSparse" BinaryKey="WixCA" DllEntry="CAQuietExec"
  Execute="deferred" Impersonate="yes" Return="check" />

<InstallExecuteSequence>
  <Custom Action="SetRegisterSparseCmd" After="InstallFiles">NOT Installed</Custom>
  <Custom Action="RegisterSparse" After="SetRegisterSparseCmd">NOT Installed</Custom>
</InstallExecuteSequence>

CAQuietExec は WiX ユーティリティ拡張機能 (WixUtilExtension) に付属しています。 WixCA バイナリを使用できるように参照します。

偽装された 1 つのアクションは、インストーラーを実行しているユーザーに対してのみ ID を登録します。 マシンごとのインストールのすべてのユーザーをプロビジョニングするには、代わりに初回起動時 (ユーザーごと) に登録するか、 Add-AppxProvisionedPackageなどのプロビジョニング メカニズムを使用します。

NSIS

Section
  # Capture the PowerShell exit code and abort if registration failed. register-sparse.ps1 exits
  # nonzero on failure (it sets $ErrorActionPreference='Stop' and traps), so without this check the
  # installer would complete even though the app has no identity.
  ExecWait 'powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$INSTDIR\register-sparse.ps1" -MsixPath "$INSTDIR\MyApp.identity.msix" -ExternalLocation "$INSTDIR" -PackageName "MyPackageIdentityName"' $0
  IntCmp $0 0 +2
    Abort "Registering the sparse identity package failed (exit code $0). The app requires package identity."
SectionEnd

Troubleshooting

Package.Current 実行時に /"パッケージ ID なし" をスローする

  • ID パッケージが登録されていないか、exe の fusion マニフェストに <msix> 要素がありません。 winapp embed-identityを再実行し (XML モードを使用する場合は再構築)、Add-AppxPackage -ExternalLocationに再登録します。
  • exe 内の <msix packageName> / publisher / applicationId は、登録済みパッケージの ID と 完全に 一致している必要があります。

アセット/ロゴが表示されない

  • Assets/ フォルダーが、マニフェストで想定されているのと同じ相対パスを持つ外部の場所に配置されていることを確認します。 アセットは、 .msixではなく、外部の場所から解決されます。

Add-AppxPackage 署名/信頼エラーで失敗する

  • .msixは、マシンで信頼され、そのサブジェクトがマニフェスト Publisherと一致する証明書によって署名されている必要があります。 ローカル テストの場合は、 winapp cert generateを使用して開発証明書を生成して信頼し、マニフェスト Publisher 一致していることを確認します。

MakeAppx: "RuntimeBehavior 値 'win32App' を持つアプリケーションで EntryPoint を宣言することはできません"

  • スパース win32App アプリケーションで EntryPointを宣言することはできません。 winapp init --sparseによって生成されたマニフェストは既に正しいです。マニフェストを手動で編集した場合は、EntryPoint属性を削除します。

"Input is a file but not a sparse manifest" (入力はファイルですが、スパース マニフェストではありません)

  • winapp pack <file> は、 <uap10:AllowExternalContent>true</uap10:AllowExternalContent>を宣言するマニフェストのみを受け入れます。 winapp init --exe <exe> --sparseで生成するか、入力フォルダーを渡して完全な MSIX を構築します。

こちらも参照ください