diff --git a/playbook/preinstall/preinstall.json b/playbook/preinstall/preinstall.json index 7b894c3..46d1e2d 100644 --- a/playbook/preinstall/preinstall.json +++ b/playbook/preinstall/preinstall.json @@ -14,41 +14,82 @@ "PCTypes": ["*"] }, { + "_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", + "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": "vcredist2010_x86.exe", - "Type": "EXE", - "InstallArgs": "/quiet /norestart", + "Installer": "vcredist/2010/installer.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart REBOOT=ReallySuppress", "DetectionMethod": "Registry", "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5}", "PCTypes": ["*"] }, { - "Name": "VC++ Redistributable 2012 x86", - "Installer": "vcredist2012_x86.exe", - "Type": "EXE", - "InstallArgs": "/quiet /norestart", + "_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", "DetectionMethod": "Registry", "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BD95A8CD-1D9F-35AD-981A-3E7925026EBB}", "PCTypes": ["*"] }, { - "Name": "VC++ Redistributable 2013 x86", - "Installer": "vcredist2013_x86.exe", - "Type": "EXE", - "InstallArgs": "/quiet /norestart", + "Name": "VC++ Redistributable 2012 x86 (Additional)", + "Installer": "vcredist/2012-add/installer.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart REBOOT=ReallySuppress", + "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", "DetectionMethod": "Registry", "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{13A4EE12-23EA-3371-91EE-EFB36DDFFF3E}", "PCTypes": ["*"] }, { - "Name": "VC++ Redistributable 2015-2022 x86", - "Installer": "vcredist2015_2017_2019_2022_x86.exe", - "Type": "EXE", - "InstallArgs": "/install /quiet /norestart", + "Name": "VC++ Redistributable 2013 x86 (Additional)", + "Installer": "vcredist/2013-add/installer.msi", + "Type": "MSI", + "InstallArgs": "/qn /norestart REBOOT=ReallySuppress", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{F8CFEB22-A2E7-3971-9EDA-4B11EDEFC185}", + "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", "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", + "DetectionMethod": "Registry", + "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{C18FB403-1E88-43C8-AD8A-CED50F23DE8B}", + "PCTypes": ["*"] + }, { "Name": "UDC", "Installer": "UDC_Setup.exe", @@ -57,16 +98,6 @@ "DetectionMethod": "Registry", "DetectionPath": "HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\UDC", "PCTypes": ["Standard"] - }, - { - "_comment": "VC++ 2008 — installed via the extracted vc_red.msi (NOT the bootstrapper) because vcredist2008_x86.exe ignores /norestart (per Aaron Stebner's Microsoft docs). REBOOT=ReallySuppress is a Windows Installer property that hard-blocks reboots even when files are in use; msiexec may return 3010 but won't actually reboot. vc_red.cab must live in the same directory as vc_red.msi or msiexec can't find the data files.", - "Name": "VC++ Redistributable 2008 x86", - "Installer": "vc_red.msi", - "Type": "MSI", - "InstallArgs": "/qn /norestart REBOOT=ReallySuppress", - "DetectionMethod": "Registry", - "DetectionPath": "HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{9BE518E6-ECC6-35A9-88E4-87755C07200F}", - "PCTypes": ["*"] } ] } diff --git a/playbook/sync-preinstall.sh b/playbook/sync-preinstall.sh index 880a050..3e76efb 100755 --- a/playbook/sync-preinstall.sh +++ b/playbook/sync-preinstall.sh @@ -1,5 +1,5 @@ #!/bin/bash -# sync-preinstall.sh — Push preinstall.json + installer binaries to the live PXE server. +# 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/ @@ -11,7 +11,7 @@ # Usage: # ./playbook/sync-preinstall.sh # -# Requires: sshpass (apt install sshpass), scp, ssh +# Requires: sshpass (apt install sshpass), scp, ssh, tar set -euo pipefail @@ -30,19 +30,18 @@ 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" +# 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. +TREE_SUBDIRS=( + "dependencies/vcredist" +) + +# Individual files copied flat (basename only) into installers/. +FLAT_INSTALLERS=( "machineapps/UDC_Setup.exe" "globalassets/oracleclient/Oracle 10.2.0.3.msi" ) @@ -53,8 +52,6 @@ ssh_run() { } 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'" } @@ -67,7 +64,19 @@ if [ ! -f "$PREINSTALL_JSON" ]; then fi missing=0 -for rel in "${INSTALLERS[@]}"; do + +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 @@ -78,7 +87,7 @@ for rel in "${INSTALLERS[@]}"; do done if [ "$missing" -gt 0 ]; then - echo "ERROR: $missing installer file(s) missing in $PXE_IMAGES_DIR" >&2 + echo "ERROR: $missing source(s) missing in $PXE_IMAGES_DIR" >&2 exit 1 fi @@ -89,25 +98,42 @@ if ! ping -c 1 -W 2 "$PXE_HOST" >/dev/null 2>&1; then 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 "mkdir -p $REMOTE_TEMP && rm -f $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" -# installers (preserve filenames including spaces) -for rel in "${INSTALLERS[@]}"; do +# 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/$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)" -trap 'rm -f "$LOCAL_TEMP_SCRIPT"' EXIT cat > "$LOCAL_TEMP_SCRIPT" < installers/ +# 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//. +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" + +# 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"/*; do +for f in "$REMOTE_TEMP/flat/"*; 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 + 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 @@ -138,6 +179,9 @@ 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