Ritka csomagolás: identitás biztosítása csomagolatlan alkalmazásnak

Egy működő, teljes körű példáért (WPF alkalmazás + Inno telepítő) tekintse meg a ritka alkalmazás mintáját.

A standard asztali végrehajtható fájlok – amelyek az MSBuild, a CMake vagy bármely más eszközlánc használatával dotnet buildkészültek – nem rendelkezik csomagidentitással. Identitás nélkül nem használhat számos modern Windows API-t (bejelentési értesítések, háttérfeladatok, megosztási célok, indítási feladatok, alkalmazásadat API-k stb.).

A ritka csomagolás identitást ad egy alkalmazásnak anélkül , hogy bináris fájljait MSIX-be helyezné át. Egy apró, csak identitást tartalmazó.msix csomagot ad át (csak egy jegyzékfájlt), és azt a szokásos módon telepített alkalmazása mellett, külső hely használatával regisztrálja. Az Ön .exe pontosan ott marad, ahová a telepítő helyezi. Ez a(z) winapp create-debug-identity éles környezeti megfelelője, amely kizárólag a fejlesztés közbeni hibakeresésre szolgál.

Ez az útmutató azt a három CLI-lépést ismerteti, amely a hivatalos Identitás megadása nem csomagolt alkalmazásoknak munkafolyamat első három lépésének felel meg:

Lépés Parancs Result
1. Az identitásjegyzék létrehozása winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Az identitáscsomag létrehozása és aláírása winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Identitás beágyazása az alkalmazásba winapp embed-identity <exe> <msix> elem az exe fúziós jegyzékében

A dokumentáció 4–5. lépése (a csomag regisztrálása / regisztrációjának törlése) a telepítő feladata – lásd a Telepítő integrációját.

Mikor érdemes ritkán használt csomagolást használni?

  • Már rendelkezik kiforrott telepítővel (Inno Setup, WiX, NSIS, MSI), és nem szeretne msIX-re váltani a disztribúcióhoz, de identitásalapú Windows API-kra van szüksége.
  • Az alkalmazásnak olyan elérési útra vagy elrendezésre kell telepítenie, amelyet az MSIX nem engedélyez.
  • Minimális, additív módosítást szeretne: tartsa meg a meglévő telepítési folyamatot, és adjon hozzá egy .msix regisztrációs lépést.

Ha most kezdi, és MSIX-ként tudja terjeszteni, a teljesen csomagolt alkalmazás (winapp init + winapp pack <folder>) egyszerűbb.

Prerequisites

  1. Windows 10, 2004-es (19041-es build) vagy újabb verzió. A sparse csomagok a uap10:AllowExternalContent elemre támaszkodnak, amihez a 19041-es vagy újabb verzió szükséges.
  2. winapp CLI – telepítés wingettel (vagy frissítés, ha már telepítve van):
    winget install Microsoft.WinApp --source winget
    
  3. Kódaláíró tanúsítvány, amelyben a célgép megbízik. A helyi teszteléshez hozzon létre egy fejlesztési tanúsítványt, winapp cert generate és bízzon benne. Az éles csomagokat olyan tanúsítvánnyal kell aláírni, amelynek tárgya megegyezik a jegyzékfájllal Publisher.

Walkthrough

Az alábbi példák egy beépített végrehajtható fájlt feltételeznek a következő helyen ./bin/Release/net8.0-windows/MyApp.exe: .

1. lépés – Hozza létre a részleges identitás-jegyzékfájlt

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

Ez az .exe fájlból kiolvassa a csomag nevét, a kiadóját, a verzióját és a leírását (a fájl verzióinformációi alapján), majd felkéri, hogy fogadja el vagy írja felül ezeket. Adja hozzá --use-defaults (vagy --no-prompt) a parancssorok kihagyásához a CI-ben, és --name / --publisher felülbírálja a megadott értékeket:

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

