Renumber PXE LAN from 10.9.100.0/24 to 172.16.9.0/24

Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.

Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.

Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.

After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-05-14 16:30:32 -04:00
parent c6b249f866
commit ce604adcda
87 changed files with 697 additions and 139 deletions

BIN
Binary/Binary.NewBinary1 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
Binary/Binary.NewBinary10 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Binary/Binary.NewBinary11 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Binary/Binary.NewBinary12 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
Binary/Binary.NewBinary13 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary14 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary15 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary16 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary17 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary18 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

40
Binary/Binary.NewBinary19 Normal file
View File

@@ -0,0 +1,40 @@
Option Explicit
' アップグレードコードから、製品コードを取得
'
' 第1引数 : アップグレードコード(「{」、「}」、ハイフンあり)
Function GetProductCodeFromUpgradeCode(UpgCode)
Dim listProductCode
Dim szProductCode
' アップグレードコードから、関連する製品名のリストを取得
Set listProductCode = Session.Installer.RelatedProducts(UpgCode)
' 基本、1件のみヒットするものとする
For Each szProductCode In listProductCode
GetProductCodeFromUpgradeCode = szProductCode
' 1件目を取得した段階で抜ける
Exit For
Next
End Function
' アップグレードコードから既にインストール済みのアプリケーションのインストールパスを取得する
Sub GetInstallPath()
Dim WshShell
Dim szProductCode
Dim szInstallStringKey
Set WshShell = CreateObject("WScript.Shell")
' アップグレードコードから、製品コードを取得
szProductCode = GetProductCodeFromUpgradeCode(Session.Property("UpgradeCode"))
' レジストリのInstallLocationを取得
szInstallStringKey = WshShell.RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + szProductCode + "\InstallLocation")
Session.Property("INSTALLDIR_FOR_MAJORUPGRADE") = szInstallStringKey
Set WshShell = nothing
End Sub

BIN
Binary/Binary.NewBinary2 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

22
Binary/Binary.NewBinary20 Normal file
View File

@@ -0,0 +1,22 @@
Option Explicit
Sub CheckOSVersion
Const HKEY_LOCAL_MACHINE = &H80000002
Dim WshShell,objRegistry
Dim strComputer, strKeyPath, strValue, strValueName
Set WshShell = CreateObject("WScript.Shell")
strComputer = "."
Set objRegistry = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
strKeyPath = "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
strValueName = "CurrentMajorVersionNumber"
objRegistry.GetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,strValue
If (not IsNull(strValue)) and (strValue=10) Then
Session.Property("IsWindows10")="1"
else
Session.Property("IsWindows10")="0"
End If
End Sub

13
Binary/Binary.NewBinary21 Normal file
View File

@@ -0,0 +1,13 @@
Option Explicit
'レジストリに登録する日付をプロパティに設定
Sub SetInstallDate
Session.Property("INSTALLDATE") = YYYYMMDD
End Sub
'YYYYMMDD形式の日付を返す
Function YYYYMMDD
YYYYMMDD = Year(Date) & Right("0" & Month(Date), 2) & Right("0" & Day(Date), 2)
End Function

BIN
Binary/Binary.NewBinary3 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

BIN
Binary/Binary.NewBinary4 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
Binary/Binary.NewBinary5 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
Binary/Binary.NewBinary6 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
Binary/Binary.NewBinary7 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary8 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

BIN
Binary/Binary.NewBinary9 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -23,7 +23,7 @@ Client PXE boot (UEFI Secure Boot)
| Service | Port | Purpose | | Service | Port | Purpose |
|-------------|-----------|------------------------------------------| |-------------|-----------|------------------------------------------|
| dnsmasq | 67/udp | DHCP (10.9.100.10-100, 12h lease) | | dnsmasq | 67/udp | DHCP (172.16.9.10-100, 12h lease) |
| dnsmasq | 69/udp | TFTP (serves ipxe.efi) | | dnsmasq | 69/udp | TFTP (serves ipxe.efi) |
| Apache | 80/tcp | HTTP (wimboot, WinPE boot files, proxy) | | Apache | 80/tcp | HTTP (wimboot, WinPE boot files, proxy) |
| Apache | 4433/tcp | iPXE boot script (GetPxeScript.aspx) | | Apache | 4433/tcp | iPXE boot script (GetPxeScript.aspx) |
@@ -32,8 +32,8 @@ Client PXE boot (UEFI Secure Boot)
### Network ### Network
- **PXE server IP:** `10.9.100.1/24` - **PXE server IP:** `172.16.9.1/24`
- **DHCP range:** `10.9.100.10` - `10.9.100.100` - **DHCP range:** `172.16.9.10` - `172.16.9.100`
- **Firewall:** UFW deny-by-default, only service ports open (22, 67, 69, 80, 445, 4433, 9009) - **Firewall:** UFW deny-by-default, only service ports open (22, 67, 69, 80, 445, 4433, 9009)
## Quick Start ## Quick Start
@@ -85,12 +85,12 @@ Creates a bootable USB with two partitions:
4. After reboot, the first-boot script: 4. After reboot, the first-boot script:
- Installs all offline .deb packages - Installs all offline .deb packages
- Runs the Ansible playbook (configures dnsmasq, Apache, Samba, UFW, webapp) - Runs the Ansible playbook (configures dnsmasq, Apache, Samba, UFW, webapp)
- Configures static IP `10.9.100.1/24` - Configures static IP `172.16.9.1/24`
5. Move the server's wired NIC to the isolated PXE switch 5. Move the server's wired NIC to the isolated PXE switch
### Step 5: Access the Web Interface ### Step 5: Access the Web Interface
Open `http://10.9.100.1:9009` from any machine on the isolated network. Open `http://172.16.9.1:9009` from any machine on the isolated network.
## Web Management Interface ## Web Management Interface
@@ -213,11 +213,11 @@ This creates `pxe-server-proxmox.iso` containing the Ubuntu installer, autoinsta
3. Attach the ISO as CD-ROM and start the VM 3. Attach the ISO as CD-ROM and start the VM
4. Ubuntu auto-installs with zero interaction (~10-15 minutes) 4. Ubuntu auto-installs with zero interaction (~10-15 minutes)
5. After reboot, first-boot configures all PXE services automatically 5. After reboot, first-boot configures all PXE services automatically
6. Access the web interface at `http://10.9.100.1:9009` 6. Access the web interface at `http://172.16.9.1:9009`
### Import WinPE Images ### Import WinPE Images
After the server is running, import deployment images via the web interface at `http://10.9.100.1:9009/import` or by mounting a USB drive with WinPE content. After the server is running, import deployment images via the web interface at `http://172.16.9.1:9009/import` or by mounting a USB drive with WinPE content.
## Samba Shares ## Samba Shares
@@ -235,7 +235,7 @@ All shares use guest access (no authentication) for ease of use on the isolated
Blancco Drive Eraser 7.15.1 boots via a native Ubuntu kernel with a custom initramfs (`blancco-init.sh`) that downloads and mounts the Blancco rootfs over HTTP. XML erasure reports are automatically saved to the PXE server's Samba share (`blancco-reports`). The server supports BMC cloud licensing for Blancco activation over WiFi. Blancco Drive Eraser 7.15.1 boots via a native Ubuntu kernel with a custom initramfs (`blancco-init.sh`) that downloads and mounts the Blancco rootfs over HTTP. XML erasure reports are automatically saved to the PXE server's Samba share (`blancco-reports`). The server supports BMC cloud licensing for Blancco activation over WiFi.
Reports are viewable and downloadable from the web interface at `http://10.9.100.1:9009/reports`. Reports are viewable and downloadable from the web interface at `http://172.16.9.1:9009/reports`.
## Notes ## Notes

View File

@@ -18,7 +18,7 @@ Client PXE boot
| Service | Port | Purpose | | Service | Port | Purpose |
|-------------|-----------|------------------------------------------| |-------------|-----------|------------------------------------------|
| dnsmasq | 67/udp | DHCP (10.9.100.10-100) | | dnsmasq | 67/udp | DHCP (172.16.9.10-100) |
| dnsmasq | 69/udp | TFTP (serves ipxe.efi) | | dnsmasq | 69/udp | TFTP (serves ipxe.efi) |
| Apache | 80/tcp | HTTP (wimboot, WinPE boot files, proxy) | | Apache | 80/tcp | HTTP (wimboot, WinPE boot files, proxy) |
| Apache | 4433/tcp | iPXE boot script (GetPxeScript.aspx) | | Apache | 4433/tcp | iPXE boot script (GetPxeScript.aspx) |
@@ -95,7 +95,7 @@ Move the server's wired NIC to the isolated switch for PXE clients.
### Step 6: Import WinPE Content (if not bundled in Step 3) ### Step 6: Import WinPE Content (if not bundled in Step 3)
**Option A:** Use the web interface at `http://10.9.100.1:9009` to import from USB. **Option A:** Use the web interface at `http://172.16.9.1:9009` to import from USB.
**Option B:** Manual copy: **Option B:** Manual copy:
```bash ```bash
@@ -107,7 +107,7 @@ sudo umount /mnt/usb2
## Web Management Interface ## Web Management Interface
Access at `http://10.9.100.1:9009` from any machine on the isolated network. Access at `http://172.16.9.1:9009` from any machine on the isolated network.
| Page | URL Path | Purpose | | Page | URL Path | Purpose |
|-------------------|-------------|-----------------------------------------------| |-------------------|-------------|-----------------------------------------------|
@@ -146,7 +146,7 @@ sudo ./test-vm.sh ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso
# Watch progress (Ctrl+] to detach) # Watch progress (Ctrl+] to detach)
sudo virsh console pxe-test sudo virsh console pxe-test
# After install: ssh pxe@10.9.100.1 / http://10.9.100.1:9009 # After install: ssh pxe@172.16.9.1 / http://172.16.9.1:9009
# Clean up # Clean up
sudo ./test-vm.sh --destroy sudo ./test-vm.sh --destroy
@@ -215,8 +215,8 @@ pxe-server/
## Network Configuration ## Network Configuration
- PXE server static IP: `10.9.100.1/24` - PXE server static IP: `172.16.9.1/24`
- DHCP range: `10.9.100.10` - `10.9.100.100` - DHCP range: `172.16.9.10` - `172.16.9.100`
- Lease time: 12 hours - Lease time: 12 hours
- DNS: `8.8.8.8` (passed to clients, not used by server) - DNS: `8.8.8.8` (passed to clients, not used by server)
- Firewall: UFW deny-by-default, allow 67/udp 69/udp 80/tcp 445/tcp 4433/tcp 9009/tcp - Firewall: UFW deny-by-default, allow 67/udp 69/udp 80/tcp 445/tcp 4433/tcp 9009/tcp

View File

@@ -17,7 +17,7 @@ autoinstall:
match: match:
name: "en*" name: "en*"
addresses: addresses:
- 10.9.100.1/24 - 172.16.9.1/24
dhcp4: false dhcp4: false
dhcp6: false dhcp6: false
optional: true optional: true

View File

@@ -16,8 +16,8 @@ systems.
## Network layout ## Network layout
- PXE server static IP: `10.9.100.1/24` on an isolated subnet. - PXE server static IP: `172.16.9.1/24` on an isolated subnet.
- DHCP range served by dnsmasq: `10.9.100.10 - 10.9.100.100`, 12h leases. - DHCP range served by dnsmasq: `172.16.9.10 - 172.16.9.100`, 12h leases.
- Default gateway and DNS handed out via DHCP point at the PXE server itself. - Default gateway and DNS handed out via DHCP point at the PXE server itself.
- The subnet has no route to the corporate LAN. Client traffic (Blancco BMC - The subnet has no route to the corporate LAN. Client traffic (Blancco BMC
cloud, Intune enrollment) goes out via WiFi after Windows boots; PXE-time cloud, Intune enrollment) goes out via WiFi after Windows boots; PXE-time
@@ -166,7 +166,7 @@ USB installer (2 partitions: ISO + CIDATA)
Ubuntu auto-install + first-boot Ansible playbook Ubuntu auto-install + first-boot Ansible playbook
| |
v v
Configured PXE server (10.9.100.1) ----+ Configured PXE server (172.16.9.1) ----+
| |
Windows PCs running Upload-Image.ps1 --+--> Image content (SMB, webapp import) Windows PCs running Upload-Image.ps1 --+--> Image content (SMB, webapp import)
| |

View File

