# MSI installer
A per-user MSI built with WiX. It is what the winget package installs, and it is the reason the
app gets a Start Menu shortcut and an Add/Remove Programs entry that a portable zip cannot give it.
## Shape
- **Per-user, never elevated.** `Scope="perUser"` sets `InstallPrivileges="limited"`. Installs
into `%LOCALAPPDATA%\Programs\ForceAutoHDR.Net`. Everything the app writes at runtime lives
under HKCU, so an installer asking for admin would be the only part of the product that did.
Verified from an unelevated shell: no prompt, exit 0, and the log says *"Running product
'{...}' with user privileges: It's not assigned."* One wrinkle worth knowing about: Windows
Installer writes the Add/Remove Programs key to HKLM anyway, so winget sees the package as
`ARP\Machine`. That is why the winget manifest declares no `Scope` —
[notes/per-user-msi-still-registers-arp-under-hklm.md](../../notes/per-user-msi-still-registers-arp-under-hklm.md).
- **No PATH entry.** This was the deciding difference against a winget `portable` package: every
portable configuration puts something on PATH, and for this payload that something is a
directory holding thirty Windows App SDK DLLs.
- **Payload harvested, not listed.** `` picks up
whatever `dotnet publish` produced, so the trim list in `ForceAutoHDR.App.csproj` stays the
single source of truth for what ships.
- **Upgrades handled by Windows Installer.** `Package/@Id` is a stable string, WiX derives the
UpgradeCode from it, and `` replaces the previous version. Verified: building
two different versions off the same source gives the same UpgradeCode and different
ProductCodes, and the UpgradeCode comes out identical on a different machine, so upgrades will
line up. The flip side: renaming `Package/@Id` moves the UpgradeCode with it, and an installer
with a new UpgradeCode installs *beside* the old version rather than over it.
## Building
The payload has to exist first; the MSI is built from a publish output, not from a project
reference, because the file set only settles after the AOT link step and the trim target:
```powershell
dotnet publish src/ForceAutoHDR.App -c Release -o publish
dotnet build packaging/msi/ForceAutoHDR.wixproj -c Release -t:Rebuild -p:ProductVersion=1.0.0 -p:PayloadDir=$PWD\publish
```
`-t:Rebuild` is not optional. See
[notes/msi-rebuild-needed-when-only-the-version-changes.md](../../notes/msi-rebuild-needed-when-only-the-version-changes.md).
To read the generated codes back out, for a winget manifest or just to check what got built:
```powershell
./Get-MsiProperty.ps1 -Path bin/Release/ForceAutoHDR.msi
```
## The WiX licence
WiX v7 participates in the [Open Source Maintenance Fee](https://opensourcemaintenancefee.org/).
The fee itself is owed by organizations above a revenue threshold (typically US$10,000) that use
the binary releases as part of revenue generating activities. A free hobby project run by one
person is not that, so nothing is owed here.
What *is* required of everyone is accepting the EULA: from v7 on, `wix` refuses to run any command
until it is accepted, with `error WIX7015`. That is the single `wix7` line
in the wixproj. If that obligation ever becomes unwelcome, WiX v5 is the last release before the
scheme was introduced.
## Why not Inno Setup or a plain zip
Inno produces an unsigned `.exe`, which is the one installer shape that reliably trips SmartScreen
for users who download it by hand. The zip is still built and still published — it is what the
README tells people to grab, and it stays the answer for anyone who does not want an installer at
all. The MSI exists for the people who want the app to behave like an installed application.