fix(msi): ship WinUI 3 GUI; pick host-arch makepri.exe#2
Conversation
- Wire Generate-GuiAppFiles.ps1 into the wixproj as a BeforeBuild
target so the 463-file WinUI 3 publish output (BootstrapMate.exe,
Microsoft.UI.Xaml.*, App.xbf, MainWindow.xbf, Assets, locale .mui)
is harvested into a GuiAppFiles ComponentGroup.
- Add ComponentGroupRef GuiAppFiles to the DefaultFeature; drop the
dead single-file BootstrapMateGuiApp component that only shipped
BootstrapMate.exe with no deps.
- Suppress ICE03 — WinUI MUI locales (gd-gb, mi-NZ) and the master
Microsoft.ui.xaml.dll's >255-char locale list trip ICE03 but are
benign at install time.
- Pick makepri.exe host-arch first; previous arm64-first ordering
picked a binary that can't run on x64 hosts ("not a valid
application for this OS platform") and silently fell back to the
cached publish/app contents.
Result: signed MSI now installs 544 files including the GUI, vs 1
(installapplications.exe only) before.
There was a problem hiding this comment.
Pull request overview
This PR updates the WiX-based MSI packaging so the WinUI 3 GUI app (and its runtime dependencies) is included in the installer, and fixes build.ps1 so it selects a host-compatible makepri.exe when generating PRI resources.
Changes:
- Wire a pre-build MSBuild target to generate/compile a harvested
GuiAppFilesComponentGroup from the GUI publish output and reference it from the default feature. - Remove the old single-file GUI component and suppress ICE03 to accommodate WinUI MUI language metadata edge cases.
- Prefer host-architecture
makepri.exeselection to avoid picking an ARM64 tool on x64 hosts.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| installer/Product.wxs | Switches from a single GUI exe component to a harvested GuiAppFiles ComponentGroup referenced by DefaultFeature. |
| installer/BootstrapMate.Installer.wixproj | Adds ICE suppression and a pre-build target to generate/compile GuiAppFiles.wxs; adds a GUI publish-output validation error. |
| build.ps1 | Fixes makepri.exe selection order to prefer host architecture first. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <Target Name="BeforeBuild"> | ||
| <Error Text="BootstrapMate executable not found at $(BinDir)\installapplications.exe. Please run build.ps1 first." | ||
| Condition="!Exists('$(BinDir)\installapplications.exe')" /> | ||
| <Error Text="BootstrapMate GUI app directory not found at $(AppDir). Run build.ps1 to publish the GUI before building the MSI." |
There was a problem hiding this comment.
The error text says the GUI app directory is not found, but the Condition checks for a specific file ($(AppDir)\BootstrapMate.exe). Update the message to reflect what’s actually being validated (or change the Condition to check the directory) so troubleshooting output is accurate.
| <Error Text="BootstrapMate GUI app directory not found at $(AppDir). Run build.ps1 to publish the GUI before building the MSI." | |
| <Error Text="BootstrapMate GUI app executable not found at $(AppDir)\BootstrapMate.exe. Run build.ps1 to publish the GUI before building the MSI." |
| <File Id="BootstrapMateGuiExe" Source="$(var.APP_DIR)\BootstrapMate.exe" Vital="yes" /> | ||
| </Component> | ||
| <!-- GUI Application files (BootstrapMate.exe + WinUI 3 dependencies) are | ||
| harvested from $(APP_DIR) into the GuiAppFiles ComponentGroup by the |
There was a problem hiding this comment.
The comment refers to
| harvested from $(APP_DIR) into the GuiAppFiles ComponentGroup by the | |
| harvested from $(var.APP_DIR) into the GuiAppFiles ComponentGroup by the |
| <Target Name="GenerateGuiAppFiles" BeforeTargets="BeforeBuild"> | ||
| <MakeDir Directories="$(IntermediateOutputPath)" /> | ||
| <Exec Command="pwsh -NoProfile -ExecutionPolicy Bypass -File "$(MSBuildProjectDirectory)\Generate-GuiAppFiles.ps1" -AppDir "$(AppDir)" -OutputPath "$(IntermediateOutputPath)GuiAppFiles.wxs"" /> | ||
| <ItemGroup> | ||
| <Compile Include="$(IntermediateOutputPath)GuiAppFiles.wxs" /> | ||
| </ItemGroup> | ||
| </Target> |
There was a problem hiding this comment.
Generate-GuiAppFiles.ps1 currently generates sequential Component/File IDs (c_00001, f_00001, …) which will shift whenever files are added/removed. Because the script also uses Guid="*", this can churn many component GUIDs across releases and lead to unreliable MSI upgrades (old files orphaned / components treated as new). Prefer deterministic IDs derived from each file’s relative path (e.g., a stable hash) so unchanged files keep the same Component Id (and therefore GUID) between builds.
| <SuppressIces>ICE03</SuppressIces> | ||
| </PropertyGroup> | ||
|
|
||
| <Target Name="GenerateGuiAppFiles" BeforeTargets="BeforeBuild"> |
There was a problem hiding this comment.
GenerateGuiAppFiles runs BeforeBuild, but the missing-GUI check is implemented in the BeforeBuild target. If $(AppDir) doesn’t exist, the script will fail first and the more actionable MSBuild won’t be shown. Consider adding a Condition to the Exec/Target (or moving this target to run after the validation) so the build consistently fails with the intended error message when the GUI publish output is missing.
| <Target Name="GenerateGuiAppFiles" BeforeTargets="BeforeBuild"> | |
| <Target Name="GenerateGuiAppFiles" | |
| BeforeTargets="BeforeBuild" | |
| Condition="Exists('$(AppDir)\BootstrapMate.exe')"> |
- Generate-GuiAppFiles.ps1: derive Component/File IDs from a SHA1 hash of the relative path (truncated to 16 hex chars) instead of a sequential counter. Unchanged files now keep the same Component Id across builds — and therefore the same Guid="*"-derived GUID — so MSI upgrade tracking is stable when WinUI 3 ships a new locale or assets are added/removed. Hash input is lowercased for case-insensitive Windows path equivalence. - BootstrapMate.Installer.wixproj: gate the GenerateGuiAppFiles target on Exists($(AppDir)\BootstrapMate.exe) so the friendlier <Error> in the BeforeBuild target fires when the GUI publish output is missing, rather than the harvester script's stack trace. - BootstrapMate.Installer.wixproj: error text now references the executable path (was: "GUI app directory") to match the Condition's actual check. - Product.wxs: comment now uses $(var.APP_DIR) to match the WiX preprocessor form used elsewhere in this file.
Summary
Generate-GuiAppFiles.ps1into the wixproj as aBeforeBuildtarget so the WinUI 3 publish output is harvested into aGuiAppFilesComponentGroup (463 files —BootstrapMate.exe, Microsoft.UI.Xaml runtime,App.xbf/MainWindow.xbf,Assets/, locale.mui).<ComponentGroupRef Id="GuiAppFiles" />to theDefaultFeature; drop the dead single-fileBootstrapMateGuiAppcomponent that only shippedBootstrapMate.exewith no deps and was conditional onAPP_DIR <> "".gd-gb,mi-NZaren't in WiX's ICE validation table, and the masterMicrosoft.ui.xaml.dll's embedded locale list overflows theFile.Language255-char column. Both are benign at install time.makepri.exehost-arch first inbuild.ps1. The previous arm64-first order picked a binary that can't run on x64 hosts (The specified executable is not a valid application for this OS platform) and silently fell back to cachedpublish/appcontents.Result
Before: signed
BootstrapMate-x64-2026.04.24.1542.msiwas 28 MB and installed onlyinstallapplications.exetoC:\Program Files\BootstrapMate\(1 file).After: signed
BootstrapMate-x64-2026.04.27.1219.msiis 86 MB and installs 544 files includingBootstrapMate.exe,App.xbf,MainWindow.xbf, the fullMicrosoft.UI.Xaml.*runtime,Microsoft.UI.*Windows App SDK, andAssets/.Test plan
dotnet build installer/BootstrapMate.Installer.wixproj -p:Platform=x64succeeds (463-file harvest, 0 warnings, 0 errors).\build.ps1 -Architecture both -Thumbprint <cert>produces signed x64 + arm64 MSI and.intunewinEmilyCarrU Intune Windows Enterprise Certificate(6516E0FE…), DigiCert-timestampedC:\Program Files\BootstrapMate\BootstrapMate.exeis present and signedprovisioning/deploy.ps1 -Appand confirm fleet endpoints land both binaries