@@ -22,9 +22,9 @@ contribute a `config/sites/<sitename>.yaml` template back to the repo.
| Value | Default | Where it lives | | Value | Default | Where it lives |
|-------------------|----------------------|--------------------------------------------------------------------------------| |-------------------|----------------------|--------------------------------------------------------------------------------|
| PXE server IP | 10.9.100.1 | `playbook/pxe_server_setup.yml` (dnsmasq config, iPXE script, samba conf, webapp env), `playbook/startnet.cmd` (mount paths), `boot-tools/blancco/grub-blancco.cfg` (TFTP/HTTP URLs) | | PXE server IP | 172.16.9.1 | `playbook/pxe_server_setup.yml` (dnsmasq config, iPXE script, samba conf, webapp env), `playbook/startnet.cmd` (mount paths), `boot-tools/blancco/grub-blancco.cfg` (TFTP/HTTP URLs) |
| PXE subnet | 10.9.100.0/24 | Same as above, plus `playbook/pxe_server_setup.yml` (UFW rules) | | PXE subnet | 172.16.9.0/24 | Same as above, plus `playbook/pxe_server_setup.yml` (UFW rules) |
| DHCP range | 10.9.100.10-100 | `playbook/pxe_server_setup.yml` (dnsmasq config) | | DHCP range | 172.16.9.10-100 | `playbook/pxe_server_setup.yml` (dnsmasq config) |
| Hostname | pxeserver | `autoinstall/user-data` (identity.hostname) | | Hostname | pxeserver | `autoinstall/user-data` (identity.hostname) |
### Identity and credentials ### Identity and credentials
@@ -143,7 +143,7 @@ Blob storage account.
### Image-upload paths on Windows ### Image-upload paths on Windows
`scripts/Upload-Image.ps1` defaults to: `scripts/Upload-Image.ps1` defaults to:
- `\\10.9.100.1\image-upload` as the destination - `\\172.16.9.1\image-upload` as the destination
- `C:\ProgramData\GEAerospace\MediaCreator\Cache\` as the source - `C:\ProgramData\GEAerospace\MediaCreator\Cache\` as the source
Update both for a different site. Update both for a different site.
@@ -156,10 +156,10 @@ A site config file should drive substitution at build time. Proposed schema:
# config/sites/<sitename>.yaml # config/sites/<sitename>.yaml
site: site:
name: westjeff name: westjeff
pxe_server_ip: 10.9.100.1 pxe_server_ip: 172.16.9.1
pxe_subnet: 10.9.100.0/24 pxe_subnet: 172.16.9.0/24
dhcp_range_start: 10.9.100.10 dhcp_range_start: 172.16.9.10
dhcp_range_end: 10.9.100.100 dhcp_range_end: 172.16.9.100
hostname: pxeserver hostname: pxeserver
credentials: credentials:

View File

@@ -196,7 +196,7 @@ Two separate copies of overlapping content with different roles:
| Path | Source | Used by | Updated when | | Path | Source | Used by | Updated when |
|------|--------|---------|--------------| |------|--------|---------|--------------|
| `C:\Enrollment\shopfloor-setup\` | PXE imaging copy from `\\10.9.100.1\enrollment\shopfloor-setup\` | Imaging-flow scripts: `Run-ShopfloorSetup.ps1`, `Stage-Dispatcher.ps1`, `Set-MachineNumber.ps1` -> `Update-MachineNumber.ps1` | Re-image only | | `C:\Enrollment\shopfloor-setup\` | PXE imaging copy from `\\172.16.9.1\enrollment\shopfloor-setup\` | Imaging-flow scripts: `Run-ShopfloorSetup.ps1`, `Stage-Dispatcher.ps1`, `Set-MachineNumber.ps1` -> `Update-MachineNumber.ps1` | Re-image only |
| SFLD share `\<scope>\` | Direct upload | GE-Enforce.ps1 / Install-FromManifest.ps1 (every logon) | Direct file upload to share | | SFLD share `\<scope>\` | Direct upload | GE-Enforce.ps1 / Install-FromManifest.ps1 (every logon) | Direct file upload to share |
Implication for hot-fixing scripts: a fix to `Restore-UDCData.ps1` needs to Implication for hot-fixing scripts: a fix to `Restore-UDCData.ps1` needs to

View File

@@ -52,11 +52,11 @@ Add a new entry (insert before the existing `D12 OptiPlex Family / 7090` entry):
the actual driver pack from Dell's catalog by model name (`extract_model_ids` the actual driver pack from Dell's catalog by model name (`extract_model_ids`
matches "7080") and downloads the latest pack at run time. matches "7080") and downloads the latest pack at run time.
### Side artifacts already on the live PXE server (10.9.100.1) ### Side artifacts already on the live PXE server (172.16.9.1)
- `\\10.9.100.1\winpeapps\_shared\BIOS\OptiPlex_7080_1.37.0.exe` (39.8 MB, BIOS update) - `\\172.16.9.1\winpeapps\_shared\BIOS\OptiPlex_7080_1.37.0.exe` (39.8 MB, BIOS update)
- `\\10.9.100.1\image-upload\Deploy\Out-of-box Drivers\Dell_11\OptiPlex\D11 OptiPlex Family\win11_70809ntr8_a09.zip` (Win11 driver pack, 2.6 GB) - `\\172.16.9.1\image-upload\Deploy\Out-of-box Drivers\Dell_11\OptiPlex\D11 OptiPlex Family\win11_70809ntr8_a09.zip` (Win11 driver pack, 2.6 GB)
- `\\10.9.100.1\winpeapps\_shared\BIOS\models.txt` includes the 7080 line. - `\\172.16.9.1\winpeapps\_shared\BIOS\models.txt` includes the 7080 line.
These persist regardless of `geastandardpbr/` rebuilds. Only the model-registry These persist regardless of `geastandardpbr/` rebuilds. Only the model-registry
edits need to be re-applied after a USB re-import. edits need to be re-applied after a USB re-import.

View File

@@ -6,7 +6,7 @@ Step-by-step for imaging a new (or replacement) shopfloor PC that will sit at a
- PC connected to the **PXE switch** (not the production network yet) - PC connected to the **PXE switch** (not the production network yet)
- USB mouse + keyboard connected - USB mouse + keyboard connected
- PXE server is running and reachable (verify by pinging `10.9.100.1` from another PC on the same switch) - PXE server is running and reachable (verify by pinging `172.16.9.1` from another PC on the same switch)
- **Target machine number** known (e.g., `7605`) — you can enter it at PXE time, or use `9999` as a placeholder if the PC will be configured at the bay later - **Target machine number** known (e.g., `7605`) — you can enter it at PXE time, or use `9999` as a placeholder if the PC will be configured at the bay later
- **ARTS Lockdown request submitted** for this PC (or know that you'll submit one mid-imaging) - **ARTS Lockdown request submitted** for this PC (or know that you'll submit one mid-imaging)
@@ -229,7 +229,7 @@ The script needs a desktop session. Won't run via WinRM/SSH/non-interactive. Mak
## Reference ## Reference
- **PXE server**: `10.9.100.1` - **PXE server**: `172.16.9.1`
- **SFLD share**: `\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\` - **SFLD share**: `\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\`
- **Manifest engine log**: `C:\GE Aerospace\machineapps-enforce.log` - **Manifest engine log**: `C:\GE Aerospace\machineapps-enforce.log`
- **Intune sync transcript**: `C:\Logs\SFLD\sync_intune_transcript.txt` - **Intune sync transcript**: `C:\Logs\SFLD\sync_intune_transcript.txt`

View File

@@ -166,11 +166,16 @@
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>6</Order> <Order>6</Order>
<CommandLine>msiexec.exe /i "C:\PreInstall\installers\powershell7\PowerShell-7.5.4-win-x64.msi" /qn /norestart ADD_PATH=1 USE_MU=0 ENABLE_MU=0 DISABLE_TELEMETRY=1</CommandLine>
<Description>Install PowerShell 7 BEFORE PPKG so Intune SetupCredentials Win32App finds pwsh.exe (race fix)</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>7</Order>
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\run-enrollment.ps1"</CommandLine> <CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\run-enrollment.ps1"</CommandLine>
<Description>Run GCCH Enrollment</Description> <Description>Run GCCH Enrollment</Description>
</SynchronousCommand> </SynchronousCommand>
<SynchronousCommand wcm:action="add"> <SynchronousCommand wcm:action="add">
<Order>7</Order> <Order>8</Order>
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Run-ShopfloorSetup.ps1"</CommandLine> <CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Run-ShopfloorSetup.ps1"</CommandLine>
<Description>Run shopfloor PC type setup</Description> <Description>Run shopfloor PC type setup</Description>
</SynchronousCommand> </SynchronousCommand>

View File

@@ -80,10 +80,10 @@ echo " IFACE=$IFACE, bringing up..."
ip link set "$IFACE" up || ifconfig "$IFACE" up ip link set "$IFACE" up || ifconfig "$IFACE" up
sleep 2 sleep 2
SERVER=10.9.100.1 SERVER=172.16.9.1
ifconfig "$IFACE" 10.9.100.250 netmask 255.255.255.0 up ifconfig "$IFACE" 172.16.9.250 netmask 255.255.255.0 up
sleep 1 sleep 1
echo " IP: 10.9.100.250 SERVER: $SERVER" echo " IP: 172.16.9.250 SERVER: $SERVER"
ip addr ip addr
echo "[3/5] Downloading airootfs.sfs (~756 MB)..." echo "[3/5] Downloading airootfs.sfs (~756 MB)..."

View File

@@ -176,7 +176,7 @@
<username encrypted="false">blancco</username> <username encrypted="false">blancco</username>
<password encrypted="false">blancco</password> <password encrypted="false">blancco</password>
<domain/> <domain/>
<hostname>10.9.100.1</hostname> <hostname>172.16.9.1</hostname>
<path>blancco-reports</path> <path>blancco-reports</path>
<protocols key="protocol" type="array"> <protocols key="protocol" type="array">
<protocol selected="true">smb</protocol> <protocol selected="true">smb</protocol>

View File

@@ -3,16 +3,16 @@
# Previously this disabled all wired NICs at first logon to keep PPKG / # Previously this disabled all wired NICs at first logon to keep PPKG /
# Intune enrollment routing internet traffic via WiFi. The wired NIC was # Intune enrollment routing internet traffic via WiFi. The wired NIC was
# preferred by Windows because the PXE dnsmasq was handing out a default # preferred by Windows because the PXE dnsmasq was handing out a default
# gateway (dhcp-option=3,10.9.100.1) which Windows installed as a default # gateway (dhcp-option=3,172.16.9.1) which Windows installed as a default
# route, and the lower interface metric of wired beat WiFi. Internet-bound # route, and the lower interface metric of wired beat WiFi. Internet-bound
# traffic then black-holed at 10.9.100.1 (the PXE server, which doesn't # traffic then black-holed at 172.16.9.1 (the PXE server, which doesn't
# forward). # forward).
# #
# That root cause was fixed by removing the dhcp-option=3 and =6 lines # That root cause was fixed by removing the dhcp-option=3 and =6 lines
# from /etc/dnsmasq.conf on the PXE server. Without an advertised gateway # from /etc/dnsmasq.conf on the PXE server. Without an advertised gateway
# on the PXE side, Windows can't add a default route via wired, so all # on the PXE side, Windows can't add a default route via wired, so all
# internet traffic uses WiFi by default and the wired NIC stays harmless # internet traffic uses WiFi by default and the wired NIC stays harmless
# for same-subnet PXE/SMB traffic to 10.9.100.1. # for same-subnet PXE/SMB traffic to 172.16.9.1.
# #
# Side effect of the original behavior was an eDNC race: eDNC autostart # Side effect of the original behavior was an eDNC race: eDNC autostart
# would fire while the wired NIC was still disabled and hit WSAEINVAL # would fire while the wired NIC was still disabled and hit WSAEINVAL

View File

@@ -2,6 +2,17 @@
"Version": "1.0", "Version": "1.0",
"Site": "West Jefferson", "Site": "West Jefferson",
"Applications": [ "Applications": [
{
"_comment": "PowerShell 7.5.4 - installed BEFORE PPKG via FlatUnattendW10-shopfloor.xml FirstLogonCommand Order 6 (race fix: Intune SetupCredentials Win32App install command starts with pwsh.exe; if PS7 not yet installed when that Win32App fires, it errors with FILE_NOT_FOUND 0x80070002 and IME's GRS retry never re-fires under V3Processor). This entry is a backstop - no-op via ProductCode detection if unattend Order 6 already installed it. PreEnrollment flag is informational; runner does not currently filter on it.",
"Name": "PowerShell 7.5.4",
"Installer": "powershell7\\PowerShell-7.5.4-win-x64.msi",
"Type": "MSI",
"InstallArgs": "/qn /norestart ADD_PATH=1 USE_MU=0 ENABLE_MU=0 DISABLE_TELEMETRY=1",
"DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{E8159677-ACF8-4D64-9D36-5C36B8BBEA39}",
"PreEnrollment": true,
"PCTypes": ["*"]
},
{ {
"_comment": "Oracle Client 11.2 Administrator - installed first because downstream apps (eDNC/NTLARS/UDC and CMM tooling) link against the Oracle home and fail cold if it's missing. Installer is a .cmd wrapper (Type=EXE is the preinstall runner's shim for non-MSI launchers, same pattern as OpenText Setup-OpenText.cmd). The wrapper expects Oracle_OracleDatabase_11r2_V03.zip (686 MB) staged next to it, unpacks to %TEMP%, runs Oracle Universal Installer silently with ge_client_install.rsp, then cleans up the staging dir. OUI exit 3 is treated as success (warnings-but-ok). Detection via the registered home key; downstream upgrades or version pins are handled by the runtime enforcer's Oracle Client 11.2 manifest entry in common/manifest.json.", "_comment": "Oracle Client 11.2 Administrator - installed first because downstream apps (eDNC/NTLARS/UDC and CMM tooling) link against the Oracle home and fail cold if it's missing. Installer is a .cmd wrapper (Type=EXE is the preinstall runner's shim for non-MSI launchers, same pattern as OpenText Setup-OpenText.cmd). The wrapper expects Oracle_OracleDatabase_11r2_V03.zip (686 MB) staged next to it, unpacks to %TEMP%, runs Oracle Universal Installer silently with ge_client_install.rsp, then cleans up the staging dir. OUI exit 3 is treated as success (warnings-but-ok). Detection via the registered home key; downstream upgrades or version pins are handled by the runtime enforcer's Oracle Client 11.2 manifest entry in common/manifest.json.",
"Name": "Oracle Client 11.2", "Name": "Oracle Client 11.2",
@@ -153,7 +164,7 @@
"Type": "EXE", "Type": "EXE",
"InstallArgs": "", "InstallArgs": "",
"LogFile": "C:\\Logs\\PreInstall\\Setup-OpenText.log", "LogFile": "C:\\Logs\\PreInstall\\Setup-OpenText.log",
"PCTypes": ["Standard", "CMM", "Keyence", "Genspect", "WaxAndTrace", "Lab"] "PCTypes": ["Standard", "CMM", "Keyence", "Genspect", "WaxAndTrace", "Lab", "Heattreat"]
}, },
{ {
"_comment": "UDC_Setup.exe spawns a hidden WPF window (UDC.exe) after install and never exits, so the runner needs KillAfterDetection: true to terminate UDC_Setup.exe + UDC.exe once the registry detection passes. This is an OPT-IN flag - normal installers should NOT set it because killing msiexec mid-install leaves msiserver holding the install mutex and the next msiexec call returns 1618 (Oracle hit this exact bug).", "_comment": "UDC_Setup.exe spawns a hidden WPF window (UDC.exe) after install and never exits, so the runner needs KillAfterDetection: true to terminate UDC_Setup.exe + UDC.exe once the registry detection passes. This is an OPT-IN flag - normal installers should NOT set it because killing msiexec mid-install leaves msiserver holding the install mutex and the next msiexec call returns 1618 (Oracle hit this exact bug).",
@@ -164,7 +175,9 @@
"KillAfterDetection": true, "KillAfterDetection": true,
"DetectionMethod": "Registry", "DetectionMethod": "Registry",
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\UDC", "DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\UDC",
"PCTypes": ["Standard-Machine"] "PCTypes": ["gea-shopfloor-collections"],
"PCTypesStrict": true,
"_pcTypesNote": "UDC = the C in 'collections'. nocollections does NOT collect data so MUST NOT install UDC. PCTypesStrict bypasses the alias-expansion matcher so a nocollections PC's myNames (which transitively contains gea-shopfloor-collections via the Standard group) still won't match this entry."
}, },
{ {
"_comment": "Display kiosk app (Lobby Display or Dashboard). Install-KioskApp.cmd wrapper reads C:\\Enrollment\\display-type.txt to determine which installer to run. Both GEAerospaceLobbyDisplaySetup.exe and GEAerospaceDashboardSetup.exe must be staged in the display\\ subtree alongside the wrapper. Inno Setup /VERYSILENT is idempotent so no detection needed.", "_comment": "Display kiosk app (Lobby Display or Dashboard). Install-KioskApp.cmd wrapper reads C:\\Enrollment\\display-type.txt to determine which installer to run. Both GEAerospaceLobbyDisplaySetup.exe and GEAerospaceDashboardSetup.exe must be staged in the display\\ subtree alongside the wrapper. Inno Setup /VERYSILENT is idempotent so no detection needed.",

View File

@@ -3,7 +3,7 @@
# pxe-dhcp-hook.sh - dnsmasq dhcp-script hook. # pxe-dhcp-hook.sh - dnsmasq dhcp-script hook.
# #
# Runs every time a PXE client gets/changes/releases a DHCP lease on # Runs every time a PXE client gets/changes/releases a DHCP lease on
# 10.9.100.0/24. Flushes conntrack entries and drops any lingering # 172.16.9.0/24. Flushes conntrack entries and drops any lingering
# TCP sockets for that client IP. Prevents stale server-side state from # TCP sockets for that client IP. Prevents stale server-side state from
# causing "System error 53 - network path not found" when a WinPE client # causing "System error 53 - network path not found" when a WinPE client
# re-images the same machine without a clean SMB session teardown. # re-images the same machine without a clean SMB session teardown.

View File

@@ -14,7 +14,7 @@
# Step 2: restart nmbd (NetBIOS daemon - separate from smbd) # Step 2: restart nmbd (NetBIOS daemon - separate from smbd)
# Step 3: restart smbd (full smbd restart, kills all child sessions) # Step 3: restart smbd (full smbd restart, kills all child sessions)
# Step 4: kill any leftover smbd child processes that survived restart # Step 4: kill any leftover smbd child processes that survived restart
# Step 5: flush conntrack for 10.9.100.0/24 (kernel connection tracking) # Step 5: flush conntrack for 172.16.9.0/24 (kernel connection tracking)
# Step 6: flush ARP / neighbour cache on br-pxe # Step 6: flush ARP / neighbour cache on br-pxe
# Step 7: drop TCP sockets on port 445 via ss -K # Step 7: drop TCP sockets on port 445 via ss -K
# Step 8: restart dnsmasq (DHCP/TFTP state as a last resort before reboot) # Step 8: restart dnsmasq (DHCP/TFTP state as a last resort before reboot)
@@ -56,10 +56,10 @@ sleep 1
systemctl start smbd 2>&1 systemctl start smbd 2>&1
pause "Step 4 done" pause "Step 4 done"
echo "=== Step 5/8: flush conntrack entries for 10.9.100.0/24 ===" echo "=== Step 5/8: flush conntrack entries for 172.16.9.0/24 ==="
if command -v conntrack >/dev/null 2>&1; then if command -v conntrack >/dev/null 2>&1; then
conntrack -D -s 10.9.100.0/24 2>&1 || true conntrack -D -s 172.16.9.0/24 2>&1 || true
conntrack -D -d 10.9.100.0/24 2>&1 || true conntrack -D -d 172.16.9.0/24 2>&1 || true
else else
echo " conntrack tool not installed - skipping (apt install conntrack)" echo " conntrack tool not installed - skipping (apt install conntrack)"
fi fi

View File

@@ -72,7 +72,7 @@
loop: "{{ ansible_interfaces | select('match','^e(th|n)') | list }}" loop: "{{ ansible_interfaces | select('match','^e(th|n)') | list }}"
ignore_errors: yes ignore_errors: yes
- name: "Find interface with 10.9.100.1 already configured" - name: "Find interface with 172.16.9.1 already configured"
set_fact: set_fact:
preconfigured_iface: >- preconfigured_iface: >-
{{ ansible_interfaces {{ ansible_interfaces
@@ -80,7 +80,7 @@
| map('regex_replace','^(.*)$','ansible_\1') | map('regex_replace','^(.*)$','ansible_\1')
| map('extract', hostvars[inventory_hostname]) | map('extract', hostvars[inventory_hostname])
| selectattr('ipv4','defined') | selectattr('ipv4','defined')
| selectattr('ipv4.address','equalto','10.9.100.1') | selectattr('ipv4.address','equalto','172.16.9.1')
| map(attribute='device') | map(attribute='device')
| list | list
| first | first
@@ -147,11 +147,11 @@
port=0 port=0
interface={{ pxe_iface }} interface={{ pxe_iface }}
bind-interfaces bind-interfaces
dhcp-range=10.9.100.10,10.9.100.100,12h dhcp-range=172.16.9.10,172.16.9.100,12h
# No default gateway (option 3) and no DNS (option 6) handed out: # No default gateway (option 3) and no DNS (option 6) handed out:
# the PXE network is isolated and the PXE server does not forward # the PXE network is isolated and the PXE server does not forward
# internet traffic. Previously we set both, which made imaged PCs # internet traffic. Previously we set both, which made imaged PCs
# add a default route via 10.9.100.1 and prefer it over WiFi (lower # add a default route via 172.16.9.1 and prefer it over WiFi (lower
# interface metric). PPKG / Intune enrollment then black-holed # interface metric). PPKG / Intune enrollment then black-holed
# internet-bound traffic. The fix used to be migrate-to-wifi.ps1 # internet-bound traffic. The fix used to be migrate-to-wifi.ps1
# disabling the wired NIC during first-logon, which created an # disabling the wired NIC during first-logon, which created an
@@ -163,7 +163,7 @@
# Important: dnsmasq DEFAULTS to sending its own listening address as # Important: dnsmasq DEFAULTS to sending its own listening address as
# both router and DNS when these options are unset. Commenting them # both router and DNS when these options are unset. Commenting them
# out is NOT the same as disabling - imaged PCs (and Blancco PXE # out is NOT the same as disabling - imaged PCs (and Blancco PXE
# clients) end up with 10.9.100.1 as gateway. The empty-value form # clients) end up with 172.16.9.1 as gateway. The empty-value form
# below explicitly suppresses both options. # below explicitly suppresses both options.
dhcp-option=3 dhcp-option=3
dhcp-option=6 dhcp-option=6
@@ -227,7 +227,7 @@
content: | content: |
#!ipxe #!ipxe
set server 10.9.100.1 set server 172.16.9.1
:menu :menu
menu GE Aerospace PXE Boot Menu menu GE Aerospace PXE Boot Menu
@@ -505,7 +505,7 @@
- name: "Deploy BIOS check script + manifest to winpeapps/_shared/BIOS/" - name: "Deploy BIOS check script + manifest to winpeapps/_shared/BIOS/"
# Path matches what startnet.cmd reads at WinPE boot: # Path matches what startnet.cmd reads at WinPE boot:
# net use B: \\10.9.100.1\winpeapps\_shared # net use B: \\172.16.9.1\winpeapps\_shared
# if exist B:\BIOS\check-bios.cmd ... # if exist B:\BIOS\check-bios.cmd ...
# Earlier deploy targeted enrollment/pre-install/bios/ (different share) # Earlier deploy targeted enrollment/pre-install/bios/ (different share)
# which startnet.cmd never read, so BIOS_STATUS perma-stuck on # which startnet.cmd never read, so BIOS_STATUS perma-stuck on
@@ -899,7 +899,7 @@
shell: | shell: |
set -e set -e
python3 -c 'import xml.etree.ElementTree as ET; ET.parse("{{ web_root }}/blancco/preferences.xml")' python3 -c 'import xml.etree.ElementTree as ET; ET.parse("{{ web_root }}/blancco/preferences.xml")'
grep -q '<hostname>10.9.100.1</hostname>' "{{ web_root }}/blancco/preferences.xml" grep -q '<hostname>172.16.9.1</hostname>' "{{ web_root }}/blancco/preferences.xml"
grep -q '<path>blancco-reports</path>' "{{ web_root }}/blancco/preferences.xml" grep -q '<path>blancco-reports</path>' "{{ web_root }}/blancco/preferences.xml"
changed_when: false changed_when: false
@@ -1089,7 +1089,7 @@
# Single-NIC fresh-deploy default. Boxes that need higher throughput # Single-NIC fresh-deploy default. Boxes that need higher throughput
# (e.g. WJF prod uses a USB-C 5 Gbps NIC) override this with a bridge # (e.g. WJF prod uses a USB-C 5 Gbps NIC) override this with a bridge
# config bonding the USB NIC + onboard NIC into br-pxe. Live override # config bonding the USB NIC + onboard NIC into br-pxe. Live override
# currently deployed on 10.9.100.1 (do NOT re-run this task there # currently deployed on 172.16.9.1 (do NOT re-run this task there
# without first reviewing /etc/netplan/50-cloud-init.yaml.pre-gold-swap): # without first reviewing /etc/netplan/50-cloud-init.yaml.pre-gold-swap):
# #
# network: # network:
@@ -1101,7 +1101,7 @@
# bridges: # bridges:
# br-pxe: # br-pxe:
# interfaces: [enp128s31f6, enx34c8d6b11010] # interfaces: [enp128s31f6, enx34c8d6b11010]
# addresses: [10.9.100.1/24] # addresses: [172.16.9.1/24]
# parameters: # parameters:
# stp: false # stp: false
# #
@@ -1120,7 +1120,7 @@
ethernets: ethernets:
{{ pxe_iface }}: {{ pxe_iface }}:
dhcp4: no dhcp4: no
addresses: [10.9.100.1/24] addresses: [172.16.9.1/24]
notify: "Apply netplan" notify: "Apply netplan"
handlers: handlers:

View File

@@ -27,7 +27,7 @@ Write-Host "================================================================"
Write-Host "" Write-Host ""
# Imaging-progress reporter. Posts coarse stage updates to the PXE webapp # Imaging-progress reporter. Posts coarse stage updates to the PXE webapp
# at http://10.9.100.1:9009/imaging/status so the operator can watch # at http://172.16.9.1:9009/imaging/status so the operator can watch
# progress in a browser. Best-effort: failures never block imaging. # progress in a browser. Best-effort: failures never block imaging.
$pxeStatusLib = Join-Path $PSScriptRoot 'shopfloor-setup\Shopfloor\lib\Send-PxeStatus.ps1' $pxeStatusLib = Join-Path $PSScriptRoot 'shopfloor-setup\Shopfloor\lib\Send-PxeStatus.ps1'
if (Test-Path $pxeStatusLib) { if (Test-Path $pxeStatusLib) {

View File

@@ -165,8 +165,12 @@ if (Test-Path -LiteralPath $machineNumFile) {
# before UDC_Setup.exe runs means the installer's File.Copy (overwrite:true) # before UDC_Setup.exe runs means the installer's File.Copy (overwrite:true)
# would overwrite it IF the share were reachable, but since it isn't, our # would overwrite it IF the share were reachable, but since it isn't, our
# pre-staged file survives and UDC launches with correct settings. # pre-staged file survives and UDC launches with correct settings.
# UDC payload (settings backups + webserver settings) lives only in the
# collections per-pc-type dir - UDC is the "C" of "collections". On nocoll
# bays the dir doesn't exist; Test-Path skips silently.
$udcCollDir = Join-Path (Split-Path $PSScriptRoot -Parent) 'gea-shopfloor-collections'
if ($machineNum -and $machineNum -ne '9999') { if ($machineNum -and $machineNum -ne '9999') {
$udcBackupDir = 'C:\Enrollment\shopfloor-setup\Standard\udc-backups' $udcBackupDir = Join-Path $udcCollDir 'udc-backups'
$udcBackup = Join-Path $udcBackupDir "udc_settings_$machineNum.json" $udcBackup = Join-Path $udcBackupDir "udc_settings_$machineNum.json"
$udcTarget = 'C:\ProgramData\UDC\udc_settings.json' $udcTarget = 'C:\ProgramData\UDC\udc_settings.json'
if (Test-Path -LiteralPath $udcBackup) { if (Test-Path -LiteralPath $udcBackup) {
@@ -176,11 +180,11 @@ if ($machineNum -and $machineNum -ne '9999') {
Copy-Item -Path $udcBackup -Destination $udcTarget -Force Copy-Item -Path $udcBackup -Destination $udcTarget -Force
Write-PreInstallLog "Pre-staged UDC settings from $udcBackup -> $udcTarget" Write-PreInstallLog "Pre-staged UDC settings from $udcBackup -> $udcTarget"
} else { } else {
Write-PreInstallLog "No UDC settings backup for machine $machineNum in $udcBackupDir" Write-PreInstallLog "No UDC settings backup for machine $machineNum at $udcBackup (skipping - normal for nocoll bays)"
} }
} }
$udcWebSrc = 'C:\Enrollment\shopfloor-setup\Standard\udc_webserver_settings.json' $udcWebSrc = Join-Path $udcCollDir 'udc_webserver_settings.json'
$udcWebDst = 'C:\ProgramData\UDC\udc_webserver_settings.json' $udcWebDst = 'C:\ProgramData\UDC\udc_webserver_settings.json'
if (Test-Path -LiteralPath $udcWebSrc) { if (Test-Path -LiteralPath $udcWebSrc) {
if (-not (Test-Path 'C:\ProgramData\UDC')) { if (-not (Test-Path 'C:\ProgramData\UDC')) {
@@ -189,7 +193,7 @@ if (Test-Path -LiteralPath $udcWebSrc) {
Copy-Item -Path $udcWebSrc -Destination $udcWebDst -Force Copy-Item -Path $udcWebSrc -Destination $udcWebDst -Force
Write-PreInstallLog "Pre-staged UDC webserver settings from $udcWebSrc -> $udcWebDst" Write-PreInstallLog "Pre-staged UDC webserver settings from $udcWebSrc -> $udcWebDst"
} else { } else {
Write-PreInstallLog "No UDC webserver settings file at $udcWebSrc" "WARN" Write-PreInstallLog "No UDC webserver settings file at $udcWebSrc (skipping - normal for nocoll bays)"
} }
# --- Suppress Windows Defender Firewall "Allow access" prompts globally for # --- Suppress Windows Defender Firewall "Allow access" prompts globally for
@@ -326,15 +330,27 @@ foreach ($app in $config.Applications) {
if ($g -icontains $n) { foreach ($x in $g) { [void]$myNames.Add($x) } } if ($g -icontains $n) { foreach ($x in $g) { [void]$myNames.Add($x) } }
} }
} }
# PCTypesStrict=true bypasses the alias-expansion matcher and requires
# the actual pcType (or composite pcProfileKey) to literally equal one
# of the allowedTypes entries. Used by UDC because the alias graph
# transitively connects gea-shopfloor-collections <-> nocollections via
# the legacy 'Standard' group, which would otherwise cause UDC to install
# on nocoll bays even with PCTypes=['gea-shopfloor-collections'].
$matchesType = ($allowedTypes -contains '*') $matchesType = ($allowedTypes -contains '*')
if (-not $matchesType) { if (-not $matchesType) {
foreach ($t in $allowedTypes) { if ($app.PCTypesStrict) {
if ($myNames.Contains($t)) { $matchesType = $true; break } foreach ($t in $allowedTypes) {
foreach ($g in $aliasGroups) { if (($pcType -ieq $t) -or ($pcProfileKey -ieq $t)) { $matchesType = $true; break }
if ($g -icontains $t) { }
foreach ($x in $g) { if ($myNames.Contains($x)) { $matchesType = $true; break } } } else {
foreach ($t in $allowedTypes) {
if ($myNames.Contains($t)) { $matchesType = $true; break }
foreach ($g in $aliasGroups) {
if ($g -icontains $t) {
foreach ($x in $g) { if ($myNames.Contains($x)) { $matchesType = $true; break } }
}
if ($matchesType) { break }
} }
if ($matchesType) { break }
} }
} }
} }

View File

@@ -5,7 +5,7 @@
# #
# Reason: GE's Intune Proactive-Remediation "Report IP" script enumerates # Reason: GE's Intune Proactive-Remediation "Report IP" script enumerates
# Get-NetIPAddress and POSTs every IP it finds to a GE webhook. When a # Get-NetIPAddress and POSTs every IP it finds to a GE webhook. When a
# shopfloor bay is still cabled to the air-gapped PXE LAN (10.9.100.0/24), # shopfloor bay is still cabled to the air-gapped PXE LAN (172.16.9.0/24),
# the webhook sees 10.9.100.x as one of the device's IPs and tags the bay # the webhook sees 10.9.100.x as one of the device's IPs and tags the bay
# "not on corp net". A dynamic group / assignment-filter at GE then excludes # "not on corp net". A dynamic group / assignment-filter at GE then excludes
# the bay from receiving the SFLD ConfigurationProfile (Function + SasToken # the bay from receiving the SFLD ConfigurationProfile (Function + SasToken

View File

@@ -66,6 +66,15 @@ if (Test-Path -LiteralPath $subtypeFile) {
$pcSubtype = (Get-Content -LiteralPath $subtypeFile -First 1 -ErrorAction SilentlyContinue).Trim() $pcSubtype = (Get-Content -LiteralPath $subtypeFile -First 1 -ErrorAction SilentlyContinue).Trim()
} }
# Display sub-type fallback: if pc-subtype.txt is absent (post-rename-reorg
# default) but display-type.txt exists, use it as the subtype. Lets the
# Display-Lobby / Display-Dashboard / gea-shopfloor-display-{lobby,dashboard}
# profile keys resolve correctly for Display PCs.
$displayTypeFile = 'C:\Enrollment\display-type.txt'
if (-not $pcSubtype -and ($pcType -ieq 'gea-shopfloor-display' -or $pcType -ieq 'Display') -and (Test-Path -LiteralPath $displayTypeFile)) {
$pcSubtype = (Get-Content -LiteralPath $displayTypeFile -First 1 -ErrorAction SilentlyContinue).Trim()
}
# Build the profile key: "Standard-Machine", "CMM", "Display-Lobby", etc. # Build the profile key: "Standard-Machine", "CMM", "Display-Lobby", etc.
$profileKey = if ($pcSubtype) { "$pcType-$pcSubtype" } else { $pcType } $profileKey = if ($pcSubtype) { "$pcType-$pcSubtype" } else { $pcType }
@@ -82,6 +91,8 @@ $pcProfileAliasGroups = @(
@('WaxAndTrace', 'gea-shopfloor-waxtrace'), @('WaxAndTrace', 'gea-shopfloor-waxtrace'),
@('Genspect', 'gea-shopfloor-genspect'), @('Genspect', 'gea-shopfloor-genspect'),
@('Display', 'gea-shopfloor-display'), @('Display', 'gea-shopfloor-display'),
@('Display-Lobby', 'gea-shopfloor-display-Lobby', 'gea-shopfloor-display-lobby'),
@('Display-Dashboard', 'gea-shopfloor-display-Dashboard', 'gea-shopfloor-display-dashboard'),
@('Heattreat', 'gea-shopfloor-heattreat') @('Heattreat', 'gea-shopfloor-heattreat')
) )

View File

@@ -19,7 +19,7 @@ function Send-PxeStatus {
# Only available post-AAD-join; pass it from Monitor-IntuneProgress # Only available post-AAD-join; pass it from Monitor-IntuneProgress
# once captured. The dashboard renders a QR of this value. # once captured. The dashboard renders a QR of this value.
[string]$IntuneDeviceId = '', [string]$IntuneDeviceId = '',
[string]$PxeServer = '10.9.100.1', [string]$PxeServer = '172.16.9.1',
[int]$Port = 9009, [int]$Port = 9009,
[int]$TimeoutSec = 5 [int]$TimeoutSec = 5
) )

View File

@@ -0,0 +1,44 @@
# Set-OpenTextAutoStart.ps1 - place WJ Shopfloor.lnk in the All Users
# Startup folder so HostExplorer's "WJ Shopfloor" session launches at
# every login. Idempotent: re-running is a no-op when the .lnk already
# exists at the same path.
#
# Used by per-pc-type 09-Setup scripts for shopfloor types whose only
# business app is OpenText (common, waxtrace, genspect, heattreat).
# collections + nocollections do NOT auto-start OpenText - their techs
# pick which apps via Configure-PC.ps1.
#
# Source .lnk is created by the OpenText preinstall (Setup-OpenText.ps1)
# on the public desktop. If the .lnk is missing, log a warning and exit
# 0 - imaging chain still continues; auto-start can be re-attempted on a
# subsequent login by re-running this script.
$ErrorActionPreference = 'Continue'
$startupDir = 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp'
$publicDesktop = 'C:\Users\Public\Desktop'
$candidates = @(
Join-Path $publicDesktop 'WJ Shopfloor.lnk'
Join-Path (Join-Path $publicDesktop 'Shopfloor Tools') 'WJ Shopfloor.lnk'
)
$src = $candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if (-not $src) {
Write-Warning "WJ Shopfloor.lnk not found on public desktop - OpenText auto-start NOT configured."
Write-Warning " Searched: $($candidates -join ' ; ')"
Write-Warning " Setup-OpenText.ps1 should create it during preinstall - check OpenText install state."
return
}
if (-not (Test-Path -LiteralPath $startupDir)) {
New-Item -Path $startupDir -ItemType Directory -Force | Out-Null
}
$dst = Join-Path $startupDir 'WJ Shopfloor.lnk'
try {
Copy-Item -LiteralPath $src -Destination $dst -Force
Write-Host "OpenText auto-start enabled: $dst (source: $src)"
} catch {
Write-Warning "Failed to copy WJ Shopfloor.lnk to startup: $_"
}

View File

@@ -36,7 +36,7 @@ $siteConfig = Get-SiteConfig
$siteName = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' } $siteName = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' }
$siteNameCompact = if ($siteConfig) { $siteConfig.siteNameCompact } else { 'WestJefferson' } $siteNameCompact = if ($siteConfig) { $siteConfig.siteNameCompact } else { 'WestJefferson' }
$edncDir = "C:\Enrollment\shopfloor-setup\Standard\eDNC" $edncDir = Join-Path $PSScriptRoot 'eDNC'
if (-not (Test-Path $edncDir)) { if (-not (Test-Path $edncDir)) {
Write-Warning "eDNC folder not found at $edncDir - skipping." Write-Warning "eDNC folder not found at $edncDir - skipping."

View File

@@ -54,7 +54,11 @@ if (-not $machineNum -or $machineNum -eq '9999') {
Write-Host "Machine number: $machineNum" Write-Host "Machine number: $machineNum"
# ---- Locate local backup root (staged from PXE during imaging) ---- # ---- Locate local backup root (staged from PXE during imaging) ----
$backupRoot = 'C:\Enrollment\shopfloor-setup\Standard\ntlars-backups' # Lives at C:\Enrollment\shopfloor-setup\_ntlars-backups (one shared dir
# at the root of the staged shopfloor-setup tree, populated by Ansible
# from playbook/shopfloor-setup/_ntlars-backups). Path is relative to
# this script so it follows wherever per-pc-type dir is staged.
$backupRoot = Join-Path $PSScriptRoot '..\_ntlars-backups'
if (-not (Test-Path $backupRoot)) { if (-not (Test-Path $backupRoot)) {
Write-Host "ntlars-backups folder not staged at $backupRoot - skipping." Write-Host "ntlars-backups folder not staged at $backupRoot - skipping."
try { Stop-Transcript | Out-Null } catch {} try { Stop-Transcript | Out-Null } catch {}

View File

@@ -6,7 +6,7 @@ REM 1. %~dp0Set-MachineNumber.ps1
REM - .bat and .ps1 side-by-side (normal desktop-copied case, repo layout) REM - .bat and .ps1 side-by-side (normal desktop-copied case, repo layout)
REM 2. C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 REM 2. C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1
REM - dispatcher-copied location, if this .bat lives somewhere else REM - dispatcher-copied location, if this .bat lives somewhere else
REM 3. C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 REM 3. C:\Enrollment\shopfloor-setup\gea-shopfloor-collections\Set-MachineNumber.ps1
REM - canonical enrollment staging copy REM - canonical enrollment staging copy
REM REM
REM Goto-based dispatch - no nested if blocks, no literal parens in echo lines. REM Goto-based dispatch - no nested if blocks, no literal parens in echo lines.
@@ -21,13 +21,13 @@ if exist "%PS1%" goto :run
set "PS1=C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1" set "PS1=C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1"
if exist "%PS1%" goto :run if exist "%PS1%" goto :run
set "PS1=C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1" set "PS1=C:\Enrollment\shopfloor-setup\gea-shopfloor-collections\Set-MachineNumber.ps1"
if exist "%PS1%" goto :run if exist "%PS1%" goto :run
echo ERROR: Set-MachineNumber.ps1 not found in any of: echo ERROR: Set-MachineNumber.ps1 not found in any of:
echo %~dp0Set-MachineNumber.ps1 echo %~dp0Set-MachineNumber.ps1
echo C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 echo C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1
echo C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 echo C:\Enrollment\shopfloor-setup\gea-shopfloor-collections\Set-MachineNumber.ps1
echo. echo.
pause pause
exit /b 1 exit /b 1

View File

@@ -1,12 +0,0 @@
# 09-Setup-Lab.ps1 - Lab-specific setup (runs after Shopfloor baseline)
#
# PLACEHOLDER: add type-specific app installs when details are finalized.
# This script will be called by Run-ShopfloorSetup.ps1 as part of the
# type-specific phase, after all baseline scripts have completed.
#
# For share-based installs, copy the pattern from CMM/09-Setup-CMM.ps1
# (credential lookup + share mount + install from share).
Write-Host "=== Lab Setup ==="
Write-Host " (no type-specific apps configured yet)"
Write-Host "=== Lab Setup Complete ==="

View File

@@ -36,7 +36,7 @@ $siteConfig = Get-SiteConfig
$siteName = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' } $siteName = if ($siteConfig) { $siteConfig.siteName } else { 'West Jefferson' }
$siteNameCompact = if ($siteConfig) { $siteConfig.siteNameCompact } else { 'WestJefferson' } $siteNameCompact = if ($siteConfig) { $siteConfig.siteNameCompact } else { 'WestJefferson' }
$edncDir = "C:\Enrollment\shopfloor-setup\Standard\eDNC" $edncDir = Join-Path $PSScriptRoot 'eDNC'
if (-not (Test-Path $edncDir)) { if (-not (Test-Path $edncDir)) {
Write-Warning "eDNC folder not found at $edncDir - skipping." Write-Warning "eDNC folder not found at $edncDir - skipping."

View File

@@ -54,7 +54,8 @@ if (-not $machineNum -or $machineNum -eq '9999') {
Write-Host "Machine number: $machineNum" Write-Host "Machine number: $machineNum"
# ---- Locate local backup root (staged from PXE during imaging) ---- # ---- Locate local backup root (staged from PXE during imaging) ----
$backupRoot = 'C:\Enrollment\shopfloor-setup\Standard\ntlars-backups' # Lives at C:\Enrollment\shopfloor-setup\_ntlars-backups (shared root dir).
$backupRoot = Join-Path $PSScriptRoot '..\_ntlars-backups'
if (-not (Test-Path $backupRoot)) { if (-not (Test-Path $backupRoot)) {
Write-Host "ntlars-backups folder not staged at $backupRoot - skipping." Write-Host "ntlars-backups folder not staged at $backupRoot - skipping."
try { Stop-Transcript | Out-Null } catch {} try { Stop-Transcript | Out-Null } catch {}

View File

@@ -6,7 +6,7 @@ REM 1. %~dp0Set-MachineNumber.ps1
REM - .bat and .ps1 side-by-side (normal desktop-copied case, repo layout) REM - .bat and .ps1 side-by-side (normal desktop-copied case, repo layout)
REM 2. C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 REM 2. C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1
REM - dispatcher-copied location, if this .bat lives somewhere else REM - dispatcher-copied location, if this .bat lives somewhere else
REM 3. C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 REM 3. C:\Enrollment\shopfloor-setup\gea-shopfloor-nocollections\Set-MachineNumber.ps1
REM - canonical enrollment staging copy REM - canonical enrollment staging copy
REM REM
REM Goto-based dispatch - no nested if blocks, no literal parens in echo lines. REM Goto-based dispatch - no nested if blocks, no literal parens in echo lines.
@@ -21,13 +21,13 @@ if exist "%PS1%" goto :run
set "PS1=C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1" set "PS1=C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1"
if exist "%PS1%" goto :run if exist "%PS1%" goto :run
set "PS1=C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1" set "PS1=C:\Enrollment\shopfloor-setup\gea-shopfloor-nocollections\Set-MachineNumber.ps1"
if exist "%PS1%" goto :run if exist "%PS1%" goto :run
echo ERROR: Set-MachineNumber.ps1 not found in any of: echo ERROR: Set-MachineNumber.ps1 not found in any of:
echo %~dp0Set-MachineNumber.ps1 echo %~dp0Set-MachineNumber.ps1
echo C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 echo C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1
echo C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 echo C:\Enrollment\shopfloor-setup\gea-shopfloor-nocollections\Set-MachineNumber.ps1
echo. echo.
pause pause
exit /b 1 exit /b 1

View File

@@ -98,6 +98,30 @@
] ]
}, },
"gea-shopfloor-nocollections": {
"_comment": "Shopfloor PC running eDNC + NTLARS + Defect Tracker but no UDC (no Collections). Same as Standard-Machine minus UDC pin/desktop/startup. Direct profile lookup (line 92 in Get-PCProfile.ps1) finds this BEFORE the alias-group fallback to Standard-Machine, so Standard-Machine's UDC entries do not leak through.",
"machineappsSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\machineapps",
"ntlarsBackupSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\main\\ntlars-backups",
"startupItems": [
{ "label": "WJ Shopfloor", "type": "existing", "sourceLnk": "WJ Shopfloor.lnk" },
{ "label": "Plant Apps", "type": "url", "urlKey": "plantApps" },
{ "label": "eDNC", "type": "exe", "target": "C:\\Program Files (x86)\\Dnc\\bin\\DncMain.exe" }
],
"taskbarPins": [
{ "name": "Microsoft Edge", "lnkPath": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Microsoft Edge.lnk" },
{ "name": "WJ Shopfloor", "lnkPath": "%PUBLIC%\\Desktop\\Shopfloor Tools\\WJ Shopfloor.lnk" },
{ "name": "eDNC", "lnkPath": "%PUBLIC%\\Desktop\\Shopfloor Tools\\eDNC.lnk" },
{ "name": "NTLARS", "lnkPath": "%PUBLIC%\\Desktop\\Shopfloor Tools\\NTLARS.lnk" },
{ "name": "Defect_Tracker", "lnkPath": "%PUBLIC%\\Desktop\\Shopfloor Tools\\Defect_Tracker.lnk" }
],
"desktopApps": [
{ "name": "eDNC", "kind": "exe", "exePath": "C:\\Program Files (x86)\\Dnc\\bin\\DncMain.exe" },
{ "name": "NTLARS", "kind": "exe", "exePath": "C:\\Program Files (x86)\\Dnc\\Common\\NTLARS.exe" },
{ "name": "WJ Shopfloor", "kind": "existing", "sourceName": "WJ Shopfloor.lnk" },
{ "name": "Defect_Tracker", "kind": "exe", "exePath": "C:\\Program Files (x86)\\WJF_Defect_Tracker\\Defect_Tracker.exe" }
]
},
"CMM": { "CMM": {
"_comment": "Hexagon CMM apps (CLM 1.8, goCMM, PC-DMIS 2016, PC-DMIS 2019 R2). At imaging time they install from a WinPE-staged local bootstrap at C:\\CMM-Install (put there by startnet.cmd when pc-type=CMM, source is the PXE server enrollment share). Post-imaging, the unified GE-Enforce dispatcher reads cmm/manifest.json on the tsgwp00525 share below and enforces versions on every user logon (the SFLD creds Azure DSC provisions unlock the mount). cmmSharePath is the ongoing-enforcement source, not the imaging-time source.", "_comment": "Hexagon CMM apps (CLM 1.8, goCMM, PC-DMIS 2016, PC-DMIS 2019 R2). At imaging time they install from a WinPE-staged local bootstrap at C:\\CMM-Install (put there by startnet.cmd when pc-type=CMM, source is the PXE server enrollment share). Post-imaging, the unified GE-Enforce dispatcher reads cmm/manifest.json on the tsgwp00525 share below and enforces versions on every user logon (the SFLD creds Azure DSC provisions unlock the mount). cmmSharePath is the ongoing-enforcement source, not the imaging-time source.",
"cmmSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\cmm\\machineapps", "cmmSharePath": "\\\\tsgwp00525.wjs.geaerospace.net\\shared\\dt\\shopfloor\\cmm\\machineapps",

View File

@@ -6,7 +6,7 @@ powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
REM --- Wait for network (DHCP may take a moment after wpeinit) --- REM --- Wait for network (DHCP may take a moment after wpeinit) ---
echo Waiting for network... echo Waiting for network...
:wait_net :wait_net
ping -n 2 10.9.100.1 >NUL 2>&1 ping -n 2 172.16.9.1 >NUL 2>&1
if errorlevel 1 goto wait_net if errorlevel 1 goto wait_net
echo Network ready. echo Network ready.
@@ -17,7 +17,7 @@ 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 this script reliably. Use goto-flow instead so the CALL runs at the
REM top scope and BIOS_STATUS persists. REM top scope and BIOS_STATUS persists.
set BIOS_STATUS=No BIOS check (share unavailable) set BIOS_STATUS=No BIOS check (share unavailable)
net use B: \\10.9.100.1\winpeapps_bios /user:pxe-upload pxe /persistent:no 2>NUL 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 if not exist B:\check-bios.cmd goto :bios_check_done
echo. echo.
echo Checking for BIOS updates... echo Checking for BIOS updates...
@@ -167,7 +167,7 @@ set NEED_ENROLL=0
if not "%PPKG%"=="" set NEED_ENROLL=1 if not "%PPKG%"=="" set NEED_ENROLL=1
if not "%PCTYPE%"=="" set NEED_ENROLL=1 if not "%PCTYPE%"=="" set NEED_ENROLL=1
if "%NEED_ENROLL%"=="0" goto enroll_staged if "%NEED_ENROLL%"=="0" goto enroll_staged
net use Y: \\10.9.100.1\enrollment /user:pxe-upload pxe /persistent:no net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no
if "%PPKG%"=="" goto enroll_staged if "%PPKG%"=="" goto enroll_staged
if not exist "Y:\ppkgs\%SOURCE_PPKG%" ( if not exist "Y:\ppkgs\%SOURCE_PPKG%" (
echo WARNING: %SOURCE_PPKG% not found on server. Enrollment will be skipped. echo WARNING: %SOURCE_PPKG% not found on server. Enrollment will be skipped.
@@ -192,7 +192,7 @@ echo.
echo Starting GEA Standard setup... echo Starting GEA Standard setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-standard /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-standard /user:pxe-upload pxe /persistent:no
goto end goto end
:gea-engineer :gea-engineer
@@ -200,7 +200,7 @@ echo.
echo Starting GEA Engineer setup... echo Starting GEA Engineer setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-engineer /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-engineer /user:pxe-upload pxe /persistent:no
goto end goto end
:gea-shopfloor :gea-shopfloor
@@ -208,7 +208,7 @@ echo.
echo Starting GEA Shopfloor setup... echo Starting GEA Shopfloor setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-shopfloor /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-standard :ge-standard
@@ -216,7 +216,7 @@ echo.
echo Starting GE Standard setup... echo Starting GE Standard setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-standard /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-standard /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-engineer :ge-engineer
@@ -224,7 +224,7 @@ echo.
echo Starting GE Engineer setup... echo Starting GE Engineer setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-engineer /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-engineer /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-shopfloor-lockdown :ge-shopfloor-lockdown
@@ -232,7 +232,7 @@ echo.
echo Starting GE Shopfloor Lockdown setup... echo Starting GE Shopfloor Lockdown setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-lockdown /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-shopfloor-lockdown /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-shopfloor-mce :ge-shopfloor-mce
@@ -240,7 +240,7 @@ echo.
echo Starting GE Shopfloor MCE setup... echo Starting GE Shopfloor MCE setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-mce /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-shopfloor-mce /user:pxe-upload pxe /persistent:no
goto end goto end
:end :end

View File

@@ -3,7 +3,7 @@
# #
# Copies the Hexagon installers + cmm-manifest.json from the local workstation # Copies the Hexagon installers + cmm-manifest.json from the local workstation
# to /srv/samba/enrollment/cmm-installers on the PXE server. That directory # to /srv/samba/enrollment/cmm-installers on the PXE server. That directory
# becomes visible as \\10.9.100.1\enrollment\cmm-installers so startnet.cmd # becomes visible as \\172.16.9.1\enrollment\cmm-installers so startnet.cmd
# can xcopy it onto the target disk during WinPE phase. # can xcopy it onto the target disk during WinPE phase.
# #
# Run this on the workstation (not on the PXE server) any time: # Run this on the workstation (not on the PXE server) any time:
@@ -24,7 +24,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PXE_HOST="${PXE_HOST:-10.9.100.1}" PXE_HOST="${PXE_HOST:-172.16.9.1}"
PXE_USER="${PXE_USER:-pxe}" PXE_USER="${PXE_USER:-pxe}"
PXE_PASS="${PXE_PASS:-pxe}" PXE_PASS="${PXE_PASS:-pxe}"

View File

@@ -19,7 +19,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
PXE_HOST="${PXE_HOST:-10.9.100.1}" PXE_HOST="${PXE_HOST:-172.16.9.1}"
PXE_USER="${PXE_USER:-pxe}" PXE_USER="${PXE_USER:-pxe}"
PXE_PASS="${PXE_PASS:-pxe}" PXE_PASS="${PXE_PASS:-pxe}"

View File

@@ -10,7 +10,7 @@
# X:\Windows\Temp\winpe-status-push.log; failures are swallowed. # X:\Windows\Temp\winpe-status-push.log; failures are swallowed.
param( param(
[string]$PxeServer = '10.9.100.1', [string]$PxeServer = '172.16.9.1',
[int]$Port = 9009, [int]$Port = 9009,
[int]$TimeoutSec = 5, [int]$TimeoutSec = 5,
[string]$PCType = $env:PCTYPE [string]$PCType = $env:PCTYPE

View File

@@ -8,15 +8,15 @@
# .\Upload-Image.ps1 (selected OS + packages, no drivers) # .\Upload-Image.ps1 (selected OS + packages, no drivers)
# .\Upload-Image.ps1 -IncludeDrivers (also upload selected hardware drivers) # .\Upload-Image.ps1 -IncludeDrivers (also upload selected hardware drivers)
# .\Upload-Image.ps1 -CachePath "D:\MCL\Cache" (custom cache location) # .\Upload-Image.ps1 -CachePath "D:\MCL\Cache" (custom cache location)
# .\Upload-Image.ps1 -Server 10.9.100.1 (custom server IP) # .\Upload-Image.ps1 -Server 172.16.9.1 (custom server IP)
# #
# After upload, use the PXE webapp (http://10.9.100.1:9009) to import # After upload, use the PXE webapp (http://172.16.9.1:9009) to import
# the uploaded content into the desired image type. # the uploaded content into the desired image type.
# #
param( param(
[string]$CachePath = "C:\ProgramData\GEAerospace\MediaCreator\Cache", [string]$CachePath = "C:\ProgramData\GEAerospace\MediaCreator\Cache",
[string]$Server = "10.9.100.1", [string]$Server = "172.16.9.1",
[string]$User = "pxe-upload", [string]$User = "pxe-upload",
[string]$Pass = "pxe", [string]$Pass = "pxe",
[switch]$IncludeDrivers [switch]$IncludeDrivers

View File

@@ -350,8 +350,8 @@ echo " - Network: Bridge connected to isolated PXE network"
echo " 3. Attach ISO as CD-ROM and start the VM" echo " 3. Attach ISO as CD-ROM and start the VM"
echo " 4. Ubuntu auto-installs (~10-15 minutes, zero interaction)" echo " 4. Ubuntu auto-installs (~10-15 minutes, zero interaction)"
echo " 5. After reboot, first-boot configures all PXE services" echo " 5. After reboot, first-boot configures all PXE services"
echo " 6. Access webapp at http://10.9.100.1:9009" echo " 6. Access webapp at http://172.16.9.1:9009"
echo "" echo ""
echo "NOTE: The VM's network bridge must be connected to your isolated PXE" echo "NOTE: The VM's network bridge must be connected to your isolated PXE"
echo " network. The server will use static IP 10.9.100.1/24." echo " network. The server will use static IP 172.16.9.1/24."
echo "" echo ""

View File

@@ -3,12 +3,12 @@
# Copies Flash64W.exe, BIOS binaries, models.txt, and check-bios.cmd # Copies Flash64W.exe, BIOS binaries, models.txt, and check-bios.cmd
# #
# Usage: ./deploy-bios.sh [server-ip] # Usage: ./deploy-bios.sh [server-ip]
# Default server: 10.9.100.1 # Default server: 172.16.9.1
set -e set -e
REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)"
PXE_SERVER="${1:-10.9.100.1}" PXE_SERVER="${1:-172.16.9.1}"
PXE_USER="pxe" PXE_USER="pxe"
PXE_PASS="pxe" PXE_PASS="pxe"
REMOTE_DIR="/srv/samba/enrollment/BIOS" REMOTE_DIR="/srv/samba/enrollment/BIOS"

View File

@@ -23,7 +23,7 @@
Output lands under C:\ProgramData\state-<stage>-<timestamp>\. Output lands under C:\ProgramData\state-<stage>-<timestamp>\.
Copy the whole folder back to the workstation Copy the whole folder back to the workstation
(\\10.9.100.1\image-upload or dump to pxe-images manually) and diff. (\\172.16.9.1\image-upload or dump to pxe-images manually) and diff.
Diffing tips: Diffing tips:
pre-category -> post-category : what the category-driven Intune pre-category -> post-category : what the category-driven Intune
@@ -175,6 +175,189 @@ Step "dsregcmd /status" {
dsregcmd /status 2>&1 | Out-File "$out\dsregcmd.txt" dsregcmd /status 2>&1 | Out-File "$out\dsregcmd.txt"
} }
# --- Intune readiness probe (4-layer gate, frozen at this snapshot moment).
# Aligned with Microsoft's documented IME / ESP gates - see:
# https://learn.microsoft.com/en-us/windows/client-management/mdm-diagnose-enrollment
# https://patchmypc.com/blog/intune-management-extension-esp-phases/
#
# Layer 1 - AAD + MDM enrollment object exists
# AzureAdJoined, IntuneEnrolled (HKLM\Enrollments\<GUID> EnrollmentState=1)
# Layer 2 - Microsoft's own success markers
# MdmEnrollEvent75Found ("Auto MDM Enroll: Succeeded" in DeviceManagement-Enterprise-Diagnostics-Provider/Admin)
# No MdmEnrollEvent76Found (failure) within last 7d
# HasProvisioningCompleted=1 in OMADM\Accounts\<id>
# FirstSync IsSyncDone=1 in Enrollments\<id>\FirstSync
# Layer 3 - Policy actually delivered (CSP succeeded, not just provider registered)
# PolicyManager\Providers\<EnrollmentId>\default\Device exists
# PolicyManager\current\device has subkeys
# Layer 4 - IME running (Win32App / PowerShell channel ready)
# IME service running, IME log dir populated
# Sidecar Policy Provider InstallationState=Completed (best-effort log grep)
#
# Pre-category snapshots that show layers 2-3 red = category assigned too
# early -> reboot races the policy/payload pull -> stalled deploy -> re-image.
Step "intune category-readiness probe" {
$r = [ordered]@{
# Layer 1
AzureAdJoined = $false
AzureAdTenant = $null
DeviceId = $null
IntuneEnrolled = $false
EnrollmentId = $null
# Layer 2
MdmEnrollEvent75Found = $false
MdmEnrollEvent75Time = $null
MdmEnrollEvent76Found = $false
MdmEnrollEvent76Time = $null
HasProvisioningCompleted = $false
FirstSyncIsSyncDone = $false
# Layer 3
PolicyManagerProviderForEnrollment = $false
PolicyManagerCurrentDeviceSubkeys = 0
# Layer 4
ImeServiceRunning = $false
ImeLogDirPopulated = $false
SidecarInstallationState = $null # 'Completed' | 'InProgress' | $null
# Misc
LastSyncEventTime = $null
Stage = $Stage
SnapshotTime = $null
}
# Layer 1: AAD + tenant + DeviceId
try {
$ds = & dsregcmd /status 2>&1 | Out-String
if ($ds -match 'AzureAdJoined\s*:\s*YES') { $r.AzureAdJoined = $true }
if ($ds -match 'TenantId\s*:\s*(\S+)') { $r.AzureAdTenant = $matches[1] }
if ($ds -match '(?m)^\s*DeviceId\s*:\s*(\S+)') { $r.DeviceId = $matches[1] }
} catch {}
# Layer 1: enrollment record
try {
$eks = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Enrollments' -ErrorAction SilentlyContinue |
Where-Object {
$p = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
$p -and $p.EnrollmentState -eq 1 -and $_.PSChildName -ne 'Context'
}
if ($eks) {
$r.IntuneEnrolled = $true
$r.EnrollmentId = $eks[0].PSChildName
}
} catch {}
# Layer 2: event 75 (success) + 76 (failure) in last 7d
try {
$cutoff = (Get-Date).AddDays(-7)
$evts = Get-WinEvent -LogName 'Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin' -MaxEvents 1000 -ErrorAction SilentlyContinue |
Where-Object { $_.TimeCreated -gt $cutoff }
$e75 = $evts | Where-Object { $_.Id -eq 75 } | Sort-Object TimeCreated -Descending | Select-Object -First 1
$e76 = $evts | Where-Object { $_.Id -eq 76 } | Sort-Object TimeCreated -Descending | Select-Object -First 1
if ($e75) { $r.MdmEnrollEvent75Found = $true; $r.MdmEnrollEvent75Time = $e75.TimeCreated.ToString('o') }
if ($e76) { $r.MdmEnrollEvent76Found = $true; $r.MdmEnrollEvent76Time = $e76.TimeCreated.ToString('o') }
$latest = $evts | Sort-Object TimeCreated -Descending | Select-Object -First 1
if ($latest) { $r.LastSyncEventTime = $latest.TimeCreated.ToString('o') }
} catch {}
# Layer 2 + 3: per-enrollment regs (only meaningful with an EnrollmentId)
if ($r.EnrollmentId) {
try {
$omadm = "HKLM:\SOFTWARE\Microsoft\Provisioning\OMADM\Accounts\$($r.EnrollmentId)"
if (Test-Path $omadm) {
$v = (Get-ItemProperty -Path $omadm -Name HasProvisioningCompleted -ErrorAction SilentlyContinue).HasProvisioningCompleted
if ($v -eq 1) { $r.HasProvisioningCompleted = $true }
}
} catch {}
try {
$fs = "HKLM:\SOFTWARE\Microsoft\Enrollments\$($r.EnrollmentId)\FirstSync"
if (Test-Path $fs) {
$v = (Get-ItemProperty -Path $fs -Name IsSyncDone -ErrorAction SilentlyContinue).IsSyncDone
if ($v -eq 1) { $r.FirstSyncIsSyncDone = $true }
}
} catch {}
try {
$pp = "HKLM:\SOFTWARE\Microsoft\PolicyManager\Providers\$($r.EnrollmentId)\default\Device"
if (Test-Path $pp) { $r.PolicyManagerProviderForEnrollment = $true }
} catch {}
}
# Layer 3: actual policy mirror under current\device
try {
$cur = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device' -ErrorAction SilentlyContinue
$r.PolicyManagerCurrentDeviceSubkeys = if ($cur) { $cur.Count } else { 0 }
} catch {}
# Layer 4: IME service + logs
try {
$svc = Get-Service -Name 'IntuneManagementExtension' -ErrorAction SilentlyContinue
if ($svc -and $svc.Status -eq 'Running') { $r.ImeServiceRunning = $true }
$imeLog = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs'
if ((Test-Path $imeLog) -and (Get-ChildItem $imeLog -ErrorAction SilentlyContinue | Select-Object -First 1)) {
$r.ImeLogDirPopulated = $true
}
} catch {}
# Layer 4: Sidecar Policy Provider InstallationState (best-effort grep of IME log)
try {
$imeLogFile = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log'
if (Test-Path $imeLogFile) {
$hits = Select-String -Path $imeLogFile -Pattern 'Sidecar.*?[Ii]nstallation\s*[Ss]tate.*?(Completed|InProgress|NotStarted|Failed)' -ErrorAction SilentlyContinue |
Select-Object -Last 1
if ($hits -and $hits.Matches.Count -gt 0 -and $hits.Matches[0].Groups.Count -ge 2) {
$r.SidecarInstallationState = $hits.Matches[0].Groups[1].Value
}
}
} catch {}
# Verdict
$r.SnapshotTime = (Get-Date).ToString('o')
$r.ReadyForCategoryAssignment = (
$r.AzureAdJoined -and
$r.IntuneEnrolled -and
$r.MdmEnrollEvent75Found -and
(-not $r.MdmEnrollEvent76Found) -and
$r.HasProvisioningCompleted -and
$r.FirstSyncIsSyncDone -and
$r.PolicyManagerProviderForEnrollment -and
($r.PolicyManagerCurrentDeviceSubkeys -gt 0) -and
$r.ImeServiceRunning -and
$r.ImeLogDirPopulated
)
[pscustomobject]$r | ConvertTo-Json -Depth 3 | Out-File "$out\intune-readiness.json"
# Human-readable summary (PS 5.1-safe: $(if ...) subexpressions, no inline-if-as-expression)
@(
"Intune category-readiness probe @ $($r.SnapshotTime) (stage=$Stage)"
'------------------------------------------------------------------'
' Layer 1 - AAD + MDM enrollment object'
(" [{0}] AzureAdJoined tenant={1}" -f $(if ($r.AzureAdJoined) {'OK '} else {'FAIL'}), $r.AzureAdTenant)
(" [{0}] IntuneEnrolled id={1}" -f $(if ($r.IntuneEnrolled) {'OK '} else {'FAIL'}), $r.EnrollmentId)
' Layer 2 - Microsoft success markers'
(" [{0}] Event 75 (Auto MDM Enroll: Succeeded) time={1}" -f $(if ($r.MdmEnrollEvent75Found) {'OK '} else {'FAIL'}), $r.MdmEnrollEvent75Time)
(" [{0}] No event 76 in last 7d (no enroll failure) lastFail={1}" -f $(if ($r.MdmEnrollEvent76Found) {'FAIL'} else {'OK '}), $r.MdmEnrollEvent76Time)
(" [{0}] OMADM HasProvisioningCompleted=1" -f $(if ($r.HasProvisioningCompleted) {'OK '} else {'FAIL'}))
(" [{0}] Enrollments\<id>\FirstSync IsSyncDone=1" -f $(if ($r.FirstSyncIsSyncDone) {'OK '} else {'FAIL'}))
' Layer 3 - policy actually delivered'
(" [{0}] PolicyManager\Providers\<EnrollmentId>\default\Device exists" -f $(if ($r.PolicyManagerProviderForEnrollment) {'OK '} else {'FAIL'}))
(" [{0}] PolicyManager\current\device subkey count={1}" -f $(if ($r.PolicyManagerCurrentDeviceSubkeys -gt 0) {'OK '} else {'FAIL'}), $r.PolicyManagerCurrentDeviceSubkeys)
' Layer 4 - IME running (Win32App / PS1 channel)'
(" [{0}] IME service running" -f $(if ($r.ImeServiceRunning) {'OK '} else {'FAIL'}))
(" [{0}] IME log dir populated" -f $(if ($r.ImeLogDirPopulated) {'OK '} else {'FAIL'}))
(" [--] Sidecar InstallationState (best-effort) value={0}" -f $(if ($r.SidecarInstallationState) { $r.SidecarInstallationState } else { '(unknown)' }))
'------------------------------------------------------------------'
("ReadyForCategoryAssignment: $($r.ReadyForCategoryAssignment)")
("DeviceId: $($r.DeviceId)")
("LastMDMEvent: $($r.LastSyncEventTime)")
''
'Notes:'
' - All layers must be green before assigning device category in Intune.'
' - Even when ready locally, allow >=15 min after adding the user/device'
' to the target Entra group (server-side group eval delay, MS docs).'
' - Pre-category snapshot with red Layer 2/3 = root cause for "imaging'
' stalled and required re-image" (category raced the policy pull).'
) | Out-File "$out\intune-readiness.txt"
}
# --- SFLD credential manager YAML (identifies the Intune category) # --- SFLD credential manager YAML (identifies the Intune category)
Step "snapshot SFLD CredentialManager dir" { Step "snapshot SFLD CredentialManager dir" {
$cmDir = 'C:\ProgramData\SFLD\CredentialManager' $cmDir = 'C:\ProgramData\SFLD\CredentialManager'
@@ -291,6 +474,6 @@ Write-Host ""
Write-Host "Snapshot complete: $out" Write-Host "Snapshot complete: $out"
Write-Host "" Write-Host ""
Write-Host "Next: copy that folder to the workstation. Easiest:" Write-Host "Next: copy that folder to the workstation. Easiest:"
Write-Host " net use Z: \\10.9.100.1\image-upload /user:pxe-upload pxe /persistent:no" Write-Host " net use Z: \\172.16.9.1\image-upload /user:pxe-upload pxe /persistent:no"
Write-Host " robocopy `"$out`" `"Z:\state-$Stage-$stamp`" /E /NFL /NDL /NJH /NJS" Write-Host " robocopy `"$out`" `"Z:\state-$Stage-$stamp`" /E /NFL /NDL /NJH /NJS"
Write-Host " net use Z: /delete /y" Write-Host " net use Z: /delete /y"

