Files
pxe-server/docs/PESETUP-INTERNALS.md
cproudlock 936902dc4d Document how PESetup.exe actually works
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.
2026-08-06 08:04:48 -04:00

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:

  1. Secure boot is mandatory. SecurebootEnabled != 1 fails the step outright.
  2. Reads Control\LanguagePacks.json.
  3. SystemHelpers.GetModel() and GetManufacturer() off WMI.
  4. Reads Control\HardwareDriver.json and Control\hw_applications.json. NOTE: it reads HardwareDriver.json, not the hw_drivers.json that also sits in that folder. Editing the wrong one changes nothing.
  5. 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.
  6. Otherwise GetDriverByModel (below).
  7. If Control\skip.json exists, 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\Applications on 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 in Applications fails 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 *.tag from the tools dir to w:\windows\system32\. This is how build/media tags reach the OS.
  • CopyLogFiles("X:\\") and CopyXMLFile("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

  1. 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.
  2. Matching is substring, first match wins. A token like 7020 matches any model string containing 7020. Order in the JSON decides ties.
  3. The family filter knows only three Dell lines. Anything else - MicroPCs, NUCs, non-Dell - skips the filter and depends entirely on modelswminame substrings being right.
  4. 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\Applications on the media is copied to W:\Deploy\Applications by PESetup itself, and is readable at C:\Deploy\Applications at first boot.
  • "Display MicroPCs failed because the applied volume was not W:." Worth re-testing. A missing HardwareDriver.json match 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