-
Notifications
You must be signed in to change notification settings - Fork 1
Deployment
How BootstrapMate gets onto a Windows device and how it gets invoked. This page covers the MSI, the scheduled task the MSI creates, delivery as an Intune Win32 app, and re-running the tool on a device that is already provisioned.
BootstrapMate ships as a per-architecture, per-machine MSI: BootstrapMate-x64-<version>.msi
and BootstrapMate-arm64-<version>.msi. There is no combined package — deploy the one that
matches the device architecture.
The MSI installs into C:\Program Files\BootstrapMate:
| File | What it is |
|---|---|
managedbootstrapinstall.exe |
The CLI. This is what actually runs a manifest. |
BootstrapMate.exe |
The WinUI 3 GUI, plus its Windows App SDK payload. |
[INSTALLDIR] is appended to the system PATH, so managedbootstrapinstall is callable by
name from any elevated shell after a session refresh. A Start Menu shortcut named
BootstrapMate points at the GUI.
The MSI also writes these values under HKLM\SOFTWARE\BootstrapMate: InstallPath,
Version, InstallDate, Architecture and LastRunVersion — all set to install-time
values, not run-time values. Read the detection section before you build a
rule on any of them.
A machine-level fallback setting is written to HKLM\SOFTWARE\BootstrapMate\Settings
→ ManifestUrl, carrying the manifest URL baked into the build. Override it with policy or
your own registry write unless that URL is genuinely yours. See
Preferences for the full precedence chain.
MajorUpgrade is configured with AllowSameVersionUpgrades="no" and AllowDowngrades="no",
so installing an MSI whose version equals the installed one does nothing, and an older MSI
is refused with A newer version of [ProductName] is already installed.
Two custom actions run during a fresh install:
-
RegisterSelfHealTask, sequencedAfter="InstallFiles", creates the scheduled task described below. -
RunBootstrapMate, sequencedBefore="InstallFinalize"withCondition="NOT REMOVE",Execute="deferred",Impersonate="no"andReturn="ignore", shellsschtasks.exe /Run /TN "BootstrapMate Self-Heal".
The install-time run therefore happens through the scheduled task, not by invoking the exe
from the MSI. The comment in installer/Product.wxs records why: a deferred custom action
calling managedbootstrapinstall.exe directly deadlocks, because the child msiexec
processes it spawns for MSI packages wait on the Windows Installer mutex the parent MSI still
holds.
Two consequences follow. The bootstrap run inherits the task's SYSTEM context rather than the
MSI's. And because the custom action starts the task rather than waiting on it, and its return
code is ignored, the MSI can reach InstallFinalize and report success while packages are
still installing.
On uninstall, UnregisterSelfHealTask runs Before="RemoveFiles" under
Condition="REMOVE=\"ALL\"" and deletes the task.
One task is created, named BootstrapMate Self-Heal. The MSI registers it with:
schtasks /Create /TN "BootstrapMate Self-Heal" /TR "\"[INSTALLDIR]managedbootstrapinstall.exe\" --force --silent --no-dialog" /SC DAILY /ST 03:00 /RU SYSTEM /RL HIGHEST /F
So: daily at 03:00 local time, as SYSTEM, at highest run level, with no console output and
no progress dialog. It passes no --url, so the manifest URL is resolved through the normal
precedence chain — policy, then machine settings, then the baked-in default.
--force is documented in --help as deprecated, but the task passes it on every daily run,
and --force still triggers ClearAllCachesAggressive(). Every scheduled run therefore wipes
the BootstrapMate cache directory and the Chocolatey cache directories as well.
Inspect the task with:
schtasks /Query /TN "BootstrapMate Self-Heal" /V /FO LIST
build.ps1 produces BootstrapMate-<arch>-<version>.intunewin alongside the MSI. Create one
Win32 app per architecture.
Install command:
msiexec /i "BootstrapMate-x64-<version>.msi" /qn /norestart
Uninstall command — the ProductCode is not declared in the WiX source and WiX generates a new one for every build, so uninstall by referencing the MSI in the package rather than a hard-coded GUID:
msiexec /x "BootstrapMate-x64-<version>.msi" /qn /norestart
Install behaviour is System. The binaries are published self-contained, so there is no .NET runtime prerequisite. Add an architecture requirement rule to each app so the x64 package never lands on an ARM64 device or the reverse.
This is the part that most often goes wrong, because two different registry roots are in play.
-
HKLM\SOFTWARE\BootstrapMateis written by the MSI at install time. ItsVersionandLastRunVersionvalues tell you the package is installed. They say nothing about whether a bootstrap run ever succeeded. -
HKLM\SOFTWARE\Cimian\BootstrapMateis written by a successful run.LastRunVersionunder this key is set once, at the end of a run that completed without an unhandled exception. Per-phase status lives underHKLM\SOFTWARE\Cimian\BootstrapMate\Status\SetupAssistantand...\Status\Userland.
For Win32 app detection — "is the MSI installed" — use the MSI-written key:
HKLM\SOFTWARE\BootstrapMate → Version, string, equals the MSI version
Note the MSI version is in YY.M.d.HHmm form, while the version a run records is
YYYY.MM.DD.HHmm. They are not interchangeable strings.
To answer "did the bootstrap actually run", query the run-written key instead:
Get-ItemProperty 'HKLM:\SOFTWARE\Cimian\BootstrapMate' -Name LastRunVersion
Be aware of what the shipped example scripts in examples/detection-scripts/ really do.
esp-detection.ps1, userland-detection.ps1 and generic-detection.ps1 all read
HKLM:\SOFTWARE\BootstrapMate → LastRunVersion and compare it to a hard-coded
2025.08.30.1300. That value is the one the MSI writes, so these scripts detect the MSI
install, not a completed bootstrap — and because the expected version is hard-coded to a
literal, they return exit 1 on any build other than that one. Their diagnostic output also
reads HKLM:\SOFTWARE\BootstrapMate\Status\* and values named BootstrapStatus,
LastError, InstallationStarted, CompletionTime and PackageArchitecture, none of which
any code in this repo writes. Treat them as a record of an older layout, not as working
detection. See Troubleshooting and Gotchas.
The daily task already re-runs the whole manifest, so on a healthy device convergence is automatic. To force a run now:
schtasks /Run /TN "BootstrapMate Self-Heal"
To run interactively from an elevated console, with output on screen:
managedbootstrapinstall.exe --url https://example.com/bootstrap/manifest.json --verbose
Points to know when re-running:
- Only one instance runs at a time. A second invocation waits on the
Global\BootstrapMate.SingleInstancemutex for up to 30 minutes, loggingAnother BootstrapMate instance is already running - waiting for it to finish, then exits 1. - The cache is cleared at the start of every run, so downloads are always fresh. Re-running does not reuse a previously downloaded package.
- There is no state that marks an item as already done. Every run processes every item in the manifest; idempotence is the packages' responsibility.
-
--silentwithout elevation always fails. The CLI refuses to raise a UAC prompt in silent mode and returns 1. Run elevated, or via the SYSTEM task.
Reinstalling the MSI at the same version will not upgrade, and so will not re-fire the
install-time run. Use schtasks /Run for that.