Pakiet rozrzedzony: nadawanie tożsamości aplikacji bez pakietu

Aby zobaczyć działający przykład end-to-end (aplikacja WPF + instalator Inno Setup), zapoznaj się z przykładem sparse-app.

Standardowy wykonywalny plik desktopowy — zbudowany przy użyciu dotnet build, MSBuild, CMake lub dowolnego innego łańcucha narzędzi — nie ma tożsamości pakietu. Bez tożsamości nie może używać wielu nowoczesnych interfejsów API Windows (wyskakujące powiadomienia, zadania w tle, udostępniać obiekty docelowe, zadania uruchamiania, interfejsy API danych aplikacji i nie tylko).

Pakietowanie rozproszone nadaje aplikacji tożsamość bez przenoszenia jej plików binarnych do pakietu MSIX. Dostarczasz niewielki pakiet zawierający wyłącznie tożsamość.msix (tylko manifest) i rejestrujesz go obok standardowo zainstalowanej aplikacji za pomocą lokalizacji zewnętrznej. Twój .exe pozostaje dokładnie tam, gdzie umieści go instalator. Jest to produkcyjny odpowiednik winapp create-debug-identity, który służy wyłącznie do debugowania na etapie programowania.

W tym przewodniku opisano trzy kroki w interfejsie wiersza polecenia, które odpowiadają pierwszym trzem krokom oficjalnego przepływu pracy Nadawanie tożsamości aplikacjom niespakietowanym:

Krok Polecenie Result
1. Tworzenie manifestu tożsamości winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Kompilowanie i podpisywanie pakietu tożsamości winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Osadzanie tożsamości w aplikacji winapp embed-identity <exe> <msix> element w manifeście zestawiania pliku exe

Kroki od 4 do 5 dokumentów (rejestrowanie/wyrejestrowywanie pakietu) to odpowiedzialność instalatora — zobacz Integracja instalatora.

Kiedy należy używać pakowania rozrzedliwego

  • Masz już dojrzały instalator (Inno Setup, WiX, NSIS, MSI) i nie chcesz przełączać się na MSIX na potrzeby dystrybucji, ale potrzebujesz interfejsów API Windows z bramą tożsamości.
  • Aplikacja musi instalować się w lokalizacji lub w układzie, na które MSIX nie zezwala.
  • Potrzebujesz minimalnej zmiany addytywnej: zachowaj istniejący przepływ instalacji i dodaj jeden .msix krok rejestracji.

Jeśli zaczynasz od nowa i możesz dystrybuować jako MSIX, pełna spakowana aplikacja (winapp init + winapp pack <folder>) jest prostsza.

Wymagania wstępne

  1. Windows 10, wersja 2004 (kompilacja 19041) lub nowsza. Pakiety rozproszone opierają się na uap10:AllowExternalContent, co wymaga wersji 19041 lub nowszej.
  2. Interfejs wiersza polecenia winapp — instalowanie za pomocą zestawu narzędzi (lub aktualizacja, jeśli jest już zainstalowana):
    winget install Microsoft.WinApp --source winget
    
  3. Certyfikat podpisywania kodu uznawany na komputerze docelowym za zaufany. Do testów lokalnych wygeneruj certyfikat deweloperski za pomocą winapp cert generate i zaufaj mu. Pakiety produkcyjne muszą być podpisane przy użyciu certyfikatu, którego temat pasuje do manifestu Publisher.

Walkthrough

Poniższe przykłady zakładają, że skompilowany plik wykonywalny znajduje się pod adresem ./bin/Release/net8.0-windows/MyApp.exe.

Krok 1 — Utwórz rzadki manifest tożsamości

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

Na tej podstawie zostaną określone nazwa pakietu, wydawca, wersja i opis z pliku .exe (na podstawie informacji o wersji pliku), a następnie pojawi się monit o ich zaakceptowanie lub zastąpienie. Dodaj --use-defaults (lub --no-prompt), aby pominąć monity w CI, a --name / --publisher, aby nadpisać określone wartości:

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

