Compare commits
292 Commits
main
...
pxe-webapp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
120553c975 | ||
|
|
b9a4d0230d | ||
|
|
04db6d6605 | ||
|
|
b11e678297 | ||
|
|
9af1c6d89a | ||
|
|
d63cc5761b | ||
|
|
821a927365 | ||
|
|
9d75d03b2d | ||
|
|
bca04f4ca2 | ||
|
|
576955785d | ||
|
|
23ee99a390 | ||
|
|
9d56a5369e | ||
|
|
dde1aacccb | ||
|
|
54015566a7 | ||
|
|
c8e0b4eace | ||
|
|
da6e62c084 | ||
|
|
0b1625ed87 | ||
|
|
b20e6be629 | ||
|
|
21ff95af78 | ||
|
|
606659bf07 | ||
|
|
53d39bfc50 | ||
|
|
24c41d7e87 | ||
|
|
2717439690 | ||
|
|
bcd91b488a | ||
|
|
bab6927bdb | ||
|
|
c1a5fb3e02 | ||
|
|
86b4c1f301 | ||
|
|
18f20b00e2 | ||
|
|
6dc9761e67 | ||
|
|
29d815fbeb | ||
|
|
45cf7be37a | ||
|
|
ead3314f58 | ||
|
|
689eebf9d1 | ||
|
|
d3a0f90ca1 | ||
|
|
bc422649e9 | ||
|
|
28fd9fe474 | ||
|
|
454e30ff6f | ||
|
|
0d69222f85 | ||
|
|
63fafbfc73 | ||
|
|
045a1fb97f | ||
|
|
a94c20331a | ||
|
|
81c2d85301 | ||
|
|
4db9dc093f | ||
|
|
2dbaaded44 | ||
|
|
a54df7ebd9 | ||
|
|
2ed2cbe2e9 | ||
|
|
2bcc9d8016 | ||
|
|
fd00d5ff89 | ||
|
|
a6f95fcca1 | ||
|
|
cc1e76f3ab | ||
|
|
731d7b044d | ||
|
|
0b86255711 | ||
|
|
35cad62733 | ||
|
|
dfd57bef17 | ||
|
|
24b4d9df66 | ||
|
|
aa026bace0 | ||
|
|
6187219378 | ||
|
|
92f1e62966 | ||
|
|
ae806cf98e | ||
|
|
28cd977c9e | ||
|
|
bfd5c5b75e | ||
|
|
dd9ee33cd5 | ||
|
|
397063f987 | ||
|
|
ddf0a2fdf2 | ||
|
|
6fac73c947 | ||
|
|
f0c049376c | ||
|
|
b691a82530 | ||
|
|
e43ab2a652 | ||
|
|
83ba4865e8 | ||
|
|
121577f090 | ||
|
|
6fcdc8ddb5 | ||
|
|
8c36d1ba5d | ||
|
|
12f6f00cdb | ||
|
|
020f043d8b | ||
|
|
44a7eeae95 | ||
|
|
eff956ae6c | ||
|
|
19049e6b1f | ||
|
|
a97faf9fad | ||
|
|
165d6d34a3 | ||
|
|
b50ff36f71 | ||
|
|
143c27c3bf | ||
|
|
b3ed4cda81 | ||
|
|
14eab13a1a | ||
|
|
783f2fece0 | ||
|
|
1f5f9eb60a | ||
|
|
77655bbfcd | ||
|
|
372c3802e5 | ||
|
|
25f65424c7 | ||
|
|
29e0d97f59 | ||
|
|
b474ac376e | ||
|
|
96c4e5dd11 | ||
|
|
678d10d5c2 | ||
|
|
737a729548 | ||
|
|
47f5d4603a | ||
|
|
5e249e8ad0 | ||
|
|
eb4d067f12 | ||
|
|
f1755751bb | ||
|
|
8ba86ba560 | ||
|
|
f99ece1366 | ||
|
|
ba4df185cd | ||
|
|
bd131f6da9 | ||
|
|
18168b54f8 | ||
|
|
b228a69364 | ||
|
|
abbdc9873c | ||
|
|
280a3b3ba4 | ||
|
|
e40a294193 | ||
|
|
12c6c771dc | ||
|
|
d479e6823c | ||
|
|
ad075c255c | ||
|
|
6e8b8a83a7 | ||
|
|
6f86c81a44 | ||
|
|
2415e6cfd6 | ||
|
|
f3df6c7175 | ||
|
|
26d14b0684 | ||
|
|
cc20501e2e | ||
|
|
0d9c4fdac4 | ||
|
|
54cbe6b5d6 | ||
|
|
b075939c38 | ||
|
|
6d5fee786c | ||
|
|
0e1b80b903 | ||
|
|
8907a86c42 | ||
|
|
a7cb7164d9 | ||
|
|
28efde7d76 | ||
|
|
d5cf255443 | ||
|
|
7ed30f9b85 | ||
|
|
c4aeaaaa17 | ||
|
|
54176e591f | ||
|
|
d185e2b810 | ||
|
|
68f2ce29e4 | ||
|
|
f442ff2f65 | ||
|
|
a0aceb4680 | ||
|
|
bb08392b84 | ||
|
|
9d51c0b987 | ||
|
|
68df59e117 | ||
|
|
d2200e8522 | ||
|
|
8c21282024 | ||
|
|
36be60e9ae | ||
|
|
b96ff7e970 | ||
|
|
d04865da0f | ||
|
|
2b6189f739 | ||
|
|
385acfefa7 | ||
|
|
d5f7abc19f | ||
|
|
049dd7bde6 | ||
|
|
c7b17696d0 | ||
|
|
dfa026b4ee | ||
|
|
936902dc4d | ||
|
|
27e76467a8 | ||
|
|
66c24b5d59 | ||
|
|
1a175bc4fe | ||
|
|
01f524e8b3 | ||
|
|
3880151e71 | ||
|
|
5f97e81dec | ||
|
|
626561a1fa | ||
|
|
f1d9bdf478 | ||
|
|
cc391529bd | ||
|
|
234295931e | ||
|
|
0ffdcc79ed | ||
|
|
3b63697176 | ||
|
|
0cb6b26c27 | ||
|
|
8fbae24b4d | ||
|
|
18db077475 | ||
|
|
8df30017aa | ||
|
|
b240cfea12 | ||
|
|
e7be0f5391 | ||
|
|
0c4c2a245d | ||
|
|
fdf63ab32f | ||
|
|
64e1f7e088 | ||
|
|
287ec86c12 | ||
|
|
85e7d91b1a | ||
|
|
2461804fc8 | ||
|
|
48bc609eb5 | ||
|
|
132c57ab39 | ||
|
|
99deaa6882 | ||
|
|
948d013539 | ||
|
|
6e218f3daf | ||
|
|
b5b644a360 | ||
|
|
bc89ba1cf3 | ||
|
|
1487abdba5 | ||
|
|
7af66575d0 | ||
|
|
8e11f0824a | ||
|
|
d441abd20f | ||
|
|
9145023440 | ||
|
|
51edf98e7d | ||
|
|
e3a3fa6794 | ||
|
|
a165a79f95 | ||
|
|
41cace17e8 | ||
|
|
e97e5bd049 | ||
|
|
c2538a05c5 | ||
|
|
59deaea714 | ||
|
|
f49fa0f940 | ||
|
|
c8e704b595 | ||
|
|
1d65103cc0 | ||
|
|
bfe17fe123 | ||
|
|
da380fbcd7 | ||
|
|
a935c04e1f | ||
|
|
1e17a0564f | ||
|
|
99e7679e87 | ||
|
|
1e6603d331 | ||
|
|
e844ff367c | ||
|
|
913c807142 | ||
|
|
1853db0903 | ||
|
|
7a67716fcc | ||
|
|
995909042b | ||
|
|
a351160520 | ||
|
|
a380b17112 | ||
|
|
5211861409 | ||
|
|
db6be99d43 | ||
|
|
a6fa21589b | ||
|
|
f570e92847 | ||
|
|
ea136687e0 | ||
|
|
57fae57d3b | ||
|
|
c88b2b0ab8 | ||
|
|
1a5852f7ff | ||
|
|
0b116e3ecf | ||
|
|
f6d970c08d | ||
|
|
59b1a9fb65 | ||
|
|
69a1682a7f | ||
|
|
c74148a222 | ||
|
|
191083e440 | ||
|
|
1860a92afa | ||
|
|
9ad467ba6c | ||
|
|
485fe1c7c4 | ||
|
|
9b46d0279f | ||
|
|
5e13d38512 | ||
|
|
55c1ab4814 | ||
|
|
5c3db71879 | ||
|
|
97b9e58d23 | ||
|
|
7298d433eb | ||
|
|
de7d41f5e5 | ||
|
|
f95d305cca | ||
|
|
8e1f81b942 | ||
|
|
77c917157d | ||
|
|
d0dcce5427 | ||
|
|
6602afde38 | ||
|
|
d359563a4c | ||
|
|
cb149ed8cd | ||
|
|
821e3179d1 | ||
|
|
fce6680c6f | ||
|
|
ed12988591 | ||
|
|
b8bb00e2fe | ||
|
|
a104cfdebb | ||
|
|
b57ba0fb6f | ||
|
|
54dddaa760 | ||
|
|
00d4105956 | ||
|
|
86fbc132dd | ||
|
|
4015adeb33 | ||
|
|
de3018512a | ||
|
|
7f93347f74 | ||
|
|
548d85fed5 | ||
|
|
45f39fd431 | ||
|
|
b86b830568 | ||
|
|
a22d2f0313 | ||
|
|
44554b95b0 | ||
|
|
02499cf74b | ||
|
|
27045d5e4a | ||
|
|
1f60c86ec8 | ||
|
|
44d2f0afd5 | ||
|
|
5891a1966f | ||
|
|
e1ea6b7c62 | ||
|
|
2a0b4885fe | ||
|
|
37357eee43 | ||
|
|
3aabd47571 | ||
|
|
7f097013fc | ||
|
|
9108b495c9 | ||
|
|
d8c64bef2b | ||
|
|
76a3ba513c | ||
|
|
3385bc87aa | ||
|
|
220c5db5b9 | ||
|
|
8debc4ddb3 | ||
|
|
036090348c | ||
|
|
b9f66687ac | ||
|
|
65eeead5a0 | ||
|
|
a9a7478d5a | ||
|
|
1c361e138b | ||
|
|
ca647cb690 | ||
|
|
5f322d1110 | ||
|
|
520d4aa791 | ||
|
|
894305e906 | ||
|
|
1b7e1bfee4 | ||
|
|
d5398bdd74 | ||
|
|
cdb6655e4a | ||
|
|
74ba3d1339 | ||
|
|
4599c85509 | ||
|
|
2d75935dfc | ||
|
|
3fb1d983df | ||
|
|
9beee842f1 | ||
|
|
f013aa2bff | ||
|
|
a9260ecadd | ||
|
|
ab3e1c98f6 | ||
|
|
842ef88ccb | ||
|
|
a17b3fae6a | ||
|
|
ce604adcda |
20
.githooks/pre-commit
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Refuse a commit that stages an unattend answer file the linter rejects.
|
||||||
|
# A bad answer file stops every bay imaged with it at "Windows could not
|
||||||
|
# parse or process unattend answer file". Enable with:
|
||||||
|
# git config core.hooksPath .githooks
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
mapfile -t FILES < <(git diff --cached --name-only --diff-filter=ACMR | grep -iE '(^|/)(Flat)?Unattend[^/]*\.xml$' || true)
|
||||||
|
[ ${#FILES[@]} -eq 0 ] && exit 0
|
||||||
|
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||||
|
STAGED=()
|
||||||
|
for f in "${FILES[@]}"; do
|
||||||
|
out="$TMP/$(echo "$f" | tr '/' '_')"
|
||||||
|
git show ":$f" > "$out" # lint what is staged, not the working copy
|
||||||
|
STAGED+=("$out")
|
||||||
|
done
|
||||||
|
if ! python3 "$ROOT/scripts/lint-unattend.py" --local "${STAGED[@]}"; then
|
||||||
|
echo "pre-commit: unattend lint failed for: ${FILES[*]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
32
.gitignore
vendored
@@ -101,6 +101,34 @@ playbook/shopfloor-setup/Shopfloor/PrinterInstallerMap.exe
|
|||||||
# /home/camp/pxe-images/keyence/Logs/Keyence/install.log for the signature).
|
# /home/camp/pxe-images/keyence/Logs/Keyence/install.log for the signature).
|
||||||
# Canonical source on the GE-Enforce SFLD share:
|
# Canonical source on the GE-Enforce SFLD share:
|
||||||
# tsgwp00525\sfld$\v2\shared\dt\shopfloor\gea-shopfloor-keyence\apps\Data1.cab
|
# tsgwp00525\sfld$\v2\shared\dt\shopfloor\gea-shopfloor-keyence\apps\Data1.cab
|
||||||
# Stage to playbook/shopfloor-setup/gea-shopfloor-keyence/installers/Data1.cab
|
|
||||||
# before building the USB image.
|
# before building the USB image.
|
||||||
playbook/shopfloor-setup/gea-shopfloor-keyence/installers/Data1.cab
|
playbook/shopfloor-setup/gea-shopfloor-waxtrace/captured-binary/
|
||||||
|
|
||||||
|
# Keyence per-model installer payloads - too big for git, staged via sync-keyence.sh
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-keyence/vr3000/installers/Data*.cab
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-keyence/vr3000/installers/*.msi
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-keyence/vr5000/installers/Data*.cab
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-keyence/vr5000/installers/*.msi
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-keyence/vr6000/installers/Data1.cab
|
||||||
|
|
||||||
|
# Part Marker (Telesis) utility password - secret, deployed via the enrollment
|
||||||
|
# share from the working tree, never committed.
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-partmarker/PartMarker/Mark/utilpassword.txt
|
||||||
|
|
||||||
|
# HeatTreat per-machine DNC .reg exports (6601-6604) - contain DNC FtpPasswd
|
||||||
|
# credentials. Deployed via the enrollment share from the working tree.
|
||||||
|
playbook/shopfloor-setup/gea-shopfloor-heattreat/reg/*.reg
|
||||||
|
|
||||||
|
# SIC e10 controller config files carry the programmer/supervisor password
|
||||||
|
# in cleartext. Never commit them. See reference/e10/README.md
|
||||||
|
reference/e10/**/ConfigCtrl*
|
||||||
|
reference/e10/**/*.kind1
|
||||||
|
# GE part programs carry part numbers and marking content. Keep them out of
|
||||||
|
# git; they live in ~/pxe-images/e10-testing/partprograms/
|
||||||
|
reference/e10/partprograms/
|
||||||
|
|
||||||
|
# Per-site playbook values (NIC MACs); template: playbook/site-vars.example.yml
|
||||||
|
playbook/site-vars.yml
|
||||||
|
|
||||||
|
# Site content pulled from a live server (scripts/capture-site-content.sh)
|
||||||
|
site-content/
|
||||||
|
|||||||
BIN
Binary/Binary.NewBinary1
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
Binary/Binary.NewBinary10
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Binary/Binary.NewBinary11
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Binary/Binary.NewBinary12
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Binary/Binary.NewBinary13
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary14
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary15
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary16
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary17
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary18
Normal file
|
After Width: | Height: | Size: 766 B |
40
Binary/Binary.NewBinary19
Normal 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
|
After Width: | Height: | Size: 318 B |
22
Binary/Binary.NewBinary20
Normal 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
@@ -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
|
After Width: | Height: | Size: 318 B |
BIN
Binary/Binary.NewBinary4
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
Binary/Binary.NewBinary5
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
Binary/Binary.NewBinary6
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
Binary/Binary.NewBinary7
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary8
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
Binary/Binary.NewBinary9
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Binary/Binary.SetAllUsers.dll
Normal file
29
Build-PxeUsb.cmd
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@echo off
|
||||||
|
rem Build the PXE server installer USB. Double-click this file in the build
|
||||||
|
rem folder (copied to a local disk, e.g. C:\pxe-usb). It asks for admin
|
||||||
|
rem rights, lists the USB disks and asks which one to erase and build.
|
||||||
|
rem Runs scripts\Build-PxeUsb.ps1 -Interactive with the execution policy
|
||||||
|
rem bypassed for that one window only.
|
||||||
|
setlocal
|
||||||
|
set "PS1=%~dp0scripts\Build-PxeUsb.ps1"
|
||||||
|
if not exist "%PS1%" (
|
||||||
|
echo Cannot find %PS1%
|
||||||
|
echo Run this from the pxe-usb build folder.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
if "%~d0"=="\\" goto :network
|
||||||
|
net use %~d0 >nul 2>&1 && goto :network
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','\"%PS1%\"','-Interactive'"
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:network
|
||||||
|
echo This build folder is on a network drive (%~d0).
|
||||||
|
echo An administrator window cannot see network drives mapped in this session.
|
||||||
|
echo Copy the whole pxe-usb folder to a local disk first, for example:
|
||||||
|
echo.
|
||||||
|
echo robocopy "%~dp0." C:\pxe-usb /E
|
||||||
|
echo.
|
||||||
|
echo then double-click C:\pxe-usb\Build-PxeUsb.cmd
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
14
Capture-SiteContent.cmd
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@echo off
|
||||||
|
rem Pull the site content (Office, Adobe, certificates, Engineering Layer,
|
||||||
|
rem BIOS files, boot.wim, Blancco) from a running PXE server into this
|
||||||
|
rem build folder. Double-click it; it asks for the server address and the
|
||||||
|
rem pxe password. Runs scripts\Capture-SiteContent.ps1 -Interactive.
|
||||||
|
setlocal
|
||||||
|
set "PS1=%~dp0scripts\Capture-SiteContent.ps1"
|
||||||
|
if not exist "%PS1%" (
|
||||||
|
echo Cannot find %PS1%
|
||||||
|
echo Run this from the pxe-usb build folder.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%PS1%" -Interactive
|
||||||
BIN
Icon/Icon.ARPPRODUCTICON.exe
Normal file
BIN
Icon/Icon.NewShortcut1_AB8E7834205C40DE9AD7D94845255E55.exe
Normal file
BIN
Icon/Icon.NewShortcut2_1E955C7F522448028AA8419A487CA996.exe
Normal file
BIN
Icon/Icon._052C53DD_2BAF_4769_A1A0_1519CA2B23FB
Normal file
BIN
Icon/Icon._173060F8_E06D_4B06_B289_4656D92A595E
Normal file
BIN
Icon/Icon._3336CEAD_1861_4B26_A1C1_2D2F75C36A27
Normal file
BIN
Icon/Icon._59497049_1008_4427_84DF_744B04B0F075
Normal file
BIN
Icon/Icon._5EF3CFE6_6B6A_49D5_A54E_95222360D405
Normal file
BIN
Icon/Icon._823D3F96_4702_483C_BE0E_6CF23CAA78AD
Normal file
BIN
Icon/Icon._90919B36_D3D7_437D_B050_7D5620C88056
Normal file
BIN
Icon/Icon._E7806B4E_27A4_4270_8F13_3E9E01192773
Normal file
516
README.md
@@ -1,265 +1,251 @@
|
|||||||
# GE Aerospace PXE Boot Server
|
# GE Aerospace PXE Boot Server
|
||||||
|
|
||||||
Automated, air-gapped PXE boot server for deploying GE Aerospace Windows images. Built on Ubuntu 24.04 Server with zero-touch provisioning via autoinstall and Ansible.
|
Automated, air-gapped PXE boot server for deploying GE Aerospace Windows images. Built on Ubuntu 24.04 Server with zero-touch provisioning via autoinstall and Ansible.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This project provides a complete, repeatable build process for a PXE boot server that serves Windows PE images to client machines on an isolated network. Everything runs offline after initial setup — no internet required on the target server.
|
This project provides a complete, repeatable build process for a PXE boot server that serves Windows PE images to client machines on an isolated network. Everything runs offline after initial setup — no internet required on the target server.
|
||||||
|
|
||||||
### Boot Chain
|
### Boot Chain
|
||||||
|
|
||||||
```
|
```
|
||||||
Client PXE boot (UEFI Secure Boot)
|
Client PXE boot (UEFI Secure Boot)
|
||||||
-> iPXE (TFTP, Broadcom-signed for Secure Boot)
|
-> iPXE (TFTP, Broadcom-signed for Secure Boot)
|
||||||
-> iPXE boot menu (HTTP, port 4433)
|
-> iPXE boot menu (HTTP, port 4433)
|
||||||
-> User selects boot option:
|
-> User selects boot option:
|
||||||
├── Windows PE -> wimboot -> boot.wim -> startnet.cmd -> Samba share -> Image deployment
|
├── Windows PE -> wimboot -> boot.wim -> startnet.cmd -> Samba share -> Image deployment
|
||||||
├── Clonezilla -> vmlinuz/initrd -> Disk cloning/imaging
|
├── Clonezilla -> vmlinuz/initrd -> Disk cloning/imaging
|
||||||
├── Blancco -> Native kernel + initramfs -> NIST 800-88 drive erasure
|
├── Blancco -> Native kernel + initramfs -> NIST 800-88 drive erasure
|
||||||
└── Memtest86+ -> Memory diagnostics
|
(memory test: Dell F12 > Diagnostics)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
|
|
||||||
| 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) |
|
||||||
| Samba | 445/tcp | Deployment content + Clonezilla + Blancco|
|
| Samba | 445/tcp | Deployment content + Clonezilla + Blancco|
|
||||||
| Flask Webapp| 9009/tcp | Web management interface |
|
| Flask Webapp| 9009/tcp | Web management interface |
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
**On your workstation (internet-connected):**
|
**On your workstation (internet-connected):**
|
||||||
- Ubuntu 24.04 (or Linux Mint / similar) for downloading packages
|
- Ubuntu 24.04 (or Linux Mint / similar) for downloading packages
|
||||||
- Ubuntu Server 24.04 ISO
|
- Ubuntu Server 24.04 ISO
|
||||||
- GE Aerospace Media Creator LITE (for WinPE images)
|
- GE Aerospace Media Creator LITE (for WinPE images)
|
||||||
- USB drive >= 8 GB (32+ GB if bundling WinPE images)
|
- USB drive >= 8 GB (32+ GB if bundling WinPE images)
|
||||||
|
|
||||||
**GE Access Packages (MyAccess portal):**
|
**GE Access Packages (MyAccess portal):**
|
||||||
- EPM Rufus Exception Request
|
- EPM Rufus Exception Request
|
||||||
- EPM DT Functions
|
- EPM DT Functions
|
||||||
- DLP - Encrypted Removable (USB) Long Term Access
|
- DLP - Encrypted Removable (USB) Long Term Access
|
||||||
|
|
||||||
### Step 1: Download Offline Packages
|
### Step 1: Download Offline Packages
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/download-packages.sh
|
./scripts/download-packages.sh --iso /path/to/ubuntu-24.04.x-live-server-amd64.iso
|
||||||
```
|
./scripts/check-updates.py # expect: PASSED, 0 security updates
|
||||||
|
./scripts/fetch-ipxe.sh
|
||||||
Downloads all .deb packages and Python wheels for offline installation (~140 MB of debs, ~20 MB of wheels).
|
```
|
||||||
|
|
||||||
### Step 2: Prepare Boot Tools (optional)
|
`fetch-ipxe.sh` downloads the Secure Boot iPXE chain into `boot-files/`, pinned by sha256: the iPXE release
|
||||||
|
tarball (its snponly build becomes `ipxe.efi`) and iPXE's dual-signed shim (Microsoft UEFI CA 2011 + 2023),
|
||||||
```bash
|
which every UEFI client boots as `ipxe-shim.efi`. Keep the old Broadcom-signed `boot-files/ipxe.efi` too: the
|
||||||
./scripts/prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin
|
playbook installs it as `ipxe-bcm.efi`, the rollback boot file.
|
||||||
```
|
|
||||||
|
Downloads all .deb packages and Python wheels for offline installation (~140 MB of debs, ~20 MB of wheels).
|
||||||
Extracts and configures boot tool files (Blancco, Clonezilla, Memtest86+). Automatically patches Blancco's config.img to auto-save erasure reports to the PXE server's Samba share.
|
|
||||||
|
### Step 2: Prepare Boot Tools (optional)
|
||||||
### Step 3: Build the USB
|
|
||||||
|
```bash
|
||||||
```bash
|
./scripts/prepare-boot-tools.sh /path/to/blancco.iso
|
||||||
sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
|
```
|
||||||
```
|
|
||||||
|
Extracts and configures boot tool files (Blancco, Clonezilla with Debian's signed shim for Secure Boot). Automatically patches Blancco's config.img to auto-save erasure reports to the PXE server's Samba share.
|
||||||
Creates a bootable USB with two partitions:
|
|
||||||
- **Partition 1:** Ubuntu Server installer
|
### Step 3: Build the USB
|
||||||
- **Partition 2:** CIDATA (autoinstall config, offline .debs, pip wheels, Ansible playbook, webapp, boot tools)
|
|
||||||
|
```bash
|
||||||
### Step 4: Install on Target Server
|
sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
|
||||||
|
```
|
||||||
1. Insert USB into the target machine
|
|
||||||
2. Press F12 and boot from USB
|
On a Windows PC instead: `scripts/stage-usb-share.sh` here, then
|
||||||
3. Ubuntu auto-installs with no interaction
|
`Build-PxeUsb.ps1 -DiskNumber N -NoSiteVars` there (see SETUP.md, "Building
|
||||||
4. After reboot, the first-boot script:
|
the USB on a Windows PC").
|
||||||
- Installs all offline .deb packages
|
|
||||||
- Runs the Ansible playbook (configures dnsmasq, Apache, Samba, UFW, webapp)
|
`build-usb.sh` creates a bootable USB with two partitions:
|
||||||
- Configures static IP `10.9.100.1/24`
|
- **Partition 1:** Ubuntu Server installer
|
||||||
5. Move the server's wired NIC to the isolated PXE switch
|
- **Partition 2:** CIDATA (autoinstall config, offline .debs, pip wheels, Ansible playbook, webapp, boot tools)
|
||||||
|
|
||||||
### Step 5: Access the Web Interface
|
### Step 4: Install on Target Server
|
||||||
|
|
||||||
Open `http://10.9.100.1:9009` from any machine on the isolated network.
|
1. Insert USB into the target machine
|
||||||
|
2. Press F12 and boot from USB
|
||||||
## Web Management Interface
|
3. Ubuntu auto-installs with no interaction
|
||||||
|
4. After reboot, the first-boot script:
|
||||||
The Flask webapp (port 9009) provides a browser-based management UI:
|
- Installs all offline .deb packages
|
||||||
|
- Runs the Ansible playbook (configures dnsmasq, Apache, Samba, UFW, webapp)
|
||||||
- **Dashboard** — Service status overview, disk usage, connected DHCP clients
|
- Configures static IP `172.16.9.1/24`
|
||||||
- **Image Import** — Import WinPE deployment images from USB drives
|
5. Move the server's wired NIC to the isolated PXE switch
|
||||||
- **Unattend Editor** — Edit Windows unattend.xml files per image type (XML syntax highlighting)
|
|
||||||
- **startnet.cmd Editor** — Modify the startnet.cmd inside boot.wim without Windows (uses wimtools)
|
### Step 5: Access the Web Interface
|
||||||
- **Clonezilla Backups** — Upload, download, and manage disk backup images
|
|
||||||
- **Blancco Reports** — View, download, and manage drive erasure reports (auto-collected via Samba)
|
Open `http://172.16.9.1:9009` from any machine on the isolated network.
|
||||||
- **Image Config** — Per-image configuration editor (drivers, OS packages, hardware models)
|
|
||||||
- **Enrollment** — Upload, download, and manage GCCH bulk enrollment packages
|
## Web Management Interface
|
||||||
- **Audit Log** — Activity history for all write operations (imports, edits, deletes)
|
|
||||||
|
The Flask webapp (port 9009) provides a browser-based management UI:
|
||||||
### Image Types Supported
|
|
||||||
|
- **Dashboard** — Service status overview, disk usage, connected DHCP clients
|
||||||
| Image Type | Domain | Description |
|
- **Image Import** — Import WinPE deployment images from USB drives
|
||||||
|----------------------|-----------------|-------------------------|
|
- **Unattend Editor** — Edit Windows unattend.xml files per image type (XML syntax highlighting)
|
||||||
| gea-standard | geaerospace.com | Standard desktop |
|
- **startnet.cmd Editor** — Modify the startnet.cmd inside boot.wim without Windows (uses wimtools)
|
||||||
| gea-engineer | geaerospace.com | Engineering desktop |
|
- **Clonezilla Backups** — Upload, download, and manage disk backup images
|
||||||
| gea-shopfloor | geaerospace.com | Shop floor kiosk |
|
- **Blancco Reports** — View, download, and manage drive erasure reports (auto-collected via Samba)
|
||||||
| ge-standard | ge.com | Standard desktop |
|
- **Image Config** — Per-image configuration editor (drivers, OS packages, hardware models)
|
||||||
| ge-engineer | ge.com | Engineering desktop |
|
- **Enrollment** — Upload, download, and manage GCCH bulk enrollment packages
|
||||||
| ge-shopfloor-lockdown| ge.com | Shop floor (locked) |
|
- **Audit Log** — Activity history for all write operations (imports, edits, deletes)
|
||||||
| ge-shopfloor-mce | ge.com | Shop floor (MCE) |
|
|
||||||
|
### Image Types Supported
|
||||||
## Project Structure
|
|
||||||
|
| Image Type | Domain | Description |
|
||||||
```
|
|----------------------|-----------------|-------------------------|
|
||||||
pxe-server/
|
| gea-standard | geaerospace.com | Standard desktop |
|
||||||
├── autoinstall/
|
| gea-engineer | geaerospace.com | Engineering desktop |
|
||||||
│ ├── user-data # Cloud-init autoinstall config + first-boot script
|
| gea-shopfloor | geaerospace.com | Shop floor kiosk |
|
||||||
│ └── meta-data # Cloud-init metadata (required, empty)
|
| ge-standard | ge.com | Standard desktop |
|
||||||
├── playbook/
|
| ge-engineer | ge.com | Engineering desktop |
|
||||||
│ ├── pxe_server_setup.yml # Ansible playbook: all server configuration
|
| ge-shopfloor-lockdown| ge.com | Shop floor (locked) |
|
||||||
│ ├── inventory.ini # Ansible inventory
|
| ge-shopfloor-mce | ge.com | Shop floor (MCE) |
|
||||||
│ ├── startnet.cmd # WinPE startup script (injected into boot.wim)
|
|
||||||
│ ├── blancco-init.sh # Custom initramfs for Blancco PXE boot
|
## Project Structure
|
||||||
│ ├── blancco-preferences.xml # Blancco auto-report preferences
|
|
||||||
│ ├── check-bios.cmd # Pre-imaging BIOS update checker
|
```
|
||||||
│ ├── FlatUnattendW10-shopfloor.xml # Shopfloor unattend.xml
|
pxe-server/
|
||||||
│ └── shopfloor-setup/ # PC type setup scripts (Standard, CMM, Display, etc.)
|
├── autoinstall/
|
||||||
├── webapp/
|
│ ├── user-data # Cloud-init autoinstall config + first-boot script
|
||||||
│ ├── app.py # Flask application (~1600 lines)
|
│ └── meta-data # Cloud-init metadata (required, empty)
|
||||||
│ ├── requirements.txt # Python deps (flask, lxml)
|
├── playbook/
|
||||||
│ ├── static/
|
│ ├── pxe_server_setup.yml # Ansible playbook: all server configuration
|
||||||
│ │ ├── ge-aerospace-logo.svg # GE Aerospace branding
|
│ ├── inventory.ini # Ansible inventory
|
||||||
│ │ ├── favicon.ico # Browser favicon
|
│ ├── startnet.cmd # WinPE startup script (injected into boot.wim)
|
||||||
│ │ ├── app.js # Frontend JavaScript
|
│ ├── blancco-init.sh # Custom initramfs for Blancco PXE boot
|
||||||
│ │ ├── bootstrap.min.css # Bootstrap 5 (bundled offline)
|
│ ├── blancco-preferences.xml # Blancco auto-report preferences
|
||||||
│ │ ├── bootstrap.bundle.min.js
|
│ ├── check-bios.cmd # Pre-imaging BIOS update checker
|
||||||
│ │ ├── bootstrap-icons.min.css
|
│ ├── FlatUnattendW10-shopfloor.xml # Shopfloor unattend.xml
|
||||||
│ │ └── fonts/ # Icon fonts (woff/woff2)
|
│ └── shopfloor-setup/ # PC type setup scripts (Standard, CMM, Display, etc.)
|
||||||
│ └── templates/
|
├── webapp/
|
||||||
│ ├── base.html # Layout with GE branding and sidebar nav
|
│ ├── app.py # Flask application (~1600 lines)
|
||||||
│ ├── dashboard.html # Service status and overview
|
│ ├── requirements.txt # Python deps (flask, lxml)
|
||||||
│ ├── import.html # USB image import wizard
|
│ ├── static/
|
||||||
│ ├── unattend_editor.html # XML editor for unattend files
|
│ │ ├── ge-aerospace-logo.svg # GE Aerospace branding
|
||||||
│ ├── startnet_editor.html # startnet.cmd WIM editor
|
│ │ ├── favicon.ico # Browser favicon
|
||||||
│ ├── image_config.html # Per-image driver and hardware config
|
│ │ ├── app.js # Frontend JavaScript
|
||||||
│ ├── enrollment.html # GCCH bulk enrollment packages
|
│ │ ├── bootstrap.min.css # Bootstrap 5 (bundled offline)
|
||||||
│ ├── backups.html # Clonezilla backup management
|
│ │ ├── bootstrap.bundle.min.js
|
||||||
│ ├── reports.html # Blancco erasure reports
|
│ │ ├── bootstrap-icons.min.css
|
||||||
│ └── audit.html # Activity audit log
|
│ │ └── fonts/ # Icon fonts (woff/woff2)
|
||||||
├── docs/
|
│ └── templates/
|
||||||
│ └── shopfloor-display-imaging-guide.md # End-user imaging guide
|
│ ├── base.html # Layout with GE branding and sidebar nav
|
||||||
├── boot-tools/ # Extracted boot tool files (gitignored)
|
│ ├── dashboard.html # Service status and overview
|
||||||
│ ├── blancco/ # Blancco Drive Eraser
|
│ ├── import.html # USB image import wizard
|
||||||
│ ├── clonezilla/ # Clonezilla Live
|
│ ├── unattend_editor.html # XML editor for unattend files
|
||||||
│ └── memtest/ # Memtest86+
|
│ ├── startnet_editor.html # startnet.cmd WIM editor
|
||||||
├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD)
|
│ ├── image_config.html # Per-image driver and hardware config
|
||||||
├── offline-packages/ # .deb files (gitignored, built by download-packages.sh)
|
│ ├── enrollment.html # GCCH bulk enrollment packages
|
||||||
├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh)
|
│ ├── backups.html # Clonezilla backup management
|
||||||
├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored)
|
│ ├── reports.html # Blancco erasure reports
|
||||||
├── bios-staging/ # Dell BIOS update binaries (gitignored)
|
│ └── audit.html # Activity audit log
|
||||||
├── scripts/ # Build, deploy, and helper scripts
|
├── docs/
|
||||||
│ ├── build-usb.sh # Builds the installer USB (2-partition)
|
│ └── shopfloor-display-imaging-guide.md # End-user imaging guide
|
||||||
│ ├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO
|
├── boot-tools/ # Extracted boot tool files (gitignored)
|
||||||
│ ├── prepare-boot-tools.sh # Extracts and patches boot tool files
|
│ ├── blancco/ # Blancco Drive Eraser
|
||||||
│ ├── download-packages.sh # Downloads offline .debs + pip wheels
|
│ └── clonezilla/ # Clonezilla Live
|
||||||
│ ├── download-drivers.py # Downloads Dell drivers directly from dell.com
|
├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD)
|
||||||
│ ├── deploy-bios.sh # Pushes BIOS updates to enrollment share
|
├── offline-packages/ # .deb files (gitignored, built by download-packages.sh)
|
||||||
│ ├── pull-bios.sh # Pulls BIOS binaries from upstream cache
|
├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh)
|
||||||
│ ├── sync_hardware_models.py # Syncs hardware model configs across images
|
├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored)
|
||||||
│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB
|
├── bios-staging/ # Dell BIOS update binaries (gitignored)
|
||||||
│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN
|
├── scripts/ # Build, deploy, and helper scripts
|
||||||
├── config/ # Site-specific configuration overrides
|
│ ├── build-usb.sh # Builds the installer USB (2-partition)
|
||||||
├── startnet-template.cmd # startnet.cmd template (synced with playbook copy)
|
│ ├── Build-PxeUsb.ps1 # Windows: builds the installer USB (one FAT32 partition)
|
||||||
├── README.md # This file
|
│ ├── stage-usb-share.sh # Puts repo + payload on the pxe-images share for it
|
||||||
└── SETUP.md # Detailed setup guide
|
│ ├── prepare-boot-tools.sh # Extracts and patches boot tool files
|
||||||
```
|
│ ├── download-packages.sh # Downloads offline .debs + pip wheels
|
||||||
|
│ ├── download-drivers.py # Downloads Dell drivers directly from dell.com
|
||||||
## Proxmox Deployment
|
│ ├── capture-site-content.sh # Pulls site content + fresh payload from a live server
|
||||||
|
│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB
|
||||||
A single ISO can be built for deploying the PXE server in a Proxmox VM:
|
│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN
|
||||||
|
├── config/ # Site-specific configuration overrides
|
||||||
### Build the ISO
|
├── README.md # This file
|
||||||
|
└── SETUP.md # Detailed setup guide
|
||||||
```bash
|
```
|
||||||
# Prerequisites (on build workstation)
|
|
||||||
sudo apt install xorriso p7zip-full
|
## Proxmox Deployment
|
||||||
|
|
||||||
# Build the installer ISO
|
Build a disk image with
|
||||||
./scripts/build-proxmox-iso.sh /path/to/ubuntu-24.04-live-server-amd64.iso
|
`scripts/build-usb.sh --site-vars site-vars.yml --image pxe-usb.img auto UBUNTU.iso`
|
||||||
```
|
and import it on the Proxmox host (`qm importdisk <vmid> pxe-usb.img
|
||||||
|
<storage>`), attached after the system disk in the boot order. The VM needs
|
||||||
This creates `pxe-server-proxmox.iso` containing the Ubuntu installer, autoinstall config, all offline packages, the Ansible playbook, webapp, and boot tools.
|
two NICs: one on an isolated imaging bridge (no other DHCP server) and one
|
||||||
|
on the corp bridge; their MACs go in `site-vars.yml`.
|
||||||
### Deploy on Proxmox
|
|
||||||
|
### Import WinPE Images
|
||||||
1. Upload `pxe-server-proxmox.iso` to Proxmox storage (Datacenter -> Storage -> ISO Images)
|
|
||||||
2. Create a new VM:
|
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.
|
||||||
- **OS:** Linux 6.x kernel
|
|
||||||
- **BIOS:** OVMF (UEFI) or SeaBIOS
|
## Samba Shares
|
||||||
- **Memory:** 4096 MB
|
|
||||||
- **CPU:** 2+ cores
|
| Share | Path | Purpose |
|
||||||
- **Disk:** 40+ GB (VirtIO SCSI)
|
|-----------------|---------------------------|--------------------------------|
|
||||||
- **Network:** Bridge connected to your isolated PXE network
|
| winpeapps | /srv/samba/winpeapps | WinPE deployment images |
|
||||||
3. Attach the ISO as CD-ROM and start the VM
|
| clonezilla | /srv/samba/clonezilla | Clonezilla disk backup images |
|
||||||
4. Ubuntu auto-installs with zero interaction (~10-15 minutes)
|
| blancco-reports | /srv/samba/blancco-reports| Blancco erasure reports (auto) |
|
||||||
5. After reboot, first-boot configures all PXE services automatically
|
| enrollment | /srv/samba/enrollment | GCCH bulk enrollment packages |
|
||||||
6. Access the web interface at `http://10.9.100.1:9009`
|
| image-upload | /home/pxe/image-upload | Image upload staging area |
|
||||||
|
|
||||||
### Import WinPE Images
|
All shares use guest access (no authentication) for ease of use on the isolated network.
|
||||||
|
|
||||||
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.
|
## Blancco Drive Erasure
|
||||||
|
|
||||||
## Samba Shares
|
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.
|
||||||
|
|
||||||
| Share | Path | Purpose |
|
Reports are viewable and downloadable from the web interface at `http://172.16.9.1:9009/reports`.
|
||||||
|-----------------|---------------------------|--------------------------------|
|
|
||||||
| winpeapps | /srv/samba/winpeapps | WinPE deployment images |
|
## Notes
|
||||||
| clonezilla | /srv/samba/clonezilla | Clonezilla disk backup images |
|
|
||||||
| blancco-reports | /srv/samba/blancco-reports| Blancco erasure reports (auto) |
|
- Run `download-packages.sh --iso <the server ISO>` before building the USB: it builds the offline apt repo (every update for the ISO's base plus the explicit packages, wimtools included) that the installer patches and installs from
|
||||||
| enrollment | /srv/samba/enrollment | GCCH bulk enrollment packages |
|
- The webapp uses session-based CSRF tokens on all POST forms and API endpoints
|
||||||
| image-upload | /home/pxe/image-upload | Image upload staging area |
|
|
||||||
|
## Commit History
|
||||||
All shares use guest access (no authentication) for ease of use on the isolated network.
|
|
||||||
|
| Commit | Description |
|
||||||
## Blancco Drive Erasure
|
|---------|--------------------------------------------------------------------|
|
||||||
|
| 5791bd1 | Initial project setup: automated PXE server provisioning |
|
||||||
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.
|
| cee4ecd | Add web management UI, offline packages, WinPE consolidation |
|
||||||
|
| f614596 | Fix unattend.xml path to match actual image structure |
|
||||||
Reports are viewable and downloadable from the web interface at `http://10.9.100.1:9009/reports`.
|
| e7313c2 | Add multi-boot PXE menu, Clonezilla backups, GE Aerospace branding|
|
||||||
|
| 89b5834 | Add wimtools and startnet.cmd editor for boot.wim modification |
|
||||||
## Notes
|
| 05dbb7e | Add Blancco erasure reports Samba share and webapp viewer |
|
||||||
|
| ef75839 | Auto-patch Blancco config.img for network report storage |
|
||||||
- Run `download-packages.sh` before building USB — it downloads all offline `.deb` packages including wimtools (needed for startnet.cmd editing)
|
| 92c9b0f | Fix review findings: offline assets, security, audit logging |
|
||||||
- The webapp uses session-based CSRF tokens on all POST forms and API endpoints
|
| 725c8f4 | Change webapp to port 9009, add test VM script |
|
||||||
|
| f3a384f | Add Proxmox ISO builder, CSRF protection, boot-files integration |
|
||||||
## Commit History
|
| 1a5c4f7 | Eliminate USB requirement for WinPE PXE boot |
|
||||||
|
| dd2fec5 | Blancco PXE boot via Ubuntu kernel switch_root |
|
||||||
| Commit | Description |
|
| 86660a0 | Remove UEFI HTTP Boot config from dnsmasq |
|
||||||
|---------|--------------------------------------------------------------------|
|
| 6d0e6ee | BIOS check fix, parallel downloads, shopfloor hardening |
|
||||||
| 5791bd1 | Initial project setup: automated PXE server provisioning |
|
| 7616549 | Shopfloor PC type system, webapp enhancements |
|
||||||
| cee4ecd | Add web management UI, offline packages, WinPE consolidation |
|
| b7cd097 | Blancco 7.15.1 upgrade: native kernel boot, BMC cloud licensing |
|
||||||
| f614596 | Fix unattend.xml path to match actual image structure |
|
| 163e58a | Fix dnsmasq reboot cron: use /etc/cron.d/ instead of crontab |
|
||||||
| e7313c2 | Add multi-boot PXE menu, Clonezilla backups, GE Aerospace branding|
|
|
||||||
| 89b5834 | Add wimtools and startnet.cmd editor for boot.wim modification |
|
|
||||||
| 05dbb7e | Add Blancco erasure reports Samba share and webapp viewer |
|
|
||||||
| ef75839 | Auto-patch Blancco config.img for network report storage |
|
|
||||||
| 92c9b0f | Fix review findings: offline assets, security, audit logging |
|
|
||||||
| 725c8f4 | Change webapp to port 9009, add test VM script |
|
|
||||||
| f3a384f | Add Proxmox ISO builder, CSRF protection, boot-files integration |
|
|
||||||
| 1a5c4f7 | Eliminate USB requirement for WinPE PXE boot |
|
|
||||||
| dd2fec5 | Blancco PXE boot via Ubuntu kernel switch_root |
|
|
||||||
| 86660a0 | Remove UEFI HTTP Boot config from dnsmasq |
|
|
||||||
| 6d0e6ee | BIOS check fix, parallel downloads, shopfloor hardening |
|
|
||||||
| 7616549 | Shopfloor PC type system, webapp enhancements |
|
|
||||||
| b7cd097 | Blancco 7.15.1 upgrade: native kernel boot, BMC cloud licensing |
|
|
||||||
| 163e58a | Fix dnsmasq reboot cron: use /etc/cron.d/ instead of crontab |
|
|
||||||
|
|||||||
551
SETUP.md
@@ -1,234 +1,317 @@
|
|||||||
# PXE Server Setup Guide
|
# PXE Server Setup Guide
|
||||||
|
|
||||||
Automated build process for deploying an Ubuntu-based PXE boot server that hosts GE Aerospace Windows PE images. The entire setup is air-gapped — no internet required on the target machine.
|
Automated build process for deploying an Ubuntu-based PXE boot server that hosts GE Aerospace Windows PE images. The entire setup is air-gapped — no internet required on the target machine.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Client PXE boot
|
Client PXE boot
|
||||||
-> Broadcom signed iPXE (Secure Boot)
|
-> Broadcom signed iPXE (Secure Boot)
|
||||||
-> iPXE boot menu (HTTP, port 4433)
|
-> iPXE boot menu (HTTP, port 4433)
|
||||||
├── Windows PE -> wimboot -> boot.wim -> startnet.cmd -> Samba -> Image deployment
|
├── Windows PE -> wimboot -> boot.wim -> startnet.cmd -> Samba -> Image deployment
|
||||||
├── Clonezilla -> vmlinuz/initrd -> Disk cloning/imaging
|
├── Clonezilla -> vmlinuz/initrd -> Disk cloning/imaging
|
||||||
├── Blancco -> Native kernel + initramfs -> NIST 800-88 drive erasure (auto-reports)
|
├── Blancco -> Native kernel + initramfs -> NIST 800-88 drive erasure (auto-reports)
|
||||||
└── Memtest86+ -> Memory diagnostics
|
(memory test: Dell F12 > Diagnostics)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Services on the PXE Server
|
### Services on the PXE Server
|
||||||
|
|
||||||
| 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) |
|
||||||
| Samba | 445/tcp | Deployment content + backup + reports |
|
| Samba | 445/tcp | Deployment content + backup + reports |
|
||||||
| Flask Webapp| 9009/tcp | Web management interface |
|
| Flask Webapp| 9009/tcp | Web management interface |
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
### Hardware
|
### Hardware
|
||||||
- Server or PC with >= 8 GB RAM, >= 250 GB disk, one wired NIC
|
- Server or PC with >= 8 GB RAM, >= 250 GB disk, one wired NIC
|
||||||
- USB thumb drive >= 8 GB (32+ GB if bundling WinPE images)
|
- USB thumb drive >= 8 GB (32+ GB if bundling WinPE images)
|
||||||
|
|
||||||
### Software (on your workstation)
|
### Software (on your workstation)
|
||||||
- Ubuntu Server 24.04 ISO — https://ubuntu.com/download/server
|
- Ubuntu Server 24.04 ISO — https://ubuntu.com/download/server
|
||||||
- Linux workstation (Ubuntu/Mint) for running download and build scripts
|
- Linux workstation (Ubuntu/Mint) for running download and build scripts
|
||||||
- GE Aerospace Media Creator LITE (for caching WinPE images)
|
- GE Aerospace Media Creator LITE (for caching WinPE images)
|
||||||
|
|
||||||
### GE Access Packages
|
### GE Access Packages
|
||||||
- EPM Rufus Exception Request
|
- EPM Rufus Exception Request
|
||||||
- EPM DT Functions
|
- EPM DT Functions
|
||||||
- DLP - Encrypted Removable (USB) Long Term Access
|
- DLP - Encrypted Removable (USB) Long Term Access
|
||||||
|
|
||||||
## Setup Process
|
## Setup Process
|
||||||
|
|
||||||
### Step 1: Download Offline Packages (one-time, requires internet)
|
### Step 1: Download Offline Packages (one-time, requires internet)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/download-packages.sh
|
./scripts/download-packages.sh --iso /path/to/ubuntu-24.04.x-live-server-amd64.iso
|
||||||
```
|
./scripts/check-updates.py # expect: PASSED, 0 security updates
|
||||||
|
./scripts/fetch-ipxe.sh
|
||||||
Downloads all .deb packages (ansible, dnsmasq, apache2, samba, wimtools, etc.) into `offline-packages/` and Python wheels (flask, lxml) into `pip-wheels/`. Approximately 252 packages (~140 MB) + 8 Python wheels.
|
```
|
||||||
|
|
||||||
**Packages included:**
|
`fetch-ipxe.sh` downloads the Secure Boot iPXE chain into `boot-files/`, pinned by sha256: the iPXE release
|
||||||
- **Server:** dnsmasq, apache2, samba, ufw, cron
|
tarball (its snponly build becomes `ipxe.efi`) and iPXE's dual-signed shim (Microsoft UEFI CA 2011 + 2023),
|
||||||
- **Automation:** ansible
|
which every UEFI client boots as `ipxe-shim.efi`. Keep the old Broadcom-signed `boot-files/ipxe.efi` too: the
|
||||||
- **Tools:** wimtools, unzip, p7zip-full
|
playbook installs it as `ipxe-bcm.efi`, the rollback boot file.
|
||||||
- **Python:** python3-pip, python3-venv
|
|
||||||
- **Network:** network-manager, wpasupplicant, wireless-tools, linux-firmware
|
`download-packages.sh` needs docker (no sudo) and the SAME server ISO that
|
||||||
|
`build-usb.sh` will use. The install is offline (autoinstall uses a dead
|
||||||
### Step 2: Prepare Boot Tools (optional)
|
mirror on purpose), so the server's base is exactly that ISO's; the script
|
||||||
|
unpacks the ISO's install image into a docker image and resolves there:
|
||||||
```bash
|
every update of an installed package, the explicit package list
|
||||||
./scripts/prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin
|
(ansible, dnsmasq, apache2, samba, wimtools, zip, the HWE kernel, ...) and
|
||||||
```
|
the newest version of what the installer adds from the ISO's pool (kernel,
|
||||||
|
grub, openssh-server). `offline-packages/` becomes an apt repository
|
||||||
Extracts boot files for Blancco, Clonezilla, and Memtest86+ into the `boot-tools/` directory. Automatically patches Blancco's `config.img` to auto-save erasure reports to the PXE server's Samba share.
|
(debs + `Packages` + `explicit.list`), checked offline in a fresh container
|
||||||
|
of the base before the script finishes. On the server, autoinstall's
|
||||||
### Step 3: Build the USB
|
late-commands run `scripts/offline-apt-install.sh` on it: one apt
|
||||||
|
transaction from the local repo only, any error fails the install (log:
|
||||||
```bash
|
`/var/log/offline-apt-install.log`). The webapp's pinned, hashed wheels go
|
||||||
# Basic — server only (import WinPE images later)
|
to `pip-wheels/`. About 500 debs (~1.6 GB) + 8 wheels.
|
||||||
sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
|
|
||||||
|
**Packages included:**
|
||||||
# With WinPE images bundled (single USB, larger drive needed)
|
- **Server:** dnsmasq, apache2, samba, ufw, cron
|
||||||
sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04.iso /path/to/winpe-images
|
- **Automation:** ansible
|
||||||
```
|
- **Tools:** wimtools, unzip, p7zip-full
|
||||||
|
- **Python:** python3-pip, python3-venv
|
||||||
This creates a bootable USB with:
|
- **Network:** network-manager, wpasupplicant, wireless-tools, linux-firmware
|
||||||
- **Partition 1:** Ubuntu Server installer
|
|
||||||
- **Partition 2:** CIDATA (autoinstall config, offline .debs, pip wheels, Ansible playbook, webapp, boot tools)
|
### Step 2: Prepare Boot Tools (optional)
|
||||||
|
|
||||||
### Step 4: Boot the Target Machine
|
```bash
|
||||||
|
./scripts/prepare-boot-tools.sh /path/to/blancco.iso
|
||||||
1. Insert the USB into the target machine
|
```
|
||||||
2. Press F12 (or vendor boot key) and select the USB
|
|
||||||
3. Ubuntu auto-installs — no interaction needed
|
Extracts boot files for Blancco and Clonezilla (64-bit with Debian's signed shim, so it boots with Secure Boot on, plus the last 32-bit release for XP-era PCs) into the `boot-tools/` directory. Automatically patches Blancco's `config.img` to auto-save erasure reports to the PXE server's Samba share.
|
||||||
4. After reboot, the first-boot script installs all .deb packages and runs the Ansible playbook
|
|
||||||
5. PXE services (dnsmasq, Apache, Samba) are configured automatically
|
### Step 2b: Capture site content from a running server
|
||||||
6. Flask webapp starts on port 9009
|
|
||||||
|
```bash
|
||||||
### Step 5: Connect to Isolated Network
|
./scripts/capture-site-content.sh # dry run: lists what would change
|
||||||
|
./scripts/capture-site-content.sh --apply # pull it
|
||||||
Move the server's wired NIC to the isolated switch for PXE clients.
|
```
|
||||||
|
|
||||||
### Step 6: Import WinPE Content (if not bundled in Step 3)
|
Pulls what neither the repo nor the MCL image import brings, from a working PXE server (default 172.16.9.1, SSH key login as `pxe`): `_shared/Applications/extra` (Office, Adobe, OpenText, Java, fonts, Wi-Fi, Zscaler), the GE CA certificates and the Engineering Layer payload in each image's `Deploy/Applications`, and the links images keep there, into `site-content/`. It also refreshes `bios-staging/` (BIOS files and `models.txt`), `boot-files/boot.wim` and the Blancco files in `boot-tools/blancco/` from the server. It writes `site-content/SHA256SUMS`, which the playbook checks before restoring anything, and refuses content a FAT32 USB cannot hold. Without it a new server cannot build Standard or Engineer (`build-usb.sh` refuses unless you pass `--skip-site-content`).
|
||||||
|
|
||||||
**Option A:** Use the web interface at `http://10.9.100.1:9009` to import from USB.
|
The first MCL image import on the new server then seeds `_shared/Applications/BPRT`, adding our overlay scripts from the repo.
|
||||||
|
|
||||||
**Option B:** Manual copy:
|
### Step 3: Build the USB
|
||||||
```bash
|
|
||||||
sudo mkdir -p /mnt/usb2
|
```bash
|
||||||
sudo mount /dev/sdb2 /mnt/usb2
|
# Basic: server only (import WinPE images later). site-vars.yml holds the
|
||||||
sudo cp -r /mnt/usb2/. /srv/samba/winpeapps/gea-standard
|
# two NIC MACs (copy playbook/site-vars.example.yml); --site-vars or
|
||||||
sudo umount /mnt/usb2
|
# --no-site-vars is required.
|
||||||
```
|
sudo ./scripts/build-usb.sh --site-vars site-vars.yml /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
|
||||||
|
|
||||||
## Web Management Interface
|
# With WinPE images bundled (single USB, larger drive needed; no file may
|
||||||
|
# be 4 GiB or more, FAT32)
|
||||||
Access at `http://10.9.100.1:9009` from any machine on the isolated network.
|
sudo ./scripts/build-usb.sh --site-vars site-vars.yml /dev/sdX /path/to/ubuntu-24.04.iso /path/to/winpe-images
|
||||||
|
|
||||||
| Page | URL Path | Purpose |
|
# Same layout into a disk-image file, no root (dd it to a stick later, or
|
||||||
|-------------------|-------------|-----------------------------------------------|
|
# boot it in a VM: see tests/vm/README.md; on Proxmox: qm importdisk).
|
||||||
| Dashboard | / | Service status, disk usage, DHCP clients |
|
# SIZE is e.g. 16G or "auto".
|
||||||
| Image Import | /import | Import WinPE images from USB drives |
|
./scripts/build-usb.sh --site-vars site-vars.yml --image pxe-usb.img auto /path/to/ubuntu-24.04.iso
|
||||||
| Unattend Editor | /unattend | Edit Windows unattend.xml per image type |
|
```
|
||||||
| startnet.cmd | /startnet | Edit startnet.cmd inside boot.wim (wimtools) |
|
|
||||||
| Clonezilla Backups| /backups | Upload/download/manage disk backup images |
|
Options: `--payload-root DIR` takes offline-packages/, pip-wheels/,
|
||||||
| Blancco Reports | /reports | View/download drive erasure reports |
|
boot-files/, boot-tools/, enrollment/, bios-staging/ and drivers-staging/
|
||||||
| Image Config | /images/*/config | Per-image driver, OS, and hardware config |
|
from another checkout; `--skip-enrollment`, `--skip-bios`,
|
||||||
| Enrollment | /enrollment | GCCH bulk enrollment package management |
|
`--skip-drivers`, `--skip-boot-tools` leave those out; `--yes` skips the
|
||||||
| Audit Log | /audit | Activity history for all write operations |
|
prompt. The build refuses payloads that do not match their pins: the iPXE
|
||||||
|
files against the playbook's `ipxe_*_sha256`, each wheel against its own
|
||||||
## Verification
|
package's hashes in `webapp/requirements.txt` (no PyPI fallback), and the
|
||||||
|
ISO against the volume id `offline-packages/iso-volid` was resolved for. The ISO volume label and El Torito load sizes are read from the
|
||||||
1. Connect a test workstation to the isolated switch
|
ISO, so any 24.04.x point release works.
|
||||||
2. Set Network Boot (PXE) as first boot in BIOS/UEFI
|
|
||||||
3. Boot — the client should pull an IP from 10.9.100.x
|
This creates a bootable USB with:
|
||||||
4. iPXE loads, fetches the boot script from port 4433
|
- **Partition 1:** Ubuntu Server installer
|
||||||
5. Select an option from the boot menu:
|
- **Partition 2:** CIDATA (autoinstall config, offline .debs, pip wheels, Ansible playbook, webapp, boot tools)
|
||||||
- **Windows PE**: Boots via wimboot + boot.wim, maps Samba shares, begins deployment
|
|
||||||
- **Clonezilla**: Boots Clonezilla Live for disk imaging
|
#### Building the USB on a Windows PC
|
||||||
- **Blancco**: Boots Drive Eraser, auto-saves reports to server
|
|
||||||
- **Memtest86+**: Runs memory diagnostics
|
`scripts/Build-PxeUsb.ps1` builds the same stick on Windows, with the same
|
||||||
|
checks, no Linux tools needed. It writes ONE FAT32 partition labelled
|
||||||
## Testing with KVM
|
CIDATA holding the Ubuntu ISO's files (boot menu patched for autoinstall)
|
||||||
|
and the payload; it boots in UEFI mode only (Secure Boot works) and is at
|
||||||
```bash
|
most 32 GB (the FAT32 size Windows formats). Every file is read back and
|
||||||
# Download Ubuntu ISO
|
compared by SHA-256.
|
||||||
wget -O ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso \
|
|
||||||
https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso
|
On the build PC, put the repo and payload on the share:
|
||||||
|
|
||||||
# Launch test VM
|
```bash
|
||||||
sudo ./test-vm.sh ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso
|
./scripts/stage-usb-share.sh # -> ~/pxe-images/pxe-usb (hard links)
|
||||||
|
```
|
||||||
# Watch progress (Ctrl+] to detach)
|
|
||||||
sudo virsh console pxe-test
|
On the Windows PC, in an elevated PowerShell (Z: = the pxe-images share):
|
||||||
|
|
||||||
# After install: ssh pxe@10.9.100.1 / http://10.9.100.1:9009
|
```powershell
|
||||||
|
Z:\pxe-usb\scripts\Build-PxeUsb.ps1 # lists USB disks
|
||||||
# Clean up
|
Z:\pxe-usb\scripts\Build-PxeUsb.ps1 -DiskNumber 3 -NoSiteVars # or -SiteVars FILE
|
||||||
sudo ./test-vm.sh --destroy
|
```
|
||||||
```
|
|
||||||
|
It refuses a disk that is not on the USB bus or is the boot/system disk,
|
||||||
## Project Structure
|
and asks you to type the disk number before erasing it.
|
||||||
|
|
||||||
```
|
### Step 4: Boot the Target Machine
|
||||||
pxe-server/
|
|
||||||
├── autoinstall/
|
1. Insert the USB into the target machine
|
||||||
│ ├── user-data # Cloud-init autoinstall + first-boot script
|
2. Press F12 (or vendor boot key) and select the USB
|
||||||
│ └── meta-data # Cloud-init metadata (required, empty)
|
3. Ubuntu auto-installs — no interaction needed
|
||||||
├── playbook/
|
4. After reboot, the first-boot script runs the Ansible playbook (the .debs
|
||||||
│ ├── pxe_server_setup.yml # Ansible: dnsmasq, Apache, Samba, iPXE, UFW, webapp
|
were installed by the installer); it pins the two NICs by MAC
|
||||||
│ ├── inventory.ini # Ansible inventory
|
5. PXE services (dnsmasq, Apache, Samba) are configured automatically
|
||||||
│ ├── startnet.cmd # WinPE startup script (injected into boot.wim)
|
6. Flask webapp starts on port 9009
|
||||||
│ ├── blancco-init.sh # Custom initramfs for Blancco PXE boot
|
7. If the playbook fails, `/var/lib/pxe-firstboot.failed` and a banner on
|
||||||
│ ├── blancco-preferences.xml # Blancco auto-report preferences
|
the console and at SSH login point at `/var/log/first-boot.log`; first
|
||||||
│ ├── check-bios.cmd # Pre-imaging BIOS update checker
|
boot retries at the next reboot (5 attempts)
|
||||||
│ ├── FlatUnattendW10-shopfloor.xml # Shopfloor unattend.xml
|
|
||||||
│ └── shopfloor-setup/ # PC type setup scripts (Standard, CMM, Display, etc.)
|
### Step 5: Connect to Isolated Network
|
||||||
├── webapp/
|
|
||||||
│ ├── app.py # Flask application (~1600 lines)
|
During the install plug in ONLY the imaging switch (GE unplugged). With
|
||||||
│ ├── requirements.txt # Python deps (flask, lxml)
|
`pxe_nic_mac` empty in site-vars, the first boot picks the one wired port
|
||||||
│ ├── static/ # CSS, JS, fonts, logo (all bundled offline)
|
with a link, checks it is isolated (no GE switch, no 802.1X, no other DHCP
|
||||||
│ └── templates/ # Jinja2 HTML templates (10 pages)
|
server) and pins it. Then plug the GE cable into any other port: it becomes
|
||||||
├── docs/
|
a DHCP client for SSH and the webapp, and dnsmasq never serves it. If the
|
||||||
│ └── shopfloor-display-imaging-guide.md # End-user imaging guide
|
first boot cannot tell which port is the imaging one, the console says so:
|
||||||
├── boot-tools/ # Extracted boot files (gitignored, built by prepare-boot-tools.sh)
|
log in and run `sudo pxe-pick-nic`, pull the imaging cable and plug it back
|
||||||
│ ├── blancco/ # Blancco Drive Eraser
|
in. Sites that prefer to pin both ports by MAC set `pxe_nic_mac` and
|
||||||
│ ├── clonezilla/ # Clonezilla Live
|
`corp_nic_mac` instead; both cables can then be in from the start.
|
||||||
│ └── memtest/ # Memtest86+
|
|
||||||
├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD)
|
### Step 6: Import WinPE Content (if not bundled in Step 3)
|
||||||
├── offline-packages/ # .deb files (gitignored, built by download-packages.sh)
|
|
||||||
├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh)
|
**Option A:** Use the web interface at `http://172.16.9.1:9009` to import from USB.
|
||||||
├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored)
|
|
||||||
├── bios-staging/ # Dell BIOS update binaries (gitignored)
|
**Option B:** Manual copy:
|
||||||
├── scripts/ # Build, deploy, and helper scripts
|
```bash
|
||||||
│ ├── build-usb.sh # Builds the 2-partition installer USB
|
sudo mkdir -p /mnt/usb2
|
||||||
│ ├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO
|
sudo mount /dev/sdb2 /mnt/usb2
|
||||||
│ ├── prepare-boot-tools.sh # Extracts/patches boot tools from ISOs
|
sudo cp -r /mnt/usb2/. /srv/samba/winpeapps/gea-standard
|
||||||
│ ├── download-packages.sh # Downloads all offline packages
|
sudo umount /mnt/usb2
|
||||||
│ ├── download-drivers.py # Downloads Dell drivers from dell.com
|
```
|
||||||
│ ├── deploy-bios.sh # Pushes BIOS updates to enrollment share
|
|
||||||
│ ├── pull-bios.sh # Pulls BIOS binaries from upstream cache
|
## Web Management Interface
|
||||||
│ ├── sync_hardware_models.py # Syncs hardware model configs across images
|
|
||||||
│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB
|
Access at `http://172.16.9.1:9009` from any machine on the isolated network.
|
||||||
│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN
|
|
||||||
├── config/ # Site-specific configuration overrides
|
| Page | URL Path | Purpose |
|
||||||
├── startnet-template.cmd # startnet.cmd template (synced with playbook copy)
|
|-------------------|-------------|-----------------------------------------------|
|
||||||
├── README.md # Project overview
|
| Dashboard | / | Service status, disk usage, DHCP clients |
|
||||||
└── SETUP.md # Detailed setup guide (this file)
|
| Image Import | /import | Import WinPE images from USB drives |
|
||||||
```
|
| Unattend Editor | /unattend | Edit Windows unattend.xml per image type |
|
||||||
|
| startnet.cmd | /startnet | Edit startnet.cmd inside boot.wim (wimtools) |
|
||||||
## Image Types
|
| Clonezilla Backups| /backups | Upload/download/manage disk backup images |
|
||||||
|
| Blancco Reports | /reports | View/download drive erasure reports |
|
||||||
| Image Type | Domain | Description |
|
| Image Config | /images/*/config | Per-image driver, OS, and hardware config |
|
||||||
|----------------------|-----------------|-------------------------|
|
| Enrollment | /enrollment | GCCH bulk enrollment package management |
|
||||||
| gea-standard | geaerospace.com | Standard desktop |
|
| Audit Log | /audit | Activity history for all write operations |
|
||||||
| gea-engineer | geaerospace.com | Engineering desktop |
|
|
||||||
| gea-shopfloor | geaerospace.com | Shop floor kiosk |
|
## Verification
|
||||||
| ge-standard | ge.com | Standard desktop |
|
|
||||||
| ge-engineer | ge.com | Engineering desktop |
|
1. Connect a test workstation to the isolated switch
|
||||||
| ge-shopfloor-lockdown| ge.com | Shop floor (locked) |
|
2. Set Network Boot (PXE) as first boot in BIOS/UEFI
|
||||||
| ge-shopfloor-mce | ge.com | Shop floor (MCE) |
|
3. Boot — the client should pull an IP from 10.9.100.x
|
||||||
|
4. iPXE loads, fetches the boot script from port 4433
|
||||||
## Network Configuration
|
5. Select an option from the boot menu:
|
||||||
|
- **Windows PE**: Boots via wimboot + boot.wim, maps Samba shares, begins deployment
|
||||||
- PXE server static IP: `10.9.100.1/24`
|
- **Clonezilla**: Boots Clonezilla Live for disk imaging
|
||||||
- DHCP range: `10.9.100.10` - `10.9.100.100`
|
- **Blancco**: Boots Drive Eraser, auto-saves reports to server
|
||||||
- Lease time: 12 hours
|
- **Memory test**: not on the menu; Dell PCs have one built in (F12 at power-on, Diagnostics)
|
||||||
- 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
|
## Testing with KVM
|
||||||
|
|
||||||
## Samba Shares
|
```bash
|
||||||
|
# Download Ubuntu ISO
|
||||||
| Share | Path | Purpose |
|
wget -O ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso \
|
||||||
|-----------------|----------------------------|-------------------------------|
|
https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso
|
||||||
| winpeapps | /srv/samba/winpeapps | WinPE deployment images |
|
|
||||||
| clonezilla | /srv/samba/clonezilla | Clonezilla disk backup images |
|
# Launch test VM
|
||||||
| blancco-reports | /srv/samba/blancco-reports | Blancco erasure reports (auto)|
|
sudo ./test-vm.sh ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso
|
||||||
| enrollment | /srv/samba/enrollment | GCCH bulk enrollment packages |
|
|
||||||
| image-upload | /home/pxe/image-upload | Image upload staging area |
|
# Watch progress (Ctrl+] to detach)
|
||||||
|
sudo virsh console pxe-test
|
||||||
All shares use guest access for the isolated network.
|
|
||||||
|
# After install: ssh pxe@172.16.9.1 / http://172.16.9.1:9009
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
sudo ./test-vm.sh --destroy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
pxe-server/
|
||||||
|
├── autoinstall/
|
||||||
|
│ ├── user-data # Cloud-init autoinstall + first-boot script
|
||||||
|
│ └── meta-data # Cloud-init metadata (required, empty)
|
||||||
|
├── playbook/
|
||||||
|
│ ├── pxe_server_setup.yml # Ansible: dnsmasq, Apache, Samba, iPXE, UFW, webapp
|
||||||
|
│ ├── inventory.ini # Ansible inventory
|
||||||
|
│ ├── startnet.cmd # WinPE startup script (injected into boot.wim)
|
||||||
|
│ ├── blancco-init.sh # Custom initramfs for Blancco PXE boot
|
||||||
|
│ ├── blancco-preferences.xml # Blancco auto-report preferences
|
||||||
|
│ ├── check-bios.cmd # Pre-imaging BIOS update checker
|
||||||
|
│ ├── FlatUnattendW10-shopfloor.xml # Shopfloor unattend.xml
|
||||||
|
│ └── shopfloor-setup/ # PC type setup scripts (Standard, CMM, Display, etc.)
|
||||||
|
├── webapp/
|
||||||
|
│ ├── app.py # Flask application (~1600 lines)
|
||||||
|
│ ├── requirements.txt # Python deps (flask, lxml)
|
||||||
|
│ ├── static/ # CSS, JS, fonts, logo (all bundled offline)
|
||||||
|
│ └── templates/ # Jinja2 HTML templates (10 pages)
|
||||||
|
├── docs/
|
||||||
|
│ └── shopfloor-display-imaging-guide.md # End-user imaging guide
|
||||||
|
├── boot-tools/ # Extracted boot files (gitignored, built by prepare-boot-tools.sh)
|
||||||
|
│ ├── blancco/ # Blancco Drive Eraser
|
||||||
|
│ └── clonezilla/ # Clonezilla Live
|
||||||
|
├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD)
|
||||||
|
├── offline-packages/ # .deb files (gitignored, built by download-packages.sh)
|
||||||
|
├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh)
|
||||||
|
├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored)
|
||||||
|
├── bios-staging/ # Dell BIOS update binaries (gitignored)
|
||||||
|
├── scripts/ # Build, deploy, and helper scripts
|
||||||
|
│ ├── build-usb.sh # Builds the 2-partition installer USB
|
||||||
|
│ ├── Build-PxeUsb.ps1 # Windows: builds the installer USB (one FAT32 partition)
|
||||||
|
│ ├── stage-usb-share.sh # Puts repo + payload on the pxe-images share for it
|
||||||
|
│ ├── prepare-boot-tools.sh # Extracts/patches boot tools from ISOs
|
||||||
|
│ ├── download-packages.sh # Downloads all offline packages
|
||||||
|
│ ├── download-drivers.py # Downloads Dell drivers from dell.com
|
||||||
|
│ ├── capture-site-content.sh # Pulls site content + fresh payload from a live server
|
||||||
|
│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB
|
||||||
|
│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN
|
||||||
|
├── config/ # Site-specific configuration overrides
|
||||||
|
├── README.md # Project overview
|
||||||
|
└── SETUP.md # Detailed setup guide (this file)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Image Types
|
||||||
|
|
||||||
|
| Image Type | Domain | Description |
|
||||||
|
|----------------------|-----------------|-------------------------|
|
||||||
|
| gea-standard | geaerospace.com | Standard desktop |
|
||||||
|
| gea-engineer | geaerospace.com | Engineering desktop |
|
||||||
|
| gea-shopfloor | geaerospace.com | Shop floor kiosk |
|
||||||
|
| ge-standard | ge.com | Standard desktop |
|
||||||
|
| ge-engineer | ge.com | Engineering desktop |
|
||||||
|
| ge-shopfloor-lockdown| ge.com | Shop floor (locked) |
|
||||||
|
| ge-shopfloor-mce | ge.com | Shop floor (MCE) |
|
||||||
|
|
||||||
|
## Network Configuration
|
||||||
|
|
||||||
|
- PXE server static IP: `172.16.9.1/24`
|
||||||
|
- DHCP range: `172.16.9.10` - `172.16.9.100`
|
||||||
|
- Lease time: 12 hours
|
||||||
|
- 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
|
||||||
|
|
||||||
|
## Samba Shares
|
||||||
|
|
||||||
|
| Share | Path | Purpose |
|
||||||
|
|-----------------|----------------------------|-------------------------------|
|
||||||
|
| winpeapps | /srv/samba/winpeapps | WinPE deployment images |
|
||||||
|
| clonezilla | /srv/samba/clonezilla | Clonezilla disk backup images |
|
||||||
|
| blancco-reports | /srv/samba/blancco-reports | Blancco erasure reports (auto)|
|
||||||
|
| enrollment | /srv/samba/enrollment | GCCH bulk enrollment packages |
|
||||||
|
| image-upload | /home/pxe/image-upload | Image upload staging area |
|
||||||
|
|
||||||
|
All shares use guest access for the isolated network.
|
||||||
|
|||||||
@@ -9,19 +9,40 @@ autoinstall:
|
|||||||
variant: ""
|
variant: ""
|
||||||
timezone: America/New_York
|
timezone: America/New_York
|
||||||
|
|
||||||
# Network: static IP for isolated PXE LAN (no internet/DHCP needed)
|
# Network: DHCP only, on every wired NIC. On the standard two-NIC box the
|
||||||
|
# GE corporate NIC gets an address and the imaging NIC (no DHCP server on
|
||||||
|
# the imaging LAN) stays without one. No static 172.16.9.1 here: matched
|
||||||
|
# by name it would land on EVERY NIC, the GE one included, and the box
|
||||||
|
# would answer as 172.16.9.1 on the corporate network. First boot's
|
||||||
|
# playbook replaces this file with the imaging NIC pinned by MAC
|
||||||
|
# (pxe_nic_mac in playbook/site-vars.yml, or picked by its link when
|
||||||
|
# empty; corp_nic_mac optional). optional: the
|
||||||
|
# install and boot never wait for a lease (air-gapped is fine).
|
||||||
network:
|
network:
|
||||||
version: 2
|
version: 2
|
||||||
ethernets:
|
ethernets:
|
||||||
any-eth:
|
any-eth:
|
||||||
match:
|
match:
|
||||||
name: "en*"
|
name: "en*"
|
||||||
addresses:
|
dhcp4: true
|
||||||
- 10.9.100.1/24
|
|
||||||
dhcp4: false
|
|
||||||
dhcp6: false
|
dhcp6: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
# The install is always offline, even when the corp NIC has a lease and
|
||||||
|
# internet: the mirror points at a closed local port, so subiquity's
|
||||||
|
# mirror check fails and it installs from the ISO alone (no "security
|
||||||
|
# updates" step from the archive). The base is then exactly the ISO's,
|
||||||
|
# which is what download-packages.sh resolved the offline set against;
|
||||||
|
# late-commands patch it from CIDATA. The real archive sources are
|
||||||
|
# written back at the end of late-commands.
|
||||||
|
apt:
|
||||||
|
geoip: false
|
||||||
|
preserve_sources_list: false
|
||||||
|
primary:
|
||||||
|
- arches: [default]
|
||||||
|
uri: "http://127.0.0.1:9/ubuntu"
|
||||||
|
fallback: offline-install
|
||||||
|
|
||||||
# Storage configuration
|
# Storage configuration
|
||||||
storage:
|
storage:
|
||||||
layout:
|
layout:
|
||||||
@@ -44,34 +65,63 @@ autoinstall:
|
|||||||
|
|
||||||
# Installer-stage late commands
|
# Installer-stage late commands
|
||||||
late-commands:
|
late-commands:
|
||||||
# Step 1: Install deb packages from CIDATA USB
|
# Step 1: Upgrade and install from the offline set on CIDATA
|
||||||
|
# (packages/ is an apt repo: debs + Packages + explicit.list). Any apt
|
||||||
|
# error fails the install; the log is /var/log/offline-apt-install.log.
|
||||||
- |
|
- |
|
||||||
curtin in-target --target=/target -- bash -c '
|
curtin in-target --target=/target -- bash -euc '
|
||||||
mkdir -p /mnt/cidata
|
mkdir -p /mnt/cidata
|
||||||
CIDATA_DEV=$(blkid -L CIDATA)
|
CIDATA_DEV=$(blkid -L CIDATA)
|
||||||
if [ -n "$CIDATA_DEV" ]; then
|
mount -o ro "$CIDATA_DEV" /mnt/cidata
|
||||||
mount "$CIDATA_DEV" /mnt/cidata
|
set +e
|
||||||
if compgen -G "/mnt/cidata/packages/*.deb" > /dev/null; then
|
bash /mnt/cidata/offline-apt-install.sh /mnt/cidata/packages 2>&1 | tee /var/log/offline-apt-install.log
|
||||||
cp /mnt/cidata/packages/*.deb /tmp/
|
rc=${PIPESTATUS[0]}
|
||||||
dpkg -i /tmp/*.deb 2>/dev/null || true
|
set -e
|
||||||
dpkg -i /tmp/*.deb 2>/dev/null || true
|
umount /mnt/cidata
|
||||||
dpkg --configure -a 2>/dev/null || true
|
if [ "$rc" -ne 0 ]; then
|
||||||
if command -v nmcli >/dev/null; then
|
echo "offline package install FAILED (rc=$rc), see /var/log/offline-apt-install.log" >&2
|
||||||
systemctl enable NetworkManager
|
exit "$rc"
|
||||||
fi
|
fi
|
||||||
fi
|
if command -v nmcli >/dev/null; then
|
||||||
umount /mnt/cidata
|
systemctl enable NetworkManager
|
||||||
fi
|
fi
|
||||||
'
|
'
|
||||||
|
|
||||||
# Step 2: Disable networkd-wait-online (hangs on air-gapped network)
|
# Step 2: Disable networkd-wait-online (hangs on air-gapped network)
|
||||||
- ln -sf /dev/null /target/etc/systemd/system/systemd-networkd-wait-online.service
|
- ln -sf /dev/null /target/etc/systemd/system/systemd-networkd-wait-online.service
|
||||||
|
|
||||||
# Step 3: Create first-boot.sh
|
# Step 3: Create first-boot.sh. rc.local runs it on every boot until the
|
||||||
|
# playbook succeeds (at most MAX attempts). A failure leaves
|
||||||
|
# /var/lib/pxe-firstboot.failed and a banner on the console (/etc/issue)
|
||||||
|
# and at SSH login (/etc/motd) pointing at /var/log/first-boot.log.
|
||||||
- |
|
- |
|
||||||
curtin in-target --target=/target -- bash -c '
|
curtin in-target --target=/target -- bash -c '
|
||||||
cat <<"EOF" > /opt/first-boot.sh
|
cat <<"EOF" > /opt/first-boot.sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
MAX=5
|
||||||
|
STATE=/var/lib/pxe-firstboot
|
||||||
|
FAILED=/var/lib/pxe-firstboot.failed
|
||||||
|
mkdir -p "$STATE"
|
||||||
|
N=$(( $(cat "$STATE/attempts" 2>/dev/null || echo 0) + 1 ))
|
||||||
|
echo "$N" > "$STATE/attempts"
|
||||||
|
echo "=== first-boot attempt $N of $MAX, $(date -Is) ==="
|
||||||
|
banner_off() {
|
||||||
|
for f in /etc/issue /etc/motd; do
|
||||||
|
[ -f "$f" ] && sed -i "/^### PXE FIRST-BOOT BEGIN/,/^### PXE FIRST-BOOT END/d" "$f"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
banner_on() {
|
||||||
|
banner_off
|
||||||
|
for f in /etc/issue /etc/motd; do
|
||||||
|
{
|
||||||
|
echo "### PXE FIRST-BOOT BEGIN"
|
||||||
|
echo "!!! PXE server setup (first-boot playbook) FAILED: $1"
|
||||||
|
echo "!!! Log: /var/log/first-boot.log Marker: $FAILED"
|
||||||
|
echo "!!! $2"
|
||||||
|
echo "### PXE FIRST-BOOT END"
|
||||||
|
} >> "$f"
|
||||||
|
done
|
||||||
|
}
|
||||||
# Expand root LV to full disk BEFORE playbook (playbook copies ~130GB of drivers+PPKGs)
|
# Expand root LV to full disk BEFORE playbook (playbook copies ~130GB of drivers+PPKGs)
|
||||||
ROOT_DEV=$(findmnt -n -o SOURCE /)
|
ROOT_DEV=$(findmnt -n -o SOURCE /)
|
||||||
ROOT_DISK=$(lsblk -n -o PKNAME "$(readlink -f "$ROOT_DEV")" | tail -1)
|
ROOT_DISK=$(lsblk -n -o PKNAME "$(readlink -f "$ROOT_DEV")" | tail -1)
|
||||||
@@ -82,37 +132,79 @@ autoinstall:
|
|||||||
pvresize "$PV_PART" 2>&1 || true
|
pvresize "$PV_PART" 2>&1 || true
|
||||||
fi
|
fi
|
||||||
lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv 2>&1 || true
|
lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv 2>&1 || true
|
||||||
|
RC=1
|
||||||
|
WHY="CIDATA (the installer USB) not found"
|
||||||
CIDATA_DEV=$(blkid -L CIDATA)
|
CIDATA_DEV=$(blkid -L CIDATA)
|
||||||
if [ -n "$CIDATA_DEV" ]; then
|
if [ -n "$CIDATA_DEV" ]; then
|
||||||
mkdir -p /mnt/usb
|
mkdir -p /mnt/usb
|
||||||
mount "$CIDATA_DEV" /mnt/usb
|
if mount "$CIDATA_DEV" /mnt/usb; then
|
||||||
if compgen -G "/mnt/usb/packages/*.deb" > /dev/null; then
|
if [ -f /mnt/usb/playbook/pxe_server_setup.yml ]; then
|
||||||
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
|
cd /mnt/usb/playbook
|
||||||
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
|
rm -f "$STATE/nic-hint"
|
||||||
dpkg --configure -a 2>/dev/null || true
|
ansible-playbook -i localhost, -c local pxe_server_setup.yml
|
||||||
|
RC=$?
|
||||||
|
WHY="ansible-playbook exit code $RC"
|
||||||
|
# pxe-pick-nic leaves the reason it could not pick the imaging NIC
|
||||||
|
[ "$RC" -ne 0 ] && [ -s "$STATE/nic-hint" ] && WHY="$(head -1 "$STATE/nic-hint")"
|
||||||
|
cd /
|
||||||
|
else
|
||||||
|
WHY="no playbook/pxe_server_setup.yml on CIDATA"
|
||||||
|
fi
|
||||||
|
umount /mnt/usb
|
||||||
|
else
|
||||||
|
WHY="could not mount CIDATA ($CIDATA_DEV)"
|
||||||
fi
|
fi
|
||||||
if [ -f /mnt/usb/playbook/pxe_server_setup.yml ]; then
|
|
||||||
cd /mnt/usb/playbook
|
|
||||||
ansible-playbook -i localhost, -c local pxe_server_setup.yml
|
|
||||||
fi
|
|
||||||
umount /mnt/usb
|
|
||||||
fi
|
fi
|
||||||
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
|
if [ "$RC" -eq 0 ]; then
|
||||||
|
echo "=== first-boot: playbook succeeded on attempt $N ==="
|
||||||
|
rm -f "$FAILED"
|
||||||
|
banner_off
|
||||||
|
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "=== first-boot: FAILED ($WHY), attempt $N of $MAX ==="
|
||||||
|
printf "attempt=%s of %s\nwhen=%s\nreason=%s\nlog=/var/log/first-boot.log\n" "$N" "$MAX" "$(date -Is)" "$WHY" > "$FAILED"
|
||||||
|
if [ "$N" -ge "$MAX" ]; then
|
||||||
|
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
|
||||||
|
banner_on "$WHY (attempt $N of $MAX)" "Gave up retrying. Fix it, then run: sudo /opt/first-boot.sh after rm $STATE/attempts"
|
||||||
|
else
|
||||||
|
banner_on "$WHY (attempt $N of $MAX)" "It runs again at the next boot (fix the cause first, e.g. playbook/site-vars.yml on the USB)."
|
||||||
|
fi
|
||||||
|
exit "$RC"
|
||||||
EOF
|
EOF
|
||||||
chmod +x /opt/first-boot.sh
|
chmod +x /opt/first-boot.sh
|
||||||
'
|
'
|
||||||
|
|
||||||
# Step 4: Create rc.local
|
# Step 4: Create rc.local (the log is appended to, one block per attempt)
|
||||||
- |
|
- |
|
||||||
curtin in-target --target=/target -- bash -c '
|
curtin in-target --target=/target -- bash -c '
|
||||||
cat <<"EOF" > /etc/rc.local
|
cat <<"EOF" > /etc/rc.local
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
/opt/first-boot.sh > /var/log/first-boot.log 2>&1 &
|
/opt/first-boot.sh >> /var/log/first-boot.log 2>&1 &
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
chmod +x /etc/rc.local
|
chmod +x /etc/rc.local
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Step 5: Real archive sources for later online maintenance (the
|
||||||
|
# install used a dead mirror on purpose, see apt: above).
|
||||||
|
- |
|
||||||
|
rm -f /target/etc/apt/sources.list.d/*.list /target/etc/apt/sources.list.d/*.curtin.orig
|
||||||
|
echo "# Ubuntu sources have moved to /etc/apt/sources.list.d/ubuntu.sources" > /target/etc/apt/sources.list
|
||||||
|
cat > /target/etc/apt/sources.list.d/ubuntu.sources <<"EOF"
|
||||||
|
Types: deb
|
||||||
|
URIs: http://archive.ubuntu.com/ubuntu/
|
||||||
|
Suites: noble noble-updates noble-backports
|
||||||
|
Components: main restricted universe multiverse
|
||||||
|
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||||
|
|
||||||
|
Types: deb
|
||||||
|
URIs: http://security.ubuntu.com/ubuntu/
|
||||||
|
Suites: noble-security
|
||||||
|
Components: main restricted universe multiverse
|
||||||
|
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
|
||||||
|
EOF
|
||||||
|
|
||||||
user-data:
|
user-data:
|
||||||
disable_root: false
|
disable_root: false
|
||||||
|
|
||||||
|
|||||||
14
boot-tools/blancco/blancco-chain.ipxe
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!ipxe
|
||||||
|
|
||||||
|
dhcp
|
||||||
|
echo SAN booting Blancco ISO...
|
||||||
|
sanboot http://172.16.9.1/blancco/blancco.iso || goto failed
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:failed
|
||||||
|
echo
|
||||||
|
echo FAILED!
|
||||||
|
prompt Press any key for iPXE shell...
|
||||||
|
shell
|
||||||
|
|
||||||
|
:end
|
||||||
48
boot-tools/blancco/blancco-debug.ipxe
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!ipxe
|
||||||
|
|
||||||
|
echo =============================================
|
||||||
|
echo Blancco PXE Debug Boot
|
||||||
|
echo =============================================
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo [1/4] Network configuration...
|
||||||
|
dhcp || echo DHCP FAILED
|
||||||
|
echo MAC: ${net0/mac}
|
||||||
|
echo IP: ${net0/ip}
|
||||||
|
echo GW: ${net0/gateway}
|
||||||
|
echo DNS: ${net0/dns}
|
||||||
|
echo
|
||||||
|
|
||||||
|
set server 172.16.9.1
|
||||||
|
|
||||||
|
echo [2/4] Testing HTTP connectivity...
|
||||||
|
imgfetch --name test http://${server}/blancco/config.img || echo HTTP FETCH FAILED
|
||||||
|
imgfree test
|
||||||
|
echo HTTP to ${server}: OK
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo [3/4] Loading kernel and initrd...
|
||||||
|
echo Fetching vmlinuz-bde-linux...
|
||||||
|
kernel http://${server}/blancco/vmlinuz-bde-linux initrd=initrd-combined.img archisobasedir=arch archiso_http_srv=http://${server}/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp libata.allow_tpm=1 modprobe.blacklist=iwlwifi,iwlmvm,btusb rd.udev.timeout=10 || goto failed
|
||||||
|
echo Kernel loaded OK.
|
||||||
|
echo
|
||||||
|
echo Fetching initrd-combined.img (43MB, may take a moment)...
|
||||||
|
initrd http://${server}/blancco/initrd-combined.img || goto failed
|
||||||
|
echo Initrd loaded OK.
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo [4/4] About to call boot command...
|
||||||
|
echo
|
||||||
|
echo !! Note the LAST kernel line visible before any freeze !!
|
||||||
|
echo
|
||||||
|
prompt Press any key to boot (or Ctrl-C for iPXE shell)... && goto doboot || shell
|
||||||
|
|
||||||
|
:doboot
|
||||||
|
boot || goto failed
|
||||||
|
|
||||||
|
:failed
|
||||||
|
echo
|
||||||
|
echo !! BOOT FAILED !!
|
||||||
|
echo
|
||||||
|
prompt Press any key for iPXE shell...
|
||||||
|
shell
|
||||||
17
boot-tools/blancco/blancco.ipxe
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!ipxe
|
||||||
|
|
||||||
|
dhcp
|
||||||
|
set server 172.16.9.1
|
||||||
|
|
||||||
|
echo Loading Blancco kernel...
|
||||||
|
kernel http://${server}/blancco/vmlinuz-bde-linux initrd=initrd-combined.img archisobasedir=arch archiso_http_srv=http://${server}/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp libata.allow_tpm=1 modprobe.blacklist=iwlwifi,iwlmvm,btusb rd.udev.timeout=10 nomodeset || goto failed
|
||||||
|
echo Loading initrd (combined)...
|
||||||
|
initrd http://${server}/blancco/initrd-combined.img || goto failed
|
||||||
|
echo All files loaded. Booting now...
|
||||||
|
boot || goto failed
|
||||||
|
|
||||||
|
:failed
|
||||||
|
echo
|
||||||
|
echo Blancco boot FAILED.
|
||||||
|
prompt Press any key to drop to iPXE shell...
|
||||||
|
shell
|
||||||
@@ -3,22 +3,10 @@ set timeout=0
|
|||||||
|
|
||||||
insmod efinet
|
insmod efinet
|
||||||
insmod net
|
insmod net
|
||||||
insmod http
|
|
||||||
insmod tftp
|
insmod tftp
|
||||||
net_bootp
|
net_bootp
|
||||||
|
|
||||||
# Blancco via Ubuntu-kernel switch_root. This is the cmdline that produces
|
|
||||||
# the slim Ubuntu-kernel-chain grubx64.efi. DO NOT flip this back to
|
|
||||||
# vmlinuz-bde-linux / archiso_http_srv / copytoram=y - that was the Apr-14
|
|
||||||
# regression (commit d6776f7) that put us into Blancco's narrow-NIC-driver
|
|
||||||
# archiso path and hung on Dell Precision hardware. The Ubuntu kernel path
|
|
||||||
# with our verbose, full-drivers/net/-tree kexec-initrd.img is what works.
|
|
||||||
#
|
|
||||||
# kexec-initrd.img is built by the pxe_server_setup.yml "Build Blancco PXE
|
|
||||||
# initramfs" task (sweeps drivers/net/ + depmod). blancco-init.sh inside it
|
|
||||||
# handles the rest: modprobe all common NICs, DHCP, download airootfs.sfs,
|
|
||||||
# overlay mount, switch_root.
|
|
||||||
menuentry "Blancco Drive Eraser" {
|
menuentry "Blancco Drive Eraser" {
|
||||||
linux (http,10.9.100.1)/blancco/vmlinuz-ubuntu ip=dhcp
|
linux (tftp,172.16.9.1)/blancco/vmlinuz-bde-linux archisobasedir=arch archiso_http_srv=http://172.16.9.1/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp libata.allow_tpm=1 modprobe.blacklist=iwlwifi,iwlmvm,btusb rd.udev.timeout=10
|
||||||
initrd (http,10.9.100.1)/blancco/kexec-initrd.img
|
initrd (tftp,172.16.9.1)/blancco/intel-ucode.img (tftp,172.16.9.1)/blancco/amd-ucode.img (tftp,172.16.9.1)/blancco/config.img (tftp,172.16.9.1)/blancco/initramfs-bde-linux.img
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,7 +29,7 @@ systems.
|
|||||||
|---------------|-----------|------------------------------------------------------|
|
|---------------|-----------|------------------------------------------------------|
|
||||||
| dnsmasq | 67/udp | DHCP. Hands out IPs and the next-server (TFTP) info. |
|
| dnsmasq | 67/udp | DHCP. Hands out IPs and the next-server (TFTP) info. |
|
||||||
| dnsmasq | 69/udp | TFTP. Serves `ipxe.efi` to firmware PXE clients. |
|
| dnsmasq | 69/udp | TFTP. Serves `ipxe.efi` to firmware PXE clients. |
|
||||||
| Apache | 80/tcp | HTTP. Serves wimboot, boot.wim, Blancco binaries, Clonezilla, memtest, and proxies the webapp on /. |
|
| Apache | 80/tcp | HTTP. Serves wimboot, boot.wim, Blancco binaries, Clonezilla, and proxies the webapp on /. |
|
||||||
| Apache | 4433/tcp | HTTP. Serves `Altiris/iPXE/GetPxeScript.aspx` (the iPXE menu script). |
|
| Apache | 4433/tcp | HTTP. Serves `Altiris/iPXE/GetPxeScript.aspx` (the iPXE menu script). |
|
||||||
| Samba | 445/tcp | SMB shares. WinPE mounts these to fetch image content during deployment. |
|
| Samba | 445/tcp | SMB shares. WinPE mounts these to fetch image content during deployment. |
|
||||||
| Flask webapp | 9009/tcp | Browser UI for image import, unattend editing, log/report viewing. |
|
| Flask webapp | 9009/tcp | Browser UI for image import, unattend editing, log/report viewing. |
|
||||||
@@ -112,9 +112,23 @@ WinPE; the Blancco and Clonezilla utilities require Secure Boot disabled.
|
|||||||
|
|
||||||
c. Clonezilla Live:
|
c. Clonezilla Live:
|
||||||
iPXE loads vmlinuz + initrd over HTTP, fetches filesystem.squashfs.
|
iPXE loads vmlinuz + initrd over HTTP, fetches filesystem.squashfs.
|
||||||
|
With Secure Boot on, the Debian-signed kernel starts through Debian's
|
||||||
|
Microsoft-signed shim (clonezilla/shimx64.efi, iPXE shim command).
|
||||||
|
The menu checks the CPU (iPXE cpuid --ext 29): 64-bit CPUs get the
|
||||||
|
current release, 32-bit-only CPUs (XP-era Pentium 4/M, early Atom) get
|
||||||
|
clonezilla-i686/ (3.2.0-5, the last 32-bit Clonezilla) over legacy BIOS.
|
||||||
|
"Clonezilla: back up / restore this PC" ask for the machine number,
|
||||||
|
mount \\<server>\clonezilla as the clonezilla Samba user
|
||||||
|
(ocs_repository smb3://, password from clonezilla_smb_password) and
|
||||||
|
run clonezilla-scripts/pxe-cz.sh (ocs_prerun downloads it,
|
||||||
|
ocs_live_run runs it): images are <machine>-<YYYY-MM-DD-HHMM> with
|
||||||
|
pxe-meta.txt, checked after saving; restore lists that machine's
|
||||||
|
images and restores with -k1 -r (larger disks get proportionally
|
||||||
|
larger partitions). "Clonezilla: manual" is the interactive mode.
|
||||||
|
|
||||||
d. Memtest86+:
|
Memory testing is not on the menu: Dell PCs have a built-in test
|
||||||
iPXE loads memtest.efi.
|
(F12 at power-on, Diagnostics). Memtest86+ was removed because it is
|
||||||
|
unsigned and needed Secure Boot off.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Windows imaging lifecycle (gea-* images)
|
## Windows imaging lifecycle (gea-* images)
|
||||||
@@ -166,7 +180,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)
|
||||||
|
|
|
|
||||||
@@ -179,9 +193,7 @@ PXE clients (UEFI -> iPXE -> Blancco) -+--> Drive erasure + report upload
|
|||||||
## Where things live
|
## Where things live
|
||||||
|
|
||||||
- Ansible playbook: `playbook/pxe_server_setup.yml`. Single file, idempotent.
|
- Ansible playbook: `playbook/pxe_server_setup.yml`. Single file, idempotent.
|
||||||
- WinPE startup script (template): `playbook/startnet.cmd` (canonical) and
|
- WinPE startup script: `playbook/startnet.cmd`. The playbook injects this into `Windows/System32/startnet.cmd` inside the
|
||||||
`startnet-template.cmd` (mirror copy at repo root for editor convenience).
|
|
||||||
The playbook injects this into `Windows/System32/startnet.cmd` inside the
|
|
||||||
`boot.wim` it deploys to `/var/www/html/win11/sources/`.
|
`boot.wim` it deploys to `/var/www/html/win11/sources/`.
|
||||||
- iPXE menu script source: embedded in the playbook (`Create GetPxeScript.aspx`
|
- iPXE menu script source: embedded in the playbook (`Create GetPxeScript.aspx`
|
||||||
task); deployed to `/var/www/html/Altiris/iPXE/GetPxeScript.aspx`.
|
task); deployed to `/var/www/html/Altiris/iPXE/GetPxeScript.aspx`.
|
||||||
|
|||||||
95
docs/OWNERSHIP.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Who owns what
|
||||||
|
|
||||||
|
Every bug that cost a day in this pipeline has had the same shape: two systems
|
||||||
|
both setting the same thing, and whichever ran last silently won. This file says
|
||||||
|
who owns each concern so the next person deletes the loser instead of adding a
|
||||||
|
third writer.
|
||||||
|
|
||||||
|
All four examples below are real, from 2026-08-06.
|
||||||
|
|
||||||
|
## Computer name - the PPKG owns it
|
||||||
|
|
||||||
|
The provisioning package declares:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<DNSComputerName>F%SERIAL%</DNSComputerName>
|
||||||
|
```
|
||||||
|
|
||||||
|
so a bay comes up as `F<serial>`.
|
||||||
|
|
||||||
|
`run-enrollment.ps1` used to also run `Rename-Computer -NewName "E$serial"`.
|
||||||
|
Both are pending renames; last writer wins at reboot. The script ran twice, and
|
||||||
|
its second run landed after the package had already queued `F579C144`, so the
|
||||||
|
bay came up `E579C144` with the package reporting no errors at all.
|
||||||
|
|
||||||
|
**Rule:** nothing in this repo renames a machine. If the naming convention
|
||||||
|
changes, it changes in the package.
|
||||||
|
|
||||||
|
## Drive letters - PESetup owns them
|
||||||
|
|
||||||
|
PESetup hardcodes `W:` in nine places - every copy destination, both DISM
|
||||||
|
offline sessions, `bcdboot`, `reagentc` - and creates it during its own disk
|
||||||
|
preparation.
|
||||||
|
|
||||||
|
`startnet.cmd` briefly had a volume finder that scanned for "the applied
|
||||||
|
Windows volume" and `diskpart`-assigned it to `W:`. On a re-image it found the
|
||||||
|
*previous* install, relabelled a partition PESetup was about to erase, and every
|
||||||
|
staging copy failed into a volume that no longer existed.
|
||||||
|
|
||||||
|
**Rule:** wait for `W:\Windows\System32\config\system` - the hive only exists
|
||||||
|
once the WIM apply has written it. Never run `diskpart` while PESetup is
|
||||||
|
running.
|
||||||
|
|
||||||
|
See `docs/PESETUP-INTERNALS.md`.
|
||||||
|
|
||||||
|
## Enrollment - the PPKG owns it, the orchestrator drives reboots
|
||||||
|
|
||||||
|
The SFLD package joins Entra using the BPRT token in
|
||||||
|
`0__Accounts_Azure.provxml`. A human then assigns the device category in Intune.
|
||||||
|
|
||||||
|
The vendor's `Start-BulkEnrollOrchestrator.ps1` has two branches. The normal one
|
||||||
|
aborts the package's own reboot, registers `AutoSecondReboot`, and drives the
|
||||||
|
Entra join to completion. The `-ManualFallback` one runs `sysprep /oobe /reboot`
|
||||||
|
- it is an interactive escape hatch for handing a machine back to OOBE, meant to
|
||||||
|
be triggered by a person.
|
||||||
|
|
||||||
|
The shopfloor unattend registered `-ManualFallback` as an at-logon scheduled
|
||||||
|
task. So OOBE completed, autologon fired, and four seconds later the machine
|
||||||
|
syspreped itself back to OOBE, losing the deployment chain permanently.
|
||||||
|
|
||||||
|
**Rule:** shopfloor bays enrol. Never wire `-ManualFallback` to anything
|
||||||
|
automatic.
|
||||||
|
|
||||||
|
## Kiosk URLs - GE-Enforce owns them
|
||||||
|
|
||||||
|
`plugins/geenforce/seed_display_scope.py` in shopdb-flask is authoritative:
|
||||||
|
|
||||||
|
| display-type.txt | route |
|
||||||
|
|---|---|
|
||||||
|
| `Dashboard` | `/shopdb/shopfloor` |
|
||||||
|
| `Lobby` | `/shopdb/tv` |
|
||||||
|
| `3DPrintRoom` | `/shopdb/parts-kiosk` |
|
||||||
|
|
||||||
|
Its dispatcher prefers the server-side role from Settings > Dashboard Defaults
|
||||||
|
(resolved by device IP), falls back to `C:\Enrollment\display-type.txt`, writes
|
||||||
|
the Startup shortcut itself, and **sweeps** any shortcut matching
|
||||||
|
`shopfloor-dashboard` or `/shopdb/`.
|
||||||
|
|
||||||
|
`site-config.json` also carries `edgeHomepage` per display type. Those are a
|
||||||
|
backstop for the window before the kiosk installer runs - a stale value there
|
||||||
|
gets deleted on the next enforce cycle rather than honoured.
|
||||||
|
|
||||||
|
**Rule:** if a kiosk points at the wrong page, fix the GE-Enforce scope first.
|
||||||
|
Keep `site-config.json` correct, but do not expect it to win.
|
||||||
|
|
||||||
|
## Repo vs share
|
||||||
|
|
||||||
|
The share is production; the repo is meant to describe it. Drift runs both ways -
|
||||||
|
live hand-edits nobody committed, and repo fixes never deployed.
|
||||||
|
|
||||||
|
`scripts/share-drift.py` classifies every mapped pair as `git-owned` (repo wins,
|
||||||
|
safe to push) or `unreconciled` (diverged, nobody has decided). It reports and
|
||||||
|
never writes. Run it before a build day; `scripts/preflight.py` covers the rest.
|
||||||
|
|
||||||
|
The unattends are `unreconciled` on purpose: the live copies are ~17 KB and the
|
||||||
|
repo copies ~12 KB, so pushing the repo would regress production.
|
||||||
278
docs/PESETUP-INTERNALS.md
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
# PESetup.exe internals
|
||||||
|
|
||||||
|
What GE Image Setup actually does, start to finish. Written from the decompiled
|
||||||
|
assembly, not from observation, because several long-standing beliefs about this
|
||||||
|
tool turned out to be wrong and cost weeks of debugging.
|
||||||
|
|
||||||
|
**Version documented:** 4.0.0.17 (`Sources/PESetup.exe`, PE32+ native apphost
|
||||||
|
wrapping a .NET 6 single-file bundle, 468 embedded files).
|
||||||
|
|
||||||
|
**Version in production:** 4.0.0.20, as of 2026-08-06 — the media reports it in
|
||||||
|
its own log (`AppVersion: 4.0.0.20`). Everything in this document was verified
|
||||||
|
against a 4.0.0.20 run on bay 579C144 that day: the media drive resolved to `Z:\`,
|
||||||
|
`W:` was created by `PrepareDisk` and used for every copy destination, the fallback
|
||||||
|
unattend at `Deploy\FlatUnattendW10.xml` was the one loaded, and driver selection
|
||||||
|
matched `win11_optiplexd13mlk7020_a09.zip` by model. The decompiled detail below has
|
||||||
|
not been re-derived from the 4.0.0.20 binary, so treat exact line-level claims as
|
||||||
|
4.0.0.17 and the observed behaviour as current.
|
||||||
|
|
||||||
|
## How to re-derive this
|
||||||
|
|
||||||
|
The managed code is not directly readable - the outer PE has no managed
|
||||||
|
metadata, so ILSpy refuses it. Extract `PESetup.dll` from the bundle first:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# .NET single-file bundle: signature is SHA-256 of ".net core bundle";
|
||||||
|
# the int64 EIGHT BYTES BEFORE it is the bundle header offset.
|
||||||
|
SIG = bytes([0x8b,0x12,0x02,0xb9,0x6a,0x61,0x20,0x38,
|
||||||
|
0x72,0x7b,0x93,0x02,0x14,0xd7,0xa0,0x32])
|
||||||
|
i = data.find(SIG)
|
||||||
|
header = struct.unpack_from('<q', data, i - 8)[0]
|
||||||
|
# header: uint32 major, uint32 minor, int32 count, 7-bit-prefixed bundle id,
|
||||||
|
# then (v2+) 4 x int64 deps/runtimeconfig, then uint64 flags.
|
||||||
|
# each entry: int64 offset, int64 size, (v6+) int64 compressedSize,
|
||||||
|
# byte type, 7-bit-prefixed path. Compressed entries are raw
|
||||||
|
# deflate (zlib.decompress(raw, -15)).
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `ilspycmd -t <TypeName> PESetup.dll`.
|
||||||
|
|
||||||
|
## Paths it hardcodes
|
||||||
|
|
||||||
|
From `PESetup.Models.GlobalSettings`. All are relative to the MEDIA drive
|
||||||
|
(`Z:\`, whichever winpeapps share startnet mapped) unless stated.
|
||||||
|
|
||||||
|
```
|
||||||
|
ControlDir \Deploy\Control
|
||||||
|
ToolsDir \Deploy\Tools\GE
|
||||||
|
SoftwarePackagesDir \Deploy\Applications
|
||||||
|
HWPackagesDir \Deploy\HW_Apps
|
||||||
|
UnattendFile \Deploy\Tools\GE\XML\FlatUnattendW10.xml
|
||||||
|
UnattendFile2 \Deploy\FlatUnattendW10.xml (fallback)
|
||||||
|
DiskPartDir <exe dir>\DiskPart
|
||||||
|
ConfigSkipPackagesFile skip.json
|
||||||
|
ConfigDisableAutoStartFile disableauto.json
|
||||||
|
ExpirationDuration 30 days
|
||||||
|
AutoStartCountDown 31
|
||||||
|
MinRequiredSpaceWithoutCompression 128849018880 (120 GB)
|
||||||
|
```
|
||||||
|
|
||||||
|
**`W:` IS HARDCODED, EVERYWHERE.** Not derived, not configurable, not a
|
||||||
|
convention this project invented:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
CopyPackages destDir = "W:\\" + SoftwarePackagesDir
|
||||||
|
CopyHWPackages destDir = "W:\\" + HWPackagesDir
|
||||||
|
CopyTools destDir = "W:\\GE"
|
||||||
|
CopyDrivers destinationPath = "W:\\Drivers\\" + <zip name without extension>
|
||||||
|
ApplyImage destination = "W:\\"
|
||||||
|
CreateBCD "W:\\Windows\\System32\\bcdboot.exe", "W:\\Windows /l en-US"
|
||||||
|
ApplyPackages DismApi.OpenOfflineSession("W:\\")
|
||||||
|
CopyLogs tags -> "w:\\windows\\system32\\"
|
||||||
|
```
|
||||||
|
|
||||||
|
PESetup's own disk preparation is what makes W: exist. Any downstream script
|
||||||
|
that hunts for "the applied volume" is solving a problem this tool does not
|
||||||
|
have. `diskpart list volume` on a machine mid-image shows `Volume 0 W Windows
|
||||||
|
NTFS 237GB Healthy`.
|
||||||
|
|
||||||
|
## The five working steps
|
||||||
|
|
||||||
|
Keys as they appear in the log: `workingstep_gatherdata`, `workingstep_selectos`,
|
||||||
|
`workingstep_prepare`, `workingstep_copy`, `workingstep_apply`,
|
||||||
|
`workingstep_finalize`, `workingstep_reboot`.
|
||||||
|
|
||||||
|
### 1. GATHER DATA
|
||||||
|
|
||||||
|
Four operations, all automatic:
|
||||||
|
|
||||||
|
| Operation | What it establishes |
|
||||||
|
|---|---|
|
||||||
|
| `GatherDataMediaDrive` | `ImageInfo.MediaDrive`, e.g. `Z:\` |
|
||||||
|
| `GatherDataBootDrive` | `ImageInfo.BootDrive`, always `X:\` (WinPE RAM disk) |
|
||||||
|
| `GatherDataSelection` | BIOS version/type, serial, secure boot, OS, languages, model, **driver** |
|
||||||
|
| `GatherDataImageDisk` | picks the physical disk and logs its partitions |
|
||||||
|
|
||||||
|
`GatherDataSelection` is where most decisions are made:
|
||||||
|
|
||||||
|
1. **Secure boot is mandatory.** `SecurebootEnabled != 1` fails the step outright.
|
||||||
|
2. Reads `Control\LanguagePacks.json`.
|
||||||
|
3. `SystemHelpers.GetModel()` and `GetManufacturer()` off WMI.
|
||||||
|
4. Reads `Control\HardwareDriver.json` and `Control\hw_applications.json`.
|
||||||
|
NOTE: it reads **HardwareDriver.json**, not the `hw_drivers.json` that also
|
||||||
|
sits in that folder. Editing the wrong one changes nothing.
|
||||||
|
5. **Virtual platform check.** If ANY driver entry has a manufacturer containing
|
||||||
|
"virtual platform", the tool goes into virtual-only mode: it takes
|
||||||
|
`list[0]` as the driver and then REQUIRES the machine to look virtual
|
||||||
|
(model/serial/BIOS containing VIRTUAL, VMWARE, XEN, QEMU, VirtualBox, vmw).
|
||||||
|
On real hardware that is a hard failure. A stray "virtual platform" entry in
|
||||||
|
the catalogue therefore breaks imaging for every physical machine.
|
||||||
|
6. Otherwise `GetDriverByModel` (below).
|
||||||
|
7. If `Control\skip.json` exists, package installation is skipped.
|
||||||
|
|
||||||
|
### 2. SELECT OS
|
||||||
|
|
||||||
|
Operator-facing. Warns when the media holds an LTSC image ("99% of the time only
|
||||||
|
for ShopFloor"). Also shows days-to-expiry - the media expires 30 days after
|
||||||
|
build (`ExpirationDuration`).
|
||||||
|
|
||||||
|
### 3. PREPARE
|
||||||
|
|
||||||
|
| Operation | What it does |
|
||||||
|
|---|---|
|
||||||
|
| `PrepareDoDisking` | copies `<exedir>\DiskPart\diskpartEFI.txt` to `X:\diskpartEFI<n>.txt`, then `cmd /c DISKPART /S X:\diskpartEFI<n>.txt` |
|
||||||
|
| `PrepareUnattend` | loads the unattend, substitutes, saves to `X:\Unattend.xml` |
|
||||||
|
|
||||||
|
`PrepareUnattend` in detail:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
text = MediaDrive + UnattendFile; // \Deploy\Tools\GE\XML\FlatUnattendW10.xml
|
||||||
|
if (!File.Exists(text)) text = MediaDrive + UnattendFile2; // \Deploy\FlatUnattendW10.xml
|
||||||
|
xml.Load(text);
|
||||||
|
xml.InnerXml = xml.InnerXml.Replace("%serialnumber%", imageinfo.Serialnumber);
|
||||||
|
xml.InnerXml = xml.InnerXml.Replace("*arch*", arch);
|
||||||
|
SetPackages(arch, xml, imageinfo); // needs Control\Packages.xml
|
||||||
|
xml.Save("X:\\Unattend.xml");
|
||||||
|
```
|
||||||
|
|
||||||
|
Two tokens are substituted: `%serialnumber%` and `*arch*`. `SetPackages` merges
|
||||||
|
in servicing packages from `Control\Packages.xml` and `Control\PackageGroups.xml`.
|
||||||
|
|
||||||
|
The unattend that is USED is the one on the media at `Tools\GE\XML\` if present,
|
||||||
|
otherwise `Deploy\FlatUnattendW10.xml`. A shopfloor variant only takes effect if
|
||||||
|
it occupies one of those two paths.
|
||||||
|
|
||||||
|
### 4. COPY
|
||||||
|
|
||||||
|
Four operations. **None of them filters, and none of them reads the unattend.**
|
||||||
|
|
||||||
|
| Operation | Source | Destination | Rule |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `CopyTools` | `Z:\Deploy\Tools\GE` | `W:\GE` | whole directory, recursive |
|
||||||
|
| `CopyPackages` | `Z:\Deploy\Applications` | `W:\Deploy\Applications` | whole directory, recursive |
|
||||||
|
| `CopyHWPackages` | `Z:\Deploy\HW_Apps` | `W:\Deploy\HW_Apps` | whole directory; **skipped if `IsVirtual` or `HWApps == null`** |
|
||||||
|
| `CopyDrivers` | one `.zip` chosen by model | `W:\Drivers\<zipname>` | **unzipped**, not copied |
|
||||||
|
|
||||||
|
`ExecuteInternalCopy` walks `GetDirectoriesRecursive(source, includeSubDirs:
|
||||||
|
true)` then `Directory.GetFiles(item, "*")`. Every file, every subdirectory. It
|
||||||
|
clears the read-only attribute on each copy.
|
||||||
|
|
||||||
|
**Consequences worth designing around:**
|
||||||
|
|
||||||
|
- Anything dropped into `Deploy\Applications` on the media lands on the target.
|
||||||
|
No manifest, no registration, no unattend reference required.
|
||||||
|
- The copy is FAIL-FAST. One exception on one file returns false and fails the
|
||||||
|
whole step with `copy_packages_error`. A locked or unreadable file in
|
||||||
|
`Applications` fails imaging, it does not get skipped.
|
||||||
|
- Progress is computed from total directory size up front, so bulky additions
|
||||||
|
visibly lengthen this phase.
|
||||||
|
|
||||||
|
### 5. APPLY
|
||||||
|
|
||||||
|
| Operation | What it does |
|
||||||
|
|---|---|
|
||||||
|
| `ApplyImage` | `install.wim` from `OperatingSystem.json`'s `destinationDir`, at `wimindex`, applied to `W:\` |
|
||||||
|
| `ApplyPackages` | DISM offline session on `W:\`, adds servicing packages; logs to `X:\ApplyOSPatch_<n>.log` |
|
||||||
|
| `ApplyLanguagePacks` | same pattern, `X:\ApplyLanguagePack_<n>.log` |
|
||||||
|
| `ApplyUnattend` | DISM offline session on `W:\`, applies `X:\Unattend.xml`; logs to `X:\ApplyUnattend.log` |
|
||||||
|
| `CreateBCD` | `W:\Windows\System32\bcdboot.exe W:\Windows /l en-US` |
|
||||||
|
| `CopyWinRE` | `reagentc /setreimage /path T:\Recovery\WindowsRE /target W:\Windows` |
|
||||||
|
|
||||||
|
All DISM work uses `W:\imagetemp` as scratch and deletes it afterwards.
|
||||||
|
|
||||||
|
### 6. FINALIZE
|
||||||
|
|
||||||
|
`CopyLogs`:
|
||||||
|
- `CopyTagFiles(ToolsDir)` - every `*.tag` from the tools dir to
|
||||||
|
**`w:\windows\system32\`**. This is how build/media tags reach the OS.
|
||||||
|
- `CopyLogFiles("X:\\")` and `CopyXMLFile("X:\\")` - the PESetup log and the
|
||||||
|
generated unattend are preserved.
|
||||||
|
|
||||||
|
## Driver selection, in full
|
||||||
|
|
||||||
|
This is the part most worth understanding, because a miss is nearly silent.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// GatherDataSelection
|
||||||
|
List<HardwareDriversRootObject> list =
|
||||||
|
JSONHelpers.ReadJSON_Driver(MediaDrive + ControlDir + "\\HardwareDriver.json");
|
||||||
|
...
|
||||||
|
imageinfo.HWDriver = GetDriverByModel(list, imageinfo.Model);
|
||||||
|
if (imageinfo.HWDriver == null) {
|
||||||
|
// status = Warning, message "driver for [MODEL] not found"
|
||||||
|
return operationResult;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private static HardwareDriversRootObject? GetDriverByModel(List<...> drivers, string model)
|
||||||
|
{
|
||||||
|
string modelFamily = "";
|
||||||
|
if (model.ToUpper().Contains("LATITUDE")) modelFamily = "Latitude";
|
||||||
|
if (model.ToUpper().Contains("OPTIPLEX")) modelFamily = "Optiplex";
|
||||||
|
if (model.ToUpper().Contains("PRECISION")) modelFamily = "Precision";
|
||||||
|
|
||||||
|
return drivers.Where(d => {
|
||||||
|
if (!string.IsNullOrEmpty(modelFamily) &&
|
||||||
|
!d.family.ToLower().Contains(modelFamily.ToLower())) return false;
|
||||||
|
foreach (string token in d.modelswminame.Split(','))
|
||||||
|
if (model.ToLower().Contains(token.ToLower())) return true; // SUBSTRING
|
||||||
|
return false;
|
||||||
|
}).ToList()?.FirstOrDefault();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
sourceFilePath = MediaDrive + HWDriver.destinationDir.Replace("*destinationdir*","")
|
||||||
|
+ "\\" + HWDriver.fileName;
|
||||||
|
destinationPath = "W:\\Drivers\\" + Path.GetFileNameWithoutExtension(sourceFilePath);
|
||||||
|
StartUnzipAsync(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Four traps in that logic
|
||||||
|
|
||||||
|
1. **A miss is a WARNING, not a failure.** Imaging continues and the machine
|
||||||
|
comes up with no drivers - no NIC, no WiFi, so DNS fails and anything
|
||||||
|
network-dependent at first boot fails with it. Symptoms appear far from the
|
||||||
|
cause.
|
||||||
|
2. **Matching is substring, first match wins.** A token like `7020` matches any
|
||||||
|
model string containing 7020. Order in the JSON decides ties.
|
||||||
|
3. **The family filter knows only three Dell lines.** Anything else - MicroPCs,
|
||||||
|
NUCs, non-Dell - skips the filter and depends entirely on
|
||||||
|
`modelswminame` substrings being right.
|
||||||
|
4. **One "virtual platform" entry hijacks the whole catalogue** (see GATHER
|
||||||
|
DATA step 5) and fails every physical machine.
|
||||||
|
|
||||||
|
## What this means for startnet.cmd
|
||||||
|
|
||||||
|
Three beliefs encoded in `startnet.cmd` do not survive contact with the source:
|
||||||
|
|
||||||
|
- *"The applied volume might not be W:."* PESetup hardcodes W: in nine places
|
||||||
|
and creates it during disk prep. The volume finder, the diskpart
|
||||||
|
reassignment and the `W:` wait loop are machinery around a non-problem.
|
||||||
|
- *"We must copy our payload from the enrollment share after imaging."* Anything
|
||||||
|
in `Deploy\Applications` on the media is copied to `W:\Deploy\Applications`
|
||||||
|
by PESetup itself, and is readable at `C:\Deploy\Applications` at first boot.
|
||||||
|
- *"Display MicroPCs failed because the applied volume was not W:."* Worth
|
||||||
|
re-testing. A missing `HardwareDriver.json` match produces the same
|
||||||
|
end-state - no drivers, no network - via a completely different route, and
|
||||||
|
that route only logs a warning.
|
||||||
|
|
||||||
|
## Files PESetup reads from the media
|
||||||
|
|
||||||
|
```
|
||||||
|
Deploy\Control\HardwareDriver.json driver catalogue (NOT hw_drivers.json)
|
||||||
|
Deploy\Control\hw_applications.json per-model applications
|
||||||
|
Deploy\Control\OperatingSystem.json OS list, wim path, wimindex
|
||||||
|
Deploy\Control\LanguagePacks.json language packs
|
||||||
|
Deploy\Control\packages.json servicing packages
|
||||||
|
Deploy\Control\Packages.xml unattend package merge
|
||||||
|
Deploy\Control\PackageGroups.xml package grouping
|
||||||
|
Deploy\Control\Media.tag media identity
|
||||||
|
Deploy\Control\skip.json presence = skip package install
|
||||||
|
Deploy\Control\disableauto.json presence = disable autostart
|
||||||
|
Deploy\Tools\GE\XML\FlatUnattendW10.xml preferred unattend
|
||||||
|
Deploy\FlatUnattendW10.xml fallback unattend
|
||||||
|
```
|
||||||
@@ -20,13 +20,67 @@ contribute a `config/sites/<sitename>.yaml` template back to the repo.
|
|||||||
|
|
||||||
### Network
|
### Network
|
||||||
|
|
||||||
|
The imaging NIC (isolated imaging LAN, static 172.16.9.1, the only NIC that
|
||||||
|
serves DHCP/TFTP/HTTP boot/SMB) is pinned by MAC. The GE corporate side (DHCP
|
||||||
|
client; SSH and the webapp on 9009 only) is optional. Both MACs in
|
||||||
|
`playbook/site-vars.yml` (copy `playbook/site-vars.example.yml`) may be left
|
||||||
|
empty:
|
||||||
|
|
||||||
|
- `pxe_nic_mac` empty: the first boot picks the imaging NIC itself
|
||||||
|
(`pxe-pick-nic --auto`). It takes the one wired port with a link, and only
|
||||||
|
if `pxe-nic-check` calls it isolated, then pins it; the choice is in
|
||||||
|
`/etc/pxe-server/nic-pick.conf` and is never re-guessed at boot. So during
|
||||||
|
the install plug in ONLY the imaging switch. When it cannot tell (no link,
|
||||||
|
two links, or that port sees GE) it changes nothing and the console says
|
||||||
|
to run `sudo pxe-pick-nic`: pull the imaging cable and plug it back in,
|
||||||
|
and the port that blinked is the imaging one. Run it again after swapping
|
||||||
|
a dead USB NIC, then re-run the playbook.
|
||||||
|
- `corp_nic_mac` empty: every other wired port (`en*`, `eth*`) is a DHCP
|
||||||
|
client, so the GE cable can go into any of them, at install or later.
|
||||||
|
dnsmasq is pinned to the imaging NIC (`interface=`) and serves only the
|
||||||
|
imaging subnet, and those ports ignore DHCP offers from 172.16.9.1. UFW
|
||||||
|
allows SSH and 9009 by source, not by interface.
|
||||||
|
- `corp_nic_mac` set: that port alone is the GE NIC, named `corp0` (as on
|
||||||
|
the WJ server).
|
||||||
|
|
||||||
|
The first-boot playbook pins the result in netplan (names `pxe0` / `corp0`,
|
||||||
|
overridable), dnsmasq, UFW and Samba.
|
||||||
|
|
||||||
| 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/site-vars.yml` (`pxe_dhcp_start`, `pxe_dhcp_end`) |
|
||||||
|
| NIC MACs / names | (picked at install) / pxe0, corp0 | `playbook/site-vars.yml` (`pxe_nic_mac`, `corp_nic_mac`, `pxe_nic_name`, `corp_nic_name`) |
|
||||||
| Hostname | pxeserver | `autoinstall/user-data` (identity.hostname) |
|
| Hostname | pxeserver | `autoinstall/user-data` (identity.hostname) |
|
||||||
|
|
||||||
|
### Access from the GE corporate network
|
||||||
|
|
||||||
|
SSH (password auth, the shared `pxe` password) and the webapp on 9009 (no
|
||||||
|
login, runs as root) are reachable on the corp NIC. By default from ALL of
|
||||||
|
GE, as before, so a re-run never locks anyone out; the play prints a loud
|
||||||
|
warning on every run while that is so. Set these in `playbook/site-vars.yml`:
|
||||||
|
|
||||||
|
| Var | Default | Effect |
|
||||||
|
|--------------------------|---------|--------|
|
||||||
|
| `corp_admin_cidrs` | `[]` | List of admin subnets/hosts, e.g. `["10.134.48.0/24", "3.12.34.56/32"]`. When non-empty, UFW allows 22 and 9009 from these only (on `corp_nic_name` when `corp_nic_mac` is set, else on any interface) and drops the any-source rules. The play refuses (nothing changed) if an entry is invalid or your own SSH session is not inside one of them. The imaging NIC keeps its rules. |
|
||||||
|
| `corp_ssh_password_auth` | `true` | `false` adds `/etc/ssh/sshd_config.d/10-pxe-corp-auth.conf`: `PasswordAuthentication no`, re-allowed by a `Match Address` block for `pxe_subnet` and localhost. Keys only from GE. The play refuses while `pxe` has no `~/.ssh/authorized_keys`, and removes the drop-in again if `sshd -t` rejects it. |
|
||||||
|
| `allow_disruptive` | `false` | Re-runs while imaging is active (SMB sessions on 445, or DHCP leases granted in the last `pxe_busy_window_min` = 30 min) do not bounce the NICs or restart dnsmasq/Apache/Samba; a needed network change stops the play. `-e allow_disruptive=true` overrides. |
|
||||||
|
|
||||||
|
Recommended for a production box: `corp_admin_cidrs` set to the admin
|
||||||
|
desks' subnet, then (after `ssh-copy-id`) `corp_ssh_password_auth: false`.
|
||||||
|
|
||||||
|
### Re-running the playbook over SSH (from the GE side)
|
||||||
|
|
||||||
|
The network step refuses before changing anything when the NIC that
|
||||||
|
`pxe_nic_mac` names holds the default route, a DHCP lease, or the route to
|
||||||
|
your SSH client (swapped MACs or cables). When it does apply, it arms a
|
||||||
|
120 s auto-revert timer (`pxe-netplan-revert-*`), checks the names, the
|
||||||
|
imaging address and that the corp NIC (or, without one pinned, every other
|
||||||
|
port that had an address) gets its IPv4 back within 30 s, and
|
||||||
|
restores the previous netplan config AND NIC names on any failure. Backups:
|
||||||
|
`/var/backups/netplan-<timestamp>/`.
|
||||||
|
|
||||||
### Identity and credentials
|
### Identity and credentials
|
||||||
|
|
||||||
| Value | Default | Where it lives |
|
| Value | Default | Where it lives |
|
||||||
@@ -64,6 +118,31 @@ The repo defines seven image types (see `playbook/pxe_server_setup.yml`,
|
|||||||
|
|
||||||
Trim or extend this list and the matching `shopfloor_types` filter as needed.
|
Trim or extend this list and the matching `shopfloor_types` filter as needed.
|
||||||
|
|
||||||
|
### Engineering Layer (gea-engineer)
|
||||||
|
|
||||||
|
Handled by the repo; nothing to work out per site except the payload.
|
||||||
|
|
||||||
|
- `playbook/engineering/Install-EngineeringLayer.ps1` is deployed by the
|
||||||
|
playbook to `gea-engineer/Deploy/Applications/extra_engineering/`.
|
||||||
|
- `FlatUnattendW10-engineer.xml` runs it twice:
|
||||||
|
- specialize Order 37 (`-StageOnly`) copies the payload and the script to
|
||||||
|
`C:\PreInstall\engineering` while `C:\Deploy` exists;
|
||||||
|
- first logon Order 11 installs from that copy. The bulk-enrollment
|
||||||
|
`DeleteDeployDir` task removes `C:\Deploy` on the next boot, so a PC
|
||||||
|
that reboots before the employee signs in would otherwise install
|
||||||
|
nothing and show no icon.
|
||||||
|
- After the install it makes sure the "Configure my Engineering Layer"
|
||||||
|
icon is on the Public desktop (until the PC is configured), and registers
|
||||||
|
Active Setup so a later user gets the icon if someone else already used
|
||||||
|
it. The employee clicks it once, on the GE network: it maps V: and runs
|
||||||
|
`install_local_new.cmd` from GE's share, which removes the icon.
|
||||||
|
- Log on the PC: `C:\Logs\Engineering\install-engineering-layer.log`.
|
||||||
|
|
||||||
|
The site supplies the payload beside the script, in
|
||||||
|
`gea-engineer/Deploy/Applications/extra_engineering/`:
|
||||||
|
`engineering_layer/Engineering_Install_v3.1.exe` and `prereqs/`
|
||||||
|
(.NET 3.5 sxs cabs, VC++ redists), about 650 MB.
|
||||||
|
|
||||||
### PC types (shopfloor sub-types)
|
### PC types (shopfloor sub-types)
|
||||||
|
|
||||||
`playbook/shopfloor-setup/` contains per-PC-type install logic. The current
|
`playbook/shopfloor-setup/` contains per-PC-type install logic. The current
|
||||||
@@ -80,8 +159,7 @@ A site without CMMs can drop the CMM type and its installers entirely.
|
|||||||
|
|
||||||
### GCCH bulk-enrollment PPKGs
|
### GCCH bulk-enrollment PPKGs
|
||||||
|
|
||||||
PPKG filenames are baked into `playbook/startnet.cmd` and
|
PPKG filenames are baked into `playbook/startnet.cmd`. They are tied to the GE Aerospace tenant. Other
|
||||||
`startnet-template.cmd`. They are tied to the GE Aerospace tenant. Other
|
|
||||||
sites/tenants will have their own PPKGs with different filenames.
|
sites/tenants will have their own PPKGs with different filenames.
|
||||||
|
|
||||||
| PPKG enrollment menu choice | Default filename |
|
| PPKG enrollment menu choice | Default filename |
|
||||||
@@ -143,10 +221,12 @@ 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 (`-Server`, `-CachePath`; `-WebPort` if the
|
||||||
|
webapp is not on 9009). The script checks every file on the server through
|
||||||
|
the webapp before the Image Import page will accept the upload.
|
||||||
|
|
||||||
## Recommended forward state
|
## Recommended forward state
|
||||||
|
|
||||||
@@ -156,10 +236,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:
|
||||||
|
|||||||
695
docs/ZERO-TOUCH-IMAGING-PROPOSAL.md
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
# Zero-touch, ShopDB-driven imaging - proposal
|
||||||
|
|
||||||
|
**Status:** proposal, nothing built
|
||||||
|
**Written:** 2026-08-06
|
||||||
|
**Author's note:** every claim about current behaviour below is from evidence
|
||||||
|
gathered on 2026-08-06 - decompiled binaries, live logs off bay 579C144, and the
|
||||||
|
live share. Where something is inferred rather than observed it says so.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What we want
|
||||||
|
|
||||||
|
Two ideas, one architecture.
|
||||||
|
|
||||||
|
**A. The image becomes thin.** It carries only what must happen at image time -
|
||||||
|
partition, apply the WIM, join Entra, install the enforce client. Everything else
|
||||||
|
(PC type, subtype, tool association, apps, settings) is data, pulled at first
|
||||||
|
boot from the ShopDB-Flask API and enforced continuously by GE-Enforce.
|
||||||
|
|
||||||
|
**B. Imaging becomes a job, not a menu.** Open a machine in ShopDB, click
|
||||||
|
*Create this PC*, pick the zeroed box that is sitting in WinPE waiting, and it
|
||||||
|
images start to finish with nobody touching the keyboard - reporting progress
|
||||||
|
onto that machine's record in real time.
|
||||||
|
|
||||||
|
Between them: no menus in WinPE, no per-type media, no decisions baked into text
|
||||||
|
files by whoever happened to be standing at the bay.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Why - what today actually costs
|
||||||
|
|
||||||
|
Today every decision is made in WinPE, written to files, and carried through a
|
||||||
|
chain where each link can fail silently:
|
||||||
|
|
||||||
|
```
|
||||||
|
startnet.cmd -> unattend (4 passes) -> FirstLogonCommands (18 orders)
|
||||||
|
-> ppkg -> DSC -> GE-Enforce
|
||||||
|
```
|
||||||
|
|
||||||
|
On 2026-08-06 alone, that chain produced:
|
||||||
|
|
||||||
|
| Failure | Root cause | How long it hid |
|
||||||
|
|---|---|---|
|
||||||
|
| Every build stopped at a dialog | one unattend `Path` at 676 chars against a 259 limit | ~1 day, and it masked the next two |
|
||||||
|
| Bays staged nothing, silently | a volume scan raced PESetup's `diskpart` and staged into a partition being erased | weeks (4 Display bays sat "green") |
|
||||||
|
| Deployment destroyed 4s after autologon | an at-logon task ran `-ManualFallback`, which is `sysprep /oobe /reboot` | until traced through Panther |
|
||||||
|
| Machines named `E<serial>` not `F<serial>` | our script re-applied a rename over the package's own | unknown |
|
||||||
|
| Six copies reported success having moved 0 bytes | unconditional `echo` after `robocopy` | weeks |
|
||||||
|
|
||||||
|
The pattern is identical in every case: **a decision made early, carried far, and
|
||||||
|
verified nowhere.** Moving decisions to a running, networked machine with an API
|
||||||
|
and a UI makes them recoverable - re-runnable, correctable, and observable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. What we already have to build on
|
||||||
|
|
||||||
|
This is not a greenfield project. Most of the mechanism exists.
|
||||||
|
|
||||||
|
### 3.1 ShopDB-Flask
|
||||||
|
- Assets keyed by serial; CMM bays, measuring tools, machine numbers already modelled
|
||||||
|
- Collector API: `POST /shopdb/api/collector/computers` (X-API-Key), ingests inventory by serial
|
||||||
|
- GE-Enforce plugin: scopes, manifests, **content-addressed payload store**
|
||||||
|
(`instance/geenforce/payloads/<sha256>`), `GET /api/geenforce/payload/<sha256>`
|
||||||
|
- Display scope (`plugins/geenforce/seed_display_scope.py`) already proves the pattern:
|
||||||
|
read a local subtype, prefer a **server-side role resolved by device IP**, pull
|
||||||
|
payloads over HTTPS, write the kiosk shortcut, sweep stale ones, self-heal each cycle
|
||||||
|
- Per-device role API: `GET /api/dashboarddefaults/display-role`
|
||||||
|
|
||||||
|
### 3.2 PXE server
|
||||||
|
- Per-bay state: `/var/log/pxe-imaging/<serial>.json` with stage history
|
||||||
|
- `winpe-status-push.ps1` already POSTs stage updates from WinPE
|
||||||
|
- Data-driven menus already: `menu.json` on the share, rendered by
|
||||||
|
`select-shopfloor-type.ps1`, edited by the webapp
|
||||||
|
- Imaging dashboard with per-bay tiles
|
||||||
|
- As of today: staging verification, imaging-log harvest to
|
||||||
|
`enrollment/imaging-logs/<serial>/`, `preflight.ps1` at the bay,
|
||||||
|
`preflight.py` + `lint-unattend.py` + `lint-driver-catalogue.py` +
|
||||||
|
`share-drift.py` on the server
|
||||||
|
|
||||||
|
### 3.3 Hardware
|
||||||
|
- Second NIC already present and unused: `enp0s31f6` (onboard, `e1000e`, currently down)
|
||||||
|
- PXE LAN currently on a USB adapter, `enx34c8d6b11010`
|
||||||
|
- That adapter was silently capped at USB 2.0 (327 Mb/s measured); moved to a
|
||||||
|
USB 3 port on 2026-08-06 and now runs at line-rate gigabit (**937 Mb/s measured**)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Target architecture
|
||||||
|
|
||||||
|
### 4.1 Three (or one) pre-imaged types
|
||||||
|
|
||||||
|
Proposed: **Displays, Inspection, Shopfloor** as pre-imaged stock, with the
|
||||||
|
subtype chosen later by the user at first boot.
|
||||||
|
|
||||||
|
**Open question worth settling early:** if the base image is identical and all
|
||||||
|
configuration comes from the API, what actually differs between the three?
|
||||||
|
Drivers are selected by PESetup per *model*, not per type. Unless disk layout or
|
||||||
|
a must-be-offline application differs, **one image** is better - three media trees
|
||||||
|
mean three things to keep in sync, and drift is what caused the day-long outage.
|
||||||
|
|
||||||
|
### 4.2 First-boot selection
|
||||||
|
|
||||||
|
A friendly PS1 runs when no PC type is recorded yet:
|
||||||
|
|
||||||
|
```
|
||||||
|
Inspection -> CMM | Genspect | Wax Trace | Keyence
|
||||||
|
└─ CMM / Keyence / WaxTrace -> which measuring tool?
|
||||||
|
(list served from ShopDB assets, not a CSV)
|
||||||
|
Displays -> Lobby | Dashboard | 3D Print Kiosk
|
||||||
|
Shopfloor -> Collections | No Collections | Common | Heattreat | Part Marker | ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The tool selection is the important half: it drives PC-DMIS version, FormTracePak
|
||||||
|
version, DODA flag, NTLARS/eDNC registry, UDC settings, machine number.
|
||||||
|
|
||||||
|
**The network constraint that shapes this.** ShopDB is only reachable once the
|
||||||
|
bay has joined the **AESFMA wifi SSID** - not on the imaging LAN, and not
|
||||||
|
necessarily at first boot. So a first-boot selection UI cannot assume the API is
|
||||||
|
there. It must either work offline and sync later, or wait. Confirmed on
|
||||||
|
579C144: the bay held `172.16.9.81` and `172.24.19.142`, neither in the
|
||||||
|
production ranges, and `sync_intune` sat retrying every 30 seconds.
|
||||||
|
|
||||||
|
**The pattern that solves it, already built and worth reusing.**
|
||||||
|
`Install-ShopdbKiosk-WhenOnline.ps1` is a SYSTEM task armed at imaging that does
|
||||||
|
nothing until ShopDB answers, then acts once, verifies, and unregisters itself.
|
||||||
|
Any step in the new architecture that needs the API - selection sync, config
|
||||||
|
pull, enrollment completion - can use the same shape rather than assuming
|
||||||
|
connectivity.
|
||||||
|
|
||||||
|
**Design points:**
|
||||||
|
|
||||||
|
- **Server is the source of truth.** The selection POSTs to ShopDB keyed on
|
||||||
|
serial; the local file is a cache. A mis-selection is then fixable from a
|
||||||
|
browser, not by walking to the bay.
|
||||||
|
- **Re-runnable.** Available from the Start menu, shows what was chosen and what
|
||||||
|
was installed. People pick wrong; that must not mean re-imaging.
|
||||||
|
- **Audited.** ShopDB records who chose what and when - that is what separates
|
||||||
|
"misconfigured" from "chose wrong" six months later.
|
||||||
|
- **Do not require the LAPS password.** Handing a shop-floor user the local admin
|
||||||
|
password to run a selection dialog is a real control change, and the app runs
|
||||||
|
elevated. Prefer a SYSTEM-run UI on the logon desktop, or a kiosk shell.
|
||||||
|
|
||||||
|
### 4.3 Per-machine configuration records
|
||||||
|
|
||||||
|
Today a machine's identity is scattered:
|
||||||
|
|
||||||
|
| What | Where today | Size |
|
||||||
|
|---|---|---|
|
||||||
|
| eDNC / NTLARS registry | `_ntlars-backups/` - 147 per-bay `.reg` files | ~1.5 MB total |
|
||||||
|
| WaxTrace bay config | `bay-config.csv` - 15 bays, 7 FTPak versions, per-bay user ID | small |
|
||||||
|
| CMM bay config | `cmm-bay-config.csv` -> version.txt, doda.txt | small |
|
||||||
|
| UDC settings | `pre-install/udc-backups/`, per-bay | ~240 MB per bay |
|
||||||
|
| Machine number | `machine-number.txt` written by startnet | trivial |
|
||||||
|
|
||||||
|
Every bay gets **all** of it staged, then picks its own.
|
||||||
|
|
||||||
|
Proposed: one record per machine in ShopDB.
|
||||||
|
|
||||||
|
```
|
||||||
|
ShopDB asset (key: serial and/or machine number)
|
||||||
|
├─ pctype + subtype -> selects the GE-Enforce scope
|
||||||
|
├─ tool association -> CMM bay, Keyence model, FTPak version, DODA
|
||||||
|
├─ machine number -> UDC / eDNC naming
|
||||||
|
└─ payload refs (sha256) -> NTLARS .reg, UDC backup, per-bay settings
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reference payloads, do not embed them.** NTLARS `.reg` files carry binary
|
||||||
|
values and UDC backups are hundreds of MB. GE-Enforce already has a
|
||||||
|
content-addressed blob store with an HTTP fetch endpoint - the record names the
|
||||||
|
payload, the store holds it.
|
||||||
|
|
||||||
|
This also removes a live bug class. `Restore-UDCData.ps1` and
|
||||||
|
`Update-MachineNumber.ps1` each mount SMB backup shares on their own drive
|
||||||
|
letters, which collided with GE-Enforce's `W:` and killed the manifest entry
|
||||||
|
running next (fixed in `66c24b5` by moving them to `R:` and `N:`). Payloads over
|
||||||
|
HTTPS remove drive-letter juggling entirely.
|
||||||
|
|
||||||
|
Migration is a one-time import of two CSVs and 147 `.reg` files. A script, not a
|
||||||
|
project.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Zero-touch: ShopDB-triggered imaging
|
||||||
|
|
||||||
|
### 5.1 Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Zeroed PC, boot order = IPv4 only. PXE boots into WinPE.
|
||||||
|
2. WinPE reports serial + MAC + model, then WAITS.
|
||||||
|
3. ShopDB shows it under "awaiting assignment".
|
||||||
|
4. Operator opens the target machine -> "Create this PC" -> picks the waiting box.
|
||||||
|
5. WinPE polls, receives a job:
|
||||||
|
{ pctype, subtype, tool/bay, tenant, purpose, target asset id }
|
||||||
|
and images with NO menus.
|
||||||
|
6. Progress streams to that machine's record in real time.
|
||||||
|
7. On completion the PC binds to the asset record.
|
||||||
|
```
|
||||||
|
|
||||||
|
Every menu in `startnet.cmd` becomes a field in the job. If no job exists, it
|
||||||
|
falls back to today's interactive menu and displays its own serial so one can be
|
||||||
|
created.
|
||||||
|
|
||||||
|
### 5.2 Safety - this is a remote-triggered disk wipe
|
||||||
|
|
||||||
|
Clicking a web button destroys a machine, and the target is chosen from a list.
|
||||||
|
The nightmare is picking the wrong row.
|
||||||
|
|
||||||
|
**Mitigation:** the waiting PC displays a short code on screen; the job cannot be
|
||||||
|
claimed unless that code matches what the operator confirms in ShopDB. Cheap, and
|
||||||
|
it makes wrong-machine wipes structurally hard rather than merely unlikely.
|
||||||
|
|
||||||
|
Additionally: a job may only target a machine currently in the waiting state, and
|
||||||
|
the wipe must be logged against both the operator and the asset.
|
||||||
|
|
||||||
|
### 5.3 Real-time record updates
|
||||||
|
|
||||||
|
Each phase already produces data; it simply is not joined up.
|
||||||
|
|
||||||
|
| Phase | Existing source | Lands on the record as |
|
||||||
|
|---|---|---|
|
||||||
|
| PXE boot | dnsmasq lease + WinPE check-in | serial, MAC, model, "imaging started" |
|
||||||
|
| WIM apply | `winpe-status-push.ps1` | live stage / progress |
|
||||||
|
| Staging | `winpe-staging.log` + harvested bundle | what payload actually landed |
|
||||||
|
| First boot | collector API | CPU, RAM, disk, OS build |
|
||||||
|
| Enrollment | `criticalChecks.json`, `TokenMatch.json` | Entra join state, tenant, purpose |
|
||||||
|
| Steady state | GE-Enforce report | installed apps, drift, self-heal events |
|
||||||
|
|
||||||
|
End state is one timeline per machine: *imaged 14:32 -> staged
|
||||||
|
gea-shopfloor-cmm, bay CMM4 -> enrolled GCCH_SH -> PC-DMIS 2019 R2 installed ->
|
||||||
|
enforcing clean since*.
|
||||||
|
|
||||||
|
**Two rules so it does not rot:**
|
||||||
|
|
||||||
|
1. **Push, best-effort.** If ShopDB is unreachable the bay must still image.
|
||||||
|
Queue events on the PXE server and drain them; the harvested logs are the
|
||||||
|
durable local copy.
|
||||||
|
2. **One writer per field.** Imaging status from the PXE server, inventory from
|
||||||
|
the collector, app state from GE-Enforce. Two writers on one field is the
|
||||||
|
exact bug that ate 2026-08-06.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Network design
|
||||||
|
|
||||||
|
### 6.1 Dual-homed PXE server
|
||||||
|
|
||||||
|
- **PXE side** - isolated `172.16.9.0/24`, dnsmasq DHCP bound to that adapter
|
||||||
|
- **GE side** - corporate, reachable from anywhere; carries ShopDB API traffic,
|
||||||
|
the job queue, status pushes, and (if wanted) the BPRT token fetch
|
||||||
|
|
||||||
|
The onboard `enp0s31f6` is free for this today.
|
||||||
|
|
||||||
|
### 6.2 Security prerequisites - these gate the whole thing
|
||||||
|
|
||||||
|
Checked 2026-08-06:
|
||||||
|
|
||||||
|
**Already safe.** dnsmasq has `listen-address=172.16.9.1` + `bind-dynamic`, so it
|
||||||
|
binds only to the interface holding that address. DHCP will **not** leak onto a
|
||||||
|
GE-side NIC. This was the failure I most expected and it is already handled.
|
||||||
|
|
||||||
|
**Not safe yet:**
|
||||||
|
|
||||||
|
```
|
||||||
|
smb.conf no `interfaces =` / `bind interfaces only` -> Samba listens on ALL interfaces
|
||||||
|
ufw every rule is ALLOW IN Anywhere, not interface-scoped:
|
||||||
|
22/tcp SSH (account is pxe/pxe)
|
||||||
|
445/tcp shares (pxe-upload, password in scripts)
|
||||||
|
69/udp TFTP
|
||||||
|
9009,9011 webapp
|
||||||
|
```
|
||||||
|
|
||||||
|
The moment a GE-side interface comes up, all of that is corporate-reachable.
|
||||||
|
|
||||||
|
**Required before dual-homing:**
|
||||||
|
1. Scope every UFW rule to the PXE interface (`ufw allow in on <pxe-if> ...`)
|
||||||
|
2. `interfaces = <pxe-if>` + `bind interfaces only = yes` in `smb.conf`
|
||||||
|
3. Expose deliberately on the GE side only what must be - realistically the
|
||||||
|
webapp, behind real authentication
|
||||||
|
4. Reconsider SSH credentials if the host becomes corporate-reachable
|
||||||
|
|
||||||
|
*(Correction to an earlier note: the shares are **not** guest-accessible -
|
||||||
|
`guest ok = no` on every share, with `map to guest = bad user`. They require the
|
||||||
|
`pxe-upload` account. That account's password is in scripts, so it is not a
|
||||||
|
control once 445 is corporate-reachable.)*
|
||||||
|
|
||||||
|
### 6.3 What dual-homing unlocks
|
||||||
|
|
||||||
|
- Job queue sync and live status without the share as intermediary
|
||||||
|
- BPRT token fetch (`mcl.dwcdn.geaerospace.com`) reachable at imaging time
|
||||||
|
- Possibly the Entra join during imaging rather than after re-cabling - see §8.2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. The PESetup question
|
||||||
|
|
||||||
|
### 7.1 Autostart does not apply to our media
|
||||||
|
|
||||||
|
`AutoStartCountDown = 31` exists in the decompiled settings, and
|
||||||
|
`disableauto.json` - whose *presence* disables autostart - is absent from the
|
||||||
|
media. On the surface that suggests a 31-second countdown, and the observed log
|
||||||
|
is consistent with an operator simply clicking before it fired:
|
||||||
|
|
||||||
|
```
|
||||||
|
11:30:07.74 'Selection View' started (LTSC warning displayed)
|
||||||
|
11:30:18.63 Btn 'Next' pressed.
|
||||||
|
```
|
||||||
|
|
||||||
|
**However:** per the team's operational experience, **LTSC builds cannot
|
||||||
|
autostart**. The SELECT OS step warns when the media holds an LTSC image
|
||||||
|
("99% of the time only for ShopFloor. Are you sure you want 'LTSC'?") and that
|
||||||
|
confirmation is mandatory regardless of the countdown. Our media is
|
||||||
|
`Windows 11 LTSC 24H2` (`OperatingSystem.json`, build 26100, id 20), so every
|
||||||
|
image we produce hits it.
|
||||||
|
|
||||||
|
Consequence: **the countdown is not a route to zero-touch for us.** The 40-second
|
||||||
|
hands-off test is still worth doing once to confirm the behaviour first-hand and
|
||||||
|
record it - but plan on the answer being "it waits".
|
||||||
|
|
||||||
|
That promotes §7.2 from a contingency to the actual decision.
|
||||||
|
|
||||||
|
### 7.2 Re-implementation - now the likely path, and tractable
|
||||||
|
|
||||||
|
Nothing in the mechanism is proprietary - only the data, which we already own and
|
||||||
|
already parse.
|
||||||
|
|
||||||
|
| PESetup step | Equivalent |
|
||||||
|
|---|---|
|
||||||
|
| `PrepareDisk` | `diskpart /s diskpartEFI.txt` |
|
||||||
|
| `PrepareUnattend` | token substitution (`%serialnumber%`, `*arch*`) + `Packages.xml` merge |
|
||||||
|
| `CopyTools` / `CopyPackages` / `CopyHWPackages` | robocopy |
|
||||||
|
| `CopyDrivers` | select by model from `HardwareDriver.json`, unzip |
|
||||||
|
| `ApplyImage` | `DISM /Apply-Image` per `OperatingSystem.json` |
|
||||||
|
| `ApplyPackages` / `ApplyLanguagePacks` | `DISM /Add-Package` |
|
||||||
|
| `ApplyUnattend` | `DISM /Apply-Unattend` |
|
||||||
|
| `CreateBCD` / `CopyWinRE` | `bcdboot`, `reagentc` |
|
||||||
|
|
||||||
|
A few hundred lines of PowerShell. We would also get to fix the traps documented
|
||||||
|
in `docs/PESETUP-INTERNALS.md`: substring first-match-wins driver matching, a
|
||||||
|
family filter that knows only Latitude/OptiPlex/Precision, and a driver miss
|
||||||
|
being a **warning** rather than a failure.
|
||||||
|
|
||||||
|
**The cost is ownership, not effort.** `Release.json` points at MCL auto-update
|
||||||
|
URLs, so GE expects this media to self-update. Diverging means tracking their
|
||||||
|
format changes indefinitely, plus a likely compliance conversation about the
|
||||||
|
mandated imaging tool. Since the LTSC gate means autostart will not save us
|
||||||
|
(§7.1), this is a decision to take deliberately rather than a fallback.
|
||||||
|
|
||||||
|
### 7.3 What we would keep either way
|
||||||
|
|
||||||
|
Image-time preflight stays valuable regardless: secure boot (PESetup hard-fails
|
||||||
|
without it), >=120 GB disk (`MinRequiredSpaceWithoutCompression`), driver match
|
||||||
|
for the model, media age (30-day expiry). Already built as
|
||||||
|
`playbook/scripts/preflight.ps1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Enrollment
|
||||||
|
|
||||||
|
### 8.1 How it works today
|
||||||
|
|
||||||
|
- Six MCL packages on the media: `PERS` / `SH` / `LOAN` x `GCCH` / `RoW`, plus
|
||||||
|
the SFLD package for shopfloor
|
||||||
|
- `Start-BulkEnrollOrchestrator.ps1` asks tenant + purpose via
|
||||||
|
`Select-PCConfig.ps1` (a GUI), then applies the matching package
|
||||||
|
- `Insert-BPRTToPPKG.ps1` fetches an **encrypted token table** from
|
||||||
|
`mcl.dwcdn.geaerospace.com`, decrypts with a static passphrase
|
||||||
|
(PBKDF2 310k / SHA-256 / AES-CBC), matches on **Tenant AND Purpose**, injects
|
||||||
|
the token and stamps the real expiry into the filename
|
||||||
|
(`Exp_XXXXXXXX` -> `Exp_yyyyMMdd`)
|
||||||
|
- The package itself sets `<DNSComputerName>F%SERIAL%</DNSComputerName>` and
|
||||||
|
performs the Entra join
|
||||||
|
- A human then assigns the device category in Intune
|
||||||
|
|
||||||
|
As of 2026-08-06 the tenant/purpose choice can be pre-seeded at the PXE menu
|
||||||
|
(`C:\Enrollment\pc-config.txt`, consumed by a shim at the path the orchestrator
|
||||||
|
already calls). Injection deliberately stays at first logon - it needs internet.
|
||||||
|
|
||||||
|
### 8.2 The constraint that shapes pre-imaged stock
|
||||||
|
|
||||||
|
The BPRT token is stamped into the package with an expiry. A PC imaged, powered
|
||||||
|
off, and left in a cupboard for three months has a **dead token** and will never
|
||||||
|
join - and the failure looks identical to "not on the production network yet",
|
||||||
|
because `Entra ID Joined: false` right after imaging is normal.
|
||||||
|
|
||||||
|
Three options, pick deliberately:
|
||||||
|
|
||||||
|
1. **Join at imaging time** - requires the production network at the bench
|
||||||
|
(which dual-homing could provide), and pre-imaged stock is then already joined
|
||||||
|
2. **Join at first power-on** - cleaner logistically, but stock has a shelf life
|
||||||
|
bounded by token expiry
|
||||||
|
3. **Re-inject on demand** - first boot detects a dead token and fetches a fresh
|
||||||
|
one; needs the machine to reach the token blob
|
||||||
|
|
||||||
|
Whichever is chosen, the *silent* failure must be closed: a check that
|
||||||
|
distinguishes "token expired" from "no network yet".
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Ownership model
|
||||||
|
|
||||||
|
Four times on 2026-08-06 two systems owned one thing and the last writer won
|
||||||
|
silently. A clean slate is the moment to fix that. See `docs/OWNERSHIP.md`.
|
||||||
|
|
||||||
|
| Concern | Owner | Not |
|
||||||
|
|---|---|---|
|
||||||
|
| Computer name | the provisioning package (`F%SERIAL%`) | any script in this repo |
|
||||||
|
| Drive letters during imaging | PESetup (`W:`, hardcoded 9 places) | anything running `diskpart` concurrently |
|
||||||
|
| Entra enrollment | the package + orchestrator | `run-enrollment.ps1` |
|
||||||
|
| Kiosk / display targets | GE-Enforce scope | `site-config.json` (backstop only) |
|
||||||
|
| Imaging status | PXE server | the collector |
|
||||||
|
| Inventory | collector API | the PXE server |
|
||||||
|
| Installed app state | GE-Enforce reports | anything else |
|
||||||
|
| Job queue | **ShopDB** (owns assets) | the PXE webapp (executor only) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9a. Design rules learned the hard way (2026-08-06)
|
||||||
|
|
||||||
|
A day of fixing the current pipeline produced five rules. They are cheap to
|
||||||
|
honour in a new design and expensive to retrofit.
|
||||||
|
|
||||||
|
### Absent and empty are different
|
||||||
|
|
||||||
|
Four separate scripts tested config as
|
||||||
|
`$null -ne $cfg -and $cfg.Count -gt 0`, so an explicitly empty list fell through
|
||||||
|
to a hardcoded default. A Display kiosk configured with
|
||||||
|
|
||||||
|
```json
|
||||||
|
"desktopApps": [], "startupItems": [], "taskbarPins": []
|
||||||
|
```
|
||||||
|
|
||||||
|
was given UDC, eDNC, NTLARS, WJ Shopfloor, Defect Tracker and Plant Apps -
|
||||||
|
**configuring "none" produced "everything"**. Plant Apps launched
|
||||||
|
`msedge --new-window` on a screen with no keyboard.
|
||||||
|
|
||||||
|
Measured, not assumed: an empty JSON array round-trips as `Object[]` with
|
||||||
|
`Count = 0`, and an absent key as `$null`. They are distinguishable; the code
|
||||||
|
simply conflated them.
|
||||||
|
|
||||||
|
*Rule: in the API model, "no items" and "not configured" must be different
|
||||||
|
values on the wire, and the default for an unrecognised or missing scope is
|
||||||
|
NOTHING, not a fallback set.*
|
||||||
|
|
||||||
|
### Deny by default, always
|
||||||
|
|
||||||
|
`preinstall.json` had 16 of 21 entries at `PCTypes: ["*"]`, so a kiosk installed
|
||||||
|
Adobe, OpenText, Defect Tracker, the serial drivers and twelve legacy VC++
|
||||||
|
redistributables. The SMB manifest had 18 of 25 entries unfiltered. In both
|
||||||
|
cases the filter worked perfectly - it simply was not applied.
|
||||||
|
|
||||||
|
*Rule: every payload declares which PC types it is for. An undeclared payload
|
||||||
|
installs nowhere and fails validation, rather than installing everywhere.*
|
||||||
|
|
||||||
|
### Gate at the point of action, not the call site
|
||||||
|
|
||||||
|
`Run-ShopfloorSetup.ps1` gated both the S: mapper and the machine-number prompt
|
||||||
|
on PC type. Both registered anyway on a Display, with no "Skipping" line in the
|
||||||
|
log - something in the finalization phase reaches those registrars past the
|
||||||
|
call-site gate. The fix was to gate inside each registrar.
|
||||||
|
|
||||||
|
*Rule: authority checks belong with the thing being done. A caller-side check is
|
||||||
|
a convenience, never the control.*
|
||||||
|
|
||||||
|
### The image itself carries configuration
|
||||||
|
|
||||||
|
`WJ Shopfloor.lnk`, dated April, sits in the all-users Startup folder of every
|
||||||
|
bay - **inside the WIM**. No script change removes it, and no amount of thinning
|
||||||
|
the pipeline touches it.
|
||||||
|
|
||||||
|
*Rule: "thin image" must include auditing what is baked into the WIM, not just
|
||||||
|
what the pipeline adds afterwards.*
|
||||||
|
|
||||||
|
### The provisioning package is a hard boundary
|
||||||
|
|
||||||
|
These are installed by the ppkg, not by anything PXE controls:
|
||||||
|
|
||||||
|
```
|
||||||
|
Google Chrome (+2 updater tasks) RealVNC Tanium
|
||||||
|
CyberArk EPM .NET 3.5 Report IP Sysinternals Autologon DSC
|
||||||
|
PowerShell 7 x64 AND x86
|
||||||
|
```
|
||||||
|
|
||||||
|
*Rule: thinning the image does not thin the package. Anything that must not be
|
||||||
|
on a kiosk and comes from the ppkg needs a package change or an explicit
|
||||||
|
post-install removal - it cannot be solved by scoping our own payloads.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Phased delivery
|
||||||
|
|
||||||
|
Each phase is useful standing alone. No phase requires the next.
|
||||||
|
|
||||||
|
**Phase 0 - settle the unknowns (hours)**
|
||||||
|
- Confirm the LTSC autostart gate first-hand (one bay, hands off, 40 seconds)
|
||||||
|
and record it - expected to wait, per operational experience
|
||||||
|
- Decide: re-implement the imaging step, or accept one click per build
|
||||||
|
- Decide: one image or three
|
||||||
|
- Decide: join at imaging time, at first power-on, or re-inject
|
||||||
|
|
||||||
|
**Phase 1 - visibility (small)**
|
||||||
|
- WinPE reports serial / MAC / model on boot; "awaiting assignment" list in the webapp
|
||||||
|
- Relay imaging status to ShopDB over the GE side
|
||||||
|
- *Useful immediately: you can see what is booting and what it did.*
|
||||||
|
|
||||||
|
**Phase 2 - dual-home safely (small, gating)**
|
||||||
|
- Interface-scope UFW, bind Samba, bring up `enp0s31f6`
|
||||||
|
- Nothing else proceeds safely until this is done
|
||||||
|
|
||||||
|
**Phase 3 - job queue**
|
||||||
|
- Job model + API in ShopDB (create / claim / complete), with the on-screen
|
||||||
|
confirmation code
|
||||||
|
- WinPE polls once where the menu is today; falls back to the menu if no job
|
||||||
|
|
||||||
|
**Phase 4 - first-boot selection**
|
||||||
|
- Selection UI, server-backed lists, POST the choice, re-runnable
|
||||||
|
- Retire the WinPE sub-menus for subtype and tool association
|
||||||
|
|
||||||
|
**Phase 5 - per-machine records**
|
||||||
|
- Import the CSVs and `.reg` files into ShopDB with payload refs
|
||||||
|
- Retire `bay-config.csv`, `cmm-bay-config.csv`, and staging all 147 `.reg` files
|
||||||
|
to every bay
|
||||||
|
|
||||||
|
**Phase 6 - thin the image**
|
||||||
|
- Remove per-type media (`_media/<pctype>`), the staging block, the Office ppkg
|
||||||
|
variants, the CMM bay picker, the WaxTrace ISO cherry-pick
|
||||||
|
- WinPE's job becomes: partition, apply, join, install enforce client, reboot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Risks
|
||||||
|
|
||||||
|
| Risk | Why it matters | Mitigation |
|
||||||
|
|---|---|---|
|
||||||
|
| Remote-triggered wipe hits the wrong machine | destructive, irreversible | on-screen confirmation code; job may only target a waiting machine; log against operator and asset |
|
||||||
|
| ShopDB becomes a single point of failure | today a bay configures from the share with ShopDB down | GE-Enforce ETag / last-known-good cache; explicit failure UI, never a silent stall |
|
||||||
|
| Dual-homing exposes the PXE server | SSH `pxe/pxe`, SMB, TFTP currently `ALLOW IN Anywhere` | Phase 2 gates everything |
|
||||||
|
| BPRT expiry on shelf stock | silent, indistinguishable from "no network yet" | pick a join strategy (§8.2) and add an explicit expiry check |
|
||||||
|
| Selection UI becomes the new single point of correctness | wrong subtype = wrong PC-DMIS, wrong bay config | re-runnable, server-corrected, audited |
|
||||||
|
| Re-implementing PESetup | ongoing MCL format tracking + a likely compliance conversation | scope it against "one click per build" - zero-touch is the only thing that needs it |
|
||||||
|
| Big payloads over the corporate network | PC-DMIS, FormTracePak ISOs (~2 GB each), Keyence | GE-Enforce supports `smb` / `http` / `inline` per entry - decide per payload |
|
||||||
|
| Three images drift apart | drift caused the 2026-08-06 outage | prefer one image; `share-drift.py` guards what remains |
|
||||||
|
| API unreachable at first boot | ShopDB needs AESFMA; the imaging LAN cannot reach it | wait-for-network task pattern (§4.2); never assume connectivity |
|
||||||
|
| ppkg-delivered apps on a kiosk | Chrome, RealVNC, Tanium etc. are outside PXE control | package change or explicit post-install removal - scoping our payloads cannot fix it |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Open questions
|
||||||
|
|
||||||
|
1. One image or three? What genuinely differs between them?
|
||||||
|
2. Join at imaging time, at first power-on, or re-inject on demand?
|
||||||
|
3. Is the LTSC confirmation truly unskippable? (expected yes - it decides
|
||||||
|
whether zero-touch requires re-implementing the imaging step)
|
||||||
|
4. Is replacing the GE-mandated imaging tool acceptable, if it comes to that?
|
||||||
|
5. Does the selection UI run as SYSTEM (no LAPS password), or as an admin user?
|
||||||
|
6. Which payloads stay on SMB and which move to HTTPS?
|
||||||
|
7. Who operates the job queue day to day - is "Create this PC" a technician
|
||||||
|
action, or does it need approval?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Where everything referenced here lives
|
||||||
|
|
||||||
|
### 13.1 This repo (`/home/camp/projects/pxe`)
|
||||||
|
|
||||||
|
| Path | What |
|
||||||
|
|---|---|
|
||||||
|
| `docs/PESETUP-INTERNALS.md` | decompiled imaging tool behaviour; the source for every PESetup claim above |
|
||||||
|
| `docs/OWNERSHIP.md` | who owns what, with the 2026-08-06 collisions |
|
||||||
|
| `playbook/startnet.cmd` | the WinPE script this proposal thins out - menus, staging, verify, log harvest |
|
||||||
|
| `playbook/scripts/preflight.ps1` | bay-side checks (secure boot, disk, driver match, media age) |
|
||||||
|
| `playbook/shopfloor-setup/run-enrollment.ps1` | applies the SFLD package via `provtool` |
|
||||||
|
| `playbook/shopfloor-setup/Run-ShopfloorSetup.ps1` | post-boot setup; the enrollment-model comment is here |
|
||||||
|
| `playbook/shopfloor-setup/Fetch-StagingPayload.ps1`, `Verify-And-Heal-Staging.ps1` | first-logon self-heal; read `C:\Enrollment\fetch-source.txt` |
|
||||||
|
| `playbook/shopfloor-setup/BPRT/Select-PCConfig.ps1` | the pre-seed shim (vendor GUI preserved as `-vendor.ps1` on the share) |
|
||||||
|
| `playbook/shopfloor-setup/BIOS/{check-bios.cmd,models.txt}` | firmware matching |
|
||||||
|
| `scripts/preflight.py` | runs every server-side lint in one command |
|
||||||
|
| `scripts/lint-unattend.py` | schema limits: Path 259, CommandLine 1024, Description 256 |
|
||||||
|
| `scripts/lint-driver-catalogue.py` | reimplements `GetDriverByModel` |
|
||||||
|
| `scripts/share-drift.py` | repo vs live share, git-owned vs unreconciled |
|
||||||
|
| `playbook/shopfloor-setup/Collect-ImagingDiagnostics.ps1` | one-pass bay diagnostics - identity, enrollment, installed apps, all four autostart surfaces, GE-Enforce reachability |
|
||||||
|
| `scripts/build-pctype-media.py` | per-PCTYPE media views (retired by Phase 6) |
|
||||||
|
|
||||||
|
### 13.2 PXE server (`pxe@172.16.9.1`)
|
||||||
|
|
||||||
|
| Path | What |
|
||||||
|
|---|---|
|
||||||
|
| `/srv/samba/winpeapps/<image>/Deploy/Control/` | `HardwareDriver.json`, `OperatingSystem.json`, `Media.tag`, `Release.json` |
|
||||||
|
| `/srv/samba/winpeapps/<image>/Deploy/FlatUnattendW10.xml` | the answer file that actually boots machines |
|
||||||
|
| `/srv/samba/winpeapps/_shared/Applications/BPRT/` | the six MCL packages, `Insert-BPRTToPPKG.ps1`, `Start-BulkEnrollOrchestrator.ps1`, `Select-PCConfig*.ps1` |
|
||||||
|
| `/srv/samba/winpeapps/_shared/BIOS/` | firmware EXEs + `models.txt` (served as the `winpeapps_bios` share) |
|
||||||
|
| `/srv/samba/winpeapps/_media/<pctype>/` | per-PCTYPE media views |
|
||||||
|
| `/srv/samba/enrollment/scripts/` | `run-enrollment.ps1`, `preflight.ps1`, `winpe-status-push.ps1` |
|
||||||
|
| `/srv/samba/enrollment/shopfloor-setup/` | per-PC-type trees, `menu.json`, `_ntlars-backups/` (147 `.reg`) |
|
||||||
|
| `/srv/samba/enrollment/shopfloor-setup/gea-shopfloor-waxtrace/bay-config.csv` | 15 bays, FTPak versions |
|
||||||
|
| `/srv/samba/enrollment/installers-post/cmm/cmm-bay-config.csv` | CMM bay -> version, DODA |
|
||||||
|
| `/srv/samba/enrollment/pre-install/udc-backups/` | per-bay UDC settings |
|
||||||
|
| `/srv/samba/enrollment/ppkgs/` | the SFLD package (8.2 GB) |
|
||||||
|
| `/srv/samba/enrollment/imaging-logs/<serial>/` | harvested imaging logs (added 2026-08-06) |
|
||||||
|
| `/var/www/html/win11/sources/boot.wim` | the booted WinPE image |
|
||||||
|
| `/var/log/pxe-imaging/<serial>.json` | per-bay stage history |
|
||||||
|
| `/opt/pxe-webapp/` | Flask webapp (unit `pxe-webapp`, :9009) |
|
||||||
|
| `/etc/dnsmasq.conf`, `/etc/samba/smb.conf` | the binding config §6.2 depends on |
|
||||||
|
|
||||||
|
### 13.3 shopdb-flask (`/home/camp/projects/shopdb-flask`)
|
||||||
|
|
||||||
|
| Path | What |
|
||||||
|
|---|---|
|
||||||
|
| `plugins/geenforce/seed_display_scope.py` | the pattern this proposal generalises - subtype map, server role by IP, sweep |
|
||||||
|
| `plugins/geenforce/client/` | `ShopdbEnforceClient.psm1`, `Invoke-ShopdbEnforce.ps1` |
|
||||||
|
| `shopdb/core/api/` | collector endpoint, dashboard-defaults display-role |
|
||||||
|
|
||||||
|
### 13.4 Evidence behind the claims in this document
|
||||||
|
|
||||||
|
| Path | What it shows |
|
||||||
|
|---|---|
|
||||||
|
| `/home/camp/pxe-images/now/pesetuplog.txt` | PESetup 4.0.0.20 run - media drive, disking, copy, driver selection, the `Btn 'Next'` timing in §7.1 |
|
||||||
|
| `/home/camp/pxe-images/now/debug.txt` | the WinPE console for the failed volume-finder run |
|
||||||
|
| `/home/camp/pxe-images/pxe2/Panther/` | `setupact.log` / `setuperr.log` naming the unattend `Path` failure |
|
||||||
|
| `/home/camp/pxe-images/debug2/` | the autologon registry state and the sysprep-loop timeline |
|
||||||
|
| `/home/camp/pxe-images/debug3/Logs/BPRT/` | the 13 package components, `criticalChecks.json`, `packageInfo.json` |
|
||||||
|
| `/home/camp/pxe-images/debug3/Logs/PPKG/` | the package itself, `provisioning-sessions.json`, the diagnostics evtx |
|
||||||
|
| `/home/camp/pxe-images/debug3/Logs/enrollment.log` | the double run and the rename race in §9 |
|
||||||
|
| `/home/camp/pxe-images/debug3/Logs/FilteredReportIP.log` | proves the bay had no route to Entra (§8.2) |
|
||||||
|
|
||||||
|
### 13.5 On-bay locations (for anyone diagnosing one)
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Enrollment\ staged payload, pc-type.txt, display-type.txt,
|
||||||
|
pc-config.txt, fetch-source.txt, winpe-staging.log
|
||||||
|
C:\Deploy\Applications\BPRT\ the packages + orchestrator, copied by PESetup
|
||||||
|
C:\Logs\BPRT\<Component>\ per-component package logs (UTF-16)
|
||||||
|
C:\Logs\BPRT\criticalChecks.json PPKG matched / Entra joined / OOBE execution
|
||||||
|
C:\Logs\PPKG\ provisioning diagnostics + session state
|
||||||
|
C:\Logs\SFLD\ DSC deployment, sync_intune, shopfloor-setup
|
||||||
|
C:\Logs\enrollment.log run-enrollment.ps1
|
||||||
|
C:\Windows\Panther\ setupact.log, setuperr.log, unattend.xml, PESetup*.log
|
||||||
|
C:\Windows\Panther\UnattendGC\ oobeSystem pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 13.5a Gotchas any new tooling will hit
|
||||||
|
|
||||||
|
- **`HardwareDriver.json` cannot be read by `ConvertFrom-Json`.** It carries both
|
||||||
|
casings of the same fields (`fileName`/`FileName`, `destinationDir`/
|
||||||
|
`DestinationDir`) and PowerShell rejects the document - on 5.1 *and* 7.
|
||||||
|
`-AsHashtable` is PS6+ and WinPE runs 5.1. Extract fields by regex, or parse in
|
||||||
|
Python where duplicate keys are tolerated.
|
||||||
|
- **`Compress-Archive` caps at 2 GB**, and `run-enrollment` harvests the 8 GB
|
||||||
|
provisioning package into `C:\Logs\PPKG`, so any log collection must exclude
|
||||||
|
`*.ppkg`.
|
||||||
|
- **Backslash-vs-forward-slash**: PowerShell Core normalises `\` to `/` on Linux,
|
||||||
|
so `Join-Path` with Windows separators works cross-platform - handy for testing
|
||||||
|
bay scripts on the dev box.
|
||||||
|
|
||||||
|
### 13.6 How to re-derive things
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# read a file off the share
|
||||||
|
sshpass -p pxe ssh -o StrictHostKeyChecking=no pxe@172.16.9.1 'cat <path>'
|
||||||
|
smbclient //172.16.9.1/enrollment -U pxe-upload%pxe -c 'ls'
|
||||||
|
|
||||||
|
# what is actually inside the booted WinPE
|
||||||
|
sudo wimextract /var/www/html/win11/sources/boot.wim 1 \
|
||||||
|
/Windows/System32/startnet.cmd --dest-dir=/tmp/x
|
||||||
|
sudo wimdir /var/www/html/win11/sources/boot.wim 1 | grep -i <name>
|
||||||
|
|
||||||
|
# a .ppkg is a WIM - pull out the spec without extracting 8 GB
|
||||||
|
7z l <pkg>.ppkg
|
||||||
|
7z e <pkg>.ppkg -o<dir> "Multivariant/0/customizations.xml" \
|
||||||
|
"Multivariant/0/Prov/RunTime/0__Accounts_Azure.provxml"
|
||||||
|
|
||||||
|
# provisioning diagnostics (python-evtx is installed)
|
||||||
|
python3 -c "from Evtx.Evtx import Evtx;
|
||||||
|
[print(r.xml()) for r in Evtx('Provisioning-Diagnostics-Admin.evtx').records()]"
|
||||||
|
|
||||||
|
# decompile PESetup - the bundle-extraction recipe is in PESETUP-INTERNALS.md
|
||||||
|
ilspycmd -t <TypeName> PESetup.dll
|
||||||
|
|
||||||
|
# validate before deploying
|
||||||
|
./scripts/preflight.py # every server-side lint
|
||||||
|
./scripts/share-drift.py --diff # repo vs live
|
||||||
|
```
|
||||||
|
|
||||||
|
### 13.7 The 2026-08-06 commits this document draws on
|
||||||
|
|
||||||
|
```
|
||||||
|
dfa026b lint-driver-catalogue c7b1769 build-pctype-media
|
||||||
|
049dd7b startnet per-PCTYPE media d5f7abc BIOS 7020 family
|
||||||
|
385acfe lint-unattend 2b6189f startnet W: wait + honest reporting
|
||||||
|
d04865d PESetup version + LogonCount b96ff7e retire shopfloor-dashboard route
|
||||||
|
36be60e run-enrollment stops renaming 8c21282 staging verify + log harvest
|
||||||
|
d2200e8 preflight (bay + server) 68df59e OWNERSHIP.md + share-drift
|
||||||
|
9d51c0b purpose menu + 3D kiosk a0aceb4 PXESERVER consolidation
|
||||||
|
bb08392 download-drivers case fix
|
||||||
|
```
|
||||||
144
docs/cyberark-cmm-doda-policy.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# CyberArk EPM - CMM / DODA elevation policy
|
||||||
|
|
||||||
|
Reference for the CyberArk EPM admin. Fixes the "PC-DMIS is elevated but the
|
||||||
|
tools it calls error that they are not running elevated" problem on CMM bays.
|
||||||
|
|
||||||
|
## Problem (root cause)
|
||||||
|
|
||||||
|
CyberArk EPM elevation is per-process and is NOT inherited by child processes.
|
||||||
|
The existing policy elevates PC-DMIS (`PCDLRN.exe`), but the external `.exe`
|
||||||
|
files the PC-DMIS routine spawns (report, geometry, and DODA tools) launch with
|
||||||
|
the standard user token (Medium integrity) and fail their own "must run
|
||||||
|
elevated" check, even though the parent PC-DMIS is elevated. The elevation dies
|
||||||
|
at the process boundary.
|
||||||
|
|
||||||
|
## Flow that breaks
|
||||||
|
|
||||||
|
The PC-DMIS routine drives in-process `.BAS` scripts that shell out to separate
|
||||||
|
executables:
|
||||||
|
|
||||||
|
| Step | Process | Separate process? |
|
||||||
|
|------|---------|-------------------|
|
||||||
|
| Merge / sort report results | `MergeFiles.exe` (.NET), reads `C:\Apps\DODA\PreProcess\` | yes |
|
||||||
|
| Geometry export | `PCDToIGES.exe`, `RotateProbeVector.exe` | yes |
|
||||||
|
| DODA calculation | `DovetailAnalysis.exe` (+ embedded JVM and python) | yes |
|
||||||
|
| RTF -> PDF, display | `winword.exe`, `AcroRd32.exe` | yes (removed by the BAS rework below) |
|
||||||
|
| Folder create / save | `MAKEDIR.BAS`, `MAKEFOLDER.bas`, `SaveAsFolder.bas` | no (in-process, uses PC-DMIS token) |
|
||||||
|
|
||||||
|
The in-process file operations inherit PC-DMIS's elevated token. The separate
|
||||||
|
`.exe` files do not. Those are what get blocked.
|
||||||
|
|
||||||
|
## Fix: one Application Group + one Elevate policy
|
||||||
|
|
||||||
|
Do NOT use "elevate all child processes of PC-DMIS". That would elevate
|
||||||
|
`cmd.exe` and anything PC-DMIS launches, which is a large hole on a locked-down
|
||||||
|
shopfloor PC. Elevate only the named toolchain.
|
||||||
|
|
||||||
|
### Application Group: CMM-DODA-Tools
|
||||||
|
|
||||||
|
These are in-house, unsigned tools, so match by SHA-256 (or by path + filename
|
||||||
|
if the install directory is admin-write-only and the confirmed path is known).
|
||||||
|
|
||||||
|
| App | SHA-256 | Spawns children? |
|
||||||
|
|-----|---------|------------------|
|
||||||
|
| `MergeFiles.exe` | `e58ce7599d3bdba816c7ecb183d4f52b32ad8be0b8e4f41813824d8eb472d723` | no |
|
||||||
|
| `PCDToIGES.exe` | `7bdc961c406f7a0f6f8a10752988a17504bdfd691469c08d20f0d5b6673974cf` | no |
|
||||||
|
| `RotateProbeVector.exe` | `f8a1b5b0025769fe0d28dc12826ef5d1fbcdba3b29383799c1eb04b955abebdc` | no |
|
||||||
|
| `DovetailAnalysis.exe` | `86dcb0898bdef4687427ce339520a9c9f5a582890c2241d784fa985019eaaec1` | yes (JVM + python) |
|
||||||
|
|
||||||
|
### Elevate policy
|
||||||
|
|
||||||
|
- Target: the `CMM-DODA-Tools` Application Group
|
||||||
|
- Action: Elevate (run with administrator rights)
|
||||||
|
- Applies to: the CMM computer set + the `ShopFloor` user
|
||||||
|
- Child processes: elevate ONLY for `DovetailAnalysis.exe` (it launches the
|
||||||
|
embedded JVM and python scripts that do the actual file work). The other
|
||||||
|
three have no children.
|
||||||
|
- Match basis: SHA-256 hash
|
||||||
|
|
||||||
|
## Explicitly NOT in the policy
|
||||||
|
|
||||||
|
`winword.exe`, `AcroRd32.exe`, `cmd.exe`. The `CREATE_PDF_FROM_RTF.BAS` rework
|
||||||
|
(Word writes the PDF to user `%TEMP%`, PC-DMIS moves it to the final path with
|
||||||
|
its own elevated in-process token, display via the default PDF handler) removes
|
||||||
|
their need for elevation. Keep Office and Reader out of the elevation set.
|
||||||
|
|
||||||
|
## What the EPM admin needs from GE
|
||||||
|
|
||||||
|
- The computer group = the CMM bays (hostname list, OU, or AD group)
|
||||||
|
- The user = `ShopFloor` (local account)
|
||||||
|
- If using path matching instead of hash: the confirmed install directory of the
|
||||||
|
four exes on a bay (`where MergeFiles.exe`)
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
- Before: from elevated PC-DMIS, spawn a child `cmd.exe`, then run
|
||||||
|
`whoami /groups | findstr Label`. Medium Mandatory Level confirms children are
|
||||||
|
not elevated (the bug).
|
||||||
|
- After: the four tools run at High Mandatory Level; report generation plus
|
||||||
|
copy/move/delete to C: and S: succeed; no "not elevated" error.
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
- Hash churn: rebuilding a tool changes its hash, so the Application Group hash
|
||||||
|
must be re-stamped. Path + filename matching avoids this IF the install
|
||||||
|
directory is admin-write-only (so a same-named spoof cannot be dropped there).
|
||||||
|
- Not an ACL fix: the tools hard-check elevation and bail before touching the
|
||||||
|
filesystem, so pre-granting NTFS ACLs alone will not unblock them. The EPM
|
||||||
|
elevation is the actual lever.
|
||||||
|
- Scope tight: match by hash and scope to the CMM computer group + ShopFloor so
|
||||||
|
this elevation never applies fleet-wide.
|
||||||
|
|
||||||
|
## Related code fixes (PXE imaging side)
|
||||||
|
|
||||||
|
- `09-Setup-CMM.ps1` Step 2.5 ACL list corrected from `C:\Program Files\DODA`
|
||||||
|
(nonexistent) to `C:\Apps\DODA` (where `Install-DODA.ps1` actually extracts).
|
||||||
|
- `MergeFiles.exe` expects `C:\Apps\DODA\PreProcess\`, which the DODA zip does
|
||||||
|
not create (it extracts flat). A missing `PreProcess` directory is the likely
|
||||||
|
cause of the historical `MergeFiles.GetDoDAFolder` `DirectoryNotFoundException`
|
||||||
|
crash (see `cmm-utilities` repo `dotNET event.txt`). Have `Install-DODA.ps1`
|
||||||
|
create the `PreProcess` subdir, or have whoever deploys the toolchain own it.
|
||||||
|
- The CMM tool chain (`MergeFiles.exe`, `PCDToIGES.exe`, `RotateProbeVector.exe`,
|
||||||
|
the `.BAS` scripts) lives in the separate `cmm-utilities` repo and is NOT
|
||||||
|
deployed by PXE imaging today. Decide whether imaging should own it so the
|
||||||
|
install path, ACLs, and `PreProcess` directory are consistent.
|
||||||
|
|
||||||
|
## CREATE_PDF_FROM_RTF.BAS rework (removes Word/Reader from the elevation set)
|
||||||
|
|
||||||
|
```vb
|
||||||
|
' CREATE_PDF_FROM_RTF.BAS - rev 1.0: convert in user temp, then move with
|
||||||
|
' PC-DMIS's own token; display via default handler (no elevation needed).
|
||||||
|
Sub Main(filename As String, displayReport As String)
|
||||||
|
Dim rtfFile As String, finalPdf As String, tempPdf As String
|
||||||
|
rtfFile = filename & ".RTF"
|
||||||
|
finalPdf = filename & ".PDF"
|
||||||
|
Dim base As String
|
||||||
|
base = Mid(filename, InStrRev(filename, "\") + 1)
|
||||||
|
tempPdf = Environ$("TEMP") & "\" & base & ".PDF"
|
||||||
|
|
||||||
|
' Word (un-elevated COM) can write to %TEMP% - a user-writable path.
|
||||||
|
Dim word As Object
|
||||||
|
Set word = CreateObject("word.application")
|
||||||
|
word.Visible = False
|
||||||
|
word.Documents.Open rtfFile
|
||||||
|
word.ActiveDocument.SaveAs2 tempPdf, 17 ' 17 = wdFormatPDF
|
||||||
|
word.Quit
|
||||||
|
Set word = Nothing
|
||||||
|
|
||||||
|
' Move temp -> final using PC-DMIS's in-process token (elevated via
|
||||||
|
' CyberArk), so the protected/S: destination is written without needing
|
||||||
|
' Word itself elevated. FileCopy works across volumes; Name does not.
|
||||||
|
If Dir(finalPdf) <> "" Then Kill finalPdf
|
||||||
|
FileCopy tempPdf, finalPdf
|
||||||
|
Kill tempPdf
|
||||||
|
|
||||||
|
' Display via the default PDF handler in the user's own context.
|
||||||
|
If UCase(displayReport) = "TRUE" Then
|
||||||
|
Dim sh As Object
|
||||||
|
Set sh = CreateObject("Shell.Application")
|
||||||
|
sh.ShellExecute finalPdf, "", "", "open", 1
|
||||||
|
End If
|
||||||
|
|
||||||
|
Kill rtfFile
|
||||||
|
End Sub
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
1065
docs/kb/How_To_Deploy_PXE_Server_New_Site.html
Normal file
296
docs/mtconnect-followup-runbook.md
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
# MTConnect follow-up runbook
|
||||||
|
|
||||||
|
Written 2026-08-11. Everything here needs a machine this box cannot reach - the
|
||||||
|
SFLD share, the middle-man PC, or a shopfloor bay - so it is yours to run.
|
||||||
|
|
||||||
|
Work in the order given. Part 1 changes what future installs get; parts 2 and 3
|
||||||
|
fix bays that are already wrong; parts 4 and 5 are questions to answer before
|
||||||
|
any more code is written.
|
||||||
|
|
||||||
|
### Identify every bay by machine number
|
||||||
|
|
||||||
|
The machine number is the only identifier that stays put. A PC gets replaced or
|
||||||
|
re-imaged and its name changes; the machine it sits at does not. Both machine-to-PC
|
||||||
|
sources used to build this document are point-in-time snapshots and will already
|
||||||
|
have drifted.
|
||||||
|
|
||||||
|
Every PC name below came from `mtconnect-hostname-machine-map.csv` (12 June),
|
||||||
|
the `Shop Floor PC Name` column of `West Jefferson_Machines.csv`, or the variant
|
||||||
|
captures. Treat them as "this is where the fault was last seen" - useful for
|
||||||
|
finding the bay, never for deciding what to write into it.
|
||||||
|
|
||||||
|
**The bay knows its own number.** In order of authority:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 1. eDNC registry - what GE-Enforce trusts post-imaging
|
||||||
|
(Get-ItemProperty 'HKLM:\SOFTWARE\WOW6432Node\GE Aircraft Engines\DNC\General' -Name MachineNo).MachineNo
|
||||||
|
# 2. the enrollment file imaging writes
|
||||||
|
Get-Content C:\Enrollment\machine-number.txt
|
||||||
|
# 3. UDC
|
||||||
|
(Get-Content C:\ProgramData\UDC\udc_settings.json -Raw | ConvertFrom-Json).GeneralSettings.MachineNumber
|
||||||
|
```
|
||||||
|
|
||||||
|
Or dot-source the lib and ask for both at once:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. C:\GE Aerospace\lib\Update-MachineNumber.ps1
|
||||||
|
Get-CurrentMachineNumber # @{ Udc = ...; Ednc = ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**ShopDB is the live PC-to-machine record**, maintained by the collector rather
|
||||||
|
than by hand, so it beats any CSV:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://tsgwp00525.wjs.geaerospace.net/shopdb/api/computers/by-hostname/<HOSTNAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
If those disagree with each other, or with the machine plate, stop and resolve
|
||||||
|
that first - a rename applied to the wrong number is worse than the mismatch
|
||||||
|
you started with.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1. Push the Fanuc adapter 3.25.3 to the SFLD share
|
||||||
|
|
||||||
|
The staged bundle here is already on 3.25.3.0 (`sha256` starts `6e46dcd51affa9e0`).
|
||||||
|
The live share is not. Until this is copied, new installs still get 3.23.0.0.
|
||||||
|
|
||||||
|
**1.1** From the middle-man PC, with `Z:` on the dev share:
|
||||||
|
|
||||||
|
```
|
||||||
|
Z:\mtconnect-bundles\fanuc\Adapter\
|
||||||
|
```
|
||||||
|
|
||||||
|
should contain `FanucCppAdapterWinX64.exe` at **3,369,984 bytes**. Confirm before copying:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
(Get-Item Z:\mtconnect-bundles\fanuc\Adapter\FanucCppAdapterWinX64.exe).Length
|
||||||
|
(Get-FileHash Z:\mtconnect-bundles\fanuc\Adapter\FanucCppAdapterWinX64.exe -Algorithm SHA256).Hash.Substring(0,16)
|
||||||
|
# expect 3369984 and 6E46DCD51AFFA9E0
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.2** Back up what is on the share now, then copy the three files:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$share = '\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\main\machineapps\mtconnect-bundles\fanuc\Adapter'
|
||||||
|
Copy-Item $share\FanucCppAdapterWinX64.exe "$share\..\FanucCppAdapterWinX64-3.23.0-replaced-$(Get-Date -f yyyyMMdd).exe"
|
||||||
|
Copy-Item Z:\mtconnect-bundles\fanuc\Adapter\FanucCppAdapterWinX64.exe $share -Force
|
||||||
|
Copy-Item Z:\mtconnect-bundles\fanuc\Adapter\Fwlib64.dll $share -Force
|
||||||
|
Copy-Item Z:\mtconnect-bundles\fanuc\Adapter\fwlibe64.dll $share -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
**1.3** Verify on the share:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
(Get-FileHash $share\FanucCppAdapterWinX64.exe -Algorithm SHA256).Hash.Substring(0,16) # 6E46DCD51AFFA9E0
|
||||||
|
```
|
||||||
|
|
||||||
|
`adapter.xml` is deliberately NOT copied. The config vocabulary is identical
|
||||||
|
between 3.23 and 3.25.3, and the share's copy carries site settings.
|
||||||
|
|
||||||
|
**Rollback:** copy the `-3.23.0-replaced-` file back over `FanucCppAdapterWinX64.exe`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2. Fix the two Makino bays
|
||||||
|
|
||||||
|
Both have the agent on the machine number and the other three files still on
|
||||||
|
`MAKINO-1`. The fixed `Update-MachineNumber.ps1` converges them.
|
||||||
|
|
||||||
|
| machine | PC when captured | confirm at the bay |
|
||||||
|
|---|---|---|
|
||||||
|
| **7502** | `G5W5V7V3ESF` | eDNC reg / enrollment file / plate |
|
||||||
|
| **7506** | `G4H8KF33ESF` | eDNC reg / enrollment file / plate |
|
||||||
|
|
||||||
|
Find the current PC for a machine number in ShopDB rather than trusting the
|
||||||
|
name above; if the PC was replaced, the new one may never have had the bad
|
||||||
|
rename applied, in which case check its four files before doing anything.
|
||||||
|
|
||||||
|
**2.1** On the bay, elevated PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. C:\GE Aerospace\lib\Update-MachineNumber.ps1 # or wherever the lib landed
|
||||||
|
$r = Update-MTConnectVariantName -NewNumber '7502' `
|
||||||
|
-Root 'C:\Makino-MTConnect' `
|
||||||
|
-Targets @(
|
||||||
|
@{ Path='Agent\Devices.xml'; Kind='attr' },
|
||||||
|
@{ Path='Agent\Devices.EDM.xml'; Kind='attr' },
|
||||||
|
@{ Path='Data\AdapterDataFile.xml'; Kind='name' },
|
||||||
|
@{ Path='Agent\Agent.cfg'; Kind='cfg' }
|
||||||
|
) `
|
||||||
|
-Services @('Makino Adapter Manager','Makino MTConnect Agent')
|
||||||
|
$r.Updated; $r.Errors
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the bay's own number - do not paste 7502 onto 7506.
|
||||||
|
|
||||||
|
**2.2** Verify all four agree, and nothing still says MAKINO:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Select-String -Path C:\Makino-MTConnect\Agent\Devices.xml,`
|
||||||
|
C:\Makino-MTConnect\Agent\Devices.EDM.xml,`
|
||||||
|
C:\Makino-MTConnect\Data\AdapterDataFile.xml,`
|
||||||
|
C:\Makino-MTConnect\Agent\Agent.cfg `
|
||||||
|
-Pattern 'MAKINO-\d+|7502'
|
||||||
|
```
|
||||||
|
|
||||||
|
**2.3** Confirm it is serving:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
(Invoke-WebRequest http://localhost:5000/current -UseBasicParsing).Content -match '<MTConnectDevices'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Watch for:** the vendor guide says the Adapter Manager rewrites its whole
|
||||||
|
configuration from memory when it stops. The function stops it first, but this
|
||||||
|
has never been proven on real hardware. If the names revert after a reboot,
|
||||||
|
stop here and tell me - that changes the approach.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3. Fix the seven Fanuc bays
|
||||||
|
|
||||||
|
The agent and the adapter disagree. Three have a correct agent and a stale
|
||||||
|
adapter; four have no agent device name at all and need looking at before any
|
||||||
|
rename (Part 4).
|
||||||
|
|
||||||
|
**Rename these three.** The "correct number" column is what the June map said -
|
||||||
|
it is a starting point, not authority. Confirm from the bay itself (see the
|
||||||
|
note at the top) before running anything:
|
||||||
|
|
||||||
|
| machine (per map) | adapter says | agent says | PC when captured |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **7801** | 4007 | 7801 | `G9N2JNZ3ESF` |
|
||||||
|
| **7804** | 3031 | 7804 | `GG1J98Y3ESF` |
|
||||||
|
| **2005 or 2006** | 2005 | 2006 | `GC7ZN7V3ESF` |
|
||||||
|
|
||||||
|
`GC7ZN7V3ESF` is the one to be careful with: 2005 and 2006 are a Hwacheon
|
||||||
|
dual-spindle pair sharing a PC, so two machines legitimately answer to one
|
||||||
|
hostname and the map and the agent disagree about which. Read the machine plate
|
||||||
|
and the eDNC registry before running it.
|
||||||
|
|
||||||
|
**3.1** On the bay, elevated:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
. C:\GE Aerospace\lib\Update-MachineNumber.ps1
|
||||||
|
$r = Update-MTConnectVariantName -NewNumber '7801' `
|
||||||
|
-Root 'C:\MTConnect' `
|
||||||
|
-Targets @(
|
||||||
|
@{ Path='Agent\devices.xml'; Kind='attr' },
|
||||||
|
@{ Path='Adapter\devices.xml'; Kind='attr' },
|
||||||
|
@{ Path='Adapter\adapter.xml'; Kind='elem' }
|
||||||
|
) `
|
||||||
|
-Services @('MTConnect Adapter Fanuc','MTConnect Agent Fanuc')
|
||||||
|
$r.Updated; $r.Errors
|
||||||
|
```
|
||||||
|
|
||||||
|
**3.2** Verify the three files agree:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Select-String -Path C:\MTConnect\Agent\devices.xml,C:\MTConnect\Adapter\devices.xml `
|
||||||
|
-Pattern 'name="[^"]+"' | Select-Object -First 2
|
||||||
|
Select-String -Path C:\MTConnect\Adapter\adapter.xml -Pattern '<Device(Name|ID|UUID)>[^<]+'
|
||||||
|
```
|
||||||
|
|
||||||
|
All should read the same number. Then the HTTP probe as in 2.3 (port 5000).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4. Four Fanuc bays with no agent device name
|
||||||
|
|
||||||
|
| machine (per map) | adapter says | PC when captured |
|
||||||
|
|---|---|---|
|
||||||
|
| **2013** | 4007 | `GJBJC724ESF` |
|
||||||
|
| **7606** | 3126 | `G7QLY5X3ESF` |
|
||||||
|
| **4002** | 4002 | `G6JLMSZ2ESF` |
|
||||||
|
| **2001** | 3115 | `GB07T5X3ESF` |
|
||||||
|
|
||||||
|
These are not a rename problem - the agent's `devices.xml` has no `<Device
|
||||||
|
name=...>` at all, so the agent has nothing to serve. Do NOT run Part 3 on them.
|
||||||
|
|
||||||
|
On each, collect and send back:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Service 'MTConnect*' | Select-Object Name,Status,StartType
|
||||||
|
Get-Content C:\MTConnect\Agent\devices.xml -TotalCount 20
|
||||||
|
Test-Path C:\MTConnect\Agent\devices.xml
|
||||||
|
(Invoke-WebRequest http://localhost:5000/current -UseBasicParsing -TimeoutSec 5).StatusCode
|
||||||
|
Get-Content C:\MTConnect\Agent\agent.log -Tail 30
|
||||||
|
```
|
||||||
|
|
||||||
|
Note three of the four also disagree with the hostname map on the number
|
||||||
|
(4007 vs 2013, 3126 vs 7606, 3115 vs 2001), so these bays may have been
|
||||||
|
re-purposed without a full reconfigure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 5. Two questions to settle
|
||||||
|
|
||||||
|
**5.1 DMG Mori - do they need a bundle at all?**
|
||||||
|
|
||||||
|
Five machines, all flagged `1,2,3` in the machine list, no bundle among the
|
||||||
|
five we ship, and no captured variant. Their controllers are `DMG Mori F31i
|
||||||
|
A5/B5` - Fanuc 31i underneath - so the Fanuc adapter may already serve them.
|
||||||
|
|
||||||
|
| machine | PC per the machine list |
|
||||||
|
|---|---|
|
||||||
|
| WJ-7401 | `G1KDN7X2ESF` |
|
||||||
|
| WJ-7402 | `G1KCR7X2ESF` |
|
||||||
|
| WJ-7403 | `G1HYK7X2ESF` |
|
||||||
|
| WJ-7404 | `G1HGQ7X2ESF` |
|
||||||
|
| WJ-7405 | `G1KLM7X2ESF` |
|
||||||
|
|
||||||
|
Those names come from the `Shop Floor PC Name` column of the OneDrive machine
|
||||||
|
list and are subject to the same staleness - look the machine up in ShopDB for
|
||||||
|
the PC that is actually on it today.
|
||||||
|
|
||||||
|
On any one of them:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-Service | Where-Object { $_.Name -match 'MTConnect|Makino|Coordinator' } |
|
||||||
|
Select-Object Name,Status,StartType
|
||||||
|
Get-ChildItem C:\ -Directory | Where-Object Name -match 'MTConnect|DMG|Coordinator'
|
||||||
|
(Invoke-WebRequest http://localhost:5000/current -UseBasicParsing -TimeoutSec 5).StatusCode
|
||||||
|
```
|
||||||
|
|
||||||
|
If they run the Fanuc bundle already, no new variant is needed and the DMG
|
||||||
|
Coordinator MSI in the OneDrive zip can be ignored. If they collect some other
|
||||||
|
way, or not at all, that is the answer to whether a sixth bundle is worth
|
||||||
|
building.
|
||||||
|
|
||||||
|
**5.2 Does the live v2 manifest carry the MTConnect entries?**
|
||||||
|
|
||||||
|
You are on v2. The concern is that the MTConnect entries were left behind in
|
||||||
|
the dead v1 manifest, in which case v2 bays are not getting MTConnect from the
|
||||||
|
manifest engine at all.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$m = Get-Content '\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\main\machineapps\machineapps-manifest.json' -Raw | ConvertFrom-Json
|
||||||
|
$m.Applications.Count
|
||||||
|
$m.Applications | ForEach-Object { $_.Name }
|
||||||
|
```
|
||||||
|
|
||||||
|
Expect 8 applications, five of them MTConnect (fanuc, okuma, edncStandard,
|
||||||
|
edncDualSpindle, makino). Anything less and the missing ones never made it to
|
||||||
|
v2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A note on the CSVs
|
||||||
|
|
||||||
|
Neither machine-to-PC list will age well: `mtconnect-hostname-machine-map.csv`
|
||||||
|
(12 June) and the `Shop Floor PC Name` column in `West Jefferson_Machines.csv`
|
||||||
|
are both hand-built snapshots, and every PC swap silently invalidates a row.
|
||||||
|
|
||||||
|
ShopDB now maintains the PC-to-machine link from collector reports, so it
|
||||||
|
corrects itself as bays are re-imaged. Anything that needs to go from a machine
|
||||||
|
number to "which PC is on it today" should ask ShopDB; if a CSV is still wanted
|
||||||
|
for offline use, generate it from ShopDB rather than editing it by hand.
|
||||||
|
|
||||||
|
## What I still owe you
|
||||||
|
|
||||||
|
- Auto-detection of controller and variant for a new machine. The inputs exist
|
||||||
|
(`mtconnect-bay-config.csv` has controller IP and FOCAS port per machine, the
|
||||||
|
machine list has the controller model, the collector already knows the machine
|
||||||
|
number at imaging time) - design pending.
|
||||||
|
- The code fix is committed and pushed as `0d9c4fd` on `pxe-webapp-redesign`.
|
||||||
|
It changes future renames only; it does not reach out and fix these bays.
|
||||||
47
docs/post-deploy-checklist.live.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Post-Deploy Verification Checklist</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.css">
|
||||||
|
<style>
|
||||||
|
body { box-sizing: border-box; max-width: 980px; margin: 2em auto; padding: 0 2em; }
|
||||||
|
.markdown-body img { max-width: 100%; }
|
||||||
|
.markdown-body ul.task-list { list-style: none; padding-left: 0; }
|
||||||
|
.markdown-body li.task-list-item { list-style: none; }
|
||||||
|
.markdown-body li.task-list-item input[type=checkbox] { margin-right: .5em; transform: scale(1.2); }
|
||||||
|
@media print { body { max-width: none; margin: 0; padding: 1em; } }
|
||||||
|
</style>
|
||||||
|
</head><body class="markdown-body">
|
||||||
|
<h1 id="post-deploy-checklist">Post-Deploy Checklist</h1>
|
||||||
|
<p>Run after first boot. Sign off when all boxes checked. Fail any step, see <a href="post-deploy-debug-flowchart.md">post-deploy-debug-flowchart.md</a>.</p>
|
||||||
|
<h2 id="1-common-shop-floor">1. Common Shop Floor</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Shopfloor Tools</code> then <code>WJ Shopfloor</code> opens</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Login prompt and menu screen render</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail: check corp network, ping <code>WJFMS3.AE.GE.COM</code>.</p>
|
||||||
|
<h2 id="2-controller-skip-if-standalone-pc">2. Controller (skip if standalone PC)</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>ping 192.168.1.1</code> returns 4 of 4 replies</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> NTLARS General tab populated</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> NTLARS FMS Host Primary set to <code>WJFMS3.AE.GE.COM</code> (FQDN)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail ping, see <a href="post-deploy-debug-flowchart.md#2b-controller-nic-has-no-static-ip">2B NIC</a>.
|
||||||
|
Blank General, see <a href="post-deploy-debug-flowchart.md#2a-ntlars-reg-file-never-imported-blank-general-tab-fields">2A reg load</a>.</p>
|
||||||
|
<h2 id="3-udc-com-port">3. UDC COM port</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> UDC opens with no machine-communication error dialog</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Tools</code> then <code>Retry Connection</code> succeeds (no error)</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Port Name matches physical port: onboard COM1, PCIe card COM2 or COM4</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Data lines populate after reopen</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail, see <a href="post-deploy-debug-flowchart.md#step-4-set-the-correct-com-port">Step 4</a>.</p>
|
||||||
|
<h2 id="4-printers-genspect-all-nearby">4. Printers (Genspect: all nearby)</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Install Printers</code> shortcut opens</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Each nearby printer installed, status Ready</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Test page prints</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Genspect: default printer set</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail, see <a href="../printer-mapping.md">printer-mapping.md</a>.</p>
|
||||||
|
</body></html>
|
||||||
37
docs/post-deploy-checklist.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Post-Deploy Checklist
|
||||||
|
|
||||||
|
Run after first boot. Sign off when all boxes checked. Fail any step, see [post-deploy-debug-flowchart.md](post-deploy-debug-flowchart.md).
|
||||||
|
|
||||||
|
## 1. Common Shop Floor
|
||||||
|
|
||||||
|
- [ ] `Shopfloor Tools` then `WJ Shopfloor` opens
|
||||||
|
- [ ] Login prompt and menu screen render
|
||||||
|
|
||||||
|
Fail: check corp network, ping `WJFMS3.AE.GE.COM`.
|
||||||
|
|
||||||
|
## 2. Controller (skip if standalone PC)
|
||||||
|
|
||||||
|
- [ ] `ping 192.168.1.1` returns 4 of 4 replies
|
||||||
|
- [ ] NTLARS General tab populated
|
||||||
|
- [ ] NTLARS FMS Host Primary set to `WJFMS3.AE.GE.COM` (FQDN)
|
||||||
|
|
||||||
|
Fail ping, see [2B NIC](post-deploy-debug-flowchart.md#2b-controller-nic-has-no-static-ip).
|
||||||
|
Blank General, see [2A reg load](post-deploy-debug-flowchart.md#2a-ntlars-reg-file-never-imported-blank-general-tab-fields).
|
||||||
|
|
||||||
|
## 3. UDC COM port
|
||||||
|
|
||||||
|
- [ ] UDC opens with no machine-communication error dialog
|
||||||
|
- [ ] `Tools` then `Retry Connection` succeeds (no error)
|
||||||
|
- [ ] Port Name matches physical port: onboard COM1, PCIe card COM2 or COM4
|
||||||
|
- [ ] Data lines populate after reopen
|
||||||
|
|
||||||
|
Fail, see [Step 4](post-deploy-debug-flowchart.md#step-4-set-the-correct-com-port).
|
||||||
|
|
||||||
|
## 4. Printers (Genspect: all nearby)
|
||||||
|
|
||||||
|
- [ ] `Install Printers` shortcut opens
|
||||||
|
- [ ] Each nearby printer installed, status Ready
|
||||||
|
- [ ] Test page prints
|
||||||
|
- [ ] Genspect: default printer set
|
||||||
|
|
||||||
|
Fail, see [printer-mapping.md](../printer-mapping.md).
|
||||||
59
docs/post-deploy-checklist.static.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Post-Deploy Verification Checklist</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, system-ui, "Segoe UI", sans-serif; max-width: 980px; margin: 2em auto; padding: 0 2em; line-height: 1.5; color: #24292f; }
|
||||||
|
h1, h2, h3 { border-bottom: 1px solid #d0d7de; padding-bottom: .3em; scroll-margin-top: 1em; }
|
||||||
|
h1 { font-size: 2em; }
|
||||||
|
code { background: #f6f8fa; padding: .2em .4em; border-radius: 6px; font-size: 85%; }
|
||||||
|
pre { background: #f6f8fa; padding: 1em; border-radius: 6px; overflow: auto; }
|
||||||
|
pre code { background: none; padding: 0; }
|
||||||
|
table { border-collapse: collapse; }
|
||||||
|
table th, table td { border: 1px solid #d0d7de; padding: 6px 13px; }
|
||||||
|
table tr:nth-child(2n) { background: #f6f8fa; }
|
||||||
|
img { max-width: 100%; height: auto; }
|
||||||
|
blockquote { border-left: .25em solid #d0d7de; padding: 0 1em; color: #57606a; }
|
||||||
|
ul.task-list, li.task-list-item { list-style: none; }
|
||||||
|
ul.task-list { padding-left: 0; }
|
||||||
|
li.task-list-item input[type=checkbox] { margin-right: .5em; transform: scale(1.2); }
|
||||||
|
hr { border: 0; border-top: 1px solid #d0d7de; margin: 2em 0; }
|
||||||
|
@media print {
|
||||||
|
body { max-width: none; margin: 0; padding: 1em; }
|
||||||
|
input[type=checkbox] { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head><body>
|
||||||
|
<h1 id="post-deploy-checklist">Post-Deploy Checklist</h1>
|
||||||
|
<p>Run after first boot. Sign off when all boxes checked. Fail any step, see <a href="post-deploy-debug-flowchart.md">post-deploy-debug-flowchart.md</a>.</p>
|
||||||
|
<h2 id="1-common-shop-floor">1. Common Shop Floor</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Shopfloor Tools</code> then <code>WJ Shopfloor</code> opens</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Login prompt and menu screen render</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail: check corp network, ping <code>WJFMS3.AE.GE.COM</code>.</p>
|
||||||
|
<h2 id="2-controller-skip-if-standalone-pc">2. Controller (skip if standalone PC)</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>ping 192.168.1.1</code> returns 4 of 4 replies</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> NTLARS General tab populated</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> NTLARS FMS Host Primary set to <code>WJFMS3.AE.GE.COM</code> (FQDN)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail ping, see <a href="post-deploy-debug-flowchart.md#2b-controller-nic-has-no-static-ip">2B NIC</a>.
|
||||||
|
Blank General, see <a href="post-deploy-debug-flowchart.md#2a-ntlars-reg-file-never-imported-blank-general-tab-fields">2A reg load</a>.</p>
|
||||||
|
<h2 id="3-udc-com-port">3. UDC COM port</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> UDC opens with no machine-communication error dialog</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Tools</code> then <code>Retry Connection</code> succeeds (no error)</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Port Name matches physical port: onboard COM1, PCIe card COM2 or COM4</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Data lines populate after reopen</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail, see <a href="post-deploy-debug-flowchart.md#step-4-set-the-correct-com-port">Step 4</a>.</p>
|
||||||
|
<h2 id="4-printers-genspect-all-nearby">4. Printers (Genspect: all nearby)</h2>
|
||||||
|
<ul class="task-list">
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> <code>Install Printers</code> shortcut opens</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Each nearby printer installed, status Ready</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Test page prints</li>
|
||||||
|
<li class="task-list-item"><input type="checkbox" disabled> Genspect: default printer set</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fail, see <a href="../printer-mapping.md">printer-mapping.md</a>.</p>
|
||||||
|
</body></html>
|
||||||
@@ -24,7 +24,7 @@ flowchart TD
|
|||||||
U2 --> U3[Tools - Options - Serial tab]
|
U2 --> U3[Tools - Options - Serial tab]
|
||||||
U3 --> U4{Which physical COM port is the cable in?}
|
U3 --> U4{Which physical COM port is the cable in?}
|
||||||
U4 -->|Intel / onboard| U5[Set Port Name = COM 1]
|
U4 -->|Intel / onboard| U5[Set Port Name = COM 1]
|
||||||
U4 -->|PCIe add-in card| U6[Set Port Name = COM 2]
|
U4 -->|PCIe add-in card| U6[Set Port Name = COM 2 or COM 4]
|
||||||
U5 --> U7[Save - File - Exit - reopen UDC]
|
U5 --> U7[Save - File - Exit - reopen UDC]
|
||||||
U6 --> U7
|
U6 --> U7
|
||||||
U7 --> U8([Verify data lines populate])
|
U7 --> U8([Verify data lines populate])
|
||||||
@@ -51,7 +51,7 @@ flowchart TD
|
|||||||
click U3 "#step-3-open-options" "Step 3 - Options"
|
click U3 "#step-3-open-options" "Step 3 - Options"
|
||||||
click U4 "#step-4-set-the-correct-com-port" "Step 4 - COM port"
|
click U4 "#step-4-set-the-correct-com-port" "Step 4 - COM port"
|
||||||
click U5 "#step-4-set-the-correct-com-port" "Step 4 - COM 1"
|
click U5 "#step-4-set-the-correct-com-port" "Step 4 - COM 1"
|
||||||
click U6 "#step-4-set-the-correct-com-port" "Step 4 - COM 2"
|
click U6 "#step-4-set-the-correct-com-port" "Step 4 - COM 2 or COM 4"
|
||||||
click U7 "#step-5-exit-to-apply" "Step 5 - Exit"
|
click U7 "#step-5-exit-to-apply" "Step 5 - Exit"
|
||||||
|
|
||||||
click D2 "#2a-ntlars-reg-file-never-imported-blank-general-tab-fields" "2A - Load reg backup"
|
click D2 "#2a-ntlars-reg-file-never-imported-blank-general-tab-fields" "2A - Load reg backup"
|
||||||
@@ -113,7 +113,7 @@ Click the **Serial** tab on the left. Set **Port Name** to match the **physical*
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- **COM 2** = PCIe add-in serial card
|
- **COM 2 or COM 4** = PCIe add-in serial card (Windows enumeration varies by hardware - check Device Manager -> Ports if unsure)
|
||||||
|
|
||||||
Logical:
|
Logical:
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ Reopen UDC. Data lines should start populating as the machine runs.
|
|||||||
### If data still does not appear
|
### If data still does not appear
|
||||||
|
|
||||||
Once the COM port is correct, ~~rule out~~ check these in order:
|
Once the COM port is correct, ~~rule out~~ check these in order:
|
||||||
- Wrong physical cable - cable is in COM1 socket but Port Name set to COM 2 (or vice versa). Re-check Step 4.
|
- Wrong physical cable - cable is in COM1 socket but Port Name set to COM 2 / COM 4 (or vice versa). Re-check Step 4.
|
||||||
- Cable / connector damaged - swap with a known-good cable.
|
- Cable / connector damaged - swap with a known-good cable.
|
||||||
- Machine controller side not transmitting - confirm at the controller HMI.
|
- Machine controller side not transmitting - confirm at the controller HMI.
|
||||||
|
|
||||||
|
|||||||
203
docs/pxe-nic-guard.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# PXE NIC guard
|
||||||
|
|
||||||
|
The PXE server's DHCP/PXE (dnsmasq) must never serve on the GE corporate
|
||||||
|
network. Each server has two NICs:
|
||||||
|
|
||||||
|
| Role | Network | Address |
|
||||||
|
|---------|-----------------------------|----------------------------------|
|
||||||
|
| imaging | isolated imaging switch | static 172.16.9.1/24, dnsmasq |
|
||||||
|
| ge | GE corporate network | DHCP client (systemd-networkd) |
|
||||||
|
|
||||||
|
`/etc/dnsmasq.d/pxe-nic-only.conf` pins dnsmasq to the imaging NIC by name.
|
||||||
|
That stops dnsmasq answering on the GE NIC, but not on the imaging NIC when
|
||||||
|
that NIC itself ends up on the wrong network: a cable swap, an imaging
|
||||||
|
switch uplinked into a GE port, or a second PXE server on the same switch.
|
||||||
|
The NIC guard catches those.
|
||||||
|
|
||||||
|
## What is installed
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|-----------------------------------------------------------|---------------------------------|
|
||||||
|
| `/usr/local/sbin/pxe-nic-check` | the classifier |
|
||||||
|
| `/etc/pxe-server/nic.conf` | which NIC is which |
|
||||||
|
| `/etc/systemd/system/dnsmasq.service.d/nic-guard.conf` | guard before every dnsmasq start|
|
||||||
|
| `/etc/systemd/system/pxe-nic-check.{service,timer}` | watch every 5 min |
|
||||||
|
| `/usr/local/sbin/pxe-nic-carrier` | re-check on imaging cable moves |
|
||||||
|
| `/etc/networkd-dispatcher/<state>.d/50-pxe-nic-carrier` | calls it on link changes |
|
||||||
|
| `/etc/systemd/system/pxe-nic-carrier.service` | the re-check after carrier gain |
|
||||||
|
| `/run/pxe-nic-status.json` | last result, read by the webapp |
|
||||||
|
|
||||||
|
Sources: `playbook/pxe-server-helpers/`, installed by `pxe_server_setup.yml`
|
||||||
|
(vars `pxe_nic_mac`, `corp_nic_mac`, `pxe_corp_domain_suffixes`,
|
||||||
|
`pxe_nic_allowed_switches`; `pxe_imaging_mac` / `pxe_ge_mac` are derived
|
||||||
|
from the first two). The same MACs pin the NICs in netplan
|
||||||
|
(`playbook/templates/netplan-pxe.yaml.j2`, names `pxe0` / `corp0`) and in
|
||||||
|
`/etc/dnsmasq.d/pxe-nic-only.conf`. With `pxe_nic_mac` empty the playbook
|
||||||
|
picks the imaging NIC at install time with `pxe-pick-nic --auto`, which
|
||||||
|
runs this check on the one port with a link (`--iface X --probe`, 65 s)
|
||||||
|
and accepts only "isolated". With `corp_nic_mac` empty `ge_mac` is blank
|
||||||
|
and the GE NIC is whichever other port holds a DHCP lease.
|
||||||
|
|
||||||
|
## How it decides
|
||||||
|
|
||||||
|
It listens on each NIC (raw socket, sends nothing; it joins the STP,
|
||||||
|
802.1X, LLDP, CDP and PVST+ multicast groups so a NIC that is not
|
||||||
|
promiscuous still hears them) for:
|
||||||
|
|
||||||
|
- LLDP and CDP: managed switches announce themselves. GE switches do; the
|
||||||
|
unmanaged imaging switch does not.
|
||||||
|
- 802.1X EAPOL from an authenticator: GE ports ask for port
|
||||||
|
authentication (EAP Request, Success or Failure). EAPOL that clients
|
||||||
|
send is ignored: iPXE sends three EAPOL-Starts every time it opens a
|
||||||
|
NIC and Windows sends Starts and EAP Responses, so a bay PXE-booting on
|
||||||
|
the imaging switch is not mistaken for a GE port. Those frames show up
|
||||||
|
only as "N client EAPOL frames ignored".
|
||||||
|
- DHCP OFFER/ACK from any other server.
|
||||||
|
- STP and the count of distinct ARP hosts: reported, never decisive.
|
||||||
|
|
||||||
|
On the imaging NIC it also sends one DHCPDISCOVER and collects offers for
|
||||||
|
6 s. On the GE NIC it reads the networkd lease instead (no traffic).
|
||||||
|
|
||||||
|
| Verdict | Meaning |
|
||||||
|
|----------------|------------------------------------------------------------|
|
||||||
|
| `isolated` | no foreign DHCP, no LLDP/CDP/EAPOL |
|
||||||
|
| `ge` | LLDP, CDP or EAPOL seen, or a DHCP server whose domain ends in a corp suffix (`geaerospace.net`, `ge.com`; whole labels, so `notgeaerospace.net` does not count) |
|
||||||
|
| `foreign-dhcp` | another DHCP server, no corp domain |
|
||||||
|
| `no-link` | no carrier |
|
||||||
|
| `error` | the check itself failed |
|
||||||
|
|
||||||
|
Healthy: imaging NIC `isolated` (or `no-link`), GE NIC `ge`.
|
||||||
|
|
||||||
|
Our own dnsmasq never counts. An offer whose server id is our own address
|
||||||
|
but comes from a MAC that is not ours is a second PXE server and DOES count.
|
||||||
|
|
||||||
|
If the imaging switch is itself a managed switch that sends LLDP/CDP, put
|
||||||
|
its LLDP system name, CDP device id or chassis MAC in `allowed_switches`
|
||||||
|
(`pxe_nic_allowed_switches`), or the guard will block dnsmasq. 802.1X is
|
||||||
|
never allowed.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
- `pxe-nic-check` - check every NIC (60 s), print a summary. `--json`,
|
||||||
|
`--write`, `--probe` (imaging DHCP probe), `--iface NAME`, `--window S`.
|
||||||
|
- `pxe-nic-check --guard` - dnsmasq `ExecStartPre`. Imaging NIC only,
|
||||||
|
about 6 s. Exit 1 = do not serve: dnsmasq does not start. A bug or no
|
||||||
|
link exits 0 (fail open) with an error line in the journal.
|
||||||
|
- `pxe-nic-check --watch` - the timer. On a bad imaging verdict it runs
|
||||||
|
`systemctl stop dnsmasq` and logs CRIT. It never starts dnsmasq. If
|
||||||
|
dnsmasq was (re)started during the 60 s window (an operator, or the
|
||||||
|
carrier hook, restarted it and its guard passed), the watch re-checks
|
||||||
|
for about 6 s and stops dnsmasq only if that still says blocked; if
|
||||||
|
dnsmasq is already stopped it only logs.
|
||||||
|
- `pxe-nic-carrier` - the networkd-dispatcher hook. Only the imaging NIC
|
||||||
|
(or its bridge ports) counts; GE NIC events are ignored. Carrier lost:
|
||||||
|
dnsmasq is stopped at once if it was running. Carrier back: after 2 s,
|
||||||
|
`pxe-nic-carrier.service` restarts dnsmasq if it was running or the hook
|
||||||
|
stopped it, so the guard checks the new network before dnsmasq serves
|
||||||
|
again; dnsmasq that an operator or the watch stopped stays stopped.
|
||||||
|
A bridge with several ports stops dnsmasq when any one of them loses
|
||||||
|
carrier. Log: `journalctl -t pxe-nic-carrier`.
|
||||||
|
|
||||||
|
Every start or restart of dnsmasq runs the guard, so `systemctl restart
|
||||||
|
dnsmasq` and the `@reboot` cron restart take about 6 s longer than before.
|
||||||
|
|
||||||
|
## Reading it
|
||||||
|
|
||||||
|
sudo pxe-nic-check # full summary, 60 s
|
||||||
|
cat /run/pxe-nic-status.json # last guard/watch result
|
||||||
|
journalctl -u dnsmasq -b | grep pxe-nic-check
|
||||||
|
journalctl -u pxe-nic-check -b
|
||||||
|
journalctl -t pxe-nic-carrier -b # cable unplug / replug
|
||||||
|
|
||||||
|
## dnsmasq will not start
|
||||||
|
|
||||||
|
1. `journalctl -u dnsmasq -b | grep pxe-nic-check` shows the evidence, e.g.
|
||||||
|
`REFUSING to start dnsmasq: imaging NIC br-pxe verdict ge: LLDP from
|
||||||
|
WJ-IDF2 port Gi1/0/14`.
|
||||||
|
2. Check the imaging cabling: the imaging NIC must go to the imaging
|
||||||
|
switch only, and that switch must not be uplinked anywhere.
|
||||||
|
3. `sudo pxe-nic-check` until the imaging NIC reads `isolated`.
|
||||||
|
4. `sudo systemctl start dnsmasq` (runs the guard again).
|
||||||
|
|
||||||
|
## Floor tests (run with the floor idle)
|
||||||
|
|
||||||
|
All three tests stop imaging for a few minutes. Have console or GE-side SSH
|
||||||
|
access to the server; do not rely on the imaging network for the session.
|
||||||
|
|
||||||
|
### (a) Rogue DHCP on the imaging switch: expect the alarm
|
||||||
|
|
||||||
|
1. Confirm the baseline: `sudo pxe-nic-check --probe` shows the imaging
|
||||||
|
NIC `isolated` and `dnsmasq_bound` true.
|
||||||
|
2. Plug a device that runs a DHCP server into a free port of the IMAGING
|
||||||
|
switch (a spare home router with DHCP on, LAN port only, nothing in its
|
||||||
|
WAN port; or a laptop running dnsmasq).
|
||||||
|
3. Run the watch now instead of waiting up to 5 min:
|
||||||
|
`sudo systemctl start pxe-nic-check.service`
|
||||||
|
4. Expect:
|
||||||
|
- `journalctl -u pxe-nic-check -n 20` has a CRIT line `imaging NIC is
|
||||||
|
NOT isolated, stopping dnsmasq: ... verdict foreign-dhcp: DHCP from
|
||||||
|
192.168.x.1, ...` (or `ge` if the router sends LLDP).
|
||||||
|
- `systemctl is-active dnsmasq` is `inactive`.
|
||||||
|
- `/run/pxe-nic-status.json` has `"dhcp_guard": {"state": "blocked", ...}`.
|
||||||
|
5. Prove the guard too: `sudo systemctl start dnsmasq` must FAIL, and
|
||||||
|
`journalctl -u dnsmasq -n 20` shows `REFUSING to start dnsmasq`.
|
||||||
|
6. Unplug the rogue device. `sudo pxe-nic-check --probe` shows `isolated`.
|
||||||
|
`sudo systemctl start dnsmasq` starts. PXE-boot one client to confirm.
|
||||||
|
|
||||||
|
### (b) Cable swap into a GE port: expect the guard to refuse dnsmasq
|
||||||
|
|
||||||
|
dnsmasq must be stopped BEFORE the cable moves, so it never serves on GE.
|
||||||
|
|
||||||
|
1. `sudo systemctl stop dnsmasq`; `systemctl is-active dnsmasq` must say
|
||||||
|
`inactive`. Do not reboot during the test (the `@reboot` cron restarts
|
||||||
|
dnsmasq; the guard would still block it, but do not test that way).
|
||||||
|
2. Move the IMAGING NIC's cable from the imaging switch into a live GE
|
||||||
|
network port. Wait for link: `ip -br link` shows the imaging NIC `UP`.
|
||||||
|
The carrier hook sees the link come up and runs the guard once (one
|
||||||
|
DHCPDISCOVER) to refresh the status file; with dnsmasq stopped it does
|
||||||
|
not start it. `journalctl -t pxe-nic-carrier -n 5` shows `dnsmasq was
|
||||||
|
not running, left stopped`.
|
||||||
|
3. Run the guard by hand first:
|
||||||
|
`sudo pxe-nic-check --guard; echo "exit $?"`
|
||||||
|
Expect `exit 1` and a `REFUSING to start dnsmasq` line naming LLDP/CDP,
|
||||||
|
802.1X, or `DHCP from 10.x.x.x, domain ....geaerospace.net`.
|
||||||
|
This sends one DHCPDISCOVER on the GE port, the same as any PC being
|
||||||
|
plugged in. To send nothing, use `--no-probe --window 60` instead and
|
||||||
|
rely on LLDP/CDP/802.1X (switches announce every 30-60 s).
|
||||||
|
**If it exits 0, STOP: do not start dnsmasq. Move the cable back and
|
||||||
|
send the output of `sudo pxe-nic-check --probe --json`.**
|
||||||
|
4. Only after step 3 exited 1: `sudo systemctl start dnsmasq`. Expect
|
||||||
|
`Job for dnsmasq.service failed`; `systemctl is-active dnsmasq` says
|
||||||
|
`failed`; `journalctl -u dnsmasq -n 20` shows `REFUSING`.
|
||||||
|
5. Move the cable back to the imaging switch. `sudo pxe-nic-check --probe`
|
||||||
|
shows the imaging NIC `isolated`. `sudo systemctl start dnsmasq`
|
||||||
|
starts. PXE-boot one client to confirm.
|
||||||
|
|
||||||
|
### (c) Imaging cable unplugged and replugged: expect stop, re-check, start
|
||||||
|
|
||||||
|
1. dnsmasq running, imaging NIC `isolated`.
|
||||||
|
2. Unplug the imaging NIC's cable. Within a second
|
||||||
|
`journalctl -t pxe-nic-carrier -n 5` shows `carrier lost on imaging NIC
|
||||||
|
... stopped dnsmasq` and `systemctl is-active dnsmasq` is `inactive`.
|
||||||
|
3. Plug it back into the imaging switch. About 10 s later the journal
|
||||||
|
shows `guard passed, dnsmasq (re)started` and dnsmasq is `active`.
|
||||||
|
Had it gone into a GE port instead, the guard refuses: the journal
|
||||||
|
shows `dnsmasq NOT started` and dnsmasq stays down.
|
||||||
|
|
||||||
|
## Status file
|
||||||
|
|
||||||
|
`/run/pxe-nic-status.json`, mode 0644, replaced atomically:
|
||||||
|
|
||||||
|
{"checked_at": "2026-10-14T16:47:09-04:00", "duration_s": 60,
|
||||||
|
"dhcp_guard": {"state": "allowed"|"blocked"|"unknown", "reason": "..."},
|
||||||
|
"interfaces": [{"iface": "br-pxe", "mac": "aa:bb:..", "role": "imaging"|"ge"|"other",
|
||||||
|
"link": "up"|"down", "speed_mbps": 5000, "addrs": ["172.16.9.1/24"],
|
||||||
|
"verdict": "isolated"|"ge"|"foreign-dhcp"|"no-link"|"error", "evidence": ["..."],
|
||||||
|
"dhcp_servers": [{"server": "10.1.2.3", "domain": "x.geaerospace.net", "own": false}],
|
||||||
|
"switch": {"lldp": [{"system": "...", "port": "...", "vlan": null}],
|
||||||
|
"cdp": [{"device": "...", "port": "..."}], "eapol": false, "stp": false},
|
||||||
|
"arp_hosts": 3, "dnsmasq_bound": true}]}
|
||||||
|
|
||||||
|
`dnsmasq_bound` is null when it cannot be told (no `ss`). A bridge
|
||||||
|
(br-pxe) is reported as one interface; its ports are listened on too,
|
||||||
|
because LLDP and 802.1X frames stop at the port and never reach the bridge.
|
||||||
@@ -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`
|
||||||
|
|||||||
414
playbook/FlatUnattendW10-engineer.xml
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
|
<!-- 1. windowsPE is intentionally empty -->
|
||||||
|
<settings pass="windowsPE" />
|
||||||
|
|
||||||
|
<!-- 2. Offline servicing (drivers) -->
|
||||||
|
<settings pass="offlineServicing">
|
||||||
|
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<DriverPaths>
|
||||||
|
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
||||||
|
<Path>W:\Drivers</Path>
|
||||||
|
</PathAndCredentials>
|
||||||
|
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
||||||
|
<Path>W:\Deploy\Applications\extra\printdrivers\BROTHER\UNIV-PS-01181\PS\64</Path>
|
||||||
|
</PathAndCredentials>
|
||||||
|
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
|
||||||
|
<Path>W:\Deploy\Applications\extra\printdrivers\HP</Path>
|
||||||
|
</PathAndCredentials>
|
||||||
|
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
|
||||||
|
<Path>W:\Deploy\Applications\extra\printdrivers\XEROX\UNIV_5.1035.2.0_PS_x64_Driver</Path>
|
||||||
|
</PathAndCredentials>
|
||||||
|
</DriverPaths>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<!-- 3. specialize: computer naming + RunSynchronous all in ONE component -->
|
||||||
|
<settings pass="specialize">
|
||||||
|
<!-- 3a. Shell-Setup for naming/owner/org -->
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<ComputerName>H%serialnumber%</ComputerName>
|
||||||
|
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
|
||||||
|
<RegisteredOwner>GE</RegisteredOwner>
|
||||||
|
<TimeZone>Eastern Standard Time</TimeZone>
|
||||||
|
</component>
|
||||||
|
|
||||||
|
<!-- 3b. RunSynchronous for all of your installers, copies, etc. -->
|
||||||
|
<component name="Microsoft-Windows-Deployment"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<RunSynchronous>
|
||||||
|
|
||||||
|
<!-- INTERNETACCESS bootstrap profile. BLUESSO and EAP-PEAP were removed:
|
||||||
|
the MCL ppkg installs both, and BLUESSO cannot bootstrap (hidden
|
||||||
|
SSID, manual connect, user password). wlansvc must be started
|
||||||
|
first or netsh silently fails. -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Path>cmd /c sc config wlansvc start= auto & net start wlansvc & exit /b 0</Path>
|
||||||
|
<Description>Start WLAN AutoConfig so the WiFi profile can be added</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<!-- PXE imaging floor progress (best-effort, never fails setup). -Persist copies the script
|
||||||
|
out of C:\Deploy, which the bulk-enrollment task deletes before first logon.
|
||||||
|
RunSynchronous Path max 259 chars: check with scripts/lint-unattend.py (local mode). -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Path>powershell.exe -NoP -EP Bypass -File C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1 -Persist -PCType gea-engineer -StageIndex 2 -Stage "Windows setup: installing apps"</Path>
|
||||||
|
<Description>PXE status: stage 2 of 5</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
|
||||||
|
<Description>Add generic WiFi profile</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Certificates -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>4</Order>
|
||||||
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_External_Root_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install External Root Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install External Intermediate Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>6</Order>
|
||||||
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install Enterprise Root Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>7</Order>
|
||||||
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install Enterprise Device Issuing Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>8</Order>
|
||||||
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install Enterprise Server Issuing Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>9</Order>
|
||||||
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install SmartCard Issuing Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>10</Order>
|
||||||
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer"</Path>
|
||||||
|
<Description>Install User Issuing Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>11</Order>
|
||||||
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer"</Path>
|
||||||
|
<Description>Install Aerospace Enterprise Root CA</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>12</Order>
|
||||||
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt"</Path>
|
||||||
|
<Description>Install Zscaler Certificate</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>13</Order>
|
||||||
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\3OF9.TTF" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
|
<Description>Copy 3OF9 Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>14</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "3 of 9 Barcode" /t REG_SZ /d "3OF9.TTF" /f</Path>
|
||||||
|
<Description>Register 3OF9 Barcode Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>15</Order>
|
||||||
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39AzaleaNarrow3.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
|
<Description>Copy Code39 Azalea Narrow Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>16</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39AzaleaNarrow3" /t REG_SZ /d "Code39AzaleaNarrow3.ttf" /f</Path>
|
||||||
|
<Description>Register Code39 Azalea Narrow Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>17</Order>
|
||||||
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39Azalea.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
|
<Description>Copy Code39 Azalea Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>18</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39Azalea" /t REG_SZ /d "Code39Azalea.ttf" /f</Path>
|
||||||
|
<Description>Register Code39 Azalea Font</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- OpenText installers -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>19</Order>
|
||||||
|
<Path>cmd /c "C:\Deploy\Applications\extra\opentext\Setup-OpenText.cmd"</Path>
|
||||||
|
<Description>Install OpenText HostExplorer SP1 (shopfloor method)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>20</Order>
|
||||||
|
<Path>C:\Deploy\Applications\extra\opentext\J2SE_Runtime_Environment_1.6.0_22_Static_Config_V2_Co-Exist.EXE /silent /norestart</Path>
|
||||||
|
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Adobe -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>22</Order>
|
||||||
|
<Path>msiexec /i "C:\Deploy\Applications\extra\adobe\AcroRead.msi" TRANSFORMS="C:\Deploy\Applications\extra\adobe\AcroRead.mst" /quiet /norestart</Path>
|
||||||
|
<Description>Install Adobe</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>23</Order>
|
||||||
|
<Path>msiexec /p "C:\Deploy\Applications\extra\adobe\AcroRdrDCUpd2500120531.msp" /quiet /norestart</Path>
|
||||||
|
<Description>Apply Adobe Reader Update</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Java JRE 8 Enterprise Installation -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>26</Order>
|
||||||
|
<Path>C:\Deploy\Applications\extra\java\jre-8u441-windows-i586.exe /s INSTALLDIR="C:\Program Files (x86)\Java\jre1.8.0_441" STATIC=1 AUTO_UPDATE=0 REBOOT=0 SPONSORS=0 WEB_JAVA=0 /L "C:\Windows\Logs\java-install.log"</Path>
|
||||||
|
<Description>Install Java JRE 8 Update 441 with Enterprise Settings and Logging</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Java Auto Updates via Registry (64-bit) -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>27</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Java Auto Updates (64-bit)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Java Auto Updates via Registry (32-bit) -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>28</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Java Auto Updates (32-bit)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Java Auto Update Check (64-bit) -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>29</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Java Auto Update Check (64-bit)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Java Auto Update Check (32-bit) -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>30</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Java Auto Update Check (32-bit)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Cortana -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>31</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AllowCortana /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Cortana</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Web Search in Start Menu -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>33</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v DisableWebSearch /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Disable Web Search in Start Menu</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Bing / web search suggestions in Start menu.
|
||||||
|
BingSearchEnabled is documented at HKCU\Software\Microsoft\Windows\CurrentVersion\Search
|
||||||
|
(not HKLM), so the old HKLM policy placement was a no-op. The current documented
|
||||||
|
suppression is DisableSearchBoxSuggestions=1 at Software\Policies\Microsoft\Windows\Explorer.
|
||||||
|
Writing it into the Default User hive so every newly-created account inherits it. -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>34</Order>
|
||||||
|
<Path>cmd /c reg load HKU\TempDU C:\Users\Default\NTUSER.DAT & reg add "HKU\TempDU\Software\Policies\Microsoft\Windows\Explorer" /v DisableSearchBoxSuggestions /t REG_DWORD /d 1 /f & reg unload HKU\TempDU</Path>
|
||||||
|
<Description>Disable Bing/web search suggestions (Default User hive)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Search Web when searching Windows -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>35</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v ConnectedSearchUseWeb /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Connected Search Use Web</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Cortana Speech Recognition -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>36</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\InputPersonalization" /v AllowInputPersonalization /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Cortana Speech Recognition</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<!-- Disable Search Highlights (news/trending) -->
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>38</Order>
|
||||||
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable Search Highlights</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>37</Order>
|
||||||
|
<Path>powershell.exe -NoP -EP Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1" -StageOnly</Path>
|
||||||
|
<!-- Copies the Engineering Layer payload and the script itself to
|
||||||
|
C:\PreInstall\engineering now, while C:\Deploy certainly exists. The
|
||||||
|
first-logon install (oobeSystem Order 11) runs from there: a PC that
|
||||||
|
reboots before the employee signs in has lost C:\Deploy to the
|
||||||
|
orchestrator's DeleteDeployDir task by then. -->
|
||||||
|
<Description>Engineering Layer: stage payload out of C:\Deploy</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>39</Order>
|
||||||
|
<Path>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Deploy\Applications\BPRT\Register-BulkEnrollTask-Standard.ps1"</Path>
|
||||||
|
<Description>Add bulk enrollment orchestrator to Run key</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>40</Order>
|
||||||
|
<Path>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</Path>
|
||||||
|
<Description>Load Default User registry hive</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>41</Order>
|
||||||
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable startup delay for all new users</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>42</Order>
|
||||||
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /t REG_DWORD /d 0 /f</Path>
|
||||||
|
<Description>Disable wait for idle state for all new users</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>43</Order>
|
||||||
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v StartShownOnUpgrade /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Prevent Start menu from opening on first login</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>44</Order>
|
||||||
|
<Path>reg.exe unload HKU\TempDefault</Path>
|
||||||
|
<Description>Unload Default User registry hive</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>45</Order>
|
||||||
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v NoAutoUpdate /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Disable automatic Windows updates (NoAutoUpdate)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>46</Order>
|
||||||
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v AUOptions /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Disable automatic Windows updates (AUOptions)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>47</Order>
|
||||||
|
<Path>reg.exe add "HKLM\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "doskey vanilla=powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File C:\Deploy\Applications\BPRT\Show-VanillaConfirmation.ps1" /f</Path>
|
||||||
|
<Description>Define vanilla CMD alias for manual OOBE fallback via Shift+F10</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>48</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Block all removable media reads to prevent PPKG auto-detection at OOBE</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>49</Order>
|
||||||
|
<Path>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 3 -Status succeeded -Stage "Ready at OOBE - hand to the employee"</Path>
|
||||||
|
<Description>PXE status: stage 3 of 3, done (the rest needs the employee to log in)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
</RunSynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<!-- 4. oobeSystem: hide OEM/EULA screens -->
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
|
<HideOnlineAccountScreens>false</HideOnlineAccountScreens>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
|
<NetworkLocation>Work</NetworkLocation>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
<SkipUserOOBE>false</SkipUserOOBE>
|
||||||
|
<SkipMachineOOBE>false</SkipMachineOOBE>
|
||||||
|
</OOBE>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 4 -Stage "First logon: installing engineering layer"</CommandLine>
|
||||||
|
<Description>PXE status: stage 4 of 5</Description>
|
||||||
|
<Order>1</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc config wuauserv start= disabled</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>4</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc stop wuauserv</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>5</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</CommandLine>
|
||||||
|
<Description>Load Default User registry hive to reverse startup delay</Description>
|
||||||
|
<Order>6</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /f</CommandLine>
|
||||||
|
<Description>Remove startup delay override from Default User</Description>
|
||||||
|
<Order>7</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /f</CommandLine>
|
||||||
|
<Description>Remove wait for idle state override from Default User</Description>
|
||||||
|
<Order>8</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe unload HKU\TempDefault</CommandLine>
|
||||||
|
<Description>Unload Default User registry hive</Description>
|
||||||
|
<Order>9</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /f</CommandLine>
|
||||||
|
<Description>Restore removable media access after OOBE PPKG block</Description>
|
||||||
|
<Order>10</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>11</Order>
|
||||||
|
<CommandLine>cmd /c if exist "C:\PreInstall\engineering\Install-EngineeringLayer.ps1" (powershell.exe -NoP -EP Bypass -File "C:\PreInstall\engineering\Install-EngineeringLayer.ps1") else (powershell.exe -NoP -EP Bypass -File "C:\Deploy\Applications\extra_engineering\Install-EngineeringLayer.ps1")</CommandLine>
|
||||||
|
<!-- Installs from the copy specialize Order 37 staged in
|
||||||
|
C:\PreInstall\engineering, so it works after a reboot has removed
|
||||||
|
C:\Deploy (DeleteDeployDir). Falls back to C:\Deploy if staging did not
|
||||||
|
happen. The installer (.NET 3.5, VC++, Cygwin, DUU) can reboot part way
|
||||||
|
through; the script is idempotent and marker-gated, and it makes sure
|
||||||
|
the "Configure my Engineering Layer" icon reaches the employee.
|
||||||
|
Description is capped at 256 chars, so the detail lives here. -->
|
||||||
|
<Description>Engineering Layer: install from the staged copy</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-engineer -StageIndex 5 -Stage "Setup complete - enrollment continues on the PC" -Status succeeded</CommandLine>
|
||||||
|
<Description>PXE status: stage 5 of 5</Description>
|
||||||
|
<Order>12</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
</unattend>
|
||||||
@@ -1,181 +1,288 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
<settings pass="windowsPE">
|
<settings pass="windowsPE">
|
||||||
</settings>
|
</settings>
|
||||||
<settings pass="offlineServicing">
|
<settings pass="offlineServicing">
|
||||||
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<DriverPaths>
|
<DriverPaths>
|
||||||
<PathAndCredentials xmlns="" wcm:keyValue="1" action="add">
|
<PathAndCredentials xmlns="" wcm:keyValue="1" action="add">
|
||||||
<Path>W:\Drivers</Path>
|
<Path>W:\Drivers</Path>
|
||||||
</PathAndCredentials>
|
</PathAndCredentials>
|
||||||
</DriverPaths>
|
</DriverPaths>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
<settings pass="specialize">
|
<settings pass="specialize">
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
<ComputerName>H%serialnumber%</ComputerName>
|
<ComputerName>H%serialnumber%</ComputerName>
|
||||||
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
|
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
|
||||||
<RegisteredOwner>GE</RegisteredOwner>
|
<RegisteredOwner>GE</RegisteredOwner>
|
||||||
<TimeZone>Eastern Standard Time</TimeZone>
|
<TimeZone>Eastern Standard Time</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
<component name="Microsoft-Windows-Deployment" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
<component name="Microsoft-Windows-Deployment" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
<RunSynchronous>
|
<RunSynchronous>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>1</Order>
|
<Order>1</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_External_Root_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_External_Root_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
||||||
<Description>Install External Root Certificate</Description>
|
<Description>Install External Root Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>2</Order>
|
<Order>2</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
||||||
<Description>Install External Intermediate Certificate</Description>
|
<Description>Install External Intermediate Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>3</Order>
|
<Order>3</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
||||||
<Description>Install Enterprise Root Certificate</Description>
|
<Description>Install Enterprise Root Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>4</Order>
|
<Order>4</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
||||||
<Description>Install Enterprise Device Issuing Certificate</Description>
|
<Description>Install Enterprise Device Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>5</Order>
|
<Order>5</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
||||||
<Description>Install Enterprise Server Issuing Certificate</Description>
|
<Description>Install Enterprise Server Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>6</Order>
|
<Order>6</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
||||||
<Description>Install Enterprise SmartCard Issuing Certificate</Description>
|
<Description>Install Enterprise SmartCard Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>7</Order>
|
<Order>7</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer' -CertStoreLocation 'Cert:\LocalMachine\CA'"</Path>
|
||||||
<Description>Install Enterprise User Issuing Certificate</Description>
|
<Description>Install Enterprise User Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>8</Order>
|
<Order>8</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
||||||
<Description>Install Aerospace Enterprise Root Certificate</Description>
|
<Description>Install Aerospace Enterprise Root Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>9</Order>
|
<Order>9</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Import-Certificate -FilePath 'C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt' -CertStoreLocation 'Cert:\LocalMachine\Root'"</Path>
|
||||||
<Description>Install Zscaler Commercial Certificate</Description>
|
<Description>Install Zscaler Commercial Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Path>reg.exe add "HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff" /f </Path>
|
<Path>reg.exe add "HKLM\System\CurrentControlSet\Control\Network\NewNetworkWindowOff" /f </Path>
|
||||||
<Description>Disable Network Windows</Description>
|
<Description>Disable Network Windows</Description>
|
||||||
<Order>10</Order>
|
<Order>10</Order>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>11</Order>
|
<Order>11</Order>
|
||||||
<Path>netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
|
<Path>netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
|
||||||
<Description>Install INTERNETACCESS WiFi Profile</Description>
|
<Description>Install INTERNETACCESS WiFi Profile</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>12</Order>
|
<Order>12</Order>
|
||||||
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Enable-PSRemoting -Force -SkipNetworkProfileCheck"</Path>
|
<Path>powershell.exe -ExecutionPolicy Bypass -Command "Enable-PSRemoting -Force -SkipNetworkProfileCheck"</Path>
|
||||||
<Description>Enable WinRM</Description>
|
<Description>Enable WinRM</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>13</Order>
|
<Order>13</Order>
|
||||||
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path>
|
||||||
<Description>Bypass OOBE network requirement</Description>
|
<Description>Bypass OOBE network requirement</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>14</Order>
|
<Order>14</Order>
|
||||||
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipMachineOOBE /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipMachineOOBE /t REG_DWORD /d 1 /f</Path>
|
||||||
<Description>Skip machine OOBE phase</Description>
|
<Description>Skip machine OOBE phase</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>15</Order>
|
<Order>15</Order>
|
||||||
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipUserOOBE /t REG_DWORD /d 1 /f</Path>
|
<Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v SkipUserOOBE /t REG_DWORD /d 1 /f</Path>
|
||||||
<Description>Skip user OOBE phase</Description>
|
<Description>Skip user OOBE phase</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
</RunSynchronous>
|
|
||||||
</component>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
</settings>
|
<Order>17</Order>
|
||||||
<settings pass="oobeSystem">
|
<Path>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</Path>
|
||||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
<Description>Load Default User registry hive</Description>
|
||||||
<InputLocale>en-US</InputLocale>
|
</RunSynchronousCommand>
|
||||||
<SystemLocale>en-US</SystemLocale>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<UILanguage>en-US</UILanguage>
|
<Order>18</Order>
|
||||||
<UserLocale>en-US</UserLocale>
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /t REG_DWORD /d 0 /f</Path>
|
||||||
</component>
|
<Description>Disable startup delay for all new users</Description>
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
</RunSynchronousCommand>
|
||||||
<OOBE>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<HideEULAPage>true</HideEULAPage>
|
<Order>19</Order>
|
||||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /t REG_DWORD /d 0 /f</Path>
|
||||||
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
<Description>Disable wait for idle state for all new users</Description>
|
||||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
</RunSynchronousCommand>
|
||||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
<Order>20</Order>
|
||||||
</OOBE>
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v StartShownOnUpgrade /t REG_DWORD /d 1 /f</Path>
|
||||||
<UserAccounts>
|
<Description>Prevent Start menu from opening on first login</Description>
|
||||||
<LocalAccounts>
|
</RunSynchronousCommand>
|
||||||
<LocalAccount wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Password>
|
<Order>21</Order>
|
||||||
<Value>Pa55word</Value>
|
<Path>reg.exe unload HKU\TempDefault</Path>
|
||||||
<PlainText>true</PlainText>
|
<Description>Unload Default User registry hive</Description>
|
||||||
</Password>
|
</RunSynchronousCommand>
|
||||||
<Name>SupportUser</Name>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Group>Administrators</Group>
|
<Order>22</Order>
|
||||||
<DisplayName>SupportUser</DisplayName>
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v NoAutoUpdate /t REG_DWORD /d 1 /f</Path>
|
||||||
</LocalAccount>
|
<Description>Disable automatic Windows updates (NoAutoUpdate)</Description>
|
||||||
</LocalAccounts>
|
</RunSynchronousCommand>
|
||||||
</UserAccounts>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<AutoLogon>
|
<Order>23</Order>
|
||||||
<Password>
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v AUOptions /t REG_DWORD /d 1 /f</Path>
|
||||||
<Value>Pa55word</Value>
|
<Description>Disable automatic Windows updates (AUOptions)</Description>
|
||||||
<PlainText>true</PlainText>
|
</RunSynchronousCommand>
|
||||||
</Password>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Enabled>true</Enabled>
|
<Order>24</Order>
|
||||||
<Username>SupportUser</Username>
|
<Path>reg.exe add "HKLM\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "doskey vanilla=powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File C:\Deploy\Applications\BPRT\Show-VanillaConfirmation.ps1" /f</Path>
|
||||||
<LogonCount>7</LogonCount>
|
<Description>Define vanilla CMD alias for manual OOBE fallback via Shift+F10</Description>
|
||||||
</AutoLogon>
|
</RunSynchronousCommand>
|
||||||
<FirstLogonCommands>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<SynchronousCommand wcm:action="add">
|
<Order>25</Order>
|
||||||
<Order>1</Order>
|
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /t REG_DWORD /d 1 /f</Path>
|
||||||
<CommandLine>cmd.exe /c powercfg /change monitor-timeout-ac 0 & powercfg /change monitor-timeout-dc 0 & powercfg /change standby-timeout-ac 0 & powercfg /change standby-timeout-dc 0</CommandLine>
|
<Description>Block all removable media reads to prevent PPKG auto-detection at OOBE</Description>
|
||||||
<Description>Disable display and sleep timeout during setup</Description>
|
</RunSynchronousCommand>
|
||||||
</SynchronousCommand>
|
</RunSynchronous>
|
||||||
<SynchronousCommand wcm:action="add">
|
</component>
|
||||||
<Order>2</Order>
|
</settings>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -match 'Wi-Fi|Wireless' } | Set-NetIPInterface -InterfaceMetric 10; Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -notmatch 'Wi-Fi|Wireless' } | Set-NetIPInterface -InterfaceMetric 100"</CommandLine>
|
<settings pass="oobeSystem">
|
||||||
<Description>Prioritize WiFi over ethernet</Description>
|
<component name="Microsoft-Windows-International-Core" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
</SynchronousCommand>
|
<InputLocale>en-US</InputLocale>
|
||||||
<SynchronousCommand wcm:action="add">
|
<SystemLocale>en-US</SystemLocale>
|
||||||
<Order>3</Order>
|
<UILanguage>en-US</UILanguage>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private"</CommandLine>
|
<UserLocale>en-US</UserLocale>
|
||||||
<Description>Set network profile to Private</Description>
|
</component>
|
||||||
</SynchronousCommand>
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="*arch*" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
<SynchronousCommand wcm:action="add">
|
<OOBE>
|
||||||
<Order>4</Order>
|
<HideEULAPage>true</HideEULAPage>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\wait-for-internet.ps1"</CommandLine>
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
<Description>Prompt to connect production network then wait for TCP 443 connectivity</Description>
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
</SynchronousCommand>
|
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||||
<SynchronousCommand wcm:action="add">
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
<Order>5</Order>
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\migrate-to-wifi.ps1"</CommandLine>
|
</OOBE>
|
||||||
<Description>Migrate from wired to WiFi if WiFi adapter present, else stay on wired</Description>
|
<UserAccounts>
|
||||||
</SynchronousCommand>
|
<LocalAccounts>
|
||||||
<SynchronousCommand wcm:action="add">
|
<LocalAccount wcm:action="add">
|
||||||
<Order>6</Order>
|
<Password>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\run-enrollment.ps1"</CommandLine>
|
<Value>Pa55word</Value>
|
||||||
<Description>Run GCCH Enrollment</Description>
|
<PlainText>true</PlainText>
|
||||||
</SynchronousCommand>
|
</Password>
|
||||||
<SynchronousCommand wcm:action="add">
|
<Name>SupportUser</Name>
|
||||||
<Order>7</Order>
|
<Group>Administrators</Group>
|
||||||
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Run-ShopfloorSetup.ps1"</CommandLine>
|
<DisplayName>SupportUser</DisplayName>
|
||||||
<Description>Run shopfloor PC type setup</Description>
|
</LocalAccount>
|
||||||
</SynchronousCommand>
|
</LocalAccounts>
|
||||||
</FirstLogonCommands>
|
</UserAccounts>
|
||||||
<TimeZone>Eastern Standard Time</TimeZone>
|
<AutoLogon>
|
||||||
</component>
|
<Password>
|
||||||
</settings>
|
<Value>Pa55word</Value>
|
||||||
</unattend>
|
<PlainText>true</PlainText>
|
||||||
|
</Password>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<Username>SupportUser</Username>
|
||||||
|
<LogonCount>12</LogonCount>
|
||||||
|
</AutoLogon>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<CommandLine>cmd.exe /c powercfg /change monitor-timeout-ac 0 & powercfg /change monitor-timeout-dc 0 & powercfg /change standby-timeout-ac 0 & powercfg /change standby-timeout-dc 0</CommandLine>
|
||||||
|
<Description>Disable display and sleep timeout during setup</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -match 'Wi-Fi|Wireless' } | Set-NetIPInterface -InterfaceMetric 10; Get-NetAdapter -Physical | Where-Object { $_.InterfaceDescription -notmatch 'Wi-Fi|Wireless' } | Set-NetIPInterface -InterfaceMetric 100"</CommandLine>
|
||||||
|
<Description>Prioritize WiFi over ethernet</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -Command "Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private"</CommandLine>
|
||||||
|
<Description>Set network profile to Private</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>4</Order>
|
||||||
|
<CommandLine>powershell.exe -ep Bypass -Command "net use Z: \\172.16.9.1\enrollment /user:pxe-upload pxe; robocopy Z:\shopfloor-setup C:\Enrollment Fetch-StagingPayload.ps1 Verify-And-Heal-Staging.ps1 /R:2 /W:2; robocopy Z:\scripts C:\Enrollment run-enrollment.ps1 wait-for-internet.ps1 migrate-to-wifi.ps1 /R:2 /W:2"</CommandLine>
|
||||||
|
<Description>Bootstrap self-heal: pull Fetch/Verify-Heal + enrollment scripts from the PXE share so Order 5/6 run after an early WinPE staging failure.</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>5</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Fetch-StagingPayload.ps1"</CommandLine>
|
||||||
|
<Description>Fetch bulk staging (shopfloor-setup tree + preinstall bundle) from the PXE share on a fresh mount, BEFORE the production-network switch takes the bay off the imaging LAN. Detailed log at C:\Logs\Fetch\.</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>6</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Verify-And-Heal-Staging.ps1"</CommandLine>
|
||||||
|
<Description>Verify + re-pull any missing imaging payload (shopfloor setup scripts, site app lists and installers) from the PXE share on the imaging LAN before the production-network switch. Log C:\Logs\Fetch.</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>7</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\wait-for-internet.ps1"</CommandLine>
|
||||||
|
<Description>Prompt to connect production network then wait for TCP 443 connectivity</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>8</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\migrate-to-wifi.ps1"</CommandLine>
|
||||||
|
<Description>Migrate from wired to WiFi if WiFi adapter present, else stay on wired</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>9</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>10</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\run-enrollment.ps1"</CommandLine>
|
||||||
|
<!-- run-enrollment.ps1 lives in C:\Enrollment: startnet stages it and
|
||||||
|
the .ppkg there. The earlier C:\run-enrollment.ps1 path pointed at a
|
||||||
|
file that never existed, so this step silently did nothing.
|
||||||
|
Keep prose in comments - Description is capped at 256 chars and an
|
||||||
|
over-length one invalidates the whole answer file for its pass. -->
|
||||||
|
<Description>Run GCCH Enrollment</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>11</Order>
|
||||||
|
<CommandLine>powershell.exe -ExecutionPolicy Bypass -File "C:\Enrollment\Run-ShopfloorSetup.ps1"</CommandLine>
|
||||||
|
<Description>Run shopfloor PC type setup</Description>
|
||||||
|
</SynchronousCommand>
|
||||||
|
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc config wuauserv start= disabled</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>12</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc stop wuauserv</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>13</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</CommandLine>
|
||||||
|
<Description>Load Default User registry hive to reverse startup delay</Description>
|
||||||
|
<Order>14</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /f</CommandLine>
|
||||||
|
<Description>Remove startup delay override from Default User</Description>
|
||||||
|
<Order>15</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /f</CommandLine>
|
||||||
|
<Description>Remove wait for idle state override from Default User</Description>
|
||||||
|
<Order>16</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe unload HKU\TempDefault</CommandLine>
|
||||||
|
<Description>Unload Default User registry hive</Description>
|
||||||
|
<Order>17</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /f</CommandLine>
|
||||||
|
<Description>Restore removable media access after OOBE PPKG block</Description>
|
||||||
|
<Order>18</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
<TimeZone>Eastern Standard Time</TimeZone>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
|
|||||||
@@ -1,307 +1,392 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend"
|
<unattend xmlns="urn:schemas-microsoft-com:unattend"
|
||||||
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
<!-- 1. windowsPE is intentionally empty -->
|
<!-- 1. windowsPE is intentionally empty -->
|
||||||
<settings pass="windowsPE" />
|
<settings pass="windowsPE" />
|
||||||
|
|
||||||
<!-- 2. Offline servicing (drivers) -->
|
<!-- 2. Offline servicing (drivers) -->
|
||||||
<settings pass="offlineServicing">
|
<settings pass="offlineServicing">
|
||||||
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE"
|
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE"
|
||||||
processorArchitecture="amd64"
|
processorArchitecture="amd64"
|
||||||
publicKeyToken="31bf3856ad364e35"
|
publicKeyToken="31bf3856ad364e35"
|
||||||
language="neutral"
|
language="neutral"
|
||||||
versionScope="nonSxS">
|
versionScope="nonSxS">
|
||||||
<DriverPaths>
|
<DriverPaths>
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
|
||||||
<Path>W:\Drivers</Path>
|
<Path>W:\Drivers</Path>
|
||||||
</PathAndCredentials>
|
</PathAndCredentials>
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
||||||
<Path>W:\Deploy\Applications\extra\printdrivers\BROTHER\UNIV-PS-01181\PS\64</Path>
|
<Path>W:\Deploy\Applications\extra\printdrivers\BROTHER\UNIV-PS-01181\PS\64</Path>
|
||||||
</PathAndCredentials>
|
</PathAndCredentials>
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
|
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
|
||||||
<Path>W:\Deploy\Applications\extra\printdrivers\HP</Path>
|
<Path>W:\Deploy\Applications\extra\printdrivers\HP</Path>
|
||||||
</PathAndCredentials>
|
</PathAndCredentials>
|
||||||
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
|
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
|
||||||
<Path>W:\Deploy\Applications\extra\printdrivers\XEROX\UNIV_5.1035.2.0_PS_x64_Driver</Path>
|
<Path>W:\Deploy\Applications\extra\printdrivers\XEROX\UNIV_5.1035.2.0_PS_x64_Driver</Path>
|
||||||
</PathAndCredentials>
|
</PathAndCredentials>
|
||||||
</DriverPaths>
|
</DriverPaths>
|
||||||
</component>
|
</component>
|
||||||
</settings>
|
</settings>
|
||||||
|
|
||||||
<!-- 3. specialize: computer naming + RunSynchronous all in ONE component -->
|
<!-- 3. specialize: computer naming + RunSynchronous all in ONE component -->
|
||||||
<settings pass="specialize">
|
<settings pass="specialize">
|
||||||
<!-- 3a. Shell-Setup for naming/owner/org -->
|
<!-- 3a. Shell-Setup for naming/owner/org -->
|
||||||
<component name="Microsoft-Windows-Shell-Setup"
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
processorArchitecture="amd64"
|
processorArchitecture="amd64"
|
||||||
publicKeyToken="31bf3856ad364e35"
|
publicKeyToken="31bf3856ad364e35"
|
||||||
language="neutral"
|
language="neutral"
|
||||||
versionScope="nonSxS">
|
versionScope="nonSxS">
|
||||||
<ComputerName>H%serialnumber%</ComputerName>
|
<ComputerName>H%serialnumber%</ComputerName>
|
||||||
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
|
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
|
||||||
<RegisteredOwner>GE</RegisteredOwner>
|
<RegisteredOwner>GE</RegisteredOwner>
|
||||||
<TimeZone>Eastern Standard Time</TimeZone>
|
<TimeZone>Eastern Standard Time</TimeZone>
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
<!-- 3b. RunSynchronous for all of your installers, copies, etc. -->
|
<!-- 3b. RunSynchronous for all of your installers, copies, etc. -->
|
||||||
<component name="Microsoft-Windows-Deployment"
|
<component name="Microsoft-Windows-Deployment"
|
||||||
processorArchitecture="amd64"
|
processorArchitecture="amd64"
|
||||||
publicKeyToken="31bf3856ad364e35"
|
publicKeyToken="31bf3856ad364e35"
|
||||||
language="neutral"
|
language="neutral"
|
||||||
versionScope="nonSxS">
|
versionScope="nonSxS">
|
||||||
<RunSynchronous>
|
<RunSynchronous>
|
||||||
<!-- EAP-PEAP MSI -->
|
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<!-- INTERNETACCESS bootstrap profile. BLUESSO and EAP-PEAP were removed:
|
||||||
<Order>1</Order>
|
the MCL ppkg installs both, and BLUESSO cannot bootstrap (hidden
|
||||||
<Path>msiexec /i "C:\Deploy\Applications\extra\wireless\EAP-PEAP.msi" /quiet /norestart</Path>
|
SSID, manual connect, user password). wlansvc must be started
|
||||||
<Description>Install EAP-PEAP</Description>
|
first or netsh silently fails. -->
|
||||||
</RunSynchronousCommand>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
<!-- Wi-Fi profiles -->
|
<Path>cmd /c sc config wlansvc start= auto & net start wlansvc & exit /b 0</Path>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<Description>Start WLAN AutoConfig so the WiFi profile can be added</Description>
|
||||||
<Order>2</Order>
|
</RunSynchronousCommand>
|
||||||
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\BLUESSO.xml" user=all</Path>
|
<!-- PXE imaging floor progress (best-effort, never fails setup). -Persist copies the script
|
||||||
<Description>Add BLUESSO WiFi profile</Description>
|
out of C:\Deploy, which the bulk-enrollment task deletes before first logon.
|
||||||
</RunSynchronousCommand>
|
RunSynchronous Path max 259 chars: check with scripts/lint-unattend.py (local mode). -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>3</Order>
|
<Order>2</Order>
|
||||||
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
|
<Path>powershell.exe -NoP -EP Bypass -File C:\Deploy\Applications\extra\pxe-status\Send-ImagingStage.ps1 -Persist -PCType gea-standard -StageIndex 2 -Stage "Windows setup: installing apps"</Path>
|
||||||
<Description>Add generic WiFi profile</Description>
|
<Description>PXE status: stage 2 of 5</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<!-- Certificates -->
|
<Order>3</Order>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
|
||||||
<Order>4</Order>
|
<Description>Add generic WiFi profile</Description>
|
||||||
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_External_Root_CA_2_1.cer"</Path>
|
</RunSynchronousCommand>
|
||||||
<Description>Install External Root Certificate</Description>
|
|
||||||
</RunSynchronousCommand>
|
<!-- Certificates -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>5</Order>
|
<Order>4</Order>
|
||||||
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_External_Root_CA_2_1.cer"</Path>
|
||||||
<Description>Install External Intermediate Certificate</Description>
|
<Description>Install External Root Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>6</Order>
|
<Order>5</Order>
|
||||||
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer"</Path>
|
||||||
<Description>Install Enterprise Root Certificate</Description>
|
<Description>Install External Intermediate Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>7</Order>
|
<Order>6</Order>
|
||||||
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer"</Path>
|
||||||
<Description>Install Enterprise Device Issuing Certificate</Description>
|
<Description>Install Enterprise Root Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>8</Order>
|
<Order>7</Order>
|
||||||
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer"</Path>
|
||||||
<Description>Install Enterprise Server Issuing Certificate</Description>
|
<Description>Install Enterprise Device Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>9</Order>
|
<Order>8</Order>
|
||||||
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer"</Path>
|
||||||
<Description>Install SmartCard Issuing Certificate</Description>
|
<Description>Install Enterprise Server Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>10</Order>
|
<Order>9</Order>
|
||||||
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer"</Path>
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer"</Path>
|
||||||
<Description>Install User Issuing Certificate</Description>
|
<Description>Install SmartCard Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>11</Order>
|
<Order>10</Order>
|
||||||
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer"</Path>
|
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer"</Path>
|
||||||
<Description>Install Aerospace Enterprise Root CA</Description>
|
<Description>Install User Issuing Certificate</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>12</Order>
|
<Order>11</Order>
|
||||||
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt"</Path>
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer"</Path>
|
||||||
<Description>Install Zscaler Certificate</Description>
|
<Description>Install Aerospace Enterprise Root CA</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<!-- Fonts -->
|
<Order>12</Order>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt"</Path>
|
||||||
<Order>13</Order>
|
<Description>Install Zscaler Certificate</Description>
|
||||||
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\3OF9.TTF" "%WINDIR%\Fonts\" /Y</Path>
|
</RunSynchronousCommand>
|
||||||
<Description>Copy 3OF9 Font</Description>
|
|
||||||
</RunSynchronousCommand>
|
<!-- Fonts -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>14</Order>
|
<Order>13</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "3 of 9 Barcode" /t REG_SZ /d "3OF9.TTF" /f</Path>
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\3OF9.TTF" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
<Description>Register 3OF9 Barcode Font</Description>
|
<Description>Copy 3OF9 Font</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>15</Order>
|
<Order>14</Order>
|
||||||
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39AzaleaNarrow3.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "3 of 9 Barcode" /t REG_SZ /d "3OF9.TTF" /f</Path>
|
||||||
<Description>Copy Code39 Azalea Narrow Font</Description>
|
<Description>Register 3OF9 Barcode Font</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>16</Order>
|
<Order>15</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39AzaleaNarrow3" /t REG_SZ /d "Code39AzaleaNarrow3.ttf" /f</Path>
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39AzaleaNarrow3.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
<Description>Register Code39 Azalea Narrow Font</Description>
|
<Description>Copy Code39 Azalea Narrow Font</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>17</Order>
|
<Order>16</Order>
|
||||||
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39Azalea.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39AzaleaNarrow3" /t REG_SZ /d "Code39AzaleaNarrow3.ttf" /f</Path>
|
||||||
<Description>Copy Code39 Azalea Font</Description>
|
<Description>Register Code39 Azalea Narrow Font</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>18</Order>
|
<Order>17</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39Azalea" /t REG_SZ /d "Code39Azalea.ttf" /f</Path>
|
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39Azalea.ttf" "%WINDIR%\Fonts\" /Y</Path>
|
||||||
<Description>Register Code39 Azalea Font</Description>
|
<Description>Copy Code39 Azalea Font</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<!-- OpenText installers -->
|
<Order>18</Order>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39Azalea" /t REG_SZ /d "Code39Azalea.ttf" /f</Path>
|
||||||
<Order>19</Order>
|
<Description>Register Code39 Azalea Font</Description>
|
||||||
<Path>C:\Deploy\Applications\extra\opentext\opentext_hostexplorer_sp1_15.0_v01.exe /quiet /norestart</Path>
|
</RunSynchronousCommand>
|
||||||
<Description>Install OpenText HostExplorer SP1</Description>
|
|
||||||
</RunSynchronousCommand>
|
<!-- OpenText installers -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>20</Order>
|
<Order>19</Order>
|
||||||
<Path>C:\Deploy\Applications\extra\opentext\J2SE_Runtime_Environment_1.6.0_22_Static_Config_V2_Co-Exist.EXE /silent /norestart</Path>
|
<Path>cmd /c "C:\Deploy\Applications\extra\opentext\Setup-OpenText.cmd"</Path>
|
||||||
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
|
<Description>Install OpenText HostExplorer SP1 (shopfloor method)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>21</Order>
|
<Order>20</Order>
|
||||||
<Path>C:\Deploy\Applications\extra\opentext\unattended.bat</Path>
|
<Path>C:\Deploy\Applications\extra\opentext\J2SE_Runtime_Environment_1.6.0_22_Static_Config_V2_Co-Exist.EXE /silent /norestart</Path>
|
||||||
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
|
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Adobe -->
|
<!-- Adobe -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>22</Order>
|
<Order>22</Order>
|
||||||
<Path>msiexec /i "C:\Deploy\Applications\extra\adobe\AcroRead.msi" TRANSFORMS="C:\Deploy\Applications\extra\adobe\AcroRead.mst" /quiet /norestart</Path>
|
<Path>msiexec /i "C:\Deploy\Applications\extra\adobe\AcroRead.msi" TRANSFORMS="C:\Deploy\Applications\extra\adobe\AcroRead.mst" /quiet /norestart</Path>
|
||||||
<Description>Install Adobe</Description>
|
<Description>Install Adobe</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>23</Order>
|
<Order>23</Order>
|
||||||
<Path>msiexec /p "C:\Deploy\Applications\extra\adobe\AcroRdrDCUpd2500120531.msp" /quiet /norestart</Path>
|
<Path>msiexec /p "C:\Deploy\Applications\extra\adobe\AcroRdrDCUpd2500120531.msp" /quiet /norestart</Path>
|
||||||
<Description>Apply Adobe Reader Update</Description>
|
<Description>Apply Adobe Reader Update</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Java JRE 8 Enterprise Installation -->
|
<!-- Java JRE 8 Enterprise Installation -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>26</Order>
|
<Order>26</Order>
|
||||||
<Path>C:\Deploy\Applications\extra\java\jre-8u441-windows-i586.exe /s INSTALLDIR="C:\Program Files (x86)\Java\jre1.8.0_441" STATIC=1 AUTO_UPDATE=0 REBOOT=0 SPONSORS=0 WEB_JAVA=0 /L "C:\Windows\Logs\java-install.log"</Path>
|
<Path>C:\Deploy\Applications\extra\java\jre-8u441-windows-i586.exe /s INSTALLDIR="C:\Program Files (x86)\Java\jre1.8.0_441" STATIC=1 AUTO_UPDATE=0 REBOOT=0 SPONSORS=0 WEB_JAVA=0 /L "C:\Windows\Logs\java-install.log"</Path>
|
||||||
<Description>Install Java JRE 8 Update 441 with Enterprise Settings and Logging</Description>
|
<Description>Install Java JRE 8 Update 441 with Enterprise Settings and Logging</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Java Auto Updates via Registry (64-bit) -->
|
<!-- Disable Java Auto Updates via Registry (64-bit) -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>27</Order>
|
<Order>27</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Java Auto Updates (64-bit)</Description>
|
<Description>Disable Java Auto Updates (64-bit)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Java Auto Updates via Registry (32-bit) -->
|
<!-- Disable Java Auto Updates via Registry (32-bit) -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>28</Order>
|
<Order>28</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableJavaUpdate /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Java Auto Updates (32-bit)</Description>
|
<Description>Disable Java Auto Updates (32-bit)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Java Auto Update Check (64-bit) -->
|
<!-- Disable Java Auto Update Check (64-bit) -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>29</Order>
|
<Order>29</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Java Auto Update Check (64-bit)</Description>
|
<Description>Disable Java Auto Update Check (64-bit)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Java Auto Update Check (32-bit) -->
|
<!-- Disable Java Auto Update Check (32-bit) -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>30</Order>
|
<Order>30</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Wow6432Node\JavaSoft\Java Update\Policy" /v EnableAutoUpdateCheck /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Java Auto Update Check (32-bit)</Description>
|
<Description>Disable Java Auto Update Check (32-bit)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Cortana -->
|
<!-- Disable Cortana -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>31</Order>
|
<Order>31</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AllowCortana /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AllowCortana /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Cortana</Description>
|
<Description>Disable Cortana</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Web Search in Start Menu -->
|
<!-- Disable Web Search in Start Menu -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>33</Order>
|
<Order>33</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v DisableWebSearch /t REG_DWORD /d 1 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v DisableWebSearch /t REG_DWORD /d 1 /f</Path>
|
||||||
<Description>Disable Web Search in Start Menu</Description>
|
<Description>Disable Web Search in Start Menu</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Bing / web search suggestions in Start menu.
|
<!-- Disable Bing / web search suggestions in Start menu.
|
||||||
BingSearchEnabled is documented at HKCU\Software\Microsoft\Windows\CurrentVersion\Search
|
BingSearchEnabled is documented at HKCU\Software\Microsoft\Windows\CurrentVersion\Search
|
||||||
(not HKLM), so the old HKLM policy placement was a no-op. The current documented
|
(not HKLM), so the old HKLM policy placement was a no-op. The current documented
|
||||||
suppression is DisableSearchBoxSuggestions=1 at Software\Policies\Microsoft\Windows\Explorer.
|
suppression is DisableSearchBoxSuggestions=1 at Software\Policies\Microsoft\Windows\Explorer.
|
||||||
Writing it into the Default User hive so every newly-created account inherits it. -->
|
Writing it into the Default User hive so every newly-created account inherits it. -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>34</Order>
|
<Order>34</Order>
|
||||||
<Path>cmd /c reg load HKU\TempDU C:\Users\Default\NTUSER.DAT & reg add "HKU\TempDU\Software\Policies\Microsoft\Windows\Explorer" /v DisableSearchBoxSuggestions /t REG_DWORD /d 1 /f & reg unload HKU\TempDU</Path>
|
<Path>cmd /c reg load HKU\TempDU C:\Users\Default\NTUSER.DAT & reg add "HKU\TempDU\Software\Policies\Microsoft\Windows\Explorer" /v DisableSearchBoxSuggestions /t REG_DWORD /d 1 /f & reg unload HKU\TempDU</Path>
|
||||||
<Description>Disable Bing/web search suggestions (Default User hive)</Description>
|
<Description>Disable Bing/web search suggestions (Default User hive)</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Search Web when searching Windows -->
|
<!-- Disable Search Web when searching Windows -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>35</Order>
|
<Order>35</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v ConnectedSearchUseWeb /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v ConnectedSearchUseWeb /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Connected Search Use Web</Description>
|
<Description>Disable Connected Search Use Web</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Cortana Speech Recognition -->
|
<!-- Disable Cortana Speech Recognition -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>36</Order>
|
<Order>36</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\InputPersonalization" /v AllowInputPersonalization /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\InputPersonalization" /v AllowInputPersonalization /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Cortana Speech Recognition</Description>
|
<Description>Disable Cortana Speech Recognition</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
|
|
||||||
<!-- Disable Search Highlights (news/trending) -->
|
<!-- Disable Search Highlights (news/trending) -->
|
||||||
<RunSynchronousCommand wcm:action="add">
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<Order>38</Order>
|
<Order>38</Order>
|
||||||
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f</Path>
|
<Path>cmd /c reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v EnableDynamicContentInWSB /t REG_DWORD /d 0 /f</Path>
|
||||||
<Description>Disable Search Highlights</Description>
|
<Description>Disable Search Highlights</Description>
|
||||||
</RunSynchronousCommand>
|
</RunSynchronousCommand>
|
||||||
</RunSynchronous>
|
|
||||||
</component>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
</settings>
|
<Order>39</Order>
|
||||||
|
<Path>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Deploy\Applications\BPRT\Register-BulkEnrollTask-Standard.ps1"</Path>
|
||||||
<!-- 4. oobeSystem: hide OEM/EULA screens -->
|
<Description>Add bulk enrollment orchestrator to Run key</Description>
|
||||||
<settings pass="oobeSystem">
|
</RunSynchronousCommand>
|
||||||
<component name="Microsoft-Windows-Shell-Setup"
|
<RunSynchronousCommand wcm:action="add">
|
||||||
processorArchitecture="amd64"
|
<Order>40</Order>
|
||||||
publicKeyToken="31bf3856ad364e35"
|
<Path>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</Path>
|
||||||
language="neutral"
|
<Description>Load Default User registry hive</Description>
|
||||||
versionScope="nonSxS">
|
</RunSynchronousCommand>
|
||||||
<OOBE>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<HideEULAPage>true</HideEULAPage>
|
<Order>41</Order>
|
||||||
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /t REG_DWORD /d 0 /f</Path>
|
||||||
<HideOnlineAccountScreens>false</HideOnlineAccountScreens>
|
<Description>Disable startup delay for all new users</Description>
|
||||||
<HideWirelessSetupInOOBE>false</HideWirelessSetupInOOBE>
|
</RunSynchronousCommand>
|
||||||
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<NetworkLocation>Work</NetworkLocation>
|
<Order>42</Order>
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /t REG_DWORD /d 0 /f</Path>
|
||||||
<SkipUserOOBE>false</SkipUserOOBE>
|
<Description>Disable wait for idle state for all new users</Description>
|
||||||
<SkipMachineOOBE>false</SkipMachineOOBE>
|
</RunSynchronousCommand>
|
||||||
</OOBE>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<FirstLogonCommands>
|
<Order>43</Order>
|
||||||
<SynchronousCommand wcm:action="add">
|
<Path>reg.exe add "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v StartShownOnUpgrade /t REG_DWORD /d 1 /f</Path>
|
||||||
<Order>1</Order>
|
<Description>Prevent Start menu from opening on first login</Description>
|
||||||
<CommandLine>shutdown -a</CommandLine>
|
</RunSynchronousCommand>
|
||||||
<Description>Cancel any scheduled shutdown from Office installation</Description>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
</SynchronousCommand>
|
<Order>44</Order>
|
||||||
|
<Path>reg.exe unload HKU\TempDefault</Path>
|
||||||
<!-- Install Zscaler -->
|
<Description>Unload Default User registry hive</Description>
|
||||||
<SynchronousCommand wcm:action="add">
|
</RunSynchronousCommand>
|
||||||
<Order>2</Order>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<CommandLine>C:\Deploy\Applications\extra\zscaler\zscaler.bat</CommandLine>
|
<Order>45</Order>
|
||||||
<Description>Install Zscaler Client Connector</Description>
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v NoAutoUpdate /t REG_DWORD /d 1 /f</Path>
|
||||||
</SynchronousCommand>
|
<Description>Disable automatic Windows updates (NoAutoUpdate)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
<!-- Install Microsoft Office -->
|
<RunSynchronousCommand wcm:action="add">
|
||||||
<SynchronousCommand wcm:action="add">
|
<Order>46</Order>
|
||||||
<Order>3</Order>
|
<Path>reg add HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU /v AUOptions /t REG_DWORD /d 1 /f</Path>
|
||||||
<CommandLine>cmd /c "cd /d C:\Deploy\Applications\extra\office\ && install.bat"</CommandLine>
|
<Description>Disable automatic Windows updates (AUOptions)</Description>
|
||||||
<Description>Install Microsoft Office</Description>
|
</RunSynchronousCommand>
|
||||||
</SynchronousCommand>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
</FirstLogonCommands>
|
<Order>47</Order>
|
||||||
</component>
|
<Path>reg.exe add "HKLM\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "doskey vanilla=powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File C:\Deploy\Applications\BPRT\Show-VanillaConfirmation.ps1" /f</Path>
|
||||||
</settings>
|
<Description>Define vanilla CMD alias for manual OOBE fallback via Shift+F10</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
</unattend>
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>48</Order>
|
||||||
|
<Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /t REG_DWORD /d 1 /f</Path>
|
||||||
|
<Description>Block all removable media reads to prevent PPKG auto-detection at OOBE</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>49</Order>
|
||||||
|
<Path>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 3 -Status succeeded -Stage "Ready at OOBE - hand to the employee"</Path>
|
||||||
|
<Description>PXE status: stage 3 of 3, done (the rest needs the employee to log in)</Description>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
</RunSynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
<!-- 4. oobeSystem: hide OEM/EULA screens -->
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup"
|
||||||
|
processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35"
|
||||||
|
language="neutral"
|
||||||
|
versionScope="nonSxS">
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
|
<HideOnlineAccountScreens>false</HideOnlineAccountScreens>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
|
<NetworkLocation>Work</NetworkLocation>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
<SkipUserOOBE>false</SkipUserOOBE>
|
||||||
|
<SkipMachineOOBE>false</SkipMachineOOBE>
|
||||||
|
</OOBE>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 4 -Stage "First logon"</CommandLine>
|
||||||
|
<Description>PXE status: stage 4 of 5</Description>
|
||||||
|
<Order>1</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc config wuauserv start= disabled</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>4</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>cmd /c sc stop wuauserv</CommandLine>
|
||||||
|
<Description>disable windows update</Description>
|
||||||
|
<Order>5</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe load HKU\TempDefault C:\Users\Default\NTUSER.DAT</CommandLine>
|
||||||
|
<Description>Load Default User registry hive to reverse startup delay</Description>
|
||||||
|
<Order>6</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v StartupDelayInMSec /f</CommandLine>
|
||||||
|
<Description>Remove startup delay override from Default User</Description>
|
||||||
|
<Order>7</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKU\TempDefault\Software\Microsoft\Windows\CurrentVersion\Explorer\Serialize" /v WaitForIdleState /f</CommandLine>
|
||||||
|
<Description>Remove wait for idle state override from Default User</Description>
|
||||||
|
<Order>8</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe unload HKU\TempDefault</CommandLine>
|
||||||
|
<Description>Unload Default User registry hive</Description>
|
||||||
|
<Order>9</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>reg.exe delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices" /v Deny_All /f</CommandLine>
|
||||||
|
<Description>Restore removable media access after OOBE PPKG block</Description>
|
||||||
|
<Order>10</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<CommandLine>powershell.exe -NoP -EP Bypass -File C:\ProgramData\PxeStatus\Send-ImagingStage.ps1 -PCType gea-standard -StageIndex 5 -Stage "Setup complete - enrollment continues on the PC" -Status succeeded</CommandLine>
|
||||||
|
<Description>PXE status: stage 5 of 5</Description>
|
||||||
|
<Order>11</Order>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
|
||||||
|
</unattend>
|
||||||
|
|||||||
@@ -7,35 +7,46 @@
|
|||||||
# NIC driver set), download Blancco rootfs (squashfs), overlay-mount, and
|
# NIC driver set), download Blancco rootfs (squashfs), overlay-mount, and
|
||||||
# switch_root into Blancco's userspace.
|
# switch_root into Blancco's userspace.
|
||||||
#
|
#
|
||||||
# Verbose trace + shell-on-NIC-failure because silent hangs during Blancco
|
# Quiet by default: one line per step. Add blancco.debug=1 to the kernel
|
||||||
# PXE boot are painful to debug. set -x goes to /dev/console so the screen
|
# cmdline for the full trace (set -x, modprobe -v, ip link/addr). Failures
|
||||||
# shows every step; if no NIC appears after the modprobe sweep, we dump
|
# always print their diagnostics and drop to sh so the operator can
|
||||||
# dmesg / lspci / /proc/modules and drop to sh so the operator can
|
|
||||||
# investigate without re-imaging.
|
# investigate without re-imaging.
|
||||||
|
|
||||||
exec >/dev/console 2>&1
|
exec >/dev/console 2>&1
|
||||||
set -x
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "============================================"
|
|
||||||
echo " Blancco PXE loader (verbose)"
|
|
||||||
echo "============================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
export PATH=/bin:/sbin
|
export PATH=/bin:/sbin
|
||||||
mount -t proc proc /proc
|
mount -t proc proc /proc
|
||||||
mount -t sysfs sysfs /sys
|
mount -t sysfs sysfs /sys
|
||||||
mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs tmpfs /dev
|
mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs tmpfs /dev
|
||||||
|
|
||||||
|
DEBUG=""
|
||||||
|
grep -q 'blancco.debug=1' /proc/cmdline && DEBUG=1
|
||||||
|
# dbg CMD...: run only in debug mode
|
||||||
|
dbg() { [ -n "$DEBUG" ] && "$@"; return 0; }
|
||||||
|
if [ -n "$DEBUG" ]; then
|
||||||
|
set -x
|
||||||
|
else
|
||||||
|
printf '\033c' # clear the kernel messages off the screen
|
||||||
|
dmesg -n 1 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Blancco Drive Eraser - loading from $( [ -n "$DEBUG" ] && echo "PXE (debug)" || echo "PXE")"
|
||||||
|
echo ""
|
||||||
mkdir -p /tmp /run /run/lower /run/upper /run/work /run/newroot
|
mkdir -p /tmp /run /run/lower /run/upper /run/work /run/newroot
|
||||||
KVER=$(uname -r)
|
KVER=$(uname -r)
|
||||||
echo "Running kernel: $KVER"
|
dbg echo "Running kernel: $KVER"
|
||||||
ls /lib/modules/ 2>/dev/null
|
dbg ls /lib/modules/
|
||||||
|
|
||||||
echo "[1/5] Loading NIC drivers via modprobe (resolves deps automatically)..."
|
echo " [1/5] Network drivers"
|
||||||
# Throw the full common-NIC driver list at the wall. modprobe resolves the
|
# Throw the full common-NIC driver list at the wall. modprobe resolves the
|
||||||
# deps from /lib/modules/$KVER/modules.dep (built by prepare-boot-tools).
|
# deps from /lib/modules/$KVER/modules.dep (built by prepare-boot-tools).
|
||||||
# Anything missing is silently ignored; whatever matches PCI IDs will bind.
|
# Anything missing is silently ignored; whatever matches PCI IDs will bind.
|
||||||
|
# USB host controllers first: xhci_pci is a module in the Ubuntu kernel
|
||||||
|
# (CONFIG_USB_XHCI_PCI=m) and there is no udev here, so without this no
|
||||||
|
# USB 3 / USB-C port works and USB-C to RJ45 adapters never appear.
|
||||||
for drv in \
|
for drv in \
|
||||||
|
xhci_pci xhci_pci_renesas \
|
||||||
mii libphy ptp \
|
mii libphy ptp \
|
||||||
e1000 e1000e igb igc ixgbe ixgbevf i40e ice iavf \
|
e1000 e1000e igb igc ixgbe ixgbevf i40e ice iavf \
|
||||||
tg3 bnx2 bnx2x bnxt_en b44 \
|
tg3 bnx2 bnx2x bnxt_en b44 \
|
||||||
@@ -46,15 +57,19 @@ for drv in \
|
|||||||
alx atl1c atl1e atl2 \
|
alx atl1c atl1e atl2 \
|
||||||
via_rhine via_velocity forcedeth \
|
via_rhine via_velocity forcedeth \
|
||||||
pegasus dm9601 asix ax88179_178a cdc_ether cdc_ncm rndis_host; do
|
pegasus dm9601 asix ax88179_178a cdc_ether cdc_ncm rndis_host; do
|
||||||
modprobe -v "$drv" 2>/dev/null && echo " OK $drv" || true
|
if [ -n "$DEBUG" ]; then
|
||||||
|
modprobe -v "$drv" 2>/dev/null && echo " OK $drv" || true
|
||||||
|
else
|
||||||
|
modprobe "$drv" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
echo "[2/5] /sys/class/net after driver load:"
|
echo " [2/5] Waiting for a network link"
|
||||||
ls /sys/class/net/ || true
|
dbg ls /sys/class/net/
|
||||||
ip link || true
|
dbg ip link
|
||||||
|
|
||||||
echo " Waiting up to 60s for non-lo interface..."
|
dbg echo " Waiting up to 60s for non-lo interface..."
|
||||||
IFACE=""
|
IFACE=""
|
||||||
for i in $(seq 1 60); do
|
for i in $(seq 1 60); do
|
||||||
for n in /sys/class/net/*; do
|
for n in /sys/class/net/*; do
|
||||||
@@ -76,21 +91,87 @@ if [ -z "$IFACE" ]; then
|
|||||||
exec /bin/sh
|
exec /bin/sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo " IFACE=$IFACE, bringing up..."
|
# The first interface to appear is not necessarily the cabled one: a
|
||||||
|
# second onboard port, a dock or a USB NIC can come first, and a static
|
||||||
|
# IP on a dead port leaves wget at "Connecting" forever. Bring every
|
||||||
|
# wired interface up and use the first with carrier.
|
||||||
|
for n in /sys/class/net/*; do
|
||||||
|
name="${n##*/}"
|
||||||
|
case "$name" in lo|wl*) continue ;; esac
|
||||||
|
ip link set "$name" up 2>/dev/null || ifconfig "$name" up 2>/dev/null || true
|
||||||
|
done
|
||||||
|
dbg echo " Waiting up to 30s for a cabled interface..."
|
||||||
|
CARRIER=""
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
for n in /sys/class/net/*; do
|
||||||
|
name="${n##*/}"
|
||||||
|
case "$name" in lo|wl*) continue ;; esac
|
||||||
|
# USB NICs enumerate seconds after onboard ports: bring up late ones too
|
||||||
|
[ "$(cat "$n/operstate" 2>/dev/null)" = "down" ] && { ip link set "$name" up 2>/dev/null || ifconfig "$name" up 2>/dev/null || true; }
|
||||||
|
[ "$(cat "$n/carrier" 2>/dev/null)" = "1" ] && CARRIER="$name" && break 2
|
||||||
|
done
|
||||||
|
sleep 1
|
||||||
|
echo -n "."
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
if [ -z "$CARRIER" ]; then
|
||||||
|
echo "ERROR: no wired interface has link. Interfaces seen:"
|
||||||
|
for n in /sys/class/net/*; do
|
||||||
|
drv=$(readlink "$n/device/driver" 2>/dev/null)
|
||||||
|
echo " ${n##*/} carrier=$(cat "$n/carrier" 2>/dev/null) driver=${drv##*/}"
|
||||||
|
done
|
||||||
|
echo "=== kernel messages (NIC / firmware / link) ==="
|
||||||
|
dmesg | grep -i -E 'eth|enp|enx|r8152|r8169|e1000e|igc|cdc|firmware|link' | tail -20
|
||||||
|
echo "Check the cable, then type 'exit' to reboot."
|
||||||
|
exec /bin/sh
|
||||||
|
fi
|
||||||
|
IFACE="$CARRIER"
|
||||||
|
|
||||||
|
echo " using $IFACE"
|
||||||
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
|
|
||||||
sleep 1
|
|
||||||
echo " IP: 10.9.100.250 SERVER: $SERVER"
|
|
||||||
ip addr
|
|
||||||
|
|
||||||
echo "[3/5] Downloading airootfs.sfs (~756 MB)..."
|
# Where the release files (rootfs, preferences, licence container,
|
||||||
wget -O /tmp/airootfs.sfs http://$SERVER/blancco/arch/x86_64/airootfs.sfs 2>&1
|
# config.img) come from. Default /blancco, the active release. The webapp's
|
||||||
|
# test menu entry adds blancco.base=/blancco/staged/<version> to boot a
|
||||||
|
# staged release before it is activated. The kmod tarball, progress
|
||||||
|
# reporter and debug key are not part of a release and stay in /blancco.
|
||||||
|
BASE=/blancco
|
||||||
|
for arg in $(cat /proc/cmdline); do
|
||||||
|
case "$arg" in
|
||||||
|
blancco.base=/blancco/*) BASE="${arg#blancco.base=}" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
case "$BASE" in
|
||||||
|
*..*|*[!A-Za-z0-9._/-]*) BASE=/blancco ;;
|
||||||
|
esac
|
||||||
|
BASE="${BASE%/}"
|
||||||
|
[ "$BASE" = /blancco ] || echo " release under test: $BASE"
|
||||||
|
|
||||||
|
# Coarse progress for the webapp's /imaging dashboard (same endpoint the
|
||||||
|
# WinPE imaging clients use). Stage 1 also resets a finished tile for this
|
||||||
|
# serial. Best effort: a webapp outage must never stop a wipe.
|
||||||
|
post_status() {
|
||||||
|
# sed, not tr: the initrd's busybox has no tr link
|
||||||
|
serial=$(sed 's/["\\]//g' /sys/class/dmi/id/product_serial 2>/dev/null)
|
||||||
|
[ -n "$serial" ] || return 0
|
||||||
|
wget -q -T 5 -O /dev/null --header 'Content-Type: application/json' \
|
||||||
|
--post-data "{\"serial\":\"$serial\",\"pctype\":\"blancco\",\"source\":\"blancco\",\"current_stage\":\"$2\",\"stage_index\":$1,\"stage_total\":7,\"status\":\"in_progress\",\"ip\":\"172.16.9.250\"}" \
|
||||||
|
"http://$SERVER:9009/imaging/status" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
ifconfig "$IFACE" 172.16.9.250 netmask 255.255.255.0 up
|
||||||
|
sleep 1
|
||||||
|
dbg echo " IP: 172.16.9.250 SERVER: $SERVER"
|
||||||
|
dbg ip addr
|
||||||
|
|
||||||
|
echo " [3/5] Downloading Blancco (~850 MB)"
|
||||||
|
post_status 1 "Downloading Blancco"
|
||||||
|
wget -T 30 -O /tmp/airootfs.sfs http://$SERVER$BASE/arch/x86_64/airootfs.sfs 2>&1
|
||||||
[ -s /tmp/airootfs.sfs ] || { echo "ERROR: download failed"; exec /bin/sh; }
|
[ -s /tmp/airootfs.sfs ] || { echo "ERROR: download failed"; exec /bin/sh; }
|
||||||
|
|
||||||
echo "[4/5] Mounting rootfs + overlay..."
|
echo " [4/5] Preparing system"
|
||||||
modprobe overlay 2>/dev/null || insmod /lib/modules/$KVER/kernel/fs/overlayfs/overlay.ko 2>/dev/null
|
modprobe overlay 2>/dev/null || insmod /lib/modules/$KVER/kernel/fs/overlayfs/overlay.ko 2>/dev/null
|
||||||
modprobe squashfs 2>/dev/null || insmod /lib/modules/$KVER/kernel/fs/squashfs/squashfs.ko 2>/dev/null
|
modprobe squashfs 2>/dev/null || insmod /lib/modules/$KVER/kernel/fs/squashfs/squashfs.ko 2>/dev/null
|
||||||
modprobe loop 2>/dev/null
|
modprobe loop 2>/dev/null
|
||||||
@@ -100,8 +181,12 @@ mount -t tmpfs -o size=50% tmpfs /run/upper
|
|||||||
mkdir -p /run/upper/upper /run/upper/work
|
mkdir -p /run/upper/upper /run/upper/work
|
||||||
mount -t overlay overlay -o lowerdir=/run/lower,upperdir=/run/upper/upper,workdir=/run/upper/work /run/newroot
|
mount -t overlay overlay -o lowerdir=/run/lower,upperdir=/run/upper/upper,workdir=/run/upper/work /run/newroot
|
||||||
|
|
||||||
echo "[5/5] Fetching kmod tarball + config..."
|
echo " [5/5] Loading drivers and settings"
|
||||||
wget -O /tmp/kmod.tar.gz http://$SERVER/blancco/kmod.tar.gz 2>&1
|
# Modules (+ iwlwifi firmware) for the running kernel. kmod-$KVER.tar.gz
|
||||||
|
# lets several boot kernels share one web root; kmod.tar.gz is the
|
||||||
|
# original single-kernel name, kept as the fallback.
|
||||||
|
wget -q -T 30 -O /tmp/kmod.tar.gz "http://$SERVER/blancco/kmod-$KVER.tar.gz" 2>/dev/null ||
|
||||||
|
wget -q -T 30 -O /tmp/kmod.tar.gz http://$SERVER/blancco/kmod.tar.gz 2>&1
|
||||||
[ -s /tmp/kmod.tar.gz ] && (cd /run/newroot && gunzip -c /tmp/kmod.tar.gz | tar xf - && rm -f /tmp/kmod.tar.gz)
|
[ -s /tmp/kmod.tar.gz ] && (cd /run/newroot && gunzip -c /tmp/kmod.tar.gz | tar xf - && rm -f /tmp/kmod.tar.gz)
|
||||||
|
|
||||||
mkdir -p /run/newroot/albus
|
mkdir -p /run/newroot/albus
|
||||||
@@ -111,8 +196,8 @@ mkdir -p /run/newroot/albus
|
|||||||
# /albus/preferences.save (factory defaults, empty network_share) so
|
# /albus/preferences.save (factory defaults, empty network_share) so
|
||||||
# erasure reports never reach SMB. Fail loud here instead.
|
# erasure reports never reach SMB. Fail loud here instead.
|
||||||
PREFS=/run/newroot/albus/preferences.xml
|
PREFS=/run/newroot/albus/preferences.xml
|
||||||
if ! wget -O "$PREFS" "http://$SERVER/blancco/preferences.xml"; then
|
if ! wget -q -T 30 -O "$PREFS" "http://$SERVER$BASE/preferences.xml"; then
|
||||||
echo "ERROR: preferences.xml download failed from http://$SERVER/blancco/preferences.xml"
|
echo "ERROR: preferences.xml download failed from http://$SERVER$BASE/preferences.xml"
|
||||||
echo "Reports would fall back to factory defaults (no SMB target)."
|
echo "Reports would fall back to factory defaults (no SMB target)."
|
||||||
echo "Dropping to shell - check Apache + network."
|
echo "Dropping to shell - check Apache + network."
|
||||||
exec /bin/sh
|
exec /bin/sh
|
||||||
@@ -132,11 +217,29 @@ fi
|
|||||||
# fires, we want the fallback to still have the right SMB target.
|
# fires, we want the fallback to still have the right SMB target.
|
||||||
cp -f "$PREFS" /run/newroot/albus/preferences.save
|
cp -f "$PREFS" /run/newroot/albus/preferences.save
|
||||||
|
|
||||||
if ! wget -O /run/newroot/albus/config.xml "http://$SERVER/blancco/config-clean.xml"; then
|
if ! wget -q -T 30 -O /run/newroot/albus/config.xml "http://$SERVER$BASE/config-clean.xml"; then
|
||||||
echo "ERROR: config.xml (license container) download failed."
|
echo "ERROR: config.xml (license container) download failed."
|
||||||
exec /bin/sh
|
exec /bin/sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# On a native boot config.img is an initrd, its files land in the
|
||||||
|
# initramfs root and archiso keeps them in /run/initramfs, where Blancco's
|
||||||
|
# /opt/scripts/inc/* pick up report.key (report signing), certificate.ca,
|
||||||
|
# hybrid.ids and flexible_container. This path never had them, so
|
||||||
|
# 1_reportKey wrote an empty /albus/report.key. Recreate the layout on a
|
||||||
|
# tmpfs at the new root's /run; systemd leaves an existing /run mounted.
|
||||||
|
mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs /run/newroot/run
|
||||||
|
mkdir -p /run/newroot/run/initramfs
|
||||||
|
if wget -q -T 30 -O /tmp/config.img "http://$SERVER$BASE/config.img" &&
|
||||||
|
(cd /run/newroot/run/initramfs && cpio -id < /tmp/config.img 2>/dev/null) &&
|
||||||
|
[ -s /run/newroot/run/initramfs/report.key ]; then
|
||||||
|
dbg ls -la /run/newroot/run/initramfs
|
||||||
|
else
|
||||||
|
echo "ERROR: config.img (report key, certificate) download or unpack failed."
|
||||||
|
exec /bin/sh
|
||||||
|
fi
|
||||||
|
rm -f /tmp/config.img
|
||||||
|
|
||||||
mkdir -p /run/newroot/etc/X11/xorg.conf.d
|
mkdir -p /run/newroot/etc/X11/xorg.conf.d
|
||||||
# Don't pin a single Xorg driver - hardware varies per site (Intel iGPU,
|
# Don't pin a single Xorg driver - hardware varies per site (Intel iGPU,
|
||||||
# NVIDIA GK208, AMD, etc). Let Xorg auto-pick. With nomodeset + KMS
|
# NVIDIA GK208, AMD, etc). Let Xorg auto-pick. With nomodeset + KMS
|
||||||
@@ -168,6 +271,24 @@ HandleLidSwitchDocked=ignore
|
|||||||
HandlePowerKey=ignore
|
HandlePowerKey=ignore
|
||||||
XEOF
|
XEOF
|
||||||
|
|
||||||
|
# Restart after an erasure stalled on "A stop job is running for
|
||||||
|
# /mnt/share": the network goes down before the SMB report share is
|
||||||
|
# unmounted, and the CIFS umount waits out the 90s timeout. A mount unit's
|
||||||
|
# timeout is TimeoutSec= (default: the START timeout), so
|
||||||
|
# DefaultTimeoutStopSec alone does not reach it. Lazy + forced umount
|
||||||
|
# detaches the dead share at once; reports are written long before this.
|
||||||
|
mkdir -p /run/newroot/etc/systemd/system.conf.d /run/newroot/etc/systemd/system/mnt-share.mount.d
|
||||||
|
cat > /run/newroot/etc/systemd/system.conf.d/10-fast-stop.conf << XEOF
|
||||||
|
[Manager]
|
||||||
|
DefaultTimeoutStopSec=10s
|
||||||
|
XEOF
|
||||||
|
cat > /run/newroot/etc/systemd/system/mnt-share.mount.d/10-fast-umount.conf << XEOF
|
||||||
|
[Mount]
|
||||||
|
TimeoutSec=10s
|
||||||
|
LazyUnmount=yes
|
||||||
|
ForceUnmount=yes
|
||||||
|
XEOF
|
||||||
|
|
||||||
# Disable Xorg screensaver + DPMS so display stays on during long erasures.
|
# Disable Xorg screensaver + DPMS so display stays on during long erasures.
|
||||||
# Numbered 00-* to run before vendor xinitrc.d hooks.
|
# Numbered 00-* to run before vendor xinitrc.d hooks.
|
||||||
mkdir -p /run/newroot/etc/X11/xinit/xinitrc.d
|
mkdir -p /run/newroot/etc/X11/xinit/xinitrc.d
|
||||||
@@ -180,6 +301,115 @@ XEOF
|
|||||||
chmod +x /run/newroot/etc/X11/xinit/xinitrc.d/00-no-screen-blank.sh
|
chmod +x /run/newroot/etc/X11/xinit/xinitrc.d/00-no-screen-blank.sh
|
||||||
|
|
||||||
mkdir -p /run/newroot/proc /run/newroot/sys /run/newroot/dev /run/newroot/run /run/newroot/tmp
|
mkdir -p /run/newroot/proc /run/newroot/sys /run/newroot/dev /run/newroot/run /run/newroot/tmp
|
||||||
|
# Auto-save reports to the SMB share. process=auto never writes the share
|
||||||
|
# (Albus forces auto_backup off outside workflow/manual) but does keep each
|
||||||
|
# report in /reports; a small service copies them across.
|
||||||
|
mkdir -p /run/newroot/usr/local/sbin /run/newroot/etc/systemd/system/multi-user.target.wants
|
||||||
|
cat > /run/newroot/usr/local/sbin/blancco-report-sync << 'XEOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# Copy Blancco reports to the SMB report share.
|
||||||
|
#
|
||||||
|
# In process=auto Albus saves each report to /reports/<document_id>.xml
|
||||||
|
# (and .pdf when pdf+xml is selected) and uploads it to the cloud, but never
|
||||||
|
# writes the share: auto_backup is forced off outside workflow/manual mode.
|
||||||
|
# Albus has the share mounted at /mnt/share already, so copy each finished
|
||||||
|
# file there, named the way a manual Save names it (UTC
|
||||||
|
# YYYYMMDD_HHMMSS_report.<ext>, the time taken from the XML so the PDF of
|
||||||
|
# the same report gets the same name).
|
||||||
|
# Usage: blancco-report-sync [--once]
|
||||||
|
DONE=/var/lib/blancco-report-sync
|
||||||
|
mkdir -p "$DONE"
|
||||||
|
while :; do
|
||||||
|
for f in /reports/*.xml /reports/*.pdf; do
|
||||||
|
[ -s "$f" ] || continue
|
||||||
|
base=$(basename "$f")
|
||||||
|
[ -e "$DONE/$base" ] && continue
|
||||||
|
# Leave files Albus may still be writing.
|
||||||
|
[ $(( $(date +%s) - $(stat -c %Y "$f") )) -ge 5 ] || continue
|
||||||
|
mountpoint -q /mnt/share || continue
|
||||||
|
id=${base%.*}
|
||||||
|
ext=${base##*.}
|
||||||
|
ref="/reports/$id.xml"
|
||||||
|
[ -f "$ref" ] || ref="$f"
|
||||||
|
name="$(date -u -r "$ref" +%Y%m%d_%H%M%S)_report.$ext"
|
||||||
|
if cp "$f" "/mnt/share/$name.part" && mv -f "/mnt/share/$name.part" "/mnt/share/$name"; then
|
||||||
|
touch "$DONE/$base"
|
||||||
|
echo "copied $base to $name"
|
||||||
|
serial=$(tr -d '"\\' 2>/dev/null < /sys/class/dmi/id/product_serial)
|
||||||
|
[ -n "$serial" ] && curl -s -m 5 -o /dev/null -H 'Content-Type: application/json' \
|
||||||
|
-d "{\"serial\":\"$serial\",\"log_lines\":[\"Report copied to share: $name\"]}" \
|
||||||
|
http://172.16.9.1:9009/imaging/status || true
|
||||||
|
else
|
||||||
|
rm -f "/mnt/share/$name.part"
|
||||||
|
echo "copy of $base failed, will retry"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$1" = "--once" ] && exit 0
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
XEOF
|
||||||
|
chmod 755 /run/newroot/usr/local/sbin/blancco-report-sync
|
||||||
|
cat > /run/newroot/etc/systemd/system/blancco-report-sync.service << 'XEOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Copy Blancco reports to the SMB report share
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/sbin/blancco-report-sync
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
XEOF
|
||||||
|
ln -sf /etc/systemd/system/blancco-report-sync.service \
|
||||||
|
/run/newroot/etc/systemd/system/multi-user.target.wants/blancco-report-sync.service
|
||||||
|
|
||||||
|
# Live erase progress for the /imaging dashboard: follows the AlbusSeven
|
||||||
|
# journal and posts stages and drive percentages. Fetched at boot so the
|
||||||
|
# server copy (playbook/blancco-progress.py) is the only one.
|
||||||
|
if wget -q -T 10 -O /run/newroot/usr/local/sbin/blancco-progress "http://$SERVER/blancco/blancco-progress.py"; then
|
||||||
|
chmod 755 /run/newroot/usr/local/sbin/blancco-progress
|
||||||
|
cat > /run/newroot/etc/systemd/system/blancco-progress.service << 'XEOF'
|
||||||
|
[Unit]
|
||||||
|
Description=Report Blancco erase progress to the PXE webapp
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/python3 /usr/local/sbin/blancco-progress
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
XEOF
|
||||||
|
ln -sf /etc/systemd/system/blancco-progress.service \
|
||||||
|
/run/newroot/etc/systemd/system/multi-user.target.wants/blancco-progress.service
|
||||||
|
else
|
||||||
|
echo " (no blancco-progress.py on server: no live progress on the dashboard)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Site timezone for Blancco's own clock (GUI, logs). Report timestamps keep
|
||||||
|
# an explicit UTC offset; report.utc_offset in the preferences stays 0
|
||||||
|
# because it is a fixed offset and cannot follow daylight saving.
|
||||||
|
if [ -e /run/newroot/usr/share/zoneinfo/America/New_York ]; then
|
||||||
|
ln -sf /usr/share/zoneinfo/America/New_York /run/newroot/etc/localtime
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Debug boots only: Blancco's own debug mode (/etc/debug.environment makes
|
||||||
|
# isDebugMode true, so 2_sshd starts sshd) plus an SSH key for root.
|
||||||
|
# Root is locked and sshd is key-only, so without the key there is no way
|
||||||
|
# in. The key is served from the PXE server and never baked in here.
|
||||||
|
if [ -n "$DEBUG" ]; then
|
||||||
|
touch /run/newroot/etc/debug.environment
|
||||||
|
mkdir -p /run/newroot/root/.ssh
|
||||||
|
if wget -q -T 10 -O /run/newroot/root/.ssh/authorized_keys "http://$SERVER/blancco/debug_authorized_keys"; then
|
||||||
|
chmod 700 /run/newroot/root/.ssh
|
||||||
|
chmod 600 /run/newroot/root/.ssh/authorized_keys
|
||||||
|
echo " debug: sshd on, root key installed"
|
||||||
|
else
|
||||||
|
echo " debug: no debug_authorized_keys on server, SSH stays closed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
mount --move /proc /run/newroot/proc
|
mount --move /proc /run/newroot/proc
|
||||||
mount --move /sys /run/newroot/sys
|
mount --move /sys /run/newroot/sys
|
||||||
mount --move /dev /run/newroot/dev
|
mount --move /dev /run/newroot/dev
|
||||||
@@ -196,5 +426,6 @@ mount --bind /dev/null /run/newroot/sys/power/disk 2>/dev/null || true
|
|||||||
mount --bind /dev/null /run/newroot/sys/power/mem_sleep 2>/dev/null || true
|
mount --bind /dev/null /run/newroot/sys/power/mem_sleep 2>/dev/null || true
|
||||||
mount --bind /dev/null /run/newroot/sys/power/autosleep 2>/dev/null || true
|
mount --bind /dev/null /run/newroot/sys/power/autosleep 2>/dev/null || true
|
||||||
|
|
||||||
echo "Switching root..."
|
echo " Starting Blancco..."
|
||||||
|
post_status 2 "Starting Blancco"
|
||||||
exec switch_root /run/newroot /sbin/init
|
exec switch_root /run/newroot /sbin/init
|
||||||
|
|||||||
@@ -1,49 +1,42 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- FASDHFOSGHSVASLGHASDLASDFUILEFHLKJDKLFJGKLXCJGHFKLJDGHKLFJ -->
|
<!-- FASDHFOSGHSVASLGHASDLASDFUILEFHLKJDKLFJGKLXCJGHFKLJDGHKLFJ -->
|
||||||
<root>
|
<root>
|
||||||
<blancco type="albus-preferences" version="4.15"/>
|
<blancco type="albus-preferences" version="4.20"/>
|
||||||
<version>
|
<version>
|
||||||
<generation>7</generation>
|
<generation>7</generation>
|
||||||
<major>15</major>
|
<major>20</major>
|
||||||
<minor>1</minor>
|
<minor>0</minor>
|
||||||
</version>
|
</version>
|
||||||
|
<integrity>qJ4hjOqd1NaMbEPsbmaz+8gZlG1duEXinTXI+Wrsc00=</integrity>
|
||||||
<configuration>
|
<configuration dect="3.15.0" imported="false">
|
||||||
|
|
||||||
<device_enrollment_detection>
|
|
||||||
<persistent_software>false</persistent_software>
|
|
||||||
</device_enrollment_detection>
|
|
||||||
|
|
||||||
<processes type="array" key="process">
|
<processes type="array" key="process">
|
||||||
<process>workflow</process>
|
<process>workflow</process>
|
||||||
<process>auto</process>
|
<process selected="true">auto</process>
|
||||||
<process>semi</process>
|
<process>semi</process>
|
||||||
<process selected="true">manual</process>
|
<process>manual</process>
|
||||||
</processes>
|
</processes>
|
||||||
|
<hardware_tests type="array" key="test" enabled="true">
|
||||||
<hardware_tests type="array" key="test" enabled="false">
|
<test enabled="false" mandatory="false" auto="true" threshold="60">battery</test>
|
||||||
<test enabled="true" mandatory="false" auto="true" threshold="60">battery</test>
|
|
||||||
<test enabled="true" mandatory="false" auto="true">cpu</test>
|
<test enabled="true" mandatory="false" auto="true">cpu</test>
|
||||||
<test enabled="false" mandatory="false" auto="true" passes="0">memory</test>
|
<test enabled="false" mandatory="false" auto="true" passes="0">memory</test>
|
||||||
<test enabled="true" mandatory="false" auto="true">motherboard</test>
|
<test enabled="true" mandatory="false" auto="true">motherboard</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" threshold="50" duration="600">battery_discharge</test>
|
<test enabled="false" mandatory="false" auto="false" threshold="50" duration="600">battery_discharge</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">bios_logo</test>
|
<test enabled="false" mandatory="false" auto="false">bios_logo</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" threshold="80" duration="180">cpu_stress</test>
|
<test enabled="false" mandatory="false" auto="false" threshold="80" duration="180">cpu_stress</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">display</test>
|
<test enabled="false" mandatory="false" auto="false">display</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" formfactor="compact" language="us">keyboard</test>
|
<test enabled="false" mandatory="false" auto="false" formfactor="compact" language="us">keyboard</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">microphone</test>
|
<test enabled="false" mandatory="false" auto="false">microphone</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" targets="">network</test>
|
<test enabled="false" mandatory="false" auto="false" targets="">network</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" tests="R">optical_devices</test>
|
<test enabled="false" mandatory="false" auto="false" tests="R">optical_devices</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">sound</test>
|
<test enabled="false" mandatory="false" auto="false">sound</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">pointing_devices</test>
|
<test enabled="false" mandatory="false" auto="false">pointing_devices</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">sim_presence</test>
|
<test enabled="false" mandatory="false" auto="false">sim_presence</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">speaker</test>
|
<test enabled="false" mandatory="false" auto="false">speaker</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">touchscreen</test>
|
<test enabled="false" mandatory="false" auto="false">touchscreen</test>
|
||||||
<test enabled="true" mandatory="false" auto="false" tests="R">usb_ports</test>
|
<test enabled="false" mandatory="false" auto="false" tests="R">usb_ports</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">webcam</test>
|
<test enabled="false" mandatory="false" auto="false">webcam</test>
|
||||||
<test enabled="true" mandatory="false" auto="false">wifi</test>
|
<test enabled="false" mandatory="false" auto="false">wifi</test>
|
||||||
</hardware_tests>
|
</hardware_tests>
|
||||||
|
|
||||||
<layouts type="array" key="layout">
|
<layouts type="array" key="layout">
|
||||||
<layout selected="true">us</layout>
|
<layout selected="true">us</layout>
|
||||||
<layout>be</layout>
|
<layout>be</layout>
|
||||||
@@ -71,7 +64,6 @@
|
|||||||
<layout>se</layout>
|
<layout>se</layout>
|
||||||
<layout>sk</layout>
|
<layout>sk</layout>
|
||||||
</layouts>
|
</layouts>
|
||||||
|
|
||||||
<locales type="array" key="locale">
|
<locales type="array" key="locale">
|
||||||
<locale selected="true">en_US</locale>
|
<locale selected="true">en_US</locale>
|
||||||
<locale>de_DE</locale>
|
<locale>de_DE</locale>
|
||||||
@@ -88,119 +80,110 @@
|
|||||||
<locale>zh_CN</locale>
|
<locale>zh_CN</locale>
|
||||||
<locale>zh_TW</locale>
|
<locale>zh_TW</locale>
|
||||||
</locales>
|
</locales>
|
||||||
|
<erasure_standards type="array" key="standard">
|
||||||
<erasure_standards key="standard" type="array">
|
|
||||||
<standard enabled="true" id="afssi_5020" name="Air Force System Security Instruction 5020"/>
|
<standard enabled="true" id="afssi_5020" name="Air Force System Security Instruction 5020"/>
|
||||||
<standard enabled="true" id="aperiodic_random" name="Aperiodic random overwrite"/>
|
<standard enabled="true" id="aperiodic_random" name="Aperiodic random overwrite"/>
|
||||||
<standard enabled="true" id="bl_ssd" name="Blancco SSD Erasure"/>
|
|
||||||
<standard enabled="true" id="bruce_schneier" name="Bruce Schneier's Algorithm"/>
|
|
||||||
<standard enabled="true" id="bsi_vs" name="BSI-2011-VS"/>
|
<standard enabled="true" id="bsi_vs" name="BSI-2011-VS"/>
|
||||||
<standard enabled="true" id="bsi_gs" name="BSI-GS"/>
|
<standard enabled="true" id="bsi_gs" name="BSI-GS"/>
|
||||||
<standard enabled="true" id="bsi_gse" name="BSI-GSE"/>
|
<standard enabled="true" id="bsi_gse" name="BSI-GSE"/>
|
||||||
<standard enabled="true" id="bsi_gsk" name="BSI-GSK"/>
|
<standard enabled="true" id="bsi_gsk" name="BSI-GSK"/>
|
||||||
|
<standard enabled="true" id="bl_ssd" name="Blancco SSD Erasure"/>
|
||||||
|
<standard enabled="true" id="bruce_schneier" name="Bruce Schneier's Algorithm"/>
|
||||||
<standard enabled="true" id="cesg_higher" name="CESG CPA - Higher Level"/>
|
<standard enabled="true" id="cesg_higher" name="CESG CPA - Higher Level"/>
|
||||||
<standard enabled="true" id="crypto_erase" name="Sanitize Cryptographic Erasure"/>
|
|
||||||
<standard enabled="true" id="dod" name="DoD 5220.22-M"/>
|
<standard enabled="true" id="dod" name="DoD 5220.22-M"/>
|
||||||
<standard enabled="true" id="dod_ece" name="DoD 5220.22-M ECE"/>
|
<standard enabled="true" id="dod_ece" name="DoD 5220.22-M ECE"/>
|
||||||
<standard enabled="true" id="nist_clear" name="NIST 800-88 Clear" pattern="0x00" pattern_type="static"/>
|
|
||||||
<standard selected="true" enabled="true" id="nist_purge" name="NIST 800-88 Purge"/>
|
|
||||||
<standard enabled="true" id="firmware" name="Firmware Based Erasure"/>
|
|
||||||
<standard enabled="true" id="extended_firmware" name="Extended Firmware Based Erasure"/>
|
<standard enabled="true" id="extended_firmware" name="Extended Firmware Based Erasure"/>
|
||||||
<standard enabled="true" id="hmg_lower" name="HMG Infosec Standard 5, Lower Standard"/>
|
<standard enabled="true" id="firmware" name="Firmware Based Erasure"/>
|
||||||
<standard enabled="true" id="hmg_higher" name="HMG Infosec Standard 5, Higher Standard"/>
|
<standard enabled="true" id="hmg_higher" name="HMG Infosec Standard 5, Higher Standard"/>
|
||||||
<standard enabled="true" id="nsa" name="NSA 130-1"/>
|
<standard enabled="true" id="hmg_lower" name="HMG Infosec Standard 5, Lower Standard"/>
|
||||||
<standard enabled="true" id="ncsc" name="National Computer Security Center (NCSC-TG-025)"/>
|
|
||||||
<standard enabled="true" id="navso" name="Navy Staff Office Publication (NAVSO P-5239-26)"/>
|
|
||||||
<standard enabled="true" id="tcg_crypto_erase" name="TCG Cryptographic Erasure"/>
|
|
||||||
<standard enabled="true" id="opnavinst" name="OPNAVINST 5239.1A"/>
|
|
||||||
<standard enabled="true" id="peter_gutmann" name="Peter Gutmann's Algorithm"/>
|
|
||||||
<standard enabled="true" id="random_byte_3x" name="Random Byte Overwrite (3x)"/>
|
|
||||||
<standard enabled="true" id="rcmp_tssit" name="RCMP TSSIT OPS-II"/>
|
|
||||||
<standard enabled="true" id="us_army" name="U.S. Army AR380-19"/>
|
|
||||||
<standard enabled="true" id="ieee_clear" name="IEEE 2883-2022 Clear"/>
|
<standard enabled="true" id="ieee_clear" name="IEEE 2883-2022 Clear"/>
|
||||||
<standard enabled="true" id="ieee_purge" name="IEEE 2883-2022 Purge"/>
|
<standard enabled="true" id="ieee_purge" name="IEEE 2883-2022 Purge"/>
|
||||||
<standard enabled="false" id="nist_pc" name="NIST 800-88" pattern="0x00" pattern_type="static"/>
|
<standard enabled="false" id="nist_pc" name="NIST 800-88" pattern="0x00" pattern_type="static"/>
|
||||||
|
<standard enabled="true" id="nist_clear" name="NIST 800-88 Clear" pattern="0x00" pattern_type="static"/>
|
||||||
|
<standard selected="true" enabled="true" id="nist_purge" name="NIST 800-88 Purge"/>
|
||||||
|
<standard enabled="true" id="nist_clear_rev2" name="NIST 800-88r2 Clear" pattern="0x00" pattern_type="static"/>
|
||||||
|
<standard enabled="true" id="nist_purge_rev2" name="NIST 800-88r2 Purge"/>
|
||||||
|
<standard enabled="true" id="nsa" name="NSA 130-1"/>
|
||||||
|
<standard enabled="true" id="ncsc" name="National Computer Security Center (NCSC-TG-025)"/>
|
||||||
|
<standard enabled="true" id="navso" name="Navy Staff Office Publication (NAVSO P-5239-26)"/>
|
||||||
|
<standard enabled="true" id="opnavinst" name="OPNAVINST 5239.1A"/>
|
||||||
|
<standard enabled="true" id="peter_gutmann" name="Peter Gutmann's Algorithm"/>
|
||||||
|
<standard enabled="true" id="rcmp_tssit" name="RCMP TSSIT OPS-II"/>
|
||||||
|
<standard enabled="true" id="random_byte_3x" name="Random Byte Overwrite (3x)"/>
|
||||||
|
<standard enabled="true" id="crypto_erase" name="Sanitize Cryptographic Erasure"/>
|
||||||
|
<standard enabled="true" id="tcg_crypto_erase" name="TCG Cryptographic Erasure"/>
|
||||||
|
<standard enabled="true" id="us_army" name="U.S. Army AR380-19"/>
|
||||||
</erasure_standards>
|
</erasure_standards>
|
||||||
|
<verification_standards type="array" key="standard">
|
||||||
<verification_standards key="standard" type="array">
|
<standard enabled="true" id="all_bytes_same" name="All bytes the same" pattern="0x00"/>
|
||||||
<standard enabled="true" id="all_bytes_same" pattern="0x00" name="All bytes the same"/>
|
|
||||||
<standard enabled="true" id="all_ones" name="All ones"/>
|
<standard enabled="true" id="all_ones" name="All ones"/>
|
||||||
<standard enabled="true" id="all_sectors_same" selected="true" name="All sectors the same"/>
|
<standard selected="true" enabled="true" id="all_sectors_same" name="All sectors the same"/>
|
||||||
<standard enabled="true" id="all_zeros" name="All zeros"/>
|
<standard enabled="true" id="all_zeros" name="All zeros"/>
|
||||||
</verification_standards>
|
</verification_standards>
|
||||||
|
|
||||||
<adapters key="adapter" type="array"/>
|
|
||||||
|
|
||||||
<network>
|
<network>
|
||||||
<enabled>false</enabled>
|
<enabled>true</enabled>
|
||||||
<dhcp>true</dhcp>
|
<dhcp>true</dhcp>
|
||||||
<sshd>false</sshd>
|
<sshd>false</sshd>
|
||||||
<looping>false</looping>
|
<looping>false</looping>
|
||||||
<address/>
|
<address></address>
|
||||||
<mask/>
|
<mask></mask>
|
||||||
<gateway/>
|
<gateway></gateway>
|
||||||
<dns1/>
|
<dns1></dns1>
|
||||||
<dns2/>
|
<dns2></dns2>
|
||||||
<vlan/>
|
<vlan></vlan>
|
||||||
</network>
|
</network>
|
||||||
|
|
||||||
<network_security>
|
<network_security>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<fallback>false</fallback>
|
<fallback>false</fallback>
|
||||||
<protocols key="protocol" type="array">
|
<certificate>false</certificate>
|
||||||
|
<protocols type="array" key="protocol">
|
||||||
<protocol selected="true">peap</protocol>
|
<protocol selected="true">peap</protocol>
|
||||||
<protocol>tls</protocol>
|
<protocol>tls</protocol>
|
||||||
</protocols>
|
</protocols>
|
||||||
<certificate>false</certificate>
|
<identity encrypted="false"></identity>
|
||||||
<identity encrypted="false"/>
|
<password encrypted="false"></password>
|
||||||
<password encrypted="false"/>
|
|
||||||
</network_security>
|
</network_security>
|
||||||
|
|
||||||
<proxy>
|
|
||||||
<proxy_address encrypted="false"/>
|
|
||||||
<proxy_port encrypted="false"/>
|
|
||||||
<proxy_username encrypted="false"/>
|
|
||||||
<proxy_password encrypted="false"/>
|
|
||||||
</proxy>
|
|
||||||
|
|
||||||
<mc>
|
|
||||||
<username encrypted="false"/>
|
|
||||||
<password encrypted="false"/>
|
|
||||||
<hostname/>
|
|
||||||
<port/>
|
|
||||||
<timeout>20</timeout>
|
|
||||||
<validate_remote_certificate>true</validate_remote_certificate>
|
|
||||||
</mc>
|
|
||||||
|
|
||||||
<network_share>
|
<network_share>
|
||||||
<username encrypted="false">blancco</username>
|
<username encrypted="false">blancco</username>
|
||||||
<password encrypted="false">blancco</password>
|
<password encrypted="false">blancco</password>
|
||||||
<domain/>
|
<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 type="array" key="protocol">
|
||||||
<protocol selected="true">smb</protocol>
|
<protocol selected="true">smb</protocol>
|
||||||
</protocols>
|
</protocols>
|
||||||
</network_share>
|
</network_share>
|
||||||
|
<proxy>
|
||||||
<workflow default="false" offline="false"/>
|
<proxy_address encrypted="false"></proxy_address>
|
||||||
|
<proxy_port encrypted="false"></proxy_port>
|
||||||
|
<proxy_username encrypted="false"></proxy_username>
|
||||||
|
<proxy_password encrypted="false"></proxy_password>
|
||||||
|
</proxy>
|
||||||
|
<mc>
|
||||||
|
<username encrypted="true">k7vMT3SelitU7wIfPSTGZiZa/mNcMlwpPsxGNaBDc2Y1s7VsOBHXEsopfRupMP+shwmLq/sKDIgGR3v9hGrN5A1gmDvvMw/Z+ClZmGIeqhzomR34yUncQA/6GXFq6jc4CNtwXQwKRAvw+stQpE9RYqNvtgFyfmQufOrz75xQyejLXUDmA7KpS6ycdXQxzFKUoqRJOEGlM6I2efjEKkjgKQrvkj8nuRDHw5g5hYXyapKXJhJn7fbwK+AWN+vKx4cceSWVM0wqvofq5BnYj+7hyD5RHphoUI1NqDYnhj2qj8dt3nGKi6EpaXtGJTnXmXplOxbT/PLtZ2afYkxZWRH96negSweOJ4fYkRcJQONe8wGukufSLh/pQcY3NNwkJv7UNh2tC4ogyEKo+E8+n3beImrUWybl6FlUk2lnyQYJFihnnolepze4w5JmPZ2yTEbRi94OUj+bdNEt4ZqRBK2HuDfCI/jzz7h+ytyA2H2+SZWQGjr/oy/RN15a6sgzS5T7Nys+yAbXAH8HSMQm5KUEtLWSSICUpNbRsBOcC+oQhoJnCzWNafj0w5gY/uTNjBm9F0Yd+kjdcO2kqnBF6aeFaLWX6HkTh1DCsMyIJtOr5wZYUmLLtRwgsruhauSbkkQxJA5iy9QsC2Kv+SSACeTLU4WP99Q5MEQIWb+Tjx/S4z6qISgMhxhu4ioteopClaBSizn2oBd/y6gdp7Fz0FK8JSOmPx+pwT3QrDULoK07O5w=</username>
|
||||||
|
<password encrypted="true">eHTwqUlvjRsTYm5ljXTb8uN4hiG6ceE5OIBO0PA+TSMCwmI9qD6co8tmDHCwRdiDdZH2bssDAW40ZPQNfxTdKbDHZ0hunEcQob0xnrr8668L4OkZlWx5V17k8KXB5QgBUd0v1ALnvp9m2SQbsnP6IljTtIL+cZNnfrpmSrvPoGFe8fuoxwMrRE9PZett0DHuYnsnaaP7lD/hAWy8hONsp6MbegS0YA7vZi56cFSrpFnPwjWbNxYREfO3FY6kxxOZoslGARLKLS9//EA0DmOzN4HcXhKhVsiIIbNS1rUEpZi8PsAjS9EKPBMaD2riKlmQYkcw4gIis8Cdj649TsqG5+VESRtXtGCelaGPKSZJaeh7x3Obh6Me+TYQBN5gx4YBUB8Ao11MJR1i5cZj0vdYLPknIsZ9EaFOtRozIo5S37sd7uhgsLPX9pUKJBiDMy+a/CP07rqlKS7pI4+onhInRapU1hwTaBQuO1DxvjhtPXwoPrz0aadmq3jXREhs1YRiJ0bIpZcTA6c9zWYWBSQQ/kfbBB18BSEBZmKfUq42K/8ZDmGuhS/rVIXKLU14/VL9vMqngMfxZ9rBLzTQobXmkt9dKTO9VKxd90P3j3epTQXQsPZ2hwW1rF7NlRA3il2iL94aRAgOGWGe0P9O18VmKEd9uGKWVjXid6vNKBK5m5JHBMoN5n7eXxo9uKQEn8Ex6CKN7vMVvbms25RcUiHUOg==</password>
|
||||||
|
<hostname>classic.eu-west-1.blancco.cloud</hostname>
|
||||||
|
<port>443</port>
|
||||||
|
<timeout>20</timeout>
|
||||||
|
<validate_remote_certificate>false</validate_remote_certificate>
|
||||||
|
</mc>
|
||||||
|
<workflow offline="false" default="true"></workflow>
|
||||||
<erasure>
|
<erasure>
|
||||||
|
<drive_self_test type="short">false</drive_self_test>
|
||||||
<max_simultaneous_erasures>50</max_simultaneous_erasures>
|
<max_simultaneous_erasures>50</max_simultaneous_erasures>
|
||||||
<remove_hidden_areas>false</remove_hidden_areas>
|
<remove_hidden_areas>true</remove_hidden_areas>
|
||||||
<preserve_recovery_partition>false</preserve_recovery_partition>
|
<preserve_recovery_partition>false</preserve_recovery_partition>
|
||||||
<store_erasure_progress>false</store_erasure_progress>
|
<store_erasure_progress>false</store_erasure_progress>
|
||||||
<remapped mandatory="false" threshold="0">false</remapped>
|
<remapped mandatory="false" threshold="0">true</remapped>
|
||||||
<action_if_not_possible>interrupt</action_if_not_possible>
|
<action_if_not_possible>interrupt</action_if_not_possible>
|
||||||
<fail_on_remapped_sectors>false</fail_on_remapped_sectors>
|
<fail_on_remapped_sectors>false</fail_on_remapped_sectors>
|
||||||
<fail_on_erasure_errors threshold="5">true</fail_on_erasure_errors>
|
<fail_on_erasure_errors threshold="5">true</fail_on_erasure_errors>
|
||||||
<fail_on_read_errors threshold="5">true</fail_on_read_errors>
|
<fail_on_read_errors threshold="5">true</fail_on_read_errors>
|
||||||
<verification>10</verification>
|
<verification>10</verification>
|
||||||
<enforced_rules key="rule" type="array">
|
<enforced_rules type="array" key="rule">
|
||||||
<rule enabled="false" name="enforced_standard" standard_id="bl_ssd" target="ssd"/>
|
<rule enabled="false" name="enforced_standard" standard_id="bl_ssd" target="ssd"/>
|
||||||
<rule enabled="true" name="fallback_nist_standard" standard_id="nist_purge" fallback_id="nist_clear"/>
|
<rule enabled="true" name="fallback_nist_standard" standard_id="nist_purge" fallback_id="nist_clear"/>
|
||||||
</enforced_rules>
|
</enforced_rules>
|
||||||
<drive_self_test type="short">false</drive_self_test>
|
|
||||||
<fail_on_failed_self_test>false</fail_on_failed_self_test>
|
<fail_on_failed_self_test>false</fail_on_failed_self_test>
|
||||||
<fail_on_low_erasure_speed threshold="1">false</fail_on_low_erasure_speed>
|
<fail_on_low_erasure_speed threshold="1">false</fail_on_low_erasure_speed>
|
||||||
<fail_on_timeout threshold="1">false</fail_on_timeout>
|
<fail_on_timeout threshold="1">false</fail_on_timeout>
|
||||||
@@ -219,36 +202,29 @@
|
|||||||
<allow_tcg_reset_write_pointers>true</allow_tcg_reset_write_pointers>
|
<allow_tcg_reset_write_pointers>true</allow_tcg_reset_write_pointers>
|
||||||
<allow_tcg_unmap>true</allow_tcg_unmap>
|
<allow_tcg_unmap>true</allow_tcg_unmap>
|
||||||
</erasure>
|
</erasure>
|
||||||
|
|
||||||
<fingerprint>
|
<fingerprint>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<location>67000</location>
|
<location>67000</location>
|
||||||
</fingerprint>
|
</fingerprint>
|
||||||
|
|
||||||
<bootable_report>
|
<bootable_report>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</bootable_report>
|
</bootable_report>
|
||||||
|
|
||||||
<format_disk>
|
<format_disk>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<type>ntfs</type>
|
<type>ntfs</type>
|
||||||
</format_disk>
|
</format_disk>
|
||||||
|
|
||||||
<spin_down_idle_disk>
|
<spin_down_idle_disk>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</spin_down_idle_disk>
|
</spin_down_idle_disk>
|
||||||
|
|
||||||
<raid>
|
<raid>
|
||||||
<show_logical_disks>false</show_logical_disks>
|
<show_logical_disks>true</show_logical_disks>
|
||||||
<enable_passthrough>true</enable_passthrough>
|
<enable_passthrough>true</enable_passthrough>
|
||||||
<kill_logical_disks>true</kill_logical_disks>
|
<kill_logical_disks>false</kill_logical_disks>
|
||||||
</raid>
|
</raid>
|
||||||
|
|
||||||
<hotplug>
|
<hotplug>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<timeout>30</timeout>
|
<timeout>60</timeout>
|
||||||
</hotplug>
|
</hotplug>
|
||||||
|
|
||||||
<external_device_service>
|
<external_device_service>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<port>80</port>
|
<port>80</port>
|
||||||
@@ -264,28 +240,24 @@
|
|||||||
<test enabled="true">webcam</test>
|
<test enabled="true">webcam</test>
|
||||||
</hardware_tests>
|
</hardware_tests>
|
||||||
</external_device_service>
|
</external_device_service>
|
||||||
|
|
||||||
<license>
|
<license>
|
||||||
<provider>bios</provider>
|
<provider>clurichaun</provider>
|
||||||
<provider_report>bios</provider_report>
|
<provider_report>clurichaun</provider_report>
|
||||||
<edition>Enterprise Volume Edition</edition>
|
<edition>Enterprise Volume Edition</edition>
|
||||||
|
<offline></offline>
|
||||||
<mode>Drive Eraser</mode>
|
<mode>Drive Eraser</mode>
|
||||||
<offline/>
|
|
||||||
</license>
|
</license>
|
||||||
|
|
||||||
<image>
|
<image>
|
||||||
<description/>
|
<description>BMP Internet - June 2026</description>
|
||||||
<creation_date>2025-03-10</creation_date>
|
<creation_date>2026-06-22</creation_date>
|
||||||
</image>
|
</image>
|
||||||
|
|
||||||
<customer_data>
|
<customer_data>
|
||||||
<business_location/>
|
<business_location></business_location>
|
||||||
<customer_license>General Electric Company</customer_license>
|
<customer_license>GE Aerospace</customer_license>
|
||||||
<erasure_person/>
|
<erasure_person></erasure_person>
|
||||||
<erasure_provider/>
|
<erasure_provider></erasure_provider>
|
||||||
<business_name/>
|
<business_name>Non_GEAE_Wired</business_name>
|
||||||
</customer_data>
|
</customer_data>
|
||||||
|
|
||||||
<settings>
|
<settings>
|
||||||
<gui>
|
<gui>
|
||||||
<erasure_console>local</erasure_console>
|
<erasure_console>local</erasure_console>
|
||||||
@@ -295,11 +267,11 @@
|
|||||||
<ssd_erasure_exception>true</ssd_erasure_exception>
|
<ssd_erasure_exception>true</ssd_erasure_exception>
|
||||||
<report_view locked="true">standard</report_view>
|
<report_view locked="true">standard</report_view>
|
||||||
<erasure_settings locked="false"/>
|
<erasure_settings locked="false"/>
|
||||||
<show_customer>false</show_customer>
|
<show_customer>true</show_customer>
|
||||||
<show_operator>false</show_operator>
|
<show_operator>false</show_operator>
|
||||||
<show_partitions>false</show_partitions>
|
<show_partitions>false</show_partitions>
|
||||||
<screensaver locked="false">
|
<screensaver locked="false">
|
||||||
<timeout>30</timeout>
|
<timeout>86400</timeout>
|
||||||
<show_exceptions>false</show_exceptions>
|
<show_exceptions>false</show_exceptions>
|
||||||
</screensaver>
|
</screensaver>
|
||||||
<hexviewer>
|
<hexviewer>
|
||||||
@@ -309,7 +281,7 @@
|
|||||||
<drive_selection_percentage>5</drive_selection_percentage>
|
<drive_selection_percentage>5</drive_selection_percentage>
|
||||||
<reconfigure_controller_mode>
|
<reconfigure_controller_mode>
|
||||||
<timeout enabled="false">30</timeout>
|
<timeout enabled="false">30</timeout>
|
||||||
<default_focus_on_proceed>true</default_focus_on_proceed>
|
<default_focus_on_proceed>false</default_focus_on_proceed>
|
||||||
</reconfigure_controller_mode>
|
</reconfigure_controller_mode>
|
||||||
<disable_sid_block_authentication>
|
<disable_sid_block_authentication>
|
||||||
<timeout enabled="true">0</timeout>
|
<timeout enabled="true">0</timeout>
|
||||||
@@ -323,8 +295,8 @@
|
|||||||
<accessibility>
|
<accessibility>
|
||||||
<available>false</available>
|
<available>false</available>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
<speaking_rate>80</speaking_rate>
|
|
||||||
<key_echo>true</key_echo>
|
<key_echo>true</key_echo>
|
||||||
|
<speaking_rate>80</speaking_rate>
|
||||||
</accessibility>
|
</accessibility>
|
||||||
<sound_level>70</sound_level>
|
<sound_level>70</sound_level>
|
||||||
<input_method>
|
<input_method>
|
||||||
@@ -334,82 +306,55 @@
|
|||||||
</input_method>
|
</input_method>
|
||||||
</gui>
|
</gui>
|
||||||
</settings>
|
</settings>
|
||||||
|
<custom_fields type="array" key="custom_field"/>
|
||||||
|
<exclude_verification_ranges type="array" key="exclude_verification_range"/>
|
||||||
<report>
|
<report>
|
||||||
<per_drive>false</per_drive>
|
<per_drive>false</per_drive>
|
||||||
<auto_backup>true</auto_backup>
|
<auto_backup>true</auto_backup>
|
||||||
<extensions type="array" key="extension">
|
<extensions type="array" key="extension">
|
||||||
<extension selected="true">xml</extension>
|
<extension>xml</extension>
|
||||||
|
<extension selected="true">pdf+xml</extension>
|
||||||
</extensions>
|
</extensions>
|
||||||
<utc_offset>0</utc_offset>
|
<utc_offset>0</utc_offset>
|
||||||
<network_info>true</network_info>
|
<network_info>true</network_info>
|
||||||
<custom_signature>
|
<custom_signature>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<label/>
|
<label></label>
|
||||||
</custom_signature>
|
</custom_signature>
|
||||||
<drive_life_estimation>false</drive_life_estimation>
|
<drive_life_estimation>false</drive_life_estimation>
|
||||||
</report>
|
</report>
|
||||||
|
|
||||||
<custom_fields type="array" key="custom_field">
|
|
||||||
<custom_field type="input" mandatory="true" locked="false" per_drive="false" show_in_editor="false" pattern="" hint="">
|
|
||||||
<name>custom_field_1</name>
|
|
||||||
<title>GERITM Number</title>
|
|
||||||
<value/>
|
|
||||||
</custom_field>
|
|
||||||
<custom_field type="input" mandatory="true" locked="false" per_drive="false" show_in_editor="false" pattern="" hint="">
|
|
||||||
<name>custom_field_2</name>
|
|
||||||
<title>Device Name</title>
|
|
||||||
<value/>
|
|
||||||
</custom_field>
|
|
||||||
<custom_field type="input" mandatory="true" locked="false" per_drive="false" show_in_editor="false" pattern="" hint="">
|
|
||||||
<name>custom_field_3</name>
|
|
||||||
<title>Device Serial Number</title>
|
|
||||||
<value/>
|
|
||||||
</custom_field>
|
|
||||||
<custom_field type="input" mandatory="false" locked="true" per_drive="false" show_in_editor="false" pattern="" hint="">
|
|
||||||
<name>custom_field_4</name>
|
|
||||||
<title>Version</title>
|
|
||||||
<value>EVE_20250310</value>
|
|
||||||
</custom_field>
|
|
||||||
</custom_fields>
|
|
||||||
|
|
||||||
<exclude_verification_ranges type="array" key="exclude_verification_range"/>
|
|
||||||
|
|
||||||
<wireless>
|
<wireless>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
<ssid encrypted="false">INTERNETACCESS</ssid>
|
<ssid encrypted="false"></ssid>
|
||||||
<password encrypted="false">Acc3ssGr@nted22</password>
|
<password encrypted="false"></password>
|
||||||
<key_mgmt>WPA-PSK</key_mgmt>
|
<key_mgmt>WPA-PSK</key_mgmt>
|
||||||
<scan_ssid>0</scan_ssid>
|
<scan_ssid>0</scan_ssid>
|
||||||
</wireless>
|
</wireless>
|
||||||
|
|
||||||
<cd_eject>
|
<cd_eject>
|
||||||
<after_boot>true</after_boot>
|
<after_boot>false</after_boot>
|
||||||
<after_erasure>false</after_erasure>
|
<after_erasure>false</after_erasure>
|
||||||
<after_report>false</after_report>
|
<after_report>false</after_report>
|
||||||
<at_shutdown>false</at_shutdown>
|
<at_shutdown>false</at_shutdown>
|
||||||
</cd_eject>
|
</cd_eject>
|
||||||
|
|
||||||
<system_shutdown>
|
<system_shutdown>
|
||||||
<after_erasure>false</after_erasure>
|
<after_erasure>false</after_erasure>
|
||||||
<after_successful_erasure>false</after_successful_erasure>
|
<after_successful_erasure>false</after_successful_erasure>
|
||||||
</system_shutdown>
|
</system_shutdown>
|
||||||
|
|
||||||
<system_restart>
|
<system_restart>
|
||||||
<after_erasure>false</after_erasure>
|
<after_erasure>false</after_erasure>
|
||||||
<after_successful_erasure>false</after_successful_erasure>
|
<after_successful_erasure>false</after_successful_erasure>
|
||||||
</system_restart>
|
</system_restart>
|
||||||
|
<device_enrollment_detection>
|
||||||
|
<persistent_software>false</persistent_software>
|
||||||
|
</device_enrollment_detection>
|
||||||
<vnc_remote>
|
<vnc_remote>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<mode>viewer</mode>
|
<mode>viewer</mode>
|
||||||
<repeater_hostname/>
|
<repeater_hostname></repeater_hostname>
|
||||||
<port>5900</port>
|
<port>5900</port>
|
||||||
<password encrypted="false"/>
|
<password encrypted="false"></password>
|
||||||
</vnc_remote>
|
</vnc_remote>
|
||||||
|
<nvme_over_fabrics enabled="false" type="array" key="connection"/>
|
||||||
<nvme_over_fabrics type="array" key="connection" enabled="false"/>
|
|
||||||
|
|
||||||
<update>
|
<update>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
<intervals type="array" key="interval">
|
<intervals type="array" key="interval">
|
||||||
@@ -420,6 +365,6 @@
|
|||||||
</intervals>
|
</intervals>
|
||||||
<url>https://cdn.blancco.com/products/drive_eraser/download/ota/drive_eraser.json</url>
|
<url>https://cdn.blancco.com/products/drive_eraser/download/ota/drive_eraser.json</url>
|
||||||
</update>
|
</update>
|
||||||
|
<adapters type="array" key="adapter"/>
|
||||||
</configuration>
|
</configuration>
|
||||||
</root>
|
</root>
|
||||||
<!-- JFLKHGDJLKFHGJCXLKGJFLKDJKLHFELIUFDSALDSAHGLSAVSHGSOFHDSAF -->
|
|
||||||
|
|||||||
285
playbook/blancco-progress.py
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Report Blancco erase progress to the PXE webapp's /imaging dashboard.
|
||||||
|
|
||||||
|
Runs inside the Blancco rootfs (installed by blancco-init.sh as a systemd
|
||||||
|
service). Follows the AlbusSeven journal and maps its log lines onto the
|
||||||
|
same /imaging/status payload the WinPE imaging clients post, so each
|
||||||
|
wiping PC gets a tile keyed by BIOS serial with pctype "blancco".
|
||||||
|
|
||||||
|
Stages (stage_total 7): 1 booting and 2 starting are posted by
|
||||||
|
blancco-init.sh; this script posts 3 waiting for licence, 4 erasing,
|
||||||
|
5 report saved, 6 report sent / on share, 7 done. A failed erasure step
|
||||||
|
marks the session failed.
|
||||||
|
|
||||||
|
The final state comes from the report itself, not the journal: outside
|
||||||
|
Blancco's debug mode Albus does not log "erasureFinishedForEntity",
|
||||||
|
"Report saving success" or "Auto process reached the final step", so a
|
||||||
|
watcher on /reports (where auto mode always saves the report) reads each
|
||||||
|
new report and posts 7 "Wipe complete" or "Erasure FAILED".
|
||||||
|
|
||||||
|
Usage: blancco-progress.py [--replay FILE] [--dry-run]
|
||||||
|
"""
|
||||||
|
import glob
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
STATUS_URL = "http://172.16.9.1:9009/imaging/status"
|
||||||
|
CLOUD_URL = "http://172.16.9.1:9009/reports/cloud-status"
|
||||||
|
STAGE_TOTAL = 7
|
||||||
|
# Progress lines arrive in bursts; post at most this often unless the
|
||||||
|
# stage itself changes.
|
||||||
|
MIN_POST_INTERVAL = 3.0
|
||||||
|
|
||||||
|
|
||||||
|
def read(path):
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def pxe_iface():
|
||||||
|
"""(mac, ip) of the interface on the PXE LAN, or blanks."""
|
||||||
|
try:
|
||||||
|
out = subprocess.run(["ip", "-o", "-4", "addr"], capture_output=True,
|
||||||
|
text=True, timeout=5).stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return "", ""
|
||||||
|
for line in out.splitlines():
|
||||||
|
m = re.search(r"^\d+:\s+(\S+)\s+inet (172\.16\.9\.\d+)/", line)
|
||||||
|
if m:
|
||||||
|
return read(f"/sys/class/net/{m.group(1)}/address"), m.group(2)
|
||||||
|
return "", ""
|
||||||
|
|
||||||
|
|
||||||
|
REPORTS_DIR = "/reports"
|
||||||
|
|
||||||
|
|
||||||
|
def report_outcome(path):
|
||||||
|
"""(wiped: bool, failed_drives: list[str]) from a Blancco report XML.
|
||||||
|
|
||||||
|
Per drive: wiped if any of its erasures succeeded, so a failed NIST
|
||||||
|
Purge followed by a successful Clear fallback still counts as wiped.
|
||||||
|
None if the report has no erasures (hardware-only report).
|
||||||
|
"""
|
||||||
|
root = ET.parse(path).getroot()
|
||||||
|
drives = {}
|
||||||
|
for i, er in enumerate(root.iter("entries")):
|
||||||
|
if er.get("name") != "erasure":
|
||||||
|
continue
|
||||||
|
state, serial, model = "", "", ""
|
||||||
|
for e in er:
|
||||||
|
if e.tag == "entry" and e.get("name") == "state":
|
||||||
|
state = (e.text or "").strip()
|
||||||
|
if e.tag == "entries" and e.get("name") == "target":
|
||||||
|
for t in e:
|
||||||
|
if t.get("name") == "serial":
|
||||||
|
serial = (t.text or "").strip()
|
||||||
|
elif t.get("name") == "model":
|
||||||
|
model = (t.text or "").strip()
|
||||||
|
key = serial or f"#{i}"
|
||||||
|
ok = state.lower() == "successful"
|
||||||
|
prev = drives.get(key, (False, ""))
|
||||||
|
drives[key] = (prev[0] or ok, f"{model} {serial}".strip())
|
||||||
|
if not drives:
|
||||||
|
return None
|
||||||
|
failed = [label for ok, label in drives.values() if not ok]
|
||||||
|
return (not failed, failed)
|
||||||
|
|
||||||
|
|
||||||
|
SERIAL = read("/sys/class/dmi/id/product_serial") or read("/etc/machine-id")
|
||||||
|
MODEL = " ".join(x for x in (read("/sys/class/dmi/id/sys_vendor"),
|
||||||
|
read("/sys/class/dmi/id/product_name")) if x)
|
||||||
|
|
||||||
|
# (pattern, stage_index, stage text or callable(match) -> text, status)
|
||||||
|
RULES = [
|
||||||
|
(re.compile(r"Waiting for MC connectivity"), 3,
|
||||||
|
"Waiting for licence (cloud)", None),
|
||||||
|
(re.compile(r"ClurichaunLicenseProvider: ([1-9]\d*) licenses available for product id 1\b"), 3,
|
||||||
|
lambda m: f"Licence OK ({m.group(1)} left)", None),
|
||||||
|
(re.compile(r"startErasure: success"), 4, "Erasing", None),
|
||||||
|
(re.compile(r"About to start Step (\d+) \((.*?)\)\.?$"), 4,
|
||||||
|
lambda m: f"Erasing: step {m.group(1)} - {m.group(2).split(' or ')[0][:60]}", None),
|
||||||
|
(re.compile(r"Step \d+ \(.*\) failed|Erasure step .* failed\b|erasure failed", re.I), 4,
|
||||||
|
"Erasure FAILED", "failed"),
|
||||||
|
(re.compile(r"erasureFinishedForEntity"), 5, "Erasure finished", None),
|
||||||
|
(re.compile(r"\[report\.manager\].*Report saving success"), 5, "Report saved", None),
|
||||||
|
(re.compile(r"Report sent to MC"), 6, "Report sent to cloud", None),
|
||||||
|
(re.compile(r"Auto process reached the final step"), 7, "Done", "succeeded"),
|
||||||
|
]
|
||||||
|
# Cloud upload of a report: "[comm.manager] Sending report document id: <id>"
|
||||||
|
# then "Report sent to MC" (or an upload failure).
|
||||||
|
SENDING = re.compile(r"Sending report document id: ([0-9a-fA-F-]{36})")
|
||||||
|
SENT = re.compile(r"Report sent to MC")
|
||||||
|
SEND_FAILED = re.compile(r"(report (upload|send|sending)[^\n]*fail|failed to (send|upload) report)", re.I)
|
||||||
|
# Drive progress, e.g. "FORMAT NVM progress: 75%". Excludes the self-test
|
||||||
|
# completion and volume lines that also carry a percentage.
|
||||||
|
PROGRESS = re.compile(r"\bprogress[^%\d]{0,20}(\d{1,3})%", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
class Reporter:
|
||||||
|
def __init__(self, dry_run=False):
|
||||||
|
self.dry_run = dry_run
|
||||||
|
self.mac, self.ip = pxe_iface()
|
||||||
|
self.stage_index = 2
|
||||||
|
self.stage = "Blancco started"
|
||||||
|
self.status = "in_progress"
|
||||||
|
self.last_post = 0.0
|
||||||
|
self.pending = []
|
||||||
|
|
||||||
|
def post(self, force=False):
|
||||||
|
now = time.monotonic()
|
||||||
|
if not force and now - self.last_post < MIN_POST_INTERVAL:
|
||||||
|
return
|
||||||
|
payload = {
|
||||||
|
"serial": SERIAL,
|
||||||
|
"mac": self.mac,
|
||||||
|
"ip": self.ip,
|
||||||
|
"pctype": "blancco",
|
||||||
|
"hostname_target": MODEL,
|
||||||
|
"current_stage": self.stage,
|
||||||
|
"stage_index": self.stage_index,
|
||||||
|
"stage_total": STAGE_TOTAL,
|
||||||
|
"status": self.status,
|
||||||
|
"source": "blancco",
|
||||||
|
}
|
||||||
|
if self.pending:
|
||||||
|
payload["log_lines"] = self.pending[-20:]
|
||||||
|
if self.dry_run:
|
||||||
|
print(json.dumps(payload))
|
||||||
|
else:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
STATUS_URL, data=json.dumps(payload).encode(),
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(req, timeout=5).read()
|
||||||
|
except OSError:
|
||||||
|
return # webapp unreachable: keep the lines for the next post
|
||||||
|
self.pending = []
|
||||||
|
self.last_post = now
|
||||||
|
|
||||||
|
def cloud(self, document_id, sent, detail):
|
||||||
|
"""Tell the webapp whether a report reached the Blancco cloud."""
|
||||||
|
payload = {"document_id": document_id, "serial": SERIAL,
|
||||||
|
"sent": sent, "detail": detail[:200]}
|
||||||
|
if self.dry_run:
|
||||||
|
print(json.dumps({"cloud": payload}))
|
||||||
|
return
|
||||||
|
req = urllib.request.Request(
|
||||||
|
CLOUD_URL, data=json.dumps(payload).encode(),
|
||||||
|
headers={"Content-Type": "application/json"})
|
||||||
|
try:
|
||||||
|
urllib.request.urlopen(req, timeout=5).read()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def line(self, text):
|
||||||
|
text = text.strip()
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
m = SENDING.search(text)
|
||||||
|
if m:
|
||||||
|
self.sending = m.group(1)
|
||||||
|
elif getattr(self, "sending", None):
|
||||||
|
if SENT.search(text):
|
||||||
|
self.cloud(self.sending, True, text)
|
||||||
|
self.sending = None
|
||||||
|
elif SEND_FAILED.search(text):
|
||||||
|
self.cloud(self.sending, False, text)
|
||||||
|
self.sending = None
|
||||||
|
for pat, idx, stage, status in RULES:
|
||||||
|
m = pat.search(text)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
if self.status == "failed" and status != "failed":
|
||||||
|
return # a failure sticks for the rest of the run
|
||||||
|
if idx < self.stage_index and status != "failed":
|
||||||
|
return # late licence chatter must not rewind the tile
|
||||||
|
new_stage = stage(m) if callable(stage) else stage
|
||||||
|
changed = (idx, new_stage) != (self.stage_index, self.stage)
|
||||||
|
self.stage_index = max(self.stage_index, idx) if status != "failed" else idx
|
||||||
|
self.stage = new_stage
|
||||||
|
if status:
|
||||||
|
self.status = status
|
||||||
|
self.pending.append(text[:200])
|
||||||
|
if changed:
|
||||||
|
self.post(force=True)
|
||||||
|
return
|
||||||
|
m = PROGRESS.search(text)
|
||||||
|
if m and self.stage_index == 4 and "Self-Test" not in text:
|
||||||
|
base = self.stage.split(" [")[0]
|
||||||
|
self.stage = f"{base} [{m.group(1)}%]"
|
||||||
|
self.post()
|
||||||
|
|
||||||
|
|
||||||
|
def finish(self, path):
|
||||||
|
"""Final stage from a finished report on disk."""
|
||||||
|
try:
|
||||||
|
outcome = report_outcome(path)
|
||||||
|
except (OSError, ET.ParseError):
|
||||||
|
return False
|
||||||
|
if outcome is None:
|
||||||
|
return True # nothing erased in this report; keep watching
|
||||||
|
wiped, failed = outcome
|
||||||
|
name = os.path.basename(path)
|
||||||
|
self.stage_index = 7
|
||||||
|
if wiped:
|
||||||
|
self.stage, self.status = "Wipe complete", "succeeded"
|
||||||
|
else:
|
||||||
|
self.stage, self.status = "Erasure FAILED: " + ", ".join(failed)[:120], "failed"
|
||||||
|
self.pending.append(f"Report {name}: {'all drives wiped' if wiped else 'failed: ' + ', '.join(failed)}")
|
||||||
|
self.post(force=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def watch_reports(rep, interval=5, stable=5):
|
||||||
|
"""Post the final stage for each new report Albus saves in /reports."""
|
||||||
|
seen = set(glob.glob(os.path.join(REPORTS_DIR, "*.xml")))
|
||||||
|
while True:
|
||||||
|
time.sleep(interval)
|
||||||
|
for path in sorted(glob.glob(os.path.join(REPORTS_DIR, "*.xml"))):
|
||||||
|
if path in seen:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
st = os.stat(path)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
if st.st_size == 0 or time.time() - st.st_mtime < stable:
|
||||||
|
continue # Albus may still be writing it
|
||||||
|
if rep.finish(path):
|
||||||
|
seen.add(path)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
dry_run = "--dry-run" in sys.argv
|
||||||
|
rep = Reporter(dry_run=dry_run)
|
||||||
|
if "--outcome" in sys.argv:
|
||||||
|
print(report_outcome(sys.argv[sys.argv.index("--outcome") + 1]))
|
||||||
|
return
|
||||||
|
if "--replay" in sys.argv:
|
||||||
|
with open(sys.argv[sys.argv.index("--replay") + 1]) as f:
|
||||||
|
for text in f:
|
||||||
|
rep.line(text)
|
||||||
|
rep.post(force=True)
|
||||||
|
return
|
||||||
|
rep.post(force=True)
|
||||||
|
threading.Thread(target=watch_reports, args=(rep,), daemon=True).start()
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
["journalctl", "-f", "-b", "-o", "cat", "-t", "AlbusSeven", "-n", "all"],
|
||||||
|
stdout=subprocess.PIPE, text=True, bufsize=1)
|
||||||
|
for text in proc.stdout:
|
||||||
|
rep.line(text)
|
||||||
|
if rep.status in ("succeeded", "failed") and rep.stage_index == STAGE_TOTAL:
|
||||||
|
rep.post(force=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
28
playbook/certs/zscaler-root-ca.crt
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE0zCCA7ugAwIBAgIJANu+mC2Jt3uTMA0GCSqGSIb3DQEBCwUAMIGhMQswCQYD
|
||||||
|
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2Ux
|
||||||
|
FTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMMWnNjYWxlciBJbmMuMRgw
|
||||||
|
FgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRA
|
||||||
|
enNjYWxlci5jb20wHhcNMTQxMjE5MDAyNzU1WhcNNDIwNTA2MDAyNzU1WjCBoTEL
|
||||||
|
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBK
|
||||||
|
b3NlMRUwEwYDVQQKEwxac2NhbGVyIEluYy4xFTATBgNVBAsTDFpzY2FsZXIgSW5j
|
||||||
|
LjEYMBYGA1UEAxMPWnNjYWxlciBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNzdXBw
|
||||||
|
b3J0QHpzY2FsZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
||||||
|
qT7STSxZRTgEFFf6doHajSc1vk5jmzmM6BWuOo044EsaTc9eVEV/HjH/1DWzZtcr
|
||||||
|
fTj+ni205apMTlKBW3UYR+lyLHQ9FoZiDXYXK8poKSV5+Tm0Vls/5Kb8mkhVVqv7
|
||||||
|
LgYEmvEY7HPY+i1nEGZCa46ZXCOohJ0mBEtB9JVlpDIO+nN0hUMAYYdZ1KZWCMNf
|
||||||
|
5J/aTZiShsorN2A38iSOhdd+mcRM4iNL3gsLu99XhKnRqKoHeH83lVdfu1XBeoQz
|
||||||
|
z5V6gA3kbRvhDwoIlTBeMa5l4yRdJAfdpkbFzqiwSgNdhbxTHnYYorDzKfr2rEFM
|
||||||
|
dsMU0DHdeAZf711+1CunuQIDAQABo4IBCjCCAQYwHQYDVR0OBBYEFLm33UrNww4M
|
||||||
|
hp1d3+wcBGnFTpjfMIHWBgNVHSMEgc4wgcuAFLm33UrNww4Mhp1d3+wcBGnFTpjf
|
||||||
|
oYGnpIGkMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8G
|
||||||
|
A1UEBxMIU2FuIEpvc2UxFTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMM
|
||||||
|
WnNjYWxlciBJbmMuMRgwFgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG
|
||||||
|
9w0BCQEWE3N1cHBvcnRAenNjYWxlci5jb22CCQDbvpgtibd7kzAMBgNVHRMEBTAD
|
||||||
|
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw0NdJh8w3NsJu4KHuVZUrmZgIohnTm0j+
|
||||||
|
RTmYQ9IKA/pvxAcA6K1i/LO+Bt+tCX+C0yxqB8qzuo+4vAzoY5JEBhyhBhf1uK+P
|
||||||
|
/WVWFZN/+hTgpSbZgzUEnWQG2gOVd24msex+0Sr7hyr9vn6OueH+jj+vCMiAm5+u
|
||||||
|
kd7lLvJsBu3AO3jGWVLyPkS3i6Gf+rwAp1OsRrv3WnbkYcFf9xjuaf4z0hRCrLN2
|
||||||
|
xFNjavxrHmsH8jPHVvgc1VD0Opja0l/BRVauTrUaoW6tE+wFG5rEcPGS80jjHK4S
|
||||||
|
pB5iDj2mUZH1T8lzYtuZy0ZPirxmtsk3135+CKNa2OCAhhFjE0xd
|
||||||
|
-----END CERTIFICATE-----
|
||||||
473
playbook/clonezilla-scripts/pxe-cz.sh
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# PXE Clonezilla: back up or restore a PC by its machine number.
|
||||||
|
#
|
||||||
|
# The iPXE menu (GetPxeScript.aspx, "Clonezilla: back up / restore this
|
||||||
|
# PC") boots Clonezilla live with:
|
||||||
|
# ocs_prerun="wget ... -O /tmp/pxe-cz.sh http://<server>/clonezilla-scripts/pxe-cz.sh"
|
||||||
|
# ocs_repository="smb3://clonezilla:<password>@<server>/clonezilla/"
|
||||||
|
# ocs_live_run="bash /tmp/pxe-cz.sh"
|
||||||
|
# pxe_cz_mode=save|restore pxe_machine=<typed text> pxe_end=1
|
||||||
|
# Clonezilla runs the prerun (this download), mounts the repository on
|
||||||
|
# /home/partimag, then runs this script as root on the console.
|
||||||
|
#
|
||||||
|
# Images are named <machine>-<YYYY-MM-DD-HHMM> (server time, site zone),
|
||||||
|
# with pxe-meta.txt (machine, serial, model, disk, date, Clonezilla
|
||||||
|
# version, 32/64-bit, check result) next to Clonezilla's own files. The
|
||||||
|
# PXE webapp (Tools > Clonezilla backups) reads both.
|
||||||
|
#
|
||||||
|
# Runs under Clonezilla live's bash (3.3.3 amd64 and 3.2.0-5 i686, both
|
||||||
|
# Debian with coreutils, util-linux, dmidecode, wget). Plain ASCII only.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# PXE_CZ_* overrides exist only for testing this script off Clonezilla.
|
||||||
|
OCSROOT="${PXE_CZ_OCSROOT:-/home/partimag}"
|
||||||
|
CMDLINE_FILE="${PXE_CZ_CMDLINE:-/proc/cmdline}"
|
||||||
|
SYS_BLOCK="${PXE_CZ_SYS_BLOCK:-/sys/block}"
|
||||||
|
SITE_TZ="America/New_York"
|
||||||
|
SERVER="$(sed -n 's/.*ocs_prerun="[^"]*http:\/\/\([^/":]*\).*/\1/p' "$CMDLINE_FILE" 2>/dev/null)"
|
||||||
|
[ -n "$SERVER" ] || SERVER=172.16.9.1
|
||||||
|
CMDLINE=" $(cat "$CMDLINE_FILE" 2>/dev/null) "
|
||||||
|
|
||||||
|
# --- output helpers -------------------------------------------------------
|
||||||
|
|
||||||
|
line() { echo "======================================================================"; }
|
||||||
|
|
||||||
|
banner() {
|
||||||
|
echo
|
||||||
|
line
|
||||||
|
for l in "$@"; do echo " $l"; done
|
||||||
|
line
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ask what to do at the end. Success: power off after 60 s unless a key is
|
||||||
|
# pressed. Failure: wait, the operator has to read the message.
|
||||||
|
finish() {
|
||||||
|
local ok="$1" ans
|
||||||
|
sync
|
||||||
|
while true; do
|
||||||
|
echo
|
||||||
|
echo " [Enter] power off r = reboot s = shell (type exit to come back)"
|
||||||
|
if [ "$ok" = "yes" ]; then
|
||||||
|
echo -n " Powering off in 60 seconds... "
|
||||||
|
if ! read -r -t 60 ans; then ans=""; echo; fi
|
||||||
|
else
|
||||||
|
echo -n " Choice: "
|
||||||
|
read -r ans || ans=""
|
||||||
|
fi
|
||||||
|
case "$ans" in
|
||||||
|
r|R) umount "$OCSROOT" 2>/dev/null; sync; reboot; sleep 30 ;;
|
||||||
|
s|S) bash -i ;;
|
||||||
|
*) umount "$OCSROOT" 2>/dev/null; sync; poweroff; sleep 30 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
banner "FAILED: $1" "${@:2}"
|
||||||
|
finish no
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- machine number -------------------------------------------------------
|
||||||
|
|
||||||
|
# Letters, digits and dash, starting with a letter or digit, up to 32.
|
||||||
|
valid_machine() {
|
||||||
|
[[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,31}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# The text typed at the iPXE prompt sits between pxe_machine= and the
|
||||||
|
# pxe_end=1 sentinel, so a typed space shows up here and is rejected
|
||||||
|
# instead of being split into another kernel parameter.
|
||||||
|
machine_from_cmdline() {
|
||||||
|
local rest
|
||||||
|
case "$CMDLINE" in
|
||||||
|
*" pxe_machine="*" pxe_end=1 "*) ;;
|
||||||
|
*) echo ""; return ;;
|
||||||
|
esac
|
||||||
|
rest="${CMDLINE#* pxe_machine=}"
|
||||||
|
echo "${rest%% pxe_end=1 *}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sets MACHINE (upper case). Not run in $(...): finish() reads the
|
||||||
|
# keyboard and must write to the console.
|
||||||
|
get_machine() {
|
||||||
|
local m
|
||||||
|
m="$(machine_from_cmdline)"
|
||||||
|
if [ -z "$m" ] || ! valid_machine "$m"; then
|
||||||
|
if [ -n "$m" ]; then
|
||||||
|
banner "The machine number typed at the boot menu is not valid:" \
|
||||||
|
" [$m]" "Use letters, digits and dash only (for example 6501 or WJ-6501)."
|
||||||
|
else
|
||||||
|
banner "No machine number was given at the boot menu."
|
||||||
|
fi
|
||||||
|
while true; do
|
||||||
|
echo -n " Machine number (letters, digits, dash; empty = stop): "
|
||||||
|
read -r m || m=""
|
||||||
|
[ -n "$m" ] || finish no
|
||||||
|
valid_machine "$m" && break
|
||||||
|
echo " [$m] is not a valid machine number: use letters, digits and dash only."
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
MACHINE="$(echo "$m" | tr '[:lower:]' '[:upper:]')"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- time -----------------------------------------------------------------
|
||||||
|
|
||||||
|
# The PC's clock is often wrong (dead CMOS battery on old PCs, local time
|
||||||
|
# on Windows PCs), so take the time from the PXE server's HTTP Date header
|
||||||
|
# and set the live system clock from it. Falls back to the PC clock.
|
||||||
|
TIME_SOURCE="pc-clock"
|
||||||
|
sync_clock() {
|
||||||
|
local hdr epoch
|
||||||
|
hdr="$(wget -q -S --spider -T 10 -t 2 "http://$SERVER/clonezilla-scripts/pxe-cz.sh" 2>&1 \
|
||||||
|
| sed -n 's/^ *Date: *//p' | head -1)"
|
||||||
|
[ -n "$hdr" ] || return 0
|
||||||
|
epoch="$(date -u -d "$hdr" +%s 2>/dev/null)" || return 0
|
||||||
|
[ -n "$epoch" ] || return 0
|
||||||
|
date -u -s "@$epoch" >/dev/null 2>&1 && TIME_SOURCE="pxe-server"
|
||||||
|
}
|
||||||
|
|
||||||
|
site_date() {
|
||||||
|
if [ -e "/usr/share/zoneinfo/$SITE_TZ" ]; then
|
||||||
|
TZ="$SITE_TZ" date "$@"
|
||||||
|
else
|
||||||
|
date "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- hardware -------------------------------------------------------------
|
||||||
|
|
||||||
|
dmi() {
|
||||||
|
local v
|
||||||
|
v="$(dmidecode -s "$1" 2>/dev/null | grep -v '^#' | head -1 | tr -d '\r' | sed 's/^ *//; s/ *$//')"
|
||||||
|
echo "$v"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Whole disks a backup can come from or a restore can go to: no CD, loop,
|
||||||
|
# RAM, zram, device-mapper, md, eMMC boot areas, or empty card readers.
|
||||||
|
list_disks() {
|
||||||
|
local d n size
|
||||||
|
for d in "$SYS_BLOCK"/*; do
|
||||||
|
n="${d##*/}"
|
||||||
|
case "$n" in
|
||||||
|
loop*|sr*|ram*|zram*|dm-*|md*|fd*|nbd*|*boot[0-9]|*rpmb) continue ;;
|
||||||
|
esac
|
||||||
|
size="$(cat "$d/size" 2>/dev/null)"
|
||||||
|
[ -n "$size" ] && [ "$size" -gt 0 ] 2>/dev/null || continue
|
||||||
|
echo "$n"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
disk_bytes() { echo $(( $(cat "$SYS_BLOCK/$1/size" 2>/dev/null || echo 0) * 512 )); }
|
||||||
|
|
||||||
|
disk_model() {
|
||||||
|
local m
|
||||||
|
m="$(cat "$SYS_BLOCK/$1/device/model" 2>/dev/null | sed 's/^ *//; s/ *$//')"
|
||||||
|
[ -n "$m" ] || m="$(lsblk -dn -o MODEL "/dev/$1" 2>/dev/null | sed 's/^ *//; s/ *$//')"
|
||||||
|
[ -n "$m" ] || m="unknown model"
|
||||||
|
echo "$m"
|
||||||
|
}
|
||||||
|
|
||||||
|
disk_tran() { lsblk -dn -o TRAN "/dev/$1" 2>/dev/null | sed 's/ //g'; }
|
||||||
|
|
||||||
|
human() {
|
||||||
|
local b="$1"
|
||||||
|
awk -v b="$b" 'BEGIN { if (b >= 1e12) printf "%.1f TB", b/1e12; else printf "%.0f GB", b/1e9 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
describe_disk() {
|
||||||
|
local n="$1" t
|
||||||
|
t="$(disk_tran "$n")"
|
||||||
|
echo "/dev/$n $(human "$(disk_bytes "$n")") $(disk_model "$n")${t:+ ($t)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# pick_disk "prompt" -> disk name on stdout. One disk: that one.
|
||||||
|
pick_disk() {
|
||||||
|
local prompt="$1" disks n i ans
|
||||||
|
mapfile -t disks < <(list_disks)
|
||||||
|
if [ "${#disks[@]}" -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "${#disks[@]}" -eq 1 ]; then
|
||||||
|
echo "${disks[0]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
while true; do
|
||||||
|
{
|
||||||
|
echo
|
||||||
|
echo " $prompt"
|
||||||
|
i=1
|
||||||
|
for n in "${disks[@]}"; do
|
||||||
|
echo " $i) $(describe_disk "$n")"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
echo -n " Number (1-${#disks[@]}): "
|
||||||
|
} >&2
|
||||||
|
read -r ans || ans=""
|
||||||
|
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "${#disks[@]}" ]; then
|
||||||
|
echo "${disks[$((ans - 1))]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo " Type a number from the list." >&2
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
live_version() {
|
||||||
|
local v=""
|
||||||
|
if [ -e /etc/ocs/ocs-live.conf ]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
v="$(. /etc/ocs/ocs-live.conf >/dev/null 2>&1; echo "${drbl_ocs_live_ver:-}")"
|
||||||
|
fi
|
||||||
|
[ -n "$v" ] || v="clonezilla $(dpkg-query -W -f='${Version}' clonezilla 2>/dev/null)"
|
||||||
|
echo "$v"
|
||||||
|
}
|
||||||
|
|
||||||
|
bits() {
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64|amd64) echo 64 ;;
|
||||||
|
*) echo 32 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# set_meta FILE KEY VALUE: replace or add one key=value line.
|
||||||
|
set_meta() {
|
||||||
|
local f="$1" k="$2" v="$3" tmp
|
||||||
|
v="$(echo "$v" | tr -d '\r\n')"
|
||||||
|
tmp="$(mktemp /tmp/pxe-meta.XXXXXX)"
|
||||||
|
[ -e "$f" ] && grep -v "^$k=" "$f" > "$tmp"
|
||||||
|
echo "$k=$v" >> "$tmp"
|
||||||
|
cat "$tmp" > "$f"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_meta() {
|
||||||
|
sed -n "s/^$2=//p" "$1" 2>/dev/null | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_ready() {
|
||||||
|
mountpoint -q "$OCSROOT" 2>/dev/null || grep -qs " $OCSROOT cifs " /proc/mounts
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- backup ---------------------------------------------------------------
|
||||||
|
|
||||||
|
do_save() {
|
||||||
|
local m="$1" disk stamp img n meta rc chk
|
||||||
|
disk="$(pick_disk "This PC has more than one disk. Which one do you want to back up?")"
|
||||||
|
[ -n "$disk" ] || fail "No disk found to back up." "Check the disk is connected and shows in the BIOS."
|
||||||
|
stamp="$(site_date +%Y-%m-%d-%H%M)"
|
||||||
|
img="$m-$stamp"
|
||||||
|
n=2
|
||||||
|
while [ -e "$OCSROOT/$img" ]; do
|
||||||
|
img="$m-$stamp-$n"
|
||||||
|
n=$((n + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
banner "BACKUP of machine $m" \
|
||||||
|
"Disk: $(describe_disk "$disk")" \
|
||||||
|
"Image: $img" \
|
||||||
|
"To: \\\\$SERVER\\clonezilla\\$img"
|
||||||
|
|
||||||
|
ocs-sr -q2 -batch -j2 -z9p -i 0 -sfsck -scs -senc -p command savedisk "$img" "$disk"
|
||||||
|
rc=$?
|
||||||
|
|
||||||
|
meta="$OCSROOT/$img/pxe-meta.txt"
|
||||||
|
if [ ! -d "$OCSROOT/$img" ]; then
|
||||||
|
fail "Clonezilla did not create the image $img (exit code $rc)." \
|
||||||
|
"See the messages above. Nothing was saved."
|
||||||
|
fi
|
||||||
|
: > "$meta"
|
||||||
|
set_meta "$meta" machine "$m"
|
||||||
|
set_meta "$meta" date "$(site_date '+%Y-%m-%d %H:%M %Z')"
|
||||||
|
set_meta "$meta" date_utc "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
set_meta "$meta" time_source "$TIME_SOURCE"
|
||||||
|
set_meta "$meta" serial "$(dmi system-serial-number)"
|
||||||
|
set_meta "$meta" manufacturer "$(dmi system-manufacturer)"
|
||||||
|
set_meta "$meta" model "$(dmi system-product-name)"
|
||||||
|
set_meta "$meta" disk "$disk"
|
||||||
|
set_meta "$meta" disk_model "$(disk_model "$disk")"
|
||||||
|
set_meta "$meta" disk_size_bytes "$(disk_bytes "$disk")"
|
||||||
|
set_meta "$meta" clonezilla "$(live_version)"
|
||||||
|
set_meta "$meta" bits "$(bits)"
|
||||||
|
if [ -d /sys/firmware/efi ]; then set_meta "$meta" firmware uefi; else set_meta "$meta" firmware bios; fi
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
set_meta "$meta" save failed
|
||||||
|
set_meta "$meta" check not-run
|
||||||
|
fail "Clonezilla could not save machine $m (exit code $rc)." \
|
||||||
|
"The partial image $img is marked as failed on the share;" \
|
||||||
|
"delete it in the webapp (Tools > Clonezilla backups) and try again."
|
||||||
|
fi
|
||||||
|
set_meta "$meta" save ok
|
||||||
|
|
||||||
|
banner "Checking the saved image $img ..."
|
||||||
|
ocs-chkimg -b "$img"
|
||||||
|
chk=$?
|
||||||
|
set_meta "$meta" check_date "$(site_date '+%Y-%m-%d %H:%M %Z')"
|
||||||
|
if [ "$chk" -ne 0 ]; then
|
||||||
|
set_meta "$meta" check failed
|
||||||
|
fail "The image $img was saved but its CHECK FAILED (exit code $chk)." \
|
||||||
|
"Do not rely on it. Back this PC up again."
|
||||||
|
fi
|
||||||
|
set_meta "$meta" check ok
|
||||||
|
sync
|
||||||
|
banner "SUCCESS: machine $m backed up and checked." \
|
||||||
|
"Image: $img" \
|
||||||
|
"Serial: $(get_meta "$meta" serial) Model: $(get_meta "$meta" model)"
|
||||||
|
finish yes
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- restore --------------------------------------------------------------
|
||||||
|
|
||||||
|
is_cz_image() {
|
||||||
|
local d="$1"
|
||||||
|
[ -d "$d" ] || return 1
|
||||||
|
[ -f "$d/disk" ] || [ -f "$d/parts" ] || return 1
|
||||||
|
local f
|
||||||
|
for f in "$d"/*; do
|
||||||
|
case "${f##*/}" in
|
||||||
|
*-ptcl-img*|*.dd-img*|*-img.*|clonezilla-img) return 0 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Images of one machine, newest first: <machine>-YYYY-MM-DD-HHMM[-n] by
|
||||||
|
# name, plus any image whose pxe-meta.txt says machine=<machine>.
|
||||||
|
machine_images() {
|
||||||
|
local m="$1" d n mm
|
||||||
|
for d in "$OCSROOT"/*/; do
|
||||||
|
d="${d%/}"
|
||||||
|
n="${d##*/}"
|
||||||
|
is_cz_image "$d" || continue
|
||||||
|
mm="$(get_meta "$d/pxe-meta.txt" machine | tr '[:lower:]' '[:upper:]')"
|
||||||
|
if [ "$mm" = "$m" ]; then
|
||||||
|
echo "$n"
|
||||||
|
elif [ -z "$mm" ] && echo "$n" | tr '[:lower:]' '[:upper:]' | grep -qE "^$m-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}(-[0-9]+)?$"; then
|
||||||
|
echo "$n"
|
||||||
|
fi
|
||||||
|
done | sort_newest
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sort names by their date stamp (the last YYYY-MM-DD-HHMM[-n]), newest first.
|
||||||
|
sort_newest() {
|
||||||
|
local n key
|
||||||
|
while read -r n; do
|
||||||
|
key="$(echo "$n" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}(-[0-9]+)?$')"
|
||||||
|
echo "${key:-0000}|$n"
|
||||||
|
done | sort -r | cut -d'|' -f2-
|
||||||
|
}
|
||||||
|
|
||||||
|
image_disks() { tr -s ' \t' '\n' < "$OCSROOT/$1/disk" 2>/dev/null | grep -v '^$'; }
|
||||||
|
|
||||||
|
image_disk_bytes() {
|
||||||
|
local img="$1" src="$2" b sec
|
||||||
|
b="$(get_meta "$OCSROOT/$img/pxe-meta.txt" disk_size_bytes)"
|
||||||
|
if [[ "$b" =~ ^[0-9]+$ ]]; then echo "$b"; return; fi
|
||||||
|
# Clonezilla's sfdisk dump: "last-lba: N" (GPT) or the partitions' end.
|
||||||
|
sec="$(sed -n 's/^last-lba: *//p' "$OCSROOT/$img/$src-pt.sf" 2>/dev/null | head -1)"
|
||||||
|
if [[ "$sec" =~ ^[0-9]+$ ]]; then echo $(( (sec + 34) * 512 )); return; fi
|
||||||
|
echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
do_restore() {
|
||||||
|
local m="$1" imgs i n ans img src srcb tgt tgtb meta chkopt rc mo se ck
|
||||||
|
mapfile -t imgs < <(machine_images "$m")
|
||||||
|
if [ "${#imgs[@]}" -eq 0 ]; then
|
||||||
|
fail "No images for machine $m on \\\\$SERVER\\clonezilla." \
|
||||||
|
"Check the machine number, or look in the webapp (Tools > Clonezilla backups)."
|
||||||
|
fi
|
||||||
|
while true; do
|
||||||
|
echo
|
||||||
|
echo " Images of machine $m (newest first):"
|
||||||
|
i=1
|
||||||
|
for n in "${imgs[@]}"; do
|
||||||
|
meta="$OCSROOT/$n/pxe-meta.txt"
|
||||||
|
mo="$(get_meta "$meta" model)"; se="$(get_meta "$meta" serial)"; ck="$(get_meta "$meta" check)"
|
||||||
|
echo " $i) $n ${mo:-model ?} serial ${se:-?} check: ${ck:-?}"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
echo -n " Image to restore (1-${#imgs[@]}, q = quit): "
|
||||||
|
read -r ans || ans=q
|
||||||
|
case "$ans" in q|Q) finish no ;; esac
|
||||||
|
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "${#imgs[@]}" ]; then
|
||||||
|
img="${imgs[$((ans - 1))]}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo " Type a number from the list."
|
||||||
|
done
|
||||||
|
meta="$OCSROOT/$img/pxe-meta.txt"
|
||||||
|
if [ "$(get_meta "$meta" save)" = "failed" ]; then
|
||||||
|
fail "The image $img is marked as a FAILED backup and cannot be restored."
|
||||||
|
fi
|
||||||
|
mapfile -t src < <(image_disks "$img")
|
||||||
|
if [ "${#src[@]}" -ne 1 ]; then
|
||||||
|
fail "The image $img holds ${#src[@]} disks (${src[*]:-none})." \
|
||||||
|
"Restore it with Clonezilla: manual (boot menu)."
|
||||||
|
fi
|
||||||
|
srcb="$(image_disk_bytes "$img" "${src[0]}")"
|
||||||
|
|
||||||
|
tgt="$(pick_disk "Which disk do you want to restore $img to? (ALL DATA ON IT WILL BE ERASED)")"
|
||||||
|
[ -n "$tgt" ] || fail "No disk found to restore to."
|
||||||
|
tgtb="$(disk_bytes "$tgt")"
|
||||||
|
if [ "$srcb" -gt 0 ] && [ "$tgtb" -lt "$srcb" ]; then
|
||||||
|
fail "The target disk is SMALLER than the disk the image came from." \
|
||||||
|
"Image disk: $(human "$srcb")" "Target disk: $(describe_disk "$tgt")" \
|
||||||
|
"Use a disk at least as large, or Clonezilla: manual (expert mode, -icds)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
banner "RESTORE machine $m" \
|
||||||
|
"Image: $img (saved $(get_meta "$meta" date))" \
|
||||||
|
"From: $(get_meta "$meta" model) serial $(get_meta "$meta" serial) disk $(human "$srcb")" \
|
||||||
|
"Target: $(describe_disk "$tgt")" \
|
||||||
|
"" \
|
||||||
|
"EVERYTHING on /dev/$tgt will be ERASED and replaced by this image." \
|
||||||
|
"This PC: $(dmi system-product-name) serial $(dmi system-serial-number)"
|
||||||
|
echo -n " Type YES (capitals) to erase /dev/$tgt and restore: "
|
||||||
|
read -r ans || ans=""
|
||||||
|
if [ "$ans" != "YES" ]; then
|
||||||
|
banner "Cancelled. Nothing was written."
|
||||||
|
finish no
|
||||||
|
fi
|
||||||
|
|
||||||
|
# A larger target: -k1 recreates the partition table proportionally
|
||||||
|
# (every partition scaled by target/source size) and -r grows each
|
||||||
|
# filesystem to its new partition, so the extra space is used. The
|
||||||
|
# default (-k0) copies the table as-is and leaves the rest unallocated.
|
||||||
|
# The image was checked after it was saved; check again only if that
|
||||||
|
# check did not pass.
|
||||||
|
chkopt=""
|
||||||
|
[ "$(get_meta "$meta" check)" = "ok" ] && chkopt="-scr"
|
||||||
|
ocs-sr -g auto -e1 auto -e2 -r -j2 -k1 $chkopt -batch -p command restoredisk "$img" "$tgt"
|
||||||
|
rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
fail "Clonezilla could not restore $img to /dev/$tgt (exit code $rc)." \
|
||||||
|
"The disk may be partly written: do not boot from it until a restore succeeds."
|
||||||
|
fi
|
||||||
|
sync
|
||||||
|
banner "SUCCESS: $img restored to /dev/$tgt." "At the next start, boot this PC from its disk (not the network)."
|
||||||
|
finish yes
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- main -----------------------------------------------------------------
|
||||||
|
|
||||||
|
MODE="$(echo "$CMDLINE" | sed -n 's/.* pxe_cz_mode=\([a-z]*\) .*/\1/p')"
|
||||||
|
case "$MODE" in
|
||||||
|
save|restore) ;;
|
||||||
|
*) fail "Unknown mode [$MODE] on the kernel command line (pxe_cz_mode=save or restore)." ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! repo_ready; then
|
||||||
|
fail "The backup share \\\\$SERVER\\clonezilla is not mounted on $OCSROOT." \
|
||||||
|
"Check the PXE server is up and the clonezilla Samba account works" \
|
||||||
|
"(playbook var clonezilla_smb_password), then boot this option again."
|
||||||
|
fi
|
||||||
|
|
||||||
|
sync_clock
|
||||||
|
MACHINE=""
|
||||||
|
get_machine
|
||||||
|
case "$MODE" in
|
||||||
|
save) do_save "$MACHINE" ;;
|
||||||
|
restore) do_restore "$MACHINE" ;;
|
||||||
|
esac
|
||||||
190
playbook/engineering/Install-EngineeringLayer.ps1
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# Install-EngineeringLayer.ps1 - install the Engineering Layer before the MCL
|
||||||
|
# orchestrator's cleanup removes C:\Deploy, and make sure the employee gets
|
||||||
|
# the "Configure my Engineering Layer" icon.
|
||||||
|
#
|
||||||
|
# THE RACE THIS SOLVES
|
||||||
|
# The bulk-enrollment orchestrator registers a DeleteDeployDir startup task that
|
||||||
|
# does "rmdir /s /q C:\Drivers & rmdir /s /q C:\GEA & rmdir /s /q C:\Deploy" on
|
||||||
|
# the NEXT boot. The engineering payload (529 MB installer + .NET 3.5 sxs + VC++
|
||||||
|
# redists, 653 MB total) lives under C:\Deploy\Applications\extra_engineering.
|
||||||
|
# Standard/Engineer builds have no autologon, so a PC that reboots between OOBE
|
||||||
|
# and the employee's first sign-in loses C:\Deploy before the first-logon
|
||||||
|
# install - and with it this script, which used to be called from there too.
|
||||||
|
# Then nothing installed, no error anywhere, and no icon.
|
||||||
|
#
|
||||||
|
# So, in two steps (both from FlatUnattendW10-engineer.xml):
|
||||||
|
# specialize -StageOnly : copy the payload AND this script to
|
||||||
|
# C:\PreInstall\engineering while C:\Deploy certainly exists.
|
||||||
|
# first logon (no switch, run from C:\PreInstall\engineering): install
|
||||||
|
# from the staged copy. C:\Deploy is not needed any more.
|
||||||
|
# Run from C:\Deploy (an older unattend) it still stages first, as before.
|
||||||
|
#
|
||||||
|
# THE ICON
|
||||||
|
# Engineering_Install_v3.1 copies "Configure my Engineering Layer.lnk" to
|
||||||
|
# C:\Users\Public\Desktop. The employee clicks it once (on the GE network: it
|
||||||
|
# maps V: and runs install_local_new.cmd from there), which writes
|
||||||
|
# C:\TMP\DUU_Complete.log and deletes the Public icon. After the install this
|
||||||
|
# script puts the icon back if it is missing and the PC is not configured
|
||||||
|
# yet, and registers an Active Setup component: at each user's first sign-in
|
||||||
|
# Windows runs Ensure-EngineeringIcon.ps1 in that user's session, which puts
|
||||||
|
# the icon on that user's desktop if the PC is still not configured and the
|
||||||
|
# Public icon is gone (someone else clicked it, or it never appeared).
|
||||||
|
#
|
||||||
|
# Idempotent. Safe to re-run: it skips the copy when the staged payload is
|
||||||
|
# already complete and skips the install when the marker is present (the
|
||||||
|
# icon check still runs).
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$Source = 'C:\Deploy\Applications\extra_engineering',
|
||||||
|
# C:\PreInstall is already the convention for payload that must outlive
|
||||||
|
# imaging - the unattend installs PowerShell 7 from C:\PreInstall\installers.
|
||||||
|
[string]$StagingRoot = 'C:\PreInstall\engineering',
|
||||||
|
[string]$LogDir = 'C:\Logs\Engineering',
|
||||||
|
# Relative to StagingRoot. Empty: the newest
|
||||||
|
# engineering_layer\Engineering_Install_v<version>.exe (uploaded on the
|
||||||
|
# PXE webapp, engineer image > Engineering Layer).
|
||||||
|
[string]$Installer = '',
|
||||||
|
[switch]$StageOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
||||||
|
$log = Join-Path $LogDir 'install-engineering-layer.log'
|
||||||
|
$marker = Join-Path $StagingRoot '.installed'
|
||||||
|
|
||||||
|
$IconName = 'Configure my Engineering Layer.lnk'
|
||||||
|
$IconSource = 'C:\TMP\Engineering_Install\Imaging\packages\' + $IconName
|
||||||
|
$PublicIcon = 'C:\Users\Public\Desktop\' + $IconName
|
||||||
|
$Configured = 'C:\TMP\DUU_Complete.log' # written by install_local_new.cmd
|
||||||
|
$ActiveSetup = 'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\GEA-EngineeringLayerIcon'
|
||||||
|
|
||||||
|
function Write-EngLog {
|
||||||
|
param([string]$Message, [string]$Level = 'INFO')
|
||||||
|
$line = '[{0}] [{1}] [{2}] {3}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $env:USERNAME, $Message
|
||||||
|
Add-Content -Path $log -Value $line -ErrorAction SilentlyContinue
|
||||||
|
Write-Host $line
|
||||||
|
}
|
||||||
|
|
||||||
|
# Icon for whoever signs in next. Called after every install attempt that
|
||||||
|
# left the layer installed (fresh or already marked).
|
||||||
|
function Set-EngineeringIcon {
|
||||||
|
if (Test-Path -LiteralPath $Configured) {
|
||||||
|
Write-EngLog 'PC already configured (DUU_Complete.log) - no icon needed.'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (-not (Test-Path -LiteralPath $IconSource)) {
|
||||||
|
Write-EngLog "Icon source missing: $IconSource (installer layout changed?)" 'ERROR'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Test-Path -LiteralPath $PublicIcon) {
|
||||||
|
Write-EngLog 'Public desktop icon present.'
|
||||||
|
} else {
|
||||||
|
Copy-Item -LiteralPath $IconSource -Destination $PublicIcon -Force -ErrorAction SilentlyContinue
|
||||||
|
if (Test-Path -LiteralPath $PublicIcon) { Write-EngLog 'Public desktop icon was missing - restored.' 'WARN' }
|
||||||
|
else { Write-EngLog "Could not restore $PublicIcon" 'ERROR' }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Per-user fallback, run by Windows once per user at first sign-in.
|
||||||
|
$ensure = Join-Path $StagingRoot 'Ensure-EngineeringIcon.ps1'
|
||||||
|
$body = @(
|
||||||
|
'# Written by Install-EngineeringLayer.ps1; run once per user by Active Setup.'
|
||||||
|
'# Puts "Configure my Engineering Layer" on this user''s desktop when the PC'
|
||||||
|
'# is not configured yet and the Public desktop icon is gone.'
|
||||||
|
"`$src = '$IconSource'"
|
||||||
|
"if (Test-Path -LiteralPath '$Configured') { exit 0 }"
|
||||||
|
"if (Test-Path -LiteralPath '$PublicIcon') { exit 0 }"
|
||||||
|
'if (-not (Test-Path -LiteralPath $src)) { exit 0 }'
|
||||||
|
'$desk = [Environment]::GetFolderPath(''Desktop'')'
|
||||||
|
'if ($desk) { Copy-Item -LiteralPath $src -Destination (Join-Path $desk (Split-Path $src -Leaf)) -Force -ErrorAction SilentlyContinue }'
|
||||||
|
'exit 0'
|
||||||
|
)
|
||||||
|
Set-Content -Path $ensure -Value $body -Encoding ascii -Force
|
||||||
|
New-Item -Path $ActiveSetup -Force | Out-Null
|
||||||
|
Set-ItemProperty -Path $ActiveSetup -Name '(Default)' -Value 'GE Aerospace Engineering Layer icon'
|
||||||
|
Set-ItemProperty -Path $ActiveSetup -Name 'Version' -Value '1,0'
|
||||||
|
Set-ItemProperty -Path $ActiveSetup -Name 'IsInstalled' -Value 1 -Type DWord
|
||||||
|
Set-ItemProperty -Path $ActiveSetup -Name 'StubPath' -Value ('powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "' + $ensure + '"')
|
||||||
|
Write-EngLog 'Active Setup registered: each new user gets the icon if the PC is still unconfigured.'
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-EngLog '=== Install-EngineeringLayer starting ==='
|
||||||
|
Write-EngLog "Source: $Source"
|
||||||
|
Write-EngLog "Staged: $StagingRoot"
|
||||||
|
Write-EngLog "Running from: $PSCommandPath"
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $marker) {
|
||||||
|
Write-EngLog 'Marker present - Engineering Layer already installed.'
|
||||||
|
if (-not $StageOnly) { Set-EngineeringIcon }
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- stage the payload out of C:\Deploy -------------------------------------
|
||||||
|
function Find-Installer([string]$Dir) {
|
||||||
|
Get-ChildItem -LiteralPath (Join-Path $Dir 'engineering_layer') -Filter 'Engineering_Install_v*.exe' -File -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -match '^Engineering_Install_v(\d+(\.\d+)*)\.exe$' } |
|
||||||
|
Sort-Object { $v = ($_.Name -replace '^Engineering_Install_v', '') -replace '\.exe$', ''; if ($v -notmatch '\.') { $v += '.0' }; [version]$v } -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
}
|
||||||
|
if (-not $Installer) {
|
||||||
|
$found = Find-Installer $StagingRoot
|
||||||
|
if (-not $found) { $found = Find-Installer $Source }
|
||||||
|
$Installer = if ($found) { 'engineering_layer\' + $found.Name } else { 'engineering_layer\Engineering_Install_v3.1.exe' }
|
||||||
|
}
|
||||||
|
Write-EngLog "Installer: $Installer"
|
||||||
|
$stagedExe = Join-Path $StagingRoot $Installer
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $stagedExe) {
|
||||||
|
Write-EngLog 'Payload already staged outside C:\Deploy - skipping copy.'
|
||||||
|
} else {
|
||||||
|
if (-not (Test-Path -LiteralPath $Source)) {
|
||||||
|
Write-EngLog "Source not found: $Source" 'ERROR'
|
||||||
|
Write-EngLog 'C:\Deploy was removed (DeleteDeployDir) before the payload was staged. Re-stage from the share:' 'ERROR'
|
||||||
|
Write-EngLog ' \\172.16.9.1\winpeapps\gea-engineer\Deploy\Applications\extra_engineering' 'ERROR'
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
Write-EngLog 'Copying payload out of C:\Deploy (this is the whole point - do not skip)'
|
||||||
|
New-Item -ItemType Directory -Force -Path $StagingRoot | Out-Null
|
||||||
|
# robocopy, not Copy-Item: 653 MB, restartable, and it reports properly.
|
||||||
|
# Exit codes 0-7 are success, 8+ are failure - inverted vs everything else.
|
||||||
|
# The copy includes this script, so first logon can run it from here.
|
||||||
|
$rc = Start-Process -FilePath 'robocopy.exe' `
|
||||||
|
-ArgumentList @("`"$Source`"", "`"$StagingRoot`"", '/E', '/R:2', '/W:5', '/NFL', '/NDL', '/NJH', '/NJS') `
|
||||||
|
-Wait -PassThru -WindowStyle Hidden
|
||||||
|
if ($rc.ExitCode -ge 8) {
|
||||||
|
Write-EngLog "robocopy failed with exit $($rc.ExitCode) - payload NOT staged" 'ERROR'
|
||||||
|
exit 3
|
||||||
|
}
|
||||||
|
Write-EngLog "Payload staged (robocopy exit $($rc.ExitCode))"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $stagedExe)) {
|
||||||
|
Write-EngLog "Installer missing after staging: $stagedExe" 'ERROR'
|
||||||
|
exit 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($StageOnly) {
|
||||||
|
Write-EngLog 'StageOnly requested - payload is safe from cleanup, not installing.'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- install -----------------------------------------------------------------
|
||||||
|
# The prereqs (.NET 3.5 sxs + VC++ redists) ship alongside; v3.1 installs them
|
||||||
|
# itself and is idempotent, so they are staged for it rather than invoked here.
|
||||||
|
Write-EngLog "Running $Installer (this takes a while - .NET 3.5, VC++, Cygwin, DUU)"
|
||||||
|
try {
|
||||||
|
$p = Start-Process -FilePath $stagedExe -ArgumentList '/S' -Wait -PassThru
|
||||||
|
Write-EngLog "Installer exit code: $($p.ExitCode)"
|
||||||
|
if ($p.ExitCode -eq 0 -or $p.ExitCode -eq 3010) {
|
||||||
|
if ($p.ExitCode -eq 3010) { Write-EngLog 'Installer requests a reboot (3010)' 'WARN' }
|
||||||
|
Set-Content -Path $marker -Value (Get-Date -Format 'o') -Encoding ascii -Force
|
||||||
|
Write-EngLog 'Engineering Layer installed. Marker written.'
|
||||||
|
Set-EngineeringIcon
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
Write-EngLog "Installer returned $($p.ExitCode) - NOT marking as installed" 'ERROR'
|
||||||
|
exit 5
|
||||||
|
} catch {
|
||||||
|
Write-EngLog "Installer failed to start: $_" 'ERROR'
|
||||||
|
exit 6
|
||||||
|
}
|
||||||
96
playbook/imaging-status/Send-ImagingStage.ps1
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Send-ImagingStage.ps1 - posts one progress step of a gea-standard /
|
||||||
|
# gea-engineer build to the PXE webapp's /imaging/status endpoint, so the
|
||||||
|
# imaging floor follows these builds past WinPE (the shopfloor build has its
|
||||||
|
# own reporting in Shopfloor\lib\Send-PxeStatus.ps1).
|
||||||
|
#
|
||||||
|
# Called from the image's FlatUnattendW10.xml (specialize RunSynchronous and
|
||||||
|
# oobeSystem FirstLogonCommands). Keep those command lines short: Windows
|
||||||
|
# rejects the whole unattend if a RunSynchronous Path exceeds 259 characters
|
||||||
|
# (check with scripts/lint-unattend.py --local). Deployed to
|
||||||
|
# _shared\Applications\extra\pxe-status\ on the PXE server, which PESetup
|
||||||
|
# copies to C:\Deploy\Applications\extra\pxe-status\.
|
||||||
|
#
|
||||||
|
# Best-effort: never blocks or fails Windows setup. Every error is swallowed,
|
||||||
|
# the script always exits 0, and each attempt is logged to
|
||||||
|
# C:\Logs\send-imaging-stage.log.
|
||||||
|
#
|
||||||
|
# Three steps: 1 WinPE, 2 Windows setup installing apps, 3 ready at OOBE
|
||||||
|
# (succeeded). Everything after OOBE needs the employee to log in, which
|
||||||
|
# happens off the PXE network, so PXE cannot see it. Steps above
|
||||||
|
# StageTotal (the old first-logon steps 4 and 5 still in the unattends) are
|
||||||
|
# logged and not posted, so they cannot reopen a finished build.
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)][string]$Stage,
|
||||||
|
[int]$StageIndex = 0,
|
||||||
|
[int]$StageTotal = 3,
|
||||||
|
[string]$PCType = '',
|
||||||
|
[ValidateSet('in_progress', 'succeeded', 'failed')][string]$Status = 'in_progress',
|
||||||
|
[string]$PxeServer = '172.16.9.1',
|
||||||
|
[int]$Port = 9009,
|
||||||
|
[int]$TimeoutSec = 5,
|
||||||
|
# Copy this script to C:\ProgramData\PxeStatus first: the bulk-enrollment
|
||||||
|
# task deletes C:\Deploy before first logon, when the later steps run.
|
||||||
|
[switch]$Persist
|
||||||
|
)
|
||||||
|
|
||||||
|
$logFile = 'C:\Logs\send-imaging-stage.log'
|
||||||
|
function Log([string]$msg) {
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path 'C:\Logs')) { New-Item -ItemType Directory -Path 'C:\Logs' -Force | Out-Null }
|
||||||
|
"$(Get-Date -Format s) $msg" | Out-File -FilePath $logFile -Append -Encoding utf8
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Persist) {
|
||||||
|
try {
|
||||||
|
New-Item -ItemType Directory -Path 'C:\ProgramData\PxeStatus' -Force | Out-Null
|
||||||
|
Copy-Item -LiteralPath $PSCommandPath -Destination 'C:\ProgramData\PxeStatus\Send-ImagingStage.ps1' -Force
|
||||||
|
} catch { Log "persist failed: $($_.Exception.Message)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($StageIndex -gt $StageTotal) {
|
||||||
|
Log "SKIP stage $StageIndex of $StageTotal (after OOBE; PXE does not track it): $Stage"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$serial = $null
|
||||||
|
try { $serial = (Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber } catch {
|
||||||
|
try { $serial = (Get-WmiObject -Class Win32_BIOS -ErrorAction Stop).SerialNumber } catch { }
|
||||||
|
}
|
||||||
|
if ([string]::IsNullOrWhiteSpace($serial)) { $serial = $env:COMPUTERNAME }
|
||||||
|
$serial = ($serial -as [string]).Trim()
|
||||||
|
|
||||||
|
$mac = $null
|
||||||
|
try {
|
||||||
|
$nic = Get-NetAdapter -Physical -ErrorAction Stop |
|
||||||
|
Where-Object { $_.Status -eq 'Up' -and $_.MediaType -eq '802.3' } | Select-Object -First 1
|
||||||
|
if ($nic) { $mac = $nic.MacAddress -replace '-', ':' }
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
$payload = @{
|
||||||
|
serial = $serial
|
||||||
|
hostname_target = $env:COMPUTERNAME
|
||||||
|
current_stage = $Stage
|
||||||
|
stage_index = $StageIndex
|
||||||
|
stage_total = $StageTotal
|
||||||
|
status = $Status
|
||||||
|
}
|
||||||
|
if ($PCType) { $payload.pctype = $PCType }
|
||||||
|
if ($mac) { $payload.mac = $mac }
|
||||||
|
$body = $payload | ConvertTo-Json -Compress
|
||||||
|
$uri = "http://${PxeServer}:${Port}/imaging/status"
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resp = Invoke-WebRequest -Uri $uri -Method POST -Body $body -ContentType 'application/json' `
|
||||||
|
-UseBasicParsing -TimeoutSec $TimeoutSec -ErrorAction Stop
|
||||||
|
Log "OK $($resp.StatusCode) $body"
|
||||||
|
} catch {
|
||||||
|
# Off the imaging network (or the server is down): nothing to report to.
|
||||||
|
Log "FAIL $($_.Exception.Message) $body"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log "ERROR $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
198
playbook/ipxe/GetPxeScript.aspx
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
#!ipxe
|
||||||
|
|
||||||
|
set server 172.16.9.1
|
||||||
|
|
||||||
|
:menu
|
||||||
|
menu GE Aerospace PXE Boot Menu
|
||||||
|
item --gap -- ---- Windows Deployment ----
|
||||||
|
item winpe Windows PE (Image Deployment)
|
||||||
|
item --gap -- ---- Drive Erase ----
|
||||||
|
item blancco Blancco Drive Eraser
|
||||||
|
item blancco_debug Blancco Drive Eraser (debug / SSH)
|
||||||
|
item --gap -- ---- Utilities ----
|
||||||
|
item cz_backup Clonezilla: back up this PC
|
||||||
|
item cz_restore Clonezilla: restore this PC
|
||||||
|
item clonezilla Clonezilla: manual (USB or other location)
|
||||||
|
item clonezilla32 Clonezilla: manual, 32-bit (XP-era PCs, forced)
|
||||||
|
item --gap -- Memory test: Dell PCs press F12 at power-on, Diagnostics
|
||||||
|
item --gap -- ----
|
||||||
|
item reboot Reboot
|
||||||
|
item exit Exit to BIOS
|
||||||
|
choose --default winpe --timeout 30000 target && goto ${target}
|
||||||
|
|
||||||
|
:winpe
|
||||||
|
# iPXE 2.0 reads the firmware variable; older iPXE cannot (empty), so it
|
||||||
|
# warns as before. Secure Boot confirmed on: no warning, no wait.
|
||||||
|
iseq ${efi/SecureBoot:uint8} 1 && goto winpe_boot ||
|
||||||
|
iseq ${efi/SecureBoot:hex} 01 && goto winpe_boot ||
|
||||||
|
echo
|
||||||
|
echo Secure Boot is OFF on this PC (or this iPXE cannot tell).
|
||||||
|
echo (iPXE read SecureBoot as: [${efi/SecureBoot:hex}] [${efi/SecureBoot:uint8}])
|
||||||
|
echo Windows deployment requires Secure Boot to be ENABLED.
|
||||||
|
echo If you disabled it earlier, re-enable it in BIOS Setup now.
|
||||||
|
echo
|
||||||
|
prompt --timeout 5000 Press any key to continue (auto-boot in 5s)... && goto winpe_boot || goto winpe_boot
|
||||||
|
|
||||||
|
:winpe_boot
|
||||||
|
kernel http://${server}/win11/wimboot gui
|
||||||
|
initrd http://${server}/win11/EFI/Microsoft/Boot/boot.stl EFI/Microsoft/Boot/Boot.stl
|
||||||
|
initrd http://${server}/win11/EFI/Microsoft/Boot/BCD EFI/Microsoft/Boot/BCD
|
||||||
|
initrd http://${server}/win11/EFI/Boot/bootx64.efi EFI/Boot/bootx64.efi
|
||||||
|
initrd http://${server}/win11/Boot/boot.sdi Boot/boot.sdi
|
||||||
|
initrd http://${server}/win11/sources/boot.wim sources/boot.wim
|
||||||
|
# A command that fails without || aborts the whole iPXE script (the PC
|
||||||
|
# drops to firmware with no explanation), so every boot line has one.
|
||||||
|
boot || goto winpe_failed
|
||||||
|
|
||||||
|
:winpe_failed
|
||||||
|
echo
|
||||||
|
echo Windows PE boot FAILED (see the error above).
|
||||||
|
echo Check that the PXE server is up and the win11 boot files are staged.
|
||||||
|
prompt Press any key to return to menu... && goto menu || goto menu
|
||||||
|
|
||||||
|
# --- Clonezilla by machine number (back up / restore) ---------------------
|
||||||
|
# The image share is \\${server}\clonezilla (Samba account clonezilla,
|
||||||
|
# the playbook fills in its password). Clonezilla mounts it on
|
||||||
|
# /home/partimag (ocs_repository, SMB 3), downloads
|
||||||
|
# clonezilla-scripts/pxe-cz.sh (ocs_prerun) and runs it (ocs_live_run):
|
||||||
|
# it checks the machine number, saves <machine>-<YYYY-MM-DD-HHMM> or lists
|
||||||
|
# that machine's images to restore, and writes pxe-meta.txt.
|
||||||
|
:cz_backup
|
||||||
|
set cz_mode save
|
||||||
|
goto cz_machine
|
||||||
|
|
||||||
|
:cz_restore
|
||||||
|
set cz_mode restore
|
||||||
|
goto cz_machine
|
||||||
|
|
||||||
|
:cz_machine
|
||||||
|
# iPXE cannot check characters: pxe-cz.sh accepts letters, digits and dash
|
||||||
|
# and asks again there. Empty: ask again. Esc / Ctrl-C: back to the menu.
|
||||||
|
echo
|
||||||
|
# iPXE drops a trailing space from a command line, so the space after the
|
||||||
|
# colon comes from a setting (0x20) rather than the literal text.
|
||||||
|
set sp:hex 20
|
||||||
|
echo -n Machine number:${sp:string} && read czmachine || goto menu
|
||||||
|
isset ${czmachine} || goto cz_machine
|
||||||
|
# Same CPU check as the manual entry below (cpuid --ext 29 = 64-bit).
|
||||||
|
cpuid --ext 29 && goto cz_auto64 || goto cz_auto32
|
||||||
|
|
||||||
|
:cz_auto64
|
||||||
|
set base http://${server}/clonezilla
|
||||||
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales=en_US.UTF-8 keyboard-layouts=us ocs_prerun="wget -q -T 15 -t 5 -O /tmp/pxe-cz.sh http://${server}/clonezilla-scripts/pxe-cz.sh" ocs_repository="smb3://clonezilla:__CLONEZILLA_SMB_PASSWORD__@${server}/clonezilla/" echo_ocs_repository=no ocs_live_run="bash /tmp/pxe-cz.sh" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs pxe_cz_mode=${cz_mode} pxe_machine=${czmachine} pxe_end=1 || goto secureboot_warn
|
||||||
|
initrd ${base}/initrd.img || goto boot_failed
|
||||||
|
# Secure Boot ON: Debian's shim, as in :clonezilla64 below.
|
||||||
|
shim ${base}/shimx64.efi ||
|
||||||
|
boot || goto secureboot_warn
|
||||||
|
|
||||||
|
:cz_auto32
|
||||||
|
# 3.2.0-5 i686 over legacy BIOS only; UEFI goes to the 64-bit build.
|
||||||
|
iseq ${platform} efi && goto cz_auto64 ||
|
||||||
|
set base http://${server}/clonezilla-i686
|
||||||
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales=en_US.UTF-8 keyboard-layouts=us ocs_prerun="wget -q -T 15 -t 5 -O /tmp/pxe-cz.sh http://${server}/clonezilla-scripts/pxe-cz.sh" ocs_repository="smb3://clonezilla:__CLONEZILLA_SMB_PASSWORD__@${server}/clonezilla/" echo_ocs_repository=no ocs_live_run="bash /tmp/pxe-cz.sh" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs pxe_cz_mode=${cz_mode} pxe_machine=${czmachine} pxe_end=1 || goto boot_failed
|
||||||
|
initrd ${base}/initrd.img || goto boot_failed
|
||||||
|
boot || goto boot_failed
|
||||||
|
|
||||||
|
# --- Clonezilla manual (interactive: USB, SSH, NFS or any share) ------------
|
||||||
|
:clonezilla
|
||||||
|
# 64-bit CPUs (cpuid extended bit 29 = long mode) get the current Clonezilla;
|
||||||
|
# older 32-bit-only CPUs (Pentium 4 without EM64T, Pentium M, early Atom:
|
||||||
|
# many XP-era PCs) get the last 32-bit release. If this iPXE has no cpuid
|
||||||
|
# command the check fails and the 32-bit build is used, which also runs
|
||||||
|
# on 64-bit CPUs.
|
||||||
|
cpuid --ext 29 && goto clonezilla64 || goto clonezilla32
|
||||||
|
|
||||||
|
:clonezilla64
|
||||||
|
set base http://${server}/clonezilla
|
||||||
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs || goto secureboot_warn
|
||||||
|
initrd ${base}/initrd.img || goto boot_failed
|
||||||
|
# Secure Boot ON: Clonezilla runs a Debian-signed kernel, started through
|
||||||
|
# Debian's Microsoft-signed shim (from the Clonezilla release, staged as
|
||||||
|
# clonezilla/shimx64.efi by prepare-boot-tools.sh), the same way Blancco
|
||||||
|
# uses Ubuntu's. Its modules (usb-storage, uas, exfat) are signed by the
|
||||||
|
# same build key, so USB drives work. Without the shim command (legacy
|
||||||
|
# BIOS, old iPXE) the line fails harmlessly and the kernel boots directly.
|
||||||
|
shim ${base}/shimx64.efi ||
|
||||||
|
boot || goto secureboot_warn
|
||||||
|
|
||||||
|
:clonezilla32
|
||||||
|
# Clonezilla 3.2.0-5 i686 (the last 32-bit release; later ones are amd64
|
||||||
|
# only), for legacy-BIOS XP-era PCs. A UEFI PC is always 64-bit and its
|
||||||
|
# firmware cannot start a 32-bit kernel, so UEFI goes to the 64-bit build.
|
||||||
|
iseq ${platform} efi && goto clonezilla64 ||
|
||||||
|
set base http://${server}/clonezilla-i686
|
||||||
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs || goto boot_failed
|
||||||
|
initrd ${base}/initrd.img || goto boot_failed
|
||||||
|
boot || goto boot_failed
|
||||||
|
|
||||||
|
:blancco
|
||||||
|
iseq ${platform} pcbios && goto blancco_bios || goto blancco_efi
|
||||||
|
|
||||||
|
:blancco_bios
|
||||||
|
echo Loading Blancco for legacy BIOS (canonical)...
|
||||||
|
kernel http://${server}/blancco/vmlinuz-bde-linux initrd=initramfs-bde-linux.img,config.img,intel-ucode.img,amd-ucode.img quiet loglevel=3 archisobasedir=arch archiso_http_srv=http://${server}/blancco/ copytoram=y cow_spacesize=50% memtest=00 vmalloc=400M ip=dhcp libata.allow_tpm=1 modprobe.blacklist=btusb rd.udev.timeout=10 systemd.battery-check=0 || goto blancco_bios_failed
|
||||||
|
initrd --name intel-ucode.img http://${server}/blancco/intel-ucode.img || goto blancco_bios_failed
|
||||||
|
initrd --name amd-ucode.img http://${server}/blancco/amd-ucode.img || goto blancco_bios_failed
|
||||||
|
initrd --name config.img http://${server}/blancco/config.img || goto blancco_bios_failed
|
||||||
|
initrd --name initramfs-bde-linux.img http://${server}/blancco/initramfs-bde-linux.img || goto blancco_bios_failed
|
||||||
|
boot || goto blancco_bios_failed
|
||||||
|
:blancco_bios_failed
|
||||||
|
echo
|
||||||
|
echo Blancco BIOS boot FAILED.
|
||||||
|
prompt Press any key to return to menu... && goto menu
|
||||||
|
|
||||||
|
:blancco_debug
|
||||||
|
iseq ${platform} pcbios && goto blancco_bios ||
|
||||||
|
echo Loading Blancco UEFI (debug: verbose, SSH for root)...
|
||||||
|
kernel http://${server}/blancco/vmlinuz-ubuntu-70 initrd=kexec-initrd-70.img ip=dhcp nomodeset vga=normal console=tty0 i915.modeset=0 nouveau.modeset=0 modprobe.blacklist=nouveau,nvidiafb,nvidia,nvidia_drm,mei_me,btusb,dcdbas libata.allow_tpm=1 rd.udev.timeout=10 efi=noruntime usbcore.autosuspend=-1 pcie_aspm=off e1000e.IntMode=0 e1000e.eee_enable=0 consoleblank=0 systemd.battery-check=0 systemd.mask=suspend.target,sleep.target,hibernate.target,hybrid-sleep.target no_console_suspend mem_sleep_default=s2idle blancco.debug=1 || goto blancco_efi_grub
|
||||||
|
initrd --name intel-ucode.img http://${server}/blancco/intel-ucode.img || goto blancco_efi_grub
|
||||||
|
initrd --name amd-ucode.img http://${server}/blancco/amd-ucode.img || goto blancco_efi_grub
|
||||||
|
initrd --name kexec-initrd-70.img http://${server}/blancco/kexec-initrd-70.img || goto blancco_efi_grub
|
||||||
|
# Secure Boot ON: iPXE 2.0 starts the Canonical-signed kernel through
|
||||||
|
# Ubuntu's Microsoft-signed shim. Older iPXE has no shim command: the
|
||||||
|
# line fails harmlessly and the kernel boots directly (Secure Boot off).
|
||||||
|
shim http://${server}/blancco/shimx64.efi ||
|
||||||
|
boot || goto blancco_efi_grub
|
||||||
|
|
||||||
|
:blancco_efi
|
||||||
|
echo Loading Blancco UEFI (direct kernel)...
|
||||||
|
kernel http://${server}/blancco/vmlinuz-ubuntu-70 initrd=kexec-initrd-70.img ip=dhcp quiet loglevel=3 nomodeset vga=normal console=tty0 i915.modeset=0 nouveau.modeset=0 modprobe.blacklist=nouveau,nvidiafb,nvidia,nvidia_drm,mei_me,btusb,dcdbas libata.allow_tpm=1 rd.udev.timeout=10 efi=noruntime usbcore.autosuspend=-1 pcie_aspm=off e1000e.IntMode=0 e1000e.eee_enable=0 consoleblank=0 systemd.battery-check=0 systemd.mask=suspend.target,sleep.target,hibernate.target,hybrid-sleep.target no_console_suspend mem_sleep_default=s2idle || goto blancco_efi_grub
|
||||||
|
initrd --name intel-ucode.img http://${server}/blancco/intel-ucode.img || goto blancco_efi_grub
|
||||||
|
initrd --name amd-ucode.img http://${server}/blancco/amd-ucode.img || goto blancco_efi_grub
|
||||||
|
initrd --name kexec-initrd-70.img http://${server}/blancco/kexec-initrd-70.img || goto blancco_efi_grub
|
||||||
|
# Secure Boot ON: iPXE 2.0 starts the Canonical-signed kernel through
|
||||||
|
# Ubuntu's Microsoft-signed shim. Older iPXE has no shim command: the
|
||||||
|
# line fails harmlessly and the kernel boots directly (Secure Boot off).
|
||||||
|
shim http://${server}/blancco/shimx64.efi ||
|
||||||
|
boot || goto blancco_efi_grub
|
||||||
|
:blancco_efi_grub
|
||||||
|
echo Direct kernel boot failed - trying GRUB chain fallback...
|
||||||
|
chain http://${server}/blancco/grubx64.efi || goto secureboot_warn
|
||||||
|
|
||||||
|
:boot_failed
|
||||||
|
echo
|
||||||
|
echo Download FAILED (see the error above). Check the PXE server and retry.
|
||||||
|
prompt Press any key to return to menu... && goto menu || goto menu
|
||||||
|
|
||||||
|
:secureboot_warn
|
||||||
|
echo
|
||||||
|
echo ======================================================
|
||||||
|
echo This option could not start with Secure Boot enabled.
|
||||||
|
echo
|
||||||
|
echo 1. Reboot this machine
|
||||||
|
echo 2. Press F2 / Del to enter BIOS Setup
|
||||||
|
echo 3. Disable Secure Boot
|
||||||
|
echo 4. Save and exit BIOS
|
||||||
|
echo 5. PXE boot again and select this option
|
||||||
|
echo
|
||||||
|
echo Re-enable Secure Boot after completing the task.
|
||||||
|
echo ======================================================
|
||||||
|
echo
|
||||||
|
prompt Press any key to return to menu...
|
||||||
|
goto menu
|
||||||
|
|
||||||
|
:reboot
|
||||||
|
reboot
|
||||||
|
|
||||||
|
:exit
|
||||||
|
exit
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Install-AcroReader.cmd - Install Adobe Acrobat Reader DC + DC update patch
|
|
||||||
REM
|
|
||||||
REM Two-step install: base MSI with enterprise transform, then DC update patch.
|
|
||||||
REM Same version as the GEA-Engineering image (specialize pass Orders 22-23).
|
|
||||||
REM Files staged by startnet.cmd from enrollment share to C:\PreInstall\installers\adobe\.
|
|
||||||
|
|
||||||
echo Installing Adobe Acrobat Reader DC...
|
|
||||||
msiexec /i "%~dp0AcroRead.msi" TRANSFORMS="%~dp0AcroRead.mst" /quiet /norestart
|
|
||||||
if %errorlevel% neq 0 if %errorlevel% neq 3010 (
|
|
||||||
echo Acrobat Reader MSI failed with exit code %errorlevel%
|
|
||||||
exit /b %errorlevel%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Applying Adobe Reader DC update patch...
|
|
||||||
msiexec /p "%~dp0AcroRdrDCUpd2500120531.msp" /quiet /norestart
|
|
||||||
if %errorlevel% neq 0 if %errorlevel% neq 3010 (
|
|
||||||
echo Acrobat Reader patch failed with exit code %errorlevel%
|
|
||||||
exit /b %errorlevel%
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Adobe Acrobat Reader DC installed successfully.
|
|
||||||
exit /b 0
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Install-KioskApp.cmd - Install Lobby Display or Dashboard kiosk app
|
|
||||||
REM
|
|
||||||
REM Reads C:\Enrollment\display-type.txt to determine which app to install.
|
|
||||||
REM Only runs on Display PCs (PCTypes filter in preinstall.json).
|
|
||||||
REM Both installers live alongside this script in the staged display\ subtree.
|
|
||||||
|
|
||||||
set "TYPEFILE=C:\Enrollment\display-type.txt"
|
|
||||||
|
|
||||||
if not exist "%TYPEFILE%" (
|
|
||||||
echo No display-type.txt found - skipping kiosk app install.
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
|
|
||||||
set /p DISPLAYTYPE=<"%TYPEFILE%"
|
|
||||||
|
|
||||||
if /i "%DISPLAYTYPE%"=="Lobby" (
|
|
||||||
set "INSTALLER=%~dp0GEAerospaceLobbyDisplaySetup.exe"
|
|
||||||
set "APPNAME=Lobby Display"
|
|
||||||
) else if /i "%DISPLAYTYPE%"=="Dashboard" (
|
|
||||||
set "INSTALLER=%~dp0GEAerospaceDashboardSetup.exe"
|
|
||||||
set "APPNAME=Dashboard"
|
|
||||||
) else (
|
|
||||||
echo Unknown display type: %DISPLAYTYPE%
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not exist "%INSTALLER%" (
|
|
||||||
echo %APPNAME% installer not found: %INSTALLER%
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo Installing %APPNAME%...
|
|
||||||
"%INSTALLER%" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /LOG="C:\Logs\PreInstall\%APPNAME%-install.log"
|
|
||||||
set EXITCODE=%errorlevel%
|
|
||||||
|
|
||||||
if %EXITCODE% equ 0 (
|
|
||||||
echo %APPNAME% installed successfully.
|
|
||||||
) else (
|
|
||||||
echo %APPNAME% exited with code %EXITCODE%.
|
|
||||||
)
|
|
||||||
|
|
||||||
exit /b %EXITCODE%
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Install-Drivers.cmd - Adds bundled INF driver packages to the Windows driver store
|
|
||||||
REM via pnputil. Runs from C:\PreInstall\installers\drivers\ where each vendor
|
|
||||||
REM subdir (prolific\, startech\) sits alongside this script. /subdirs walks into
|
|
||||||
REM them so one call covers every bundled driver.
|
|
||||||
REM
|
|
||||||
REM Writes C:\ProgramData\PXEDrivers\drivers-installed.marker on success so the
|
|
||||||
REM preinstall runner's File DetectionMethod can skip re-runs.
|
|
||||||
|
|
||||||
setlocal
|
|
||||||
|
|
||||||
set LOGDIR=C:\Logs\PreInstall
|
|
||||||
set LOG=%LOGDIR%\Install-Drivers.log
|
|
||||||
set MARKER_DIR=C:\ProgramData\PXEDrivers
|
|
||||||
set MARKER=%MARKER_DIR%\drivers-installed.marker
|
|
||||||
|
|
||||||
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
|
|
||||||
if not exist "%MARKER_DIR%" mkdir "%MARKER_DIR%"
|
|
||||||
|
|
||||||
echo ================================================================ >> "%LOG%"
|
|
||||||
echo [%date% %time%] Install-Drivers session start >> "%LOG%"
|
|
||||||
echo Source dir: %~dp0 >> "%LOG%"
|
|
||||||
echo ================================================================ >> "%LOG%"
|
|
||||||
|
|
||||||
REM pnputil /add-driver with /subdirs recurses into vendor subdirs. /install
|
|
||||||
REM binds the driver to any matching hardware present now; drivers without a
|
|
||||||
REM matching device still land in the driver store and bind on plug-in later.
|
|
||||||
pnputil.exe /add-driver "%~dp0*.inf" /subdirs /install >> "%LOG%" 2>&1
|
|
||||||
set RC=%ERRORLEVEL%
|
|
||||||
|
|
||||||
echo [%date% %time%] pnputil exit code: %RC% >> "%LOG%"
|
|
||||||
|
|
||||||
REM pnputil exit codes:
|
|
||||||
REM 0 success
|
|
||||||
REM 259 ERROR_NO_MORE_ITEMS - no INFs matched (treat as failure)
|
|
||||||
REM 3010 at least one driver installed, reboot recommended (treat as success)
|
|
||||||
if "%RC%"=="0" goto :ok
|
|
||||||
if "%RC%"=="3010" goto :ok
|
|
||||||
|
|
||||||
echo [%date% %time%] FAILED (exit %RC%) >> "%LOG%"
|
|
||||||
exit /b %RC%
|
|
||||||
|
|
||||||
:ok
|
|
||||||
echo [%date% %time%] SUCCESS >> "%LOG%"
|
|
||||||
echo installed %date% %time% > "%MARKER%"
|
|
||||||
exit /b 0
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# OpenText HostExplorer ShopFloor - install scripts
|
|
||||||
|
|
||||||
These three files are git-tracked snapshots of what lives at runtime
|
|
||||||
in `/home/camp/pxe-images/main/dependencies/opentext/`. The full
|
|
||||||
runtime tree also contains the bundled installer binaries (~106 MB
|
|
||||||
total) which are intentionally NOT in git:
|
|
||||||
|
|
||||||
```
|
|
||||||
OpenTextHostExplorer15x64.msi ~16 MB
|
|
||||||
OpenTextHostExplorer15x64.cab ~86 MB
|
|
||||||
OpenTextHostExplorer15x64_ServicePack1.msp ~4.4 MB
|
|
||||||
ShopFloorx64.mst ~20 KB
|
|
||||||
Profile/ 5 .hep connection profiles
|
|
||||||
Accessories/EB/ 6 .ebs/.ebx macros
|
|
||||||
HostExplorer/Keymap/ 2 .kmv keymaps
|
|
||||||
HostExplorer/Menu/ 2 .hmv menu layouts
|
|
||||||
W10shortcuts/ 4 .lnk public-desktop shortcuts
|
|
||||||
```
|
|
||||||
|
|
||||||
The canonical source for everything (scripts AND binaries) is
|
|
||||||
`/home/camp/pxe-images/main/dependencies/opentext/`. The files in
|
|
||||||
this dir are mirrors that exist so changes to the install logic and
|
|
||||||
the version stamp end up in git history. When editing:
|
|
||||||
|
|
||||||
1. Edit `/home/camp/pxe-images/main/dependencies/opentext/Setup-OpenText.ps1`
|
|
||||||
(or `.cmd` / `version.txt`)
|
|
||||||
2. Re-run `bash playbook/sync-preinstall.sh` to push the runtime tree
|
|
||||||
to the live PXE server
|
|
||||||
3. `cp` the changed file(s) into `playbook/preinstall/opentext/` here
|
|
||||||
so git picks up the change
|
|
||||||
4. Re-upload the changed file(s) to Azure Blob at
|
|
||||||
`prod/main/dependencies/opentext/<filename>`
|
|
||||||
|
|
||||||
To bump OpenText version: edit `version.txt` only - the value flows
|
|
||||||
through to Setup-OpenText.ps1 (which reads it at runtime), to
|
|
||||||
Install-OpenText.ps1 (the DSC wrapper, which downloads version.txt
|
|
||||||
first as a cheap detection check), and to the registry marker at
|
|
||||||
`HKLM:\SOFTWARE\GE\OpenText\Installed`.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Setup-OpenText.cmd - tiny launcher for Setup-OpenText.ps1
|
|
||||||
REM
|
|
||||||
REM The PXE PreInstall runner only invokes Type:MSI or Type:EXE entries from
|
|
||||||
REM preinstall.json. This .cmd lives next to Setup-OpenText.ps1 in the staged
|
|
||||||
REM opentext\ subtree and just hands off to PowerShell with bypass policy.
|
|
||||||
REM Both files end up at C:\PreInstall\installers\opentext\ via xcopy /E from
|
|
||||||
REM the WinPE staging step in startnet.cmd.
|
|
||||||
|
|
||||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0Setup-OpenText.ps1"
|
|
||||||
exit /b %errorlevel%
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
# Setup-OpenText.ps1 - OpenText HostExplorer 15 SP1 ShopFloor installer + profile
|
|
||||||
# deployment, callable from both PXE PreInstall and Intune DSC paths.
|
|
||||||
#
|
|
||||||
# WHY THIS EXISTS:
|
|
||||||
# The vendor-supplied OpenText.exe (Inno Setup wrapper built by WJDT) bundles
|
|
||||||
# the install steps but its [Files] section deploys per-user content to
|
|
||||||
# {userappdata} - which resolves to SYSTEM's profile under DSC and to a single
|
|
||||||
# user under PreInstall. As a result the operator (logging in via Azure AD)
|
|
||||||
# never sees the profiles, keymaps, menus, or macros - only the installed
|
|
||||||
# binaries. This script replaces OpenText.exe entirely, doing the same install
|
|
||||||
# steps via direct msiexec calls AND fanning the per-user content out to:
|
|
||||||
# - %ProgramData%\Hummingbird\Connectivity\15.00\Shared\
|
|
||||||
# - C:\Users\Default\AppData\Roaming\Hummingbird\Connectivity\15.00\
|
|
||||||
# - Each existing user profile under C:\Users\
|
|
||||||
#
|
|
||||||
# INVOKED BY:
|
|
||||||
# - PreInstall: Setup-OpenText.cmd wrapper (because the runner only knows MSI/EXE)
|
|
||||||
# - DSC: Install-OpenText.ps1 downloads the bundled tree from blob, then
|
|
||||||
# invokes this script with -SourceDir <temp dir>
|
|
||||||
#
|
|
||||||
# DETECTION:
|
|
||||||
# Skips if HKLM:\SOFTWARE\GE\OpenText\Installed = $expectedVersion. Marker is
|
|
||||||
# written at the end of a successful run, so the runner / DSC wrapper can
|
|
||||||
# no-op on subsequent invocations.
|
|
||||||
|
|
||||||
[CmdletBinding()]
|
|
||||||
param(
|
|
||||||
# Override when invoked from a temp dir (DSC path) where the bundled files were
|
|
||||||
# just downloaded. Defaults to $PSScriptRoot, but resolved INSIDE the script body
|
|
||||||
# below - PowerShell evaluates `param([string]$X = $PSScriptRoot)` at parameter-
|
|
||||||
# binding time, when $PSScriptRoot may not yet be populated, so the default winds
|
|
||||||
# up as an empty string. Setting it in the body works because $PSScriptRoot is
|
|
||||||
# reliably populated by then.
|
|
||||||
[string]$SourceDir
|
|
||||||
)
|
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
|
||||||
|
|
||||||
if (-not $SourceDir) {
|
|
||||||
$SourceDir = $PSScriptRoot
|
|
||||||
}
|
|
||||||
|
|
||||||
# Normalize $SourceDir to a canonical absolute path. The CMD shim passes
|
|
||||||
# "%~dp0..\apps\opentext" which embeds a literal "..". msiexec / the Windows
|
|
||||||
# Installer service fail to open the package with that unresolved segment
|
|
||||||
# (exit 1619, ERROR_INSTALL_PACKAGE_OPEN_FAILED), even though every other
|
|
||||||
# .NET / PowerShell API resolves it fine. Resolve-Path collapses ".." into
|
|
||||||
# a clean drive-rooted path before any msiexec invocation.
|
|
||||||
if (Test-Path -LiteralPath $SourceDir) {
|
|
||||||
try { $SourceDir = (Resolve-Path -LiteralPath $SourceDir).ProviderPath } catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Inline site-config reader (this script runs from C:\PreInstall\installers\opentext\,
|
|
||||||
# NOT from C:\Enrollment\shopfloor-setup\, so it can't dot-source Get-PCProfile.ps1) ---
|
|
||||||
function Get-SiteConfig {
|
|
||||||
$configPath = 'C:\Enrollment\site-config.json'
|
|
||||||
if (-not (Test-Path $configPath)) { return $null }
|
|
||||||
try {
|
|
||||||
return Get-Content $configPath -Raw | ConvertFrom-Json
|
|
||||||
} catch {
|
|
||||||
return $null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Logging (set up FIRST so any startup error - missing version.txt, broken
|
|
||||||
# bundled file, etc. - lands in the log file instead of disappearing into the
|
|
||||||
# runner's stdout void) ---
|
|
||||||
$logDir = 'C:\Logs\PreInstall'
|
|
||||||
$logFile = Join-Path $logDir 'Setup-OpenText.log'
|
|
||||||
$msiLog = Join-Path $logDir 'Setup-OpenText-msi.log'
|
|
||||||
$mspLog = Join-Path $logDir 'Setup-OpenText-msp.log'
|
|
||||||
|
|
||||||
if (-not (Test-Path $logDir)) {
|
|
||||||
New-Item -Path $logDir -ItemType Directory -Force | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-SetupLog {
|
|
||||||
param([string]$Message)
|
|
||||||
$line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message"
|
|
||||||
Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
|
|
||||||
Write-Host $line
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-SetupLog "================================================================"
|
|
||||||
Write-SetupLog "=== Setup-OpenText.ps1 starting ==="
|
|
||||||
Write-SetupLog "================================================================"
|
|
||||||
Write-SetupLog "SourceDir: $SourceDir"
|
|
||||||
Write-SetupLog "PSScriptRoot: $PSScriptRoot"
|
|
||||||
Write-SetupLog "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)"
|
|
||||||
|
|
||||||
# --- Config ---
|
|
||||||
# Version is read from version.txt next to this script. ONE source of truth: bumping
|
|
||||||
# version.txt is the only edit needed when shipping a new OpenText build. Setup-
|
|
||||||
# OpenText.ps1 itself, Install-OpenText.ps1 (DSC wrapper), and the registry marker
|
|
||||||
# all derive their notion of "expected version" from this file.
|
|
||||||
$versionFile = Join-Path $SourceDir 'version.txt'
|
|
||||||
Write-SetupLog "Looking for version.txt at: $versionFile"
|
|
||||||
if (-not (Test-Path $versionFile)) {
|
|
||||||
Write-SetupLog "ERROR: version.txt not found in $SourceDir - cannot determine expected version."
|
|
||||||
Write-SetupLog "Directory listing of $SourceDir :"
|
|
||||||
if (Test-Path $SourceDir) {
|
|
||||||
Get-ChildItem $SourceDir -ErrorAction SilentlyContinue | ForEach-Object {
|
|
||||||
Write-SetupLog " $($_.Name) $($_.Length) bytes"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-SetupLog " (SourceDir does not exist)"
|
|
||||||
}
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
$expectedVersion = (Get-Content -Path $versionFile -Raw -ErrorAction Stop).Trim()
|
|
||||||
if (-not $expectedVersion) {
|
|
||||||
Write-SetupLog "ERROR: version.txt at $versionFile is empty."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Write-SetupLog "Expected version (from version.txt): $expectedVersion"
|
|
||||||
|
|
||||||
# --- Detection: skip if already deployed at expected version ---
|
|
||||||
$markerKey = 'HKLM:\SOFTWARE\GE\OpenText'
|
|
||||||
$markerVal = 'Installed'
|
|
||||||
if (Test-Path $markerKey) {
|
|
||||||
$installed = (Get-ItemProperty -Path $markerKey -Name $markerVal -ErrorAction SilentlyContinue).$markerVal
|
|
||||||
if ($installed -eq $expectedVersion) {
|
|
||||||
Write-SetupLog "OpenText $expectedVersion already deployed (marker present) - skipping."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
Write-SetupLog "OpenText marker mismatch (found '$installed', expected '$expectedVersion') - re-deploying."
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-SetupLog "OpenText marker not present - first install."
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Verify bundled files are where we expect ---
|
|
||||||
$msiPath = Join-Path $SourceDir 'OpenTextHostExplorer15x64.msi'
|
|
||||||
$cabPath = Join-Path $SourceDir 'OpenTextHostExplorer15x64.cab'
|
|
||||||
$mspPath = Join-Path $SourceDir 'OpenTextHostExplorer15x64_ServicePack1.msp'
|
|
||||||
$mstPath = Join-Path $SourceDir 'ShopFloorx64.mst'
|
|
||||||
|
|
||||||
foreach ($f in @($msiPath, $cabPath, $mspPath, $mstPath)) {
|
|
||||||
if (-not (Test-Path $f)) {
|
|
||||||
Write-SetupLog "ERROR: required file not found: $f"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 1: Install the base MSI with the ShopFloor transform ---
|
|
||||||
# NOTE: We deliberately do NOT pass REBOOT=ReallySuppress here even though we do
|
|
||||||
# for the VC++ MSIs. OpenText HostExplorer installs shell extensions that hook
|
|
||||||
# explorer.exe, and the MSI uses Restart Manager to ask explorer to close so the
|
|
||||||
# in-use shell DLLs can be replaced. With REBOOT=ReallySuppress, RM closes
|
|
||||||
# explorer.exe but interprets "restart explorer" as a reboot action and refuses
|
|
||||||
# to relaunch it - leaving the user without a desktop. /norestart on its own
|
|
||||||
# prevents the actual Windows reboot but lets RM cleanly close-and-relaunch
|
|
||||||
# explorer mid-install. msiexec still returns 3010 ("reboot would be needed"),
|
|
||||||
# which we treat as success below.
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "Step 1: Installing OpenTextHostExplorer15x64.msi with ShopFloorx64.mst..."
|
|
||||||
if (Test-Path $msiLog) { Remove-Item $msiLog -Force -ErrorAction SilentlyContinue }
|
|
||||||
|
|
||||||
$msiArgs = "/i `"$msiPath`" TRANSFORMS=`"$mstPath`" /qn /norestart /L*v `"$msiLog`""
|
|
||||||
Write-SetupLog " msiexec.exe $msiArgs"
|
|
||||||
|
|
||||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
||||||
$psi.FileName = 'msiexec.exe'
|
|
||||||
$psi.Arguments = $msiArgs
|
|
||||||
$psi.UseShellExecute = $false
|
|
||||||
$psi.CreateNoWindow = $true
|
|
||||||
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
||||||
$proc.WaitForExit()
|
|
||||||
$msiExit = $proc.ExitCode
|
|
||||||
Write-SetupLog " msiexec exit code: $msiExit"
|
|
||||||
|
|
||||||
if ($msiExit -ne 0 -and $msiExit -ne 3010) {
|
|
||||||
Write-SetupLog "ERROR: base MSI install failed (exit $msiExit). See $msiLog"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
if ($msiExit -eq 3010) {
|
|
||||||
Write-SetupLog " (3010 = reboot needed but suppressed)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 2: Apply Service Pack 1 patch ---
|
|
||||||
# Same Restart Manager rationale as Step 1 - skip REBOOT=ReallySuppress so RM
|
|
||||||
# can relaunch explorer.exe after replacing the patched shell extension DLLs.
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "Step 2: Applying SP1 patch..."
|
|
||||||
if (Test-Path $mspLog) { Remove-Item $mspLog -Force -ErrorAction SilentlyContinue }
|
|
||||||
|
|
||||||
$mspArgs = "/p `"$mspPath`" /qn /norestart /L*v `"$mspLog`""
|
|
||||||
Write-SetupLog " msiexec.exe $mspArgs"
|
|
||||||
|
|
||||||
$psi.Arguments = $mspArgs
|
|
||||||
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
||||||
$proc.WaitForExit()
|
|
||||||
$mspExit = $proc.ExitCode
|
|
||||||
Write-SetupLog " msiexec exit code: $mspExit"
|
|
||||||
|
|
||||||
if ($mspExit -ne 0 -and $mspExit -ne 3010) {
|
|
||||||
Write-SetupLog "ERROR: SP1 patch failed (exit $mspExit). See $mspLog"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 3: Deploy profiles, keymaps, menus, accessories ---
|
|
||||||
# Source layout (bundled with this script):
|
|
||||||
# $SourceDir\Profile\*.hep
|
|
||||||
# $SourceDir\Accessories\EB\*.eb*
|
|
||||||
# $SourceDir\HostExplorer\Keymap\*.kmv
|
|
||||||
# $SourceDir\HostExplorer\Menu\*.hmv
|
|
||||||
#
|
|
||||||
# Target layouts (Hummingbird-canonical):
|
|
||||||
# %ProgramData%\Hummingbird\Connectivity\15.00\Shared\Profile\
|
|
||||||
# %ProgramData%\Hummingbird\Connectivity\15.00\Shared\Accessories\EB\
|
|
||||||
# %ProgramData%\Hummingbird\Connectivity\15.00\Shared\HostExplorer\Keymap\
|
|
||||||
# %ProgramData%\Hummingbird\Connectivity\15.00\Shared\HostExplorer\Menu\
|
|
||||||
# <user>\AppData\Roaming\Hummingbird\Connectivity\15.00\Profile\
|
|
||||||
# <user>\AppData\Roaming\Hummingbird\Connectivity\15.00\Accessories\EB\
|
|
||||||
# <user>\AppData\Roaming\Hummingbird\Connectivity\15.00\HostExplorer\Keymap\
|
|
||||||
# <user>\AppData\Roaming\Hummingbird\Connectivity\15.00\HostExplorer\Menu\
|
|
||||||
#
|
|
||||||
# We deploy to ProgramData\Shared (system-wide fallback), Default User (template
|
|
||||||
# inherited by every NEW user profile), and every existing user profile (so
|
|
||||||
# already-created accounts like SupportUser get them immediately).
|
|
||||||
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "Step 3: Deploying profiles/keymaps/menus/macros..."
|
|
||||||
|
|
||||||
# --- Resolve exclude lists from site-config.json (falls back to West Jefferson defaults) ---
|
|
||||||
$siteConfig = Get-SiteConfig
|
|
||||||
$profileExcludes = if ($siteConfig -and $siteConfig.opentext -and $siteConfig.opentext.excludeProfiles) {
|
|
||||||
@($siteConfig.opentext.excludeProfiles)
|
|
||||||
} else {
|
|
||||||
@('WJ_Office.hep', 'IBM_qks.hep', 'mmcs.hep') # West Jefferson defaults
|
|
||||||
}
|
|
||||||
$shortcutExcludes = if ($siteConfig -and $siteConfig.opentext -and $siteConfig.opentext.excludeShortcuts) {
|
|
||||||
@($siteConfig.opentext.excludeShortcuts)
|
|
||||||
} else {
|
|
||||||
@('WJ_Office.lnk', 'IBM_qks.lnk', 'mmcs.lnk')
|
|
||||||
}
|
|
||||||
if ($siteConfig) {
|
|
||||||
Write-SetupLog "Site config loaded - profile excludes: $($profileExcludes -join ', ')"
|
|
||||||
Write-SetupLog "Site config loaded - shortcut excludes: $($shortcutExcludes -join ', ')"
|
|
||||||
} else {
|
|
||||||
Write-SetupLog "No site-config.json found - using West Jefferson defaults for excludes"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map of source subdir -> destination subdir relative to the Hummingbird root.
|
|
||||||
# Optional Exclude list drops specific filenames from both the source-to-dest
|
|
||||||
# copy AND from the destination if they were left over from a prior install.
|
|
||||||
$contentMap = @(
|
|
||||||
@{
|
|
||||||
Src = 'Profile'
|
|
||||||
Dst = 'Profile'
|
|
||||||
Exclude = $profileExcludes
|
|
||||||
}
|
|
||||||
@{ Src = 'Accessories\EB'; Dst = 'Accessories\EB' }
|
|
||||||
@{ Src = 'HostExplorer\Keymap'; Dst = 'HostExplorer\Keymap' }
|
|
||||||
@{ Src = 'HostExplorer\Menu'; Dst = 'HostExplorer\Menu' }
|
|
||||||
)
|
|
||||||
|
|
||||||
function Copy-HummingbirdContent {
|
|
||||||
param(
|
|
||||||
[string]$RootDst, # Hummingbird root, e.g. C:\ProgramData\Hummingbird\Connectivity\15.00\Shared
|
|
||||||
[string]$Label
|
|
||||||
)
|
|
||||||
foreach ($entry in $contentMap) {
|
|
||||||
$srcPath = Join-Path $SourceDir $entry.Src
|
|
||||||
if (-not (Test-Path $srcPath)) { continue }
|
|
||||||
$dstPath = Join-Path $RootDst $entry.Dst
|
|
||||||
New-Item -Path $dstPath -ItemType Directory -Force | Out-Null
|
|
||||||
|
|
||||||
# Remove any previously-deployed excluded files from the destination
|
|
||||||
# - handles the case where a PC got them from an older install.
|
|
||||||
if ($entry.Exclude) {
|
|
||||||
foreach ($name in $entry.Exclude) {
|
|
||||||
$stale = Join-Path $dstPath $name
|
|
||||||
if (Test-Path -LiteralPath $stale) {
|
|
||||||
try {
|
|
||||||
Remove-Item -LiteralPath $stale -Force -ErrorAction Stop
|
|
||||||
Write-SetupLog " $Label : removed stale $name"
|
|
||||||
} catch {
|
|
||||||
Write-SetupLog " $Label : failed to remove $stale : $_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$files = Get-ChildItem -Path $srcPath -File -ErrorAction SilentlyContinue
|
|
||||||
if ($entry.Exclude) {
|
|
||||||
$files = @($files | Where-Object { $entry.Exclude -notcontains $_.Name })
|
|
||||||
}
|
|
||||||
foreach ($f in $files) {
|
|
||||||
Copy-Item -Path $f.FullName -Destination $dstPath -Force
|
|
||||||
}
|
|
||||||
Write-SetupLog " $Label : $($entry.Src) -> $dstPath ($($files.Count) files)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 3a. ProgramData Shared
|
|
||||||
$sharedRoot = Join-Path $env:ProgramData 'Hummingbird\Connectivity\15.00\Shared'
|
|
||||||
Copy-HummingbirdContent -RootDst $sharedRoot -Label 'Shared'
|
|
||||||
|
|
||||||
# 3b. Default User (template for new user profiles)
|
|
||||||
$defaultUserRoot = 'C:\Users\Default\AppData\Roaming\Hummingbird\Connectivity\15.00'
|
|
||||||
Copy-HummingbirdContent -RootDst $defaultUserRoot -Label 'Default User'
|
|
||||||
|
|
||||||
# 3c. Every existing user profile under C:\Users\
|
|
||||||
$skipNames = @('Default', 'Default User', 'Public', 'defaultuser0', 'All Users', 'WDAGUtilityAccount')
|
|
||||||
$userDirs = Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue |
|
|
||||||
Where-Object { $skipNames -notcontains $_.Name -and (Test-Path "$($_.FullName)\AppData\Roaming") }
|
|
||||||
|
|
||||||
foreach ($u in $userDirs) {
|
|
||||||
$userRoot = Join-Path $u.FullName 'AppData\Roaming\Hummingbird\Connectivity\15.00'
|
|
||||||
Copy-HummingbirdContent -RootDst $userRoot -Label $u.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 4: Public Desktop shortcuts ---
|
|
||||||
# Uses $shortcutExcludes (resolved from site-config.json above) to skip
|
|
||||||
# deploying unwanted .lnk files AND remove any a prior install left behind.
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "Step 4: Deploying public desktop shortcuts..."
|
|
||||||
$shortcutSrc = Join-Path $SourceDir 'W10shortcuts'
|
|
||||||
$publicDesktop = 'C:\Users\Public\Desktop'
|
|
||||||
|
|
||||||
# Clean up stale copies from prior installs first
|
|
||||||
foreach ($name in $shortcutExcludes) {
|
|
||||||
$stale = Join-Path $publicDesktop $name
|
|
||||||
if (Test-Path -LiteralPath $stale) {
|
|
||||||
try {
|
|
||||||
Remove-Item -LiteralPath $stale -Force -ErrorAction Stop
|
|
||||||
Write-SetupLog " removed stale desktop shortcut: $name"
|
|
||||||
} catch {
|
|
||||||
Write-SetupLog " failed to remove stale $stale : $_"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Test-Path $shortcutSrc) {
|
|
||||||
$lnkFiles = Get-ChildItem -Path $shortcutSrc -Filter '*.lnk' -File -ErrorAction SilentlyContinue
|
|
||||||
foreach ($l in $lnkFiles) {
|
|
||||||
if ($shortcutExcludes -contains $l.Name) {
|
|
||||||
Write-SetupLog " skip (excluded): $($l.Name)"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
Copy-Item -Path $l.FullName -Destination $publicDesktop -Force
|
|
||||||
Write-SetupLog " $($l.Name) -> $publicDesktop"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 5: Write registry marker ---
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "Step 5: Writing registry marker..."
|
|
||||||
if (-not (Test-Path $markerKey)) {
|
|
||||||
New-Item -Path $markerKey -Force | Out-Null
|
|
||||||
}
|
|
||||||
Set-ItemProperty -Path $markerKey -Name $markerVal -Value $expectedVersion -Force
|
|
||||||
Set-ItemProperty -Path $markerKey -Name 'InstalledAt' -Value (Get-Date -Format 'o') -Force
|
|
||||||
|
|
||||||
Write-SetupLog ""
|
|
||||||
Write-SetupLog "================================================================"
|
|
||||||
Write-SetupLog "=== OpenText HostExplorer ShopFloor $expectedVersion deployed ==="
|
|
||||||
Write-SetupLog "================================================================"
|
|
||||||
exit 0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
15.0.SP1.2
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
@echo off
|
|
||||||
REM Install-Oracle11r2.cmd
|
|
||||||
REM Expands the GE Oracle Client 11.2 Administrator zip to a temp dir and
|
|
||||||
REM runs Oracle Universal Installer silently with the GE-customized
|
|
||||||
REM response file.
|
|
||||||
REM
|
|
||||||
REM Accepted zip locations (first hit wins):
|
|
||||||
REM %~dp0Oracle_OracleDatabase_11r2_V03.zip (preinstall flat)
|
|
||||||
REM %~dp0..\apps\Oracle_OracleDatabase_11r2_V03.zip (SFLD share, enforcer)
|
|
||||||
REM
|
|
||||||
REM Called by:
|
|
||||||
REM - preinstall runner (Type=EXE, image build time)
|
|
||||||
REM - Install-FromManifest.ps1 (Type=CMD, runtime enforcer)
|
|
||||||
REM
|
|
||||||
REM Oracle 11.2 OUI exit codes worth knowing:
|
|
||||||
REM 0 = success
|
|
||||||
REM 3 = success but with warnings
|
|
||||||
REM 1 = general failure
|
|
||||||
REM 6 = silent install requested but missing / bad response file
|
|
||||||
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
set "LOG=C:\Logs\OracleClient\install.log"
|
|
||||||
if not exist "C:\Logs\OracleClient" mkdir "C:\Logs\OracleClient"
|
|
||||||
|
|
||||||
REM Emit a datestamp
|
|
||||||
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value 2^>nul ^| find "="') do set LDT=%%I
|
|
||||||
set "STAMP=!LDT:~0,14!"
|
|
||||||
|
|
||||||
echo [%STAMP%] Install-Oracle11r2.cmd starting >> "%LOG%"
|
|
||||||
|
|
||||||
set "SRC_ZIP=%~dp0Oracle_OracleDatabase_11r2_V03.zip"
|
|
||||||
if not exist "%SRC_ZIP%" set "SRC_ZIP=%~dp0..\apps\Oracle_OracleDatabase_11r2_V03.zip"
|
|
||||||
set "STAGING=%TEMP%\oracle-11r2-install"
|
|
||||||
set "CLIENT_DIR=%STAGING%\Oracle_OracleDatabase_11r2_V03\client"
|
|
||||||
set "RSP=%CLIENT_DIR%\response\ge_client_install.rsp"
|
|
||||||
|
|
||||||
if not exist "%SRC_ZIP%" (
|
|
||||||
echo [%STAMP%] ERROR: zip not found at %SRC_ZIP% >> "%LOG%"
|
|
||||||
echo ERROR: zip not found at %SRC_ZIP%
|
|
||||||
exit /b 2
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [%STAMP%] Expanding %SRC_ZIP% to %STAGING% >> "%LOG%"
|
|
||||||
if exist "%STAGING%" rmdir /s /q "%STAGING%" >nul 2>&1
|
|
||||||
mkdir "%STAGING%"
|
|
||||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
|
|
||||||
"try { Expand-Archive -Path '%SRC_ZIP%' -DestinationPath '%STAGING%' -Force -ErrorAction Stop; exit 0 } catch { Write-Error $_; exit 1 }" ^
|
|
||||||
>> "%LOG%" 2>&1
|
|
||||||
|
|
||||||
if not exist "%CLIENT_DIR%\setup.exe" (
|
|
||||||
echo [%STAMP%] ERROR: expanded setup.exe not found at %CLIENT_DIR%\setup.exe >> "%LOG%"
|
|
||||||
exit /b 3
|
|
||||||
)
|
|
||||||
|
|
||||||
if not exist "%RSP%" (
|
|
||||||
echo [%STAMP%] ERROR: response file missing at %RSP% >> "%LOG%"
|
|
||||||
exit /b 4
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [%STAMP%] Running OUI silent install (this takes 2-8 minutes) >> "%LOG%"
|
|
||||||
"%CLIENT_DIR%\setup.exe" -silent -waitforcompletion -nowait ^
|
|
||||||
-ignoreSysPrereqs ^
|
|
||||||
-responseFile "%RSP%" >> "%LOG%" 2>&1
|
|
||||||
set RC=%ERRORLEVEL%
|
|
||||||
|
|
||||||
echo [%STAMP%] OUI exit code: %RC% >> "%LOG%"
|
|
||||||
|
|
||||||
REM --- Drop GE-customized tnsnames.ora / sqlnet.ora / ldap.ora into ORACLE_HOME.
|
|
||||||
REM The zip ships these in client\ora\ but Oracle's setup.exe does not consume
|
|
||||||
REM them - that copy is the GE Wise wrapper's job, which we bypass. Without
|
|
||||||
REM these, every Oracle-dependent app (eDNC/UDC/NTLARS/CMM tooling) fails with
|
|
||||||
REM ORA-12154 "TNS could not resolve the connect identifier specified".
|
|
||||||
REM
|
|
||||||
REM Per-file override: if %~dp0<name>.ora exists (deployed alongside this
|
|
||||||
REM wrapper on the PXE server / SFLD share), use it instead of the bundled
|
|
||||||
REM copy. Lets us push an updated tnsnames.ora without repackaging the zip.
|
|
||||||
REM
|
|
||||||
REM Only run when OUI succeeded (RC 0 or 3). On failure ORACLE_HOME may not
|
|
||||||
REM be fully populated and the copy targets may not exist.
|
|
||||||
if %RC%==0 goto :do_ora_copy
|
|
||||||
if %RC%==3 goto :do_ora_copy
|
|
||||||
goto :skip_ora_copy
|
|
||||||
|
|
||||||
:do_ora_copy
|
|
||||||
set "ORA_DST=C:\Apps\product\11.2.0\client_1\network\admin"
|
|
||||||
if not exist "%ORA_DST%" (
|
|
||||||
echo [%STAMP%] WARN: %ORA_DST% does not exist after OUI - skipping .ora copy >> "%LOG%"
|
|
||||||
goto :skip_ora_copy
|
|
||||||
)
|
|
||||||
for %%F in (tnsnames.ora sqlnet.ora ldap.ora) do (
|
|
||||||
set "ORA_SRC=%~dp0%%F"
|
|
||||||
if not exist "!ORA_SRC!" set "ORA_SRC=%CLIENT_DIR%\ora\%%F"
|
|
||||||
if exist "!ORA_SRC!" (
|
|
||||||
echo [%STAMP%] Copying %%F from !ORA_SRC! to %ORA_DST%\%%F >> "%LOG%"
|
|
||||||
copy /Y "!ORA_SRC!" "%ORA_DST%\%%F" >> "%LOG%" 2>&1
|
|
||||||
) else (
|
|
||||||
echo [%STAMP%] WARN: %%F not found in either %~dp0 or %CLIENT_DIR%\ora >> "%LOG%"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
:skip_ora_copy
|
|
||||||
|
|
||||||
REM Cleanup staging dir to reclaim ~1.5 GB - OUI copies everything to ORACLE_HOME
|
|
||||||
echo [%STAMP%] Cleaning up staging dir >> "%LOG%"
|
|
||||||
rmdir /s /q "%STAGING%" >nul 2>&1
|
|
||||||
|
|
||||||
REM OUI returns 0 for success, 3 for success-with-warnings. Treat both as OK.
|
|
||||||
if %RC%==3 (
|
|
||||||
echo [%STAMP%] OUI reported warnings but install succeeded - returning 0 >> "%LOG%"
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
|
|
||||||
exit /b %RC%
|
|
||||||
13
playbook/preinstall/postppkg.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"Version": "1.0",
|
||||||
|
"_comment": "Site app list, installed after the post-PPKG reboot (once, as SYSTEM). Empty by default. Add one object per app to Applications and put its installer under pre-install/installers/<folder>/ on the PXE server (enrollment share). See docs/kb/How_To_Deploy_PXE_Server_New_Site.html, 'Adding shopfloor apps'.",
|
||||||
|
"_example": {
|
||||||
|
"Name": "Example App 1.2",
|
||||||
|
"Installer": "exampleapp\\ExampleApp.msi",
|
||||||
|
"Type": "MSI",
|
||||||
|
"InstallArgs": "/qn /norestart",
|
||||||
|
"DetectionMethod": "Registry",
|
||||||
|
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{00000000-0000-0000-0000-000000000000}"
|
||||||
|
},
|
||||||
|
"Applications": []
|
||||||
|
}
|
||||||
@@ -1,200 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Version": "1.0",
|
"Version": "1.0",
|
||||||
"Site": "West Jefferson",
|
"_comment": "Site app list, installed during setup (same logon as the PPKG, before the post-PPKG reboot). Empty by default. Add one object per app to Applications and put its installer under pre-install/installers/<folder>/ on the PXE server (enrollment share). See docs/kb/How_To_Deploy_PXE_Server_New_Site.html, 'Adding shopfloor apps'.",
|
||||||
"Applications": [
|
"_example": {
|
||||||
{
|
"Name": "Example App 1.2",
|
||||||
"_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.",
|
"Installer": "exampleapp\\ExampleApp.msi",
|
||||||
"Name": "Oracle Client 11.2",
|
"Type": "MSI",
|
||||||
"Installer": "oracle\\Install-Oracle11r2.cmd",
|
"InstallArgs": "/qn /norestart",
|
||||||
"Type": "EXE",
|
"DetectionMethod": "Registry",
|
||||||
"InstallArgs": "",
|
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{00000000-0000-0000-0000-000000000000}"
|
||||||
"LogFile": "C:\\Logs\\OracleClient\\install.log",
|
},
|
||||||
"DetectionMethod": "Registry",
|
"Applications": []
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Oracle\\KEY_OraClient11g_home1",
|
|
||||||
"DetectionName": "ORACLE_HOME_NAME",
|
|
||||||
"DetectionValue": "OraClient11g_home1",
|
|
||||||
"PCTypes": ["Standard", "CMM", "Genspect", "Keyence", "WaxAndTrace", "Display"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2008 SP1 x86 - the bootstrapper (vcredist2008_x86.exe) ignores /norestart and triggers an immediate Windows reboot when files are in use (per Aaron Stebner's MSDN docs). Fix: install the extracted vc_red.msi directly with REBOOT=ReallySuppress, which IS hard-honored by Windows Installer. msiexec may return 3010 (would-have-rebooted-but-suppressed) but won't actually reboot. cab name 'vc_red.cab' is hardcoded in the MSI's Media table - do not rename.",
|
|
||||||
"Name": "VC++ Redistributable 2008 x86",
|
|
||||||
"Installer": "vcredist/2008/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9BE518E6-ECC6-35A9-88E4-87755C07200F}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2010 x86 - same fix as 2008. Bootstrapper ignores /norestart; extracted MSI with REBOOT=ReallySuppress does not.",
|
|
||||||
"Name": "VC++ Redistributable 2010 x86",
|
|
||||||
"Installer": "vcredist/2010/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2012 x86 Minimum Runtime - extracted from vcredist2012_x86.exe Burn bundle. Same REBOOT=ReallySuppress fix.",
|
|
||||||
"Name": "VC++ Redistributable 2012 x86 (Minimum)",
|
|
||||||
"Installer": "vcredist/2012-min/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BD95A8CD-1D9F-35AD-981A-3E7925026EBB}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "VC++ Redistributable 2012 x86 (Additional)",
|
|
||||||
"Installer": "vcredist/2012-add/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{B175520C-86A2-35A7-8619-86DC379688B9}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2013 x86 Minimum Runtime - extracted from vcredist2013_x86.exe Burn bundle.",
|
|
||||||
"Name": "VC++ Redistributable 2013 x86 (Minimum)",
|
|
||||||
"Installer": "vcredist/2013-min/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{13A4EE12-23EA-3371-91EE-EFB36DDFFF3E}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "VC++ Redistributable 2013 x86 (Additional)",
|
|
||||||
"Installer": "vcredist/2013-add/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{F8CFEB22-A2E7-3971-9EDA-4B11EDEFC185}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2013 x64 Minimum Runtime - required by Keyence VR-6000 Series Software. Extracted from the Keyence installer's Windows Installer cache. Same REBOOT=ReallySuppress pattern as the x86 variants.",
|
|
||||||
"Name": "VC++ Redistributable 2013 x64 (Minimum)",
|
|
||||||
"Installer": "vcredist/2013-x64-min/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{A749D8E6-B613-3BE3-8F5F-045C84EBA29B}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2013 x64 Additional Runtime - required by Keyence VR-6000 Series Software. Pairs with the Minimum Runtime above.",
|
|
||||||
"Name": "VC++ Redistributable 2013 x64 (Additional)",
|
|
||||||
"Installer": "vcredist/2013-x64-add/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{929FBD26-9020-399B-9A7A-751D61F0B942}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2010 x64 - required by PC-DMIS 2016/2019 R2 on CMM PCs. PCDLRN.exe links against msvcr100.dll and the VS 2010 MFC DLLs which are only provided by this redistributable. Extracted from the PC-DMIS 2016 bundle's attached container (a1 payload). Silent install: /q /norestart. Detection: Uninstall key under the native x64 hive with fixed product GUID.",
|
|
||||||
"Name": "VC++ Redistributable 2010 x64",
|
|
||||||
"Installer": "vcredist/2010-x64/vcredist_x64.exe",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "/q /norestart",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{1D8E6291-B0D5-35EC-8441-6616F567A0F7}",
|
|
||||||
"DetectionName": "DisplayVersion",
|
|
||||||
"DetectionValue": "10.0.40219",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2012 x64 - required by PC-DMIS 2016/2019 R2. Same rationale as 2010 x64; PC-DMIS links against msvcr110.dll / MFC110. Extracted from the PC-DMIS 2016 bundle's attached container (a2 payload). Detection on the Minimum Runtime GUID (the main redist wrapper installs both Minimum and Additional sub-packages).",
|
|
||||||
"Name": "VC++ Redistributable 2012 x64",
|
|
||||||
"Installer": "vcredist/2012-x64/vcredist_x64.exe",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "/q /norestart",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{5AF4E09F-5C9B-3AAF-B731-544D3DC821DD}",
|
|
||||||
"DetectionName": "DisplayVersion",
|
|
||||||
"DetectionValue": "11.0.51106",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "VC++ 2015-2022 x86 - extracted from vcredist2015_2017_2019_2022_x86.exe Burn bundle. The bundle contains 2022 14.44.35211 plus 8 chained KB updates for older 2015/2017/2019 releases. We install only the 2022 Min+Add MSIs - the CRT v140 ABI is shared across 2015/2017/2019/2022, so the latest pair covers all four versions on Windows 10/11.",
|
|
||||||
"Name": "VC++ Redistributable 2022 x86 (Minimum)",
|
|
||||||
"Installer": "vcredist/2022-min/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{922480B5-CAEB-4B1B-AAA4-9716EFDCE26B}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "VC++ Redistributable 2022 x86 (Additional)",
|
|
||||||
"Installer": "vcredist/2022-add/installer.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart REBOOT=ReallySuppress NOVSUI=1 USING_EXUIH_SILENT=1",
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{C18FB403-1E88-43C8-AD8A-CED50F23DE8B}",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "Adobe Acrobat Reader DC - same version as GEA-Engineering image. Two-step install via Install-AcroReader.cmd wrapper: base MSI with enterprise transform (AcroRead.mst) then DC update patch (AcroRdrDCUpd2500120531.msp). Source files on PXE server at /srv/samba/winpeapps/gea-engineer/Deploy/Applications/extra/adobe/ - copy to /srv/samba/enrollment/preinstall/installers/adobe/.",
|
|
||||||
"Name": "Adobe Acrobat Reader DC",
|
|
||||||
"Installer": "adobe\\Install-AcroReader.cmd",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "",
|
|
||||||
"DetectionMethod": "File",
|
|
||||||
"DetectionPath": "C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroRd32.exe",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "OpenText HostExplorer 15 SP1 ShopFloor - replaced the WJDT-built OpenText.exe Inno wrapper with our own Setup-OpenText.ps1 that does the same install steps (msiexec base + ShopFloor.mst transform + SP1 .msp patch) PLUS fans the per-user profile/keymap/menu/macro content out to Default User and every existing user profile. The Inno wrapper deployed per-user content to {userappdata} which resolves to whichever user is running the installer (SYSTEM under DSC, single user under PreInstall) so Azure-AD users never saw the profiles. Setup-OpenText.cmd is a tiny launcher that hands off to Setup-OpenText.ps1 because the runner only handles MSI/EXE types. All bundled files live in vcredist-style subtree at dependencies/opentext/ and get xcopied through WinPE staging. NO DetectionMethod here on purpose - Setup-OpenText.ps1 owns its own version check by reading version.txt next to itself and comparing to HKLM:\\SOFTWARE\\GE\\OpenText\\Installed; the runner always invokes the wrapper (cost: ~1s of PowerShell launch when up-to-date) so the version constant lives in exactly one place: dependencies/opentext/version.txt.",
|
|
||||||
"Name": "OpenText HostExplorer ShopFloor",
|
|
||||||
"Installer": "opentext\\Setup-OpenText.cmd",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "",
|
|
||||||
"LogFile": "C:\\Logs\\PreInstall\\Setup-OpenText.log",
|
|
||||||
"PCTypes": ["Standard", "CMM", "Keyence", "Genspect", "WaxAndTrace", "Lab"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_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).",
|
|
||||||
"Name": "UDC",
|
|
||||||
"Installer": "UDC_Setup.exe",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "\"West Jefferson\" -9999",
|
|
||||||
"KillAfterDetection": true,
|
|
||||||
"DetectionMethod": "Registry",
|
|
||||||
"DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\UDC",
|
|
||||||
"PCTypes": ["Standard-Machine"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_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.",
|
|
||||||
"Name": "Display Kiosk App",
|
|
||||||
"Installer": "display\\Install-KioskApp.cmd",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "",
|
|
||||||
"LogFile": "C:\\Logs\\PreInstall\\Dashboard-install.log",
|
|
||||||
"PCTypes": ["Display"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "WJF Defect Tracker - replaces old ClickOnce deployment. MSI creates desktop + Start Menu shortcuts. Enforcer on SFLD share handles version updates post-imaging. TARGETDIR explicit because VS deployment MSI defaults to random path without it.",
|
|
||||||
"Name": "WJF Defect Tracker",
|
|
||||||
"Installer": "WJF_Defect_Tracker.msi",
|
|
||||||
"Type": "MSI",
|
|
||||||
"InstallArgs": "/qn /norestart ALLUSERS=1 REBOOT=ReallySuppress TARGETDIR=\"C:\\Program Files (x86)\\WJF_Defect_Tracker\"",
|
|
||||||
"DetectionMethod": "File",
|
|
||||||
"DetectionPath": "C:\\Program Files (x86)\\WJF_Defect_Tracker\\Defect_Tracker.exe",
|
|
||||||
"PCTypes": ["*"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"_comment": "Shopfloor Standard serial-port drivers: StarTech PCIe serial adapter (MosChip-based) + Prolific PL2303 USB-to-serial. Install-Drivers.cmd runs pnputil /add-driver with /subdirs /install so every bundled INF under drivers/ lands in the Windows driver store and auto-binds to matching hardware present now or plugged in later. Scoped to Standard PCs (both Machine + Timeclock) because the PCTypes filter is type-level only; installing a serial driver on a Timeclock without the hardware is harmless - it just sits in the driver store.",
|
|
||||||
"Name": "Shopfloor Serial Drivers",
|
|
||||||
"Installer": "drivers\\Install-Drivers.cmd",
|
|
||||||
"Type": "EXE",
|
|
||||||
"InstallArgs": "",
|
|
||||||
"LogFile": "C:\\Logs\\PreInstall\\Install-Drivers.log",
|
|
||||||
"DetectionMethod": "File",
|
|
||||||
"DetectionPath": "C:\\ProgramData\\PXEDrivers\\drivers-installed.marker",
|
|
||||||
"PCTypes": ["Standard"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
playbook/pxe-server-helpers/dnsmasq.service.d/nic-guard.conf
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# dnsmasq drop-in: refuse to start DHCP/PXE unless the imaging NIC is on
|
||||||
|
# the isolated imaging network.
|
||||||
|
#
|
||||||
|
# Installed as /etc/systemd/system/dnsmasq.service.d/nic-guard.conf by
|
||||||
|
# pxe_server_setup.yml. pxe-nic-check --guard listens on the imaging NIC
|
||||||
|
# for 5 s and sends one DHCPDISCOVER (~6 s total), then exits:
|
||||||
|
# 0 safe to serve (isolated, no link, or the check itself broke - it
|
||||||
|
# fails open on bugs and says so loudly in the journal)
|
||||||
|
# 1 DO NOT serve: a foreign DHCP server or a managed/corporate switch
|
||||||
|
# (LLDP, CDP, 802.1X) is on the imaging NIC. dnsmasq does not start.
|
||||||
|
#
|
||||||
|
# Why did dnsmasq not start? journalctl -u dnsmasq -b | grep pxe-nic-check
|
||||||
|
# Full picture: pxe-nic-check (or the webapp status page)
|
||||||
|
# Guard does not run on "systemctl reload"; every start/restart runs it,
|
||||||
|
# including the @reboot cron restart.
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/usr/local/sbin/pxe-nic-check --guard
|
||||||
@@ -3,10 +3,15 @@
|
|||||||
# 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 that client's SMB (TCP 445 and 139) conntrack
|
||||||
# TCP sockets for that client IP. Prevents stale server-side state from
|
# entries and drops its lingering SMB sockets. Prevents stale server-side
|
||||||
# causing "System error 53 - network path not found" when a WinPE client
|
# state from causing "System error 53 - network path not found" when a
|
||||||
# re-images the same machine without a clean SMB session teardown.
|
# WinPE client re-images the same machine without a clean SMB session
|
||||||
|
# teardown.
|
||||||
|
#
|
||||||
|
# SMB only, on purpose: the lease that fires this hook is iPXE's, and its
|
||||||
|
# first HTTP fetch of the boot menu is racing this script. Killing every
|
||||||
|
# socket or conntrack entry for the client IP broke that fetch.
|
||||||
#
|
#
|
||||||
# dnsmasq calls this with argv:
|
# dnsmasq calls this with argv:
|
||||||
# $1 = action (add | old | del)
|
# $1 = action (add | old | del)
|
||||||
@@ -14,10 +19,21 @@
|
|||||||
# $3 = client IP
|
# $3 = client IP
|
||||||
# $4 = hostname (may be empty)
|
# $4 = hostname (may be empty)
|
||||||
#
|
#
|
||||||
|
# A re-image usually comes back to the SAME lease (same MAC, same IP), which
|
||||||
|
# dnsmasq reports as "old", not "add" - and only at all because dnsmasq.conf
|
||||||
|
# sets script-on-renewal. "old" also fires when a running Windows PC renews
|
||||||
|
# its lease, so on "old" we only clear state when the request came from PXE
|
||||||
|
# firmware or iPXE (vendor class PXEClient...): that machine has just
|
||||||
|
# rebooted into a network boot, so any server-side session for its IP is
|
||||||
|
# dead. A renewal from Windows (vendor class "MSFT 5.0") is left alone.
|
||||||
|
#
|
||||||
# Must exit quickly; dnsmasq waits for it. Logs to syslog via logger.
|
# Must exit quickly; dnsmasq waits for it. Logs to syslog via logger.
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
|
|
||||||
|
# dnsmasq's environment may lack sbin; keep any PATH it gave us first.
|
||||||
|
PATH="${PATH:+$PATH:}/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
action="$1"
|
action="$1"
|
||||||
mac="$2"
|
mac="$2"
|
||||||
ip="$3"
|
ip="$3"
|
||||||
@@ -25,13 +41,26 @@ host="${4:-}"
|
|||||||
|
|
||||||
[ -z "$ip" ] && exit 0
|
[ -z "$ip" ] && exit 0
|
||||||
|
|
||||||
|
clear_state() {
|
||||||
|
local port
|
||||||
|
for port in 445 139; do
|
||||||
|
# client -> our share, and anything the other way round
|
||||||
|
conntrack -D -p tcp -s "$ip" --dport "$port" >/dev/null 2>&1
|
||||||
|
conntrack -D -p tcp -d "$ip" --sport "$port" >/dev/null 2>&1
|
||||||
|
done
|
||||||
|
# our SMB sockets (local port 445/139) whose peer is this client
|
||||||
|
ss -K -t "( sport = :445 or sport = :139 ) and dst = $ip" >/dev/null 2>&1
|
||||||
|
logger -t pxe-dhcp-hook "cleared stale SMB state for $1 $ip ($mac${host:+ $host})"
|
||||||
|
}
|
||||||
|
|
||||||
case "$action" in
|
case "$action" in
|
||||||
add|del)
|
add|del)
|
||||||
/usr/sbin/conntrack -D -s "$ip" >/dev/null 2>&1
|
clear_state "$action"
|
||||||
/usr/sbin/conntrack -D -d "$ip" >/dev/null 2>&1
|
;;
|
||||||
/usr/bin/ss -K "dst = $ip" >/dev/null 2>&1
|
old)
|
||||||
/usr/bin/ss -K "src = $ip" >/dev/null 2>&1
|
case "${DNSMASQ_VENDOR_CLASS:-}" in
|
||||||
logger -t pxe-dhcp-hook "cleared stale state for $action $ip ($mac${host:+ $host})"
|
PXEClient*) clear_state "old (network boot, ${DNSMASQ_VENDOR_CLASS%%:UNDI*})" ;;
|
||||||
|
esac
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
516
playbook/pxe-server-helpers/pxe-netplan-apply.sh
Executable file
@@ -0,0 +1,516 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# pxe-netplan-apply.sh - install the PXE server's netplan config without
|
||||||
|
# ever leaving the box unreachable on the corporate side.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# pxe-netplan-apply.sh STAGED_YAML PXE_NAME PXE_MAC [CORP_NAME CORP_MAC]
|
||||||
|
# pxe-netplan-apply.sh --preflight STAGED_YAML PXE_NAME PXE_MAC [CORP_NAME CORP_MAC]
|
||||||
|
# pxe-netplan-apply.sh --revert BACKUP_DIR (the auto-revert timer)
|
||||||
|
#
|
||||||
|
# Without CORP_NAME/CORP_MAC (corp_nic_mac empty in the playbook) only the
|
||||||
|
# imaging NIC is pinned and renamed; the other wired ports are catch-all
|
||||||
|
# DHCP clients that keep their names. Every pre-flight refusal about the
|
||||||
|
# imaging NIC applies the same.
|
||||||
|
#
|
||||||
|
# Env (set by pxe_server_setup.yml):
|
||||||
|
# PXE_SUBNET imaging LAN, e.g. 172.16.9.0/24 (SSH peers inside it may
|
||||||
|
# legitimately come in on the imaging NIC)
|
||||||
|
# PXE_SSH_CLIENT IP of the admin's SSH client ($SSH_CONNECTION field 1);
|
||||||
|
# every established sshd peer is checked as well
|
||||||
|
# PXE_NETPLAN_REVERT_AFTER seconds before the auto-revert fires (120)
|
||||||
|
# PXE_NETPLAN_CORP_WAIT seconds to wait for the corp IPv4 again (30)
|
||||||
|
#
|
||||||
|
# Steps (apply):
|
||||||
|
# 1. validate STAGED_YAML in a scratch root (netplan generate --root-dir);
|
||||||
|
# nothing on the box changes if it is invalid
|
||||||
|
# 2. stop here (UNCHANGED) if it is already installed, no other file
|
||||||
|
# configures wired NICs, and both NICs carry their stable names
|
||||||
|
# 3. pre-flight: REFUSE (exit 1, nothing changed) when the NIC that
|
||||||
|
# PXE_MAC names holds the default route, a DHCP lease, or the route to
|
||||||
|
# an SSH client outside PXE_SUBNET. That NIC becomes static with no
|
||||||
|
# gateway, so any of these means it is the corp-cabled one (swapped
|
||||||
|
# MACs) and applying would cut the admin off.
|
||||||
|
# 4. back up /etc/netplan to /var/backups/netplan-<timestamp>/, record the
|
||||||
|
# current NIC names, and arm an auto-revert (systemd-run timer) that
|
||||||
|
# restores the backup unless step 8 cancels it
|
||||||
|
# 5. install it (other wired configs moved aside), netplan generate
|
||||||
|
# 6. rename the NICs to their stable names (the .link files netplan
|
||||||
|
# writes do it on every later boot)
|
||||||
|
# 7. netplan apply
|
||||||
|
# 8. check: both names present, the imaging NIC has its IPv4, the corp
|
||||||
|
# NIC has IPv4 again if it had one before (PXE_NETPLAN_CORP_WAIT s),
|
||||||
|
# SSH clients still route out of the corp side; then cancel the revert
|
||||||
|
# Any failure in 5-8 restores the backup AND the previous NIC names, then
|
||||||
|
# exits 1. Prints CHANGED / UNCHANGED (or NEEDED for --preflight) as the
|
||||||
|
# last line.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
NETPLAN_DIR=${NETPLAN_DIR:-/etc/netplan}
|
||||||
|
SYSFS_NET=${SYSFS_NET:-/sys/class/net}
|
||||||
|
BACKUP_ROOT=${BACKUP_ROOT:-/var/backups}
|
||||||
|
LEASE_DIR=${LEASE_DIR:-/run/systemd/netif/leases}
|
||||||
|
LOCK_FILE=${LOCK_FILE:-/run/pxe-netplan-apply.lock}
|
||||||
|
REVERT_AFTER=${PXE_NETPLAN_REVERT_AFTER:-120}
|
||||||
|
CORP_WAIT=${PXE_NETPLAN_CORP_WAIT:-30}
|
||||||
|
PXE_WAIT=${PXE_NETPLAN_PXE_WAIT:-20}
|
||||||
|
TARGET=$NETPLAN_DIR/50-cloud-init.yaml
|
||||||
|
SELF=$(readlink -f "$0")
|
||||||
|
|
||||||
|
log() { echo "pxe-netplan-apply: $*"; }
|
||||||
|
|
||||||
|
# --- NIC helpers -------------------------------------------------------------
|
||||||
|
|
||||||
|
# Name of the physical NIC that has MAC $1 (empty if none).
|
||||||
|
iface_of() {
|
||||||
|
local d
|
||||||
|
for d in "$SYSFS_NET"/*; do
|
||||||
|
[ -e "$d/device" ] || continue
|
||||||
|
if [ "$(cat "$d/address" 2>/dev/null)" = "$1" ]; then
|
||||||
|
basename "$d"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
has_v4() {
|
||||||
|
ip -4 -o addr show dev "$1" scope global 2>/dev/null | grep -q ' inet '
|
||||||
|
}
|
||||||
|
|
||||||
|
default_route_devs() {
|
||||||
|
{ ip -4 route show default; ip -6 route show default; } 2>/dev/null |
|
||||||
|
awk '{for (i = 1; i < NF; i++) if ($i == "dev") print $(i + 1)}' | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# DHCP-assigned address (networkd and NetworkManager both flag those
|
||||||
|
# "dynamic"), or a networkd lease file for the link.
|
||||||
|
has_dhcp_lease() {
|
||||||
|
local idx
|
||||||
|
ip -o addr show dev "$1" 2>/dev/null | grep -qw dynamic && return 0
|
||||||
|
idx=$(cat "$SYSFS_NET/$1/ifindex" 2>/dev/null)
|
||||||
|
[ -n "$idx" ] && [ -s "$LEASE_DIR/$idx" ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
route_dev() {
|
||||||
|
ip -o route get "$1" 2>/dev/null |
|
||||||
|
awk '{for (i = 1; i < NF; i++) if ($i == "dev") {print $(i + 1); exit}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
ip2int() {
|
||||||
|
local a b c d
|
||||||
|
[[ "$1" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] || return 1
|
||||||
|
a=${BASH_REMATCH[1]} b=${BASH_REMATCH[2]} c=${BASH_REMATCH[3]} d=${BASH_REMATCH[4]}
|
||||||
|
echo $(( (a << 24) | (b << 16) | (c << 8) | d ))
|
||||||
|
}
|
||||||
|
|
||||||
|
in_subnet() {
|
||||||
|
local ipn netn len mask
|
||||||
|
[ -n "${2:-}" ] || return 1
|
||||||
|
ipn=$(ip2int "$1") || return 1
|
||||||
|
netn=$(ip2int "${2%/*}") || return 1
|
||||||
|
len=${2#*/}
|
||||||
|
[[ "$len" =~ ^[0-9]+$ ]] && [ "$len" -le 32 ] || return 1
|
||||||
|
mask=$(( len == 0 ? 0 : (0xFFFFFFFF << (32 - len)) & 0xFFFFFFFF ))
|
||||||
|
[ $(( ipn & mask )) -eq $(( netn & mask )) ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admin SSH clients: PXE_SSH_CLIENT, our own SSH_CONNECTION, and every
|
||||||
|
# established peer of sshd (port 22). IPv4-mapped IPv6 is unwrapped.
|
||||||
|
ssh_peers() {
|
||||||
|
{
|
||||||
|
[ -n "${PXE_SSH_CLIENT:-}" ] && echo "$PXE_SSH_CLIENT"
|
||||||
|
[ -n "${SSH_CONNECTION:-}" ] && echo "${SSH_CONNECTION%% *}"
|
||||||
|
ss -Htn state established '( sport = :22 )' 2>/dev/null | awk '{print $NF}' |
|
||||||
|
sed -E 's/:[0-9]+$//; s/^\[//; s/\]$//'
|
||||||
|
} | sed -E 's/^::ffff://' | grep -E '^[0-9a-fA-F.:]+$' | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- pre-flight --------------------------------------------------------------
|
||||||
|
|
||||||
|
# 0 = safe to apply. Prints every reason it is not.
|
||||||
|
preflight() {
|
||||||
|
local pif cif peer dev bad=0
|
||||||
|
pif=$(iface_of "$PXE_MAC")
|
||||||
|
if [ -z "$pif" ]; then
|
||||||
|
log "REFUSED: no physical NIC has pxe_nic_mac $PXE_MAC"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ -n "$CORP_MAC" ]; then
|
||||||
|
cif=$(iface_of "$CORP_MAC")
|
||||||
|
if [ -z "$cif" ]; then
|
||||||
|
log "REFUSED: no physical NIC has corp_nic_mac $CORP_MAC"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if default_route_devs | grep -qxF "$pif"; then
|
||||||
|
log "REFUSED: $pif (pxe_nic_mac $PXE_MAC) holds the default route"
|
||||||
|
bad=1
|
||||||
|
fi
|
||||||
|
if has_dhcp_lease "$pif"; then
|
||||||
|
log "REFUSED: $pif (pxe_nic_mac $PXE_MAC) has a DHCP lease"
|
||||||
|
bad=1
|
||||||
|
fi
|
||||||
|
while IFS= read -r peer; do
|
||||||
|
[ -n "$peer" ] || continue
|
||||||
|
dev=$(route_dev "$peer")
|
||||||
|
[ "$dev" = "$pif" ] || continue
|
||||||
|
if in_subnet "$peer" "${PXE_SUBNET:-}"; then
|
||||||
|
continue # an admin on the imaging LAN keeps 172.16.9.1
|
||||||
|
fi
|
||||||
|
log "REFUSED: the SSH client $peer is reached through $pif (pxe_nic_mac $PXE_MAC)"
|
||||||
|
bad=1
|
||||||
|
done < <(ssh_peers)
|
||||||
|
if [ "$bad" -ne 0 ]; then
|
||||||
|
log "The NIC named by pxe_nic_mac looks like the GE corporate one: the MACs are"
|
||||||
|
log "probably swapped, or the cables are. It would become"
|
||||||
|
log "static ${PXE_SUBNET:-imaging} with no gateway and cut this box off. Nothing was changed."
|
||||||
|
log "NICs now: $(for d in "$SYSFS_NET"/*; do [ -e "$d/device" ] && printf '%s=%s ' "$(basename "$d")" "$(cat "$d/address" 2>/dev/null)"; done)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- renaming ----------------------------------------------------------------
|
||||||
|
|
||||||
|
link_rename() {
|
||||||
|
local cur=$1 new=$2
|
||||||
|
log "renaming $cur to $new"
|
||||||
|
if ip link set dev "$cur" down &&
|
||||||
|
ip link set dev "$cur" name "$new" &&
|
||||||
|
ip link set dev "$new" up; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "ERROR: rename $cur -> $new failed"
|
||||||
|
ip link set dev "$cur" up 2>/dev/null
|
||||||
|
ip link set dev "$new" up 2>/dev/null
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# set_names MAC1 NAME1 [MAC2 NAME2]: give each MAC its name. A NIC that must
|
||||||
|
# change goes through a temporary name first, so a swap (pxe0 <-> corp0)
|
||||||
|
# works in both directions. Temporary names carry this run's PID, so they
|
||||||
|
# never collide with a name left over from an earlier (failed) pass. A pair
|
||||||
|
# with an empty MAC or name is skipped (no pinned GE NIC).
|
||||||
|
set_names() {
|
||||||
|
local -a macs=() want=() cur=()
|
||||||
|
local i
|
||||||
|
while [ $# -ge 2 ]; do
|
||||||
|
if [ -n "$1" ] && [ -n "$2" ]; then
|
||||||
|
macs+=("$1"); want+=("$2")
|
||||||
|
fi
|
||||||
|
shift 2
|
||||||
|
done
|
||||||
|
RENAME_SEQ=$(( ${RENAME_SEQ:-0} + 1 ))
|
||||||
|
for i in "${!macs[@]}"; do
|
||||||
|
cur[i]=$(iface_of "${macs[i]}")
|
||||||
|
if [ -z "${cur[i]}" ]; then
|
||||||
|
log "ERROR: no NIC with MAC ${macs[i]}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for i in "${!macs[@]}"; do
|
||||||
|
[ "${cur[i]}" = "${want[i]}" ] && continue
|
||||||
|
link_rename "${cur[i]}" "pxt$$-$RENAME_SEQ$i" || return 1
|
||||||
|
cur[i]=pxt$$-$RENAME_SEQ$i
|
||||||
|
done
|
||||||
|
for i in "${!macs[@]}"; do
|
||||||
|
[ "${cur[i]}" = "${want[i]}" ] && continue
|
||||||
|
link_rename "${cur[i]}" "${want[i]}" || return 1
|
||||||
|
done
|
||||||
|
for i in "${!macs[@]}"; do
|
||||||
|
if [ "$(iface_of "${macs[i]}")" != "${want[i]}" ]; then
|
||||||
|
log "ERROR: MAC ${macs[i]} is not named ${want[i]} after renaming"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
names_ok() {
|
||||||
|
[ "$(iface_of "$PXE_MAC")" = "$PXE_NAME" ] || return 1
|
||||||
|
if [ -n "$CORP_MAC" ]; then
|
||||||
|
[ "$(iface_of "$CORP_MAC")" = "$CORP_NAME" ]
|
||||||
|
else
|
||||||
|
[ -z "$(stray_names)" ]
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# No pinned GE NIC: "MAC NAME" for every other physical NIC whose name the
|
||||||
|
# catch-all (en*, eth*) would not match - the previous imaging NIC still
|
||||||
|
# called pxe0 after a re-pick, or corp0 from a pinned config. Each gets the
|
||||||
|
# kernel-style MAC name enx<mac>, so it becomes a catch-all DHCP client and
|
||||||
|
# pxe0 is free for the new imaging NIC.
|
||||||
|
stray_names() {
|
||||||
|
local d n m
|
||||||
|
[ -z "$CORP_MAC" ] || return 0
|
||||||
|
for d in "$SYSFS_NET"/*; do
|
||||||
|
[ -e "$d/device" ] || continue
|
||||||
|
n=${d##*/}
|
||||||
|
m=$(cat "$d/address" 2>/dev/null)
|
||||||
|
[ "$m" = "$PXE_MAC" ] && continue
|
||||||
|
[ "$(cat "$d/type" 2>/dev/null)" = 1 ] || continue
|
||||||
|
[ -e "$d/wireless" ] || [ -e "$d/phy80211" ] || [ -e "$d/bridge" ] && continue
|
||||||
|
case "$n" in en*|eth*) continue ;; esac
|
||||||
|
echo "$m enx${m//:/}"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# MACs of the physical NICs other than the imaging one that hold a DHCP
|
||||||
|
# (dynamic) IPv4: the GE side. They must have one again after the apply,
|
||||||
|
# or an admin on the GE side may have been cut off. Static addresses (the
|
||||||
|
# previous imaging NIC's 172.16.9.1) do not count. By MAC: names change.
|
||||||
|
other_v4_macs() {
|
||||||
|
local d n m
|
||||||
|
for d in "$SYSFS_NET"/*; do
|
||||||
|
[ -e "$d/device" ] || continue
|
||||||
|
n=${d##*/}
|
||||||
|
m=$(cat "$d/address" 2>/dev/null)
|
||||||
|
[ "$m" = "$PXE_MAC" ] && continue
|
||||||
|
ip -4 -o addr show dev "$n" scope global dynamic 2>/dev/null | grep -q ' inet ' && echo "$m"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- backup / restore --------------------------------------------------------
|
||||||
|
|
||||||
|
# Other netplan files that configure wired NICs (installer leftovers, an
|
||||||
|
# en* DHCP match): they would put a second config on the two NICs. Files
|
||||||
|
# without "ethernets" (WiFi, NetworkManager wifi profiles) are left alone.
|
||||||
|
other_files() {
|
||||||
|
find "$NETPLAN_DIR" -maxdepth 1 -type f \( -name '*.yaml' -o -name '*.yml' \) \
|
||||||
|
! -path "$TARGET" -exec grep -l 'ethernets' {} + 2>/dev/null | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
# State of one apply, next to (not inside) the backup, so restoring the
|
||||||
|
# backup never copies it into /etc/netplan.
|
||||||
|
write_state() {
|
||||||
|
{
|
||||||
|
echo "NETPLAN_DIR=$NETPLAN_DIR"
|
||||||
|
echo "PXE_MAC=$PXE_MAC"
|
||||||
|
echo "ORIG_PXE_IF=$ORIG_PXE_IF"
|
||||||
|
echo "CORP_MAC=$CORP_MAC"
|
||||||
|
echo "ORIG_CORP_IF=$ORIG_CORP_IF"
|
||||||
|
echo "EXTRA=$EXTRA"
|
||||||
|
} >"$BACKUP.state"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_state() {
|
||||||
|
local k v
|
||||||
|
while IFS='=' read -r k v; do
|
||||||
|
case "$k" in
|
||||||
|
NETPLAN_DIR) [[ "$v" =~ ^/[A-Za-z0-9/._-]*$ ]] && NETPLAN_DIR=$v ;;
|
||||||
|
PXE_MAC|CORP_MAC) [[ "$v" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]] && printf -v "$k" '%s' "$v" ;;
|
||||||
|
ORIG_PXE_IF|ORIG_CORP_IF) [[ "$v" =~ ^[A-Za-z0-9._-]{0,15}$ ]] && printf -v "$k" '%s' "$v" ;;
|
||||||
|
EXTRA) [[ "$v" =~ ^(([0-9a-f]{2}:){5}[0-9a-f]{2}=[A-Za-z0-9._-]{1,15},?)*$ ]] && EXTRA=$v ;;
|
||||||
|
esac
|
||||||
|
done <"$BACKUP.state"
|
||||||
|
TARGET=$NETPLAN_DIR/50-cloud-init.yaml
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel_revert() {
|
||||||
|
[ -n "${REVERT_UNIT:-}" ] || return 0
|
||||||
|
systemctl stop "$REVERT_UNIT.timer" "$REVERT_UNIT.service" >/dev/null 2>&1
|
||||||
|
if systemctl is-active --quiet "$REVERT_UNIT.timer" 2>/dev/null; then
|
||||||
|
log "WARNING: could not cancel $REVERT_UNIT.timer (the committed marker still stops it)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Put the backed-up config AND the previous NIC names back.
|
||||||
|
restore() {
|
||||||
|
local rc=0
|
||||||
|
log "restoring the previous config from $BACKUP"
|
||||||
|
find "$NETPLAN_DIR" -maxdepth 1 -type f \( -name '*.yaml' -o -name '*.yml' \) -delete
|
||||||
|
cp -a "$BACKUP/." "$NETPLAN_DIR/"
|
||||||
|
if [ -n "${ORIG_PXE_IF:-}" ]; then
|
||||||
|
local -a back=()
|
||||||
|
local pair
|
||||||
|
for pair in ${EXTRA//,/ }; do back+=("${pair%%=*}" "${pair#*=}"); done
|
||||||
|
set_names "$PXE_MAC" "$ORIG_PXE_IF" "$CORP_MAC" "${ORIG_CORP_IF:-}" "${back[@]}" ||
|
||||||
|
{ log "ERROR: could not put the previous NIC names back"; rc=1; }
|
||||||
|
fi
|
||||||
|
netplan generate && netplan apply || { log "ERROR: netplan apply of the restored config failed"; rc=1; }
|
||||||
|
touch "$BACKUP.reverted"
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
log "ERROR: $*"
|
||||||
|
restore
|
||||||
|
cancel_revert
|
||||||
|
log "the previous config is back in place; nothing applied"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- modes -------------------------------------------------------------------
|
||||||
|
|
||||||
|
MODE=apply
|
||||||
|
case "${1:-}" in
|
||||||
|
--preflight) MODE=preflight; shift ;;
|
||||||
|
--revert)
|
||||||
|
[ $# -eq 2 ] || { echo "usage: $0 --revert BACKUP_DIR" >&2; exit 2; }
|
||||||
|
BACKUP=${2%/}
|
||||||
|
[ -d "$BACKUP" ] && [ -f "$BACKUP.state" ] || { log "revert: no backup/state at $BACKUP"; exit 1; }
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
|
flock -w 600 9 || log "revert: lock still held after 600 s; reverting anyway"
|
||||||
|
if [ -e "$BACKUP.committed" ] || [ -e "$BACKUP.reverted" ]; then
|
||||||
|
log "revert: $BACKUP already committed or reverted; nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
PXE_MAC="" CORP_MAC="" ORIG_PXE_IF="" ORIG_CORP_IF="" EXTRA=""
|
||||||
|
read_state
|
||||||
|
log "revert: the apply was not confirmed within its window"
|
||||||
|
restore
|
||||||
|
exit $?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ $# -ne 3 ] && [ $# -ne 5 ]; then
|
||||||
|
echo "usage: $0 [--preflight] STAGED_YAML PXE_NAME PXE_MAC [CORP_NAME CORP_MAC]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
STAGED=$1
|
||||||
|
PXE_NAME=$2
|
||||||
|
PXE_MAC=${3,,}
|
||||||
|
CORP_NAME=${4:-}
|
||||||
|
CORP_MAC=${5:-}
|
||||||
|
CORP_MAC=${CORP_MAC,,}
|
||||||
|
|
||||||
|
# 1. validate in a scratch root
|
||||||
|
SCRATCH=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$SCRATCH"' EXIT
|
||||||
|
mkdir -p "$SCRATCH/etc/netplan"
|
||||||
|
install -m 600 "$STAGED" "$SCRATCH/etc/netplan/50-cloud-init.yaml"
|
||||||
|
if ! netplan generate --root-dir "$SCRATCH"; then
|
||||||
|
log "ERROR: $STAGED does not validate; nothing changed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. already in place?
|
||||||
|
if cmp -s "$STAGED" "$TARGET" && [ -z "$(other_files)" ] && names_ok; then
|
||||||
|
log "netplan already in place, $PXE_NAME${CORP_NAME:+ and $CORP_NAME} present"
|
||||||
|
echo UNCHANGED
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. pre-flight
|
||||||
|
if ! preflight; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "$MODE" = preflight ]; then
|
||||||
|
log "pre-flight passed; the netplan config would change"
|
||||||
|
echo NEEDED
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec 9>"$LOCK_FILE"
|
||||||
|
flock -w 60 9 || { log "ERROR: another pxe-netplan-apply holds $LOCK_FILE; nothing changed"; exit 1; }
|
||||||
|
|
||||||
|
# 4. back up, record names, arm the auto-revert
|
||||||
|
ORIG_PXE_IF=$(iface_of "$PXE_MAC")
|
||||||
|
ORIG_CORP_IF=""
|
||||||
|
[ -n "$CORP_MAC" ] && ORIG_CORP_IF=$(iface_of "$CORP_MAC")
|
||||||
|
CORP_HAD_V4=0
|
||||||
|
[ -n "$ORIG_CORP_IF" ] && has_v4 "$ORIG_CORP_IF" && CORP_HAD_V4=1
|
||||||
|
OTHERS_V4=""
|
||||||
|
[ -z "$CORP_MAC" ] && OTHERS_V4=$(other_v4_macs)
|
||||||
|
# NICs to move off a name the catch-all would not match, recorded with
|
||||||
|
# their current names for the revert ("mac=name,...").
|
||||||
|
EXTRA=""
|
||||||
|
FREE=()
|
||||||
|
while read -r m newname; do
|
||||||
|
[ -n "$m" ] || continue
|
||||||
|
EXTRA="${EXTRA:+$EXTRA,}$m=$(iface_of "$m")"
|
||||||
|
FREE+=("$m" "$newname")
|
||||||
|
done < <(stray_names)
|
||||||
|
BACKUP=$BACKUP_ROOT/netplan-$(date +%Y%m%d-%H%M%S)
|
||||||
|
mkdir -p "$BACKUP"
|
||||||
|
cp -a "$NETPLAN_DIR/." "$BACKUP/"
|
||||||
|
write_state
|
||||||
|
log "previous config saved in $BACKUP (NICs: $ORIG_PXE_IF${ORIG_CORP_IF:+, $ORIG_CORP_IF})"
|
||||||
|
|
||||||
|
REVERT_UNIT=""
|
||||||
|
if [ "$REVERT_AFTER" -gt 0 ] 2>/dev/null && command -v systemd-run >/dev/null; then
|
||||||
|
REVERT_UNIT=pxe-netplan-revert-$(basename "$BACKUP" | tr -cd 'A-Za-z0-9-')
|
||||||
|
if systemd-run --quiet --unit="$REVERT_UNIT" --on-active="$REVERT_AFTER" \
|
||||||
|
--timer-property=AccuracySec=1s "$SELF" --revert "$BACKUP"; then
|
||||||
|
log "auto-revert armed: $REVERT_UNIT.timer restores $BACKUP in ${REVERT_AFTER}s unless this run confirms"
|
||||||
|
else
|
||||||
|
log "WARNING: could not arm the auto-revert timer; relying on the in-script checks"
|
||||||
|
REVERT_UNIT=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "WARNING: auto-revert off (no systemd-run or PXE_NETPLAN_REVERT_AFTER=0)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# An SSH drop (HUP) must not stop us half-way; TERM/INT restore.
|
||||||
|
trap '' HUP
|
||||||
|
trap 'fail "interrupted"' INT TERM
|
||||||
|
|
||||||
|
# 5. install, moving aside the other wired configs (an en* DHCP match would
|
||||||
|
# put a DHCP client on the imaging NIC too)
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
log "moving aside $f (copy in $BACKUP)"
|
||||||
|
rm -f "$f"
|
||||||
|
done < <(other_files)
|
||||||
|
install -m 600 "$STAGED" "$TARGET"
|
||||||
|
netplan generate || fail "netplan generate failed"
|
||||||
|
|
||||||
|
# 6. stable names now, so dnsmasq/UFW/Samba (which use them) work before
|
||||||
|
# the next reboot. Does not depend on the netplan version renaming.
|
||||||
|
[ ${#FREE[@]} -gt 0 ] && log "freeing names for the catch-all: $EXTRA"
|
||||||
|
set_names "$PXE_MAC" "$PXE_NAME" "$CORP_MAC" "$CORP_NAME" "${FREE[@]}" || fail "NIC rename failed"
|
||||||
|
|
||||||
|
# 7. apply
|
||||||
|
netplan apply || fail "netplan apply failed"
|
||||||
|
|
||||||
|
# 8. check
|
||||||
|
names_ok || fail "$PXE_NAME${CORP_NAME:+ / $CORP_NAME} not on MAC $PXE_MAC${CORP_MAC:+ / $CORP_MAC} after apply"
|
||||||
|
ok=0
|
||||||
|
for _ in $(seq 1 "$PXE_WAIT"); do
|
||||||
|
if has_v4 "$PXE_NAME"; then ok=1; break; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
[ "$ok" -eq 1 ] || fail "$PXE_NAME has no IPv4 address after ${PXE_WAIT}s"
|
||||||
|
for m in $OTHERS_V4; do
|
||||||
|
ok=0
|
||||||
|
for _ in $(seq 1 "$CORP_WAIT"); do
|
||||||
|
n=$(iface_of "$m")
|
||||||
|
if [ -n "$n" ] && has_v4 "$n"; then ok=1; break; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
[ "$ok" -eq 1 ] || fail "the NIC with MAC $m had a DHCP IPv4 before and has none after ${CORP_WAIT}s"
|
||||||
|
done
|
||||||
|
if [ -z "$CORP_MAC" ]; then
|
||||||
|
:
|
||||||
|
elif [ "$CORP_HAD_V4" -eq 1 ]; then
|
||||||
|
ok=0
|
||||||
|
for _ in $(seq 1 "$CORP_WAIT"); do
|
||||||
|
if has_v4 "$CORP_NAME"; then ok=1; break; fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
[ "$ok" -eq 1 ] || fail "$CORP_NAME had IPv4 before and has none after ${CORP_WAIT}s"
|
||||||
|
elif ! has_v4 "$CORP_NAME"; then
|
||||||
|
log "WARNING: $CORP_NAME has no IPv4 address (it had none before either: corp cable unplugged or no DHCP)"
|
||||||
|
fi
|
||||||
|
while IFS= read -r peer; do
|
||||||
|
[ -n "$peer" ] || continue
|
||||||
|
dev=$(route_dev "$peer")
|
||||||
|
if [ "$dev" = "$PXE_NAME" ] && ! in_subnet "$peer" "${PXE_SUBNET:-}"; then
|
||||||
|
fail "the SSH client $peer now routes through $PXE_NAME"
|
||||||
|
fi
|
||||||
|
if [ -z "$dev" ]; then
|
||||||
|
fail "no route to the SSH client $peer after apply"
|
||||||
|
fi
|
||||||
|
done < <(ssh_peers)
|
||||||
|
|
||||||
|
touch "$BACKUP.committed"
|
||||||
|
cancel_revert
|
||||||
|
trap - INT TERM
|
||||||
|
ip -br addr show dev "$PXE_NAME" 2>/dev/null
|
||||||
|
[ -n "$CORP_NAME" ] && ip -br addr show dev "$CORP_NAME" 2>/dev/null
|
||||||
|
echo CHANGED
|
||||||
|
exit 0
|
||||||
145
playbook/pxe-server-helpers/pxe-nic-carrier
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# pxe-nic-carrier - re-check the imaging NIC whenever its cable moves.
|
||||||
|
#
|
||||||
|
# The dnsmasq guard (pxe-nic-check --guard) runs only when dnsmasq starts,
|
||||||
|
# and the watch timer only every 5 min. If the imaging cable is moved into
|
||||||
|
# a GE port while dnsmasq runs, dnsmasq would answer DHCP on the GE LAN
|
||||||
|
# until the next watch. This hook closes that gap:
|
||||||
|
#
|
||||||
|
# carrier lost dnsmasq is stopped at once (if it was running) and a
|
||||||
|
# marker remembers that this hook stopped it.
|
||||||
|
# carrier back pxe-nic-carrier.service runs "pxe-nic-carrier --gain":
|
||||||
|
# after a 2 s settle, if dnsmasq is running or the marker
|
||||||
|
# is set, "systemctl restart dnsmasq" - its ExecStartPre
|
||||||
|
# guard checks the NIC while dnsmasq is down and refuses
|
||||||
|
# to start it on the GE network. Otherwise it only runs
|
||||||
|
# the guard to refresh /run/pxe-nic-status.json. dnsmasq
|
||||||
|
# that an operator or the watch stopped stays stopped.
|
||||||
|
#
|
||||||
|
# Only the imaging NIC (and, for a bridge, its ports) counts, as resolved
|
||||||
|
# by "pxe-nic-check --print-imaging" from /etc/pxe-server/nic.conf or the
|
||||||
|
# dnsmasq interface= pin. Events on any other NIC (the GE NIC) exit at
|
||||||
|
# once and touch nothing. Link state is read from sysfs, not from the
|
||||||
|
# event name, and remembered per interface, so the same transition seen
|
||||||
|
# twice (carrier, then routable) acts once.
|
||||||
|
#
|
||||||
|
# Called by networkd-dispatcher with IFACE and STATE in the environment
|
||||||
|
# (installed as /etc/networkd-dispatcher/<state>.d/50-pxe-nic-carrier for
|
||||||
|
# carrier, degraded, degraded-carrier, enslaved, routable, no-carrier and
|
||||||
|
# off), or by hand: pxe-nic-carrier IFACE. Logs: journalctl -t pxe-nic-carrier
|
||||||
|
#
|
||||||
|
# The dispatcher waits for this script, so the event path never runs a
|
||||||
|
# check itself: loss is two systemctl calls, gain hands off to the service.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
CHECK="${PXE_NIC_CHECK:-/usr/local/sbin/pxe-nic-check}"
|
||||||
|
SYS_NET="${PXE_SYS_NET:-/sys/class/net}"
|
||||||
|
RUN="${PXE_NIC_CARRIER_RUN:-/run/pxe-nic-carrier}"
|
||||||
|
SYSTEMCTL="${PXE_SYSTEMCTL:-systemctl}"
|
||||||
|
SETTLE="${PXE_NIC_CARRIER_SETTLE:-2}"
|
||||||
|
GAIN_UNIT="pxe-nic-carrier.service"
|
||||||
|
TAG="pxe-nic-carrier"
|
||||||
|
|
||||||
|
PATH="${PATH:+$PATH:}/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
log() { # log PRIORITY MESSAGE
|
||||||
|
logger -t "$TAG" -p "daemon.$1" -- "$2" 2>/dev/null || \
|
||||||
|
echo "$TAG: $2" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
imaging_ifaces() {
|
||||||
|
"$CHECK" --print-imaging 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
carrier_of() { # 1 if the interface has carrier, else 0 (down reads EINVAL)
|
||||||
|
local c
|
||||||
|
c=$(cat "$SYS_NET/$1/carrier" 2>/dev/null) || c=0
|
||||||
|
[ "$c" = 1 ] && echo 1 || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
any_carrier() { # any imaging interface or port with carrier
|
||||||
|
local i
|
||||||
|
for i in "$@"; do
|
||||||
|
[ "$(carrier_of "$i")" = 1 ] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsmasq_running() { # active, starting or reloading
|
||||||
|
case "$("$SYSTEMCTL" is-active dnsmasq 2>/dev/null)" in
|
||||||
|
active|activating|reloading) return 0 ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
on_event() {
|
||||||
|
local iface="$1" list=() match="" i now prev
|
||||||
|
mapfile -t list < <(imaging_ifaces)
|
||||||
|
for i in "${list[@]}"; do
|
||||||
|
[ -n "$i" ] && [ "$i" = "$iface" ] && match=1
|
||||||
|
done
|
||||||
|
[ -n "$match" ] || exit 0 # not the imaging NIC: hands off
|
||||||
|
|
||||||
|
mkdir -p "$RUN" || exit 0
|
||||||
|
exec 9>"$RUN/event.lock"
|
||||||
|
flock -w 10 9 || log warning "lock busy, handling $iface anyway"
|
||||||
|
|
||||||
|
now=$(carrier_of "$iface")
|
||||||
|
prev=$(cat "$RUN/link.$iface" 2>/dev/null) || prev=unknown
|
||||||
|
echo "$now" > "$RUN/link.$iface"
|
||||||
|
[ "$now" = "$prev" ] && exit 0 # same state again: already handled
|
||||||
|
|
||||||
|
if [ "$now" = 0 ]; then
|
||||||
|
# an in-flight gain check must not restart dnsmasq after this
|
||||||
|
"$SYSTEMCTL" stop "$GAIN_UNIT" >/dev/null 2>&1
|
||||||
|
if dnsmasq_running; then
|
||||||
|
: > "$RUN/stopped-dnsmasq"
|
||||||
|
"$SYSTEMCTL" stop --no-block dnsmasq
|
||||||
|
log warning "carrier lost on imaging NIC $iface (${STATE:-event}): stopped dnsmasq; it restarts after the guard passes when the link returns"
|
||||||
|
else
|
||||||
|
log info "carrier lost on imaging NIC $iface (${STATE:-event}); dnsmasq not running"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log notice "carrier up on imaging NIC $iface (${STATE:-event}, was $prev): re-checking before dnsmasq serves"
|
||||||
|
"$SYSTEMCTL" restart --no-block "$GAIN_UNIT"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
on_gain() {
|
||||||
|
local list=() rc
|
||||||
|
sleep "$SETTLE"
|
||||||
|
mapfile -t list < <(imaging_ifaces)
|
||||||
|
if [ "${#list[@]}" -eq 0 ] || [ -z "${list[0]}" ]; then
|
||||||
|
log err "imaging NIC unknown, nothing to re-check"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if ! any_carrier "${list[@]}"; then
|
||||||
|
log info "imaging NIC ${list[0]} lost carrier again, nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if dnsmasq_running || [ -e "$RUN/stopped-dnsmasq" ]; then
|
||||||
|
# restart = stop, then the ExecStartPre guard with dnsmasq down
|
||||||
|
"$SYSTEMCTL" restart dnsmasq
|
||||||
|
rc=$?
|
||||||
|
rm -f "$RUN/stopped-dnsmasq"
|
||||||
|
if [ "$rc" -eq 0 ]; then
|
||||||
|
log notice "imaging NIC ${list[0]}: guard passed, dnsmasq (re)started"
|
||||||
|
else
|
||||||
|
log crit "imaging NIC ${list[0]}: dnsmasq NOT started (guard refused or start failed, rc $rc); see journalctl -u dnsmasq. Fix the cabling, then systemctl start dnsmasq"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
"$CHECK" --guard >/dev/null 2>&1
|
||||||
|
rc=$?
|
||||||
|
log info "imaging NIC ${list[0]}: dnsmasq was not running, left stopped (guard rc $rc, status refreshed)"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--gain) on_gain ;;
|
||||||
|
-h|--help) sed -n '2,36p' "$0"; exit 0 ;;
|
||||||
|
*) on_event "${1:-${IFACE:-}}" ;;
|
||||||
|
esac
|
||||||
16
playbook/pxe-server-helpers/pxe-nic-carrier.service
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Started (never enabled) by pxe-nic-carrier, the networkd-dispatcher
|
||||||
|
# hook, when the imaging NIC gets carrier back. Re-checks the imaging NIC
|
||||||
|
# before dnsmasq serves again: if dnsmasq was running (or the hook stopped
|
||||||
|
# it on carrier loss), "systemctl restart dnsmasq" so its ExecStartPre
|
||||||
|
# guard decides; otherwise it only refreshes /run/pxe-nic-status.json.
|
||||||
|
# The hook restarts this unit on every carrier gain and stops it on a
|
||||||
|
# carrier loss, so the newest event wins.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=PXE NIC carrier change: re-check the imaging NIC before dnsmasq serves
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/sbin/pxe-nic-carrier --gain
|
||||||
|
TimeoutStartSec=120
|
||||||
1422
playbook/pxe-server-helpers/pxe-nic-check
Executable file
17
playbook/pxe-server-helpers/pxe-nic-check.service
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Periodic imaging-NIC watch (pxe-nic-check.timer runs this every 5 min).
|
||||||
|
# Listens 60 s on each NIC, probes the imaging NIC for foreign DHCP, writes
|
||||||
|
# /run/pxe-nic-status.json for the webapp, and STOPS dnsmasq if the imaging
|
||||||
|
# NIC turns out to be on the GE network or next to another DHCP server.
|
||||||
|
# It never starts dnsmasq: after fixing the cabling, start it by hand
|
||||||
|
# (systemctl start dnsmasq), which re-runs the guard. If dnsmasq was
|
||||||
|
# restarted inside the window it re-checks (~6 s) before stopping it.
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=PXE NIC watch: keep dnsmasq off the GE corporate network
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/sbin/pxe-nic-check --watch
|
||||||
|
TimeoutStartSec=180
|
||||||
|
Nice=5
|
||||||
11
playbook/pxe-server-helpers/pxe-nic-check.timer
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=PXE NIC watch every 5 minutes
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnActiveSec=1min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
AccuracySec=15s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
297
playbook/pxe-server-helpers/pxe-pick-nic
Executable file
@@ -0,0 +1,297 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# pxe-pick-nic - choose the imaging NIC by its cable, not by its MAC.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sudo pxe-pick-nic interactive: pull the imaging switch cable and
|
||||||
|
# plug it back in; the port that blinked is it
|
||||||
|
# pxe-pick-nic --auto for the first-boot playbook: the one wired
|
||||||
|
# port with a link, if it is isolated
|
||||||
|
# pxe-pick-nic --show print the saved choice
|
||||||
|
#
|
||||||
|
# The choice is saved in /etc/pxe-server/nic-pick.conf (imaging_mac=...).
|
||||||
|
# The playbook uses it when site-vars.yml leaves pxe_nic_mac empty, and pins
|
||||||
|
# that MAC from then on: the box never re-guesses at boot, so a moved cable
|
||||||
|
# can never move 172.16.9.1 and DHCP onto another port.
|
||||||
|
#
|
||||||
|
# A port is accepted only when pxe-nic-check calls it "isolated": no GE
|
||||||
|
# switch (LLDP/CDP), no 802.1X port authentication, and no other DHCP
|
||||||
|
# server answering one DHCPDISCOVER. It listens CHECK_WINDOW seconds
|
||||||
|
# (default 65: a Cisco switch sends CDP every 60 s). --auto also refuses a
|
||||||
|
# port where more than ARP_MAX (default 10) hosts were heard: an imaging
|
||||||
|
# switch at install time is nearly silent, a GE access VLAN is not, and
|
||||||
|
# that holds even when the switch sends no LLDP/CDP and the probe is lost
|
||||||
|
# (STP still listening). Anything else is refused and nothing is saved.
|
||||||
|
# The check uses the site's corp_domain_suffixes and allowed_switches
|
||||||
|
# (PXE_PICK_SUFFIXES / PXE_PICK_ALLOWED from the playbook, else
|
||||||
|
# /etc/pxe-server/nic.conf), so a managed imaging switch can be allowed.
|
||||||
|
#
|
||||||
|
# Install rule for sites: during the install plug in ONLY the imaging
|
||||||
|
# switch. Plug the GE cable into any other port afterwards; the catch-all
|
||||||
|
# netplan entry makes it a DHCP client and dnsmasq never serves it.
|
||||||
|
#
|
||||||
|
# Exit: 0 chosen (or --show found one), 1 refused / could not choose,
|
||||||
|
# 2 usage.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
CONF=${PXE_PICK_CONF:-/etc/pxe-server/nic-pick.conf}
|
||||||
|
HINT=${PXE_PICK_HINT:-/var/lib/pxe-firstboot/nic-hint}
|
||||||
|
SYSFS_NET=${SYSFS_NET:-/sys/class/net}
|
||||||
|
NIC_CHECK=${PXE_NIC_CHECK:-/usr/local/sbin/pxe-nic-check}
|
||||||
|
CHECK_WINDOW=${CHECK_WINDOW:-65}
|
||||||
|
ARP_MAX=${PXE_PICK_ARP_MAX:-10}
|
||||||
|
NIC_CONF=${PXE_NIC_CONF:-/etc/pxe-server/nic.conf}
|
||||||
|
SETTLE=${PXE_PICK_SETTLE:-8}
|
||||||
|
WAIT=${PXE_PICK_WAIT:-180}
|
||||||
|
|
||||||
|
log() { echo "pxe-pick-nic: $*"; }
|
||||||
|
|
||||||
|
# Physical wired Ethernet ports: a device behind them, ARP type 1, not
|
||||||
|
# wireless, not a bridge.
|
||||||
|
wired() {
|
||||||
|
local d n
|
||||||
|
for d in "$SYSFS_NET"/*; do
|
||||||
|
n=${d##*/}
|
||||||
|
[ -e "$d/device" ] || continue
|
||||||
|
[ "$(cat "$d/type" 2>/dev/null)" = 1 ] || continue
|
||||||
|
[ -e "$d/wireless" ] || [ -e "$d/phy80211" ] && continue
|
||||||
|
[ -e "$d/bridge" ] && continue
|
||||||
|
echo "$n"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
mac_of() { cat "$SYSFS_NET/$1/address" 2>/dev/null; }
|
||||||
|
|
||||||
|
# 1 = link, 0 = none. carrier is unreadable while the port is admin-down.
|
||||||
|
carrier() { [ "$(cat "$SYSFS_NET/$1/carrier" 2>/dev/null)" = 1 ] && echo 1 || echo 0; }
|
||||||
|
|
||||||
|
# Bring every wired port up (no address is added) so its link can be seen.
|
||||||
|
links_up() {
|
||||||
|
local n up=0
|
||||||
|
for n in $(wired); do
|
||||||
|
if ! ip link show dev "$n" 2>/dev/null | grep -q '[<,]UP[,>]'; then
|
||||||
|
ip link set dev "$n" up 2>/dev/null && up=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$up" -eq 1 ] && sleep "$SETTLE"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
table() {
|
||||||
|
local n
|
||||||
|
for n in $(wired); do
|
||||||
|
printf ' %-16s %s link %s\n' "$n" "$(mac_of "$n")" \
|
||||||
|
"$([ "$(carrier "$n")" = 1 ] && echo up || echo down)"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# A pxe-nic-check config with only the site settings that matter here
|
||||||
|
# (never the MAC keys: the port under test is given with --iface).
|
||||||
|
check_config() {
|
||||||
|
local f=$1 suffixes=${PXE_PICK_SUFFIXES:-} allowed=${PXE_PICK_ALLOWED:-}
|
||||||
|
if [ -z "$suffixes$allowed" ] && [ -f "$NIC_CONF" ]; then
|
||||||
|
suffixes=$(sed -n 's/^[[:space:]]*corp_domain_suffixes[[:space:]]*=[[:space:]]*//p' "$NIC_CONF" | tail -1)
|
||||||
|
allowed=$(sed -n 's/^[[:space:]]*allowed_switches[[:space:]]*=[[:space:]]*//p' "$NIC_CONF" | tail -1)
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
echo "[nic]"
|
||||||
|
[ -n "$suffixes" ] && echo "corp_domain_suffixes = $suffixes"
|
||||||
|
[ -n "$allowed" ] && echo "allowed_switches = $allowed"
|
||||||
|
} >"$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# check IFACE [strict]: prints the verdict, returns 0 only for "isolated"
|
||||||
|
# (with strict, also only when at most ARP_MAX hosts were heard).
|
||||||
|
check() {
|
||||||
|
local out verdict arp why cfg
|
||||||
|
if [ ! -f "$NIC_CHECK" ]; then
|
||||||
|
log "ERROR: $NIC_CHECK not found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
cfg=$(mktemp)
|
||||||
|
check_config "$cfg"
|
||||||
|
log "checking $1 is isolated: listening ${CHECK_WINDOW}s for GE switches and DHCP servers..."
|
||||||
|
out=$(python3 "$NIC_CHECK" --iface "$1" --probe --window "$CHECK_WINDOW" --json --config "$cfg" 2>/dev/null)
|
||||||
|
rm -f "$cfg"
|
||||||
|
read -r verdict arp why < <(python3 -c '
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
e = json.load(sys.stdin)["interfaces"][0]
|
||||||
|
ev = "; ".join(x for x in e["evidence"] if not x.endswith("ARP hosts seen"))
|
||||||
|
print(e["verdict"], int(e.get("arp_hosts") or 0), ev or "-")
|
||||||
|
except Exception as ex:
|
||||||
|
print("error", 0, "pxe-nic-check gave no usable result: %s" % ex)
|
||||||
|
' <<<"$out")
|
||||||
|
if [ "$verdict" = isolated ]; then
|
||||||
|
if [ "$arp" -gt "$ARP_MAX" ]; then
|
||||||
|
log "$1: isolated by its switch and DHCP, but $arp hosts were heard (more than $ARP_MAX)"
|
||||||
|
if [ "${2:-}" = strict ]; then
|
||||||
|
log "REFUSED: a busy LAN is not an imaging switch at install time; it may be a GE port"
|
||||||
|
log "with LLDP/CDP off. Pick it by its cable (sudo pxe-pick-nic) or set pxe_nic_mac."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log "WARNING: make sure this is the imaging switch, not a GE port with LLDP/CDP turned off."
|
||||||
|
else
|
||||||
|
log "$1: isolated (no GE switch, no 802.1X, no other DHCP server, $arp hosts heard)"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "$1: verdict $verdict: $why"
|
||||||
|
case "$verdict" in
|
||||||
|
ge) log "That port is on the GE network (or a managed switch not in allowed_switches). It must never be the imaging port." ;;
|
||||||
|
foreign-dhcp) log "Another DHCP server answers on that port (a second PXE server, or a routed LAN)." ;;
|
||||||
|
no-link) log "That port lost its link during the check." ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
local n=$1 mac
|
||||||
|
mac=$(mac_of "$n")
|
||||||
|
mkdir -p "$(dirname "$CONF")"
|
||||||
|
{
|
||||||
|
echo "# Written by pxe-pick-nic $(date -Is). The imaging NIC; the playbook"
|
||||||
|
echo "# pins it when site-vars.yml leaves pxe_nic_mac empty."
|
||||||
|
echo "imaging_mac=$mac"
|
||||||
|
echo "picked_iface=$n"
|
||||||
|
} >"$CONF.tmp" && mv "$CONF.tmp" "$CONF"
|
||||||
|
rm -f "$HINT"
|
||||||
|
log "saved: imaging NIC = $n ($mac) in $CONF"
|
||||||
|
}
|
||||||
|
|
||||||
|
hint() {
|
||||||
|
mkdir -p "$(dirname "$HINT")" 2>/dev/null
|
||||||
|
{ printf '%s\n' "$*" >"$HINT"; } 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
saved_mac() {
|
||||||
|
sed -n 's/^imaging_mac=\(\([0-9a-f]\{2\}:\)\{5\}[0-9a-f]\{2\}\)$/\1/p' "$CONF" 2>/dev/null | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- modes ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
mode_show() {
|
||||||
|
local mac
|
||||||
|
mac=$(saved_mac)
|
||||||
|
[ -n "$mac" ] || { log "no saved choice in $CONF"; return 1; }
|
||||||
|
echo "$mac"
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_auto() {
|
||||||
|
local mac n with=() names
|
||||||
|
mac=$(saved_mac)
|
||||||
|
if [ -n "$mac" ]; then
|
||||||
|
for n in $(wired); do
|
||||||
|
if [ "$(mac_of "$n")" = "$mac" ]; then
|
||||||
|
log "using the saved choice: $n ($mac)"
|
||||||
|
echo "$mac"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log "the saved imaging NIC $mac is not on this box any more"
|
||||||
|
hint "The saved imaging NIC $mac is gone (dongle replaced?). Log in and run: sudo pxe-pick-nic"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
links_up
|
||||||
|
for n in $(wired); do
|
||||||
|
[ "$(carrier "$n")" = 1 ] && with+=("$n")
|
||||||
|
done
|
||||||
|
log "wired ports:"
|
||||||
|
table
|
||||||
|
if [ ${#with[@]} -eq 0 ]; then
|
||||||
|
log "REFUSED: no wired port has a link. Plug the imaging switch into the port to use."
|
||||||
|
hint "Could not pick the imaging port: no port has a link. Plug in the imaging switch, then log in and run: sudo pxe-pick-nic"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ ${#with[@]} -gt 1 ]; then
|
||||||
|
names="${with[*]}"
|
||||||
|
log "REFUSED: ${#with[@]} ports have a link ($names). During the install only the imaging switch may be plugged in."
|
||||||
|
hint "Could not pick the imaging port: ${#with[@]} ports have a link ($names). Log in and run: sudo pxe-pick-nic"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
n=${with[0]}
|
||||||
|
if ! check "$n" strict; then
|
||||||
|
hint "Could not pick the imaging port: $n is not isolated (see /var/log/first-boot.log). Plug only the imaging switch in, then log in and run: sudo pxe-pick-nic"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
save "$n" >&2
|
||||||
|
mac_of "$n"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait until exactly one port changes its link; print "NAME NEWSTATE".
|
||||||
|
wait_change() {
|
||||||
|
local -A before=()
|
||||||
|
local n t=0 changed=()
|
||||||
|
for n in $(wired); do before[$n]=$(carrier "$n"); done
|
||||||
|
while [ "$t" -lt $((WAIT * 2)) ]; do
|
||||||
|
sleep 0.5
|
||||||
|
t=$((t + 1))
|
||||||
|
changed=()
|
||||||
|
for n in "${!before[@]}"; do
|
||||||
|
[ "$(carrier "$n")" != "${before[$n]}" ] && changed+=("$n")
|
||||||
|
done
|
||||||
|
if [ ${#changed[@]} -eq 1 ]; then
|
||||||
|
sleep 1 # a flapping connector settles
|
||||||
|
n=${changed[0]}
|
||||||
|
echo "$n $(carrier "$n")"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ ${#changed[@]} -gt 1 ]; then
|
||||||
|
log "more than one port changed (${changed[*]}); move one cable at a time" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
log "nothing changed in ${WAIT}s" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_interactive() {
|
||||||
|
local n state again ans
|
||||||
|
[ -t 0 ] || { log "interactive mode needs a terminal (use --auto from scripts)"; return 2; }
|
||||||
|
[ "$(id -u)" -eq 0 ] || { log "run it as root: sudo pxe-pick-nic"; return 2; }
|
||||||
|
links_up
|
||||||
|
echo
|
||||||
|
echo "Pick the imaging NIC: the port cabled to the isolated imaging switch."
|
||||||
|
echo "Wired ports now:"
|
||||||
|
table
|
||||||
|
echo
|
||||||
|
echo "Pull the imaging switch cable out of this server now"
|
||||||
|
echo "(or, if it is not plugged in yet, plug it in now). Waiting up to ${WAIT}s..."
|
||||||
|
read -r n state < <(wait_change) || return 1
|
||||||
|
[ -n "${n:-}" ] || return 1
|
||||||
|
if [ "$state" = 0 ]; then
|
||||||
|
echo "$n lost its link. Plug the cable back into the SAME port..."
|
||||||
|
read -r again state < <(wait_change) || return 1
|
||||||
|
if [ "${again:-}" != "$n" ] || [ "$state" != 1 ]; then
|
||||||
|
log "expected $n to come back up, saw ${again:-nothing} $state; try again"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep "$SETTLE"
|
||||||
|
echo "Imaging port: $n ($(mac_of "$n"))"
|
||||||
|
check "$n" || { log "nothing saved"; return 1; }
|
||||||
|
read -rp "Use $n ($(mac_of "$n")) as the imaging NIC? [y/N] " ans
|
||||||
|
[[ "$ans" =~ ^[Yy]$ ]] || { log "nothing saved"; return 1; }
|
||||||
|
save "$n"
|
||||||
|
if [ -e /var/lib/pxe-firstboot.failed ] && [ -x /opt/first-boot.sh ]; then
|
||||||
|
read -rp "Run the server setup again now? [Y/n] " ans
|
||||||
|
if [[ ! "$ans" =~ ^[Nn]$ ]]; then
|
||||||
|
rm -f /var/lib/pxe-firstboot/attempts
|
||||||
|
/opt/first-boot.sh 2>&1 | tee -a /var/log/first-boot.log
|
||||||
|
return "${PIPESTATUS[0]}"
|
||||||
|
fi
|
||||||
|
echo "Run it later with: sudo rm -f /var/lib/pxe-firstboot/attempts; sudo /opt/first-boot.sh"
|
||||||
|
else
|
||||||
|
echo "Saved. If the server is already set up, re-run the playbook from the"
|
||||||
|
echo "installer USB to move the imaging config onto this port."
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--auto) mode_auto ;;
|
||||||
|
--show) mode_show ;;
|
||||||
|
"") mode_interactive ;;
|
||||||
|
-h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
|
*) echo "usage: $0 [--auto | --show]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
# 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
|
||||||
# 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,17 +56,17 @@ 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
|
||||||
pause "Step 5 done"
|
pause "Step 5 done"
|
||||||
|
|
||||||
echo "=== Step 6/8: flush ARP / neighbour cache on br-pxe ==="
|
echo "=== Step 6/8: flush ARP / neighbour cache ==="
|
||||||
ip neigh flush dev br-pxe 2>&1 || ip neigh flush all 2>&1 || true
|
ip neigh flush all 2>&1 || true
|
||||||
pause "Step 6 done"
|
pause "Step 6 done"
|
||||||
|
|
||||||
echo "=== Step 7/8: drop tcp sockets on port 445 ==="
|
echo "=== Step 7/8: drop tcp sockets on port 445 ==="
|
||||||
|
|||||||
170
playbook/scripts/preflight.ps1
Executable file
@@ -0,0 +1,170 @@
|
|||||||
|
# preflight.ps1 - check the things PESetup fails on, before it fails on them.
|
||||||
|
#
|
||||||
|
# Called by startnet.cmd once the media is mapped. Prints a short report a tech
|
||||||
|
# can read at the bay and exits non-zero if a check is fatal.
|
||||||
|
#
|
||||||
|
# WHY EACH CHECK IS HERE - all four come from PESetup's own behaviour
|
||||||
|
# (docs/PESETUP-INTERNALS.md, decompiled 4.0.0.17, observed on 4.0.0.20):
|
||||||
|
#
|
||||||
|
# Secure boot GatherDataSelection fails the step outright when
|
||||||
|
# SecurebootEnabled != 1. Hard failure, minutes into a build.
|
||||||
|
# Disk size MinRequiredSpaceWithoutCompression is 128849018880 (120 GB).
|
||||||
|
# Driver match GetDriverByModel returns null on a miss and PESetup logs a
|
||||||
|
# WARNING and keeps going. The bay images with no drivers, so no
|
||||||
|
# NIC, so DNS fails at first boot and enrollment cannot reach the
|
||||||
|
# CDN. The symptom appears far from the cause - this is the check
|
||||||
|
# that earns the script.
|
||||||
|
# Media age The media expires 30 days after build. PESetup shows days-left
|
||||||
|
# on a screen nobody reads.
|
||||||
|
#
|
||||||
|
# Lives on the enrollment share so it can be fixed without rebuilding boot.wim.
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$MediaDrive = 'Z:',
|
||||||
|
[int]$MinDiskGB = 120,
|
||||||
|
[int]$MediaWarnDays = 25
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$fatal = 0
|
||||||
|
$warn = 0
|
||||||
|
|
||||||
|
function Ok { param($m) Write-Host (" [ OK ] " + $m) }
|
||||||
|
function Warn { param($m) Write-Host (" [WARN] " + $m); $script:warn++ }
|
||||||
|
function Fail { param($m) Write-Host (" [FAIL] " + $m); $script:fatal++ }
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "======== Pre-imaging checks ========"
|
||||||
|
|
||||||
|
# --- 1. Secure boot ------------------------------------------------------
|
||||||
|
# Confirm-SecureBootUEFI is not always present in WinPE; read the state the
|
||||||
|
# firmware exposes instead.
|
||||||
|
try {
|
||||||
|
$sb = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\State' `
|
||||||
|
-Name UEFISecureBootEnabled -ErrorAction Stop
|
||||||
|
if ($sb.UEFISecureBootEnabled -eq 1) {
|
||||||
|
Ok "Secure boot enabled"
|
||||||
|
} else {
|
||||||
|
Fail "Secure boot is OFF. PESetup will fail at GatherData. Enable it in BIOS."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Warn "Could not read secure boot state - if this is a legacy/CSM boot, PESetup will fail."
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 2. Disk size --------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$disk = Get-CimInstance Win32_DiskDrive -ErrorAction Stop |
|
||||||
|
Where-Object { $_.MediaType -like '*Fixed*' } |
|
||||||
|
Sort-Object Index | Select-Object -First 1
|
||||||
|
if ($disk) {
|
||||||
|
$gb = [math]::Round($disk.Size / 1GB, 1)
|
||||||
|
if ($gb -ge $MinDiskGB) {
|
||||||
|
Ok ("Disk 0 is {0} GB ({1})" -f $gb, $disk.Model)
|
||||||
|
} else {
|
||||||
|
Fail ("Disk 0 is only {0} GB; PESetup needs {1} GB. ({2})" -f $gb, $MinDiskGB, $disk.Model)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fail "No fixed disk found. PESetup has nothing to image."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Warn "Could not enumerate disks: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 3. Driver match for THIS model --------------------------------------
|
||||||
|
# Reimplements GetDriverByModel: family filter first (it knows only Latitude,
|
||||||
|
# OptiPlex and Precision), then a substring test of comma-separated tokens,
|
||||||
|
# first match wins. Tokens are NOT trimmed, matching the C#.
|
||||||
|
try {
|
||||||
|
$model = (Get-CimInstance Win32_ComputerSystem -ErrorAction Stop).Model
|
||||||
|
$catalogue = Join-Path $MediaDrive 'Deploy\Control\HardwareDriver.json'
|
||||||
|
if (-not (Test-Path $catalogue)) {
|
||||||
|
Warn "HardwareDriver.json not found at $catalogue - cannot check drivers."
|
||||||
|
} else {
|
||||||
|
# NOT ConvertFrom-Json. HardwareDriver.json carries both casings of the
|
||||||
|
# same fields - "fileName" and "FileName", "destinationDir" and
|
||||||
|
# "DestinationDir". Windows PowerShell 5.1 (which is what WinPE runs)
|
||||||
|
# treats object keys case-insensitively and throws:
|
||||||
|
# "cannot convert the JSON string because a dictionary that was
|
||||||
|
# converted from the string contains the duplicated keys 'FileName'
|
||||||
|
# and 'FileName'"
|
||||||
|
# -AsHashtable would handle it but that is PowerShell 6+. So pull the
|
||||||
|
# four fields we need out of each entry by text instead. Prefers the
|
||||||
|
# lowercase key, falls back to the capitalised one.
|
||||||
|
$raw = Get-Content $catalogue -Raw
|
||||||
|
$entries = @()
|
||||||
|
foreach ($chunk in ([regex]::Split($raw, '\}\s*,\s*\{'))) {
|
||||||
|
$get = {
|
||||||
|
param($names)
|
||||||
|
foreach ($n in $names) {
|
||||||
|
$m = [regex]::Match($chunk, '"' + $n + '"\s*:\s*"((?:[^"\\]|\\.)*)"')
|
||||||
|
if ($m.Success) { return $m.Groups[1].Value -replace '\\\\', '\' }
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
$e = [pscustomobject]@{
|
||||||
|
modelswminame = (& $get @('modelswminame','models'))
|
||||||
|
family = (& $get @('family'))
|
||||||
|
fileName = (& $get @('fileName','FileName'))
|
||||||
|
destinationDir = (& $get @('destinationDir','DestinationDir'))
|
||||||
|
}
|
||||||
|
if ($e.modelswminame) { $entries += $e }
|
||||||
|
}
|
||||||
|
if (-not $entries.Count) {
|
||||||
|
Warn "Could not extract any entries from $catalogue - driver check skipped."
|
||||||
|
}
|
||||||
|
$family = ''
|
||||||
|
if ($model.ToUpper().Contains('LATITUDE')) { $family = 'Latitude' }
|
||||||
|
if ($model.ToUpper().Contains('OPTIPLEX')) { $family = 'Optiplex' }
|
||||||
|
if ($model.ToUpper().Contains('PRECISION')) { $family = 'Precision' }
|
||||||
|
|
||||||
|
$hit = $null
|
||||||
|
foreach ($e in $entries) {
|
||||||
|
if ($family -and ($e.family -notlike "*$family*")) { continue }
|
||||||
|
foreach ($tok in ([string]$e.modelswminame).Split(',')) {
|
||||||
|
if ($tok -and $model.ToLower().Contains($tok.ToLower())) { $hit = $e; break }
|
||||||
|
}
|
||||||
|
if ($hit) { break }
|
||||||
|
}
|
||||||
|
if ($hit) {
|
||||||
|
$zip = Join-Path $MediaDrive (([string]$hit.destinationDir) -replace '\*destinationdir\*\\?','')
|
||||||
|
$zip = Join-Path $zip $hit.fileName
|
||||||
|
if (Test-Path $zip) {
|
||||||
|
Ok ("Driver pack for '{0}': {1}" -f $model, $hit.fileName)
|
||||||
|
} else {
|
||||||
|
Fail ("Driver pack for '{0}' is listed but MISSING on the media: {1}" -f $model, $hit.fileName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fail ("NO driver pack matches '{0}'. PESetup logs this as a warning only - the bay will image with NO network drivers." -f $model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Warn "Driver check failed: $_"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 4. Media age --------------------------------------------------------
|
||||||
|
# Approximate: PESetup expires media 30 days after build and Media.tag is
|
||||||
|
# rewritten when the media is rebuilt, so its timestamp is the best local proxy.
|
||||||
|
try {
|
||||||
|
$tag = Join-Path $MediaDrive 'Deploy\Control\Media.tag'
|
||||||
|
if (Test-Path $tag) {
|
||||||
|
$age = [int]((Get-Date) - (Get-Item $tag).LastWriteTime).TotalDays
|
||||||
|
if ($age -ge $MediaWarnDays) {
|
||||||
|
Warn ("Media is about {0} days old; it expires at 30. Rebuild it soon." -f $age)
|
||||||
|
} else {
|
||||||
|
Ok ("Media is about {0} days old" -f $age)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
Write-Host "===================================="
|
||||||
|
if ($fatal -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " $fatal BLOCKING problem(s) found. Imaging this bay will not work."
|
||||||
|
Write-Host ""
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
if ($warn -gt 0) { Write-Host " $warn warning(s), no blockers." }
|
||||||
|
else { Write-Host " All checks passed." }
|
||||||
|
Write-Host ""
|
||||||
|
exit 0
|
||||||
@@ -6,6 +6,11 @@ set "BIOSDIR=%~dp0"
|
|||||||
set "FLASH=%BIOSDIR%Flash64W.exe"
|
set "FLASH=%BIOSDIR%Flash64W.exe"
|
||||||
set "MANIFEST=%BIOSDIR%models.txt"
|
set "MANIFEST=%BIOSDIR%models.txt"
|
||||||
|
|
||||||
|
REM --- Report this machine to the PXE webapp (Hardware Coverage) ---
|
||||||
|
REM Every WinPE boot maps this share, whatever image is picked next, so this
|
||||||
|
REM is the one place that sees standard/engineer builds too. Best effort.
|
||||||
|
if exist "%BIOSDIR%hardware-report.ps1" powershell -NoProfile -ExecutionPolicy Bypass -File "%BIOSDIR%hardware-report.ps1" >NUL 2>&1
|
||||||
|
|
||||||
if exist "%FLASH%" goto :flash_ok
|
if exist "%FLASH%" goto :flash_ok
|
||||||
echo Flash64W.exe not found, skipping BIOS check.
|
echo Flash64W.exe not found, skipping BIOS check.
|
||||||
set "BIOS_STATUS=Skipped (Flash64W.exe missing)"
|
set "BIOS_STATUS=Skipped (Flash64W.exe missing)"
|
||||||
@@ -110,7 +115,8 @@ exit /b 0
|
|||||||
|
|
||||||
:flash_done
|
:flash_done
|
||||||
echo BIOS update complete.
|
echo BIOS update complete.
|
||||||
set "BIOS_STATUS=%SYSMODEL% updated %BIOSVER% -^> %TARGETVER%"
|
set "BIOS_STATUS=%SYSMODEL% updated %BIOSVER% to %TARGETVER%"
|
||||||
|
echo flash_done %SYSMODEL% %BIOSVER% to %TARGETVER%> X:\bios-fired.flag
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
:staged
|
:staged
|
||||||
@@ -121,7 +127,8 @@ echo It will flash during POST after the
|
|||||||
echo post-imaging reboot.
|
echo post-imaging reboot.
|
||||||
echo ========================================
|
echo ========================================
|
||||||
echo.
|
echo.
|
||||||
set "BIOS_STATUS=%SYSMODEL% STAGED %BIOSVER% -^> %TARGETVER% (flashes on reboot)"
|
set "BIOS_STATUS=%SYSMODEL% STAGED %BIOSVER% to %TARGETVER% (flashes on reboot)"
|
||||||
|
echo staged %SYSMODEL% %BIOSVER% to %TARGETVER%> X:\bios-fired.flag
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
:compare_versions
|
:compare_versions
|
||||||
|
|||||||
58
playbook/shopfloor-setup/BIOS/hardware-report.ps1
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# hardware-report.ps1 - Report this machine's hardware to the PXE webapp so
|
||||||
|
# its Hardware Coverage page can flag models with no driver pack or BIOS.
|
||||||
|
#
|
||||||
|
# Called by check-bios.cmd, which every WinPE boot runs from the BIOS share
|
||||||
|
# before the imaging menu, so it reports every boot - including ones that
|
||||||
|
# never pick an image - rather than only those that reach the Y: status push.
|
||||||
|
#
|
||||||
|
# Sends the exact WMI model PESetup matches driver packs on, the Dell
|
||||||
|
# system SKU (what Dell's catalogs key on) and the BIOS version. Runs in
|
||||||
|
# WinPE's stripped PowerShell: wmic only, no CIM cmdlets. Best effort:
|
||||||
|
# logs to X:\Windows\Temp\hardware-report.log and never fails the boot.
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$PxeServer = $(if ($env:PXESERVER) { $env:PXESERVER } else { '172.16.9.1' }),
|
||||||
|
[int]$Port = 9009,
|
||||||
|
[int]$TimeoutSec = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
$logFile = 'X:\Windows\Temp\hardware-report.log'
|
||||||
|
function Log {
|
||||||
|
param([string]$msg)
|
||||||
|
try { "$(Get-Date -Format s) $msg" | Out-File -FilePath $logFile -Append -Encoding utf8 } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-WmicValue {
|
||||||
|
param([string]$Class, [string]$Property)
|
||||||
|
try {
|
||||||
|
$line = (& wmic $Class get $Property /value 2>$null |
|
||||||
|
Where-Object { $_ -match '=' } | Select-Object -First 1)
|
||||||
|
if ($line) { return ($line -split '=', 2)[1].Trim() }
|
||||||
|
} catch { Log "wmic $Class $Property exception: $_" }
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
$hw = @{
|
||||||
|
serial = Get-WmicValue 'bios' 'SerialNumber'
|
||||||
|
model = Get-WmicValue 'computersystem' 'Model'
|
||||||
|
manufacturer = Get-WmicValue 'computersystem' 'Manufacturer'
|
||||||
|
sku = Get-WmicValue 'computersystem' 'SystemSKUNumber'
|
||||||
|
bios_version = Get-WmicValue 'bios' 'SMBIOSBIOSVersion'
|
||||||
|
}
|
||||||
|
Log "model=$($hw.model) sku=$($hw.sku) bios=$($hw.bios_version) serial=$($hw.serial)"
|
||||||
|
|
||||||
|
if (-not $hw.model) {
|
||||||
|
Log "no model - nothing to report"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resp = Invoke-WebRequest -Uri "http://${PxeServer}:${Port}/hardware/seen" -Method POST `
|
||||||
|
-Body ($hw | ConvertTo-Json -Compress) -ContentType 'application/json' `
|
||||||
|
-UseBasicParsing -TimeoutSec $TimeoutSec -ErrorAction Stop
|
||||||
|
Log "OK http=$($resp.StatusCode)"
|
||||||
|
} catch {
|
||||||
|
Log "ERR $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -1,51 +1,73 @@
|
|||||||
# ModelSubstring|BIOSFile|Version
|
# ModelSubstring|BIOSFile|Version
|
||||||
13 Plus PB13250|Dell_Pro_PA13250_PA14250_PB13250_PB14250_PB16250_2.8.1.exe|2.8.1
|
13 Plus PB13250|Dell_Pro_PA13250_PA14250_PB13250_PB14250_PB16250_2.18.1.exe|2.18.1
|
||||||
14 MC14250|Dell_Pro_Max_MC16250_MC14250_1.9.0.exe|1.9.0
|
14 MC14250|Dell_Pro_Max_MC16250_MC14250_1.19.2.exe|1.19.2
|
||||||
14 PC14250|Dell_Pro_PC14250_PC16250_1.7.0.exe|1.7.0
|
14 PC14250|Dell_Pro_PC14250_PC16250_1.18.0.exe|1.18.0
|
||||||
14 Premium MA14250|Dell_Pro_Max_MA14250_MA16250_1.7.1.exe|1.7.1
|
14 Premium MA14250|Dell_Pro_Max_MA14250_MA16250_1.13.1.exe|1.13.1
|
||||||
16 Plus MB16250|Dell_Pro_Max_MB16250_MB18250_2.2.2.exe|2.2.2
|
16 Plus MB16250|Dell_Pro_Max_MB16250_MB18250_3.3.2.exe|3.3.2
|
||||||
24250|Dell_Pro_Plus_QB24250_QC24250_QC24251_1.9.2.exe|1.9.2
|
QB24250|Dell_Pro_Plus_QB24250_QC24250_QC24251_Combine_1.18.0.exe|1.18.0
|
||||||
5430|Latitude_5430_7330_Rugged_1.41.0.exe|1.41.0
|
QC24250|Dell_Pro_Plus_QB24250_QC24250_QC24251_Combine_1.18.0.exe|1.18.0
|
||||||
7080|OptiPlex_7080_1.37.0.exe|1.37.0
|
QC24251|Dell_Pro_Plus_QB24250_QC24250_QC24251_Combine_1.18.0.exe|1.18.0
|
||||||
7090|OptiPlex_7090_1.40.0.exe|1.40.0
|
5430 Rugged|Latitude_5430_7330_Rugged_1.45.0.exe|1.45.0
|
||||||
7220 Rugged|Latitude_7220_Rugged_Extreme_Tablet_1.50.0.exe|1.50.0
|
7330 Rugged|Latitude_5430_7330_Rugged_1.45.0.exe|1.45.0
|
||||||
7230 Rugged Extreme Tablet|Latitude_7230_1.30.1.exe|1.30.1
|
Latitude 5430|Latitude_5430_1.41.0.exe|1.41.0
|
||||||
7320 2-in-1 Detachable|Dell_Latitude_7320_Detachable_1.45.0_64.exe|1.45.0
|
7080|OptiPlex_7080_1.39.0.exe|1.39.0
|
||||||
7350 Detachable|Latitude_7350_Detachable_1.9.1.exe|1.9.1
|
7090 Ultra|OptiPlex_7090_UFF_1.44.0.exe|1.44.0
|
||||||
7400 AIO|Latitude_7X00_1.43.0.exe|1.43.0
|
7090|OptiPlex_7090_1.44.0.exe|1.44.0
|
||||||
AIO Plus 7410|Latitude_7X10_1.43.0.exe|1.43.0
|
7220 Rugged|Latitude_7220_Rugged_Extreme_Tablet_1.53.0.exe|1.53.0
|
||||||
AIO Plus 7420|Latitude_7X20_1.48.0.exe|1.48.0
|
7230 Rugged Extreme Tablet|Latitude_7230_1.34.0.exe|1.34.0
|
||||||
Latitude 5330|Latitude_5330_1.33.0.exe|1.33.0
|
7320 Detachable|Dell_Latitude_7320_Detachable_1.49.0_64.exe|1.49.0
|
||||||
Latitude 5340|Latitude_5340_1.27.1.exe|1.27.1
|
7350 Detachable|Latitude_7350_Detachable_1.24.0.exe|1.24.0
|
||||||
Latitude 5350|Latitude_5350_1.20.0.exe|1.20.0
|
7400 AIO|OptiPlex_7400_1.1.61.exe|1.1.61
|
||||||
Latitude 5440|Latitude_5440_Precision_3480_1.28.1.exe|1.28.1
|
7400 All-in-One|OptiPlex_7400_1.1.61.exe|1.1.61
|
||||||
Latitude 5450|Latitude_5450_Precision_3490_1.20.1.exe|1.20.1
|
AIO Plus 7410|OptiPlex_7410_1.37.0.exe|1.37.0
|
||||||
Latitude 5530|Precision_5530_1.42.0.exe|1.42.0
|
AIO Plus 7420|OptiPlex_7420_1.27.0.exe|1.27.0
|
||||||
Latitude 5540|Precision_5540_1.42.0.exe|1.42.0
|
Latitude 5330|Latitude_5330_1.38.0.exe|1.38.0
|
||||||
Latitude 7430|Latitude_7X30_1.38.0.exe|1.38.0
|
Latitude 5340|Latitude_5340_1.31.0.exe|1.31.0
|
||||||
Latitude 7440|Latitude_7X40_1.28.1.exe|1.28.1
|
Latitude 5350|Latitude_5350_1.26.0.exe|1.26.0
|
||||||
Latitude 7450|OptiPlex_7450_1.34.0.exe|1.34.0
|
Latitude 5440|Latitude_5440_Precision_3480_1.34.0.exe|1.34.0
|
||||||
Micro 7010|OptiPlex_7010_1.34.0_SEMB.exe|1.34.0
|
Latitude 5450|Latitude_5450_Precision_3490_1.26.0.exe|1.26.0
|
||||||
SFF Plus 7010|OptiPlex_7010_1.34.0_SEMB.exe|1.34.0
|
Latitude 5530|Precision_3570_Latitude_5530_1.38.0.exe|1.38.0
|
||||||
Tower Plus 7010|OptiPlex_7010_1.34.0_SEMB.exe|1.34.0
|
Latitude 5540|Precision_3580_Latitude_5540_1.31.0.exe|1.31.0
|
||||||
Micro QCM1250|Dell_Pro_QBT1250_QBS1250_QBM1250_QCT1250_QCS1250_QCM1250_SEMB_1.12.2.exe|1.12.2
|
Latitude 7430|Latitude_7X30_1.42.0.exe|1.42.0
|
||||||
OptiPlex 3000|OptiPlex_3000_1.38.0.exe|1.38.0
|
Latitude 7440|Latitude_7X40_1.34.0.exe|1.34.0
|
||||||
OptiPlex 7000|OptiPlex_7000_1.38.0.exe|1.38.0
|
Latitude 7450|Latitude_7X50_1.26.0.exe|1.26.0
|
||||||
Precision 5490|OptiPlex_5490_AIO_1.45.0.exe|1.45.0
|
Micro 7010|OptiPlex_7010_1.38.0_SEMB.exe|1.38.0
|
||||||
Precision 5550|Precision_3590_3591_Latitude_5550_1.8.0.exe|1.8.0
|
SFF Plus 7010|OptiPlex_7010_1.38.0_SEMB.exe|1.38.0
|
||||||
Precision 5570|XPS9520_Precision5570_1.39.0_QSL0.exe|1.39.0
|
Tower Plus 7010|OptiPlex_7010_1.38.0_SEMB.exe|1.38.0
|
||||||
Precision 5680|Precision_5680_1_27_0.exe|1.27.0
|
Micro 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 5690|Precision_5690_1.18.0.exe|1.18.0
|
Micro Plus 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 5820 Tower|Precision_5820_2.48.0.exe|2.48.0
|
SFF 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 5860 Tower|Precision_5860_3.5.0.exe|3.5.0
|
SFF Plus 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 7560|Precision_7X60_1.44.1.exe|1.44.1
|
Tower 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 7670|Precision_7X70_1.8.0.exe|1.8.0
|
Tower Plus 7020|OptiPlex_7020_1.28.1_SEMB.exe|1.28.1
|
||||||
Precision 7680|Precision_7X80_1.9.0.exe|1.9.0
|
Micro QCM1250|Dell_Pro_QBT1250_QBS1250_QBM1250_QCT1250_QCS1250_QCM1250_SEMB_1.19.3.exe|1.19.3
|
||||||
Precision 7770|OptiPlex_7770_7470_1.40.0.exe|1.40.0
|
Tower Plus QBT1250|Dell_Pro_QBT1250_QBS1250_QBM1250_QCT1250_QCS1250_QCM1250_SEMB_1.19.3.exe|1.19.3
|
||||||
Precision 7780|OptiPlex_7780_7480_1.43.0.exe|1.43.0
|
OptiPlex 3000 Thin Client|OptiPlex_TC_1.37.0.exe|1.37.0
|
||||||
Precision 7820 Tower|Precision_7820_7920_2.50.0.exe|2.50.0
|
OptiPlex 3000|OptiPlex_3000_1.42.0.exe|1.42.0
|
||||||
Precision 7865 Tower|Precision_7865_1.6.1.exe|1.6.1
|
OptiPlex 7000|OptiPlex_7000_1.42.0.exe|1.42.0
|
||||||
Precision 7875 Tower|Precision_7875_SHP_02.07.03.exe|2.7.3
|
Precision 5490|Precision_5490_1.24.0.exe|1.24.0
|
||||||
Rugged 14 RB14250|Dell_Pro_Rugged_RB14250_RA13250_1.13.1.exe|1.13.1
|
Precision 5550|Precision_5550_1.42.0.exe|1.42.0
|
||||||
Tower Plus 7020|OptiPlex_7020_1.22.1_SEMB.exe|1.22.1
|
Precision 5570|XPS9520_Precision5570_1.42.0_QSL0.exe|1.42.0
|
||||||
Tower Plus QBT1250|Dell_Pro_QBT1250_QBS1250_QBM1250_QCT1250_QCS1250_QCM1250_SEMB_1.12.2.exe|1.12.2
|
Precision 5680|Precision_5680_1_29_0.exe|1.29.0
|
||||||
|
Precision 5690|Precision_5690_1.26.0.exe|1.26.0
|
||||||
|
Precision 5820 Tower|Precision_5820_2.50.2.exe|2.50.2
|
||||||
|
Precision 5860 Tower|Precision_5860_3.10.0.exe|3.10.0
|
||||||
|
Precision 7560|Precision_7X60_1.49.0.exe|1.49.0
|
||||||
|
Precision 7670|Precision_7x70_1.40.0.exe|1.40.0
|
||||||
|
Precision 7680|Precision_7X80_1.32.0.exe|1.32.0
|
||||||
|
Precision 7770|Precision_7x70_1.40.0.exe|1.40.0
|
||||||
|
Precision 7780|Precision_7X80_1.32.0.exe|1.32.0
|
||||||
|
Precision 7820 Tower|Precision_7820_7920_2.53.1.exe|2.53.1
|
||||||
|
Precision 7865 Tower|Precision_7865_1.29.0.exe|1.29.0
|
||||||
|
Precision 7875 Tower|Precision_7875_SHP_02.12.00.exe|2.12.0
|
||||||
|
Rugged 14 RB14250|Dell_Pro_Rugged_RB14250_RA13250_1.19.1.exe|1.19.1
|
||||||
|
P314260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P316260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P514260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P516260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P713260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P703260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P714260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
P704260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
PW514260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
PW516260|Dell_Pro_P31x260_P51x260_P7xx260_Dell_Pro_Precision_PW51x260_2.4.2.exe|2.4.2
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Register-BulkEnrollTask-Standard.ps1 - register the BulkEnrollmentOrchestrator
|
||||||
|
# logon task for the gea-standard and gea-engineer images.
|
||||||
|
#
|
||||||
|
# WHY THIS IS A FILE AND NOT AN INLINE COMMAND
|
||||||
|
# This ran inline as "powershell.exe -Command ..." in the unattend's specialize
|
||||||
|
# pass at RunSynchronousCommand Order 39. RunSynchronousCommand/Path is capped at
|
||||||
|
# 259 characters by the unattend schema; the inline form was 604, so Windows
|
||||||
|
# Setup rejected the ENTIRE answer file:
|
||||||
|
#
|
||||||
|
# /settings/RunSynchronous/RunSynchronousCommand/[Order="39"]/Path
|
||||||
|
# Description = Value is invalid. hrResult = 0x80220005 pass = specialize
|
||||||
|
#
|
||||||
|
# One over-length value invalidates every pass, so the machine stops at a dialog
|
||||||
|
# with nothing configured.
|
||||||
|
#
|
||||||
|
# WHY THERE ARE TWO OF THESE SCRIPTS
|
||||||
|
# gea-shopfloor uses Register-BulkEnrollTask.ps1, which launches the orchestrator
|
||||||
|
# as a PowerShell script with -ManualFallback: shopfloor bays are vanilla by
|
||||||
|
# design, so the orchestrator skips BPRT injection and the provisioning package.
|
||||||
|
# gea-standard and gea-engineer enrol normally and call the .cmd wrapper instead.
|
||||||
|
# All three image types share ONE Deploy\Applications\BPRT directory (a symlink
|
||||||
|
# to _shared\Applications\BPRT), so the two variants need distinct filenames.
|
||||||
|
#
|
||||||
|
# PESetup's CopyPackages copies the whole of <media>\Deploy\Applications to the
|
||||||
|
# target before the image is applied, so this script is present at
|
||||||
|
# C:\Deploy\Applications\BPRT\ by the time specialize runs. See
|
||||||
|
# docs/PESETUP-INTERNALS.md.
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$taskName = 'BulkEnrollmentOrchestrator'
|
||||||
|
$orchestrator = 'C:\Deploy\Applications\BPRT\Start-BulkEnrollOrchestrator.cmd'
|
||||||
|
|
||||||
|
# Self-deleting task: schtasks /Delete drops the registration, then the
|
||||||
|
# orchestrator runs. cmd's & chains them so the delete happens even if the
|
||||||
|
# orchestrator later reboots the machine.
|
||||||
|
$argument = '/c schtasks /Delete /TN {0} /F & "{1}"' -f $taskName, $orchestrator
|
||||||
|
|
||||||
|
$action = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument $argument
|
||||||
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
# S-1-5-32-544 = BUILTIN\Administrators. GroupId + Highest so it runs elevated
|
||||||
|
# for whichever admin logs on first, without naming an account.
|
||||||
|
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-544' -RunLevel Highest
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 3)
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
|
||||||
|
|
||||||
|
Write-Host "Registered scheduled task '$taskName'."
|
||||||