Kemasan jarang: memberikan identitas ke aplikasi yang tidak dikemas

Untuk contoh end-to-end lengkap yang berfungsi (aplikasi WPF + penginstal Inno Setup), lihat sampel sparse-app.

Executable desktop standar — dibangun dengan dotnet build, MSBuild, CMake, atau toolchain lainnya — tidak memiliki identitas paket. Tanpa identitas, ini tidak dapat menggunakan banyak API Windows modern (pemberitahuan toast, tugas latar belakang, target berbagi, tugas startup, API data aplikasi, dan banyak lagi).

Pemaketan sparse memberikan identitas pada aplikasi tanpa memindahkan file binernya ke dalam MSIX. Anda mengirim identitas kecil saja.msix (hanya manifes) dan mendaftarkannya bersama aplikasi yang biasanya diinstal menggunakan lokasi eksternal. .exe Anda tetap berada tepat di tempat program penginstal meletakkannya. Ini adalah padanan produksi untuk winapp create-debug-identity, yang hanya digunakan untuk penelusuran kesalahan selama pengembangan.

Panduan ini membahas tiga langkah CLI yang sesuai dengan tiga langkah pertama dari alur kerja resmi Memberikan identitas ke aplikasi non-paket:

Step Perintah Result
1. Buat manifes identitas winapp init --exe <exe> --sparse sparse/appxmanifest.xml + sparse/Assets/
2. Buat & tandatangani paket identitas winapp pack <appxmanifest.xml> --cert <pfx> <PackageName>.identity.msix
3. Sematkan identitas ke dalam aplikasi winapp embed-identity <exe> <msix> elemen dalam manifes Fusion milik EXE

Langkah 4–5 dokumen (daftar/batalkan pendaftaran paket) adalah tanggung jawab alat penginstal Anda — lihat Integrasi alat penginstal.

Kapan menggunakan kemasan jarang

  • Anda sudah memiliki alat penginstal yang matang (Inno Setup, WiX, NSIS, MSI) dan tidak ingin beralih ke MSIX untuk distribusi, tetapi Anda memerlukan API Windows yang terjaga identitasnya.
  • Aplikasi Anda harus diinstal ke jalur atau menggunakan tata letak yang tidak diizinkan oleh MSIX.
  • Anda menginginkan perubahan aditif minimal: pertahankan alur penginstalan yang ada dan tambahkan satu .msix langkah pendaftaran.

Jika Anda memulai dari awal dan dapat mendistribusikan sebagai MSIX, aplikasi paket lengkap (winapp init + winapp pack <folder>) lebih sederhana.

Prasyarat

  1. Windows 10, versi 2004 (build 19041) atau yang lebih baru. Paket sparse bergantung pada uap10:AllowExternalContent, yang memerlukan versi 19041 atau yang lebih baru.
  2. winapp CLI — instal melalui winget (atau perbarui jika sudah diinstal):
    winget install Microsoft.WinApp --source winget
    
  3. Sertifikat penandatanganan kode tepercaya pada komputer target. Untuk pengujian lokal, buat sertifikat pengembangan dengan winapp cert generate dan percayai. Paket produksi harus ditandatangani dengan sertifikat yang subjeknya cocok dengan manifes Publisher.

Walkthrough

Contoh di bawah ini mengasumsikan executable bawaan di ./bin/Release/net8.0-windows/MyApp.exe.

Langkah 1 — Buat manifes identitas sparse

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

Ini menentukan nama paket, penerbit, versi, dan deskripsi dari file exe (melalui informasi versi file) dan meminta Anda untuk menerima atau menggantinya. Tambahkan --use-defaults (atau --no-prompt) untuk melewati perintah di CI, dan --name / --publisher untuk mengambil alih nilai tertentu:

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

Ini menulis yang berikut ke folder khusus sparse/ di direktori saat ini secara default (ambil alih dengan --output-dir):

  • appxmanifest.xml — manifes sederhana dengan <uap10:AllowExternalContent>true</uap10:AllowExternalContent> (elemen di bawah <Properties>), ProcessorArchitecture="neutral", aplikasi win32App, dan nama file exe yang diisikan ke Executable.
  • Assets/ — aset visual placeholder (yang diekstrak dari ikon file exe jika memungkinkan).

Mengapa menggunakan folder sparse/ dan bukan di samping file exe? manifes dan Assets/ merupakan masukan saat build yang digunakan oleh winapp pack dan winapp embed-identity — tidak ada yang membacanya dari lokasi di samping file exe saat runtime (identitas runtime berasal dari elemen <msix> yang disematkan dalam exe serta lokasi eksternal paket yang terdaftar, dan manifes merujuk ke exe berdasarkan nama, sehingga lokasinya tidak bergantung pada lokasi file exe berada). Menyimpannya di folder khusus yang berada dalam kontrol versi mencegahnya berada di direktori output build (seperti bin/) yang isinya akan dihapus oleh proses clean/rebuild, serta menjaga folder tersebut tidak berisi biner agar langkah-langkah berikutnya tetap rapi. winapp pack dan winapp embed-identity lihat sparse/ secara otomatis, jadi Anda jarang perlu memberi nama jalur.

