Written from the decompiled assembly rather than from observation. Three long-standing beliefs about this tool are wrong, and each has cost real debugging time: W: is not a guess. PESetup hardcodes it in nine places - every copy destination, the DISM offline sessions, bcdboot, reagentc - and creates it during its own disk preparation. startnet.cmd's volume finder, diskpart reassignment and W: wait loop are machinery built around a problem that does not exist. The copy steps do not filter. CopyPackages copies the whole of Deploy\Applications recursively to W:\Deploy\Applications - no manifest, no unattend parsing, no extension or size rules. Anything dropped there reaches the target, which is the basis for staging our own payload without the enrollment share. It is also fail-fast: one unreadable file fails the whole step. A missing driver match is a WARNING, not an error. GetDriverByModel does a substring test of comma-separated tokens with first-match-wins, the family filter knows only Latitude, OptiPlex and Precision, and a single 'virtual platform' entry in the catalogue fails every physical machine. A miss lets imaging finish with no drivers, so no NIC, so DNS failures at first boot - symptoms far from the cause, and easily misattributed to the drive letter. Includes the bundle-extraction recipe so the next person can re-derive all of this instead of trusting this document.
12 KiB
PESetup.exe internals
What GE Image Setup actually does, start to finish. Written from the decompiled assembly, not from observation, because several long-standing beliefs about this tool turned out to be wrong and cost weeks of debugging.
Version documented: 4.0.0.17 (Sources/PESetup.exe, PE32+ native apphost
wrapping a .NET 6 single-file bundle, 468 embedded files).
How to re-derive this
The managed code is not directly readable - the outer PE has no managed
metadata, so ILSpy refuses it. Extract PESetup.dll from the bundle first:
# .NET single-file bundle: signature is SHA-256 of ".net core bundle";
# the int64 EIGHT BYTES BEFORE it is the bundle header offset.
SIG = bytes([0x8b,0x12,0x02,0xb9,0x6a,0x61,0x20,0x38,
0x72,0x7b,0x93,0x02,0x14,0xd7,0xa0,0x32])
i = data.find(SIG)
header = struct.unpack_from('<q', data, i - 8)[0]
# header: uint32 major, uint32 minor, int32 count, 7-bit-prefixed bundle id,
# then (v2+) 4 x int64 deps/runtimeconfig, then uint64 flags.
# each entry: int64 offset, int64 size, (v6+) int64 compressedSize,
# byte type, 7-bit-prefixed path. Compressed entries are raw
# deflate (zlib.decompress(raw, -15)).
Then ilspycmd -t <TypeName> PESetup.dll.
Paths it hardcodes
From PESetup.Models.GlobalSettings. All are relative to the MEDIA drive
(Z:\, whichever winpeapps share startnet mapped) unless stated.
ControlDir \Deploy\Control
ToolsDir \Deploy\Tools\GE
SoftwarePackagesDir \Deploy\Applications
HWPackagesDir \Deploy\HW_Apps
UnattendFile \Deploy\Tools\GE\XML\FlatUnattendW10.xml
UnattendFile2 \Deploy\FlatUnattendW10.xml (fallback)
DiskPartDir <exe dir>\DiskPart
ConfigSkipPackagesFile skip.json
ConfigDisableAutoStartFile disableauto.json
ExpirationDuration 30 days
AutoStartCountDown 31
MinRequiredSpaceWithoutCompression 128849018880 (120 GB)
W: IS HARDCODED, EVERYWHERE. Not derived, not configurable, not a
convention this project invented:
CopyPackages destDir = "W:\\" + SoftwarePackagesDir
CopyHWPackages destDir = "W:\\" + HWPackagesDir
CopyTools destDir = "W:\\GE"
CopyDrivers destinationPath = "W:\\Drivers\\" + <zip name without extension>
ApplyImage destination = "W:\\"
CreateBCD "W:\\Windows\\System32\\bcdboot.exe", "W:\\Windows /l en-US"
ApplyPackages DismApi.OpenOfflineSession("W:\\")
CopyLogs tags -> "w:\\windows\\system32\\"
PESetup's own disk preparation is what makes W: exist. Any downstream script
that hunts for "the applied volume" is solving a problem this tool does not
have. diskpart list volume on a machine mid-image shows Volume 0 W Windows NTFS 237GB Healthy.
The five working steps
Keys as they appear in the log: workingstep_gatherdata, workingstep_selectos,
workingstep_prepare, workingstep_copy, workingstep_apply,
workingstep_finalize, workingstep_reboot.
1. GATHER DATA
Four operations, all automatic:
| Operation | What it establishes |
|---|---|
GatherDataMediaDrive |
ImageInfo.MediaDrive, e.g. Z:\ |
GatherDataBootDrive |
ImageInfo.BootDrive, always X:\ (WinPE RAM disk) |
GatherDataSelection |
BIOS version/type, serial, secure boot, OS, languages, model, driver |
GatherDataImageDisk |
picks the physical disk and logs its partitions |
GatherDataSelection is where most decisions are made:
- Secure boot is mandatory.
SecurebootEnabled != 1fails the step outright. - Reads
Control\LanguagePacks.json. SystemHelpers.GetModel()andGetManufacturer()off WMI.- Reads
Control\HardwareDriver.jsonandControl\hw_applications.json. NOTE: it reads HardwareDriver.json, not thehw_drivers.jsonthat also sits in that folder. Editing the wrong one changes nothing. - Virtual platform check. If ANY driver entry has a manufacturer containing
"virtual platform", the tool goes into virtual-only mode: it takes
list[0]as the driver and then REQUIRES the machine to look virtual (model/serial/BIOS containing VIRTUAL, VMWARE, XEN, QEMU, VirtualBox, vmw). On real hardware that is a hard failure. A stray "virtual platform" entry in the catalogue therefore breaks imaging for every physical machine. - Otherwise
GetDriverByModel(below). - If
Control\skip.jsonexists, package installation is skipped.
2. SELECT OS
Operator-facing. Warns when the media holds an LTSC image ("99% of the time only
for ShopFloor"). Also shows days-to-expiry - the media expires 30 days after
build (ExpirationDuration).
3. PREPARE
| Operation | What it does |
|---|---|
PrepareDoDisking |
copies <exedir>\DiskPart\diskpartEFI.txt to X:\diskpartEFI<n>.txt, then cmd /c DISKPART /S X:\diskpartEFI<n>.txt |
PrepareUnattend |
loads the unattend, substitutes, saves to X:\Unattend.xml |
PrepareUnattend in detail:
text = MediaDrive + UnattendFile; // \Deploy\Tools\GE\XML\FlatUnattendW10.xml
if (!File.Exists(text)) text = MediaDrive + UnattendFile2; // \Deploy\FlatUnattendW10.xml
xml.Load(text);
xml.InnerXml = xml.InnerXml.Replace("%serialnumber%", imageinfo.Serialnumber);
xml.InnerXml = xml.InnerXml.Replace("*arch*", arch);
SetPackages(arch, xml, imageinfo); // needs Control\Packages.xml
xml.Save("X:\\Unattend.xml");
Two tokens are substituted: %serialnumber% and *arch*. SetPackages merges
in servicing packages from Control\Packages.xml and Control\PackageGroups.xml.
The unattend that is USED is the one on the media at Tools\GE\XML\ if present,
otherwise Deploy\FlatUnattendW10.xml. A shopfloor variant only takes effect if
it occupies one of those two paths.
4. COPY
Four operations. None of them filters, and none of them reads the unattend.
| Operation | Source | Destination | Rule |
|---|---|---|---|
CopyTools |
Z:\Deploy\Tools\GE |
W:\GE |
whole directory, recursive |
CopyPackages |
Z:\Deploy\Applications |
W:\Deploy\Applications |
whole directory, recursive |
CopyHWPackages |
Z:\Deploy\HW_Apps |
W:\Deploy\HW_Apps |
whole directory; skipped if IsVirtual or HWApps == null |
CopyDrivers |
one .zip chosen by model |
W:\Drivers\<zipname> |
unzipped, not copied |
ExecuteInternalCopy walks GetDirectoriesRecursive(source, includeSubDirs: true) then Directory.GetFiles(item, "*"). Every file, every subdirectory. It
clears the read-only attribute on each copy.
Consequences worth designing around:
- Anything dropped into
Deploy\Applicationson the media lands on the target. No manifest, no registration, no unattend reference required. - The copy is FAIL-FAST. One exception on one file returns false and fails the
whole step with
copy_packages_error. A locked or unreadable file inApplicationsfails imaging, it does not get skipped. - Progress is computed from total directory size up front, so bulky additions visibly lengthen this phase.
5. APPLY
| Operation | What it does |
|---|---|
ApplyImage |
install.wim from OperatingSystem.json's destinationDir, at wimindex, applied to W:\ |
ApplyPackages |
DISM offline session on W:\, adds servicing packages; logs to X:\ApplyOSPatch_<n>.log |
ApplyLanguagePacks |
same pattern, X:\ApplyLanguagePack_<n>.log |
ApplyUnattend |
DISM offline session on W:\, applies X:\Unattend.xml; logs to X:\ApplyUnattend.log |
CreateBCD |
W:\Windows\System32\bcdboot.exe W:\Windows /l en-US |
CopyWinRE |
reagentc /setreimage /path T:\Recovery\WindowsRE /target W:\Windows |
All DISM work uses W:\imagetemp as scratch and deletes it afterwards.
6. FINALIZE
CopyLogs:
CopyTagFiles(ToolsDir)- every*.tagfrom the tools dir tow:\windows\system32\. This is how build/media tags reach the OS.CopyLogFiles("X:\\")andCopyXMLFile("X:\\")- the PESetup log and the generated unattend are preserved.
Driver selection, in full
This is the part most worth understanding, because a miss is nearly silent.
// GatherDataSelection
List<HardwareDriversRootObject> list =
JSONHelpers.ReadJSON_Driver(MediaDrive + ControlDir + "\\HardwareDriver.json");
...
imageinfo.HWDriver = GetDriverByModel(list, imageinfo.Model);
if (imageinfo.HWDriver == null) {
// status = Warning, message "driver for [MODEL] not found"
return operationResult;
}
private static HardwareDriversRootObject? GetDriverByModel(List<...> drivers, string model)
{
string modelFamily = "";
if (model.ToUpper().Contains("LATITUDE")) modelFamily = "Latitude";
if (model.ToUpper().Contains("OPTIPLEX")) modelFamily = "Optiplex";
if (model.ToUpper().Contains("PRECISION")) modelFamily = "Precision";
return drivers.Where(d => {
if (!string.IsNullOrEmpty(modelFamily) &&
!d.family.ToLower().Contains(modelFamily.ToLower())) return false;
foreach (string token in d.modelswminame.Split(','))
if (model.ToLower().Contains(token.ToLower())) return true; // SUBSTRING
return false;
}).ToList()?.FirstOrDefault();
}
Then:
sourceFilePath = MediaDrive + HWDriver.destinationDir.Replace("*destinationdir*","")
+ "\\" + HWDriver.fileName;
destinationPath = "W:\\Drivers\\" + Path.GetFileNameWithoutExtension(sourceFilePath);
StartUnzipAsync(...)
Four traps in that logic
- A miss is a WARNING, not a failure. Imaging continues and the machine comes up with no drivers - no NIC, no WiFi, so DNS fails and anything network-dependent at first boot fails with it. Symptoms appear far from the cause.
- Matching is substring, first match wins. A token like
7020matches any model string containing 7020. Order in the JSON decides ties. - The family filter knows only three Dell lines. Anything else - MicroPCs,
NUCs, non-Dell - skips the filter and depends entirely on
modelswminamesubstrings being right. - One "virtual platform" entry hijacks the whole catalogue (see GATHER DATA step 5) and fails every physical machine.
What this means for startnet.cmd
Three beliefs encoded in startnet.cmd do not survive contact with the source:
- "The applied volume might not be W:." PESetup hardcodes W: in nine places
and creates it during disk prep. The volume finder, the diskpart
reassignment and the
W:wait loop are machinery around a non-problem. - "We must copy our payload from the enrollment share after imaging." Anything
in
Deploy\Applicationson the media is copied toW:\Deploy\Applicationsby PESetup itself, and is readable atC:\Deploy\Applicationsat first boot. - "Display MicroPCs failed because the applied volume was not W:." Worth
re-testing. A missing
HardwareDriver.jsonmatch produces the same end-state - no drivers, no network - via a completely different route, and that route only logs a warning.
Files PESetup reads from the media
Deploy\Control\HardwareDriver.json driver catalogue (NOT hw_drivers.json)
Deploy\Control\hw_applications.json per-model applications
Deploy\Control\OperatingSystem.json OS list, wim path, wimindex
Deploy\Control\LanguagePacks.json language packs
Deploy\Control\packages.json servicing packages
Deploy\Control\Packages.xml unattend package merge
Deploy\Control\PackageGroups.xml package grouping
Deploy\Control\Media.tag media identity
Deploy\Control\skip.json presence = skip package install
Deploy\Control\disableauto.json presence = disable autostart
Deploy\Tools\GE\XML\FlatUnattendW10.xml preferred unattend
Deploy\FlatUnattendW10.xml fallback unattend