Glespaketering: ge en opaketerad app en identitet

För ett komplett fungerande exempel (WPF-app + Inno Setup-installationsprogram), se exemplet sparse-app.

Ett vanligt körbart skrivbordsprogram – byggt med dotnet build, MSBuild, CMake eller någon annan verktygskedja – har ingen paketidentitet. Utan identitet kan den inte använda många moderna Windows API:er (popup-meddelanden, bakgrundsaktiviteter, delningsmål, startuppgifter, appdata-API:er med mera).

Sparse-paketering beviljar identitet till en app utan att flytta sina binärfiler till en MSIX. Du levererar ett litet paket med endast identitet.msix (bara ett manifest) och registrerar det tillsammans med din vanligt installerade app via en extern plats. Din .exe förblir precis där din installatör placerar den. Det här är produktionsversionen av winapp create-debug-identity, som endast är avsedd för felsökning under utveckling.

Den här guiden omfattar de tre CLI-steg som motsvarar de tre första stegen i det officiella arbetsflödet Bevilja identitet till opaketerade appar:

Steg Command Result
1. Skapa identitetsmanifestet winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Skapa och signera identitetspaketet winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Bädda in identitet i appen winapp embed-identity <exe> <msix>-element i EXE-filens fusion-manifest

Steg 4–5 i dokumenten (registrera/avregistrera paketet) är installationsprogrammets ansvar – se Installationsintegrering.

När du ska använda gles förpackning

  • Du har redan ett etablerat installationsprogram (Inno Setup, WiX, NSIS, MSI) och vill inte gå över till MSIX för distribution, men du behöver Windows-API:er som kräver identitet.
  • Appen måste installeras på en sökväg eller med en layout som MSIX inte tillåter.
  • Du vill ha en minimal, additiv ändring: behåll ditt befintliga installationsflöde och lägg till ett .msix registreringssteg.

Om du börjar om från början och kan distribuera som MSIX är en fullständig paketerad app (winapp init + winapp pack <folder>) enklare.

Förutsättningar

  1. Windows 10 version 2004 (version 19041) eller senare. Glesa paket förlitar sig på uap10:AllowExternalContent, vilket kräver 19041+.
  2. winapp CLI – installera via winget (eller uppdatera om det redan är installerat):
    winget install Microsoft.WinApp --source winget
    
  3. Ett certifikat för kodsignering som är betrott på måldatorn. För lokal testning genererar du ett utvecklingscertifikat med winapp cert generate och litar på det. Produktionspaket måste signeras med ett certifikat vars ämne matchar manifestet Publisher.

Walkthrough

Exemplen nedan förutsätter ett kompilerat körbart program på ./bin/Release/net8.0-windows/MyApp.exe.

Steg 1 – Skapa det glesa identitetsmanifestet

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

Detta härleder paketnamnet, utgivaren, versionen och beskrivningen från exe (via dess filversionsinformation) och uppmanar dig att acceptera eller åsidosätta dem. Lägg till --use-defaults (eller --no-prompt) för att hoppa över prompterna i CI och --name / --publisher för att åsidosätta specifika värden:

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

Den skriver följande till en dedikerad sparse/ mapp i den aktuella katalogen som standard (åsidosättning med --output-dir):

  • appxmanifest.xml — ett glest manifest med <uap10:AllowExternalContent>true</uap10:AllowExternalContent> (ett element under <Properties>), ProcessorArchitecture="neutral", ett win32App program och exe-namnet ifyllt i Executable.
  • Assets/ — platshållargrafik (extraheras från programmets ikon i EXE-filen när det är möjligt).

Varför en sparse/ mapp och inte bredvid exe? Manifestet och Assets/ är indata vid byggtid som förbrukas av winapp pack och winapp embed-identity – ingenting läser dem bredvid .exe-filen vid körning (identiteten vid körning kommer från elementet <msix> som är inbäddat i .exe-filen, plus det registrerade paketets externa plats, och manifestet refererar till .exe-filen med namn, så dess plats är oberoende av var .exe-filen ligger). Att skriva dem i en särskild mapp som versionshanteras håller dem borta från en katalog med byggutdata (som bin/) som skulle rensas vid en clean/rebuild, och håller mappen fri från binärfiler så att nästa steg hålls rena. winapp pack och winapp embed-identity letar automatiskt i sparse/, så du behöver sällan ange sökvägen.

