Skip to content

feat(winpe): add boot image customization suite with windowless psbootstrapper boot - #247

Draft
freedbygrace wants to merge 55 commits into
foundry-osd:mainfrom
Grace-Solutions:feat/winpe-psbootstrapper-unattend-progress
Draft

feat(winpe): add boot image customization suite with windowless psbootstrapper boot#247
freedbygrace wants to merge 55 commits into
foundry-osd:mainfrom
Grace-Solutions:feat/winpe-psbootstrapper-unattend-progress

Conversation

@freedbygrace

@freedbygrace freedbygrace commented Jul 13, 2026

Copy link
Copy Markdown

Summary

Adds a WinPE/WinRE boot image customization suite to Foundry OSD: a new Expert Boot Image page (Settings, Drivers, Optional Components, PowerShell, Modules, Additional Folders), a windowless boot path via psbootstrapper + winpeshl.ini, and ${VARIABLE} expansion for deployed computer names.

Why

Boot image content (optional components, drivers, PowerShell, modules, extra files) previously had to be hand-tuned or wasn't configurable at all, drivers lived in a confusing place on the General page, and booting through cmd /k startnet.cmd flashed a console window before the Foundry UI appeared.

Changes

Boot process

  • Bundle psbootstrapper.exe (x64/arm64) and copy it into the image's System32.
  • Launch the Foundry bootstrap from the auto-discovered X:\Unattend.xml (hidden), instead of a PowerShell line in startnet.cmd.
  • Boot via winpeshl.ini (launching only wpeinit) so WinPE no longer runs cmd /k startnet.cmdremoves the console window that flashed at boot. wpeinit still processes the unattend (Display / EnableNetwork / EnableFirewall + the bootstrap launch). startnet.cmd is kept as a wpeinit-only fallback.
  • Unattend commands are emitted as CDATA so quotes and %WINDIR% aren't XML-escaped.
  • Optional, debug-gated minimized troubleshooting console (off by default).

Boot Image page (new, Expert)

  • In-page section rail so the page doesn't grow vertically.
  • Settings — firewall (on by default), keep a copy of boot.wim next to the ISO, troubleshooting console.
  • Drivers — moved off the General page. Vendor packs (Dell/HP) as a checkbox grid with description tooltips, plus multiple driver folders that can each be enabled/disabled via checkbox or removed. Folder paths are clickable (open in Explorer).
  • Optional Components — scanned live from the installed ADK, wrapping checkbox grid with description tooltips, recommended defaults pre-checked.
  • PowerShell — optional PowerShell 7 integration; latest 10 stable releases, full archive download link + copy-to-clipboard.
  • Modules — PowerShell Gallery search (wildcard-tolerant) with a per-result version selector (latest by default), plus local Save-Module folders. Selected Gallery/local modules are shown separately; Gallery modules link to their version page.
  • Additional Folders — each folder has a boot-image-relative destination (\ = image root, \Windows, …), copied recursively.