Domyślnie zapisuje następujące dane do dedykowanego folderu sparse/ w bieżącym katalogu (można to zmienić za pomocą --output-dir):

  • appxmanifest.xml — uproszczony manifest z elementem <uap10:AllowExternalContent>true</uap10:AllowExternalContent> (pod elementem <Properties>), ProcessorArchitecture="neutral", aplikacją win32App oraz nazwą pliku exe wpisaną w Executable.
  • Assets/ — zastępcze zasoby wizualne (wyodrębnione z ikony exe, gdy jest to możliwe).

sparse/ Dlaczego folder, a nie obok pliku exe? Manifest i Assets/danymi wejściowymi używanymi na etapie kompilacji przez winapp pack i winapp embed-identity — nic nie odczytuje ich w czasie wykonywania z lokalizacji obok pliku exe (tożsamość w czasie wykonywania pochodzi z elementu <msix> osadzonego w pliku exe oraz zewnętrznej lokalizacji zarejestrowanego pakietu, a manifest odwołuje się do pliku exe według nazwy, więc jego lokalizacja jest niezależna od tego, gdzie znajduje się plik exe). Zapisywanie ich w dedykowanym folderze objętym kontrolą wersji pozwala trzymać je poza katalogiem wyjściowym kompilacji (takim jak bin/), który zostałby wyczyszczony podczas czyszczenia lub ponownej kompilacji, a także utrzymać ten folder bez plików binarnych, dzięki czemu kolejne kroki pozostają uporządkowane. winapp pack i winapp embed-identity automatycznie szukają w sparse/, więc rzadko trzeba podawać ścieżkę.

Uwaga: Uproszczony proces inicjalizacji celowo pomija całą instalację pakietów i zestawu SDK — pakiety zawierające wyłącznie składniki tożsamości nie mają zależności od zestawu SDK.

Jeśli element appxmanifest.xml już istnieje w katalogu docelowym, init zatrzyma się, zamiast go nadpisać (wraz z jego Assets/). Uruchom ponownie za pomocą --force, aby ponownie to wygenerować.

Upewnij się, że Publisher w wygenerowanym manifeście odpowiada certyfikatowi, którego użyjesz do podpisania. Edytuj appxmanifest.xml w razie potrzeby lub przekaż --publisher podczas generowania.

Krok 2. Kompilowanie i podpisywanie pakietu tożsamości

Wskaż w winapp pack manifest rozproszony (plik, a nie katalog):

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

Ponieważ manifest deklaruje AllowExternalContent, winapp pack tworzy pakiet zawierający wyłącznie tożsamość.msix, zawierający tylko manifest — bez plików binarnych i bez zasobów. Dane wyjściowe są domyślnie zapisywane w bieżącym katalogu <PackageName>.identity.msix; użyj --output, aby to zmienić. Podpisywanie następuje tylko wtedy, gdy przekażesz --cert (lub --generate-cert).

Krok 3. Osadzanie tożsamości w aplikacji

Osadź element <msix>, tak aby system Windows połączył uruchomiony plik wykonywalny z pakietem tożsamości aplikacji:

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

Możesz też zachować manifest side-by-side jako plik zaewidencjonowany i ponownie skompilować:

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

W trybie XML element <msix> jest wstawiany do manifestu docelowego (lub w nim zastępowany). Dodaj odwołanie do tego manifestu w projekcie (dla platformy .NET ustaw <ApplicationManifest>app.manifest</ApplicationManifest>) i przebuduj projekt, aby ten element został osadzony w pliku EXE.

Oba tryby odczytują tożsamość z rozrzednicy appxmanifest.xml. Po pominięciu --manifest program winapp najpierw szuka w folderze sparse/ (gdzie winapp init --exe --sparse domyślnie go zapisuje) obok elementu docelowego, następnie w bieżącym katalogu, a w ostateczności sprawdza lokalizację obok elementu docelowego i bieżący katalog; użyj --manifest, aby wskazać inną lokalizację.

