-
Notifications
You must be signed in to change notification settings - Fork 1
Troubleshooting and Gotchas
Symptom-first diagnosis for a provisioning run that did something you did not expect, plus a list of known issues in the current code that an administrator can plausibly run into. Read Logging and Reporting first if you have not yet found the log.
Everything below assumes you have the newest log to hand. Each run has its own session
directory, C:\ProgramData\ManagedBootstrap\logs\yyyy-MM-dd\HHmmss\, so the search has to
recurse:
Get-ChildItem C:\ProgramData\ManagedBootstrap\logs -Filter *.log -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 1
Nothing ran at all — there is no log for the provisioning window.
The CLI was never invoked. The MSI's install-time run goes through the
BootstrapMate Self-Heal scheduled task, so if that task was not registered, or schtasks /Run
failed, nothing happens and the MSI still reports success — the custom action's return code is
ignored. Check that the task exists and look at its last result:
schtasks /Query /TN "BootstrapMate Self-Heal" /V /FO LIST
Also check whether the MSI installed at all (HKLM\SOFTWARE\BootstrapMate → Version) and
whether an existing install blocked it: with AllowSameVersionUpgrades="no", reinstalling the
same version is a no-op, so it never fires an install-time run.
A run started but exited 1 immediately.
Three common causes, all visible at the top of the log or the console. The process was not
elevated and --silent was passed, which the CLI refuses to resolve because it will not raise
a UAC prompt in silent mode. Or --url was given without a value, which prints
ERROR: --url requires a URL parameter. Or another instance held the single-instance mutex:
look for Another BootstrapMate instance is already running - waiting for it to finish and,
after 30 minutes, Timed out after 30 minutes waiting for another BootstrapMate instance to finish.
The run exited 0 but nothing installed.
Exit code 0 is not a package-health signal, and never has been: a run whose packages all failed
exits 0 exactly like a clean one. What the run does report is the failure everywhere else —
the phase goes to Failed with the failed names in LastError, the log's last lines count
them, LastRunVersion is not written, and the reporting POST carries success: false. Count
the failures in the log:
Select-String 'Failed to install package ' <logfile>
Then check the cache directory. A cached file is deleted after a successful install and kept
after a failed one, so anything left in C:\ProgramData\ManagedBootstrap\cache is a failed
install. If the log has no per-package lines at all, check whether the phase was marked
Skipped — a missing setupassistant or userland root key in the manifest skips that phase
silently.
An MSI failed with 1603.
1603 is treated as retryable. A normal package gets 5 attempts 10 seconds apart; a package
recognised as sbin-installer — its name contains System Binary Installer or
sbin-installer, or its filename contains sbin-installer — gets 10 attempts 15 seconds
apart and retries on any non-zero code. The other retryable codes are 1619, 1620 and 1612.
The log shows each attempt as MSI error 1603, retrying in 10 seconds... (attempt 1/5) and
ends with either MSI installer failed with exit code: {n} or, for a critical package,
CRITICAL: {name} failed with exit code {n} after {a} attempts. System cannot continue without this package.
Diagnose the 1603 itself from the package's own MSI logging; BootstrapMate passes
/i "<path>" /qn /norestart and adds nothing else.
Installs are blocked by another installer.
Exit code 1618 (ERROR_INSTALL_ALREADY_RUNNING) is not treated as a failure. Before each
launch, BootstrapMate waits up to 600 seconds on the Global\_MSIExecute mutex, polling every
2 seconds. A 1618 increments a collision counter, backs off 3 seconds, and does not consume a
retry attempt. Up to 30 collisions are tolerated; beyond that it throws
MSI installer for {name} could not start: another Windows Installer transaction held the system across {n} collisions.
Look for MSI already running (1618) and Windows Installer still busy after 600s in the log.
The usual counterparty during provisioning is another management agent installing at the same
time. If the mutex cannot be opened at all — absent, ACL-denied or abandoned — the wait
returns immediately and treats the system as idle, so a permissions quirk will not block a
bootstrap.
The manifest failed to download.
The run exits 1 and the log carries Error processing manifest: {msg}. If the message is a
403, check for Download failed: Forbidden and for the Debug line
Authorization header withheld for cross-host download: {url}. The AuthorizationHeader is
attached only when the request host matches the manifest URL's host, case-insensitively. That
is deliberate: some public blob endpoints return 403 for a public object that carries an
Authorization header they cannot validate. If your packages live on a different host from
your manifest, they must be reachable without the header. The download timeout is the .NET
default of 100 seconds — the NetworkTimeout setting is not applied.
A signature check failed.
Look for Signature check failed for {file}: {reason} followed by
Refusing to install {file} — {reason}. The reason string distinguishes the two cases. An
untrusted signature can be allowed with AllowUnsigned, which logs
untrusted signature ({detail}) — allowed because AllowUnsigned is set. A publisher mismatch
is always denied, even with AllowUnsigned, and reads
publisher mismatch — found '{p}', expected publisher '{x}'. The publisher comparison is a
case-insensitive substring test against the certificate subject's CN, so an over-narrow
ExpectedPublisher value is the usual cause. Note the gate applies only to msi and exe
items; ps1, nupkg and pkg are not Authenticode-checked, and there is no hash
verification of any kind. Revocation checking is deliberately disabled, so an expired
revocation state will not be the cause. See
Security and Package Verification.
Status or detection reports the wrong thing.
This is the registry-path split. Status is written to HKLM\SOFTWARE\Cimian\BootstrapMate,
but --status, --clear-status, the MSI and the shipped detection scripts all use
HKLM\SOFTWARE\BootstrapMate. Consequences: --status reports a completion version that the
MSI wrote at install time, so it looks complete on a device that has never finished a run;
--clear-status clears nothing real; and Intune detection built on the example scripts
detects the MSI, not the bootstrap. Query the real values directly:
Get-ItemProperty 'HKLM:\SOFTWARE\Cimian\BootstrapMate\Status\SetupAssistant'
Get-ItemProperty 'HKLM:\SOFTWARE\Cimian\BootstrapMate' -Name LastRunVersion
The GUI passed a switch that did nothing.
The GUI builds its command line from the full settings object and emits --headers,
--follow-redirects, --dry-run and --reboot. The CLI's argument switch has no cases for
any of them and no default arm, so they are consumed and discarded without a warning.
--headers is worse: the switch is unrecognised and its value is not consumed either, so the
header string is left in the argument stream to be discarded in turn. A
"dry run" started from the GUI installs packages for real. The GUI also always prepends
--verbose to whatever it runs, which is why a GUI-launched run's log is noisier than a
task-launched one.
No progress dialog appeared.
The progress UI is an external, optional dependency at
C:\Program Files\csharpDialog\dialog.exe. If that file is absent, the constructor logs
csharpdialog not found at {path} - running in headless mode at Debug level and every
subsequent dialog call is a silent no-op. Because it is Debug, you will not see it on the
console without --verbose; it is always in the log file.
A .pkg or .nupkg install failed with no useful detail.
sbin-installer's own stdout and stderr are logged at Debug as sbin-installer stdout: … and
sbin-installer stderr: …, unlike PowerShell, EXE and Chocolatey output, which is written at
INFO/WARN with an [OUTPUT] marker. Read the log file directly, or rerun with --verbose.
The run threw before installing anything.
Every entry in both phases is pre-added to the progress dialog before the first install, and
that loop reads name with a hard property lookup. A manifest entry missing name throws at
that point, so a single malformed entry stops the whole run before any package is touched.
displayname is not a substitute — it is never read.
These are defects and dead configuration in the current code, stated as observable consequences. No fixes or timelines are implied.
The status registry path is split between writer and readers. StatusManager writes to
HKLM\SOFTWARE\Cimian\BootstrapMate\Status\{phase} and HKLM\SOFTWARE\Cimian\BootstrapMate\LastRunVersion.
--status, --clear-status, the MSI's registry component and all three example detection
scripts use HKLM\SOFTWARE\BootstrapMate. Consequences: --status reports the MSI's
install-time version as a completion version — its per-phase block does read the right key, so
the phase lines are correct and the completion block below them is not; --clear-status
deletes a key nothing writes
and a status file path that is not used, leaving the real status intact; the path hints
--status prints are wrong; and detection rules copied from the example scripts report on the
MSI install rather than on a completed run.
Several settings and policies are never read by the CLI. FollowRedirects, Reboot,
DryRun, NetworkTimeout and DialogIcon exist in the settings object and in the ADMX but
no execution path in the CLI consumes them. SilentMode, VerboseMode and EnableDialog
likewise: the CLI reads the --silent, --verbose and --no-dialog arguments, not the
stored values. Consequences: a configured dry run installs packages; a configured
reboot-after-completion never reboots; NetworkTimeout does not change the 100-second HTTP
default; a configured dialog icon is not shown; and setting SilentMode or VerboseMode by
policy has no effect on a run that does not also pass the flag. The only place
FollowRedirects and NetworkTimeout take effect anywhere in the product is the GUI's
"Preview manifest" fetch.
Both policy template files are malformed XML. resources/BootstrapMate.admx has an
unclosed <category name="Reporting"> and an unclosed <policy name="ReportingHeader">;
resources/en-US/BootstrapMate.adml has an unclosed <presentation id="ReportingHeader">.
Consequence: the templates fail to parse, so ADMX ingestion into Intune and loading from
C:\Windows\PolicyDefinitions both fail. The underlying values under
HKLM\SOFTWARE\Policies\BootstrapMate still work when written directly by OMA-URI or by a
plain registry write.
The exit code does not reflect package failures. Each package's failure is caught, logged
as Failed to install package {name}: {msg}, and the loop continues by design — that part is
intended, and the failures are now carried out of the loop: the phase is marked Failed,
LastRunVersion is not written, and the run summary reports success: false. The process
exit code was not changed. Consequence: a run in which every package failed still exits 0, so
orchestration that keys on the exit code alone still reads it as a success. Key on
LastRunVersion or on the phase Stage instead.
session.json never records the run's outcome. The method that rewrites it at the end of
a run is not called from anywhere. Consequence: session.json in every session directory,
including runs that finished hours ago, reads "status": "running" with no end_time and with
events, errors and warnings all zero. Read events.jsonl for the run's real content.
One MSI failure path returns without throwing. If Process.Start returns null for
msiexec.exe, the retry loop logs
Failed to start msiexec.exe (Process.Start returned null); backing off and retrying and
consumes an attempt. If the loop then exhausts its attempts through that path, RunMsiInstaller
returns normally rather than throwing. Consequence: in that narrow case the package is
recorded and reported as installed successfully when it was never installed.
The GUI log viewers do not colourise warnings. Both the Run and Logs views colourise by
substring, matching [ERROR]/[X], [WARNING]/[!], [SUCCESS]/[+] and
[DEBUG]/[DBG]. The file format writes the level as WARN. Consequence: a warning line in
a log file renders as an ordinary default-coloured line in both GUI viewers, so warnings are
easy to miss there. They are visible on the console and in the raw file.
-V prints the version only as the first argument. Uppercase -V is handled before
anything else, but only when it is args[0]. Anywhere else it is lowercased by the main
argument switch and matches the verbose case. Consequence: managedbootstrapinstall.exe --silent -V
does not print a version; it enables verbose output and continues.
--silent without elevation always fails. The elevation helper refuses to relaunch in
silent mode, logging Running in silent mode but not elevated - cannot show UAC prompt.
Consequence: an unelevated --silent invocation always returns 1 without doing anything.
--reset-chocolatey and the unelevated startup prompt read from stdin. Both call
Console.ReadLine(). --reset-chocolatey prompts unconditionally, with no silent or
non-interactive bypass despite a comment promising one. The elevation prompt is skipped under
--silent, so it only bites an unelevated non-interactive run that did not pass the flag.
Consequence: --reset-chocolatey blocks indefinitely under a scheduled task or any other
non-interactive host.
The daily task passes a flag its own help text calls deprecated. The self-heal task's
command line includes --force, and --force still triggers the aggressive cache clear.
Consequence: every 03:00 run deletes the BootstrapMate cache directory and the Chocolatey
temp, lib-bad, .chocolatey and log directories, and runs choco cache clear. Note that
.chocolatey is not a cache — it is where Chocolatey records what it has installed — so the
nightly run also discards that record.
Chocolatey repair rewrites the machine PATH. When the Chocolatey directory exists but
choco.exe --version fails, BootstrapMate runs an escalating cleanup that deletes
C:\ProgramData\chocolatey, clears the ChocolateyInstall variable in the machine, user and
process scopes, and rewrites the machine PATH with every entry containing chocolatey
removed. Consequence: a machine-wide, destructive mutation triggered by a heuristic. The log
line Broken Chocolatey detected precedes it.
The shipped example manifest is not runnable as written. The first entry in
examples/bootstrapmate.json carries displayname but no name, and it also carries a
"hash" field. Consequences: the entry throws during the pre-install dialog population, so
the run fails before installing anything; and the hash field is never read — no hash
verification exists in the tool.
appsettings.json is not read. The file ships alongside the CLI and declares a working
directory, a service name, retry and timeout values and condition expressions. No
configuration provider reads it and no project copies it to the output. Consequence: editing
it changes nothing.
Directory.Build.props describes a different product. MSBuild does import it, but every
value it sets is either overridden by the individual projects or simply wrong for them: it
declares net8.0-windows where all three projects target net10.0-windows, an
AssemblyCompany of Your Organization, and a fixed AssemblyVersion of 1.0.0.0 where the
CLI builds a timestamp version. Consequence: read it as a description of the build and you
will be wrong about the framework, the company and the version.
Chocolatey installs run with --ignore-checksums. The switch is hardcoded on every
Chocolatey install and upgrade. Consequence: combined with the absence of hash verification and
the fact that nupkg items are not Authenticode-gated, nothing verifies a Chocolatey payload's
integrity at any layer.
There is no Windows Service. Despite the hosting and service-controller packages being
referenced, and the README describing a service install, no service registration code exists.
Consequence: sc query will not find a BootstrapMate service; recurrence comes from the
BootstrapMate Self-Heal scheduled task alone.