winpe

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// WinPEPayloadDir is where the winkit payload lives inside boot.wim.
	WinPEPayloadDir = `X:\winkit`
	// WinPEBootstrapCmdPath is the cmd.exe shim that winpeshl.ini calls.
	// Stock WinPE lacks powershell.exe; this shim probes volumes for pwsh.exe
	// (PowerShell 7, xcopy-deployed on the answer volume) and launches the
	// real bootstrap.ps1 through it.
	WinPEBootstrapCmdPath = `X:\winkit\bootstrap.cmd`
	// WinPEBootstrapPath is the PowerShell bootstrap, launched by the cmd shim.
	WinPEBootstrapPath = `X:\winkit\bootstrap.ps1`
	// WinPEAgentPath is the control agent, started detached by the bootstrap.
	WinPEAgentPath = `X:\winkit\agent.ps1`

	// PwshVolDir is the directory on the answer volume containing PowerShell 7.
	// Stock WinPE has no PowerShell; pwsh.exe is self-contained and
	// xcopy-deployed from the official GitHub release zip.
	PwshVolDir = `pwsh`

	// AgentVolumeMarker identifies the removable volume carrying the command
	// and result files. WinPE drive letters are not stable, so the agent
	// searches for this file instead of assuming a letter.
	AgentVolumeMarker = `winkit-agent.marker`
	// AgentCommandFile holds a single command line for the agent to run.
	AgentCommandFile = `winkit-cmd.txt`
	// AgentResultFile receives that command's combined output.
	AgentResultFile = `winkit-out.txt`
	// AgentDoneFile is written after the command finishes. The host polls for
	// this instead of AgentResultFile to avoid reading a half-written output
	// file (the redirect flushes incrementally, so the file appears non-empty
	// before diskpart/PowerShell finishes writing).
	AgentDoneFile = `winkit-done.marker`

	// AgentScriptName is the agent's filename on the answer volume — the
	// no-rebake deployment path: a windowsPE RunSynchronous launcher starts
	// it straight off the volume (AgentLauncherCommand), so boot.wim
	// never has to be modified.
	AgentScriptName = `winkit-agent.ps1`
	// SetupActSnapshotName receives the agent's periodic copy of WinPE's
	// X:\Windows\Panther\setupact.log, which otherwise dies with the RAM
	// disk (CELL-364). The x- prefix distinguishes it from the C:\ target
	// disk variant written during the second-pass install phase.
	SetupActSnapshotName = `x-winkit-setupact.log`
	// SetupErrSnapshotName receives setuperr.log the same way.
	SetupErrSnapshotName = `x-winkit-setuperr.log`
	// SetupAPISnapshotName receives X:\Windows\INF\setupapi.dev.log, PnP's
	// full driver-binding trace. drvload.exe has no verbose switch, so this
	// is the only way to see why a driver did or did not bind.
	SetupAPISnapshotName = `x-winkit-setupapi.dev.log`
	// Target disk (C:\) snapshots for the second-pass install phase.
	SetupActTargetSnapshotName = `c-winkit-setupact.log`
	SetupErrTargetSnapshotName = `c-winkit-setuperr.log`
)

WinPE payload layout. These files are baked into boot.wim so they exist on the WinPE RAM drive (X:) before setup.exe starts.

View Source
const (
	GosshdShellCmdName = "gosshd.cmd"
	GosshdLogName      = "gosshd.log"
	ServiceVolumeName  = "winkit-service.exe"
	InitManifestName   = "init.json"

	// GosshdStructuredPort is the guest path of the serial port gosshd
	// emits per-session records to (COM2, backed by build.jsonl on the
	// host). Gosshd tolerates its absence, so passing it unconditionally
	// is safe even on a boot where the port was not wired.
	GosshdStructuredPort = GuestSerialPort
)

GosshdShellCmdName is the cmd.exe shell script that boots the base image into gosshd. GosshdLogName is gosshd's log on the WinPE ramdisk.

View Source
const (
	ChromiumVolDir     = "chrome"
	ContentShellVolDir = "content-shell"
)
View Source
const (
	// BootstrapLogName mirrors unattend.BootstrapLogName to avoid an import
	// cycle (unattend imports winpe).
	BootstrapLogName = "winkit-bootstrap.log"
	// GuestDiagnosticsLogName mirrors unattend.GuestDiagnosticsLogName.
	GuestDiagnosticsLogName = "winkit-diag.log"
)
View Source
const (

	// PeAgentLogFile is the guest path where the second-pass pe-agent
	// writes its log on the OS drive.
	PeAgentLogFile = `C:\winkit-pe-agent.log`

	// BootVolumeMarker is the filename placed on the FAT boot volume so
	// the pe-agent can discover it by scanning drive letters. The host
	// reads the log back from the same volume after the VM exits.
	BootVolumeMarker = "winkit-boot.marker"

	// BootVolumeLogFile is the filename the pe-agent writes on the boot
	// FAT volume. The host reads it with ReadFileFromFATQcow2.
	BootVolumeLogFile = "winkit-pe-agent.log"
)
View Source
const (
	WimBuilderScriptName    = `winkit-wim-builder.ps1`
	WimBuilderDoneFile      = `winkit-builder-done.txt`
	WimBuilderLogFile       = `winkit-builder.log`
	WimBuilderCompleteToken = `WINKIT_BUILDER_DONE`
)
View Source
const (
	// WSL1PEStartupCommand triggers the native Go WSL1 bootstrap inside
	// winkit-service init. The command string is only used as a non-empty
	// marker: init.go calls runBootstrapWSL1 directly in-process.
	WSL1PEStartupCommand = `X:\winkit\winkit-service.exe bootstrap-wsl1`
	WSL1PEDistroName     = "winkit"
	WSL1PEUserName       = "winkit"
)
View Source
const AgentLauncherLog = `winkit-launcher.log`

AgentLauncherLog is the diagnostic log the windowsPE RunSynchronous launcher writes to the answer volume. It records which drive letter was found, whether pwsh.exe and the agent script exist, and whether the agent was started. Readable from the host after the build.

View Source
const AgentLauncherScript = `winkit-launch.cmd`

AgentLauncherScript is the cmd batch file shipped on the answer volume. The windowsPE RunSynchronous command finds and calls it; the script does the heavy lifting (diagnostics, guards, starting the agent).

View Source
const DiagCommand = `Set-Content X:\winkit-lv.txt "list volume` + "`r`n" + `exit"; & diskpart.exe /s X:\winkit-lv.txt; & reg.exe query HKLM\SYSTEM\CurrentControlSet\Services\vioscsi; Get-ChildItem X:\Windows\Panther, X:\$windows.~bt\Sources\Panther -ErrorAction SilentlyContinue`

DiagCommand is the one-shot diagnostic the agent executes when a build ships it as AgentCommand; its combined output lands in winkit-out.txt on the answer volume. Strictly read-only: the first version drvloaded vioscsi and collided with wpeinit's own $WinPEDriver$ load — Setup aborted 0x80070103 ERROR_NO_MORE_ITEMS, run 20260812T143146.

Deprecated: prefer DiagScriptCommand, which invokes the proper diagnostics script and waits for completion before the output is read.

View Source
const (
	// DiagScriptName is the diagnostics script shipped on the answer
	// volume. It follows the same structured-output pattern as
	// GenerateGuestDiagnosticsScript (guest_diagnostics.go) but runs in
	// WinPE under PowerShell.
	DiagScriptName = `winkit-winpe-diag.ps1`
)
View Source
const (
	// EchoProbeScriptName is the filename for the COM-port echo
	// probe + virtiofs write test script.
	EchoProbeScriptName = `winkit-winpe-echo-probe.ps1`
)
View Source
const GosshdPackage = "github.com/devcell-sh/go-winkit/gosshd/cmd/gosshd"

GosshdPackage is the guest SSH server cross-compiled into WinPE payloads. Win32-OpenSSH cannot serve sessions in WinPE at all (no user logons), so the tree's own server is the only SSH that works there — see package gosshd.

View Source
const GosshdVolumeName = "gosshd.exe"

GosshdVolumeName is gosshd's filename on the shared volume.

View Source
const GuestSerialPort = `\\.\Global\winkit.structured.0`

GuestSerialPort is the virtio-serial device path used inside the guest for the unified structured/progress serial stream. The vioserial driver (drvloaded at boot) creates this device from the virtserialport QEMU device named "winkit.structured.0". ARM64 Windows has no inbox serial.sys for PCI 16550 UARTs, so virtio-serial is the only working transport.

