Adds a local-install pipeline so Standard shopfloor PCs get Oracle, the
VC++ redists (2008-2022), and UDC installed during PXE imaging via Samba
instead of pulling ~215 MB per device from Azure blob over the corporate
WAN. Intune DSC then verifies (already-installed apps are skipped) and
the only Azure traffic on the happy path is ~11 KB of CustomScripts
wrapper polling.
New files:
- playbook/preinstall/preinstall.json — curated app list with PCTypes
filter and per-app detection rules. Install order puts VC++ 2008
LAST so its (formerly) reboot-triggering bootstrapper doesn't kill
the runner mid-loop. (2008 itself now uses extracted vc_red.msi with
REBOOT=ReallySuppress; the reorder is defense in depth.)
- playbook/shopfloor-setup/Shopfloor/00-PreInstall-MachineApps.ps1 —
the runner. Numbered 00- so it runs first in the baseline sequence.
Reads preinstall.json, filters by PCTYPE, polls for completion via
detection check (handles UDC's hung WPF process by killing it once
detection passes), uses synchronous WriteThrough logging that
survives hard reboots, preserves log history across runs.
- playbook/shopfloor-setup/Standard/Set-MachineNumber.{ps1,bat} — desktop
helper for SupportUser. Reads current UDC + eDNC machine numbers,
prompts via VB InputBox, validates digits-only, kills running UDC,
edits both C:\ProgramData\UDC\udc_settings.json and HKLM\…\GE Aircraft
Engines\DNC\General\MachineNo, relaunches UDC. Lets a tech assign a
real machine number to a mass-produced PC without admin/LAPS.
- playbook/sync-preinstall.sh — workstation helper to push installer
binaries from /home/camp/pxe-images/main/ to the live PXE Samba.
Changes:
- playbook/startnet.cmd + startnet-template.cmd — add xcopy to stage
preinstall bundle from Y:\preinstall\ to W:\PreInstall\ during the
WinPE imaging phase, gated on PCTYPE being set.
- playbook/pxe_server_setup.yml — create /srv/samba/enrollment/preinstall
+ installers/ directories and deploy preinstall.json there.
- playbook/shopfloor-setup/Run-ShopfloorSetup.ps1 — bump AutoLogonCount
to 99 at start (defense against any installer triggering an immediate
reboot mid-dispatcher; final line still resets to 2 on successful
completion). Copy Set-MachineNumber.{ps1,bat} to SupportUser desktop
on Standard PCs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
4.7 KiB
Bash
Executable File
152 lines
4.7 KiB
Bash
Executable File
#!/bin/bash
|
|
# sync-preinstall.sh — Push preinstall.json + installer binaries to the live PXE server.
|
|
#
|
|
# Run this on the workstation (not on the PXE server) any time:
|
|
# - You update preinstall.json in playbook/preinstall/
|
|
# - You update installer binaries in /home/camp/pxe-images/main/
|
|
#
|
|
# The PXE server's /srv/samba/enrollment/preinstall/ tree is updated in place. The
|
|
# next imaged PC picks up the new files via startnet.cmd's xcopy during WinPE phase.
|
|
#
|
|
# Usage:
|
|
# ./playbook/sync-preinstall.sh
|
|
#
|
|
# Requires: sshpass (apt install sshpass), scp, ssh
|
|
|
|
set -euo pipefail
|
|
|
|
# --- Config ---
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
PXE_HOST="${PXE_HOST:-10.9.100.1}"
|
|
PXE_USER="${PXE_USER:-pxe}"
|
|
PXE_PASS="${PXE_PASS:-pxe}"
|
|
|
|
PREINSTALL_JSON="$PROJECT_ROOT/playbook/preinstall/preinstall.json"
|
|
PXE_IMAGES_DIR="${PXE_IMAGES_DIR:-/home/camp/pxe-images/main}"
|
|
|
|
REMOTE_DIR="/srv/samba/enrollment/preinstall"
|
|
REMOTE_INSTALLERS="$REMOTE_DIR/installers"
|
|
REMOTE_TEMP="/tmp/preinstall-stage"
|
|
|
|
# Files to push (source paths under PXE_IMAGES_DIR).
|
|
# vc_red.msi + vc_red.cab are extracted from vcredist2008_x86.exe and used directly
|
|
# (instead of the bootstrapper) because the 2008 bootstrapper ignores /norestart and
|
|
# triggers an immediate reboot. The MSI honors REBOOT=ReallySuppress, the bootstrapper
|
|
# does not. The .cab MUST be named "vc_red.cab" exactly because that name is hardcoded
|
|
# in the MSI's Media table.
|
|
INSTALLERS=(
|
|
"dependencies/vc_red.msi"
|
|
"dependencies/vc_red.cab"
|
|
"dependencies/vcredist2010_x86.exe"
|
|
"dependencies/vcredist2012_x86.exe"
|
|
"dependencies/vcredist2013_x86.exe"
|
|
"dependencies/vcredist2015_2017_2019_2022_x86.exe"
|
|
"machineapps/UDC_Setup.exe"
|
|
"globalassets/oracleclient/Oracle 10.2.0.3.msi"
|
|
)
|
|
|
|
# --- Helpers ---
|
|
ssh_run() {
|
|
sshpass -p "$PXE_PASS" ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR "$PXE_USER@$PXE_HOST" "$@"
|
|
}
|
|
|
|
scp_to() {
|
|
# Remote path is double-escaped: outer ssh layer + inner shell layer.
|
|
# Wrap in single-quotes inside the destination so spaces in filenames survive.
|
|
sshpass -p "$PXE_PASS" scp -o StrictHostKeyChecking=no -o LogLevel=ERROR "$1" "$PXE_USER@$PXE_HOST:'$2'"
|
|
}
|
|
|
|
# --- Validate sources ---
|
|
echo "Validating source files..."
|
|
|
|
if [ ! -f "$PREINSTALL_JSON" ]; then
|
|
echo "ERROR: preinstall.json not found at $PREINSTALL_JSON" >&2
|
|
exit 1
|
|
fi
|
|
|
|
missing=0
|
|
for rel in "${INSTALLERS[@]}"; do
|
|
src="$PXE_IMAGES_DIR/$rel"
|
|
if [ ! -f "$src" ]; then
|
|
echo " MISSING: $src" >&2
|
|
missing=$((missing + 1))
|
|
else
|
|
printf " OK %10d %s\n" "$(stat -c %s "$src")" "$rel"
|
|
fi
|
|
done
|
|
|
|
if [ "$missing" -gt 0 ]; then
|
|
echo "ERROR: $missing installer file(s) missing in $PXE_IMAGES_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Verify PXE server reachable ---
|
|
echo "Pinging PXE server $PXE_HOST..."
|
|
if ! ping -c 1 -W 2 "$PXE_HOST" >/dev/null 2>&1; then
|
|
echo "ERROR: PXE server $PXE_HOST not reachable" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Stage to /tmp on PXE, then sudo install ---
|
|
echo "Staging files to $PXE_HOST:$REMOTE_TEMP..."
|
|
ssh_run "mkdir -p $REMOTE_TEMP && rm -f $REMOTE_TEMP/*"
|
|
|
|
# preinstall.json
|
|
echo " -> preinstall.json"
|
|
scp_to "$PREINSTALL_JSON" "$REMOTE_TEMP/preinstall.json"
|
|
|
|
# installers (preserve filenames including spaces)
|
|
for rel in "${INSTALLERS[@]}"; do
|
|
src="$PXE_IMAGES_DIR/$rel"
|
|
base="$(basename "$rel")"
|
|
echo " -> $base"
|
|
scp_to "$src" "$REMOTE_TEMP/$base"
|
|
done
|
|
|
|
# --- Build remote install script (runs under sudo on PXE) ---
|
|
LOCAL_TEMP_SCRIPT="$(mktemp /tmp/sync-preinstall-remote.XXXXXX.sh)"
|
|
trap 'rm -f "$LOCAL_TEMP_SCRIPT"' EXIT
|
|
|
|
cat > "$LOCAL_TEMP_SCRIPT" <<REMOTE_SCRIPT
|
|
#!/bin/bash
|
|
set -e
|
|
mkdir -p "$REMOTE_INSTALLERS"
|
|
|
|
# preinstall.json -> /srv/samba/enrollment/preinstall/preinstall.json
|
|
cp "$REMOTE_TEMP/preinstall.json" "$REMOTE_DIR/preinstall.json"
|
|
chmod 0644 "$REMOTE_DIR/preinstall.json"
|
|
chown root:root "$REMOTE_DIR/preinstall.json"
|
|
|
|
# All other files -> installers/
|
|
shopt -s dotglob nullglob
|
|
for f in "$REMOTE_TEMP"/*; do
|
|
base="\$(basename "\$f")"
|
|
if [ "\$base" != "preinstall.json" ] && [ "\$base" != "_install.sh" ]; then
|
|
cp "\$f" "$REMOTE_INSTALLERS/\$base"
|
|
chmod 0644 "$REMOTE_INSTALLERS/\$base"
|
|
chown root:root "$REMOTE_INSTALLERS/\$base"
|
|
fi
|
|
done
|
|
|
|
rm -rf "$REMOTE_TEMP"
|
|
|
|
echo
|
|
echo "Final state of $REMOTE_DIR:"
|
|
ls -la "$REMOTE_DIR"
|
|
echo
|
|
echo "Final state of $REMOTE_INSTALLERS:"
|
|
ls -la "$REMOTE_INSTALLERS"
|
|
REMOTE_SCRIPT
|
|
|
|
# Stage the install script alongside the data files
|
|
scp_to "$LOCAL_TEMP_SCRIPT" "$REMOTE_TEMP/_install.sh"
|
|
|
|
# Execute remotely with sudo
|
|
echo "Installing files to $REMOTE_DIR (sudo)..."
|
|
ssh_run "echo '$PXE_PASS' | sudo -S bash $REMOTE_TEMP/_install.sh"
|
|
|
|
echo
|
|
echo "Done. Preinstall bundle synced to $PXE_HOST:$REMOTE_DIR"
|