Řídké balíčkování: udělení identity nebalíčkované aplikaci

Kompletní funkční příklad (aplikace WPF (Windows Presentation Foundation) + instalační program Inno Setup) najdete v ukázce sparse-app.

Standardní spustitelný soubor desktopu vytvořený pomocí dotnet buildnástroje MSBuild, CMake nebo jiné sady nástrojů nemá žádnou identitu balíčku. Bez identity nemůže používat mnoho moderních rozhraní API pro Windows (informační oznámení, úlohy na pozadí, sdílení cílů, úloh po spuštění, rozhraní API pro data aplikací a další).

Řídké balíčkování přiděluje aplikaci identitu bez přesunu jejích binárních souborů do MSIX. Dodáte malý balíček pouze s identitou (jen manifest) a zaregistrujete ho vedle běžně nainstalované aplikace prostřednictvím .msix. Váš .exe zůstane přesně tam, kam ho umístí instalační program. Jedná se o produkční protějšek winapp create-debug-identity, který je určen pouze pro ladění při vývoji.

Tato příručka popisuje tři kroky v CLI, které odpovídají prvním třem krokům oficiálního postupu Přiřazení identity nebaleným aplikacím:

Krok Command Výsledek
1. Vytvoření manifestu identity winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Sestavení a podepsání balíčku identity winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Vložení identity do aplikace winapp embed-identity <exe> <msix> element v manifestu technologie Fusion souboru EXE

Kroky 4–5 dokumentace (registrace nebo zrušení registrace balíčku) jsou zodpovědností vašeho instalačního programu – viz integrace instalačního programu.

Kdy použít řídké balení

  • Už máte zavedený instalační program (Inno Setup, WiX, NSIS, MSI) a nechcete kvůli distribuci přejít na MSIX, ale potřebujete rozhraní Windows API, která vyžadují identitu aplikace.
  • Vaši aplikaci je nutné nainstalovat do umístění nebo s uspořádáním, které MSIX nepovoluje.
  • Potřebujete minimální doplňkovou změnu: ponechte stávající tok instalace a přidejte jeden .msix krok registrace.

Pokud začínáte od začátku a můžete distribuovat ve formátu MSIX, je plně zabalená aplikace (winapp init + winapp pack <folder>) jednodušší.

Předpoklady

  1. Windows 10, verze 2004 (build 19041) nebo novější. Řídké balíčky využívají uap10:AllowExternalContent, což vyžaduje verzi 19041+.
  2. winapp CLI – instalace prostřednictvím wingetu (nebo aktualizace, pokud už je nainstalovaná):
    winget install Microsoft.WinApp --source winget
    
  3. Podpisový certifikát kódu důvěryhodný na cílovém počítači. Pro místní testování vygenerujte pomocí winapp cert generate vývojový certifikát a nastavte ho jako důvěryhodný. Produkční balíčky musí být podepsány certifikátem, jehož předmět odpovídá manifestu Publisher.

Walkthrough

Následující příklady předpokládají sestavený spustitelný soubor na adrese ./bin/Release/net8.0-windows/MyApp.exe.

Krok 1 – Vytvořte manifest rozptýlené identity

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

Tím odvodíte název balíčku, vydavatele, verzi a popis z exe (prostřednictvím informací o verzi souboru) a zobrazí se výzva k jejich přijetí nebo přepsání. Přidejte --use-defaults (nebo --no-prompt) k přeskočení dotazů v CI a --name / --publisher k přepsání konkrétních hodnot:

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

Ve výchozím nastavení zapisuje následující obsah do vyhrazené složky sparse/ v aktuálním adresáři (lze přepsat pomocí --output-dir):

  • appxmanifest.xml — strohý manifest s <uap10:AllowExternalContent>true</uap10:AllowExternalContent> (prvkem pod <Properties>), ProcessorArchitecture="neutral", aplikací win32App a názvem exe vyplněným do Executable.
  • Assets/ — zástupné vizuální prvky (pokud je to možné, extrahované z ikony souboru EXE).

sparse/ Proč složka a ne vedle exe? Manifest a Assets/ jsou vstupy pro dobu sestavení, které využívají winapp pack a winapp embed-identity — nikdo je za běhu nenačítá vedle souboru exe (identita za běhu pochází z prvku <msix>, který je vložený do souboru exe, spolu s externím umístěním registrovaného balíčku, a manifest na soubor exe odkazuje podle názvu, takže jeho umístění je nezávislé na tom, kde se soubor exe nachází). Uložení do vyhrazené složky pod správou zdrojového kódu je udrží mimo výstupní adresář sestavení (například bin/), který by vyčištění nebo opětovné sestavení smazalo, a zároveň ve složce nezůstanou žádné binární soubory, takže i další kroky zůstanou čisté. winapp pack a winapp embed-identity automaticky hledají v sparse/, takže cestu jen zřídka musíte zadávat.

