@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 --- Site values ----------------------------------------------------- REM The PXE server address and share credentials appeared 14 and 13 times REM respectively, so moving this server or rotating the account meant editing REM every net use in the file and rebuilding boot.wim. Set them once here. REM These are also written to C:\Enrollment\fetch-source.txt, which REM Fetch-StagingPayload.ps1 and Verify-And-Heal-Staging.ps1 read at first REM logon - so the post-boot self-heal follows this value automatically. set PXESERVER=172.16.9.1 set SHAREUSER=pxe-upload set SHAREPASS=pxe REM --- Wait for network (DHCP may take a moment after wpeinit) --- echo Waiting for network... :wait_net ping -n 2 %PXESERVER% >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: \\%PXESERVER%\winpeapps_bios /user:%SHAREUSER% %SHAREPASS% /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 echo. echo ======================================== echo. set /p choice=Enter your choice (1-3): REM --- Choice 3 = GEA Shopfloor: one generic type, gea-shopfloor-standard, then REM the GCCH enrollment (PPKG) menu. set PPKG= set PCCONFIG= if "%choice%"=="3" goto gea_shopfloor_type REM Standard and engineer enrol through the vendor orchestrator, which asks for REM tenant + PC purpose with a GUI at first logon. Ask here instead, while the REM tech who knows what the machine is for is standing at it, and pre-seed the REM answer so the dialog never appears. See :gea_purpose_submenu. if "%choice%"=="1" goto gea_purpose_submenu if "%choice%"=="2" goto gea_purpose_submenu goto enroll_staged :gea_purpose_submenu cls echo. echo ======================================== echo PC Purpose - Entra enrollment echo ======================================== echo. echo 1. Single-User PC (one assigned person) echo 2. Shared PC (multiple users) echo 3. Loaner PC (temporary assignment) echo. echo 4. Ask at first logon (show the vendor dialog instead) echo. set PCPURPOSE= set /p purp_choice=Enter your choice (1-4): if "%purp_choice%"=="1" set PCPURPOSE=PERS if "%purp_choice%"=="2" set PCPURPOSE=SH if "%purp_choice%"=="3" set PCPURPOSE=LOAN if "%purp_choice%"=="4" goto enroll_staged if "%PCPURPOSE%"=="" goto gea_purpose_submenu :gea_tenant_submenu cls echo. echo ======================================== echo Entra Tenant echo ======================================== echo. echo 1. GCC High (GCCH - US) echo 2. Rest of World (RoW) echo. REM The tenant is not cosmetic: Insert-BPRTToPPKG.ps1 matches the bulk token by REM Tenant AND Purpose, so this picks which token gets injected. set PCTENANT= set /p ten_choice=Enter your choice (1-2): if "%ten_choice%"=="1" set PCTENANT=GCCH if "%ten_choice%"=="2" set PCTENANT=RoW if "%PCTENANT%"=="" goto gea_tenant_submenu set PCCONFIG=%PCTENANT%_%PCPURPOSE% echo. echo PC config: %PCCONFIG% goto enroll_staged :gea_shopfloor_type REM One generic shopfloor type: an (empty) app list during setup, the PPKG, an REM (empty) post-PPKG app list, then the ShopFloor lockdown handover. set PCTYPE=gea-shopfloor-standard 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: \\%PXESERVER%\enrollment /user:%SHAREUSER% %SHAREPASS% /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___Exp__.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 --- Map enrollment share early (kept open for copy after imaging) --- REM Skip the re-map when Y: is already mounted (System error 85 otherwise). 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: \\%PXESERVER%\enrollment /user:%SHAREUSER% %SHAREPASS% /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: \\%PXESERVER%\winpeapps\gea-standard /user:%SHAREUSER% %SHAREPASS% /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: \\%PXESERVER%\winpeapps\gea-engineer /user:%SHAREUSER% %SHAREPASS% /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 net use Z: \\%PXESERVER%\winpeapps\gea-shopfloor /user:%SHAREUSER% %SHAREPASS% /persistent:no goto end :end echo. REM Map the enrollment share for the status push and preflight below. The REM shopfloor/enroll menus map it earlier; the gea-standard/gea-engineer REM path (purpose -> tenant -> enroll_staged) never did, so neither ran. if not exist Y:\ net use Y: \\%PXESERVER%\enrollment /user:%SHAREUSER% %SHAREPASS% /persistent:no >NUL 2>&1 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" ) REM --- Pre-imaging checks --------------------------------------------------- REM Checks the things PESetup fails on, before it fails on them: secure boot REM (hard failure at GatherData), disk >= 120 GB, a driver pack matching THIS REM model, and media age. The driver check is the one that earns its keep - a REM miss is only a WARNING to PESetup, so the bay images with no NIC and the REM symptom shows up much later as a DNS failure during enrollment. REM Advisory: it reports and pauses on a blocker, it does not abort. The tech REM decides. Lives on the share so it can be fixed without a boot.wim rebuild. if exist "Y:\scripts\preflight.ps1" ( powershell -NoProfile -ExecutionPolicy Bypass -File "Y:\scripts\preflight.ps1" -MediaDrive Z: if errorlevel 1 ( echo Press any key to image anyway, or power off the bay to stop. pause >NUL ) ) 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 --- REM PCCONFIG is here so standard/engineer builds still reach the volume wait: REM they select no ppkg and no PCTYPE, but they do need pc-config.txt written. if "%PPKG%"=="" if "%PCTYPE%"=="" if "%PCCONFIG%"=="" 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% PCCONFIG=%PCCONFIG% >> "%STAGELOG%" REM --- Pre-seed the tenant + PC purpose for the enrollment orchestrator ----- REM Start-BulkEnrollOrchestrator.ps1 asks for this with a GUI at first logon REM (Select-PCConfig.ps1) and uses the answer twice: to pick which MCL package REM to apply, and to select which bulk token Insert-BPRTToPPKG.ps1 injects - REM it matches on Tenant AND Purpose. Writing the answer here lets the shim REM return it non-interactively, so an unattended build never stops on a dialog. REM Format is exactly what the vendor dialog returns: {Tenant}_{Purpose}, REM e.g. GCCH_PERS, GCCH_SH, RoW_LOAN. REM Only the CHOICE is pre-seeded. Token injection still happens at first logon REM because it fetches an encrypted token table from mcl.dwcdn.geaerospace.com, REM which is unreachable from this isolated imaging LAN. if not "%PCCONFIG%"=="" ( echo %PCCONFIG%> %OSDRIVE%\Enrollment\pc-config.txt echo Pre-seeded PC config %PCCONFIG% for the enrollment orchestrator. echo [%TIME%] Wrote pc-config.txt=%PCCONFIG% >> "%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 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 \\%PXESERVER%\enrollment echo %SHAREUSER% echo %SHAREPASS% ) REM --- Always copy Shopfloor baseline scripts --- mkdir %OSDRIVE%\Enrollment\shopfloor-setup 2>NUL 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 --- Stage the site app lists + installers (pre-install = during setup, REM postppkg = after the post-PPKG reboot; both empty in the generic build) --- 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" "postppkg.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. ) ) else ( echo No preinstall bundle on PXE server - skipping. ) :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\Shopfloor\lib\Monitor-IntuneProgress.ps1" set STAGEBAD=%STAGEBAD% shopfloor-setup\Shopfloor if not exist "%OSDRIVE%\PreInstall\preinstall.json" set STAGEBAD=%STAGEBAD% PreInstall\preinstall.json 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\Shopfloor" "%OSDRIVE%\Enrollment\shopfloor-setup\Shopfloor" /E /MT:16 /R:2 /W:2 /NFL /NDL /NJH /NJS robocopy "Y:\pre-install" "%OSDRIVE%\PreInstall" "preinstall.json" "postppkg.json" /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\Shopfloor\lib\Monitor-IntuneProgress.ps1" set STAGEBAD=%STAGEBAD% shopfloor-setup\Shopfloor if not exist "%OSDRIVE%\PreInstall\preinstall.json" set STAGEBAD=%STAGEBAD% PreInstall\preinstall.json 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: \\%PXESERVER%\enrollment /user:%SHAREUSER% %SHAREPASS% /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 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