View File

@@ -0,0 +1,142 @@
#Requires -RunAsAdministrator
<#
.SYNOPSIS
Diff two snapshot dirs from Capture-LockdownState.ps1 to surface
deltas (what arrived between the two checkpoints).
.DESCRIPTION
Compares two state-* dirs (typically pre-category vs post-category,
or post-category vs post-lockdown) along these axes:
- intune-readiness.json (5 readiness signals, did they flip?)
- dsregcmd.txt (AAD join state diff)
- Reg dumps (.reg files): line-level diff
- File inventories (*.csv): rows added/removed
- Event log CSVs (DeviceManagement-Events, Tasks-RunHistory):
new rows count
Output: human-readable summary to console + a delta-<stamp>.txt
next to the second snapshot dir.
.PARAMETER Before
Path to the earlier snapshot dir (e.g. state-pre-category-...)
.PARAMETER After
Path to the later snapshot dir (e.g. state-post-category-...)
.EXAMPLE
.\Compare-LockdownStates.ps1 -Before C:\ProgramData\state-pre-category-20260504-180000 -After C:\ProgramData\state-post-category-20260504-181500
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)] [string]$Before,
[Parameter(Mandatory=$true)] [string]$After
)
$ErrorActionPreference = 'Continue'
if (-not (Test-Path $Before)) { throw "Before dir not found: $Before" }
if (-not (Test-Path $After)) { throw "After dir not found: $After" }
$out = Join-Path $After ("delta-vs-" + (Split-Path $Before -Leaf) + ".txt")
$lines = New-Object System.Collections.Generic.List[string]
function Add-Line { param([string]$s) $lines.Add($s); Write-Host $s }
Add-Line "=========================================================="
Add-Line "Snapshot delta"
Add-Line " Before: $Before"
Add-Line " After: $After"
Add-Line "=========================================================="
Add-Line ""
# --- 1. Intune readiness signals delta ---
Add-Line '--- Intune readiness signals (5-row gate) ---'
$rb = $null; $ra = $null
$rbPath = Join-Path $Before 'intune-readiness.json'
$raPath = Join-Path $After 'intune-readiness.json'
if ((Test-Path $rbPath) -and (Test-Path $raPath)) {
$rb = Get-Content $rbPath -Raw | ConvertFrom-Json
$ra = Get-Content $raPath -Raw | ConvertFrom-Json
$signals = 'AzureAdJoined','IntuneEnrolled','MdmSyncRecent','ImeServiceRunning','ImeLogDirPopulated'
foreach ($s in $signals) {
$b = $rb.$s; $a = $ra.$s
$tag = if ($b -eq $a) { ' same' } elseif ($a) { '+ flipped TRUE' } else { '- regressed FALSE' }
Add-Line (" {0,-25} before={1,-5} after={2,-5} {3}" -f $s, $b, $a, $tag)
}
Add-Line (" {0,-25} before={1,-5} after={2,-5}" -f 'PolicyMgr providers', $rb.PolicyManagerProviders, $ra.PolicyManagerProviders)
Add-Line (" ReadyForCategoryAssignment: before={0} after={1}" -f $rb.ReadyForCategoryAssignment, $ra.ReadyForCategoryAssignment)
} else {
Add-Line " (intune-readiness.json missing in one or both snapshots)"
}
Add-Line ''
# --- 2. dsregcmd diff (key boolean lines) ---
Add-Line '--- dsregcmd state diff ---'
$dsBefore = Get-Content (Join-Path $Before 'dsregcmd.txt') -ErrorAction SilentlyContinue
$dsAfter = Get-Content (Join-Path $After 'dsregcmd.txt') -ErrorAction SilentlyContinue
foreach ($key in 'AzureAdJoined','EnterpriseJoined','DomainJoined','TenantId','TenantName','MdmUrl','MdmTouUrl','MdmComplianceUrl','SettingsUrl') {
$b = ($dsBefore | Select-String -Pattern "^\s*$key\s*:" -SimpleMatch).Line | Select-Object -First 1
$a = ($dsAfter | Select-String -Pattern "^\s*$key\s*:" -SimpleMatch).Line | Select-Object -First 1
if ($b -ne $a) {
Add-Line " CHANGED $key"
Add-Line " before: $(if ($b) { $b } else { '(missing)' })"
Add-Line " after : $(if ($a) { $a } else { '(missing)' })"
}
}
Add-Line ''
# --- 3. Registry .reg files - line-level diff (count lines added/removed) ---
Add-Line '--- registry dump deltas (line counts) ---'
$regsBefore = Get-ChildItem $Before -Filter '*.reg' -ErrorAction SilentlyContinue
foreach ($rb in $regsBefore) {
$rbName = $rb.Name
$raPath = Join-Path $After $rbName
if (-not (Test-Path $raPath)) { continue }
$bLines = Get-Content $rb.FullName -ErrorAction SilentlyContinue
$aLines = Get-Content $raPath -ErrorAction SilentlyContinue
$added = (Compare-Object -ReferenceObject $bLines -DifferenceObject $aLines | Where-Object SideIndicator -eq '=>').Count
$removed = (Compare-Object -ReferenceObject $bLines -DifferenceObject $aLines | Where-Object SideIndicator -eq '<=').Count
if ($added -or $removed) {
Add-Line (" {0,-30} +{1,-4} -{2}" -f $rbName, $added, $removed)
}
}
Add-Line ''
# --- 4. CSV inventories - row count delta ---
Add-Line '--- file inventory deltas (CSV row counts) ---'
$csvsBefore = Get-ChildItem $Before -Filter '*.csv' -ErrorAction SilentlyContinue
foreach ($cb in $csvsBefore) {
$cbName = $cb.Name
$caPath = Join-Path $After $cbName
if (-not (Test-Path $caPath)) { continue }
try {
$bRows = (Import-Csv $cb.FullName).Count
$aRows = (Import-Csv $caPath).Count
if ($bRows -ne $aRows) {
Add-Line (" {0,-35} before={1,-5} after={2,-5} delta={3:+#;-#;0}" -f $cbName, $bRows, $aRows, ($aRows - $bRows))
}
} catch {}
}
Add-Line ''
# --- 5. Newly-present + newly-absent files in the snapshot ---
Add-Line '--- snapshot directory contents delta ---'
$bf = Get-ChildItem $Before -File -Recurse | ForEach-Object { $_.FullName.Substring($Before.Length) }
$af = Get-ChildItem $After -File -Recurse | ForEach-Object { $_.FullName.Substring($After.Length) }
$onlyA = Compare-Object -ReferenceObject $bf -DifferenceObject $af -PassThru | Where-Object { $af -contains $_ -and $bf -notcontains $_ }
$onlyB = Compare-Object -ReferenceObject $bf -DifferenceObject $af -PassThru | Where-Object { $bf -contains $_ -and $af -notcontains $_ }
if ($onlyA) {
Add-Line " NEW in After:"
$onlyA | ForEach-Object { Add-Line " + $_" }
}
if ($onlyB) {
Add-Line " REMOVED in After:"
$onlyB | ForEach-Object { Add-Line " - $_" }
}
Add-Line ''
# Persist
$lines | Out-File $out -Encoding utf8
Write-Host ""
Write-Host "Delta written to: $out" -ForegroundColor Cyan

