Geringe Verpackung: Identität für eine entpackte App gewähren

Ein funktionierendes End-to-End-Beispiel (WPF App + Inno Setup-Installationsprogramm) finden Sie im Beispiel "sparse-app".

Eine standardmäßige ausführbare Desktopdatei , die mit dotnet buildMSBuild, CMake oder einer anderen Toolkette erstellt wurde, weist keine Paketidentität auf. Ohne Identität kann es nicht viele moderne Windows-APIs (Popupbenachrichtigungen, Hintergrundaufgaben, Freigabeziele, Startaufgaben, App-Daten-APIs und mehr) verwenden.

Sparse-Paketierung verleiht einer App eine Identität, ohne ihre Binärdateien in ein MSIX-Paket zu verschieben. Sie liefern nur eine winzige reine Identität.msix (nur ein Manifest), und registrieren sie zusammen mit Ihrer normal installierten App an einem externen Speicherort. Ihr .exe Bleibt genau dort, wo ihr Installationsprogramm es platziert. Dies ist das Gegenstück für die Produktionsumgebung zu winapp create-debug-identity, das nur zum Debugging während der Entwicklung dient.

In diesem Leitfaden werden die drei CLI-Schritte behandelt, die den ersten drei Schritten des offiziellen Genehmigungsidentitätsworkflows für nicht verpackte Apps zugeordnet sind:

Schritt Befehl Result
1. Erstellen des Identitätsmanifests winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Erstellen und Signieren des Identitätspakets winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Einbetten der Identität in die App winapp embed-identity <exe> <msix> Element im Fusionsmanifest der Exe

Die Schritte 4 bis 5 der Dokumente (Registrieren/Aufheben der Registrierung des Pakets) sind die Verantwortung Ihres Installers – siehe Installationsprogrammintegration.

Gründe für die Verwendung von sparsamen Verpackungen

  • Sie verfügen bereits über ein ausgereiftes Installationsprogramm (Inno Setup, WiX, NSIS, MSI) und möchten nicht zur Verteilung zu MSIX wechseln, sie benötigen jedoch identitätsgesteuerte Windows APIs.
  • Ihre App muss auf einem Pfad oder mit einem Layout installieren, das MSIX nicht zulässt.
  • Sie möchten eine minimale, additive Änderung: Halten Sie ihren vorhandenen Installationsablauf, und fügen Sie einen .msix Registrierungsschritt hinzu.

Wenn Sie neu beginnen und als MSIX verteilen können, ist eine vollständige verpackte App (winapp init + winapp pack <folder>) einfacher.

Voraussetzungen

  1. Windows 10, Version 2004 (Build 19041) oder höher. Sparsepakete basieren auf uap10:AllowExternalContent, wofür 19041+ erforderlich ist.
  2. winapp CLI — über winget installieren (oder aktualisieren, falls bereits installiert):
    winget install Microsoft.WinApp --source winget
    
  3. Ein auf dem Zielcomputer vertrauenswürdiges Codesignaturzertifikat. Generieren Sie für lokale Tests mit winapp cert generate ein Entwicklungszertifikat und vertrauen Sie ihm. Produktionspakete müssen mit einem Zertifikat signiert werden, dessen Betreff dem Manifest Publisherentspricht.

Walkthrough

In den folgenden Beispielen wird davon ausgegangen, dass unter ./bin/Release/net8.0-windows/MyApp.exe eine kompilierte ausführbare Datei vorliegt.

Schritt 1 — Erstellen Sie das spärliche Identitätsmanifest

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

Dadurch wird der Paketname, der Herausgeber, die Version und die Beschreibung aus der Exe (über die Dateiversionsinformationen) abgeleitet und Sie aufgefordert, sie zu akzeptieren oder zu überschreiben. Fügen Sie --use-defaults (oder --no-prompt) hinzu, um die Eingabeaufforderungen in CI zu überspringen und --name / --publisher bestimmte Werte außer Kraft zu setzen:

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