Catatan: Alur inisialisasi sparse sengaja melewatkan semua instalasi SDK/paket — paket khusus identitas tidak memiliki dependensi SDK.

Jika appxmanifest.xml sudah ada di direktori target, init akan berhenti alih-alih menimpa appxmanifest.xml tersebut (beserta Assets/-nya). Jalankan kembali dengan --force untuk meregenerasinya.

Pastikan Publisher dalam manifes yang dihasilkan cocok dengan sertifikat yang akan Anda tanda tangani. Edit appxmanifest.xml jika diperlukan, atau lewati --publisher saat membuat.

Langkah 2 — Membangun dan menandatangani paket identitas

Arahkan winapp pack ke sparse manifest (berkas, bukan folder):

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

Karena manifes mendeklarasikan AllowExternalContent, winapp pack membangun paket khusus identitas.msix yang hanya berisi manifes — tanpa biner, tanpa aset. Secara default, output berada di <PackageName>.identity.msix dalam direktori saat ini; gunakan --output untuk mengubah lokasi tersebut. Penandatanganan hanya terjadi saat Anda memberikan --cert (atau --generate-cert).

Langkah 3 — Sematkan identitas ke dalam aplikasi Anda

Sematkan <msix> elemen sehingga Windows menyambungkan exe yang sedang berjalan ke paket identitas:

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

Atau pertahankan manifes berdampingan sebagai file yang diperiksa dan dibangun kembali:

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

Dalam mode XML, elemen <msix> disisipkan ke dalam (atau diganti dalam) manifes target. Tambahkan referensi ke manifes tersebut dari proyek Anda (untuk .NET, atur <ApplicationManifest>app.manifest</ApplicationManifest>), lalu kompilasi ulang agar elemen tersebut disematkan ke dalam file exe.

Kedua mode membaca identitas dari appxmanifest.xml yang sparse. Ketika Anda menghilangkan --manifest, winapp akan mencari di folder sparse/ (tempat winapp init --exe --sparse menaruhnya secara default) di sebelah target terlebih dahulu, lalu di direktori saat ini, kemudian jika tidak ditemukan akan kembali mencari di sebelah target dan direktori saat ini; gunakan --manifest untuk mengarahkannya ke lokasi lain.

Catatan: Mode EXE menulis ulang biner dengan mt.exe, yang membatalkan tanda tangan Authenticode yang ada. Tanda tangani kembali exe (misalnya winapp sign ./MyApp.exe <cert.pfx>) sebelum mendistribusikannya.

Langkah 4 — Daftar (untuk pengujian lokal)

Logo manifest diambil dari lokasi eksternal saat runtime, bukan dari .msix yang hanya berisi identitas. Langkah 1 menuliskannya di ./sparse/Assets, jadi salin ke folder yang sama dengan file .exe Anda (lokasi eksternal) sebelum mendaftarkannya — jika tidak, Windows akan meregistrasikan tata letak yang tidak memiliki semua logo yang dirujuk oleh manifes:

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

Kemudian daftarkan paket identitas terhadap folder tersebut ( lokasi eksternal):

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

Luncurkan aplikasi dan pastikan identitas tersedia — misalnya, Windows.ApplicationModel.Package.Current.Id.FamilyName harus mengembalikan package family name Anda, bukan memunculkan error.

Untuk membersihkan:

Remove-AppxPackage <full-package-name>

Penanganan aset

Sparse .msix bersifat hanya identitas. Aset visual yang dirujuk oleh manifes (Assets\StoreLogo.png, ubin, dll.) diambil dari lokasi konten eksternal saat runtime — yaitu, dari direktori instalasi aplikasi Anda — bukan dari dalam .msix.

Ini berarti Anda harus menyebarkan Assets/ folder bersama aplikasi Anda (tata letak yang sama yang diharapkan manifes, relatif terhadap lokasi eksternal).

Langkah 2 mengemas berkas manifes secara langsung (winapp pack ./sparse/appxmanifest.xml), yang menghasilkan .msix yang hanya berisi identitas dari manifes tersebut — berkas lain di direktori yang sama diabaikan, jadi tidak pernah menyertakan aset maupun biner Anda. (Jika Anda mengarahkan winapp pack ke folder yang manifesnya mendeklarasikan AllowExternalContent, Anda akan diperingatkan tentang aset atau biner apa pun yang ditemukan, karena untuk paket sparse, aset atau biner tersebut seharusnya berada di lokasi eksternal, bukan di dalam .msix.)

Integrasi penginstal