View File

@@ -13,7 +13,7 @@ enrollment, and is enrolled to Intune but no device category assigned yet.
0. Map share + stage script (run once, at the start) 0. Map share + stage script (run once, at the start)
---------------------------------------- ----------------------------------------
net use Z: \\10.9.100.1\image-upload /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\image-upload /user:pxe-upload pxe /persistent:no
Copy-Item Z:\Capture-LockdownState.ps1 C:\Windows\Temp\ Copy-Item Z:\Capture-LockdownState.ps1 C:\Windows\Temp\
Set-ExecutionPolicy -Scope Process Bypass -Force Set-ExecutionPolicy -Scope Process Bypass -Force
@@ -83,7 +83,7 @@ Output: C:\ProgramData\state-post-lockdown-<stamp>\
} }
net use Z: /delete /y net use Z: /delete /y
The three folders land at \\10.9.100.1\image-upload\state-*-<stamp>\. The three folders land at \\172.16.9.1\image-upload\state-*-<stamp>\.
On the workstation: pull from /home/pxe/image-upload/ on the PXE server On the workstation: pull from /home/pxe/image-upload/ on the PXE server
(scp or local mount) and diff against any prior baseline (e.g. the (scp or local mount) and diff against any prior baseline (e.g. the
4/15 v1.3.1 working snapshot at pxe-images/state-post-lockdown-20260415-154705/). 4/15 v1.3.1 working snapshot at pxe-images/state-post-lockdown-20260415-154705/).