Standardmäßig schreibt es Folgendes in einen eigenen sparse/-Ordner im aktuellen Verzeichnis (kann mit --output-dir überschrieben werden):

  • appxmanifest.xml — ein minimales Manifest mit <uap10:AllowExternalContent>true</uap10:AllowExternalContent> (einem Element unter <Properties>), ProcessorArchitecture="neutral", einer win32App-Anwendung und dem in Executable eingetragenen EXE-Namen.
  • Assets/ – visuelle Platzhalterressourcen (die nach Möglichkeit aus dem Symbol der EXE-Datei extrahiert werden).

Warum ein sparse/ Ordner und nicht neben der exe? Das Manifest und Assets/ sind Eingaben zur Buildzeit, die von winapp pack und winapp embed-identity verwendet werden — nichts liest sie zur Laufzeit neben der EXE ein (die Laufzeitidentität ergibt sich aus dem in die EXE eingebetteten <msix>-Element sowie dem externen Speicherort des registrierten Pakets, und das Manifest verweist anhand des Namens auf die EXE, sodass sein Speicherort unabhängig davon ist, wo sich die EXE befindet). Wenn Sie sie in einen eigenen, versionsverwalteten Ordner schreiben, bleiben sie außerhalb eines Build-Ausgabeverzeichnisses (wie bin/), das bei einem Clean/Rebuild gelöscht würde, und der Ordner bleibt frei von Binärdateien, sodass die nächsten Schritte sauber bleiben. winapp pack und winapp embed-identity durchsuchen sparse/ automatisch, sodass Sie den Pfad nur selten angeben müssen.

Hinweis: Der spärliche Init-Fluss überspringt absichtlich alle SDK-/Paketinstallationen – Identitätspakete weisen keine SDK-Abhängigkeiten auf.

Wenn im Zielverzeichnis bereits eine appxmanifest.xml vorhanden ist, wird die Initialisierung angehalten, anstatt sie (und ihre Assets/) zu überschreiben. Führen Sie den Befehl mit --force erneut aus, um es neu zu generieren.

Stellen Sie sicher, dass das im generierten Manifest enthaltene Publisher mit dem Zertifikat übereinstimmt, das Sie zum Signieren verwenden. Bearbeiten Sie --publisher bei Bedarf oder übergeben Sie appxmanifest.xml beim Generieren.

Schritt 2 – Erstellen und Signieren des Identitätspakets

Verweisen Sie winapp pack auf das Sparse-Manifest (eine Datei, kein Ordner):

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

Da das Manifest AllowExternalContentdeklariert, erstellt ein .msixwinapp pack-Paket, das nur das Manifest enthält – keine Binärdateien, keine Ressourcen. Die Ausgabe wird standardmäßig in <PackageName>.identity.msix im aktuellen Verzeichnis gespeichert; verwenden Sie --output, um dies zu ändern. Die Signierung erfolgt nur, wenn Sie --cert (oder --generate-cert) übergeben.

Schritt 3 – Einbetten der Identität in Ihre App

Betten Sie das <msix> Element ein, damit Windows die ausgeführte exe mit dem Identitätspaket verbindet:

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

Oder behalten Sie die parallele Manifestdatei als eingecheckte Datei bei, und erstellen Sie sie neu:

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

Im XML-Modus wird das <msix> Element in das Zielmanifest eingefügt (oder ersetzt). Verweisen Sie auf dieses Manifest aus Ihrem Projekt (für .NET, festlegen<ApplicationManifest>app.manifest</ApplicationManifest>), und erstellen Sie es neu, damit das Element in die exe eingebettet ist.

Beide Modi lesen die Identität aus einer Sparse-appxmanifest.xml. Wenn Sie --manifest weglassen, sucht winapp zuerst in einem sparse/-Ordner neben dem Ziel (in den winapp init --exe --sparse sie standardmäßig schreibt), dann im aktuellen Verzeichnis und greift schließlich auf den Pfad neben dem Ziel und das aktuelle Verzeichnis zurück. Übergeben Sie --manifest, um auf einen anderen Speicherort zu verweisen.