Uwaga: Tryb EXE nadpisuje plik binarny przy użyciu mt.exe, co unieważnia wszystkie istniejące podpisy Authenticode. Ponownie podpisać plik exe (np. winapp sign ./MyApp.exe <cert.pfx>) przed jego dystrybucją.

Krok 4. Rejestrowanie (na potrzeby testowania lokalnego)

Logo manifestu są pobierane z lokalizacji zewnętrznej w czasie działania, a nie z samego elementu .msix, zawierającego tylko tożsamość. Krok 1 umieścił je w katalogu ./sparse/Assets, więc skopiuj je obok pliku .exe (czyli w lokalizacji zewnętrznej) przed rejestracją — w przeciwnym razie Windows zarejestruje układ, w którym brakuje wszystkich logo, do których odwołuje się manifest:

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

Następnie zarejestruj pakiet tożsamości w tym folderze ( lokalizacja zewnętrzna):

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

Uruchom aplikację i potwierdź, że tożsamość jest dostępna — na przykład Windows.ApplicationModel.Package.Current.Id.FamilyName powinno zwrócić nazwę rodziny pakietu zamiast zgłaszać wyjątek.

Aby oczyścić:

Remove-AppxPackage <full-package-name>

Obsługa zasobów

Sparse .msix jest wyłącznie typu identity. Zasoby wizualne, do których odwołuje się manifest (Assets\StoreLogo.png, kafelki itp.), są pobierane z lokalizacji zawartości zewnętrznej w czasie wykonywania aplikacji — tj. z katalogu instalacyjnego aplikacji — .msix z wnętrza pliku .

Oznacza to, że należy umieścić folder Assets/ obok aplikacji (w układzie oczekiwanym przez manifest, względem lokalizacji zewnętrznej).

Krok 2 pakuje bezpośrednio plik manifestu (winapp pack ./sparse/appxmanifest.xml), co tworzy .msix zawierające wyłącznie tożsamość na podstawie samego manifestu — pozostałe pliki w tym samym katalogu są ignorowane, więc pakiet nigdy nie zawiera zasobów ani plików binarnych. (Jeśli zamiast tego wskażesz winapp packfolder , którego manifest deklaruje AllowExternalContent, ostrzega o wszelkich odnalezionych zasobach lub plikach binarnych, ponieważ w przypadku rozrzednego pakietu należą one do lokalizacji zewnętrznej, a nie wewnątrz .msixpliku ).

Integracja instalatora

Rejestracja i wyrejestrowanie to zadanie instalatora. Wzorzec jest taki sam w przypadku narzędzi instalatora:

  • Zainstaluj: skopiuj pliki binarne aplikacji, Assets/ folder i .msix folder do katalogu instalacji, a następnie uruchom polecenie Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>".
  • Odinstaluj: uruchom polecenie Remove-AppxPackage <full-package-name> przed usunięciem plików.

Zabezpieczenia: katalog instalacyjny jest rozpoznawany w czasie instalacji i może zawierać znaki (np. pojedynczy cudzysłów), które przerywają literał ciągu programu PowerShell. Zawsze należy unikać lub weryfikować ścieżkę przed interpolowaniem jej do -Command ciągu — fragmenty kodu WiX i NSIS poniżej zakładają zaufaną ścieżkę instalacji, podczas gdy przykład instalacji Inno demonstruje bezpieczne ucieczki. Preferuj przekazywanie ścieżek jako argumentów do skryptu -File za pośrednictwem interpolacji wbudowanej -Command .

Inno Setup