View Source
const ImplorerVolumeName = "implorer.exe"
View Source
const NetBIOSNameMax = 15

NetBIOSNameMax is the hard limit on a Windows computer name.

View Source
const PwshZipURL = "https://github.com/PowerShell/PowerShell/releases/download/v7.6.5/PowerShell-7.6.5-win-arm64.zip"
View Source
const ServicePackage = "github.com/devcell-sh/go-winkit/cmd/winkit-service"

ServicePackage is the winkit-service wrapper cross-compiled for the guest.

View Source
const StartupNSH = `` /* 497-byte string literal not displayed */

StartupNSH is the UEFI shell startup script that boots the Windows installer. UEFI ignores BIOS-style `-boot d`, so we need this to chain-load the Windows EFI boot loader. Uses sequential if-exist checks (not a for loop) because UEFI Shell %var expansion inside path strings is unreliable across EDK II builds.

View Source
const WSL1EngineDestDir = `\Program Files\WSL`

WSL1EngineDestDir is the path used by the official WSL MSI. Keeping that layout is important because WSLService locates its tools relative to its installation directory.

View Source
const WSL1PackageVersion = "2.7.12"

WSL1PackageVersion is the packaged Microsoft WSL runtime whose service and COM registration are mirrored by WSL1ServicePatchSet.

View Source
const WSL1RuntimeCatalogPath = `\Windows\winkit\WSL1Catalogs`

WSL1RuntimeCatalogPath contains the small subset of component catalogs that mention the transplanted WSL1 drivers. Registering only this directory in a live WinPE guest avoids processing the complete WinSxS catalog archive.

View Source
const (
	WebView2VolDir = "webview2"
)

Variables

View Source
var DWMDriverPaths = []string{
	`\Windows\System32\drivers\monitor.sys`,
}

DWMDriverPaths are kernel-mode driver binaries needed by the DWM display pipeline. monitor.sys is the function driver for child monitor devices created by WDDM adapters (BasicDisplay, VioGpuDod); without it VidPn targets cannot be fully mapped.

View Source
var DWMDriverStoreKeywords = []string{
	"msdv.inf",
	"basicrender.inf",
	"basicdisplay.inf",
	"monitor.inf",
}

DWMDriverStoreKeywords are matched against DriverStore FileRepository directory names to discover display driver packages that must be present in boot.wim for PnP to install display adapters and monitors. msdv.inf is not at \Windows\INF\ on ARM64; it lives only in the DriverStore FileRepository.

View Source
var DWMINFPaths = []string{
	`\Windows\INF\msdv.inf`,
	`\Windows\INF\c_display.inf`,
	`\Windows\INF\monitor.inf`,
}

DWMINFPaths are display-related INF files from install.wim that viogpudo and other WDDM display drivers depend on. viogpudo.inf has Include=msdv.inf; without it the driver may fail to initialize. monitor.inf classifies child monitor devices (DISPLAY\Default_Monitor) under the Monitor class so they receive the monitor.sys function driver.

View Source
var DWMServicingPackageKeywords = []string{
	"win3-dwm",
	"windows-clientcore-dwm",
}

DWMServicingPackageKeywords filter servicing metadata entries from install.wim for inclusion in boot.wim. These match the Win3-DWM and ClientCore-DWM package names.

View Source
var DWMSystem32Paths = []string{

	`\Windows\System32\dwm.exe`,
	`\Windows\System32\dwmcore.dll`,
	`\Windows\System32\dwminit.dll`,
	`\Windows\System32\dwmscene.dll`,
	`\Windows\System32\dwmredir.dll`,
	`\Windows\System32\dwmghost.dll`,
	`\Windows\System32\uDWM.dll`,

	`\Windows\System32\dcomp.dll`,

	`\Windows\System32\Microsoft.Internal.WarpPal.dll`,

	`\Windows\System32\d3d11.dll`,
	`\Windows\System32\dxgi.dll`,
	`\Windows\System32\d3d10warp.dll`,
	`\Windows\System32\D3D12.dll`,
	`\Windows\System32\D3D12Core.dll`,
	`\Windows\System32\DXCore.dll`,
	`\Windows\System32\d3d11on12.dll`,
	`\Windows\System32\dxilconv.dll`,

	`\Windows\System32\DirectXDatabase.dll`,
	`\Windows\System32\DirectXDatabaseHelper.dll`,
	`\Windows\System32\DXCoreSharedLib.dll`,
	`\Windows\System32\dxgiadaptercache.exe`,

	`\Windows\System32\cdd.dll`,

	`\Windows\System32\d2d1.dll`,
	`\Windows\System32\DWrite.dll`,
	`\Windows\System32\D3DCompiler_47.dll`,

	`\Windows\System32\D3DSCache.dll`,
	`\Windows\System32\dxdiagn.dll`,
	`\Windows\System32\DXGIDebug.dll`,

	`\Windows\System32\mf.dll`,
	`\Windows\System32\mfplat.dll`,

	`\Windows\System32\GraphicsPerformanceMonitor.dll`,
	`\Windows\System32\DispBroker.Desktop.dll`,
	`\Windows\System32\DispBroker.dll`,

	`\Windows\System32\dxgitrace.dll`,
}

DWMSystem32Paths are the user-mode DLLs and executables that make up the DWM compositor and its DirectX rendering pipeline. Stock WinPE carries only the dwmapi.dll stub; everything else must be transplanted from install.wim to enable DirectComposition (required by WebView2 and other Chromium-based renderers).

View Source
var DWMWinSxSKeywords = []string{
	"microsoft-windows-d..opwindowmanager",
	"microsoft-windows-d..ager-api",
	"microsoft-windows-d..ager-udwm",
	"microsoft-windows-directx-d3d11",
	"microsoft-windows-directx-dxgi",
	"microsoft-windows-directx-warp",
	"microsoft-windows-directcomposition",
	"microsoft-windows-warppal",
	"microsoft-windows-direct2d",
	"microsoft-windows-directwrite",
	"microsoft-windows-display-msdv",
	"microsoft-windows-directx-dxcore",
	"microsoft-windows-monitor-inf",
	"microsoft-windows-directxdatabase",
	"microsoft-windows-directx-d3d12",
	"microsoft-windows-directx-direct3d-runtime",
	"microsoft-windows-cdd",
	"microsoft-windows-graphicsperfmonitor",
	"microsoft-windows-dispbroker",
}

DWMWinSxSKeywords are matched against WinSxS directory names to discover DWM component packages. The naming follows the CBS pattern used by the WSL1 transplant.

View Source
var ErrNoSuchGuestLog = errors.New("not written by the guest")

ErrNoSuchGuestLog is the sentinel for a log the guest never wrote.

GuestLogNames is the contract with the guest side.

View Source
var NetFxServicingPackageKeywords = []string{
	"microsoft-windows-netfx",
}

NetFxServicingPackageKeywords filter servicing metadata from install.wim.

View Source
var NetFxSystem32Paths = []string{
	`\Windows\System32\mscoree.dll`,
	`\Windows\System32\mscoreei.dll`,
	`\Windows\System32\clrjit.dll`,
	`\Windows\System32\clr.dll`,
	`\Windows\System32\msvcr120_clr0400.dll`,
}

NetFxSystem32Paths are the .NET Framework files needed to run managed assemblies (like choco.exe) in WinPE. Stock WinPE carries only the WinPE-NetFx optional component's subset; a full install has the complete v4.0.30319 tree. The CLR hosting shim (mscoree.dll) and the runtime tree are extracted from install.wim and injected into boot.wim.

View Source
var NetFxTreePaths = []string{
	`\Windows\Microsoft.NET`,
}

NetFxTreePaths are directory trees extracted wholesale from install.wim. The v4.0.30319 directory contains the CLR, BCL assemblies, and GAC entries that managed applications link against.

View Source
var NetFxWinSxSKeywords = []string{
	"microsoft-windows-netfx",
	"microsoft-windows-m..core-runtime",
	"microsoft-windows-m..l-runtime",
	"microsoft-windows-mscoree",
}

NetFxWinSxSKeywords are matched against WinSxS directory names to discover .NET Framework component packages. Any WinSxS entry whose lowercase name contains one of these is extracted.

View Source
var WSL1FilesPaths = []string{
	`\Windows\System32\wsl.exe`,
	`\Windows\System32\wslapi.dll`,
	`\Windows\System32\lxutil.dll`,
	`\Windows\System32\lxss`,
}