Hinweis: Im EXE-Modus wird die Binärdatei mit mt.exe neu geschrieben, wodurch jede vorhandene Authenticode-Signatur ungültig wird. Signieren Sie die exe (z.B. winapp sign ./MyApp.exe <cert.pfx>) vor der Verteilung erneut.

Schritt 4 – Registrieren (für lokale Tests)

Die Logos des Manifests werden zur Laufzeit über den externen Speicherort aufgelöst, nicht über die reine Identitäts-.msix. Schritt 1 hat sie unter ./sparse/Assets abgelegt, also kopieren Sie sie vor der Registrierung neben Ihre EXE-Datei (also an den externen Speicherort) — andernfalls registriert Windows ein Layout, dem alle Logos fehlen, auf die im Manifest verwiesen wird:

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

Registrieren Sie dann das Identitätspaket für diesen Ordner (den externen Speicherort):

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

Starten Sie die App, und vergewissern Sie sich, dass eine Identität vorhanden ist, zum Beispiel sollte Windows.ApplicationModel.Package.Current.Id.FamilyName Ihren Paketfamiliennamen zurückgeben, anstatt einen Fehler auszulösen.

Zum Bereinigen:

Remove-AppxPackage <full-package-name>

Asset-Verwaltung

Die Sparse .msix ist nur identitätsbasiert. Die im Manifest referenzierten visuellen Ressourcen (Assets\StoreLogo.png, Kacheln usw.) werden zur Laufzeit aus dem externen Inhaltsspeicherort aufgelöst — d. h. aus dem Installationsverzeichnis Ihrer App — nicht aus dem Inneren des .msix.

Dies bedeutet, dass Sie den Assets/ Ordner zusammen mit Ihrer Anwendung bereitstellen müssen (dasselbe Layout, das das Manifest erwartet, relativ zum externen Speicherort).

Schritt 2 verpackt die Datei mit dem Manifest direkt (winapp pack ./sparse/appxmanifest.xml), wodurch die rein identitätsbasierte .msix nur aus diesem Manifest erstellt wird – Dateien im selben Verzeichnis werden ignoriert, sodass es Ihre Assets oder Binärdateien nie enthält. (Wenn Sie stattdessen mit auf einen winapp pack verweisen, dessen Manifest AllowExternalContent deklariert, warnt es vor allen Ressourcen oder Binärdateien, die es findet, da diese bei einem Sparsepaket an den externen Speicherort gehören und nicht in das .msix.)

Installer-Integration

Registrierung und Deregistrierung sind Aufgabe des Installationsprogramms. Das Muster ist für alle Installationstools identisch:

  • Installieren: Kopieren Sie die App-Binärdateien, den Assets/-Ordner und .msix in das Installationsverzeichnis und führen Sie dann Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>" aus.
  • Deinstallieren: Vor dem Löschen von Dateien ausführen Remove-AppxPackage <full-package-name> .

Sicherheit: Das Installationsverzeichnis wird zur Installationszeit aufgelöst und kann Zeichen (z. B. ein einzelnes Anführungszeichen) enthalten, die ein PowerShell-Zeichenfolgenliteral verlassen. Escapen oder validieren Sie den Pfad stets, bevor Sie ihn in eine -Command-Zeichenfolge interpolieren. Die folgenden WiX- und NSIS-Codeausschnitte gehen von einem vertrauenswürdigen Installationspfad aus, während das „Inno Setup“-Beispiel sicheres Escaping demonstriert. Übergeben Sie Pfade vorzugsweise als Argumente an ein -File-Skript, anstatt die Inline--Command-Interpolation zu verwenden.

Inno Setup

