Skip to content

Fix package download timeout for slow links - #15

Closed
bryanwong1 wants to merge 109 commits into
bootstrapmate:mainfrom
bryanwong1:fix/package-download-idle-timeout
Closed

Fix package download timeout for slow links#15
bryanwong1 wants to merge 109 commits into
bootstrapmate:mainfrom
bryanwong1:fix/package-download-idle-timeout

Conversation

@bryanwong1

Copy link
Copy Markdown

No description provided.

rodchristiansen and others added 30 commits August 30, 2025 16:02
- Complete Windows OOBE/ESP provisioning solution
- Registry-based status tracking with LastRunVersion
- Multi-architecture support (x64, ARM64)
- Comprehensive logging system
- Intune Win32 app integration examples
- PowerShell detection scripts for deployment phases
- Code signing build pipeline
- Build script now auto-updates version to current timestamp (YYYY.MM.DD.HHMM)
- Auto-cleanup package cache after successful completion
- Auto-cleanup old cache files (>7 days) on startup
- Enhanced cache management for better disk space usage
- Add immediate clear error message when not running as admin
- Explain specific reasons why admin privileges are needed
- Ask user permission before attempting elevation
- Provide clearer guidance on how to run as admin
- Better user experience with interactive prompts
- Code signing is now REQUIRED by default (breaking change)
- Build fails with clear error if no certificate is found
- Add -AllowUnsigned flag for development builds only
- Clear warnings for unsigned builds that they are NOT for production
- Enhanced build output shows signing status with visual indicators
- Comprehensive error messages guide users to solutions

This ensures BootstrapMate executables are always signed for enterprise deployment,
improving security and meeting organizational compliance requirements.
BREAKING CHANGE: Code signing is now REQUIRED by default for production builds

Changes:
- Remove -Sign parameter (signing is now default behavior)
- Add explicit -AllowUnsigned flag for development builds only
- Provide clear error messages when certificates are not found
- Fix PSScriptAnalyzer warning: rename Sign-Executable to Invoke-ExecutableSigning
- Enhanced build output with clear signing status indicators
- Better security warnings for unsigned builds

Production builds now fail fast if no signing certificate is available,
preventing accidentally deploying unsigned executables to enterprise environments.
- Handle --version before admin privilege check (no longer requires elevation)
- Output only the version number (e.g., '2025.08.30.1757') instead of full banner
- Remove verbose headers, admin messages, and additional build information
- Faster and cleaner for automation and scripting scenarios
- Maintains both --version and -v flag support
…taller setup

- Create .env.example for environment variable configuration
- Update .gitignore to exclude sensitive files and build outputs
- Modify Directory.Build.props for organization branding
- Update Program.cs with new version format
- Enhance README.md with configuration instructions and deployment options
- Create assets README for icon requirements
- Implement MSI and IntuneWin packaging in build.ps1
- Add WiX project files for installer configuration
…me and add silent mode support for console output
…t mode handling in installation process; modify registry paths for Cimian compatibility; improve Product.wxs for silent installation and detection.
- Remove hardcoded certificate name from build.ps1
- Remove hardcoded bootstrap URL from Product.wxs
- Add mandatory environment variable validation
- Create .env.template with placeholder values
- Add security documentation (SECURITY.md)
- Remove sensitive example log files
- Add incident response documentation

BREAKING CHANGE: Environment variables now required:
- ENTERPRISE_CERT_CN (certificate discovery)
- BOOTSTRAP_MANIFEST_URL (MSI build parameter)

This commit removes all hardcoded organizational information
to prevent accidental exposure in public repositories.
The backup tag backup-before-history-cleanup-20250907-225647 contained
the original commits with sensitive information. Deleted both locally
and remotely to ensure complete elimination of sensitive data.

