From 9d01d237c2b89fcd2644c5af49d66dcec8acc81f Mon Sep 17 00:00:00 2001 From: Kroese Date: Tue, 4 Aug 2026 02:59:43 +0200 Subject: [PATCH] fix: Do not continue when drivers are missing (#2038) --- src/answer.sh | 2 +- src/define.sh | 62 +++++++++++--- src/image.sh | 228 ++++++++++++++++++++++--------------------------- src/install.sh | 117 +++++++++++++------------ src/mido.sh | 81 ++++++++++++------ 5 files changed, 271 insertions(+), 219 deletions(-) diff --git a/src/answer.sh b/src/answer.sh index d508ed01..d3149313 100644 --- a/src/answer.sh +++ b/src/answer.sh @@ -1856,9 +1856,9 @@ setConfigurationXML() { local asset="$1" local ns="urn:schemas-microsoft-com:unattend" + local setup='/*[local-name()="unattend"]/*[local-name()="settings" and @pass="windowsPE"]/*[local-name()="component" and @name="Microsoft-Windows-Setup"]' local userdata="$setup/*[local-name()='UserData']" local config="$setup/*[local-name()='UseConfigurationSet']" - local setup='/*[local-name()="unattend"]/*[local-name()="settings" and @pass="windowsPE"]/*[local-name()="component" and @name="Microsoft-Windows-Setup"]' local setup_count config_count config_value userdata_count result_count tmp [ -s "$asset" ] || return 1 diff --git a/src/define.sh b/src/define.sh index 6398a32a..4dc544ee 100644 --- a/src/define.sh +++ b/src/define.sh @@ -934,18 +934,6 @@ getVersion() { return 0 } -skipVersion() { - - local id="$1" - - case "${id,,}" in - "win9"* | "winxp"* | "win2k"* | "win2003"* | "reactos" ) - return 0 ;; - esac - - return 1 -} - isLegacy() { local id="$1" @@ -959,6 +947,56 @@ isLegacy() { return 1 } +supportsXML() { + + local id="$1" + + case "${id,,}" in + "win9"* | "winxp"* | "win2k"* | "win2003"* | "reactos" ) + return 1 ;; + esac + + return 0 +} + +supportsUnattended() { + + local id="$1" + + case "${id,,}" in + "win9"* | "reactos" ) + return 1 ;; + esac + + return 0 +} + +getDriverFolder() { + + local id="$1" + + case "${id,,}" in + "win7x86"* ) echo "w7/x86" ;; + "win7x64"* ) echo "w7/amd64" ;; + "win81x64"* ) echo "w8.1/amd64" ;; + "win10x64"* ) echo "w10/amd64" ;; + "win11x64"* ) echo "w11/amd64" ;; + "win2025"* ) echo "2k25/amd64" ;; + "win2022"* ) echo "2k22/amd64" ;; + "win2019"* ) echo "2k19/amd64" ;; + "win2016"* ) echo "2k16/amd64" ;; + "win2012"* ) echo "2k12R2/amd64" ;; + "win2008"* ) echo "2k8R2/amd64" ;; + "win10arm64"* ) echo "w10/ARM64" ;; + "win11arm64"* ) echo "w11/ARM64" ;; + "winvistax86"* ) echo "2k8/x86" ;; + "winvistax64"* ) echo "2k8/amd64" ;; + * ) return 1 ;; + esac + + return 0 +} + switchEdition() { local version="$1" diff --git a/src/image.sh b/src/image.sh index 70e59de3..4a279585 100644 --- a/src/image.sh +++ b/src/image.sh @@ -1,113 +1,6 @@ #!/usr/bin/env bash set -Eeuo pipefail -hasVersion() { - - local wanted="$1" - shift - - local actual - - for actual in "$@"; do - [[ "${actual,,}" == "${wanted,,}" ]] || continue - echo "$actual" - return 0 - done - - return 1 -} - -getCompatibleVersions() { - - local wanted="$1" - - printf '%s\n' "$wanted" - - # Treat normal and Evaluation variants of the same edition as compatible. - # The exact requested variant is always checked first. - if [[ "${wanted,,}" == *"-eval" ]]; then - printf '%s\n' "${wanted%-eval}" - else - printf '%s\n' "$wanted-eval" - fi -} - -checkPlatform() { - - local xml="$1" - - local platform compat - - platform=$(getPlatform "$xml") - - case "${platform,,}" in - "x86" ) compat="x64" ;; - "x64" ) compat="$platform" ;; - "arm64" ) compat="$platform" ;; - "mixed" ) - error "Windows images with mixed architectures are not supported!" - return 1 - ;; - * ) compat="${PLATFORM,,}" ;; - esac - - [[ "${compat,,}" == "${PLATFORM,,}" ]] && return 0 - - error "You cannot boot ${platform^^} images on a $PLATFORM CPU!" - return 1 -} - -getPlatform() { - - local xml="$1" - - local output platform="x64" - local x86 x64 arm64 count=0 value - local -a counts=() - - if ! output=$(xmlstarlet sel \ - -T -t \ - -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="0"])' -n \ - -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="9"])' -n \ - -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="12"])' -n \ - - 2>/dev/null <<< "$xml"); then - return 1 - fi - - mapfile -t counts <<< "$output" - - if (( ${#counts[@]} != 3 )); then - error "Failed to read architecture counts from WIM metadata!" - return 1 - fi - - for value in "${counts[@]}"; do - if [[ ! "$value" =~ ^[0-9]+$ ]]; then - error "Invalid architecture count in WIM metadata: '$value'" - return 1 - fi - done - - x86="${counts[0]}" - x64="${counts[1]}" - arm64="${counts[2]}" - - (( x86 > 0 )) && ((count += 1)) - (( x64 > 0 )) && ((count += 1)) - (( arm64 > 0 )) && ((count += 1)) - - if (( count > 1 )); then - platform="mixed" - elif (( x86 > 0 )); then - platform="x86" - elif (( arm64 > 0 )); then - platform="arm64" - fi - - echo "$platform" - return 0 -} - getVersionPriority() { local id="${1,,}" @@ -571,19 +464,112 @@ getImageSize() { printf '%s\n' "$size" } -bootDirect() { +hasVersion() { - local id="$1" + local wanted="$1" + shift - # ReactOS must boot from its original media and does not use the Windows - # setup-overlay or rebuilt-image paths. - case "${id,,}" in - "reactos" ) return 0 ;; - esac + local actual + + for actual in "$@"; do + [[ "${actual,,}" == "${wanted,,}" ]] || continue + echo "$actual" + return 0 + done return 1 } +getCompatibleVersions() { + + local wanted="$1" + + printf '%s\n' "$wanted" + + # Treat normal and Evaluation variants of the same edition as compatible. + # The exact requested variant is always checked first. + if [[ "${wanted,,}" == *"-eval" ]]; then + printf '%s\n' "${wanted%-eval}" + else + printf '%s\n' "$wanted-eval" + fi +} + +checkPlatform() { + + local xml="$1" + local platform compat + + platform=$(getPlatform "$xml") || return 1 + + case "${platform,,}" in + "x86" ) compat="x64" ;; + "x64" ) compat="$platform" ;; + "arm64" ) compat="$platform" ;; + "mixed" ) + error "Windows images with mixed architectures are not supported!" + return 1 + ;; + * ) compat="${PLATFORM,,}" ;; + esac + + [[ "${compat,,}" == "${PLATFORM,,}" ]] && return 0 + + error "You cannot boot ${platform^^} images on a $PLATFORM CPU!" + return 1 +} + +getPlatform() { + + local xml="$1" + + local output platform="x64" + local x86 x64 arm64 count=0 value + local -a counts=() + + if ! output=$(xmlstarlet sel \ + -T -t \ + -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="0"])' -n \ + -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="9"])' -n \ + -v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="12"])' -n \ + - 2>/dev/null <<< "$xml"); then + return 1 + fi + + mapfile -t counts <<< "$output" + + if (( ${#counts[@]} != 3 )); then + error "Failed to read architecture counts from WIM metadata!" + return 1 + fi + + for value in "${counts[@]}"; do + if [[ ! "$value" =~ ^[0-9]+$ ]]; then + error "Invalid architecture count in WIM metadata: '$value'" + return 1 + fi + done + + x86="${counts[0]}" + x64="${counts[1]}" + arm64="${counts[2]}" + + (( x86 > 0 )) && ((count += 1)) + (( x64 > 0 )) && ((count += 1)) + (( arm64 > 0 )) && ((count += 1)) + + if (( count > 1 )); then + platform="mixed" + elif (( x86 > 0 )); then + platform="x86" + elif (( arm64 > 0 )); then + platform="arm64" + fi + + echo "$platform" + return 0 +} + canUseSetupImage() { local id="$1" @@ -927,7 +913,8 @@ resolveImage() { setImage() { - skipVersion "${DETECTED,,}" && return 0 + supportsXML "${DETECTED,,}" || return 0 + setXML "" && return 0 enabled "$MANUAL" && return 0 @@ -1079,15 +1066,8 @@ readIsoImageInfo() { # The metadata flag 0x02 is expected and deliberately allowed. (( !(xml_flags & 0x1c) )) || return 1 - result=$( - udfread range \ - --ignore-case \ - "$iso" \ - "$image" \ - "$xml_offset" \ - "$xml_size" \ - 2>/dev/null | - iconv -f UTF-16LE -t UTF-8 2>/dev/null + result=$(udfread range --ignore-case "$iso" "$image" "$xml_offset" "$xml_size" \ + 2>/dev/null | iconv -f UTF-16LE -t UTF-8 2>/dev/null ) || { local rc=$? diff --git a/src/install.sh b/src/install.sh index 522ec872..02d0efbb 100644 --- a/src/install.sh +++ b/src/install.sh @@ -3,19 +3,19 @@ set -Eeuo pipefail startWindows() { - parseVersion || return 58 - parseLanguage || return 62 - detectCustom || return 64 + parseVersion || exit 58 + parseLanguage || exit 62 + detectCustom || exit 64 if ! startInstall; then - bootWindows || return 66 + bootWindows || exit 66 return 0 fi if ! hasImage "$ISO"; then if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then removeIso "$ISO" || : - return 68 + exit 68 fi fi @@ -23,16 +23,16 @@ startWindows() { local dir="$TMP/unpack" local handled=0 extracted=0 - selectWindowsImage "$ISO" "$dir" "$boot" || return $? + selectWindowsImage "$ISO" "$dir" "$boot" || exit $? (( handled )) && return 0 - configureMachine "$ISO" "$dir" "$boot" || return $? + configureMachine "$ISO" "$dir" "$boot" || exit $? (( handled )) && return 0 - prepareWindowsImage "$ISO" "$dir" "$boot" || return $? + prepareWindowsImage "$ISO" "$dir" "$boot" || exit $? (( handled )) && return 0 - finishInstall "$BOOT" "N" "$boot" || return 100 + finishInstall "$BOOT" "N" "$boot" || exit 100 return 0 } @@ -124,8 +124,7 @@ configureMachine() { return 0 fi - # Direct-boot media skips all unattended installation preparation. - if bootDirect "$DETECTED"; then + if ! supportsUnattended "$DETECTED"; then abortInstall "$dir" "$iso" "$boot" || return 83 handled=1 return 0 @@ -259,7 +258,7 @@ startInstall() { skipInstall "$BOOT" "$previousBase" && return 1 if [ -z "$previousBase" ] && hasDisk; then - if ! backup ""; then + if ! backupPrevious ""; then warn "the backup was incomplete, continuing with installation..." fi fi @@ -421,7 +420,7 @@ skipInstall() { info "Detected that $method, a backup of your previous installation will be saved..." - if ! backup "$STORAGE/$previousBase"; then + if ! backupPrevious "$STORAGE/$previousBase"; then warn "the backup was incomplete, continuing with installation..." fi @@ -476,8 +475,9 @@ finishInstall() { local secure=0 - # Enable secure boot + TPM on manual installs as Win11 requires - if enabled "$MANUAL" || [[ "$aborted" == [Yy1]* ]]; then + # Aborted Win11 installs boot without any answer file present, + # so enable Secure Boot and TPM to satisfy its hardware checks. + if [[ "$aborted" == [Yy1]* ]] || enabled "$MANUAL"; then [[ "${DETECTED,,}" == "win11"* ]] && secure=1 fi @@ -573,13 +573,28 @@ needsExtraction() { local id="$1" local iso="$2" - # Direct-boot media does not need rebuilding. Legacy/skipped versions, - # standalone ESD downloads, and nested archives require full extraction. - bootDirect "$id" && return 1 + # Media without unattended support boots directly from the original ISO. + if ! supportsUnattended "$id"; then + return 1 + fi - skipVersion "$id" || - [[ "${iso,,}" == *".esd" ]] || - enabled "${UNPACK:-}" + # SIF-based legacy installers must be extracted and rebuilt. + if ! supportsXML "$id"; then + return 0 + fi + + # Standalone ESD downloads must be extracted before they can be prepared. + if [[ "${iso,,}" == *".esd" ]]; then + return 0 + fi + + # Nested archives must be extracted to expose their contained ISO. + if enabled "${UNPACK:-}"; then + return 0 + fi + + # Modern bootable ISOs can use the original media with a setup overlay. + return 1 } checkFreeSpace() { @@ -887,11 +902,7 @@ setMachine() { local dir="$3" local desc="$4" - if [[ "${id,,}" != "win9"* ]]; then - ETFS="boot/etfsboot.com" - else - ETFS="[BOOT]/Boot-1.44M.img" - fi + ETFS="boot/etfsboot.com" local version="" case "${id,,}" in @@ -1005,7 +1016,7 @@ prepareImage() { getBootLoadSize "$iso" "$dir" "$desc" || return 1 fi - skipVersion "$DETECTED" && return 0 + supportsXML "$DETECTED" || return 0 if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then @@ -1135,40 +1146,31 @@ addDriver() { local target="$3" local driver="$4" - local folder="" desc + local folder desc if [ -z "$id" ]; then - warn "no Windows version specified for \"$driver\" driver!" && return 0 + warn "no Windows version specified for \"$driver\" driver!" + return 1 fi - case "${id,,}" in - "win7x86"* ) folder="w7/x86" ;; - "win7x64"* ) folder="w7/amd64" ;; - "win81x64"* ) folder="w8.1/amd64" ;; - "win10x64"* ) folder="w10/amd64" ;; - "win11x64"* ) folder="w11/amd64" ;; - "win2025"* ) folder="2k25/amd64" ;; - "win2022"* ) folder="2k22/amd64" ;; - "win2019"* ) folder="2k19/amd64" ;; - "win2016"* ) folder="2k16/amd64" ;; - "win2012"* ) folder="2k12R2/amd64" ;; - "win2008"* ) folder="2k8R2/amd64" ;; - "win10arm64"* ) folder="w10/ARM64" ;; - "win11arm64"* ) folder="w11/ARM64" ;; - "winvistax86"* ) folder="2k8/x86" ;; - "winvistax64"* ) folder="2k8/amd64" ;; - esac + if ! folder=$(getDriverFolder "$id"); then + folder="" + fi if [ -z "$folder" ]; then + desc=$(printVersion "$id" "$id") - if [[ "${id,,}" != *"x86"* ]]; then - warn "no \"$driver\" driver available for \"$desc\" !" && return 0 + + if [[ "${id,,}" == *"x86"* ]]; then + warn "no \"$driver\" driver available for the 32-bit version of \"$desc\" !" else - warn "no \"$driver\" driver available for the 32-bit version of \"$desc\" !" && return 0 + warn "no \"$driver\" driver available for \"$desc\" !" fi + + return 1 fi - [ ! -d "$path/$driver/$folder" ] && return 0 + [ -d "$path/$driver/$folder" ] || return 0 case "${id,,}" in "winvista"* ) @@ -1176,6 +1178,7 @@ addDriver() { esac local dest="$path/$target/$driver" + mkdir -p "$dest" || return 1 cp -Lr "$path/$driver/$folder/." "$dest" || return 1 @@ -1275,7 +1278,7 @@ addDrivers() { fi - rm -rf "$drivers" + rm -rf "$drivers" || return 1 return 0 } @@ -1285,7 +1288,7 @@ stageSetup() { local language="$2" local stage="$3" - skipVersion "${DETECTED,,}" && return 0 + supportsUnattended "${DETECTED,,}" || return 0 local msg="Creating overlay image..." info "$msg" && html "$msg" @@ -1301,12 +1304,12 @@ stageSetup() { fi if ! addDrivers "$stage" "$stage" "$DETECTED"; then - error "Failed to stage Windows drivers!" + error "Failed to include Windows drivers!" return 1 fi if ! addFolder "$stage" "image" "Y" "overlay"; then - error "Failed to stage OEM folder!" + error "Failed to include OEM folder!" return 1 fi @@ -1328,7 +1331,7 @@ updateImage() { local dat="${xml//.xml/.dat}" local desc path src wim name info - skipVersion "${DETECTED,,}" && return 0 + supportsUnattended "${DETECTED,,}" || return 0 if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then asset="" @@ -1370,10 +1373,12 @@ updateImage() { if ! addDrivers "$src" "$tmp" "$DETECTED" "$wim" "$idx"; then error "Failed to add drivers to image!" + return 1 fi if ! addFolder "$src"; then error "Failed to add OEM folder to image!" + return 1 fi # Preserve an original answer file only once. The .dat marker identifies an @@ -1496,7 +1501,7 @@ reserveSambaPorts() { return 0 } -backup () { +backupPrevious () { local iso="$1" diff --git a/src/mido.sh b/src/mido.sh index 7a50b10c..662ac512 100644 --- a/src/mido.sh +++ b/src/mido.sh @@ -1059,6 +1059,11 @@ tryDownload() { local total minimum="104857600" + if [ -z "$iso" ] || [ -z "$url" ]; then + error "Invalid download parameters!" + return 1 + fi + # Compressed archives can legitimately be much smaller than the ISO they # contain, so use a lower sanity threshold until extraction. if isCompressed "$url"; then @@ -1114,6 +1119,7 @@ fallbackEnglish() { local culture web_msg local msg="No working download method was found for $desc, falling back to English..." + info "$msg" # Preserve the requested regional format and keyboard layout. @@ -1133,24 +1139,12 @@ fallbackEnglish() { downloadImage "$iso" "$version" "$LANGUAGE" } -downloadImage() { +validDownload() { - local iso="$1" - local version="$2" - local lang="$3" + local version="$1" - local requested="$version" - local detected="$DETECTED" - local tried="n" success="n" seconds="5" - local i url sum size base language desc web_desc - - if [[ "${version,,}" == "http"* ]]; then - - base=$(basename "$iso") - desc=$(fromFile "$base") - web_desc="$desc" - - tryDownload "$iso" "$version" "" "" "" "$desc" "$seconds" "$web_desc" && return 0 + if [ -z "$version" ]; then + error "Cannot download a Windows image without a version!" return 1 fi @@ -1159,6 +1153,32 @@ downloadImage() { return 1 fi + return 0 +} + +downloadImage() { + + local iso="$1" + local version="$2" + local lang="$3" + + local detected="$DETECTED" + local requested="$version" switched="" + local tried="n" success="n" seconds="5" + local i url sum size base language desc web_desc + + if [[ "${version,,}" == "http"* ]]; then + + base=$(basename "$iso") + desc=$(fromFile "$base") + web_desc="$desc" + + tryDownload "$iso" "$version" "" "" "" "$desc" "$seconds" "$web_desc" || return 1 + return 0 + fi + + validDownload "$version" || return 1 + desc=$(printVariant "$version" "" "Y") web_desc=$(printVariant "$version" "") @@ -1167,12 +1187,14 @@ downloadImage() { language=$(getLanguage "$lang" "desc") if ! validVersion "$version" "$lang"; then + desc=$(printEdition "$version" "$desc" "Y") web_desc=$(printEdition "$version" "$web_desc") desc+=" in $language" - fallbackEnglish "$iso" "$version" "$lang" "$desc" "$web_desc" && return 0 - return 1 + fallbackEnglish "$iso" "$version" "$lang" "$desc" "$web_desc" || return 1 + return 0 + fi desc+=" in $language" @@ -1220,10 +1242,13 @@ downloadImage() { fi fi - # Some editions share another download route. Update the effective version - # before looking up ESD catalogs and mirrors. - if version=$(switchEdition "$version"); then - + # If an evaluation version was requested, switch to the + # normal edition since none of our mirrors provide those. + if switched=$(switchEdition "$version"); then + + validDownload "$switched" || return 1 + version="$switched" + if ! enabled "${DETECTED_ORG:-}"; then DETECTED="${SUGGEST:-$version}" fi @@ -1268,7 +1293,7 @@ downloadImage() { fi fi - for ((i=1;i<=MIRRORS;i++)); do + for ((i=1; i<=MIRRORS; i++)); do url=$(getLink "$i" "$version" "$lang") @@ -1285,12 +1310,16 @@ downloadImage() { tryDownload "$iso" "$url" "$sum" "$size" "$lang" "$desc" "$seconds" "$web_desc" && return 0 fi + done if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then - if fallbackEnglish "$iso" "$requested" "$lang" "$desc" "$web_desc"; then - return 0 - fi + fallbackEnglish "$iso" "$requested" "$lang" "$desc" "$web_desc" || return 1 + return 0 + fi + + if [[ "$tried" == "n" ]]; then + error "No download method is available for $desc!" fi return 1