Obs! Det glesa init-flödet hoppar avsiktligt över alla SDK/paketinstallationer – endast identitetspaket har inga SDK-beroenden.

Om det redan finns en appxmanifest.xml i målkatalogen avbryts init i stället för att skriva över den (och dess Assets/). Kör igen med --force för att återskapa den.

Kontrollera att Publisher i det genererade manifestet matchar certifikatet som du loggar in med. Redigera appxmanifest.xml om det behövs eller skicka --publisher vid generering.

Steg 2 – Skapa och signera identitetspaketet

Peka winapp pack på det glesa manifestet (en fil, inte en mapp):

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

Eftersom manifestet deklarerar AllowExternalContent, bygger winapp pack en identitet som enbart består av.msix endast manifestet – inga binärfiler, inga resurser. Utdata är som standard <PackageName>.identity.msix i den aktuella katalogen. Använd --output för att ändra den. Signering sker endast när du skickar --cert (eller --generate-cert).

Steg 3 — Integrera identitet i din app

Bädda in elementet <msix> så att Windows ansluter den exe som körs till identitetspaketet:

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

Eller behåll side-by-side-manifestet som en incheckad fil och bygg om:

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

I XML-läge infogas elementet <msix> i (eller ersätts i) målmanifestet. Referera till manifestet från projektet (för .NET anger du <ApplicationManifest>app.manifest</ApplicationManifest>) och återskapar så att elementet bäddas in i exe.

Båda lägena läser in identiteten från en gles struktur appxmanifest.xml. När du utelämnar --manifest, letar winapp i en sparse/ mapp (där winapp init --exe --sparse skriver den som standard) bredvid målet först, sedan i den aktuella katalogen och faller sedan tillbaka till bredvid målet och den aktuella katalogen; skicka --manifest till punkt någon annanstans.

Obs! EXE-läget skriver om binärfilen med mt.exe, som ogiltigförklarar alla befintliga Authenticode-signaturer. Signera om exe (t.ex. winapp sign ./MyApp.exe <cert.pfx>) innan du distribuerar det.

Steg 4 – Registrera (för lokal testning)

Manifestets logotyper hämtas från den externa platsen vid körning, inte från den endast identitetsbaserade .msix. Steg 1 placerade dem i ./sparse/Assets, så kopiera dem bredvid din .exe-fil (den externa platsen) innan du registrerar — annars registrerar Windows en layout som saknar alla logotyper som manifestet refererar till:

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

Registrera sedan identitetspaketet för den mappen (den externa platsen):

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

Starta appen och bekräfta att identiteten finns , till exempel Windows.ApplicationModel.Package.Current.Id.FamilyName bör du returnera ditt paketfamiljenamn i stället för att kasta.

Så här rensar du:

Remove-AppxPackage <full-package-name>

Tillgångshantering

Den glesa .msix är enbart identitet. De visuella resurser som manifestet refererar till (Assets\StoreLogo.png, paneler osv.) hämtas från den externa innehållsplatsen vid körning – det vill säga från appens installationskatalog – inte från .msix.

Det innebär att du måste distribuera Assets/ mappen tillsammans med ditt program (samma layout som manifestet förväntar sig i förhållande till den externa platsen).

Steg 2 packar manifestfilen direkt (winapp pack ./sparse/appxmanifest.xml), som endast bygger identiteten .msix från just det manifestet – syskonfiler ignoreras, så den innehåller aldrig dina tillgångar eller binärfiler. (Om du i stället pekar winapp pack på en mapp vars manifest deklarerar AllowExternalContentvarnar den om alla tillgångar eller binärfiler som hittas, eftersom de för ett glesa paket hör hemma på den externa platsen, inte inuti .msix.)

Integrering av installationsprogram