Alapértelmezés szerint egy dedikált sparse/ mappába írja a következőket az aktuális könyvtárban (felülbírálás a következővel --output-dir):

  • appxmanifest.xml — egy egyszerű manifest, benne <uap10:AllowExternalContent>true</uap10:AllowExternalContent>-val (egy elem a(z) <Properties> alatt), ProcessorArchitecture="neutral"-mal, egy win32App alkalmazással, és az exe neve a(z) Executable mezőbe beírva.
  • Assets/ — helyőrző vizualizációs objektumok (lehetőség szerint kinyerve az exe ikonjából).

Miért egy sparse/ mappa, és nem az exe mellett? A manifestum és a Assets/ olyan fordításkori bemenetek, amelyeket a winapp pack és a winapp embed-identity használ fel — futásidőben semmi sem olvassa őket az .exe mellől (a futásidejű identitás az .exe-be ágyazott <msix> elemből, valamint a regisztrált csomag külső helyéből származik, a manifestum pedig név alapján hivatkozik az .exe-re, így a helye független attól, hogy maga az .exe hol található). Ha egy dedikált, forrás által vezérelt mappába írja őket, azokat egy buildkimeneti könyvtárból (például bin/) távol tartja, amelyet egy tiszta/újraépítés törölne, és a mappa bináris fájloktól mentes marad, így a következő lépések tiszta maradnak. winapp pack és winapp embed-identity automatikusan a(z) sparse/ helyen keres, ezért ritkán kell megadnia az elérési utat.

Megjegyzés: A ritka init folyamat szándékosan kihagyja az összes SDK/csomagtelepítést – a csak identitásalapú csomagok nem rendelkeznek SDK-függőségekkel.

Ha egy appxmanifest.xml már létezik a célkönyvtárban, az init leáll ahelyett, hogy felülírná azt (és a Assets/ elemét). Az újrageneráláshoz futtassa újra a(z) --force használatával.

Győződjön meg arról, hogy a létrehozott Publisher jegyzékfájl megegyezik azzal a tanúsítvánnyal, amellyel alá fogja írni. Szükség esetén szerkessze a(z) appxmanifest.xml elemet, vagy generáláskor adja meg a(z) --publisher elemet.

2. lépés – Az identitáscsomag létrehozása és aláírása

Mutasson winapp pack a ritka jegyzékre (fájlra, nem mappára):

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

Mivel a manifesztum deklarálja a AllowExternalContent elemet, a winapp pack egy csak identitást tartalmazó.msix csomagot hoz létre, amely csak a manifesztumot tartalmazza — bináris fájlok és erőforrások nélkül. A kimenet alapértelmezés szerint a jelenlegi könyvtárban lévő <PackageName>.identity.msix értéket használja; ennek módosításához használja a(z) --output kapcsolót. Az aláírásra csak akkor kerül sor, ha megadja a --cert elemet (vagy a --generate-cert elemet).

3. lépés – Identitás beágyazása az alkalmazásba

Ágyazza be az <msix> elemet úgy, hogy Windows csatlakoztassa a futó exe-t az identitáscsomaghoz:

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

Vagy megőrizheti az egymás melletti manifestfájlt verziókövetésbe felvett fájlként, majd újraépítheti:

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

XML módban az <msix> elem be lesz szúrva (vagy lecserélve) a céljegyzékbe. Hivatkozzon a projektben lévő jegyzékfájlra (.NET esetén állítsa be: <ApplicationManifest>app.manifest</ApplicationManifest>), majd építse újra, hogy az elem beágyazódjon az EXE fájlba.

Mindkét mód egy ritkás appxmanifest.xml elemből olvassa ki az azonosítót. Ha kihagyja --manifest, a winapp először a cél mellett egy sparse/ mappában (ahol winapp init --exe --sparse alapértelmezés szerint megírja) jelenik meg, majd az aktuális könyvtárban, majd visszaesik a cél és az aktuális könyvtár mellé; átmegy --manifest máshová.