View File

@@ -31,7 +31,7 @@ import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
REPO_DIR = Path(__file__).resolve().parent REPO_DIR = Path(__file__).resolve().parent
PXE_HOST = "10.9.100.1" PXE_HOST = "172.16.9.1"
PXE_USER = "pxe" PXE_USER = "pxe"
PXE_PASS = "pxe" PXE_PASS = "pxe"
UPLOAD_DEST = "/home/pxe/image-upload" UPLOAD_DEST = "/home/pxe/image-upload"
@@ -715,7 +715,7 @@ def main():
concurrent.futures.wait(futures) concurrent.futures.wait(futures)
# --- Download BIOS (goes to shared winpeapps dir, read by check-bios.cmd # --- Download BIOS (goes to shared winpeapps dir, read by check-bios.cmd
# in WinPE phase via \\10.9.100.1\winpeapps\_shared\BIOS\). Playbook # in WinPE phase via \\172.16.9.1\winpeapps\_shared\BIOS\). Playbook
# task at pxe_server_setup.yml:485 deploys check-bios.cmd + Flash64W.exe # task at pxe_server_setup.yml:485 deploys check-bios.cmd + Flash64W.exe
# to this same directory. --- # to this same directory. ---
bios_ok = bios_err = 0 bios_ok = bios_err = 0