No recovery path remains - this is intentional for security.
…installer to handle bootstrap URL conditionally
…ate right away as a custom action *and* we're installing a scheduled task to run it in 2 minutes
…hecks for IntuneWinAppUtil.exe and enhanced error handling
rodchristiansen and others added 28 commits March 12, 2026 15:07
Clarified description of the tool and expanded OOBE/ESP acronyms.
MSI install dispatch now checks for CIMIAN_PKG_BUILD_INFO marker to
identify cimipkg-built packages and routes them through sbin-installer
for native script execution. Third-party MSIs (e.g. sbin-installer
itself) always go through msiexec.exe directly.
Reads AuthorizationHeader from CSP policy/registry and attaches it
to manifest and package download requests. Token is delivered via
Intune BootstrapMatePrefs profile. No-op when empty.
…ootstrapmate#1)

* fix(build): reliable signtool resolution on clean Windows SDK hosts

Test-SignTool was looking for the Windows SDK under
  "$env:ProgramFiles(x86)\Windows Kits\10\bin"
which is a PowerShell parse trap: parens cannot terminate an interpolated
env var name, so the string expanded to
  "C:\Program Files(x86)\Windows Kits\10\bin"
(no space). Test-Path short-circuited the whole search and we fell
through to the PATH-only lookup. On boxes that ran the deploy pipeline
from a pwsh session without the Windows SDK on PATH (Intune runners,
fresh build VMs) Test-SignTool silently left PATH untouched, and every
subsequent `& signtool.exe …` call failed to launch. The symptom was
"App build failed" on both x64 and arm64 during the BootstrapMate.App
publish + sign pass, even though MSI signing (which happened earlier
in the same session, before an inherited PATH expired) succeeded.

Fix:
- Resolve signtool via [Environment]::GetFolderPath, matching the
  CimianTools pattern. Handles the (x86) path on every locale and
  avoids the interpolation trap.
- Also check the Program Files root (64-bit SDK installs) and the
  KitsRoot10 registry key as fallbacks.
- Prefer x64 > arm64 > x86 and sort by SDK version descending so we
  pick the newest SDK when multiple are installed.
- Cache the resolved absolute path on $script:SignToolPath. Expose
  Get-SignToolPath so every call site uses the absolute path and
  stops depending on PATH being set correctly at child-process
  spawn time.
- Still prepend the signtool directory to $env:Path as a belt-and-
  braces safety for any child process that calls signtool.exe by
  bare name.

Updated every call site that used `& signtool.exe …` or passed the
bare filename to sudo so they all go through the resolved path:
  * primary sign + fallback TSA loop
  * legacy timestamp append
  * signature verify
  * sudo-elevated sign
  * sudo-signed verify

No functional change when signtool is already on PATH.

* fix(build): accept any PATH-resolved signtool, fail loudly when missing

Addresses Copilot review feedback on the signtool resolver.

PATH regression. Get-SignToolPath rejected a PATH hit unless its
location matched \x64\, which silently broke hosts where signtool was
on PATH but lived in an arm64- or x86-only SDK install (or a
non-default location). Restore the previous behaviour: take any PATH
hit, but only short-circuit when it's the preferred x64 build. If the
hit isn't x64, run the SDK search anyway so we can upgrade to a better
candidate, and fall back to the PATH hit if nothing better exists.

Null-result handling. Several signing call sites called
Get-SignToolPath inline as the executable name (`& (Get-SignToolPath)
...` or interpolated into $sudoArgs). When resolution failed they
produced a confusing "&: cannot be invoked on null" error instead of
something actionable. Add Resolve-SignToolPath that throws a clear
"install the Windows SDK Signing Tools" message, and switch every
signing call site to it.

Sudo-branch dedup. The sudo-elevated signing branch was calling
Get-SignToolPath three times — once for the log line, once into
$sudoArgs, once for verify. Resolve once into $signtool and reuse
across all three so the cache can never be invalidated mid-run.
* fix(msi): ship WinUI 3 GUI; pick host-arch makepri.exe

- 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.

* fix(msi): address PR copilot feedback