Driver injection resilience

  • DISM /Add-Driver exit codes 50 and 3010 are treated as success (50 is returned when a recursive folder contains drivers that don't apply to WinPE while the applicable ones still inject).
  • New "continue on driver error" option (on by default) skips a failing package with a warning instead of failing media creation.
  • Progress reports which folder each package is injected from.

Optional component integration

  • WinPE-SecureBootCmdlets and WinPE-SecureStartup are always integrated last, in that order.
  • Per-component "Optional component X of N" progress.
  • Existing idempotency preserved (already-installed components are skipped).
  • Correct language cabs are still applied automatically for the selected WinPE language.

Computer name variables

  • Machine-name prefixes support ${SERIALNUMBER} (plus ${MODEL}, ${MANUFACTURER}, ${PRODUCT}).
  • Expanded at deployment time from the destination machine's hardware, then normalized/truncated to the 15-character NetBIOS limit.
  • A name may be a variable on its own (no literal prefix or suffix required).

Also

  • Two-level build progress ("Task X of N" + "Driver package / Optional component X of N").
  • PowerShell modules install to %WINDIR%\System32\WindowsPowerShell\v1.0\Modules\<Name>\<Version>.
  • All settings persist in the Foundry configuration document; low-cardinality telemetry added (no paths or names).
  • Localized across all 38 cultures (English source text pending translation).

Testing

  • dotnet build .\src\Foundry.slnx — 0 errors
  • Unit tests: Core 343, Deploy 313, Connect 88, Localization 54, Telemetry 31 — all pass
  • Manual testing completed (ISO build + Boot Image page exercised in the desktop app)

Notes

  • Breaking changes: none for consumers. The config document schema is bumped (10 → 11); older documents load with defaults. A legacy single custom-driver directory is migrated into the new driver folder list on first use.
  • Related issues: n/a
  • Screenshots: see below.

Screenshots

1. New Boot Image section — Settings (firewall, keep boot.wim, troubleshooting console)
OSDFoundry-Updates-00004

2. Optional Components — dynamic ADK detection with sane defaults and safe ordering
OSDFoundry-Updates-00003

3. PowerShell 7 — optional inclusion with version selection and download link
OSDFoundry-Updates-00002

4. Modules — PowerShell Gallery search and local Save-Module folders
OSDFoundry-Updates-00001

5. Customization — computer-name variables (${SERIALNUMBER})
OSDFoundry-Updates-00005

freedbygrace and others added 30 commits July 7, 2026 16:59
Add per-architecture psbootstrapper.exe under Assets/PSBootstrapper (mirroring
the bundled 7-Zip layout) and copy it to build output via a Content item so it
ships in the MSI. Expose GetPSBootstrapperSourceExecutablePath on the embedded
asset service and add the arch->folder helper.

Fix .gitignore so the blanket x64/ and arm64/ rules do not silently exclude the
committed binaries (add keep-rules; also correct the stale 7-Zip keep path), and
ignore the repo-root /Generated/ scratch folder. The real x64/arm64 binaries are
supplied separately (see Assets/PSBootstrapper/README.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy psbootstrapper.exe into the boot image System32 and write X:\Unattend.xml
(auto-discovered by bare wpeinit) with a windowsPE-pass Microsoft-Windows-Setup
component. A RunSynchronous command launches FoundryBootstrap.ps1 hidden via
psbootstrapper.exe (no flashing PowerShell window); a RunAsynchronous command
opens a minimized, alt-tab-able troubleshooting console without blocking the
bootstrap. Command paths use %WINDIR% environment variables.

startnet.cmd now contains only wpeinit; any prior PowerShell/psbootstrapper
launch line is stripped for idempotent re-provisioning. This covers both plain
WinPE and WinRE-Wi-Fi images since both mount the same boot.wim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend WinPeMountedImageCustomizationProgress with TaskIndex/TaskCount and
ItemIndex/ItemCount so the customization pipeline reports an outer "Task X of N"
alongside inner action progress. Driver injection now runs one package at a time
to report "Driver package X of N" while DISM per-package percent continues to
flow through the detail channel.

Add the TaskProgressFormat and DriverPackageProgressFormat resource keys across
all locales (English pending translation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pass the architecture-matched psbootstrapper.exe source path into asset
provisioning, and format customization progress as "Task X of N: <status>" with
a "Driver package X of N" detail line when task/item counts are present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a catalog service that scans the ADK WinPE_OCs folder for the available
neutral WinPE-* optional component cabs and flags Foundry's recommended defaults
(PowerShell, WMI, scripting, storage, networking). Extract the shared WinPE_OCs
path resolver and the default component list into reusable helpers.

Refactor the internationalization service to add the optional components named in
its options instead of a hard-coded list; when none are supplied it falls back to
the recommended defaults, preserving current behavior. This is the engine behind
the upcoming Expert Mode optional-components selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Query the PowerShell GitHub releases API for the most recent stable
(non-prerelease) versions that publish a ZIP asset for the target architecture,
returned newest first. This backs the Expert Mode "integrate PowerShell 7"
version picker (latest 3 with fallback to latest). HTTP client is injectable for
testing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the x64 and arm64 psbootstrapper.exe binaries that the boot asset
provisioning copies into System32. Kept via the .gitignore arch-folder rules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add IncludeTroubleshootingConsole to the boot asset provisioning options. The
minimized, interactive PowerShell RunAsynchronous console is now emitted into
Unattend.xml only when debug mode is enabled; by default it is omitted so the
boot image cannot be trivially tampered with.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Download the selected PowerShell 7 release (non-fatally falling back to the
latest on failure) with a version-keyed cache, extract it into
Program Files\PowerShell\7 in the image, and append the install path to Path and
PSModulePath in the offline SYSTEM hive via reg.exe. Copy the host ICU DLL on a
best-effort basis so pwsh 7.5+ can start under WinPE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…omization

Flow the selected WinPE optional components and PowerShell 7 settings from
workspace preparation into the mounted image customization pipeline. Optional
components are passed to the internationalization step; PowerShell 7 runs as a
new numbered customization task (with progress) after language/components when
enabled. Defaults keep the current behavior until the UI populates them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Always emit a Display block (1280x720, 32-bit, 60 Hz) and EnableNetwork in the
windowsPE component, and add an EnableFirewall element controlled by a new
EnableFirewall provisioning option that defaults to on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an AdditionalRootFolderSourcePaths provisioning option. Each selected
folder's contents are laid over the boot image root, preserving the destination
structure the folder contains, so callers can place files and folders exactly
where they want them in the image (validated to stay inside the image root).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a KeepBootWimCopy option to ISO media creation. When enabled, the customized
boot.wim is copied into the ISO output folder as <iso-name>.wim after the ISO is
finalized.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a PowerShell Gallery search service (OData v2) and a module provisioning
service that integrates selected modules into the image's
Program Files\WindowsPowerShell\Modules folder. Gallery modules are downloaded
as .nupkg (cached), extracted with package metadata stripped, and placed under
Name\Version; local modules are copied from a folder. Per-module progress is
reported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thread PowerShell module settings from workspace preparation into the mounted
image customization pipeline and run module provisioning as a new numbered task
(with progress) after PowerShell 7 when modules are selected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add WinPeBootImageContentSettings to GeneralSettings to persist the expert
boot media choices: optional components, PowerShell 7 integration, extra
PowerShell modules, additional root folder overlays, and the firewall,
troubleshooting-console, and keep-WIM toggles. Bump the Foundry configuration
schema version so downgraded documents are flagged for update.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the persisted boot image content choices through the media pipeline:
carry optional components, PowerShell 7, extra modules, root folder overlays,
and the firewall, troubleshooting-console, and keep-WIM toggles in
MediaPreflightOptions and apply them when preparing the workspace, provisioning
boot assets, and creating the ISO. Resolve the selected PowerShell 7 release
(with latest-stable fallback) at build time via the release service, and
register the release, Gallery search, and optional component catalog services.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Boot Image page under the Expert navigation section that surfaces the
new boot media options: firewall, keep-boot.wim, and troubleshooting-console
toggles; a WinPE optional component checklist scanned from the ADK with
recommended defaults pre-checked; PowerShell 7 integration with a version
dropdown; PowerShell module selection via Gallery search or a local folder;
and additional root folder overlays. Selections persist through the Foundry
configuration document and flow into media creation. Register the page view
model and the release, Gallery search, and optional component catalog services.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add navigation, page, and control strings for the expert Boot Image page across
all supported cultures. Non-English cultures carry the English source text as a
placeholder pending translation; resource key and placeholder parity is
preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add low-cardinality boot image content properties to the boot media finished
telemetry event: firewall, troubleshooting-console, keep-WIM, and PowerShell 7
enablement plus optional component, module, and root folder counts. No paths,
module names, or other sensitive configuration values are included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the per-row add/remove icon glyph buttons on the Boot Image page with
localized text buttons, matching the Add/Remove/Import text-button idiom used
across the Autopilot and Network pages. Introduce shared Common.Add and
Common.Remove strings and lightweight item view models that carry the localized
action label for module search results, selected modules, and root folders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ell v1.0

Change the boot image module destination to
%WINDIR%\System32\WindowsPowerShell\v1.0\Modules\ModuleName\ModuleVersion, which
is on the default WinPE PSModulePath and matches the ModuleName\Version layout
produced by Save-Module, for both Gallery and local modules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ress

Integrate WinPE-SecureBootCmdlets and WinPE-SecureStartup last (in that order)
so components they depend on are already present. Report optional component
integration as "Optional component X of N" inner progress, mirroring the driver
package progress, by carrying an item category through the customization
pipeline so the UI labels each item kind correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add GetVersionsAsync to return a module's published stable versions newest-first
(latest first) so the UI can offer a version selector per search result, and
strip wildcard characters from search terms so inputs like "Az*" resolve to the
underlying token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… picker

Split the Boot Image page into an in-page left section rail (Settings, Optional
Components, PowerShell, Modules, Additional Folders) so it no longer grows
vertically. Lay out optional components in a wrapping grid (up to four per row).
Redesign the module picker: Gallery and Local are separate vertical sections;
Gallery search results are grouped per module with a version selector (loaded on
demand, latest by default) and an Add action that accumulates into a selected
list; descriptions are trimmed so the action buttons stay visible; local modules
are added by picking the Save-Module version folder (ModuleName\ModuleVersion).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the section rail labels, firewall header, Gallery/Local/selected module
headers, and the "Optional component X of N" progress format across all
supported cultures (English source text pending translation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ComputerNameTemplate to expand $VARIABLE tokens (such as $SERIALNUMBER, plus
$MODEL, $MANUFACTURER, $PRODUCT) in a computer-name prefix and normalize the
result to the 15-character NetBIOS limit. Authoring accepts either a plain name
or a variable template.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Accept variable prefixes when authoring the machine name, and expand them at
deployment time using the detected hardware (serial number, manufacturer,
model, product) before locking and normalizing the target computer name. The
prefix is re-expanded once hardware detection completes so $SERIALNUMBER picks
up the real serial, and the final name is truncated to 15 characters.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show selected Gallery modules and selected local modules in separate labeled
lists divided by a rule, so it is clear which source each module comes from.
Narrow the search result description in favor of the version selector so it is
no longer cut off, and add a per-result expand toggle to reveal the full
description text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aders

Document the supported computer-name variables ($SERIALNUMBER, $MODEL,
$MANUFACTURER, $PRODUCT) in the machine-name prefix guidance and add the
selected Gallery/local module section headers across all cultures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
freedbygrace and others added 5 commits July 8, 2026 20:32
Make the PowerShell 7 download a hyperlink with the URL on hover, add the same
hover to the selected Gallery module link, and place the module search results
and selected Gallery/local module lists on the darkened, bordered backdrop used
elsewhere so they stand out. Driver folders, additional-folder sources, and
local module paths are now clickable links that open the folder in Explorer.
Relabel the additional-folder destination as "Boot Image Relative Destination:".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Let the PowerShell 7 download URL wrap so the full link is visible next to the
copy button instead of being truncated, and increase the spacing inside
additional root folder rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Widen the media-creation progress dialog (override the content dialog max width
and enlarge the content panel and status text) so status text fits on fewer
lines and reads cleaner. Constrain the PowerShell 7 download hyperlink to its
grid column so the URL wraps instead of running under the copy button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DATA

Launch psbootstrapper.exe by name (it lives in System32, which is on PATH) and
quote the script path, and write the RunSynchronous/RunAsynchronous commands as
CDATA so their quotes and %WINDIR% variables are not XML-escaped in Unattend.xml.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Provision winpeshl.ini so WinPE launches wpeinit directly instead of running
cmd /k startnet.cmd, which removes the console window that flashed at boot.
wpeinit still auto-discovers and processes X:\Unattend.xml (Display, network,
firewall, and the RunSynchronous command that launches the Foundry bootstrap),
so the unattend is unchanged; winpeshl.ini only runs wpeinit. startnet.cmd is
kept as a wpeinit-only fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Documentation updates dependencies Dependency version or package management change config Repository or build configuration changes tests Test coverage or test infrastructure change ui User interface or XAML changes project: foundry Changes in the Foundry desktop application project: foundry-deploy Changes in the Foundry.Deploy deployment application project: foundry-core Changes in the Foundry.Core shared business logic library labels Jul 13, 2026
@freedbygrace

Copy link
Copy Markdown
Author

I know that it is a huge PR, sorry for that, I tried to keep the commits focused because I was working on two projects in tandem, but I have tested the entire process and it works a treat, even the inclusion of Powershell 7. I have been doing endpoint management for over 13 years, so all these enhancements came from real world experience. I am glad to connect and discuss my thinking on this. I did use the assistance of Claude Code to help build the code out as I am not a software engineer by training, but I understand at a developer level data structures, code flow, etc etc. I have several examples of Powershell solutions I have done to show my writing of Powershell mnaually at this level. Anyways, I just thought this tool was sooooo cool that with these things, it would be even better! I truly applaud your approach on this so far and how polished it is.

Foundry.Connect started a fixed 10-second countdown as soon as it had
internet access, and the only way out was attaching a debugger. The
countdown is now driven by the generated runtime configuration.

- Add ConnectAutoContinueSettings (enabled + delay, clamped to 0-300s)
  to the Connect network settings, defaulting to the previous behavior.
- Author both values on the Network page and emit them through
  ConnectConfigurationGenerator.
- Read them in MainWindowViewModel instead of the hardcoded constant; a
  delay of 0 continues as soon as connectivity is validated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the project: foundry-connect Changes in the Foundry.Connect WinPE network bootstrap application label Jul 14, 2026
@mchave3 mchave3 linked an issue Jul 14, 2026 that may be closed by this pull request
1 task
@mchave3 mchave3 self-assigned this Jul 14, 2026
@mchave3 mchave3 moved this from New to In Progress in Foundry OSD Development Jul 14, 2026
@mchave3

mchave3 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Hi @freedbygrace , Thank you for the PR and for the time you spent developing this improvement. I’ll review the changes on my side and see which parts would be relevant to keep. I don’t have much time this week, but I’ll try to take a look at it as soon as possible.

mchave3 and others added 2 commits July 14, 2026 13:59
…stom wallpaper

Under winpeshl.ini the shell chain is winpeshl -> wpeinit -> psbootstrapper ->
FoundryBootstrap.ps1. The bootstrap launched Foundry.Deploy detached and then
returned, so WinPE saw the shell finish and tore the session down before Deploy
could show. Foundry.Connect showed because it is launched with -Wait.

- Wait for Foundry.Deploy in FoundryBootstrap.ps1 and surface its exit code.
- Stop writing startnet.cmd and delete the stock file; winpeshl.ini is the shell,
  so startnet.cmd never executes.
- Add a boot image wallpaper setting: the selected JPEG/PNG/BMP is provisioned as
  %WINDIR%\System32\winpe.jpg, converting PNG and BMP to JPEG during the build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@freedbygrace

freedbygrace commented Jul 14, 2026

Copy link
Copy Markdown
Author

Some more changes incoming because I noticed that the Foundry bootstrap.ps1 script doesn't wait on the Foundry.Deploy execution and since the change over to winpeshl.ini to avoid the cmd window, launching the foundry deploy async makes the process break down so in this flow, the bootstrap ps1 controls and drives the entire execution process, so when it exits, WindowsPE will exit unless an error occurs in the script and then a powershell window will spawn for troubleshooting but is optional.

The Unattend.xml RunAsynchronous command opened a minimized PowerShell console
for the whole WinPE session, whether or not anyone needed it. The console is now
owned by the runtime apps and only appears on demand.

- Drop the RunAsynchronous console from Unattend.xml and its provisioning option.
- Add TroubleshootingConsoleSettings (enabled, function key, optional modifier),
  authored on the Boot Image page and emitted into the Connect and Deploy configs.
- Foundry.Connect and Foundry.Deploy open an interactive PowerShell console when
  the configured shortcut is pressed (PreviewKeyDown, so it works from any focus).
- On failure the bootstrap now opens a console and waits on it instead of exiting
  immediately, which kept the operator staring at an empty WinPE session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mchave3
mchave3 marked this pull request as draft July 14, 2026 15:17
@freedbygrace

Copy link
Copy Markdown
Author

The flow is all working a treat now...Modules are there, files are copied, I added an optional troubleshooting key combo so that you can press ctrl + f8 to launch a powershell Window for example. I connected, launched into deploy, it formatted the specified disk (I think custom partition plans here would be helpful), although a VBScript style confirmation appears, downloaded the cloud image, then failed to do the BCDBoot operations...

I am glad to do additional testing here.

image

@mchave3

mchave3 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Sounds good. Thank you for taking the time to work on this and for your detailed implementation. I really appreciate it.

I have a lot of work this week, so I don't think I'll have time to look into it until next week. I'll take the time to review your implementation carefully and decide which parts I want to keep or adapt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Repository or build configuration changes dependencies Dependency version or package management change documentation Documentation updates project: foundry Changes in the Foundry desktop application project: foundry-connect Changes in the Foundry.Connect WinPE network bootstrap application project: foundry-core Changes in the Foundry.Core shared business logic library project: foundry-deploy Changes in the Foundry.Deploy deployment application tests Test coverage or test infrastructure change ui User interface or XAML changes

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Making the Foundry Deploy Init Sequence cleaner

2 participants