Registrering och avregistrering är installationsprogrammets jobb. Mönstret är detsamma för installationsverktyg:

  • Installera: kopiera dina app binärfiler, Assets/ mappen och .msix till installationskatalogen och kör Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>"sedan .
  • Avinstallera: kör Remove-AppxPackage <full-package-name> innan du tar bort filer.

Säkerhet: Installationskatalogen löses vid installationen och kan innehålla tecken (t.ex. ett enda citattecken) som bryter ut ur en PowerShell-strängliteral. Undvik eller verifiera alltid sökvägen innan du interpolerar den i en -Command sträng – WiX- och NSIS-kodfragmenten nedan förutsätter en betrodd installationssökväg, medan Inno-installationsexemplet visar säker flykt. Föredra att skicka sökvägar som argument till ett -File-skript framför inline--Command-interpolering.

Inno Setup

Skapa PowerShell-argumenten i en [Code] funktion så att körningsinstallationssökvägen är undantagen för den enkla PowerShell-literalen (en installationskatalog som innehåller ett ' får inte kunna mata in skript):

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

Se exemplet sparse-app för ett fullständigt, fungerande setup.iss.

WiX- och NSIS-exemplen nedan anropar en liten register-sparse.ps1 via -File så att installationssökvägen skickas som en parameter (PowerShell binder den som data) i stället för att interpoleras till en -Command sträng. Detta undviker skriptinmatning via en skapad installationskatalog (t.ex. ett mappnamn som innehåller ett citattecken eller $(...)):

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

Registrera per användare (Impersonate="yes"), eftersom Add-AppxPackage registrerar paketet för det konto som kör det. En uppskjuten åtgärd med Impersonate="no" körs som LocalSystem, vilket inte ger identitet åt den användare som installerar (och ofta avvisas). För en MSI per dator kör du registreringen som personifierad så att den gäller för den anropande användaren.

En uppskjuten anpassad åtgärd kan inte läsa INSTALLFOLDER direkt (uppskjutna åtgärder körs i en kontext utan åtkomst till egenskaper), och att bara deklarera åtgärden innebär inte att den körs. Samla alltså in sökvägarna via CustomActionData – en omedelbar åtgärd av typ 51 vars Property-namn är detsamma som den uppskjutna åtgärdens Id – och schemalägg båda efter 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 ingår i WiX util-tillägget (WixUtilExtension); referera till det så att binärfilen WixCA är tillgänglig.

En enskild personifierad åtgärd registrerar endast identitet för den användare som kör installationsprogrammet. Om du vill etablera varje användare av en installation per dator registrerar du dig vid den första starten (per användare) i stället eller använder en etableringsmekanism som 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

Felsökning

Package.Current ger "no package identity" vid körning

  • Identitetspaketet är inte registrerat eller exe:ets fusionsmanifest saknar elementet <msix> . winapp embed-identity Kör igen (och återskapa om du använder XML-läge) och registrera sedan igen med Add-AppxPackage -ExternalLocation.
  • <msix packageName> / / publisher applicationId i exe-filen måste exakt stämma överens med det registrerade paketets identitet.

Resurser/logotyper visas inte

  • Se till att mappen Assets/ distribueras till den externa platsen med samma relativa sökvägar som manifestet förväntar sig. Tillgångar hämtas från den externa platsen, inte från .msix.

Add-AppxPackage misslyckas med ett signerings-/förtroendefel

  • .msix måste signeras av ett certifikat som datorn litar på och vars subjectnamn matchar manifestets Publisher. För lokal testning genererar och litar du på ett utvecklingscertifikat med winapp cert generateoch kontrollerar att manifestet Publisher matchar det.

MakeAppx: "Program med RuntimeBehavior-värdet 'win32App' får inte deklarera EntryPoint"

  • En sparsam win32App-applikation får inte deklarera EntryPoint. Manifest som genereras av winapp init --sparse är redan korrekta. Ta bort alla EntryPoint attribut om du redigerade manifestet för hand.

"Indata är en fil men inte ett glest manifest"

  • winapp pack <file> accepterar endast ett manifest som deklarerar <uap10:AllowExternalContent>true</uap10:AllowExternalContent>. Generera en med winapp init --exe <exe> --sparseeller skicka en indatamapp för att skapa en fullständig MSIX.

Se även