Everything West Jefferson specific moved out to a separate repo (pxe-site-wj): NTLARS/UDC/heat-treat backups keyed by WJ machine numbers, CMM and Wax/Trace bay lists, WJ OpenText profiles, site-config.json, the WJ preinstall list, and the WJ shopfloor PC types (Collections, NoCollections, Common, Keyence, CMM, Genspect, HeatTreat, Wax/Trace, Display, PartMarker) with GE-Enforce, machine numbers and the desktop layout. The part-marker bench tools moved to their own repo. Dead scripts removed: build-proxmox-iso, deploy-bios, sync_hardware_models, mirror-from-gold, pull-bios, startnet-template.cmd. Shopfloor is one generic type, gea-shopfloor-standard, with today's timing: - PPKG at first logon (unattend Order 10, unchanged). - Install-AppList.ps1 runs C:\PreInstall\preinstall.json during setup. - A new SYSTEM task, GE Shopfloor Post-PPKG, runs postppkg.json once after the post-PPKG reboot. It also clears AutoLogonCount after the ShopFloor handover (the Users-level monitor can be denied that HKLM write) and removes the Intune Sync task when done. - Both lists ship empty. A site adds entries and installers. - WinPE choice 3 goes straight to the PPKG menu. Staging, fetch and heal carry only the generic payload. The webapp's shopfloor sub-menu editor is gone, and the imaging tiles name the new stages. - The playbook deploys the empty lists only if missing. It now deploys the PowerShell 7 MSI the shopfloor unattend installs, which it never did before. Blancco site settings move into the webapp (Updates > Blancco releases): business location, licensing Wi-Fi SSID and password. They are rendered into the served preferences.xml and applied to every staged release, and are seeded from site-vars (site_name, blancco_wifi_ssid, blancco_wifi_password) only while none exist. The repo XML carries no site values; the plaintext Wi-Fi password is gone from it. New site_name and site_timezone settings: report and evidence-pack site name, server clock and webapp time zone. No more West Jefferson defaults in webapp/config.py or the dashboard title. Sync-BPRT.ps1 gets the server address filled in at download instead of WJ's GE address. USB on Windows: - scripts/Build-PxeUsb.ps1 runs the same checks as build-usb.sh. It writes one FAT32 CIDATA partition (ISO files plus patched grub.cfg plus payload) with diskpart and reads every file back by SHA-256. - Tested with Windows PowerShell 5.1 in the win11 VM, and the layout installs and first-boots in the pxetest VM. - scripts/stage-usb-share.sh stages the build folder on a share. - site-content/ is optional for both builders. KB docs/kb/How_To_Deploy_PXE_Server_New_Site.html: new-site deployment, Windows only, with a site-vars.yml guide. Checks: webapp tests pass (5 suites); the unattend lint and playbook syntax checks pass; every .ps1 parses; there are no WJ hosts, IPs, names or PC types in playbook/, webapp/, scripts/ or autoinstall/.
579 lines
26 KiB
Batchfile
579 lines
26 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 --- 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_<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 --- 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
|