feat(winpe): add boot image customization suite with windowless psbootstrapper boot - #247
Conversation
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>
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>
|
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>
|
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. |
…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>
|
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>
|
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. |

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.cmdflashed a console window before the Foundry UI appeared.Changes
Boot process
psbootstrapper.exe(x64/arm64) and copy it into the image'sSystem32.X:\Unattend.xml(hidden), instead of a PowerShell line instartnet.cmd.winpeshl.ini(launching onlywpeinit) so WinPE no longer runscmd /k startnet.cmd— removes the console window that flashed at boot.wpeinitstill processes the unattend (Display / EnableNetwork / EnableFirewall + the bootstrap launch).startnet.cmdis kept as awpeinit-only fallback.%WINDIR%aren't XML-escaped.Boot Image page (new, Expert)
boot.wimnext to the ISO, troubleshooting console.Save-Modulefolders. Selected Gallery/local modules are shown separately; Gallery modules link to their version page.\= image root,\Windows, …), copied recursively.Driver injection resilience
/Add-Driverexit 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).Optional component integration
WinPE-SecureBootCmdletsandWinPE-SecureStartupare always integrated last, in that order.Computer name variables
${SERIALNUMBER}(plus${MODEL},${MANUFACTURER},${PRODUCT}).Also
%WINDIR%\System32\WindowsPowerShell\v1.0\Modules\<Name>\<Version>.Testing
dotnet build .\src\Foundry.slnx— 0 errorsNotes
Screenshots
1. New Boot Image section — Settings (firewall, keep

boot.wim, troubleshooting console)2. Optional Components — dynamic ADK detection with sane defaults and safe ordering

3. PowerShell 7 — optional inclusion with version selection and download link

4. Modules — PowerShell Gallery search and local

Save-Modulefolders5. Customization — computer-name variables (

${SERIALNUMBER})