Megjegyzés: Az EXE mód mt.exe újraírja a binárist, ami minden meglévő Authenticode-aláírást érvénytelenít. A terjesztés előtt írja alá újra az exe-t (például winapp sign ./MyApp.exe <cert.pfx>).

4. lépés – Regisztráció (helyi teszteléshez)

A jegyzékfájl logói futásidőben a külső helyről töltődnek be, nem pedig a csak identitást tartalmazó .msix elemből. Az 1. lépés a ./sparse/Assets alá írta őket, ezért a regisztrálás előtt másolja át őket az .exe fájl mellé (a külső helyre) — ellenkező esetben a Windows olyan kiosztást regisztrál, amelyből hiányzik a jegyzékfájlban hivatkozott összes logó:

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

Ezután regisztrálja az identitáscsomagot ahhoz a mappához (a külső helyhez):

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

Indítsa el az alkalmazást, és győződjön meg arról, hogy az identitás jelen van – Windows.ApplicationModel.Package.Current.Id.FamilyName például dobás helyett a csomagcsalád nevét kell visszaadnia.

Tisztítás:

Remove-AppxPackage <full-package-name>

Eszközkezelés

A ritka .msixcsak identitás lehet. A manifestumban hivatkozott vizuális elemek (Assets\StoreLogo.png, csempék stb.) futásidőben a külső tartalom helyéről kerülnek feloldásra — vagyis az alkalmazás telepítési könyvtárából —, nem pedig a .msix belsejéből.

Ez azt jelenti, hogy az alkalmazása mellett kell telepítenie a Assets/ mappát (ugyanabban az elrendezésben, mint amit a jegyzékfájl a külső helyhez képest elvár).

A 2. lépés közvetlenül a manifestfájlt csomagolja be (winapp pack ./sparse/appxmanifest.xml), ami kizárólag ebből a manifestfájlból hozza létre a csak identitást tartalmazó .msix elemet — a mellette lévő fájlokat figyelmen kívül hagyja, így az soha nem tartalmazza az erőforrásait vagy bináris fájljait. (Ha ehelyett a winapp pack mutat, amelynek a manifesztuma deklarálja a AllowExternalContent elemet, figyelmeztet az összes talált eszközfájlra vagy bináris fájlra, mivel ritkított csomag esetén ezeknek a külső helyen van a helyük, nem a .msix elemen belül.)

Telepítőintegráció

A regisztráció és a regisztráció törlése a telepítő feladata. A minta megegyezik a telepítőeszközökkel:

  • Telepítés: másolja az alkalmazás bináris fájljait, a Assets/ mappát és a .msix telepítési könyvtárba, majd futtassa Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>".
  • Eltávolítás: a fájlok törlése előtt futtassa Remove-AppxPackage <full-package-name> .

Biztonság: a telepítési könyvtár a telepítéskor feloldódik, és tartalmazhat olyan karaktereket (például egyetlen idézőjelet), amelyek egy PowerShell-sztringkonstansból bontanak ki. A sztringbe történő -Command interpolálás előtt mindig meneküljön vagy ellenőrizze az elérési utat – az alábbi WiX- és NSIS-kódrészletek megbízható telepítési útvonalat feltételeznek, míg az Inno telepítőpéldája a biztonságos menekülést szemlélteti. Részesítse előnyben az elérési utak argumentumként való átadását egy -File szkript számára a soron belüli -Command interpoláció helyett.

Inno Setup