View File

@@ -137,7 +137,7 @@ data = data.replace(
) )
data = data.replace( data = data.replace(
b'<hostname></hostname>', b'<hostname></hostname>',
b'<hostname>10.9.100.1</hostname>' b'<hostname>172.16.9.1</hostname>'
) )
data = data.replace( data = data.replace(
b'<path></path>', b'<path></path>',
@@ -190,7 +190,7 @@ PYEOF
# Repack CPIO with exact 512-byte block alignment (194560 bytes) # Repack CPIO with exact 512-byte block alignment (194560 bytes)
ls -1 "$CFGTMP" | (cd "$CFGTMP" && cpio -o -H newc 2>/dev/null) | \ ls -1 "$CFGTMP" | (cd "$CFGTMP" && cpio -o -H newc 2>/dev/null) | \
dd bs=512 conv=sync 2>/dev/null > "$OUT_DIR/blancco/config.img" dd bs=512 conv=sync 2>/dev/null > "$OUT_DIR/blancco/config.img"
echo " Reports: SMB blancco@10.9.100.1/blancco-reports, bootable report enabled" echo " Reports: SMB blancco@172.16.9.1/blancco-reports, bootable report enabled"
fi fi
cd "$REPO_ROOT" cd "$REPO_ROOT"
rm -rf "$CFGTMP" rm -rf "$CFGTMP"

View File

@@ -6,7 +6,7 @@ set -e
REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)"
DEST="$REPO_ROOT/bios-staging" DEST="$REPO_ROOT/bios-staging"
PXE_SERVER="10.9.100.1" PXE_SERVER="172.16.9.1"
PXE_USER="pxe" PXE_USER="pxe"
PXE_PASS="pxe" PXE_PASS="pxe"

View File

@@ -6,7 +6,7 @@ powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
REM --- Wait for network (DHCP may take a moment after wpeinit) --- REM --- Wait for network (DHCP may take a moment after wpeinit) ---
echo Waiting for network... echo Waiting for network...
:wait_net :wait_net
ping -n 2 10.9.100.1 >NUL 2>&1 ping -n 2 172.16.9.1 >NUL 2>&1
if errorlevel 1 goto wait_net if errorlevel 1 goto wait_net
echo Network ready. echo Network ready.
@@ -17,7 +17,7 @@ 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 this script reliably. Use goto-flow instead so the CALL runs at the
REM top scope and BIOS_STATUS persists. REM top scope and BIOS_STATUS persists.
set BIOS_STATUS=No BIOS check (share unavailable) set BIOS_STATUS=No BIOS check (share unavailable)
net use B: \\10.9.100.1\winpeapps_bios /user:pxe-upload pxe /persistent:no 2>NUL 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 if not exist B:\check-bios.cmd goto :bios_check_done
echo. echo.
echo Checking for BIOS updates... echo Checking for BIOS updates...
@@ -174,7 +174,7 @@ set NEED_ENROLL=0
if not "%PPKG%"=="" set NEED_ENROLL=1 if not "%PPKG%"=="" set NEED_ENROLL=1
if not "%PCTYPE%"=="" set NEED_ENROLL=1 if not "%PCTYPE%"=="" set NEED_ENROLL=1
if "%NEED_ENROLL%"=="0" goto enroll_staged if "%NEED_ENROLL%"=="0" goto enroll_staged
net use Y: \\10.9.100.1\enrollment /user:pxe-upload pxe /persistent:no net use Y: \\172.16.9.1\enrollment /user:pxe-upload pxe /persistent:no
if "%PPKG%"=="" goto enroll_staged if "%PPKG%"=="" goto enroll_staged
if not exist "Y:\ppkgs\%SOURCE_PPKG%" ( if not exist "Y:\ppkgs\%SOURCE_PPKG%" (
echo WARNING: %SOURCE_PPKG% not found on server. Enrollment will be skipped. echo WARNING: %SOURCE_PPKG% not found on server. Enrollment will be skipped.
@@ -199,7 +199,7 @@ echo.
echo Starting GEA Standard setup... echo Starting GEA Standard setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-standard /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-standard /user:pxe-upload pxe /persistent:no
goto end goto end
:gea-engineer :gea-engineer
@@ -207,7 +207,7 @@ echo.
echo Starting GEA Engineer setup... echo Starting GEA Engineer setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-engineer /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-engineer /user:pxe-upload pxe /persistent:no
goto end goto end
:gea-shopfloor :gea-shopfloor
@@ -215,7 +215,7 @@ echo.
echo Starting GEA Shopfloor setup... echo Starting GEA Shopfloor setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\gea-shopfloor /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\gea-shopfloor /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-standard :ge-standard
@@ -223,7 +223,7 @@ echo.
echo Starting GE Standard setup... echo Starting GE Standard setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-standard /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-standard /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-engineer :ge-engineer
@@ -231,7 +231,7 @@ echo.
echo Starting GE Engineer setup... echo Starting GE Engineer setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-engineer /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-engineer /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-shopfloor-lockdown :ge-shopfloor-lockdown
@@ -239,7 +239,7 @@ echo.
echo Starting GE Shopfloor Lockdown setup... echo Starting GE Shopfloor Lockdown setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-lockdown /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-shopfloor-lockdown /user:pxe-upload pxe /persistent:no
goto end goto end
:ge-shopfloor-mce :ge-shopfloor-mce
@@ -247,7 +247,7 @@ echo.
echo Starting GE Shopfloor MCE setup... echo Starting GE Shopfloor MCE setup...
start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe start "FlatApp" %SYSTEMDRIVE%\GESetup\FlatSetupLoader.exe
for /l %%i in (1,1,2000000) do rem for /l %%i in (1,1,2000000) do rem
net use Z: \\10.9.100.1\winpeapps\ge-shopfloor-mce /user:pxe-upload pxe /persistent:no net use Z: \\172.16.9.1\winpeapps\ge-shopfloor-mce /user:pxe-upload pxe /persistent:no
goto end goto end
:end :end

View File

@@ -0,0 +1,42 @@
// QR render helper. Scans for any element with data-qr="<text>" and renders
// a Kazuhiko Arase qrcode-generator QR into it as inline <img>. Size is
// controlled via data-qr-size="N" (px square, default 96). Error-correction
// level via data-qr-ec="L|M|Q|H" (default M).
//
// The qrcode-generator lib (loaded before this script) exposes a global
// `qrcode()` factory: typeNumber 0 = auto, ec = 'L'|'M'|'Q'|'H'.
(function () {
function render(el) {
var text = el.getAttribute('data-qr') || '';
if (!text) return;
if (el.dataset.qrRendered === '1') return;
var size = parseInt(el.getAttribute('data-qr-size') || '96', 10);
var ec = el.getAttribute('data-qr-ec') || 'M';
try {
var qr = qrcode(0, ec);
qr.addData(text);
qr.make();
// createImgTag(cellSize, margin)
// 4-cell margin keeps the QR scannable per spec; cell size derived from
// requested pixel size and module count.
var modules = qr.getModuleCount();
var cellSize = Math.max(1, Math.floor(size / (modules + 8)));
el.innerHTML = qr.createImgTag(cellSize, 4);
el.dataset.qrRendered = '1';
el.title = 'Scan: ' + text;
} catch (e) {
el.textContent = '[QR error]';
}
}
function scan() {
var nodes = document.querySelectorAll('[data-qr]');
for (var i = 0; i < nodes.length; i++) render(nodes[i]);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', scan);
} else {
scan();
}
})();

8
webapp/static/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -221,6 +221,8 @@
<script src="{{ url_for('static', filename='bootstrap.bundle.min.js') }}"></script> <script src="{{ url_for('static', filename='bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='app.js') }}"></script> <script src="{{ url_for('static', filename='app.js') }}"></script>
<script src="{{ url_for('static', filename='qrcode.min.js') }}"></script>
<script src="{{ url_for('static', filename='qr-render.js') }}"></script>
{% block extra_scripts %}{% endblock %} {% block extra_scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -116,7 +116,7 @@
<div class="card mt-3"> <div class="card mt-3">
<div class="card-body small text-muted"> <div class="card-body small text-muted">
<strong>How to push status from an imaging client:</strong> <strong>How to push status from an imaging client:</strong>
<pre class="mb-0 mt-2">POST http://10.9.100.1:9009/imaging/status <pre class="mb-0 mt-2">POST http://172.16.9.1:9009/imaging/status
Content-Type: application/json Content-Type: application/json
{ {

View File

@@ -25,7 +25,7 @@
{% endfor %} {% endfor %}
</select> </select>
<div class="form-text"> <div class="form-text">
Files uploaded via SMB to <code>\\10.9.100.1\image-upload</code> Files uploaded via SMB to <code>\\172.16.9.1\image-upload</code>
</div> </div>
</div> </div>
@@ -66,7 +66,7 @@
<div class="text-center py-4"> <div class="text-center py-4">
<h5 class="mt-3 text-muted">No Upload Content Found</h5> <h5 class="mt-3 text-muted">No Upload Content Found</h5>
<p class="text-muted mb-0"> <p class="text-muted mb-0">
Map <code>\\10.9.100.1\image-upload</code> on your Windows PC and copy Map <code>\\172.16.9.1\image-upload</code> on your Windows PC and copy
the Deploy directory contents there. the Deploy directory contents there.
</p> </p>
<button class="btn btn-outline-secondary btn-sm mt-3" onclick="location.reload()"> <button class="btn btn-outline-secondary btn-sm mt-3" onclick="location.reload()">

View File

@@ -70,7 +70,7 @@
<h6 class="card-title">Report Storage</h6> <h6 class="card-title">Report Storage</h6>
<p class="card-text mb-1"> <p class="card-text mb-1">
Blancco Drive Eraser saves erasure certificates to the network share Blancco Drive Eraser saves erasure certificates to the network share
<code>\\10.9.100.1\blancco-reports</code>. <code>\\172.16.9.1\blancco-reports</code>.
</p> </p>
<p class="card-text mb-0 text-muted"> <p class="card-text mb-0 text-muted">
Reports are generated automatically after each drive wipe and contain proof of erasure for compliance and audit purposes. Reports are generated automatically after each drive wipe and contain proof of erasure for compliance and audit purposes.

View File

@@ -75,7 +75,7 @@
<div class="col-md-6"> <div class="col-md-6">
<code class="d-block mb-1">wpeinit</code> <code class="d-block mb-1">wpeinit</code>
<small class="text-muted d-block mb-2">Initialize WinPE networking</small> <small class="text-muted d-block mb-2">Initialize WinPE networking</small>
<code class="d-block mb-1">net use Z: \\10.9.100.1\winpeapps</code> <code class="d-block mb-1">net use Z: \\172.16.9.1\winpeapps</code>
<small class="text-muted d-block mb-2">Map Samba share for deployment</small> <small class="text-muted d-block mb-2">Map Samba share for deployment</small>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">