Skompiluj argumenty programu PowerShell w [Code] funkcji, aby ścieżka instalacji środowiska uruchomieniowego została uniknięta dla pojedynczego cudzysłowu programu PowerShell (katalog instalacyjny zawierający ' skrypt nie może być w stanie wstrzyknąć skryptu):

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

Zobacz przykład setup.iss, aby uzyskać kompletny, działający .

Poniższe przykłady WiX i NSIS wywołują niewielki register-sparse.ps1 za pośrednictwem -File, dzięki czemu ścieżka instalacji jest przekazywana jako parametr (PowerShell wiąże go jako dane), zamiast interpolować ją w ciągu -Command. Pozwala to uniknąć iniekcji skryptu za pośrednictwem spreparowanego katalogu instalacyjnego (np. nazwy folderu zawierającego cudzysłów lub $(...)):

# 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 (wersja 3)

Zarejestruj dla każdego użytkownika (Impersonate="yes"), ponieważ Add-AppxPackage rejestruje pakiet dla konta, na którym jest uruchomiony. Odroczona akcja uruchamiana jako LocalSystem z Impersonate="no"nie nadaje tożsamości instalującemu użytkownikowi (i często jest odrzucana). W przypadku pakietu MSI instalowanego na poziomie komputera uruchom rejestrację z personifikacją, aby została zastosowana do użytkownika wywołującego.

Odroczona akcja niestandardowa nie może bezpośrednio odczytać INSTALLFOLDER (odroczone akcje są wykonywane w kontekście bez dostępu do właściwości), a samo zadeklarowanie akcji nie powoduje jej uruchomienia. Dlatego przekaż ścieżki za pomocą CustomActionData — natychmiastowej akcji typu 51, której nazwa Property jest taka sama jak nazwa odroczonej akcji Id — i zaplanuj obie 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 jest zawarty w rozszerzeniu narzędziowym WiX (WixUtilExtension); dodaj do niego odwołanie, aby plik binarny WixCA był dostępny.

Pojedyncza akcja wykonywana z personifikacją rejestruje tożsamość tylko dla użytkownika uruchamiającego instalator. Aby udostępnić instalację dla całego komputera każdemu użytkownikowi, zamiast tego zarejestruj ją przy pierwszym uruchomieniu (dla użytkownika) lub użyj mechanizmu aprowizacji, takiego jak 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

Rozwiązywanie problemów

Package.Current zwraca błąd / „brak tożsamości pakietu” podczas działania

  • Pakiet identyfikatora nie jest zarejestrowany lub w manifeście fusion pliku exe brakuje elementu <msix>. Uruchom ponownie (i ponownie skompiluj winapp embed-identity , jeśli używasz trybu XML), a następnie ponownie zarejestruj się przy użyciu polecenia Add-AppxPackage -ExternalLocation.
  • Element <msix packageName> / / publisherapplicationId w pliku exe musi dokładnie odpowiadać tożsamości zarejestrowanego pakietu.

Elementy zawartości/logo nie są wyświetlane

  • Upewnij się, że folder Assets/ został wdrożony w lokalizacji zewnętrznej z zachowaniem tych samych ścieżek względnych, których oczekuje manifest. Zasoby są rozpoznawane z lokalizacji zewnętrznej, a nie z .msix.

Add-AppxPackage kończy się błędem podpisu lub zaufania

  • Element .msix musi być podpisany certyfikatem, któremu ufa komputer i którego podmiot jest zgodny z elementem Publisher manifestu. Do testów lokalnych wygeneruj certyfikat deweloperski i dodaj go do zaufanych za pomocą polecenia winapp cert generate, a następnie upewnij się, że manifest Publisher jest z nim zgodny.

MakeAppx: "Aplikacja z wartością RuntimeBehavior „win32App” nie może deklarować punktu wejścia EntryPoint"

  • Aplikacja sparse win32App nie może deklarować EntryPoint. Manifesty wygenerowane przez winapp init --sparse program są już poprawne. Usuń dowolny EntryPoint atrybut, jeśli ręcznie edytowano manifest.

"Dane wejściowe są plikiem, ale nie rozrzednym manifestem"

  • winapp pack <file> Akceptuje tylko manifest, który deklaruje <uap10:AllowExternalContent>true</uap10:AllowExternalContent>. Wygeneruj go za pomocą , lub przekaż wejściowy winapp init --exe <exe> --sparse, aby zbudować pełny pakiet MSIX.

Zobacz także