Files
pxe-server/playbook/startnet.cmd
cproudlock 8c21282024 Verify staging in WinPE, harvest imaging logs, make enrollment run once
Three changes aimed at the same failure mode: a bay that images green and is
silently unusable, diagnosed only by walking over and copying files off by hand.

VERIFY STAGING (startnet, at :pctype_done)
Checks pc-type.txt, Run-ShopfloorSetup.ps1, shopfloor-setup/common and
shopfloor-setup/<PCTYPE> exist on the applied volume before anything depends on
them, retries the small trees once, and prints a loud banner if the retry does
not fix it. The enrollment package is checked but never blindly re-pulled - it is
8 GB, so a miss is reported instead.
Done here because a tech is still at the machine: a short copy found in WinPE
costs 30 seconds, the same copy found at first logon costs 20 minutes, and found
never costs a rebuild. Four Display bays sat green at stage 2 for weeks.

HARVEST LOGS (startnet, after PESetup exits)
Collects X:\*.log, the generated X:\Unattend.xml, PESetup's own log from the
target's Panther directory and winpe-staging.log into
\<server>\enrollment\imaging-logs\<serial>\, plus a build-context.txt naming
PCTYPE, PPKG, machine number and media. All of it was being discarded at reboot.
Runs after PESetup exits so the logs are final, which means re-mapping Y: since
cleanup already dropped it. Best-effort throughout - a bay must never fail to
reboot because a log copy failed.

W: WAIT CAP 20 -> 45 MINUTES
270 polls instead of 120. Fine on NVMe either way, but a WIM apply can exceed 20
minutes on slow media, and the failure mode is the os_not_found banner plus
nothing staged. The loop still exits the moment the SYSTEM hive appears.

RUN-ENROLLMENT RUNS ONCE
Marker at C:\Enrollment\.ppkg-applied, written on exit 0 and also on 0x800700B7
ERROR_ALREADY_EXISTS. Observed running twice on 579C144; the second pass
re-applied a pending rename over the package's own and otherwise did nothing.

Verified: startnet parens balance, every goto resolves, 899 CRLF lines with no
bare LF; run-enrollment parses clean under the PowerShell parser. Deployed -
boot.wim md5 159c2a4d, live run-enrollment dce9d50a.
2026-08-06 14:18:25 -04:00

900 lines
43 KiB
Batchfile

