Files
pxe-server/playbook/sync-preinstall.sh
cproudlock a363fa31c0 OpenText: migrate from Intune Win32 LOB to PXE PreInstall + DSC
OpenText HostExplorer ShopFloor was previously delivered as an Intune
Win32 LOB app that ran the inner OpenTextHostExplorer15x64.msi directly,
which (a) skipped the [Files] section of the WJDT-built Inno Setup
wrapper that deploys profile/keymap/menu/macro files, and (b) deployed
desktop shortcuts pointing at C:\\GE Aerospace\\Hummingbird\\ - a path
HostExplorer doesn't search, so the profile would open from the desktop
shortcut but the keymaps and macros never got picked up.

This commit moves the install to the PXE PreInstall pipeline so it
gets baked into every Standard PC during imaging instead of being
pulled per-device by Intune. The DSC side ships separately as
Setup-OpenText.ps1 + Install-OpenText.ps1 wrapper in the
pxe-images/main/ tree (uploaded to Azure Blob).

preinstall.json: new entry for OpenText pointing at
opentext\\Setup-OpenText.cmd, a tiny launcher in the bundled subtree
that hands off to Setup-OpenText.ps1 (the runner only handles MSI/EXE
types). No DetectionMethod fields - Setup-OpenText.ps1 reads version
from version.txt next to itself and short-circuits via its own
HKLM\\SOFTWARE\\GE\\OpenText\\Installed marker check, so the version
constant lives in exactly one place (version.txt). Trade-off: ~1s
PowerShell launch on every up-to-date runner pass instead of a
zero-cost registry compare, in exchange for never having to bump
the version in multiple places.

sync-preinstall.sh: added dependencies/opentext to TREE_SUBDIRS so
the whole bundle (base MSI + cab + SP1 patch + ShopFloor transform +
profile/accessories/keymap/menu/W10shortcuts content + Setup-OpenText
script and cmd wrapper + version.txt) rides through the existing tar
pipe. Also added OpenText.exe to the legacy-cleanup rm list since the
old flat machineapps/OpenText.exe path is now obsolete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 10:10:35 -04:00

209 lines
6.8 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, tar
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"
# Subtrees copied recursively into installers/, preserving directory structure.
#
# vcredist/ holds per-version MSI+CAB pairs (one subdir per version) extracted from
# the Microsoft bootstrappers. The bootstrappers ignore /norestart and trigger
# immediate Windows reboots; the extracted MSIs honor REBOOT=ReallySuppress and do
# not. Each MSI's CAB filename ('vc_red.cab' for 2008/2010, 'cab1.cab' for the rest)
# is hardcoded in the MSI's Media table - do not rename.
#
# opentext/ holds the OpenText HostExplorer 15 SP1 install bundle (base MSI + cab,
# ShopFloor.mst transform, SP1 .msp patch, plus the Profile/Accessories/HostExplorer/
# W10shortcuts content trees and Setup-OpenText.ps1/.cmd). Setup-OpenText.ps1 runs
# msiexec, applies SP1, then deploys profile content into ProgramData\Shared,
# Default User, and every existing user profile. The .cmd wrapper exists so the
# preinstall.json runner (which only knows MSI/EXE types) can launch it.
TREE_SUBDIRS=(
"dependencies/vcredist"
"dependencies/opentext"
)
# Individual files copied flat (basename only) into installers/.
FLAT_INSTALLERS=(
"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() {
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 tree in "${TREE_SUBDIRS[@]}"; do
src="$PXE_IMAGES_DIR/$tree"
if [ ! -d "$src" ]; then
echo " MISSING: $src (directory)" >&2
missing=$((missing + 1))
else
size=$(du -sb "$src" | cut -f1)
printf " OK %10d %s/ (tree)\n" "$size" "$tree"
fi
done
for rel in "${FLAT_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 source(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
# --- Build local tarball of all TREE_SUBDIRS ---
LOCAL_TARBALL="$(mktemp /tmp/preinstall-trees.XXXXXX.tar)"
trap 'rm -f "$LOCAL_TARBALL" "${LOCAL_TEMP_SCRIPT:-}"' EXIT
echo "Building local tarball of subtrees..."
tar_args=()
for tree in "${TREE_SUBDIRS[@]}"; do
parent_dir="$PXE_IMAGES_DIR/$(dirname "$tree")"
leaf="$(basename "$tree")"
tar_args+=( -C "$parent_dir" "$leaf" )
done
tar cf "$LOCAL_TARBALL" "${tar_args[@]}"
echo " -> $(stat -c %s "$LOCAL_TARBALL") bytes"
# --- Stage to /tmp on PXE, then sudo install ---
echo "Staging files to $PXE_HOST:$REMOTE_TEMP..."
ssh_run "rm -rf $REMOTE_TEMP && mkdir -p $REMOTE_TEMP/flat"
# preinstall.json
echo " -> preinstall.json"
scp_to "$PREINSTALL_JSON" "$REMOTE_TEMP/preinstall.json"
# tarball of subtrees
echo " -> trees.tar"
scp_to "$LOCAL_TARBALL" "$REMOTE_TEMP/trees.tar"
# flat installers (preserve filenames including spaces, all into flat/)
for rel in "${FLAT_INSTALLERS[@]}"; do
src="$PXE_IMAGES_DIR/$rel"
base="$(basename "$rel")"
echo " -> $base"
scp_to "$src" "$REMOTE_TEMP/flat/$base"
done
# --- Build remote install script (runs under sudo on PXE) ---
LOCAL_TEMP_SCRIPT="$(mktemp /tmp/sync-preinstall-remote.XXXXXX.sh)"
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"
# Remove legacy flat VC++ files (replaced by vcredist/ subdir tree). These were the
# pre-Burn-extraction layout where 2008's MSI/CAB sat at the root and 2010/2012/2013/
# 2015 used bootstrappers directly. All four now live under vcredist/<version>/.
rm -f "$REMOTE_INSTALLERS/vc_red.msi" \
"$REMOTE_INSTALLERS/vc_red.cab" \
"$REMOTE_INSTALLERS/vcredist2008_x86.exe" \
"$REMOTE_INSTALLERS/vcredist2010_x86.exe" \
"$REMOTE_INSTALLERS/vcredist2012_x86.exe" \
"$REMOTE_INSTALLERS/vcredist2013_x86.exe" \
"$REMOTE_INSTALLERS/vcredist2015_2017_2019_2022_x86.exe"
# Remove legacy flat OpenText.exe (Inno wrapper, replaced by dependencies/opentext/
# subtree + Setup-OpenText.ps1).
rm -f "$REMOTE_INSTALLERS/OpenText.exe"
# Extract tree tarball into installers/ (preserves subdirs)
tar xf "$REMOTE_TEMP/trees.tar" -C "$REMOTE_INSTALLERS/"
# Flat installers -> installers/
shopt -s dotglob nullglob
for f in "$REMOTE_TEMP/flat/"*; do
base="\$(basename "\$f")"
cp "\$f" "$REMOTE_INSTALLERS/\$base"
done
# Normalize ownership and perms (files 0644, dirs 0755, all root:root)
chown -R root:root "$REMOTE_INSTALLERS"
find "$REMOTE_INSTALLERS" -type f -exec chmod 0644 {} +
find "$REMOTE_INSTALLERS" -type d -exec chmod 0755 {} +
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"
echo
echo "vcredist tree:"
find "$REMOTE_INSTALLERS/vcredist" -type f -printf '%10s %p\n' 2>/dev/null | sort -k2
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"