WSL1FilesPaths lists System32 files that the WSL1 subsystem provides. On Windows 11 ARM64, the kernel driver (lxcore.sys) and some utilities (bash.exe) live only in WinSxS, not under System32; TransferWSL1Files discovers those by prefix-matching WinSxS directory names.

View Source
var WSL1InboxSupportPaths = []string{
	`\Windows\System32\drivers\bfs.sys`,
	`\Windows\System32\drivers\afunix.sys`,
	`\Windows\System32\wshunix.dll`,
	`\Windows\System32\wci.dll`,
	`\Windows\System32\drivers\wcifs.sys`,
	`\Windows\System32\drivers\bindflt.sys`,
	`\Windows\System32\drivers\p9rdr.sys`,
	`\Windows\System32\p9np.dll`,
}

WSL1InboxSupportPaths are inbox networking and filesystem dependencies present in a full Windows installation with the WSL optional component enabled but absent from stock WinPE.

View Source
var WSL1ServiceDependencyPaths = []string{
	`\Windows\System32\computecore.dll`,
	`\Windows\System32\computenetwork.dll`,
	`\Windows\System32\msi.dll`,
	`\Windows\System32\vid.dll`,
}

WSL1ServiceDependencyPaths are direct PE-loader dependencies of the packaged wslservice.exe that stock WinPE does not carry. They come from the same-build install.wim and are materialized if CBS stored them as DCS containers. computecore.dll imports vid.dll even when WSLService ultimately selects its WSL1 path, so vid.dll must be present for process startup.

View Source
var WoW64ServicingPackageKeywords = []string{
	"microsoft-windows-wow64",
}

WoW64ServicingPackageKeywords filter servicing metadata from install.wim.

View Source
var WoW64SysWOW64Paths = []string{
	`\Windows\SysWOW64\ntdll.dll`,
	`\Windows\SysWOW64\kernel32.dll`,
	`\Windows\SysWOW64\kernelbase.dll`,
	`\Windows\SysWOW64\ucrtbase.dll`,
	`\Windows\SysWOW64\msvcrt.dll`,
	`\Windows\SysWOW64\advapi32.dll`,
	`\Windows\SysWOW64\user32.dll`,
	`\Windows\SysWOW64\gdi32.dll`,
	`\Windows\SysWOW64\gdi32full.dll`,
	`\Windows\SysWOW64\shell32.dll`,
	`\Windows\SysWOW64\ole32.dll`,
	`\Windows\SysWOW64\oleaut32.dll`,
	`\Windows\SysWOW64\combase.dll`,
	`\Windows\SysWOW64\rpcrt4.dll`,
	`\Windows\SysWOW64\sechost.dll`,
	`\Windows\SysWOW64\bcrypt.dll`,
	`\Windows\SysWOW64\bcryptprimitives.dll`,
	`\Windows\SysWOW64\crypt32.dll`,
	`\Windows\SysWOW64\ws2_32.dll`,
	`\Windows\SysWOW64\msvcp_win.dll`,
	`\Windows\SysWOW64\win32u.dll`,
	`\Windows\SysWOW64\imm32.dll`,
	`\Windows\SysWOW64\setupapi.dll`,
	`\Windows\SysWOW64\cfgmgr32.dll`,
	`\Windows\SysWOW64\version.dll`,
	`\Windows\SysWOW64\shlwapi.dll`,
	`\Windows\SysWOW64\cabinet.dll`,
	`\Windows\SysWOW64\msi.dll`,
	`\Windows\SysWOW64\psapi.dll`,
	`\Windows\SysWOW64\powrprof.dll`,
	`\Windows\SysWOW64\sspicli.dll`,
	`\Windows\SysWOW64\profapi.dll`,
	`\Windows\SysWOW64\shcore.dll`,
	`\Windows\SysWOW64\imagehlp.dll`,
	`\Windows\SysWOW64\wintrust.dll`,
}

WoW64SysWOW64Paths are files from the SysWOW64 directory (the x86/x64 system root under WoW64). The entire SysWOW64 tree is large; we pull only the core loader and C runtime needed by most x64 applications.

View Source
var WoW64System32Paths = []string{
	`\Windows\System32\wow64.dll`,
	`\Windows\System32\wow64win.dll`,
	`\Windows\System32\wow64cpu.dll`,
	`\Windows\System32\wow64con.dll`,
	`\Windows\System32\xtajit64.dll`,
	`\Windows\System32\xtajit.dll`,
	`\Windows\System32\xtac.exe`,
	`\Windows\System32\xtaofflinesetup.exe`,
	`\Windows\System32\arm64x\xtajit64.dll`,
	`\Windows\System32\arm64x\xtajit.dll`,
}

WoW64System32Paths are the WoW64 subsystem files needed on ARM64 WinPE to run x86 and x64 binaries. Without these, any non-ARM64 executable fails with STATUS_DLL_NOT_FOUND (0xC0000135).

xtajit64.dll (~30 MB) is the ARM64 JIT that translates x64 code; xtajit.dll handles x86. wow64.dll/wow64win.dll/wow64cpu.dll form the WoW64 thunking layer.

View Source
var WoW64TreePaths = []string{
	`\Windows\SysWOW64\downlevel`,
}

WoW64TreePaths are directory trees extracted wholesale. The SysWOW64\downlevel directory has API-set DLLs that the loader resolves via API-set maps.

View Source
var WoW64WinSxSKeywords = []string{
	"microsoft-windows-wow64",
	"microsoft-windows-xtajit",
	"microsoft-windows-xta-",
}

WoW64WinSxSKeywords are matched against WinSxS directory names to discover WoW64 and x86/x64 emulation component packages on ARM64.

Functions

func AgentLauncherCommand

func AgentLauncherCommand() string

AgentLauncherCommand returns the one non-registry command allowed in windowsPE RunSynchronous. Anything that can fail there aborts Setup (0x80070001 - 0x40030, run 20260729T172019), so the whole block is wrapped in exit /b 0 to guarantee a zero exit code. The agent is started detached via "start /min" so Setup is never blocked.

Uses cmd.exe because stock WinPE lacks powershell.exe. The answer volume carries pwsh.exe (PowerShell 7) which the agent needs at runtime. The actual logic lives in AgentLauncherScript (winkit-launch.cmd on the answer volume); this one-liner just finds and calls it.

func AppendStreamJSONL added in v0.3.0

func AppendStreamJSONL(dst io.Writer, srcPath, source string) error