@echo off
echo Please wait while 'WinPE' is being processed. This may take a few seconds.
wpeinit
powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
REM --- Wait for network (DHCP may take a moment after wpeinit) ---
echo Waiting for network...
:wait_net
ping -n 2 172.16.9.1 >NUL 2>&1
if errorlevel 1 goto wait_net
echo Network ready.
REM --- BIOS update check (runs before imaging menu) ---
REM Mounts [winpeapps_bios] share. CRITICAL: do NOT call check-bios.cmd
REM inside an `if exist (...)` parens block - CMD's variable scoping for
REM CALLed scripts inside parens does not propagate BIOS_STATUS back to
REM this script reliably. Use goto-flow instead so the CALL runs at the
REM top scope and BIOS_STATUS persists.
set BIOS_STATUS=No BIOS check (share unavailable)
net use B: \\172.16.9.1\winpeapps_bios /user:pxe-upload pxe /persistent:no 2>NUL
if not exist B:\check-bios.cmd goto :bios_check_done
echo.
echo Checking for BIOS updates...
call B:\check-bios.cmd
:bios_check_done
net use B: /delete 2>NUL
:menu
cls
echo.
echo ========================================
echo WinPE Setup Menu
echo ========================================
echo Firmware: %BIOS_STATUS%
echo.
echo Please select an option:
echo.
echo 1. GEA Standard
echo 2. GEA Engineer
echo 3. GEA Shopfloor (sub-menu)
echo.
echo ========================================
echo.
set /p choice=Enter your choice (1-3):
REM --- Shopfloor images (3,6,7) need GCCH enrollment + (for 3) PC-type sub-menu.
REM Choice 3 = GEA Shopfloor: drill into sub-menu first to pick the gea-shopfloor-*
REM sub-category, THEN the office menu, THEN machine number for collections+nocollections.
set PPKG=
if "%choice%"=="3" goto gea_shopfloor_submenu
if "%choice%"=="6" goto enroll_menu
if "%choice%"=="7" goto enroll_menu
goto enroll_staged
:gea_shopfloor_submenu
cls
set PCTYPE=
REM Data-driven PC-type menu: the PXE webapp maintains menu.json on the
REM enrollment share; select-shopfloor-type.ps1 renders it and writes the
REM chosen PCTYPE to X:\pctype.txt. Falls back to the baked-in menu below if
REM the share or the picker is unavailable (mirrors the CMM bay picker).
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no >NUL 2>NUL
del X:\pctype.txt 2>NUL
if exist "Y:\shopfloor-setup\select-shopfloor-type.ps1" powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Y:\shopfloor-setup\select-shopfloor-type.ps1" -MenuJson "Y:\shopfloor-setup\menu.json" -OutFile "X:\pctype.txt"
if exist X:\pctype.txt set /p PCTYPE=<X:\pctype.txt
if not "%PCTYPE%"=="" goto gea_shopfloor_have_type
:gea_shopfloor_submenu_fallback
cls
echo.
echo ========================================
echo GEA Shopfloor PC Sub-Type
echo ========================================
echo.
echo 1. Machine with Collections (eDNC + UDC + Plant Apps)
echo 2. Machine without Collections (eDNC + Plant Apps, no UDC)
echo 3. Common (Timeclock, Lab; WJ Shopfloor only)
echo 4. Keyence (VR-3000 / VR-5000 / VR-6000 microscope)
echo 5. CMM (Hexagon PC-DMIS + Protect Viewer)
echo 6. Genspect
echo 7. Heattreat (eDNC + HeatTreat app)
echo 8. Wax and Trace
echo 9. Display (kiosk dashboard)
echo 10. Part Marker (eDNC + Telesis Mark)
echo.
set /p ges_choice=Enter your choice (1-10):
if "%ges_choice%"=="1" set PCTYPE=gea-shopfloor-collections
if "%ges_choice%"=="2" set PCTYPE=gea-shopfloor-nocollections
if "%ges_choice%"=="3" set PCTYPE=gea-shopfloor-common
if "%ges_choice%"=="4" set PCTYPE=gea-shopfloor-keyence
if "%ges_choice%"=="5" set PCTYPE=gea-shopfloor-cmm
if "%ges_choice%"=="6" set PCTYPE=gea-shopfloor-genspect
if "%ges_choice%"=="7" set PCTYPE=gea-shopfloor-heattreat
if "%ges_choice%"=="8" set PCTYPE=gea-shopfloor-waxtrace
if "%ges_choice%"=="9" set PCTYPE=gea-shopfloor-display
if "%ges_choice%"=="10" set PCTYPE=gea-shopfloor-partmarker
if "%PCTYPE%"=="" goto gea_shopfloor_submenu_fallback
:gea_shopfloor_have_type
if "%PCTYPE%"=="gea-shopfloor-display" goto display_submenu
if "%PCTYPE%"=="gea-shopfloor-keyence" goto keyence_submenu
if "%PCTYPE%"=="gea-shopfloor-cmm" goto cmm_submenu
goto enroll_menu
:keyence_submenu
cls
echo.
echo ========================================
echo Keyence Model
echo ========================================
echo.
echo 1. VR-3000 G2 (older microscope/profilometer line)
echo 2. VR-5000 (mid-range)
echo 3. VR-6000 (current line)
echo.
set KEYENCEMODEL=
set /p kmod_choice=Enter your choice (1-3):
if "%kmod_choice%"=="1" set KEYENCEMODEL=vr3000
if "%kmod_choice%"=="2" set KEYENCEMODEL=vr5000
if "%kmod_choice%"=="3" set KEYENCEMODEL=vr6000
if "%KEYENCEMODEL%"=="" goto keyence_submenu
echo Keyence model: %KEYENCEMODEL%
goto enroll_menu
:cmm_submenu
cls
echo.
echo ========================================
echo CMM Bay Selection
echo ========================================
echo.
echo Loading CMM bay list from PXE share...
REM Mount enrollment share early so the picker can read the CSV.
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no >NUL 2>NUL
del X:\cmm-bay.txt 2>NUL
set CMMID=
set CMMVARIANT=standard
if not exist "Y:\installers-post\cmm\select-cmm-bay.ps1" goto cmm_picker_skip
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Y:\installers-post\cmm\select-cmm-bay.ps1" -ConfigPath "Y:\installers-post\cmm\cmm-bay-config.csv" -OutFile "X:\cmm-bay.txt"
:cmm_picker_skip
if exist X:\cmm-bay.txt set /p CMMID=<X:\cmm-bay.txt
if not "%CMMID%"=="" goto cmm_have_bay
echo.
echo Picker unavailable or cancelled. Falling back to manual entry.
set /p CMMID=Enter CMM ID (e.g. CMM1):
:cmm_have_bay
if "%CMMID%"=="" (
echo WARNING: no CMM ID entered. Defaulting to standard variant.
goto enroll_menu
)
echo CMM bay: %CMMID%
REM Resolve bay config (writes version.txt + doda.txt to %OSDRIVE%\Enrollment\cmm\
REM after %OSDRIVE% appears). Resolution deferred to after imaging starts - store
REM the CMMID for now and resolve in the post-imaging copy block below.
goto enroll_menu
:display_submenu
cls
echo.
echo ========================================
echo Display Kiosk Sub-Type
echo ========================================
echo.
echo 1. Dashboard (shop floor metrics dashboard)
echo 2. Lobby Display (lobby information screen)
echo.
set DISPLAYTYPE=
set /p disp_choice=Enter your choice (1-2):
if "%disp_choice%"=="1" set DISPLAYTYPE=Dashboard
if "%disp_choice%"=="2" set DISPLAYTYPE=Lobby
if "%DISPLAYTYPE%"=="" goto display_submenu
goto enroll_menu
:enroll_menu
cls
echo.
echo ========================================
echo GCCH Enrollment Profile
echo ========================================
echo.
echo 1. No Office
echo 2. Standard Office (x86)
echo 3. Standard Office (x64)
echo 4. Pro Plus Office (x86) with Access
echo 5. Pro Plus Office (x64) with Access
echo 6. Skip enrollment
echo.
set /p enroll=Enter your choice (1-6):
REM --- PPKG configuration (constructed at menu time, see docs) ---
REM Vendor ships one source PPKG; we construct the BPRT-tagged filename
REM by filling in Office, Region, Expiry, Version on the target copy.
REM The ACTIVE ppkg is read from \\172.16.9.1\enrollment\ppkg.conf, which the
REM PXE webapp writes on upload - so a new ppkg goes live with no startnet edit.
REM The baked-in values below are only a fallback if ppkg.conf is missing.
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no >nul 2>&1
set SOURCE_PPKG=
set TARGET_PPKG=
set PPKG_VER=
set PPKG_EXP=
if exist "Y:\ppkg.conf" (
for /f "usebackq tokens=1,* delims==" %%A in ("Y:\ppkg.conf") do (
if /i "%%A"=="SOURCE_PPKG" set "SOURCE_PPKG=%%B"
if /i "%%A"=="PPKG_VER" set "PPKG_VER=%%B"
if /i "%%A"=="PPKG_EXP" set "PPKG_EXP=%%B"
if /i "%%A"=="TARGET_PPKG" set "TARGET_PPKG=%%B"
)
)
if "%SOURCE_PPKG%"=="" set SOURCE_PPKG=GCCH_Prod_SFLD_v4.16.ppkg
if "%PPKG_VER%"=="" set PPKG_VER=v4.16
if "%PPKG_EXP%"=="" set PPKG_EXP=20260831
set REGION=US
set OFFICE=
if "%enroll%"=="1" set OFFICE=NoOffice
if "%enroll%"=="2" set OFFICE=StdOffice-x86
if "%enroll%"=="3" set OFFICE=StdOffice-x64
if "%enroll%"=="4" set OFFICE=ProPlusOffice-x86
if "%enroll%"=="5" set OFFICE=ProPlusOffice-x64
if "%enroll%"=="6" set OFFICE=
if "%enroll%"=="" goto enroll_menu
set PPKG=
REM Target filename for the staged package.
REM
REM The SFLD-era scheme encoded an Office variant in the name
REM (GCCH_Prod_SFLD_<Office>_<Region>_Exp_<date>_<ver>.ppkg) because one source
REM package was renamed per Office choice. MCL packages do not work that way -
REM Office is installed by Install-Office.ps1 inside the package - and renaming
REM an MCL package into the SFLD scheme just produces a misleading filename.
REM
REM So: honour TARGET_PPKG from ppkg.conf when present, otherwise keep the
REM source filename unchanged. The SFLD construction remains only as a fallback
REM for a legacy SFLD source package.
if not "%TARGET_PPKG%"=="" (
set PPKG=%TARGET_PPKG%
) else (
REM find, NOT findstr: findstr.exe is not in this WinPE image (find.exe is,
REM and startnet already uses it for the PESetup wait). findstr fails with
REM "not recognized", leaves errorlevel 9009, and "if errorlevel 1" then
REM takes the NON-SFLD branch for an SFLD package - so the staged ppkg keeps
REM its source name and never gets the BPRT-tagged one.
echo %SOURCE_PPKG% | find /I "_SFLD_" >NUL
if errorlevel 1 (
set PPKG=%SOURCE_PPKG%
) else (
if not "%OFFICE%"=="" set PPKG=GCCH_Prod_SFLD_%OFFICE%_%REGION%_Exp_%PPKG_EXP%_%PPKG_VER%.ppkg
)
)
REM --- 2026-05-04 rename reorg: PCTYPE is set by gea_shopfloor_submenu above
REM (single string, e.g. gea-shopfloor-collections). pc-subtype.txt is no
REM longer written. DISPLAYTYPE IS still written (display-type.txt) when
REM PCTYPE=gea-shopfloor-display because Install-KioskApp.cmd needs Lobby
REM vs Dashboard to choose installer + Get-PCProfile builds Display-{type}
REM profile key.
set PCSUBTYPE=
REM --- Machine number / asset tag (only PC types that key per-machine config) ---
REM collections + nocollections = 4-digit machine number for shopfloor identification.
REM waxtrace = asset tag like WJRP2335 used to match per-machine cal ISO during
REM 09-Setup-WaxAndTrace.ps1. Cal ISO files are named
REM CAL-{asset_tag}_serial-*_probe-*.iso.
set MACHINENUM=9999
if /i "%PCTYPE%"=="gea-shopfloor-collections" goto prompt_machinenum
if /i "%PCTYPE%"=="gea-shopfloor-nocollections" goto prompt_machinenum
if /i "%PCTYPE%"=="gea-shopfloor-heattreat" goto prompt_machinenum
if /i "%PCTYPE%"=="gea-shopfloor-waxtrace" goto prompt_waxtrace_asset
if /i "%PCTYPE%"=="gea-shopfloor-cmm" goto skip_machinenum
goto skip_machinenum
:prompt_machinenum
echo.
set /p MACHINENUM=Enter machine number (digits, or Enter for 9999):
if "%MACHINENUM%"=="" set MACHINENUM=9999
echo Machine number: %MACHINENUM%
goto skip_machinenum
:prompt_waxtrace_asset
echo.
echo Loading Wax/Trace bay list from PXE share...
REM Mount enrollment share early so the picker can read INDEX.csv. Later
REM net use Y: will be a no-op if Y: is already mapped.
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no >NUL 2>NUL
del X:\waxtrace-asset.txt 2>NUL
if not exist "Y:\installers-post\waxtrace\select-waxtrace-asset.ps1" goto waxtrace_picker_skip
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Y:\installers-post\waxtrace\select-waxtrace-asset.ps1" -IndexPath "Y:\installers-post\waxtrace\bay-config.csv" -OutFile "X:\waxtrace-asset.txt"
:waxtrace_picker_skip
set MACHINENUM=
if exist X:\waxtrace-asset.txt set /p MACHINENUM=<X:\waxtrace-asset.txt
if not "%MACHINENUM%"=="" goto waxtrace_have_asset
echo.
echo Picker unavailable or cancelled. Falling back to free-text prompt.
echo Wax/Trace bays use the asset tag, e.g. WJRP2335 or WJF00159, to pick
echo the right calibration ISO during shopfloor setup.
set /p MACHINENUM=Enter asset tag:
:waxtrace_have_asset
if "%MACHINENUM%"=="" echo WARNING: no asset tag entered - calibration apply will be skipped.
echo Asset tag: %MACHINENUM%
:skip_machinenum
REM --- Map enrollment share early (kept open for copy after imaging) ---
REM Y: may already be mapped from :prompt_waxtrace_asset (picker block).
REM net use of an already-mapped letter throws "System error 85: The local
REM device name is already in use" which is harmless but noisy on screen.
REM Skip the re-map when Y: is already mounted.
set NEED_ENROLL=0
if not "%PPKG%"=="" set NEED_ENROLL=1
if not "%PCTYPE%"=="" set NEED_ENROLL=1
if "%NEED_ENROLL%"=="0" goto enroll_staged
if exist Y:\ goto enroll_already_mapped
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no
:enroll_already_mapped
if "%PPKG%"=="" goto enroll_staged
if not exist "Y:\ppkgs\%SOURCE_PPKG%" (
echo WARNING: %SOURCE_PPKG% not found on server. Enrollment will be skipped.
set PPKG=
)
:enroll_staged
echo. > X:\Boot.tag
if "%choice%"=="1" goto gea-standard
if "%choice%"=="2" goto gea-engineer
if "%choice%"=="3" goto gea-shopfloor
echo Invalid choice. Please try again.
pause
goto menu
:gea-standard
echo.
echo Starting GEA Standard setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem
net use Z: \\172.16.9.1\winpeapps\gea-standard /user:pxe-upload pxe /persistent:no
goto end
:gea-engineer
echo.
echo Starting GEA Engineer setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem
net use Z: \\172.16.9.1\winpeapps\gea-engineer /user:pxe-upload pxe /persistent:no
goto end
:gea-shopfloor
echo.
echo Starting GEA Shopfloor setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem
REM --- Per-PCTYPE media view -----------------------------------------------
REM PESetup's CopyPackages copies the WHOLE of <media>\Deploy\Applications to
REM the target, recursively, with no filtering and no manifest (see
REM docs/PESETUP-INTERNALS.md). One shared shopfloor media therefore hands
REM every shopfloor payload to every bay whatever its type.
REM _media\<PCTYPE> is that same media rebuilt out of symlinks by
REM scripts/build-pctype-media.py, carrying only this type's payload. It is
REM built once, server-side, and never mutated per session: bays image
REM concurrently and CopyPackages is fail-fast, so changing a path mid-copy
REM would fail some OTHER bay's imaging.
if "%PCTYPE%"=="" goto gea_shopfloor_media_shared
REM Free Z: first. A leftover mapping makes net use fail with "device already
REM in use" and the probe below would then pass against the OLD media.
net use Z: /delete /y >NUL 2>&1
net use Z: \\172.16.9.1\winpeapps\_media\%PCTYPE% /user:pxe-upload pxe /persistent:no >NUL 2>&1
REM Probe a file that only resolves when the view exists AND its symlinks are
REM intact. A dangling view must not image silently off a half-mapped drive.
if exist "Z:\Deploy\Control\HardwareDriver.json" goto end
echo.
echo WARNING: no usable per-type media at _media\%PCTYPE%.
echo Falling back to the shared shopfloor media, so EVERY shopfloor
echo payload will be staged on this bay, not just this type's.
echo Imaging is otherwise unaffected. Tell the PXE admin.
echo.
net use Z: /delete /y >NUL 2>&1
:gea_shopfloor_media_shared
net use Z: \\172.16.9.1\winpeapps\gea-shopfloor /user:pxe-upload pxe /persistent:no
goto end
:end
echo.
REM --- Push initial "WinPE staging" status to PXE webapp ---
REM Externalized to Y:\scripts\winpe-status-push.ps1 so future edits don't
REM require a boot.wim rebuild. Best-effort; the script logs to
REM X:\Windows\Temp\winpe-status-push.log and swallows all errors.
if exist "Y:\scripts\winpe-status-push.ps1" (
powershell -NoProfile -ExecutionPolicy Bypass -File "Y:\scripts\winpe-status-push.ps1"
)
echo Waiting for PESetup.exe to start...
:wait_start
ping -n 3 127.0.0.1 >NUL
wmic process where "name='PESetup.exe'" get name 2>NUL | find /I "PESetup" >NUL
if errorlevel 1 goto wait_start
echo PESetup.exe is running. Waiting for imaging to complete...
REM --- Copy enrollment package and shopfloor setup as soon as Windows partition appears ---
if "%PPKG%"=="" if "%PCTYPE%"=="" goto wait_finish
REM --- Wait for the volume PESetup applies Windows to -----------------------
REM PESetup hardcodes W: in nine places - every copy destination, both DISM
REM offline sessions, bcdboot and reagentc - and CREATES it during its own disk
REM preparation. See docs/PESETUP-INTERNALS.md. There is nothing to search for.
REM
REM Do NOT try to locate the volume earlier by scanning for a Windows install.
REM Proven on 579C144, 2026-08-06, PESetup 4.0.0.20:
REM 11:30:07 PESetup logs the OLD install still mounted at C:
REM 11:30:18 operator presses Next
REM 11:30:20 PESetup runs DISKPART /S X:\diskpartEFI0.txt - the wipe
REM 11:30:24 a volume scan had returned that C:, startnet assigned it W: and
REM staged into the partition PESetup was mid-way through erasing.
REM Every robocopy died, "The system cannot find the path
REM specified", and NOTHING was staged.
REM 11:30:30 PESetup creates its own W: and starts copying to it
REM The SYSTEM hive is the signal that distinguishes the applied image from the
REM one being wiped: it exists only once the WIM apply has written it. Do not
REM weaken this test to "the volume exists" or "Windows is present".
set OSDRIVE=
echo Waiting for the applied Windows volume at W: ...
set OSWAIT=0
:wait_enroll
if exist W:\Windows\System32\config\system (
set OSDRIVE=W:
goto found_os
)
set /a OSWAIT+=1
REM 270 polls at ~10s = 45 minutes. Was 120 (20 min), which is fine on NVMe but
REM tight on slow media - a WIM apply can run 25+ minutes there, and the failure
REM mode is the scary os_not_found banner plus nothing staged. Waiting longer
REM costs nothing: the loop exits the moment the SYSTEM hive appears.
if %OSWAIT% GEQ 270 goto os_not_found
ping -n 11 127.0.0.1 >NUL
goto wait_enroll
:os_not_found
echo.
echo ******************************************************************
echo ERROR: no applied Windows volume found.
echo NOTHING was staged: pc-type.txt, the enrollment package and
echo shopfloor-setup are all missing. Imaging will finish but the
echo post-install setup will NOT run. Tell the PXE admin.
echo ******************************************************************
echo.
goto cleanup_enroll
:found_os
echo Found Windows at %OSDRIVE%
REM No drive-letter reassignment here. PESetup owns W: and runs its own DISKPART
REM against the same disk; a second diskpart racing it destroys the volume that
REM staging is writing to. The unattend's hardcoded W:\Drivers path is PESetup's
REM own destination (CopyDrivers unzips there), so it lines up by construction.
mkdir %OSDRIVE%\Enrollment 2>NUL
REM Log all copy operations to a file that persists on the target disk.
REM Tech can check C:\Enrollment\winpe-staging.log post-imaging.
REM Each echo in the copy block below also appends to this file via
REM the >> redirect after the screen echo. robocopy /LOG+ appends its
REM own output directly.
set STAGELOG=%OSDRIVE%\Enrollment\winpe-staging.log
echo [%DATE% %TIME%] WinPE staging started >> "%STAGELOG%"
echo PCTYPE=%PCTYPE% PPKG=%PPKG% MACHINENUM=%MACHINENUM% CMMID=%CMMID% >> "%STAGELOG%"
REM --- Copy site config (drives site-specific values in all setup scripts) ---
if exist "Y:\config\site-config.json" (
robocopy "Y:\config" "%OSDRIVE%\Enrollment" "site-config.json" /R:1 /W:1 /NFL /NDL /NJH /NJS
if errorlevel 8 (
echo WARNING: site-config.json robocopy FAILED - not staged.
echo [%TIME%] FAILED: site-config.json robocopy >> "%STAGELOG%"
) else (
echo Copied site-config.json.
echo [%TIME%] Copied site-config.json >> "%STAGELOG%"
)
) else (
echo WARNING: site-config.json not found on enrollment share.
echo [%TIME%] WARNING: site-config.json not found >> "%STAGELOG%"
)
REM --- Copy PPKG if selected (renames from SOURCE to BPRT-tagged filename) ---
if "%PPKG%"=="" goto copy_pctype
robocopy "Y:\ppkgs" "%OSDRIVE%\Enrollment" "%SOURCE_PPKG%" /R:1 /W:1 /NFL /NDL /NJH /NJS
REM robocopy exit codes: 0-7 = success, 8+ = failure (inverse of copy's 0/1).
if errorlevel 8 (
echo WARNING: Failed to copy enrollment package.
echo [%TIME%] WARNING: Failed to copy PPKG %SOURCE_PPKG% >> "%STAGELOG%"
goto copy_pctype
)
REM robocopy keeps the source name; rename to the BPRT-tagged target name.
if not "%SOURCE_PPKG%"=="%PPKG%" ren "%OSDRIVE%\Enrollment\%SOURCE_PPKG%" "%PPKG%"
echo [%TIME%] Copied PPKG %SOURCE_PPKG% as %PPKG% >> "%STAGELOG%"
robocopy "Y:\scripts" "%OSDRIVE%\Enrollment" "run-enrollment.ps1" "wait-for-internet.ps1" "migrate-to-wifi.ps1" /R:1 /W:1 /NFL /NDL /NJH /NJS
echo [%TIME%] Copied enrollment scripts >> "%STAGELOG%"
REM --- Create enroll.cmd at drive root as manual fallback ---
> %OSDRIVE%\enroll.cmd (
echo @echo off
echo echo Waiting for network...
echo :waitnet
echo ping -n 2 8.8.8.8 ^>NUL 2^>^&1
echo if errorlevel 1 goto waitnet
echo echo Network connected. Running enrollment...
echo powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\run-enrollment.ps1"
)
echo Manual fallback created at %OSDRIVE%\enroll.cmd
:copy_pctype
REM --- Copy shopfloor PC type setup scripts ---
if "%PCTYPE%"=="" goto cleanup_enroll
echo %PCTYPE%> %OSDRIVE%\Enrollment\pc-type.txt
REM 2026-05-04 rename reorg: pc-subtype.txt no longer written.
REM display-type.txt IS still written for gea-shopfloor-display because
REM Install-KioskApp.cmd reads it to pick Lobby vs Dashboard installer
REM and Get-PCProfile.ps1 reads it to build the Display-{type} profile key.
if not "%DISPLAYTYPE%"=="" echo %DISPLAYTYPE%> %OSDRIVE%\Enrollment\display-type.txt
if not "%MACHINENUM%"=="" echo %MACHINENUM%> %OSDRIVE%\Enrollment\machine-number.txt
REM Keyence model goes to BOTH keyence-model.txt (read by 09-Setup-Keyence) AND
REM pc-subtype.txt (read by GE-Enforce for per-model dispatch via existing
REM PCSubType wiring: looks for gea-shopfloor-keyence-<model>\manifest.json on share).
if not "%KEYENCEMODEL%"=="" (
echo %KEYENCEMODEL%> %OSDRIVE%\Enrollment\keyence-model.txt
echo %KEYENCEMODEL%> %OSDRIVE%\Enrollment\pc-subtype.txt
)
REM CMM bay ID + resolved config. CMMID comes from the bay picker (e.g. CMM4).
REM Written to machine-number.txt (same field collections/waxtrace use) so
REM TargetMachineNumbers on the SFLD share manifest gates per-bay entries.
REM resolve-cmm-bay-config.ps1 reads the CSV and writes version.txt + doda.txt
REM to %OSDRIVE%\Enrollment\cmm\ for 09-Setup-CMM.ps1 to consume.
if "%CMMID%"=="" goto cmm_id_done
echo %CMMID%> %OSDRIVE%\Enrollment\machine-number.txt
mkdir %OSDRIVE%\Enrollment\cmm 2>NUL
if exist "Y:\installers-post\cmm\resolve-cmm-bay-config.ps1" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Y:\installers-post\cmm\resolve-cmm-bay-config.ps1" -ConfigPath "Y:\installers-post\cmm\cmm-bay-config.csv" -CmmId "%CMMID%" -OutDir "%OSDRIVE%\Enrollment\cmm"
)
REM Read doda flag to set pc-subtype.txt for GE-Enforce manifest gating.
REM goto-flow avoids CMD variable scoping issues inside parens blocks.
if not exist %OSDRIVE%\Enrollment\cmm\doda.txt goto cmm_id_done
set /p CMMDODA=<%OSDRIVE%\Enrollment\cmm\doda.txt
if /i "%CMMDODA%"=="yes" echo doda> %OSDRIVE%\Enrollment\pc-subtype.txt
:cmm_id_done
robocopy "Y:\shopfloor-setup" "%OSDRIVE%\Enrollment" "Run-ShopfloorSetup.ps1" /R:1 /W:1 /NFL /NDL /NJH /NJS
REM Post-boot bulk re-fetch. WinPE staging below is best-effort (it can fail if
REM the Y: mount went idle-dead during the WIM apply); Fetch-StagingPayload.ps1
REM re-pulls the shopfloor-setup tree + preinstall bundle at first logon on a
REM FRESH mount. The unattend FirstLogonCommands runs it before the PowerShell 7
REM MSI + Run-ShopfloorSetup. Stage the script + its source coords here.
robocopy "Y:\shopfloor-setup" "%OSDRIVE%\Enrollment" "Fetch-StagingPayload.ps1" /R:1 /W:1 /NFL /NDL /NJH /NJS
REM Stage the staging self-heal directly too, so the unattend Order 5 heal step
REM runs even if Fetch-StagingPayload itself did not land. Small, reliable copy.
robocopy "Y:\shopfloor-setup" "%OSDRIVE%\Enrollment" "Verify-And-Heal-Staging.ps1" /R:1 /W:1 /NFL /NDL /NJH /NJS
> %OSDRIVE%\Enrollment\fetch-source.txt (
echo \\172.16.9.1\enrollment
echo pxe-upload
echo pxe
)
REM --- Always copy Shopfloor baseline scripts ---
mkdir %OSDRIVE%\Enrollment\shopfloor-setup 2>NUL
robocopy "Y:\shopfloor-setup" "%OSDRIVE%\Enrollment\shopfloor-setup" "backup_lockdown.bat" /R:1 /W:1 /NFL /NDL /NJH /NJS
if exist "Y:\shopfloor-setup\Shopfloor" (
mkdir %OSDRIVE%\Enrollment\shopfloor-setup\Shopfloor 2>NUL
robocopy "Y:\shopfloor-setup\Shopfloor" "%OSDRIVE%\Enrollment\shopfloor-setup\Shopfloor" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
REM Report what actually happened. These echoes used to run unconditionally
REM and printed "Copied ..." for six copies that moved zero bytes.
REM %ERRORLEVEL% is not printed: inside a parenthesised block it expands at
REM PARSE time, so it shows a stale value from before the robocopy ran.
if errorlevel 8 (
echo WARNING: shopfloor-setup\Shopfloor robocopy FAILED - nothing copied.
echo [%TIME%] FAILED: Shopfloor baseline robocopy >> "%STAGELOG%"
) else (
echo Copied Shopfloor baseline setup files.
echo [%TIME%] Copied Shopfloor baseline >> "%STAGELOG%"
)
)
REM --- Always copy common/ (cross-PC-type GE-Enforce dispatcher + lib live here post-v2) ---
if exist "Y:\shopfloor-setup\common" (
mkdir %OSDRIVE%\Enrollment\shopfloor-setup\common 2>NUL
robocopy "Y:\shopfloor-setup\common" "%OSDRIVE%\Enrollment\shopfloor-setup\common" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: shopfloor-setup\common robocopy FAILED - nothing copied.
echo [%TIME%] FAILED: common robocopy >> "%STAGELOG%"
) else (
echo Copied common setup files.
echo [%TIME%] Copied common >> "%STAGELOG%"
)
)
REM --- Copy _ntlars-backups (147 per-bay .reg files restored by gea-shopfloor-{collections,nocollections}\03-RestoreEDncConfig.ps1) ---
REM Same root level as common/, referenced by 03-RestoreEDncConfig.ps1 via Join-Path $PSScriptRoot '..\_ntlars-backups'.
if exist "Y:\shopfloor-setup\_ntlars-backups" (
mkdir %OSDRIVE%\Enrollment\shopfloor-setup\_ntlars-backups 2>NUL
robocopy "Y:\shopfloor-setup\_ntlars-backups" "%OSDRIVE%\Enrollment\shopfloor-setup\_ntlars-backups" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: _ntlars-backups robocopy FAILED - nothing copied.
echo [%TIME%] FAILED: _ntlars-backups robocopy >> "%STAGELOG%"
) else (
echo Copied _ntlars-backups.
echo [%TIME%] Copied _ntlars-backups >> "%STAGELOG%"
)
)
REM --- Copy type-specific scripts on top of baseline ---
if exist "Y:\shopfloor-setup\%PCTYPE%" (
mkdir "%OSDRIVE%\Enrollment\shopfloor-setup\%PCTYPE%" 2>NUL
robocopy "Y:\shopfloor-setup\%PCTYPE%" "%OSDRIVE%\Enrollment\shopfloor-setup\%PCTYPE%" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: shopfloor-setup\%PCTYPE% robocopy FAILED - nothing copied.
echo [%TIME%] FAILED: %PCTYPE% type-specific robocopy >> "%STAGELOG%"
) else (
echo Copied %PCTYPE% setup files.
echo [%TIME%] Copied %PCTYPE% type-specific >> "%STAGELOG%"
)
) else (
echo WARNING: No setup files found for PC type %PCTYPE%.
echo [%TIME%] WARNING: No setup files for %PCTYPE% >> "%STAGELOG%"
)
REM --- Stage preinstall bundle (apps installed locally to save Azure bandwidth) ---
if exist "Y:\pre-install\preinstall.json" (
mkdir %OSDRIVE%\PreInstall 2>NUL
mkdir %OSDRIVE%\PreInstall\installers 2>NUL
robocopy "Y:\pre-install" "%OSDRIVE%\PreInstall" "preinstall.json" /R:1 /W:1 /NFL /NDL /NJH /NJS
if exist "Y:\pre-install\installers" (
robocopy "Y:\pre-install\installers" "%OSDRIVE%\PreInstall\installers" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: pre-install\installers robocopy FAILED - nothing staged.
) else (
echo Staged preinstall bundle to %OSDRIVE%\PreInstall.
)
echo [%TIME%] Staged preinstall bundle >> "%STAGELOG%"
) else (
echo WARNING: Y:\pre-install\installers not found - preinstall.json staged without installers.
)
if exist "Y:\pre-install\udc-backups" (
robocopy "Y:\pre-install\udc-backups" "%OSDRIVE%\PreInstall\udc-backups" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: pre-install\udc-backups robocopy FAILED - nothing staged.
) else (
echo Staged UDC settings backups to %OSDRIVE%\PreInstall\udc-backups.
)
echo [%TIME%] Staged UDC backups >> "%STAGELOG%"
)
) else (
echo No preinstall bundle on PXE server - skipping.
)
REM --- Stage CMM bootstrap bundle (CMM-type PCs only) ---
REM Copies the Hexagon installer bundle (~1.9 GB) from the PXE server enrollment
REM share onto the target disk so 09-Setup-CMM.ps1 can install from local disk.
REM The tsgwp00525 SFLD share that holds the canonical copy is not yet reachable
REM during shopfloor-setup (Azure DSC provisions those creds later), so this
REM bootstrap exists to get the first-install through. Post-imaging, the
REM unified GE-Enforce dispatcher takes over from the share for ongoing updates.
if /i not "%PCTYPE%"=="gea-shopfloor-cmm" goto skip_cmm_stage
if exist "Y:\installers-post\cmm\cmm-manifest.json" (
mkdir %OSDRIVE%\CMM-Install 2>NUL
REM /XD the backups tree: it holds EVERY bay's settings backup (some 240 MB
REM each). Copying all of it to every imaged CMM would waste GBs. The bulk
REM copy excludes it; the selected bay's backup is staged separately below.
robocopy "Y:\installers-post\cmm" "%OSDRIVE%\CMM-Install" /E /XD "Y:\installers-post\cmm\backups" /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: cmm robocopy FAILED - CMM bootstrap NOT staged.
) else (
echo Staged CMM bootstrap to %OSDRIVE%\CMM-Install.
)
echo [%TIME%] Staged CMM bootstrap >> "%STAGELOG%"
) else (
echo WARNING: Y:\cmm-installers not found - CMM PC cannot install Hexagon apps at imaging time.
)
REM Stage ONLY the selected bay's settings backup. 09-Setup-CMM's Restore-CMM
REM reads %OSDRIVE%\CMM-Install\backups\%CMMID%\ to restore PC-DMIS + goCMM settings.
if not defined CMMID goto skip_cmm_stage
if exist "Y:\installers-post\cmm\backups\%CMMID%" (
robocopy "Y:\installers-post\cmm\backups\%CMMID%" "%OSDRIVE%\CMM-Install\backups\%CMMID%" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: %CMMID% backup robocopy FAILED - settings will NOT restore.
echo [%TIME%] FAILED: %CMMID% backup robocopy >> "%STAGELOG%"
) else (
echo Staged %CMMID% settings backup for restore.
echo [%TIME%] Staged %CMMID% backup >> "%STAGELOG%"
)
)
:skip_cmm_stage
REM --- Stage Keyence per-model bootstrap bundle (Keyence PCs only) ---
REM Copies only the selected model's MSI + Data cabs from the PXE enrollment
REM share to the target disk. installers-post/keyence/<model>/ contains:
REM manifest.json
REM installers/{MSI, Data1.cab, [Data11.cab for vr3000], 1033.mst}
REM drivers/ (vr6000 only - older external .inf; vr3000/vr5000 drivers
REM are embedded in their MSIs)
REM 09-Setup-Keyence.ps1 reads C:\Enrollment\keyence-model.txt to know which.
if /i not "%PCTYPE%"=="gea-shopfloor-keyence" goto skip_keyence_stage
if "%KEYENCEMODEL%"=="" goto skip_keyence_stage
if exist "Y:\installers-post\keyence\%KEYENCEMODEL%\manifest.json" (
mkdir %OSDRIVE%\KeyenceInstall 2>NUL
mkdir %OSDRIVE%\KeyenceInstall\%KEYENCEMODEL% 2>NUL
robocopy "Y:\installers-post\keyence\%KEYENCEMODEL%" "%OSDRIVE%\KeyenceInstall\%KEYENCEMODEL%" /E /MT:16 /R:1 /W:1 /NFL /NDL /LOG+:"%STAGELOG%"
if errorlevel 8 (
echo WARNING: keyence\%KEYENCEMODEL% robocopy FAILED - nothing staged.
) else (
echo Staged Keyence %KEYENCEMODEL% bootstrap to %OSDRIVE%\KeyenceInstall\%KEYENCEMODEL%\.
)
echo [%TIME%] Staged Keyence %KEYENCEMODEL% >> "%STAGELOG%"
) else (
echo WARNING: Y:\installers-post\keyence\%KEYENCEMODEL% not found - Keyence %KEYENCEMODEL% cannot install at imaging time.
)
:skip_keyence_stage
REM --- Stage WaxTrace bootstrap bundle (wax/trace gea-shopfloor-waxtrace only) ---
REM Three-step process:
REM 1. robocopy installers-post\waxtrace -> %OSDRIVE%\WaxTrace-Install (everything
REM EXCEPT the formtracepak\ subdir, via /XD - that subdir carries all
REM 7 vendor ISOs and we only need one of them)
REM 2. resolve-bay-config.ps1: reads bay-config.csv + asset_tag, writes
REM %OSDRIVE%\Enrollment\waxtrace\{version,model,userid}.txt for 09-Setup to read
REM 3. robocopy ONLY the bay's matching FORMTRACEPAK-V<ver>.iso from
REM Y:\installers-post\waxtrace\formtracepak\ onto the target disk
REM (so 09-Setup-WaxAndTrace.ps1 mounts the right version per bay)
if /i not "%PCTYPE%"=="gea-shopfloor-waxtrace" goto skip_waxtrace_stage
if not exist "Y:\installers-post\waxtrace\waxtrace-manifest.json" goto skip_waxtrace_missing
mkdir %OSDRIVE%\WaxTrace-Install 2>NUL
REM robocopy /XD excludes the formtracepak\ dir up front so we don't waste
REM disk + time copying 12+ GB of vendor ISOs only to delete them. The
REM matched FTPak ISO gets cherry-picked below into the same target dir.
REM /NFL/NDL keep the listing manageable; output left visible (no >NUL) so
REM any failure is debuggable from the install log. robocopy exit codes
REM 0-7 are "OK" (per Microsoft); 8+ is real failure.
robocopy "Y:\installers-post\waxtrace" "%OSDRIVE%\WaxTrace-Install" /E /XD formtracepak /MT:16 /R:1 /W:1 /NFL /NDL
REM Both tests read the robocopy result, so they must sit before any command
REM that sets its own errorlevel - mkdir does, which is why it moved below.
if errorlevel 8 echo WARNING: WaxTrace bootstrap robocopy FAILED - nothing staged.
if not errorlevel 8 echo Staged WaxTrace bootstrap minus formtracepak\ to %OSDRIVE%\WaxTrace-Install.
mkdir %OSDRIVE%\WaxTrace-Install\formtracepak 2>NUL
REM Resolve bay-config: writes %OSDRIVE%\Enrollment\waxtrace\{version,model,userid}.txt
mkdir %OSDRIVE%\Enrollment\waxtrace 2>NUL
if not "%MACHINENUM%"=="" (
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "Y:\installers-post\waxtrace\resolve-bay-config.ps1" -Asset "%MACHINENUM%" -OutDir "%OSDRIVE%\Enrollment\waxtrace"
) else (
echo WARNING: no MACHINENUM set - skipping bay-config resolve. 09-Setup-WaxAndTrace will abort cleanly with no version.
)
REM Read the resolved version + robocopy ONLY that FTPak ISO (avoids dumping
REM 12 GB of ISOs on the bay disk; only the matched ~2 GB ISO lands locally).
set WTVER=
if exist %OSDRIVE%\Enrollment\waxtrace\version.txt set /p WTVER=<%OSDRIVE%\Enrollment\waxtrace\version.txt
if not "%WTVER%"=="" (
if exist "Y:\installers-post\waxtrace\formtracepak\FORMTRACEPAK-V%WTVER%.iso" (
REM robocopy /J = unbuffered I/O, meaningfully faster than xcopy on
REM 2 GB FTPak ISOs over SMB. Single-file syntax: source dir, dest
REM dir, file pattern.
robocopy "Y:\installers-post\waxtrace\formtracepak" "%OSDRIVE%\WaxTrace-Install\formtracepak" "FORMTRACEPAK-V%WTVER%.iso" /J /R:1 /W:1 /NFL /NDL
echo Staged FormTracePak V%WTVER%.iso for %MACHINENUM%.
) else (
echo WARNING: No FORMTRACEPAK-V%WTVER%.iso on share for %MACHINENUM% - 09-Setup-WaxAndTrace will abort cleanly with the version-mismatch error.
)
) else (
echo WARNING: bay-config did not resolve a FTPak version for %MACHINENUM%.
)
REM Per-asset backup ZIPs land at %OSDRIVE%\WaxTrace-Install\backups\<asset>.zip
REM as part of the robocopy bundle above (backups\ is not in the /XD list).
REM 09-Setup-WaxAndTrace.ps1 Step 3b reads C:\WaxTrace-Install\backups\
REM <asset>.zip post-vendor-MSI and runs Install-FormtracepakSettings.ps1
REM -RestoreData -RestoreConfig. Total ~17 MB on disk for the whole set, no
REM cherry-pick needed.
if not "%MACHINENUM%"=="" (
if exist "%OSDRIVE%\WaxTrace-Install\backups\%MACHINENUM%.zip" (
echo Backup ZIP %MACHINENUM%.zip is in the bundle - 09-Setup Step 3b will restore.
) else (
echo INFO: No backup ZIP for %MACHINENUM% in %OSDRIVE%\WaxTrace-Install\backups - 09-Setup will skip the restore step.
)
)
goto skip_waxtrace_stage
:skip_waxtrace_missing
echo WARNING: Y:\installers-post\waxtrace not found - WaxTrace PC cannot install FormTracePak at imaging time.
:skip_waxtrace_stage
:pctype_done
if defined STAGELOG echo [%TIME%] WinPE staging complete >> "%STAGELOG%"
REM --- Verify what we just staged ------------------------------------------
REM Check the payload BEFORE anything depends on it, while a tech is still
REM standing at the machine. A short copy found here costs 30 seconds; the same
REM copy found at first logon costs 20 minutes, and found never costs a rebuild.
REM Four Display bays sat at "imaging stage 2, green" for weeks because nothing
REM checked. Retry once for the small trees; the ppkg is 8 GB so report it
REM rather than blindly re-pulling it.
if "%PCTYPE%"=="" goto verify_done
set STAGEBAD=
if not exist "%OSDRIVE%\Enrollment\pc-type.txt" set STAGEBAD=%STAGEBAD% pc-type.txt
if not exist "%OSDRIVE%\Enrollment\Run-ShopfloorSetup.ps1" set STAGEBAD=%STAGEBAD% Run-ShopfloorSetup.ps1
if not exist "%OSDRIVE%\Enrollment\shopfloor-setup\common" set STAGEBAD=%STAGEBAD% shopfloor-setup\common
if not exist "%OSDRIVE%\Enrollment\shopfloor-setup\%PCTYPE%" set STAGEBAD=%STAGEBAD% shopfloor-setup\%PCTYPE%
if "%STAGEBAD%"=="" goto verify_ppkg
echo.
echo Staging verify: missing%STAGEBAD% - retrying once...
echo [%TIME%] VERIFY: missing%STAGEBAD% - retry >> "%STAGELOG%"
robocopy "Y:\shopfloor-setup" "%OSDRIVE%\Enrollment" "Run-ShopfloorSetup.ps1" /R:2 /W:2 /NFL /NDL /NJH /NJS
robocopy "Y:\shopfloor-setup\common" "%OSDRIVE%\Enrollment\shopfloor-setup\common" /E /MT:16 /R:2 /W:2 /NFL /NDL /NJH /NJS
robocopy "Y:\shopfloor-setup\%PCTYPE%" "%OSDRIVE%\Enrollment\shopfloor-setup\%PCTYPE%" /E /MT:16 /R:2 /W:2 /NFL /NDL /NJH /NJS
if not exist "%OSDRIVE%\Enrollment\pc-type.txt" echo %PCTYPE%> %OSDRIVE%\Enrollment\pc-type.txt
set STAGEBAD=
if not exist "%OSDRIVE%\Enrollment\pc-type.txt" set STAGEBAD=%STAGEBAD% pc-type.txt
if not exist "%OSDRIVE%\Enrollment\Run-ShopfloorSetup.ps1" set STAGEBAD=%STAGEBAD% Run-ShopfloorSetup.ps1
if not exist "%OSDRIVE%\Enrollment\shopfloor-setup\common" set STAGEBAD=%STAGEBAD% shopfloor-setup\common
if not exist "%OSDRIVE%\Enrollment\shopfloor-setup\%PCTYPE%" set STAGEBAD=%STAGEBAD% shopfloor-setup\%PCTYPE%
if "%STAGEBAD%"=="" (
echo Staging verify: retry fixed it.
echo [%TIME%] VERIFY: retry succeeded >> "%STAGELOG%"
goto verify_ppkg
)
echo.
echo ******************************************************************
echo STAGING INCOMPLETE after retry:%STAGEBAD%
echo This bay will image but the post-install setup will NOT run
echo properly. Fix the share and re-image. Tell the PXE admin.
echo ******************************************************************
echo.
echo [%TIME%] VERIFY FAILED after retry:%STAGEBAD% >> "%STAGELOG%"
:verify_ppkg
REM The enrollment package is the expensive one - 8 GB. Report, do not re-pull.
if "%PPKG%"=="" goto verify_done
if exist "%OSDRIVE%\Enrollment\%PPKG%" (
echo Staging verify: enrollment package present.
echo [%TIME%] VERIFY: %PPKG% present >> "%STAGELOG%"
) else (
echo.
echo WARNING: enrollment package %PPKG% is MISSING from %OSDRIVE%\Enrollment.
echo Enrollment will not run. Re-image this bay.
echo.
echo [%TIME%] VERIFY FAILED: %PPKG% missing >> "%STAGELOG%"
)
:verify_done
REM --- BIOS update sub-stage push (fires AFTER %OSDRIVE% copies complete) ---
REM check-bios.cmd drops X:\bios-fired.flag iff it actually flashed or
REM staged a firmware update. Reading the flag file is more reliable than
REM scanning BIOS_STATUS for substrings (caret escaping inside quoted SET
REM is subtle in WinPE cmd.exe). Push must happen before Y: cleanup since
REM the push script lives on the share.
if exist X:\bios-fired.flag (
if exist "Y:\scripts\winpe-status-push.ps1" (
powershell -NoProfile -ExecutionPolicy Bypass -File "Y:\scripts\winpe-status-push.ps1" -CurrentStage "WinPE: BIOS firmware update"
)
)
:cleanup_enroll
net use Y: /delete 2>NUL
:wait_finish
ping -n 11 127.0.0.1 >NUL
wmic process where "name='PESetup.exe'" get name 2>NUL | find /I "PESetup" >NUL
if not errorlevel 1 goto wait_finish
REM --- Harvest the imaging logs to the share --------------------------------
REM Every diagnosis of a failed build so far has meant walking to the bay and
REM copying files off by hand. PESetup preserves its log and the generated
REM unattend, DISM writes its apply logs, and our staging log sits on the target
REM - all of it is thrown away at reboot unless collected now. Runs after
REM PESetup exits so the logs are final, which means re-mapping Y: (cleanup
REM already dropped it).
REM Best-effort throughout: a bay must never fail to reboot because a log copy
REM did not work.
set SERIAL=
for /f "skip=1 tokens=*" %%S in ('wmic bios get serialnumber 2^>NUL') do if not defined SERIAL set SERIAL=%%S
for /f "tokens=*" %%a in ("%SERIAL%") do set SERIAL=%%a
if "%SERIAL%"=="" set SERIAL=unknown-serial
net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no >NUL 2>&1
if not exist "Y:\" goto harvest_done
set LOGDEST=Y:\imaging-logs\%SERIAL%
mkdir "Y:\imaging-logs" 2>NUL
mkdir "%LOGDEST%" 2>NUL
echo Collecting imaging logs to %LOGDEST% ...
REM WinPE side: DISM apply logs, the unattend PESetup generated, BIOS flash log.
robocopy X:\ "%LOGDEST%" "*.log" "Unattend.xml" /R:1 /W:1 /NFL /NDL /NJH /NJS >NUL 2>&1
REM Target side: PESetup's own log lands in Panther, and our staging log.
if defined OSDRIVE (
robocopy "%OSDRIVE%\Windows\Panther" "%LOGDEST%" "PESetup*.log" /R:1 /W:1 /NFL /NDL /NJH /NJS >NUL 2>&1
robocopy "%OSDRIVE%\Enrollment" "%LOGDEST%" "winpe-staging.log" /R:1 /W:1 /NFL /NDL /NJH /NJS >NUL 2>&1
)
REM A breadcrumb of what this build actually was, so the logs are readable
REM without cross-referencing the webapp.
> "%LOGDEST%\build-context.txt" (
echo Serial=%SERIAL%
echo PCTYPE=%PCTYPE%
echo PPKG=%PPKG%
echo MACHINENUM=%MACHINENUM%
echo CMMID=%CMMID%
echo KEYENCEMODEL=%KEYENCEMODEL%
echo DISPLAYTYPE=%DISPLAYTYPE%
echo OSDRIVE=%OSDRIVE%
echo Media=%MEDIAPATH%
echo Date=%DATE% %TIME%
)
echo Imaging logs collected.
net use Y: /delete /y >NUL 2>&1
:harvest_done
echo.
echo Imaging complete. Rebooting in 15 seconds...
echo Press Ctrl+C to cancel.
ping -n 16 127.0.0.1 >NUL
wpeutil reboot