A PowerShell-argumentumokat egy [Code] függvényben állítsa össze, hogy a futtatókörnyezet telepítési útvonala megfelelően escape-elve legyen az egyszeres idézőjelekkel határolt PowerShell-literálhoz (egy ' karaktert tartalmazó telepítési könyvtár nem teheti lehetővé szkriptinjektálást):

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

A teljes, működő setup.iss megtekintéséhez lásd a sparse-app példát.

Az alábbi WiX- és NSIS-példák a -File segítségével egy kis register-sparse.ps1-t hívnak meg, így a telepítési útvonal paraméterként lesz átadva (a PowerShell adatként kezeli), ahelyett hogy egy -Command karakterláncba lenne interpolálva. Így elkerülhető a szkriptinjektálás egy létrehozott telepítési könyvtáron keresztül (például egy idézőjelet tartalmazó mappanév vagy $(...)):

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

Regisztráljon felhasználónként (Impersonate="yes"), mert Add-AppxPackage regisztrálja a csomagot az azt futtató fiókhoz. Az Impersonate="no" használatával végrehajtott halasztott művelet LocalSystem néven fut, amely nem biztosítja a telepítő felhasználó jogosultságait (ezért ezt gyakran elutasítják). Gépszintű MSI esetén a regisztrációt megszemélyesítve futtassa, hogy az a regisztrációt kezdeményező felhasználóra vonatkozzon.

Egy halasztott egyéni művelet nem tudja közvetlenül beolvasni a INSTALLFOLDER értékét (mivel a halasztott műveletek olyan környezetben futnak, ahol nincs hozzáférés a tulajdonságokhoz), és a művelet puszta deklarálása még nem hajtja végre azt. Ezért vezesse be az elérési utakat a(z) CustomActionData használatával — egy azonnali, 51-es típusú műveleten keresztül, amelynek a Property neve megegyezik a halasztott művelet Id értékével —, majd ütemezze mindkettőtInstallFiles után:

<!-- 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 a WiX util bővítmény részeként érhető el (WixUtilExtension); hivatkozzon erre, hogy a WixCA bináris elérhető legyen.

Egyetlen megszemélyesített művelet csak a telepítőt futtató felhasználó identitását regisztrálja. Ha egy gépenkénti telepítés minden felhasználója számára szeretné biztosítani az üzembe helyezést, regisztráljon helyette az első indításkor (felhasználónként), vagy használjon üzembehelyezési mechanizmust, például 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

Hibaelhárítás

Package.Current futásidőben „nincs csomagazonosító” hibát jelez

  • Az identitáscsomag nincs regisztrálva, vagy az exe fúziós jegyzékfájlja hiányzik az <msix> elemből. Futtassa újra a(z) winapp embed-identity elemet (és XML mód használata esetén építse újra), majd regisztrálja újra a(z) Add-AppxPackage -ExternalLocation használatával.
  • Az <msix packageName> / / publisherapplicationId exe fájlnak pontosan meg kell egyeznie a regisztrált csomag identitásával.

Az eszközök/emblémák nem jelennek meg

  • Győződjön meg arról, hogy a Assets/ mappa a külső helyre van telepítve, ugyanazokkal a relatív elérési utakkal, amelyeket a manifesztum elvár. Az eszközök a külső helyről vannak feloldva, nem a .msix.

Add-AppxPackage aláírási/megbízhatósági hibával meghiúsul

  • A .msix tanúsítványt olyan tanúsítvánnyal kell aláírni, amely megbízható a gépen, és amelynek tárgya megegyezik a jegyzékfájléval Publisher. Helyi teszteléshez hozzon létre és bízzon meg egy fejlesztői tanúsítványt winapp cert generate, és győződjön meg arról, hogy a jegyzék Publisher egyezik vele.

MakeAppx: "Az alkalmazás, amelynek a RuntimeBehavior értéke 'win32App', nem deklarálhatja az EntryPointot"

  • Egy win32App ritkás alkalmazás nem deklarálhat EntryPoint. A winapp init --sparse által létrehozott jegyzékfájlok már helyesek; távolítsa el a EntryPoint attribútumot, ha kézzel szerkesztette a jegyzékfájlt.

"A bemenet egy fájl, de nem szórt jegyzékfájl"

  • winapp pack <file> csak olyan jegyzékfájlt fogad el, amely deklarálja a(z) <uap10:AllowExternalContent>true</uap10:AllowExternalContent> elemet. Hozzon létre egyet a(z) winapp init --exe <exe> --sparse használatával, vagy adjon meg egy bemeneti mappát a teljes MSIX felépítéséhez.

Lásd még