Poznámka: Zjednodušený proces inicializace záměrně vynechává veškerou instalaci SDK a balíčků — balíčky pouze pro identitu nemají žádné závislosti na SDK.

Pokud v cílovém adresáři již existuje appxmanifest.xml, příkaz init se zastaví, místo aby jej přepsal (a jeho Assets/). Spusťte znovu s --force, aby se znovu vygeneroval.

Ujistěte se, že Publisher vygenerovaný manifest odpovídá certifikátu, kterým se budete podepisovat. V případě potřeby upravte appxmanifest.xml nebo předejte --publisher při generování.

Krok 2 – Sestavení a podepsání balíčku identity

Nasměrujte winapp pack ukazatel na řídký manifest (soubor, nikoli složka):

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

Vzhledem k tomu, že manifest deklaruje AllowExternalContent, winapp pack sestaví balíček pouze s identitou.msix, který obsahuje jen manifest — žádné binární soubory ani prostředky. Výstup se ve výchozím nastavení ukládá do <PackageName>.identity.msix v aktuálním adresáři; ke změně použijte --output. K podepsání dojde pouze tehdy, když předáte --cert (nebo --generate-cert).

Krok 3 – Vložení identity do aplikace

Vložte prvek <msix> tak, aby Windows propojil spuštěný soubor EXE s balíčkem identity:

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

Nebo zachovejte side-by-side manifest jako soubor zařazený do správy verzí a znovu sestavte:

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

V režimu <msix> XML se element vloží do cílového manifestu (nebo ho nahradí). Přidejte na tento manifest odkaz ze svého projektu (pro .NET nastavte <ApplicationManifest>app.manifest</ApplicationManifest>) a znovu projekt sestavte, aby byl prvek vložen do souboru exe.

Oba režimy načítají identitu z řídkého appxmanifest.xml. Když vynecháte --manifest, winapp vypadá ve sparse/ složce (kde winapp init --exe --sparse ji ve výchozím nastavení zapisuje) vedle cíle, pak v aktuálním adresáři, pak se vrátí k cíli a aktuálnímu adresáři; předejte --manifest do jiného místa.

Poznámka: Režim EXE přepíše binární soubor pomocí mt.exe, čímž zneplatní veškeré stávající podpisy Authenticode. Před distribucí znovu podepište exe (např. winapp sign ./MyApp.exe <cert.pfx>).

Krok 4 – Registrace (pro místní testování)

Loga v manifestu se za běhu načítají z externího umístění, nikoli z pouze identitního .msix. Krok 1 je vytvořil do ./sparse/Assets, takže je před registrací zkopírujte vedle svého souboru exe (do externího umístění) — jinak Windows zaregistruje rozložení, ve kterém chybí všechna loga, na která manifest odkazuje:

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

Pak zaregistrujte balíček identity pro danou složku ( externí umístění):

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

Spusťte aplikaci a ověřte, že je identita přítomna — například Windows.ApplicationModel.Package.Current.Id.FamilyName by měl vrátit název rodiny balíčků místo vyvolání výjimky.

Vyčištění:

Remove-AppxPackage <full-package-name>

Zpracování prostředků

Řídký .msix obsahuje pouze identitu. Vizuální prostředky, na které odkazuje manifest (Assets\StoreLogo.png, dlaždice atd.), se za běhu načítají z umístění externího obsahu, tj. z instalačního adresáře vaší aplikace, a ne zevnitř .msix.

To znamená, že složku musíte nasadit Assets/ společně s vaší aplikací (stejné rozložení, které manifest očekává vzhledem k externímu umístění).

Krok 2 zabalí přímo soubor manifestu (winapp pack ./sparse/appxmanifest.xml), čímž vytvoří balíček pouze s identitou .msix jen z tohoto manifestu — ostatní soubory ve stejné složce se ignorují, takže nikdy nezahrne vaše prostředky aplikace ani binární soubory. (Pokud místo toho nasměrujete winapp pack na složku, jejíž manifest deklaruje AllowExternalContent, upozorní na všechny prostředky nebo binární soubory, které v ní najde, protože ty u řídkého balíčku patří do externího umístění, nikoli do .msix.)

Integrace instalačního programu