AppendStreamJSONL appends the lines of srcPath to dst as GuestEvent JSONL, namespaced under source. Lines that already are JSON objects (the guest's structured stream) pass through with "source" injected; plain-text lines (serial console, progress logs) are wrapped as {"event":"log","line":...,"source":...} with the message JSON-escaped. Wrapped lines are stamped with the merge time — their original wall time is unknown — so consumers should treat them as coarse-ordered. A missing srcPath is not an error: not every run produces every stream.

func BootVolumeFiles added in v0.3.0

func BootVolumeFiles(stageDir string, bootWim []byte) (map[string][]byte, error)

BootVolumeFiles returns the files that make a FAT volume a complete standalone WinPE boot disk: the EFI bootloader, BCD stores, boot.sdi, and the boot.wim at the ramdisk path the stock BCD references ([boot]\sources\boot.wim). Firmware boots \EFI\BOOT\BOOTAA64.EFI from the volume directly — no optical media involved, which matters because Linux-hosted EDK2 crashes executing genisoimage-mastered El Torito images.

func BuildBaseImageFiles added in v0.3.0

func BuildBaseImageFiles(cfg BaseImageConfig) (map[string][]byte, error)

BuildBaseImageFiles produces the FAT-volume file map for a self-contained bootable base image. The caller packs it into whatever their hypervisor boots (e.g. qemu.CreateFATQcow2). The boot chain: firmware boots \EFI\BOOT\BOOTAA64.EFI from the volume, bootmgr loads the injected boot.wim, winpeshl.ini runs gosshd.cmd — wpeinit, drvload the virtio drivers, drop the firewall, serve SSH in the foreground so the guest stays up.

func BuildSetupBootVolumeFiles added in v0.3.0

func BuildSetupBootVolumeFiles(winISO, workDir string) (map[string][]byte, error)

BuildSetupBootVolumeFiles assembles a FAT boot volume that ramdisk-boots the retail Windows Setup — the stock \sources\boot.wim from the install ISO, UNMODIFIED (no gosshd/WinPE injection). Firmware boots \EFI\BOOT\BOOTAA64.EFI from this volume (bootindex=1) and the stock BCD loads boot.wim, launching Setup, which then reads \sources\install.wim from the still-attached Windows ISO. This is the same FAT-boot method the base/WIM-builder paths use, because booting the ISO's El Torito image directly ASSERTs on Linux-hosted EDK2.

func BuildSetupBootVolumeFilesPatched added in v0.3.0

func BuildSetupBootVolumeFilesPatched(winISO, workDir string, installWimPatches []WimPatchSet) (map[string][]byte, error)

BuildSetupBootVolumeFilesPatched is like BuildSetupBootVolumeFiles but also extracts install.wim from the ISO and applies the given patch sets to it. When patches are provided, the patched install.wim is included on the boot volume at \sources\install.wim so Setup uses it instead of the ISO copy.

func CrossCompileGosshd added in v0.3.0

func CrossCompileGosshd(dst, arch string) error

CrossCompileGosshd produces the gosshd guest binary for windows/<arch> at dst: from the embedded prebuilt when this winkit was built with `task build`, else by `go build` with the local toolchain (which only works inside the go-winkit source tree). CGO is off so the result is a single static binary — WinPE's reduced System32 cannot be assumed to carry any particular runtime.

func CrossCompileImplorer added in v0.3.0

func CrossCompileImplorer(dst, arch string) error

CrossCompileImplorer produces the implorer desktop shell for windows/<arch> at dst: from the prebuilt binary in embedded/ when this winkit was built with `task build`, else returns an error. Unlike gosshd and winkit-service, implorer is a separate Go module (Wails v3 dependency) and cannot be built by `go build` from this tree.

func CrossCompileService added in v0.3.0

func CrossCompileService(dst, arch string) error

CrossCompileService produces the winkit-service guest binary for windows/<arch> at dst: from the embedded prebuilt when this winkit was built with `task build`, else by `go build` with the local toolchain (which only works inside the go-winkit source tree). CGO is off so the result is a single static binary.

func DiagScriptCommand

func DiagScriptCommand() string

DiagScriptCommand returns the agent command that invokes the diagnostics script. The agent runs this via Invoke-Expression in PowerShell, so $WinkitVol is expanded from the agent's scope.

func DiagToolPaths

func DiagToolPaths() []string

DiagToolPaths returns the WIM-internal paths of System32 binaries to extract from install.wim and inject into boot.wim. Stock WinPE lacks these; injecting them gives the diagnostics script real service management and process visibility.

func DriverLoadCommand

func DriverLoadCommand(inf string) string

DriverLoadCommand returns a windowsPE RunSynchronous command that drvloads one INF from whatever drive letter the answer volume received.

This is the last hook before Modern Setup searches for install media: run 20260812T150644 logged "WinPEInitialization: Leaving Execute Method" and "EarlyF6DriverInstall: Entering Execute Method" one second apart, in that order. The agent's poll loop is too late: its drvload landed after the media search had already failed (0x80070103, run 20260812T143146).

Uses cmd.exe because stock WinPE lacks powershell.exe. drvload.exe is a WinPE native tool. Wrapped in exit /b 0 so a broken driver degrades gracefully instead of aborting Setup.

func DwordValue added in v0.3.0

func DwordValue(v uint32) regedit.Value

DwordValue constructs a registry DWORD value.

func EchoProbeScriptCommand

func EchoProbeScriptCommand() string

EchoProbeScriptCommand returns the agent command that invokes the COM-port echo probe script.

func EncodeMultiSz added in v0.3.0

func EncodeMultiSz(ss []string) []byte

EncodeMultiSz encodes a list of strings as REG_MULTI_SZ data: each string NUL-terminated in UTF-16LE, with an extra trailing NUL.

func Extract7z

func Extract7z(isoPath, filePath string) ([]byte, error)

Extract7z extracts a single file from an ISO using 7z.

func Extract7zToFile added in v0.3.0

func Extract7zToFile(isoPath, isoFilePath, destPath string) error

Extract7zToFile extracts a single file from an ISO directly to destPath on disk, avoiding loading the entire file into memory.

func ExtractPwshFiles added in v0.3.0

func ExtractPwshFiles(zipPath string) (map[string][]byte, error)

ExtractPwshFiles reads a PowerShell zip and returns its contents as a map keyed by answer-volume paths (e.g. "/pwsh/pwsh.exe").

func ExtractStage

func ExtractStage(winISO, stageDir string) error

ExtractStage extracts the EFI boot files and boot.wim from a Windows ISO into stageDir, creating the directory structure needed to build a bootable WinPE ISO.

func FetchChromiumFiles added in v0.3.0

func FetchChromiumFiles(cacheDir string, logf func(string, ...any)) (map[string][]byte, error)

FetchChromiumFiles downloads the Chromium ARM64 snapshot build (full browser) and returns a file map ready for BaseImageConfig.ChromiumFiles. Files land at X:\winkit\chrome\ inside WinPE.

func FetchContentShellFiles added in v0.3.0

func FetchContentShellFiles(cacheDir string, logf func(string, ...any)) (map[string][]byte, error)

FetchContentShellFiles downloads the Chromium content-shell ARM64 build (minimal browser shell) and returns a file map ready for BaseImageConfig.ContentShellFiles. Files land at X:\winkit\content-shell\ inside WinPE.

func FetchPwshFiles added in v0.3.0

func FetchPwshFiles(cacheDir string, logf func(string, ...any)) (map[string][]byte, error)

FetchPwshFiles downloads PowerShell 7 (if not already cached) and returns the extracted file map ready for BaseImageConfig.PwshFiles.

func FetchWSL1Engine added in v0.3.0

func FetchWSL1Engine(ctx context.Context, cacheDir string, noCache bool, logf func(string, ...any)) (string, error)

FetchWSL1Engine returns the extracted Microsoft WSL ARM64 runtime used by the WinPE WSL1 builder. A complete cached extraction is reused. Downloads and extraction are staged so an interruption cannot look like a valid cache.

func FetchWebView2Files added in v0.3.0

func FetchWebView2Files(cacheDir string, logf func(string, ...any)) (map[string][]byte, error)

FetchWebView2Files downloads the WebView2 fixed-version runtime for ARM64 (if not already cached) and returns the extracted file map ready for BaseImageConfig.WebView2Files. The map is keyed by volume paths (e.g. "/webview2/msedgewebview2.exe").

func FormatGuestLogs added in v0.3.0

func FormatGuestLogs(logs []GuestLog) string

FormatGuestLogs renders collected logs for a terminal.

func GenerateAgent

func GenerateAgent(cfg PayloadConfig) []byte

GenerateAgent produces a PowerShell control agent: a poll loop that snapshots Setup's logs onto the winkit volume and runs one command at a time, streaming output through Tee-Object to both the result file and the virtio-serial progress port.

This exists because there is no qemu-guest-agent build for Windows ARM64 (virtio-win ships only i386/x86_64 MSIs), so QMP guest-exec is unavailable. The command file lives on the removable FAT image the host also writes, and needs no drivers beyond inbox usbstor.

func GenerateAgentLauncherScript added in v0.3.0

func GenerateAgentLauncherScript() []byte

GenerateAgentLauncherScript produces the batch file that the windowsPE RunSynchronous one-liner calls. It logs diagnostics to AgentLauncherLog, verifies pwsh.exe and the agent script exist, and starts the agent detached. Every path is guarded and logged so a silent failure is diagnosable from the answer volume after the build.

func GenerateBootstrap

func GenerateBootstrap(cfg PayloadConfig) []byte

GenerateBootstrap produces a PowerShell script that runs before setup.exe: initializes WinPE, loads requested drivers, opens the serial port for progress, and launches the agent.

Launched by the cmd.exe shim via pwsh.exe (PowerShell 7). Uses $PSHOME to locate the same pwsh.exe binary for spawning the agent.

func GenerateBootstrapCmd

func GenerateBootstrapCmd() []byte

GenerateBootstrapCmd produces the cmd.exe shim that winpeshl.ini calls. Stock WinPE has cmd.exe but not powershell.exe. This shim probes removable volumes for pwsh.exe (PowerShell 7, xcopy-deployed on the answer volume) and launches the real PowerShell bootstrap through it.

func GenerateDiagScript

func GenerateDiagScript() []byte

GenerateDiagScript produces the WinPE diagnostics script. It is shipped on the answer volume and invoked by the agent. Output goes to stdout (the agent redirects it to AgentResultFile).

Three sections:

  1. Disk/volume enumeration
  2. CIM/PowerShell probes
  3. Script access: can we see other winkit scripts on the answer volume

func GenerateEchoProbeScript

func GenerateEchoProbeScript(viofsTag string) []byte

GenerateEchoProbeScript produces a WinPE PowerShell script that:

  1. Probes COM1 through COM4, echoing a unique marker to each port so the host can determine which serial device maps to PCI-serial on ARM64.
  2. Loads the viofs driver via drvload and mounts a virtiofs share using virtiofs.exe, then writes a test file to the mount point.

The answer volume path is passed as $args[0]. viofs driver files and virtiofs.exe are expected under $Vol\drivers\viofs\. The virtiofs tag must match Spec.VirtioFSTag (default "winkit-logs").

func GenerateGosshdShellCmd added in v0.3.0

func GenerateGosshdShellCmd(driverINFs []string, peAgent bool, gosshdAddr ...string) []byte

GenerateGosshdShellCmd produces the cmd.exe shell for the base image: initialise WinPE, load the injected virtio drivers, open the firewall, and run gosshd in the foreground — standalone WinPE reboots the moment winpeshl's apps return, so the server blocking is what keeps the guest up.

func GenerateInitManifest added in v0.3.0

func GenerateInitManifest(driverINFs []string, gosshdAddr, user, password, wsl1Bootstrap, desktopShell string, desktopShellArgs, chocolateyPkgs []string) []byte

GenerateInitManifest produces the init.json that winkit-service init reads.

func GeneratePEAgentLauncherScript added in v0.3.0

func GeneratePEAgentLauncherScript(serviceBinaryName string) []byte

GeneratePEAgentLauncherScript produces the batch file equivalent for the PE standalone path (boot.wim injection). The bootstrap.cmd calls this to start the pe-agent from the answer volume.

func GenerateShellINI

func GenerateShellINI() []byte

GenerateShellINI produces winpeshl.ini, which replaces WinPE's default startup. Entries run in order and synchronously, so the bootstrap is listed first and setup.exe second — dropping setup.exe here would leave WinPE with nothing to do after the bootstrap returns.

The bootstrap is a cmd.exe script because stock WinPE lacks powershell.exe. The shim probes volumes for pwsh.exe and launches the real PS1 bootstrap.

func GenerateShellINI_NoSetup

func GenerateShellINI_NoSetup() []byte

GenerateShellINI_NoSetup produces winpeshl.ini that runs ONLY the bootstrap — no setup.exe. Used when booting WinPE standalone (CELL-430).

func GenerateWimBuilderScript added in v0.3.0

func GenerateWimBuilderScript(cfg WimPrepConfig) []byte

GenerateWimBuilderScript produces a PowerShell script that runs inside WinPE to service a boot.wim copy using DISM offline commands. The shared volume (passed as first argument) carries boot.wim in and winkit.wim out.

func GuestHostname added in v0.3.0

func GuestHostname(cellID string) string

GuestHostname derives the guest's ComputerName from a cell ID.

It sanitizes the input for NetBIOS: forbidden characters become dashes, runs of dashes collapse, and the result is truncated to 15 characters. An empty or fully-sanitized-away input returns "winkit".

func InjectWinPEPayload added in v0.3.0

func InjectWinPEPayload(bootWimPath, injectDir string, registryPatches ...RegistryPatch) error

InjectWinPEPayload uses wimlib to inject WinPE agent files into boot.wim image 2. The injectDir must contain winpeshl.ini, bootstrap.cmd, bootstrap.ps1, agent.ps1, and optionally vioserial drivers under drivers/. The WIM is modified in-place.

func InstallerBootloader added in v0.3.0

func InstallerBootloader(isoPath string) ([]byte, error)

InstallerBootloader returns the installer's BOOTAA64.EFI.

func ListImageDir added in v0.3.0

func ListImageDir(wimPath string, imageNum int, dir string) ([]string, error)

ListImageDir returns the children of dir within a WIM image (browse mode for list-features --path). dir uses backslash separators, e.g. `\Windows\System32\winevt\Logs`.

func LoadWinPENetKVMDrivers added in v0.3.0

func LoadWinPENetKVMDrivers(virtioISO string) (map[string][]byte, error)

LoadWinPENetKVMDrivers extracts the ARM64 NetKVM network driver from the virtio-win ISO, keyed by answer-volume path. Without it a WinPE guest on virtio-net has no link — nothing that serves or dials TCP can work.

func LoadWinPEStorageDrivers added in v0.3.0

func LoadWinPEStorageDrivers(virtioISO string) (map[string][]byte, error)

LoadWinPEStorageDrivers extracts the ARM64 vioscsi driver from the virtio-win ISO, keyed by answer-volume path.

func LoadWinPEViofsDrivers added in v0.3.0

func LoadWinPEViofsDrivers(virtioISO string) (map[string][]byte, error)

LoadWinPEViofsDrivers extracts the ARM64 viofs driver and virtiofs.exe mount helper from the virtio-win ISO.

func LoadWinPEViogpudoDrivers added in v0.3.0

func LoadWinPEViogpudoDrivers(virtioISO string) (map[string][]byte, error)

LoadWinPEViogpudoDrivers extracts the ARM64 virtio-gpu (viogpudo) WDDM display driver from the virtio-win ISO. When loaded alongside a virtio-gpu-pci QEMU device, it registers a DXGI adapter that enables DWM composition via WARP software rendering.

func LoadWinPEVioserialDrivers added in v0.3.0

func LoadWinPEVioserialDrivers(virtioISO string) (map[string][]byte, error)

LoadWinPEVioserialDrivers extracts the ARM64 vioserial driver from the virtio-win ISO.

func MergeContentShellDeps added in v0.3.0

func MergeContentShellDeps(chromiumFiles, contentShellFiles map[string][]byte) int

MergeContentShellDeps copies locale paks and SwiftShader Vulkan files from the Chrome file map into the content-shell file map. Content-shell's zip doesn't ship these, but they're required for rendering in WinPE.

func MultiHandler added in v0.3.0

func MultiHandler(handlers ...slog.Handler) slog.Handler

MultiHandler fans slog records out to multiple handlers so one Logger can write to both a display and a structured log file.

func NewGuestEventHandler added in v0.3.0

func NewGuestEventHandler(w io.Writer) slog.Handler

NewGuestEventHandler returns a slog.Handler writing GuestEvent JSONL.

func PEAgentLauncherCommand added in v0.3.0

func PEAgentLauncherCommand(serviceBinaryName string) string

PEAgentLauncherCommand returns the windowsPE RunSynchronous one-liner that finds winkit-service.exe on a removable volume and starts the native pe-agent detached. Replaces the PS1 agent: no pwsh dependency. The exit /b 0 wrapper guarantees a zero exit code so Setup is never aborted.

func PadForFAT added in v0.3.0

func PadForFAT(data []byte) []byte

PadForFAT appends newlines until the payload is an exact multiple of the cluster size. go-diskfs v1.9.4 mis-records the directory-entry size of files ending near a cluster boundary — first measured as the last 63 bytes (6129-byte file), later disproven by a 14270-byte file corrupting 66 bytes short — so no partial-cluster tail is trusted; cluster-aligned files are the one class that has never mis-recorded. Trailing whitespace is legal after an XML root element, in PowerShell, and in UEFI .nsh scripts, so the padding is safe for every file we write. isokit.CreateFATImage still verifies the round-trip, so if even this assumption falls it fails loudly rather than silently shipping a corrupt image.

func PatchDevcellWim added in v0.3.0

func PatchDevcellWim(wimPath string, imageNum int, registryPatches ...RegistryPatch) error

PatchDevcellWim applies registry patches to an on-disk WIM file (typically winkit.wim after DISM offline servicing). This is the host-side post-step that sets correct Start values for services created or updated by DISM.

func PatchRegistry added in v0.3.0

func PatchRegistry(wim *wimlib.WIM, imageNum int, rp RegistryPatch) (cleanup func(), err error)

PatchRegistry extracts a registry hive from a WIM image, applies DWORD patches, and writes the modified hive back. The WIM is NOT overwritten — call Overwrite() after all modifications are done. The returned cleanup function removes the temp directory holding the patched hive; call it AFTER Overwrite() completes since wimlib needs the file to exist at overwrite time.

func PatchWIM added in v0.3.0

func PatchWIM(wimPath string, ps WimPatchSet) error

PatchWIM opens a WIM file, applies all modifications from the patch set, and overwrites the file in place.

func SharedVolumeFiles added in v0.3.0

func SharedVolumeFiles(cfg WimPrepConfig, efiBootLoader []byte, pwshFiles map[string][]byte) map[string][]byte

SharedVolumeFiles returns the files to place on the builder's shared FAT volume. The caller must also add "/boot.wim" with the actual boot.wim content — it's excluded here because it's large and already on disk.

pwshFiles carries the extracted PowerShell 7 directory (from ExtractPwshFiles). Stock WinPE lacks powershell.exe, so the bootstrap.cmd shim probes for pwsh.exe on the volume at runtime.

When efiBootLoader is non-nil, the volume also ships startup.nsh and /EFI/BOOT/BOOTAA64.EFI. EDK2 pflash has no ISO9660 driver, so SCSI CDs appear as BLK-only. The FAT volume (usb-storage) gets a bootindex and startup.nsh chainloads the Windows Boot Manager, which loads boot.wim.

func Stages added in v0.3.0

func Stages(events []GuestEvent) []string

Stages returns the stage names in emission order.

func SzValue added in v0.3.0

func SzValue(s string) regedit.Value

SzValue constructs a registry REG_SZ value.

func TransferDWMFiles added in v0.3.0

func TransferDWMFiles(installWimPath, bootWimPath string) ([]string, error)

TransferDWMFiles extracts the DWM compositor and DirectX rendering pipeline from install.wim (image 1) and injects them into boot.wim (image 2). Stock WinPE carries only a dwmapi.dll stub; this transplant adds the full compositor so DWM can be started for DirectComposition, which WebView2 and other Chromium-based renderers require.

func TransferNetFxFiles added in v0.3.0

func TransferNetFxFiles(installWimPath, bootWimPath string) ([]string, error)

TransferNetFxFiles extracts .NET Framework files from install.wim (image 1) and injects them into boot.wim (image 2). This provides the CLR and BCL that managed assemblies (e.g. choco.exe) need to run in WinPE.

func TransferSerialDriver added in v0.3.0

func TransferSerialDriver(installWimPath, bootWimPath string) error

TransferSerialDriver extracts the inbox serial port driver from install.wim (image 1) and injects it into boot.wim (image 2). WinPE does not carry serial.sys, so QEMU's pci-serial device has no driver and COM ports are never enumerated.

func TransferWSL1Files added in v0.3.0

func TransferWSL1Files(installWimPath, bootWimPath string) ([]string, error)

TransferWSL1Files extracts WSL1 component files from install.wim (image 1) and injects them into boot.wim (image 2). This bypasses the DISM Enable-Feature path, which fails on boot.wim with CBS 0x800f080c.

On Windows 11 ARM64, WSL1 files are split between System32 (wsl.exe, wslapi.dll, lxutil.dll, lxss/) and WinSxS (lxcore.sys, bash.exe). This function extracts System32 files directly and discovers WinSxS component directories by prefix matching.

The returned list contains every WIM path injected into boot.wim, so callers can log or verify exactly what was transferred.

func TransferWoW64Files added in v0.3.0

func TransferWoW64Files(installWimPath, bootWimPath string) ([]string, error)

TransferWoW64Files extracts the WoW64 subsystem and ARM64 x64/x86 emulation files from install.wim (image 1) and injects them into boot.wim (image 2). This enables running x86/x64 binaries on ARM64 WinPE via xtajit64.dll (x64 JIT) and xtajit.dll (x86 JIT).

func VerifyRegistry added in v0.3.0

func VerifyRegistry(wim *wimlib.WIM, imageNum int, hivePath string, checks []DWordCheck) error

VerifyRegistry extracts a registry hive from a WIM image and verifies that every check's DWORD value matches the expectation. Returns the first non-optional mismatch as an error. Optional checks that fail (key/value missing) are silently skipped.

func WSL1EngineFiles added in v0.3.0

func WSL1EngineFiles() []string

WSL1EngineFiles returns the packaged WSL files needed by the WSL1 path. The WSL2 kernel, initrd, module VHD, device host, system distro, WSLg, and settings application are intentionally excluded.

func WSL1PERuntimeHelpers added in v0.3.0

func WSL1PERuntimeHelpers() map[string]string

WSL1PERuntimeHelpers returns the embedded guest runtime staged into boot.wim. The Go bootstrap-wsl1 verb handles provisioning; these files are non-code helpers (diskpart script, wsl.cmd shim).

func WimBuilderScriptCommand added in v0.3.0

func WimBuilderScriptCommand() string

WimBuilderScriptCommand returns the agent command line for the builder. The agent runs this via Invoke-Expression in PowerShell, so $WinkitVol is expanded from the agent's scope.

func WindowsISOBootable added in v0.3.0

func WindowsISOBootable(isoPath string) error

WindowsISOBootable reports whether firmware can boot an installer ISO.

Types

type BaseImageConfig added in v0.3.0

type BaseImageConfig struct {
	// WindowsISO is the installer providing the WinPE stage.
	WindowsISO string
	// VirtIOISO is the virtio-win driver ISO.
	VirtIOISO string
	// GosshdExe is a windows cross-compiled gosshd (CrossCompileGosshd).
	GosshdExe string
	// ServiceExe is a cross-compiled winkit-service binary
	// (CrossCompileService). When set, the pe-agent mode is started
	// detached before gosshd so Panther logs stream to build.jsonl.
	ServiceExe string
	// ImplorerExe is a cross-compiled implorer desktop shell binary
	// (CrossCompileImplorer). When set, it is injected into the PE volume
	// so winkit-service can launch it after boot.
	ImplorerExe string
	// GosshdAddr overrides the listen address gosshd binds to inside the
	// guest (e.g. ":2222"). Empty means the gosshd default (":22").
	GosshdAddr string
	// PwshFiles are the extracted PowerShell 7 files (from ExtractPwshFiles).
	// When set, they are injected into boot.wim so pwsh.exe is available at
	// X:\winkit\pwsh\pwsh.exe inside WinPE.
	PwshFiles map[string][]byte
	// WebView2Files are the extracted WebView2 fixed-version runtime files
	// (from FetchWebView2Files). When set, they are injected into boot.wim
	// so implorer can find the runtime at X:\winkit\webview2\ inside WinPE.
	WebView2Files map[string][]byte
	// ChromiumFiles are the extracted Chromium ARM64 browser files
	// (from FetchChromiumFiles). Injected at X:\winkit\chrome\ in WinPE.
	ChromiumFiles map[string][]byte
	// ContentShellFiles are the extracted content-shell ARM64 files
	// (from FetchContentShellFiles). Injected at X:\winkit\content-shell\.
	ContentShellFiles map[string][]byte
	// WorkDir holds the stage and inject trees; a temp dir when empty.
	WorkDir string
	// StartupCommand, when non-empty, is launched after WinPE/network/driver
	// initialization immediately before gosshd starts. It runs concurrently so
	// gosshd remains the foreground lifetime process and callers can inspect
	// bootstrap progress and failures over SSH.
	StartupCommand string
	// ChocolateyPackages lists packages to install via chocolatey during the
	// bootstrap phase. Requires .NET Framework and (on ARM64) WoW64 to be
	// present in boot.wim.
	ChocolateyPackages []string
}

BaseImageConfig describes a standalone bootable WinPE "base" image: virtio drivers (storage, serial, network) plus gosshd as the shell.

type BootSpec added in v0.3.0

type BootSpec struct {
	WinPEISO    string
	SharedFiles map[string][]byte
	WindowsISO  string
	VirtIOISO   string
	CPUs        int
	MemoryGB    int
	Accel       string
	OutputDir   string
}

BootSpec describes what the Runner needs to start a WinPE VM.

type BootloaderInfo added in v0.3.0

type BootloaderInfo struct {
	Arch string // "aarch64", "x86_64", or "unknown"
	Size int
}

BootloaderInfo describes the EFI bootloader extracted from a Windows ISO.

func ValidateBootloaderPE added in v0.3.0

func ValidateBootloaderPE(data []byte) (*BootloaderInfo, error)

ValidateBootloaderPE checks that raw EFI bootloader bytes are a valid aarch64 PE binary.

type BootstrapSteps added in v0.3.0

type BootstrapSteps struct {
	OK         []string
	Failed     []string
	Unfinished []string
}

BootstrapSteps is the guest's first-logon provisioning, read back from its own transcript.

func ParseBootstrapSteps added in v0.3.0

func ParseBootstrapSteps(transcript string) BootstrapSteps

ParseBootstrapSteps reads step outcomes out of a bootstrap transcript.

func (BootstrapSteps) SSHReady added in v0.3.0

func (s BootstrapSteps) SSHReady() bool

SSHReady reports whether the guest got far enough for the build to talk to it: sshd installed AND started.

type BuildConfig added in v0.3.0

type BuildConfig struct {
	WindowsISO string
	VirtIOISO  string
	PwshFiles  map[string][]byte
	OutputDir  string

	OpenSSH bool
	VirtIO  bool

	// GosshdExe is a cross-compiled gosshd binary (CrossCompileGosshd) to
	// ship on the shared volume as GosshdVolumeName. Win32-OpenSSH cannot
	// serve sessions in WinPE, so this is the base image's SSH server.
	GosshdExe string

	SerialPort string
}

BuildConfig describes what to bake into a bootable WinPE artifact.

type BuildResult added in v0.3.0

type BuildResult struct {
	WinPEISO    string
	SharedFiles map[string][]byte
}

BuildResult holds paths to the artifacts the caller needs to boot.

func Build added in v0.3.0

func Build(cfg BuildConfig) (*BuildResult, error)

Build produces a bootable WinPE ISO and a shared file map. The caller packs SharedFiles into whatever volume format their hypervisor needs (e.g. FAT qcow2 for QEMU, VHD for Hyper-V).

type DWordCheck added in v0.3.0

type DWordCheck struct {
	KeyPath   string
	ValueName string
	Expected  uint32
	Optional  bool
}

DWordCheck describes a single registry DWORD expectation for VerifyRegistry.

type Feature added in v0.3.0

type Feature struct {
	Name    string
	Detail  string
	Present bool
}

Feature is one capability-probe result within a FeatureGroup.

type FeatureGroup added in v0.3.0

type FeatureGroup struct {
	Name  string
	Items []Feature
}

FeatureGroup is a labelled set of related capability probes over a WinPE boot.wim image, as reported by InspectFeatures.

func InspectFeatures added in v0.3.0

func InspectFeatures(wimPath string, imageNum int) ([]FeatureGroup, error)

InspectFeatures probes a WIM image for the capabilities winkit cares about, grouped for display. imageNum selects the image (2 = the WinPE boot.wim convention; 1 = install.wim's first edition). Missing pieces are reported, never fatal — the whole point is to show what an image does and does not carry.

type Guest added in v0.3.0

type Guest interface {
	ReadSharedFile(path string) ([]byte, error)
	Stop() error
	ScreenshotDir() string
	Done() <-chan struct{}
	TakeScreenshot() (string, error)
}

Guest is a handle to a running WinPE VM.

type GuestEvent added in v0.3.0

type GuestEvent struct {
	TS    time.Time `json:"ts"`
	Event string    `json:"event"`

	Stage   string    `json:"stage,omitempty"`   // event=stage
	Status  string    `json:"status,omitempty"`  // ok / fail / absent / skipped / missing / already_running
	Error   string    `json:"error,omitempty"`   // present when Status=fail
	Service string    `json:"service,omitempty"` // probe_service / try_start
	Op      string    `json:"op,omitempty"`      // dism_progress / dism_output
	Line    string    `json:"line,omitempty"`    // dism_output
	Pct     *int      `json:"pct,omitempty"`     // dism_progress
	Exit    *int      `json:"exit,omitempty"`    // command exit codes
	RAM     *RAMStats `json:"ram,omitempty"`

	// Fields is the full decoded object, including keys not modeled above
	// (sc, start, source, known, pwsh, present, content, path, pid, bytes,
	// out, hvEvents, vidEvents, hvVendor, shared, ...).
	Fields map[string]any `json:"-"`
	// Raw is the original line, for logs and error reports.
	Raw string `json:"-"`
}

GuestEvent is one line of the structured build log (build.jsonl) that guest scripts stream over the second virtio-serial port. Every emitter goes through winkit-json (templates/partials/struct-json.tmpl and wim-builder.ps1.tmpl), so the wire shape is a flat JSON object with ts + event and event-specific extras.

The typed fields cover what host-side logic branches on; Fields holds the complete decoded object so nothing an emitter sends is lost.

func Failures added in v0.3.0

func Failures(events []GuestEvent) []GuestEvent

Failures returns the events reporting Status=fail.

func ParseGuestEvents added in v0.3.0

func ParseGuestEvents(r io.Reader) (events []GuestEvent, skipped int, err error)

ParseGuestEvents reads structured guest events from a JSONL stream. The stream is written by a guest that can lose power mid-line and is interleaved by QEMU chardev buffering, so malformed lines are counted and skipped, never fatal — losing one event must not hide the rest.

func ReadGuestEvents added in v0.3.0

func ReadGuestEvents(path string) (events []GuestEvent, skipped int, err error)

ReadGuestEvents parses a build.jsonl file. A missing file yields no events and no error: a guest that never opened the structured port leaves the file empty, and that is a normal outcome.

func (*GuestEvent) UnmarshalJSON added in v0.3.0

func (e *GuestEvent) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes both the typed view and the lossless Fields map.

type GuestLog added in v0.3.0

type GuestLog struct {
	Name    string
	Content []byte
	Err     error
}

GuestLog is one file the guest wrote to the answer volume, or the reason it could not be read.

func CollectGuestLogs added in v0.3.0

func CollectGuestLogs(answerImagePath string) []GuestLog

CollectGuestLogs reads every log the guest may have written to the answer volume. It always returns one entry per known log so the caller can report absence as clearly as content.

type ISOInfo added in v0.3.0

type ISOInfo struct {
	Size       int64
	Format     string // "udf", "iso9660", or "unknown"
	HasBootEFI bool
}

ISOInfo holds the result of an ISO preflight check.

func ISOPreflight added in v0.3.0

func ISOPreflight(isoPath string) (*ISOInfo, error)

ISOPreflight validates a Windows ISO without parsing its filesystem.

type PayloadConfig

type PayloadConfig struct {
	// DriverINFs are loaded with drvload before setup.exe starts. Usually
	// empty: NVMe and USB storage have inbox Windows ARM64 drivers, so
	// injection is only needed for extras like virtio-net.
	DriverINFs []string
	// SerialPort is the guest device path for the unified COM2 serial port.
	// Carries both progress markers and structured JSON (build.jsonl feed).
	// Uses the inbox serial.sys driver (pci-serial 16550), available in all
	// three phases (WinPE, second-pass Setup, desktop) without drvload.
	SerialPort string
	// WPEInit causes the bootstrap to call wpeinit before anything else.
	// Required when booting WinPE standalone (no setup.exe) — without it,
	// serial ports and other hardware are not initialized.
	WPEInit bool
	// PollSeconds is how often the agent checks for a new command (default 5).
	PollSeconds int
	// SyncAgent causes the bootstrap to run the agent synchronously (blocking)
	// instead of detached. Required when booting WinPE standalone (no
	// setup.exe): without it, winpeshl.ini returns after bootstrap.cmd and
	// WinPE reboots immediately.
	SyncAgent bool
}

PayloadConfig parameterises the generated WinPE payload scripts.

type RAMStats added in v0.3.0

type RAMStats struct {
	TotalMB float64 `json:"total_mb"`
	UsedMB  float64 `json:"used_mb"`
	FreeMB  float64 `json:"free_mb"`
}

RAMStats is the guest memory snapshot attached to structured events. PowerShell's math emits these as floats ("total_mb":4087.0), so the fields must not be integers.

type RegistryKeyWrite added in v0.3.0

type RegistryKeyWrite struct {
	HivePath string
	KeyPath  string
	Spec     *regedit.Key
}

RegistryKeyWrite describes a key+value write to a registry hive inside a WIM image.

type RegistryPatch added in v0.3.0

type RegistryPatch struct {
	// HivePath is the path inside the WIM image
	// (e.g. `\Windows\System32\config\SYSTEM`).
	HivePath string
	// Patches are the DWORD values to overwrite.
	Patches []regedit.DWordPatch
}

RegistryPatch describes a set of DWORD modifications to apply to a registry hive inside a WIM image. The hive is extracted to a temp file, patched, and written back.

type RunConfig added in v0.3.0

type RunConfig struct {
	Build              BuildConfig
	PollInterval       time.Duration
	Timeout            time.Duration
	ScreenshotInterval time.Duration

	// CPUs and MemoryGB size the builder VM (runner defaults apply when 0).
	CPUs     int
	MemoryGB int
}

RunConfig configures the full build-boot-extract cycle.

type RunResult added in v0.3.0

type RunResult struct {
	DevcellWim []byte
	GuestLogs  map[string]string

	// Events is the parsed structured guest log (build.jsonl), populated
	// when the guest exposes its path. SkippedEvents counts malformed
	// lines (partial writes at power-loss are normal, not fatal).
	Events        []GuestEvent
	SkippedEvents int
}

RunResult holds the output of a completed WIM builder run.

func Run added in v0.3.0

func Run(ctx context.Context, runner Runner, cfg RunConfig) (*RunResult, error)

Run executes the full WIM builder cycle: build WinPE artifacts, boot via runner, poll for completion, extract the serviced winkit.wim.

type Runner added in v0.3.0

type Runner interface {
	Boot(ctx context.Context, spec BootSpec) (Guest, error)
}

Runner boots a WinPE guest from the artifacts winpe.Build() produces. Implementations are hypervisor-specific (QEMU, libvirt, Hyper-V).

type SharedFileNotFoundError added in v0.3.0

type SharedFileNotFoundError struct {
	Path string
}

SharedFileNotFoundError is returned when a file is not present on the shared volume.

func (*SharedFileNotFoundError) Error added in v0.3.0

func (e *SharedFileNotFoundError) Error() string

type WimPatchSet added in v0.3.0

type WimPatchSet struct {
	// ImageNum is the 1-based WIM image index to patch.
	ImageNum int

	// Files maps WIM target paths to host filesystem source paths.
	// Each entry adds a single file.
	Files map[string]string

	// Trees maps WIM target directories to host filesystem source
	// directories. Each entry adds the directory recursively.
	Trees map[string]string

	// DWordPatches are registry DWORD patches grouped by hive path inside
	// the WIM (e.g. `\Windows\System32\config\SYSTEM`).
	DWordPatches []RegistryPatch

	// KeyWrites are full registry key writes (any value type) grouped by
	// hive path inside the WIM.
	KeyWrites []RegistryKeyWrite
}

WimPatchSet describes modifications to apply to a single WIM image.

func DWMServicePatchSet added in v0.3.0

func DWMServicePatchSet() WimPatchSet

DWMServicePatchSet registers the Desktop Window Manager service and its DirectX dependencies in the boot.wim SYSTEM hive. DWM runs as a SYSTEM service; on full Windows it is auto-started by the session manager. In WinPE we register it for demand-start so winkit-service can start it at the right point in the boot sequence (after display drivers are loaded).

The patch set also writes SOFTWARE hive keys that configure DWM for software composition (WARP). Without these, DWM may refuse to enable composition when no hardware WDDM adapter reports display sources.

func HostnamePatchSet added in v0.3.0

func HostnamePatchSet(imageNum int, hostname string) WimPatchSet

HostnamePatchSet returns a WimPatchSet that pre-seeds the ComputerName registry value in the SYSTEM hive. wpeinit reads this at boot and uses it instead of generating a random MININT-xxxxxxx name.

func PagefilePatchSet added in v0.3.0

func PagefilePatchSet() WimPatchSet

PagefilePatchSet returns a WimPatchSet that disables the WinPE auto-pagefile by clearing the PagingFiles registry value. Without this, Session Manager creates a 50 MB pagefile on the ramdisk and wpeutil CreatePageFile refuses to add a second one (0x80070061). With PagingFiles empty, the init/activate code can create a large pagefile on the persistent NTFS data disk via wpeutil.

func PeAgentPatchSet added in v0.3.0

func PeAgentPatchSet(imageNum int, serviceExePath string, registryWrite bool) WimPatchSet

PeAgentPatchSet returns a WimPatchSet that installs the pe-agent into a WIM image. serviceExePath is the host path to winkit-service.exe.

For install.wim, this places the binary at \winkit-service.exe and adds a SetupExecute registry entry so smss.exe launches it during the second-pass install phase.

For boot.wim, pass registryWrite=false: the pe-agent is started by gosshd.cmd, so no registry modification is needed. The binary is still placed at \winkit\winkit-service.exe (matching the inject tree layout).

func SerialDriverPatchSet added in v0.3.0

func SerialDriverPatchSet() WimPatchSet

SerialDriverPatchSet returns a WimPatchSet that registers the Serial and Serenum kernel drivers in the SYSTEM hive so PnP can start them when it detects matching PCI devices during wpeinit.

func WSL1EnginePatchSet added in v0.3.0

func WSL1EnginePatchSet(wslDir string) (WimPatchSet, error)

WSL1EnginePatchSet maps an msiextract WSL directory into boot.wim image 2. wslDir must point at the extracted PFiles64/WSL directory.

func WSL1PEPatchSet added in v0.3.0

func WSL1PEPatchSet(workDir, wslDir string) (WimPatchSet, error)

WSL1PEPatchSet combines the packaged WSL engine, offline service/COM registry, and first-boot helpers needed by the standard PE+WSL1 build.

func WSL1ServicePatchSet added in v0.3.0

func WSL1ServicePatchSet() WimPatchSet

WSL1ServicePatchSet registers the inbox WSL1 drivers and the packaged WSLService. The service and COM values mirror the official WSL ARM64 MSI; the only path adaptation is installing into WinPE's X: ramdisk.

The patch targets boot.wim image 2. Callers apply it via PatchWIM after TransferWSL1Files has injected the binaries.

type WimPrepConfig added in v0.3.0

type WimPrepConfig struct {
	// Ops is the ordered list of servicing operations.
	Ops []WimPrepOp

	// WimImageIndex is the boot.wim image to service (default 2 =
	// "Microsoft Windows Setup").
	WimImageIndex int

	// SourceWim is the filename of the WIM to service on the shared
	// volume. Default: "boot.wim".
	SourceWim string

	// TargetWim is the filename of the output WIM on the shared volume.
	// When equal to SourceWim the copy step is skipped (DISM commits
	// in place). Default: "winkit.wim".
	TargetWim string

	// UnmountInstallWim controls whether the builder explicitly unmounts
	// install.wim before committing boot.wim. Default false: the mount is
	// read-only and the WinPE VM is discarded after the build, so an
	// explicit unmount just burns wall-clock (15 min on TCG/ARM64).
	UnmountInstallWim bool
}

WimPrepConfig parameterises the WIM builder pipeline. A builder WinPE boots, mounts boot.wim and install.wim, applies the listed operations via DISM offline servicing, and writes the result as winkit.wim.

type WimPrepOp added in v0.3.0

type WimPrepOp struct {
	// Feature enables a Windows feature via
	//   dism /Image:<mount> /Enable-Feature /FeatureName:<Feature> /All
	//     /Source:<install.wim-mount>\Windows /LimitAccess
	// Mutually exclusive with Package, Capability, and Driver.
	Feature string

	// Package adds a .cab or .mum package via
	//   dism /Image:<mount> /Add-Package /PackagePath:<Package>
	// The path is relative to the install.wim mount point.
	Package string

	// Capability adds a Windows capability via
	//   dism /Image:<mount> /Add-Capability /CapabilityName:<Capability>
	//     /Source:<install.wim-mount>
	Capability string

	// Driver injects a driver via
	//   dism /Image:<mount> /Add-Driver /Driver:%VIRTIO%\<Driver> /Recurse
	// The path is relative to the virtio-win ISO root (e.g. "NetKVM\w11\ARM64").
	// The script probes for the virtio-win drive letter automatically when
	// any op uses this field.
	Driver string
}

WimPrepOp describes a single DISM offline servicing operation to apply to a WIM image. The builder WinPE script iterates these in order.

func OpenSSHPrepOps added in v0.3.0

func OpenSSHPrepOps() []WimPrepOp

OpenSSHPrepOps returns the servicing operations to add OpenSSH to a boot.wim. Uses the capability name rather than a feature.

func VirtIODriverPrepOps added in v0.3.0

func VirtIODriverPrepOps() []WimPrepOp

VirtIODriverPrepOps returns the servicing operations to inject the ARM64 virtio-win drivers (NetKVM, vioserial, vioscsi) into a WIM image. The paths are relative to the virtio-win ISO root.

func WSL1FeaturePrepOps added in v0.3.0

func WSL1FeaturePrepOps() []WimPrepOp

WSL1FeaturePrepOps returns the servicing operations to enable the Microsoft-Windows-Subsystem-Linux optional feature (lxcore.sys / lxss.sys) in a WIM via DISM offline servicing. The feature's backing packages are discovered from install.wim and imported before Enable-Feature runs.

Note: this only works when servicing install.wim (SourceWim="install.wim", WimImageIndex=1). Enabling inbox optional features in boot.wim fails with CBS 0x800f080c because boot.wim's parent package is Microsoft-Windows-WinPE-Package, not Microsoft-Windows-Foundation-Package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL