Skip to content

Troubleshooting and Gotchas

Rod Christiansen edited this page Sep 5, 2026 · 2 revisions

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

Symptoms

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\BootstrapMateVersion) 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.

Known issues

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.

See also

Clone this wiki locally