Registrace a zrušení registrace jsou úloha instalačního programu. Vzor je stejný u instalačních nástrojů:

  • Instalace: zkopírujte binární soubory aplikace, Assets/ složku a .msix do instalačního adresáře a pak spusťte Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>".
  • Odinstalace: SpusťteRemove-AppxPackage <full-package-name> před odstraněním souborů.

Zabezpečení: Cesta k instalačnímu adresáři se určuje během instalace a může obsahovat znaky (např. apostrof), které naruší řetězcový literál v PowerShellu. Před vložením cesty do řetězce -Command ji vždy escapujte nebo ověřte — níže uvedené ukázky pro WiX a NSIS předpokládají důvěryhodnou instalační cestu, zatímco příklad pro Inno Setup ukazuje bezpečné escapování. Upřednostňujte předávání cest jako argumentů do skriptu -File před inline interpolací -Command.

Inno Setup

Sestavte PowerShellové argumenty ve funkci [Code], aby byla instalační cesta modulu runtime správně escapována pro literál PowerShellu uzavřený v jednoduchých uvozovkách (instalační adresář obsahující ' nesmí umožnit vložení skriptu):

[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;

Ukázku sparse-app s kompletním, funkčním setup.iss najdete zde.

Níže uvedené příklady WiX a NSIS volají malý register-sparse.ps1 prostřednictvím -File, aby se instalační cesta předávala jako parametr (PowerShell jej naváže jako data), namísto interpolace do řetězce -Command. Tím se zabrání injektáži skriptu prostřednictvím vytvořeného instalačního adresáře (např. název složky obsahující uvozovku nebo $(...)):

# 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)

Zaregistrujte pro každého uživatele (Impersonate="yes"), protože Add-AppxPackage zaregistruje balíček pro účet, který ho spouští. Odložená akce s Impersonate="no" běží jako LocalSystem, což neuděluje identitu uživateli provádějícímu instalaci (a je obvykle odmítnuta). V případě MSI pro jednotlivé počítače spusťte zosobněnou registraci, aby se použila pro vyvolání uživatele.

Odložená vlastní akce nemůže číst INSTALLFOLDER přímo (odložené akce se spouštějí v kontextu bez přístupu k vlastnostem) a pouhé deklarování akce ji nespustí. Proto veďte cesty přes CustomActionData – okamžitou akci typu 51, jejíž Property název se rovná názvu odložené akce Id – a naplánujte obě až po 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 je součástí rozšíření WiX Util (WixUtilExtension); přidejte na něj odkaz, aby byl binární soubor WixCA k dispozici.

Jedna zosobněná akce registruje identitu pouze pro uživatele, který spouští instalační program. Chcete-li zřídit instalaci pro celý počítač pro každého uživatele, zaregistrujte ji místo toho při prvním spuštění (pro jednotlivého uživatele) nebo použijte mechanismus pro zřízení, například 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

Řešení problémů

Package.Current vyvolá za běhu chybu „chybí identita balíčku“

  • Balíček identity není zaregistrován nebo v manifestu Fusion souboru EXE chybí prvek <msix>. Znovu spusťte winapp embed-identity (a znovu sestavte, pokud používáte režim XML), pak znovu zaregistrujte Add-AppxPackage -ExternalLocation.
  • <msix packageName> / / publisher applicationId v souboru exe se musí přesně shodovat s identitou registrovaného balíčku.

Prostředky nebo loga se nezobrazují

  • Ujistěte se, že složka Assets/ je nasazena do externího umístění se stejnými relativními cestami, které manifest předpokládá. Prostředky se načítají z externího umístění, nikoli z .msix.

Add-AppxPackage selže kvůli chybě podpisu nebo důvěryhodnosti

  • Musí .msix být podepsán certifikátem, který je na počítači důvěryhodný a jehož předmět odpovídá manifestu Publisher. Pro místní testování vygenerujte vývojový certifikát a označte ho jako důvěryhodný pomocí winapp cert generate a ujistěte se, že manifest Publisher mu odpovídá.

MakeAppx: "Aplikace s hodnotou RuntimeBehavior win32App" nesmí deklarovat EntryPoint.

  • Řídká win32App aplikace nesmí deklarovat EntryPoint. Manifesty vygenerované winapp init --sparse pomocí jsou již správné. Pokud jste manifest upravili ručně, odeberte všechny EntryPoint atributy.

Vstup je soubor, ale není to řídký manifest.

  • winapp pack <file> přijímá pouze manifest, který deklaruje <uap10:AllowExternalContent>true</uap10:AllowExternalContent>. Vygenerujte jej pomocí winapp init --exe <exe> --sparse, nebo předejte vstupní složku pro sestavení úplného balíčku MSIX.

Viz také