Pendaftaran dan pembatalan pendaftaran adalah tugas penginstal. Polanya sama di seluruh alat penginstal:

  • Instal: salin biner aplikasi Anda, Assets/ folder, dan .msix ke direktori penginstalan, lalu jalankan Add-AppxPackage -Path "<install-dir>\MyApp.identity.msix" -ExternalLocation "<install-dir>".
  • Hapus instalan: jalankan Remove-AppxPackage <full-package-name> sebelum menghapus file.

Keamanan: direktori penginstalan diselesaikan pada waktu penginstalan dan mungkin berisi karakter (misalnya tanda kutip tunggal) yang memecah string PowerShell secara harfiah. Selalu lakukan escape atau validasi pada path sebelum menyisipkannya ke dalam string -Command — cuplikan WiX dan NSIS di bawah ini mengasumsikan path instalasi tepercaya, sedangkan contoh Inno Setup menunjukkan escape yang aman. Lebih suka meneruskan jalur sebagai argumen ke -File skrip daripada interpolasi sebaris -Command .

Penyetelan Inno

Buat argumen PowerShell dalam fungsi [Code] agar jalur penginstalan runtime di-escape untuk literal PowerShell bertanda kutip tunggal (direktori penginstalan yang berisi ' tidak boleh dapat menginjeksikan skrip):

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

Lihat sampel sparse-app untuk yang lengkap dan berfungsi setup.iss.

Contoh WiX dan NSIS di bawah ini menjalankan register-sparse.ps1 kecil melalui -File sehingga jalur instalasi diteruskan sebagai parameter (PowerShell mengikatnya sebagai data), alih-alih diinterpolasikan ke dalam string -Command. Ini menghindari injeksi skrip melalui direktori penginstalan yang dibuat (misalnya nama folder yang berisi kutipan atau $(...)):

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

Daftarkan per pengguna (Impersonate="yes"), karena Add-AppxPackage mendaftarkan paket untuk akun yang menjalankannya. Tindakan yang ditangguhkan dengan Impersonate="no" berjalan sebagai LocalSystem, yang tidak memberikan identitas kepada pengguna penginstalan (dan biasanya ditolak). Untuk MSI per mesin, jalankan pendaftaran yang ditiru sehingga berlaku untuk pengguna yang memanggil.

Aksi kustom yang ditangguhkan tidak dapat membaca INSTALLFOLDER secara langsung (aksi yang ditangguhkan berjalan dalam konteks yang tidak memiliki akses ke properti), dan sekadar mendeklarasikan aksi tersebut tidak akan menjalankannya. Jadi, arahkan path masuk melalui CustomActionData — tindakan Type-51 langsung dengan Property nama yang sama dengan Id milik tindakan tertunda — dan jadwalkan keduanya setelah 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 disertakan dalam ekstensi utilitas WiX (WixUtilExtension); referensikan ekstensi tersebut agar biner WixCA tersedia.

Satu tindakan yang ditiru mendaftarkan identitas hanya untuk pengguna yang menjalankan alat penginstal. Untuk menyediakan setiap pengguna pada instalasi per mesin, lakukan pendaftaran saat pertama kali dijalankan (per pengguna) sebagai gantinya, atau gunakan metode provisi seperti 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

Penyelesaian Masalah

Package.Current memunculkan error / "no package identity" saat dijalankan

  • Paket identitas tidak terdaftar, atau manifes fusi exe kehilangan <msix> elemen . Jalankan winapp embed-identity kembali (dan bangun kembali jika menggunakan mode XML), lalu daftar ulang dengan Add-AppxPackage -ExternalLocation.
  • <msix packageName> / applicationId / publisherdalam exe harus sama persis dengan identitas paket terdaftar.

Aset/logo tidak muncul

  • Pastikan folder Assets/ ditempatkan di lokasi eksternal dengan jalur relatif yang sama seperti yang diharapkan oleh manifes. Aset diselesaikan dari lokasi eksternal, bukan .msix.

Add-AppxPackage gagal karena kesalahan penandatanganan / kepercayaan

  • .msix harus ditandatangani oleh sertifikat yang tepercaya di komputer dan yang subjeknya sesuai dengan manifes Publisher. Untuk pengujian lokal, buat dan percayai sertifikat pengembangan dengan Publisher, dan pastikan file manifes winapp cert generate cocok dengannya.

MakeAppx: "Aplikasi dengan nilai RuntimeBehavior 'win32App' tidak boleh mendeklarasikan EntryPoint"

  • Aplikasi sparse win32App tidak boleh mendeklarasikan EntryPoint. Manifes yang dihasilkan oleh winapp init --sparse sudah benar; hapus atribut apa pun EntryPoint jika Anda mengedit manifes dengan tangan.

"Masukan berupa berkas, tetapi bukan manifes sparse"

  • winapp pack <file> hanya menerima manifes yang menyatakan <uap10:AllowExternalContent>true</uap10:AllowExternalContent>. Buat satu dengan winapp init --exe <exe> --sparse, atau berikan folder input untuk membuat MSIX lengkap.

Baca juga