Erstellen Sie die PowerShell-Argumente in einer [Code]-Funktion, sodass der Installationspfad der Runtime für das in einfache Anführungszeichen gesetzte PowerShell-Literal per Escape maskiert wird (ein Installationsverzeichnis, das ein ' enthält, darf kein Skript einfügen können):

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

Sehen Sie sich das Beispiel für sparse-app an, um ein vollständiges, funktionierendes Beispiel zu finden setup.iss.

Die folgenden WiX- und NSIS-Beispiele rufen über -File ein kleines register-sparse.ps1 auf, sodass der Installationspfad als Parameter übergeben wird (PowerShell bindet ihn als Daten), anstatt in einen -Command-String interpoliert zu werden. Dadurch wird die Skripteinfügung über ein gestaltetes Installationsverzeichnis vermieden (z. B. ein Ordnername mit einem Anführungszeichen oder $(...)):

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

Registrieren Sie pro Benutzer (Impersonate="yes"), da Add-AppxPackage das Paket für das Konto registriert, unter dem es ausgeführt wird. Eine verzögerte Aktion mit Impersonate="no" wird als LocalSystem ausgeführt, was dem installierenden Benutzer keine Identität verleiht (und häufig abgelehnt wird). Führen Sie die Registrierung für eine computerbezogene MSI-Datei mit Identitätswechsel aus, damit sie für den aufrufenden Benutzer gilt.

Eine verzögerte benutzerdefinierte Aktion kann nicht direkt gelesen INSTALLFOLDER werden (verzögerte Aktionen werden in einem Kontext ohne Zugriff auf Eigenschaften ausgeführt), und durch einfaches Deklarieren der Aktion wird sie nicht ausgeführt. Marshallen Sie also die Pfade über CustomActionData – eine sofortige Aktion vom Typ 51, deren Property-Name der Id der verzögerten Aktion entspricht –, und planen Sie beide nach InstallFiles ein:

<!-- 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 ist in der WiX-Util-Erweiterung (WixUtilExtension) enthalten; verweisen Sie auf sie, damit die WixCA-Binärdatei verfügbar ist.

Eine einzelne imitierte Aktion registriert die Identität nur für den Benutzer, der das Installationsprogramm ausführt. Um jeden Benutzer einer computerbezogenen Installation bereitzustellen, führen Sie die Registrierung stattdessen beim ersten Start (pro Benutzer) durch, oder verwenden Sie einen Bereitstellungsmechanismus wie 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 gibt zur Laufzeit / "keine Paketidentität" aus

  • Das Identitätspaket ist nicht registriert, oder im Fusion-Manifest der EXE-Datei fehlt das Element <msix>. Führen Sie die Datei erneut aus (und erstellen Sie sie erneut, wenn sie den XML-Modus winapp embed-identity verwenden), und registrieren Sie sich dann erneut bei Add-AppxPackage -ExternalLocation.
  • Die <msix packageName> / / publisherapplicationId in der Exe muss genau mit der Identität des registrierten Pakets übereinstimmen.

Objekte/Logos werden nicht angezeigt

  • Stellen Sie sicher, dass der Assets/ Ordner am externen Speicherort mit denselben relativen Pfaden bereitgestellt wird, die das Manifest erwartet. Ressourcen werden über den externen Speicherort aufgelöst, nicht über die .msix.

Add-AppxPackage schlägt mit einem Signatur- oder Vertrauensfehler fehl

  • Das .msix Muss von einem Zertifikat signiert werden, das auf dem Computer vertrauenswürdig ist und dessen Betreff mit dem Manifest Publisherübereinstimmt. Generieren und vertrauen Sie für lokale Tests ein Dev-Zertifikat mit winapp cert generate, und stellen Sie sicher, dass das Manifest mit dem Zertifikat Publisher übereinstimmt.

MakeAppx: "Anwendung mit dem RuntimeBehavior-Wert 'win32App' darf keinen EntryPoint deklarieren"

  • Eine Sparse-win32App-Anwendung darf EntryPoint nicht deklarieren. Von ihnen generierte winapp init --sparse Manifeste sind bereits korrekt. Entfernen Sie jedes EntryPoint Attribut, wenn Sie das Manifest manuell bearbeitet haben.

"Die Eingabe ist eine Datei, aber kein Sparse-Manifest"

  • winapp pack <file> akzeptiert nur ein Manifest, in dem <uap10:AllowExternalContent>true</uap10:AllowExternalContent> angegeben ist. Generieren Sie einen mit winapp init --exe <exe> --sparse, oder übergeben Sie einen Eingabeordner , um einen vollständigen MSIX zu erstellen.

Siehe auch