- 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.
- Title: "BootstrapMate for Windows vYYYY.MM.DD.HHMM"
- Body: build info (dotnet/OS/run URL) + auto-generated changelog
  from gh api releases/generate-notes + signtool/Intune deployment block
  (matches Cimian's release notes shape)
- Set TZ=America/Vancouver in both ci.yml and release.yml so any
  timestamp emitted by the runner matches the tag's local time
- Switch release job to windows-latest so we can run the same
  PowerShell-based notes generator as Cimian
…mate#3)

* fix: add Start Menu shortcut for GUI; KB display for sub-MB downloads

- Product.wxs: add ProgramMenuFolder\BootstrapMate\BootstrapMate.lnk
  targeting [INSTALLDIR]BootstrapMate.exe (harvested GUI binary)
- Program.cs: switch download size readout to KB when <1MB so small
  config MSIs no longer report 'Downloaded: 0.0 MB'

* fix(wix): switch StartMenuShortcut KeyPath to HKCU (ICE38/43/57)
Remove the 'by @user' suffixes and the New Contributors section that the
generate-notes API injects, so release notes stay attribution-free.
…tstrapmate#5)

* Verify installer Authenticode signatures before running elevated

DownloadAndInstallPackage handed any downloaded MSI/EXE straight to
msiexec/the executable, running it elevated with no provenance check. The
download only proves where the bytes came from, not who produced them: if
the manifest or its host is compromised, an attacker-supplied installer
runs as an elevated/SYSTEM process.

Add SignatureVerifier (WinVerifyTrust + signer-certificate extraction) and
gate InstallPackage on it for msi/exe items: the file must carry a
signature that chains to a trusted root and, when configured, match an
expected publisher. Unsigned/untrusted installers are refused unless
AllowUnsigned is set; a publisher mismatch is never bypassed.

Configurable via Intune CSP / Group Policy (new ADMX Security category:
VerifyPackageSignatures, ExpectedPublisher, AllowUnsigned), the
machine/user registry, and per-item manifest overrides (expectedPublisher,
allowUnsigned). Defaults are secure. Mirrors the macOS SignatureVerifier.

* Address review: ToLowerInvariant for type switch; document revocation choice

- Use ToLowerInvariant() for the package-type switch to avoid
  locale-dependent comparison.
- Add a comment explaining why WinVerifyTrust revocation checking is left
  off (OOBE/ESP often runs without reliable networking); the trusted-root
  chain is still verified, and the knob to enable WHOLECHAIN is noted.
* Rename policy detector and references to Management

Unify the management-detection vocabulary with the macOS side (where
MDMDetector becomes ManagementDetector), so both platforms share one model:

- PolicyDetector -> ManagementDetector (file + type)
- IsManagedByPolicy / IsPolicyManaged -> IsManaged
- ConfigSource.Policy -> ConfigSource.Management
- LoadFromPolicy -> LoadFromManagement, LoadPolicyAndUserSettings ->
  LoadManagementAndUserSettings
- comment/section references to 'Policy' state -> 'Management'

Kept as-is (Windows Group Policy platform terms, not our abstraction):
the HKLM\SOFTWARE\Policies\BootstrapMate registry path and its
PolicyRegistryPath constant, the ADMX/ADML templates, and 'Group Policy' /
'Intune CSP' wording that names the actual OS mechanism.

Pure rename — no behavioural change.

* Address review: correct SaveUserSettings persistence docs

SaveUserSettings writes to HKCU (no elevation), but the doc comments said
HKLM + 'Requires elevation' and still referred to 'policy-managed'. Update
both ConfigManager and PrefsViewModel comments to match the actual
behaviour: user-hive (HKCU) writes, skipping managed keys.

* Fix merge: use renamed 'management' detector var for signature policy reads

The signature PR added VerifyPackageSignatures/ExpectedPublisher/AllowUnsigned
reads in LoadFromPolicy using the 'policy' variable; this branch renamed that
method to LoadFromManagement with a 'management' variable. The textual merge
left the new reads referencing the old name — update them.
* Post a vendor-neutral run summary on completion

BootstrapMate status was local-only (registry + status.json + logs), so
checking whether a PC provisioned cleanly meant an RDP/registry expedition.
Add an optional reporting POST: when ReportingUrl is configured, a JSON run
summary is sent on completion (success and failure paths).

The payload is backend-agnostic and emits the SAME schema as the macOS
client, so one fleet view covers both platforms. It includes runId,
version, success, start/end/duration, architecture, hostname, serial
number, manifest URL, and per-phase outcomes (reusing StatusManager data).
The POST is best-effort: failures are logged and never block or fail the
run.

Configurable via Intune CSP / Group Policy (new ADMX Reporting category:
ReportingUrl, ReportingHeader) and the machine/user registry.

* Address review: report run's actual manifest URL; fix arch + docs

- Report the manifest URL actually used for the run (passed from
  ProcessManifest, which honors --url) rather than ConfigManager's value,
  which can differ.
- Map architecture explicitly (ARM64/X64/X86, else uppercased name) so x86
  is no longer mislabeled as X64.
- Lower the POST timeout to 15s and correct README/ADML wording: the report
  is bounded by a short timeout (it does not 'never block'), but never
  fails the run.
- actions/checkout v4 → v6
- actions/cache v4 → v5
- actions/setup-dotnet v4 → v5
- actions/setup-node v4 → v6
- actions/setup-go v4/v5 → v6
- actions/upload-artifact v4 → v7
- actions/download-artifact v4 → v8
- golangci/golangci-lint-action v2 → v9
- hashicorp/setup-terraform v2 → v3
- dorny/paths-filter v2 → v4
- softprops/action-gh-release v1 → v2
- actions/configure-pages v4 → v5
- actions/upload-pages-artifact v3 → v4
- Replace deprecated actions/create-release@v1 with softprops/action-gh-release@v2
- Remove FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env vars (Node24 is now the runner default)
* Rename CLI executable installapplications.exe -> managedbootstrapinstall.exe

- csproj AssemblyName drives the new exe name
- BootstrapMateConstants.CliExecutableName, Program.cs usage/help text
- build.ps1 published-exe paths
- WiX: File Source, RemoveFile, self-heal scheduled task command, wixproj existence check; adds a RemoveFile sweeping the legacy installapplications.exe on upgrade (hard cut)
- README usage + CI/release workflow steps (upstream credit links to macadmins/rodchristiansen installapplications left intact)

Registry detection (HKLM\SOFTWARE\BootstrapMate\Version) is unchanged, so no Intune detection churn. Must release before the Cimian bootstrap pipeline change that repacks this MSI.

* Address Copilot review: correct README publish path + drop redundant RemoveFile

- README Quick Start now points at publish\executables\x64 (the layout build.ps1 actually produces), not publish\x64
- Product.wxs: remove the RemoveFile targeting the component's own managedbootstrapinstall.exe (the File table removes it automatically); keep only the legacy installapplications.exe sweep
…strapmate#9)

* installer: wait on _MSIExecute mutex instead of failing on 1618

* installer: enforce idle-wait timeout, handle Process.Start null, backoff on 1618 (review)
…Info (bootstrapmate#12)

Two BootstrapMate sessions running at once (an MDM-triggered run racing an
interactive one) share the cache directory and the Windows Installer mutex,
so they corrupt each other: 1618s, cache files deleted mid-install, "file
in use" failures. Add a Global\BootstrapMate.SingleInstance mutex - the
second session waits (up to 30 minutes) for the first to finish, then runs
normally, so concurrency becomes a queue instead of an error class.

Also log "CRITICAL PACKAGE ... aggressive retry strategy" at Info: it
describes normal behavior for critical packages, not a problem.
…mate#13)

The CSP-delivered AuthorizationHeader authenticates against the manifest
server, but it was attached to every HTTP request. Azure blob storage
returns 403 Forbidden for public-blob requests carrying an Authorization
header it cannot validate, so any device with the policy failed every
package download instantly ("Download failed: Forbidden", DFS-CAD-03
2026-07-21) - and the org token leaked to whatever hosts package URLs
point at.

Record the host of the manifest URL actually used this run and attach the
header only to requests for that host. Cross-host downloads (the blob
endpoint) now go out clean, matching what a browser sends. Also give the
package download client the same BootstrapMate/<version> User-Agent the
manifest client already sends.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants