Files
pxe-server/docs/PESETUP-INTERNALS.md
cproudlock d04865da0f docs: record the PESetup version actually in production; match LogonCount to the live unattend
PESETUP-INTERNALS.md was written from a decompiled 4.0.0.17. The media in
production reports 4.0.0.20 in its own log. Rather than restate the document as
4.0.0.20, which would claim a re-derivation that has not happened, it now names
both: line-level claims are 4.0.0.17, and the behaviour re-observed on bay
579C144 on 2026-08-06 is listed so a reader knows which parts are confirmed
current - media drive Z:, W: created by PrepareDisk and used for every copy
destination, the fallback Deploy\FlatUnattendW10.xml being the unattend that
loads, and driver selection by model.

FlatUnattendW10-shopfloor.xml carried LogonCount 7 while the live shopfloor
unattend has 12, and Run-ShopfloorSetup.ps1's comment about topping up the
autologon budget already said 12. The live value is the real one, so the repo
follows it.

Both files still lint clean under scripts/lint-unattend.py.
2026-08-06 12:25:30 -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).

Version in production: 4.0.0.20, as of 2026-08-06 — the media reports it in its own log (AppVersion: 4.0.0.20). Everything in this document was verified against a 4.0.0.20 run on bay 579C144 that day: the media drive resolved to Z:\, W: was created by PrepareDisk and used for every copy destination, the fallback unattend at Deploy\FlatUnattendW10.xml was the one loaded, and driver selection matched win11_optiplexd13mlk7020_a09.zip by model. The decompiled detail below has not been re-derived from the 4.0.0.20 binary, so treat exact line-level claims as 4.0.0.17 and the observed behaviour as current.

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