Preinstall: extract MSIs for all VC++ redists to suppress reboot
The Microsoft VC++ bootstrappers (vcredist*_x86.exe) ignore /norestart and trigger immediate Windows reboots when CRT DLLs are in use, which in practice is always. We saw this break a live Standard PC imaging run (installlog showed the manual shutdown -a sequence between runs). Fix follows the existing 2008 pattern: extract the inner MSIs from each Burn bundle, run them via msiexec with REBOOT=ReallySuppress (a hard Windows Installer property the bootstrapper can't override), and treat exit 3010 as success. Files are now staged per-version under dependencies/vcredist/<version>/ because each MSI's Media table hardcodes its CAB filename, so the pairs would otherwise collide. preinstall.json: 4 EXE entries replaced with 8 MSI entries (Min+Add for 2012/2013/2022 because each version's Burn bundle ships them as separate MSIs). 2008 also moved into the same vcredist/2008/ subdir for consistency. ProductCodes verified against the existing detection paths (the previous "bootstrapper" GUIDs were actually the Min runtime GUIDs inherited up the chain). sync-preinstall.sh: now tarballs the dependencies/vcredist/ subtree to preserve directory structure across the scp+sudo-cp boundary, flat installers (UDC, Oracle) still copied individually, and the remote install script now removes the legacy flat vc_red.msi/cab plus the obsolete vcredist*_x86.exe bootstrappers on every sync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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": ["*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" <<REMOTE_SCRIPT
|
||||
#!/bin/bash
|
||||
@@ -119,17 +145,32 @@ 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/
|
||||
# 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"
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user