You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Offer IT-published .exe and .msi installers in the Windows Software Catalog - #5622
A Windows "Installer packages" manager (installer), the counterpart of the macOS pkg manager from #5603. A catalog's pkgFeed now fills it on Windows too: every .exe and .msi in the feed (a folder, a share, or an https folder read through the index.json that unigetui pkg-index <folder> writes) joins that catalog, and users install, update and uninstall them from the Software Catalog. One feed folder and one index.json can serve both platforms.
Formats:.msi is read through msi.dll (Property table, summary Template, product icon) and identified by its UpgradeCode. .exe goes through a managed, bounded PE reader (version resource, sections, appended data, icon) that recognizes Inno Setup, NSIS and WiX Burn with their silent switches. A <name>.json beside a file overrides anything, and gives the silent switches of an installer UniGetUI does not recognize (Advanced Installer, InstallShield, Squirrel…).
Detection: from Apps and Features, so apps deployed by Intune, ConfigMgr or by hand are recognized and updates are offered. An .msi matches through Windows Installer (related products, product code); an .exe by the manifest's uninstall key, a bundle's upgrade code, else its display name with an agreeing publisher. A name match found only by stripping a version must share the package's major version, so a side-by-side product (.NET SDK 8 next to 10) is not taken for it.
Install, update, uninstall: a private staged copy is checked against the index hash, its identity and Authenticode (WinVerifyTrust, waived only by allowUnsignedPackages), then run silently. An .msi elevates unless it is per-user; an .exe starts unelevated and lets the installer elevate itself, unless its manifest says "scope": "machine". Uninstall runs the registered command made silent; Windows Installer products take their scope from AssignmentType, since per-user MSIs are registered under HKLM too.
Catalog Editor: the Package feed dialog now previews Windows feeds, with platform-specific texts.
Shared feed core. The format-agnostic parts of the macOS manager move to UniGetUI.PackageEngine.Managers.Generic.InstallerFeeds (catalog feed declarations, locations, the cached listing, the index schema, sources, staging, pkg-index); the macOS manager keeps its behaviour. Windows entries are listed under their own windowsPackages key in index.json, so no Mac, whatever its release, ever offers a Windows installer. pkg-index runs on both platforms, writes its own list and keeps the other's while its files are unchanged.
Docs: docs/WINDOWS_INSTALLER_FEEDS.md for administrators, docs/PKG_FEEDS.md, docs/CLI.md, and a section in AGENTS.md.
Testing
UniGetUI.PackageEngine.Tests on Windows arm64: full suite passes (2,907, with 9 opt-in end-to-end tests skipped); the feed tests (166) also pass on top of the second commit, and the macOS feed tests pass on net10.0 (120).
WinInstallerEndToEndTests (opt-in with UNIGETUI_INSTALLER_E2E=1, or =elevated for UAC) install, update and uninstall real MSI and Inno Setup packages built by the tests: folder and web feeds, a tampered file refused, an unsigned one refused, a manifest-driven installer, the app's operation classes, and per-machine installs behind UAC. All passed locally, and nothing was left installed.
In the app on Windows, through the headless CLI and the GUI, against the real Devolutions installers (Agent, Gateway, Launcher, MsRdpEx, Password Manager, Server Console, Remote Desktop Manager, UniGetUI): installed apps recognized at the right versions, the RDM update offered, icons read from the installers.
dotnet format whitespace and dotnet format style (--verify-no-changes) pass. The win-x64 NativeAOT compile reported no trim or AOT warnings from the new code; its link step could not run on this arm64 machine, so there is no AOT runtime check.
Not run: the macOS tests that need Apple tools (disk images, the root helper, pkgbuild) — they still need a run on a Mac; NSIS and WiX Burn installers were not executed (only detected), as the tools are not installed here.
Known limits
Installers whose technology is not recognized need a manifest with their silent switches.
There is no Windows equivalent of the macOS root helper yet: an install for every user shows a UAC prompt.
Elevated installers run from a staging folder under the user's temp directory.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
ID-normalization collisions silently hide non-Latin applications from the catalog, while scope-insensitive receipts can suppress real updates and incomplete MSI staging can cause installation failures.
Per-user and per-machine installations can share an uninstall key and DisplayVersion, so a receipt for one can make the other appear updated and hide a real update. Persist and match entry.PerMachine, and have Record replace receipts by package ID, uninstall key, and scope rather than deleting every receipt for the package. Treat existing receipts without scope as unverified rather than applying them to either installation.
Stage or reject MSI packages with external cabinet dependencies
A valid MSI can reference an external cabinet, such as data1.cab beside Product.msi. The metadata reader accepts these packages, but staging copies only the MSI, so msiexec runs from a new directory without its required media and installation fails even when the cabinet is present in the feed. Either stage and verify the referenced payloads too, or detect non-self-contained MSIs during indexing/inspection and report them as unsupported instead of offering an install that cannot succeed.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Unresolved issues can suppress updates through stale version receipts, offer MSIs whose required files are not staged, and cause quadratic receipt processing during package inventory.
No open issues 🎉
3 previously missed issues
In code that hasn't changed since last review
Remove stale version receipt after successful uninstall
Successful uninstall leaves the package's version receipt behind. For example, after installing build 4.2.0.1234 registered as 4.2, uninstalling it here and manually installing an older build also registered as 4.2 makes InstalledVersion report the removed newer build, suppressing its update. Remove the matching receipt after a successful uninstall so later installations cannot inherit that stale version mapping.
Reuse indexed receipt snapshot to avoid quadratic inventory scans
WinInstaller.ListInstalled() calls this once per matched package, and Load() reads and deserializes the entire receipts file each time. With N installed packages and N receipts, each inventory performs N full-file reads/parses plus N linear searches, making receipt resolution quadratic; both installed-package and update listings repeat this work. Load an indexed receipt snapshot once per inventory and reuse it, refreshing it after Record() writes.
Inspect all MSI media rows before marking packages self-contained
FirstColumn drops empty Cabinet values and stops after 16 nonempty values, so mixed embedded/loose-file MSIs and external cabinets beyond that limit can pass this check. Staging copies only the MSI, so these packages are offered but installation lacks their required files. Inspect all relevant Media/File rows, including file compression attributes, before declaring an MSI self-contained.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What it adds
A Windows "Installer packages" manager (
installer), the counterpart of the macOSpkgmanager from #5603. A catalog'spkgFeednow fills it on Windows too: every.exeand.msiin the feed (a folder, a share, or an https folder read through theindex.jsonthatunigetui pkg-index <folder>writes) joins that catalog, and users install, update and uninstall them from the Software Catalog. One feed folder and oneindex.jsoncan serve both platforms..msiis read through msi.dll (Property table, summary Template, product icon) and identified by its UpgradeCode..exegoes through a managed, bounded PE reader (version resource, sections, appended data, icon) that recognizes Inno Setup, NSIS and WiX Burn with their silent switches. A<name>.jsonbeside a file overrides anything, and gives the silent switches of an installer UniGetUI does not recognize (Advanced Installer, InstallShield, Squirrel…)..msimatches through Windows Installer (related products, product code); an.exeby the manifest's uninstall key, a bundle's upgrade code, else its display name with an agreeing publisher. A name match found only by stripping a version must share the package's major version, so a side-by-side product (.NET SDK 8 next to 10) is not taken for it.allowUnsignedPackages), then run silently. An.msielevates unless it is per-user; an.exestarts unelevated and lets the installer elevate itself, unless its manifest says"scope": "machine". Uninstall runs the registered command made silent; Windows Installer products take their scope fromAssignmentType, since per-user MSIs are registered under HKLM too.Shared feed core. The format-agnostic parts of the macOS manager move to
UniGetUI.PackageEngine.Managers.Generic.InstallerFeeds(catalog feed declarations, locations, the cached listing, the index schema, sources, staging,pkg-index); the macOS manager keeps its behaviour. Windows entries are listed under their ownwindowsPackageskey inindex.json, so no Mac, whatever its release, ever offers a Windows installer.pkg-indexruns on both platforms, writes its own list and keeps the other's while its files are unchanged.Docs:
docs/WINDOWS_INSTALLER_FEEDS.mdfor administrators,docs/PKG_FEEDS.md,docs/CLI.md, and a section inAGENTS.md.Testing
UniGetUI.PackageEngine.Testson Windows arm64: full suite passes (2,907, with 9 opt-in end-to-end tests skipped); the feed tests (166) also pass on top of the second commit, and the macOS feed tests pass onnet10.0(120).WinInstallerEndToEndTests(opt-in withUNIGETUI_INSTALLER_E2E=1, or=elevatedfor UAC) install, update and uninstall real MSI and Inno Setup packages built by the tests: folder and web feeds, a tampered file refused, an unsigned one refused, a manifest-driven installer, the app's operation classes, and per-machine installs behind UAC. All passed locally, and nothing was left installed.dotnet format whitespaceanddotnet format style(--verify-no-changes) pass. The win-x64 NativeAOT compile reported no trim or AOT warnings from the new code; its link step could not run on this arm64 machine, so there is no AOT runtime check.pkgbuild) — they still need a run on a Mac; NSIS and WiX Burn installers were not executed (only detected), as the tools are not installed here.Known limits