feat: Simplify WIM platform and version detection (#2108)

This commit is contained in:
Kroese
2026-08-08 03:53:42 +02:00
committed by GitHub
parent 0b76ed9744
commit 4ae04facfa
2 changed files with 193 additions and 396 deletions
+16 -71
View File
@@ -762,27 +762,23 @@ normalizeServerEdition() {
edition="${edition#r2-}" edition="${edition#r2-}"
case "$edition" in case "$edition" in
"core" | "core-installation" | "server-core-installation" ) "core" | "server-core" | "servercore" | \
"core-installation" | "server-core-installation" )
edition="standard-core" ;; edition="standard-core" ;;
"desktop-experience" | "server-with-a-gui" | "full-installation" ) "desktop-experience" | "server-with-a-gui" | "full-installation" )
edition="standard" ;; edition="standard" ;;
*"-server-core-installation" ) *"-server-core-installation" | *"-core-installation" )
edition="${edition%-server-core-installation}-core" ;; edition="${edition%-server-core-installation}"
*"-core-installation" )
edition="${edition%-core-installation}-core" ;; edition="${edition%-core-installation}-core" ;;
*"-desktop-experience" ) *"-desktop-experience" | *"-server-with-a-gui" | *"-full-installation" )
edition="${edition%-desktop-experience}" ;; edition="${edition%-desktop-experience}"
*"-server-with-a-gui" ) edition="${edition%-server-with-a-gui}"
edition="${edition%-server-with-a-gui}" ;;
*"-full-installation" )
edition="${edition%-full-installation}" ;; edition="${edition%-full-installation}" ;;
esac esac
edition="${edition#server-}" edition="${edition#server-}"
edition="${edition#server}" edition="${edition#server}"
[ "$edition" == "core" ] && edition="standard-core"
echo "$edition" echo "$edition"
return 0 return 0
} }
@@ -794,24 +790,13 @@ normalizeServerEditionID() {
edition=$(normalizeServerEdition "$1") || return 1 edition=$(normalizeServerEdition "$1") || return 1
case "$edition" in case "$edition" in
"" | "standard" | "serverstandard" ) edition="" ;; "standard" ) edition="" ;;
"core" | "standard-core" | "standardcore" | "serverstandardcore" ) edition="standard-core" ;; "standardcore" | "datacentercore" | "enterprisecore" | "webcore" )
"datacenter" | "serverdatacenter" ) edition="datacenter" ;; edition="${edition%core}-core" ;;
"datacenter-core" | "datacentercore" | "serverdatacentercore" ) edition="datacenter-core" ;; "datacenter-azure-edition" | "datacenterazureedition" )
"datacenter-azure" | "datacenter-azure-edition" | "datacenterazureedition" | \ edition="datacenter-azure" ;;
"serverdatacenterazureedition" | "serverturbine" ) edition="datacenter-azure" ;; "datacenter-azure-edition-core" | "datacenterazureeditioncore" )
"datacenter-azure-core" | "datacenter-azure-edition-core" | \ edition="datacenter-azure-core" ;;
"datacenterazureeditioncore" | "serverdatacenterazureeditioncore" | \
"serverturbinecore" ) edition="datacenter-azure-core" ;;
"enterprise" | "serverenterprise" ) edition="enterprise" ;;
"enterprise-core" | "enterprisecore" | "serverenterprisecore" ) edition="enterprise-core" ;;
"web" | "serverweb" ) edition="web" ;;
"web-core" | "webcore" | "serverwebcore" ) edition="web-core" ;;
"foundation" | "serverfoundation" ) edition="foundation" ;;
"essentials" | "serveressentials" ) edition="essentials" ;;
# Keep unrecognized internal edition IDs deterministic and unique.
# Known aliases above only provide stable, friendlier public names.
* ) : ;;
esac esac
echo "$edition" echo "$edition"
@@ -842,45 +827,6 @@ getServerEditionID() {
return 0 return 0
} }
getEditionOrder() {
local id="${1,,}"
case "$id" in
"win20"* )
printf '%s\n' \
"|default|@default" \
"-datacenter|datacenter|datacenter datacenter-*" \
"-datacenter-azure|datacenter|datacenter-azure" \
"-enterprise|enterprise|enterprise enterprise-*" \
"-web|web|web web-*" \
"-foundation|foundation|foundation foundation-*" \
"-essentials|essentials|essentials essentials-*" \
"-standard-core|standard-core|standard-core standard-core-*" \
"-datacenter-core|datacenter-core|datacenter-core datacenter-core-*" \
"-datacenter-azure-core|datacenter-core|datacenter-azure-core" \
"-enterprise-core|enterprise-core|enterprise-core enterprise-core-*" \
"-web-core|web-core|web-core web-core-*" \
"-hv|hv|hv hv-*"
;;
* )
printf '%s\n' \
"-enterprise|enterprise|enterprise enterprise-*" \
"-ultimate|ultimate|ultimate ultimate-*" \
"|default|@default n pro pro-* professional professional-* business business-*" \
"-iot|iot|iot iot-* enterprise-iot enterprise-iot-*" \
"-ltsc|ltsc|ltsc ltsc-* enterprise-ltsc enterprise-ltsc-*" \
"-education|education|education education-* pro-education pro-education-*" \
"-home|home|home home-*" \
"-home-premium|home|home-premium home-premium-*" \
"-home-basic|home|home-basic home-basic-*" \
"-starter|starter|starter starter-*"
;;
esac
return 0
}
getVersion() { getVersion() {
local name="$1" local name="$1"
@@ -899,8 +845,7 @@ getVersion() {
[ -n "$evaluation" ] && id+="$evaluation" [ -n "$evaluation" ] && id+="$evaluation"
fi fi
;; ;;
"win2025"* | "win2022"* | "win2019"* | "win2016"* | \ "win20"* )
"win2012"* | "win2008"* | "win2003"* )
if [[ "${name,,}" == *"hyper-v server"* ]]; then if [[ "${name,,}" == *"hyper-v server"* ]]; then
id+="-hv" id+="-hv"
elif edition=$(getServerEditionID "$name" "$id"); then elif edition=$(getServerEditionID "$name" "$id"); then
@@ -1453,7 +1398,7 @@ getLink4() {
"win2008r2" ) "win2008r2" )
size=3166584832 size=3166584832
sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8"
url="en_windows_server_2008_r2_with_sp1_x64_dvd_617601_202006/en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso" url="en_windows_server_2008_r2_with_sp1_x64_dvd_617601_202405/en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso"
;; ;;
"win7x64" | "win7x64-ultimate" ) "win7x64" | "win7x64-ultimate" )
size=3320903680 size=3320903680
+177 -325
View File
@@ -4,39 +4,17 @@ set -Eeuo pipefail
getVersions() { getVersions() {
local xml="$1" local xml="$1"
local versions_name="$2"
local bases_name="$3"
local groups_name="$4"
local indexes_name="$5"
local -n bases_ref="$bases_name" local image_index image edition_id
local -n groups_ref="$groups_name" local install_type candidate_base
local -n indexes_ref="$indexes_name" local candidate candidate_id flags
local -n versions_ref="$versions_name" local evaluation key name structured
local platform records display product
local platform image_count records record_count=0
local image_index display product image edition_id
local install_type flags candidate candidate_id
local candidate_base evaluation key name structured
local separator=$'\x1f' local separator=$'\x1f'
local -A indexes=()
bases_ref=()
groups_ref=()
indexes_ref=()
versions_ref=()
platform=$(getPlatform "$xml") || return 1 platform=$(getPlatform "$xml") || return 1
image_count=$(xmlstarlet sel -T -t -v 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 1
if [[ ! "$image_count" =~ ^[0-9]+$ ]]; then
error "Invalid image count in WIM metadata: '$image_count'"
return 1
fi
(( image_count > 0 )) || return 0
# Keep one compact record per image. XML 1.0 cannot contain U+001F, so it
# can safely separate fields while all edition logic remains in Bash.
if ! records=$(xmlstarlet sel \ if ! records=$(xmlstarlet sel \
-T -t \ -T -t \
-m '/WIM/IMAGE' \ -m '/WIM/IMAGE' \
@@ -54,8 +32,6 @@ getVersions() {
while IFS="$separator" read -r image_index display product image edition_id install_type flags; do while IFS="$separator" read -r image_index display product image edition_id install_type flags; do
((record_count += 1))
[ -n "$image_index" ] || continue [ -n "$image_index" ] || continue
if [[ ! "$image_index" =~ ^[1-9][0-9]*$ ]]; then if [[ ! "$image_index" =~ ^[1-9][0-9]*$ ]]; then
@@ -66,47 +42,41 @@ getVersions() {
candidate_id="" candidate_id=""
candidate_base="" candidate_base=""
# NAME normally contains the most precise edition identifier (including
# Server Core), while DISPLAYNAME is the best fallback for other images.
for candidate in "$image" "$display" "$product"; do for candidate in "$image" "$display" "$product"; do
[[ "$candidate" == *"Operating System"* ]] && continue [[ "$candidate" == *"Operating System"* ]] && continue
[ -n "$candidate" ] || continue [ -n "$candidate" ] || continue
candidate_base=$(fromName "$candidate" "$platform")
candidate_id=$(getVersion "$candidate" "$platform") candidate_id=$(getVersion "$candidate" "$platform")
candidate_base="${candidate_id%%-*}"
[ -n "$candidate_base" ] && [ -n "$candidate_id" ] && break [ -n "$candidate_id" ] && break
done done
# Preserve NAME precedence, but allow a recognized DISPLAYNAME edition to # NAME sometimes contains only the Windows family while DISPLAYNAME
# refine an otherwise generic family name. # contains the actual edition. Use the more specific DISPLAYNAME then.
if [ -n "$candidate_base" ] && [ -n "$candidate_id" ] && [ -n "$display" ]; then if [ -n "$candidate_base" ] && [ -n "$candidate_id" ] && [ -n "$display" ]; then
name=$(normalizeEdition "$(printVersion "$candidate_base" "")") || return 1 name=$(normalizeEdition "$(printVersion "$candidate_base" "")") || return 1
candidate=$(normalizeEdition "$candidate") || return 1 candidate=$(normalizeEdition "$candidate") || return 1
if [ "$candidate" = "$name" ]; then if [ "$candidate" = "$name" ]; then
structured=$(getVersion "$display" "$platform") structured=$(getVersion "$display" "$platform")
if [ "$(fromName "$display" "$platform")" = "$candidate_base" ] && if [ -n "$structured" ] && [ "${structured%%-*}" = "$candidate_base" ] &&
[ -n "$structured" ] && [[ "${structured%-eval}" != "$candidate_base" ]] && [[ "${structured%-eval}" != "$candidate_base" ]] &&
[[ "$(getVersionPriority "$structured" "$candidate_base")" != "other" ]]; then [ "$(getEditionRank "$structured")" -lt 99 ]; then
candidate_id="$structured" candidate_id="$structured"
fi fi
fi fi
fi fi
if [ -z "$candidate_base" ] || [ -z "$candidate_id" ]; then if [ -z "$candidate_base" ] || [ -z "$candidate_id" ]; then
name="${display:-${image:-$product}}" name="${display:-${image:-$product}}"
[ -n "$name" ] && warn "Unknown image name: '$name'" [ -n "$name" ] && warn "Unknown image name: '$name'"
continue continue
fi fi
evaluation="" evaluation=""
@@ -122,10 +92,9 @@ getVersions() {
key="${candidate_id,,}" key="${candidate_id,,}"
# Some client media use the same friendly name-derived ID for distinct # Friendly names can collapse two different images to the same ID.
# editions. Preserve the established unsuffixed Pro ID, and use the # Structured metadata is only needed to disambiguate that collision.
# structured edition metadata only to disambiguate a collision. if [[ -v "indexes[$key]" ]]; then
if [[ -v "indexes_ref[$key]" ]]; then
structured="" structured=""
@@ -136,9 +105,6 @@ getVersions() {
"win20"* ) "win20"* )
structured=$(normalizeServerEditionID "${flags:-$edition_id}") || return 1 structured=$(normalizeServerEditionID "${flags:-$edition_id}") || return 1
# Some media use the same EDITIONID for Core and Desktop images.
# INSTALLATIONTYPE provides the structural distinction without
# requiring a hardcoded marketing name.
if [[ "${install_type,,}" == *"core"* && "$structured" != *"-core" ]]; then if [[ "${install_type,,}" == *"core"* && "$structured" != *"-core" ]]; then
structured+="-core" structured+="-core"
fi fi
@@ -152,262 +118,187 @@ getVersions() {
fi fi
if [[ -v "indexes_ref[$key]" ]]; then if [[ -v "indexes[$key]" ]]; then
warn "Duplicate image identity '$candidate_id' at indexes ${indexes_ref[$key]} and $image_index" warn "Duplicate image identity '$candidate_id' at indexes ${indexes[$key]} and $image_index"
continue continue
fi fi
indexes_ref["$key"]="$image_index" indexes["$key"]="$image_index"
versions_ref+=( "$candidate_id" ) printf '%s\t%s\n' "$candidate_id" "$image_index"
bases_ref+=( "$candidate_base" )
groups_ref+=( "$(getVersionPriority "$candidate_id" "$candidate_base")" )
done <<< "$records" done <<< "$records"
if (( record_count != image_count )); then
error "Expected $image_count image records in WIM metadata, found $record_count!"
return 1
fi
return 0 return 0
} }
getVersionPriority() { getEditionRank() {
local id="${1,,}" local id="${1,,}"
local base="${2,,}" local base="${id%%-*}"
local edition
local entry priority patterns pattern
local result="other" score best_score=-1
local -a order=() pattern_list=()
id="${id%-eval}" id="${id%-eval}"
edition="${id#"$base"}"
mapfile -t order < <(getEditionOrder "$base")
local edition="${id#"$base"}"
edition="${edition#-}" edition="${edition#-}"
# Use the most specific matching pattern. This prevents broad patterns case "$base" in
# such as enterprise-* from taking precedence over enterprise-iot-*. "win20"* )
for entry in "${order[@]}"; do case "$edition" in
"" ) echo 0 ;;
IFS='|' read -r _ priority patterns <<< "$entry" "datacenter-azure-core" | "datacenter-azure-core-"* | \
read -r -a pattern_list <<< "$patterns" "datacenter-core" | "datacenter-core-"* ) echo 7 ;;
"enterprise-core" | "enterprise-core-"* ) echo 8 ;;
for pattern in "${pattern_list[@]}"; do "web-core" | "web-core-"* ) echo 9 ;;
"standard-core" | "standard-core-"* ) echo 6 ;;
if [ "$pattern" = "@default" ]; then "datacenter" | "datacenter-"* ) echo 1 ;;
[ -z "$edition" ] || continue "enterprise" | "enterprise-"* ) echo 2 ;;
score=1 "web" | "web-"* ) echo 3 ;;
elif [[ "$pattern" == *"*" ]]; then "foundation" | "foundation-"* ) echo 4 ;;
local prefix="${pattern%\*}" "essentials" | "essentials-"* ) echo 5 ;;
[[ "$edition" == "$prefix"* ]] || continue "hv" | "hv-"* ) echo 10 ;;
score="${#pattern}" * ) echo 99 ;;
elif [ "$edition" = "$pattern" ]; then esac
score="${#pattern}" ;;
else * )
continue case "$edition" in
fi "enterprise-iot" | "enterprise-iot-"* | "iot" | "iot-"* ) echo 3 ;;
"enterprise-ltsc" | "enterprise-ltsc-"* | "ltsc" | "ltsc-"* ) echo 4 ;;
if (( score > best_score )); then "pro-education" | "pro-education-"* | "education" | "education-"* ) echo 5 ;;
result="$priority" "enterprise" | "enterprise-"* ) echo 0 ;;
best_score="$score" "ultimate" | "ultimate-"* ) echo 1 ;;
fi "" | "n" | "pro" | "pro-"* | "professional" | "professional-"* | \
"business" | "business-"* ) echo 2 ;;
done "home" | "home-"* ) echo 6 ;;
"starter" | "starter-"* ) echo 7 ;;
done * ) echo 99 ;;
esac
echo "$result" ;;
return 0 esac
} }
selectVersion() { selectVersion() {
local versions_name="$1" local wanted="$1"
local indexes_name="$2" shift
local preferred_name="$3"
local result_name="$4"
local index_name="$5"
local -a candidates=() local candidate record id index
local -n index_map="$indexes_name" local -a candidates=("$wanted")
local -n version_list="$versions_name"
local -n selected_version="$result_name"
local -n preference_list="$preferred_name"
local -n selected_image_index="$index_name"
local wanted candidate match
# A detected edition is only selectable when a matching answer file can # Normal and Evaluation variants of the same edition are compatible.
# actually be staged for it. if [[ "${wanted,,}" == *"-eval" ]]; then
for wanted in "${preference_list[@]}"; do candidates+=("${wanted%-eval}")
else
candidates+=("$wanted-eval")
fi
[ -n "$wanted" ] || continue for candidate in "${candidates[@]}"; do
mapfile -t candidates < <(getCompatibleVersions "$wanted") for record in "$@"; do
for candidate in "${candidates[@]}"; do IFS=$'\t' read -r id index <<< "$record"
[[ "${id,,}" == "${candidate,,}" ]] || continue
match=$(hasVersion "$candidate" "${version_list[@]}") || continue hasAnswerFile "$id" || continue
hasAnswerFile "$match" || continue
local key="${match,,}"
selected_version="$match"
selected_image_index="${index_map[$key]}"
printf '%s\n%s\n' "$id" "$index"
return 0 return 0
done done
done done
return 1 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
}
selectEdition() {
local versions_name="$1"
local bases_name="$2"
local groups_name="$3"
local indexes_name="$4"
local suggested="$5"
local result_name="$6"
local index_name="$7"
local normalize_name="$8"
local order_name="$9"
local -A seen=()
local -a preferred=()
local -n edition_bases="$bases_name"
local -n edition_order="$order_name"
local -n edition_groups="$groups_name"
local -n edition_versions="$versions_name"
local base edition entry suffix priority i
# Selection precedence is explicit EDITION, source suggestion, canonical
# edition order, then noncanonical editions from the same priority groups.
if [ -n "$EDITION" ]; then
for base in "${edition_bases[@]}"; do
edition=$("$normalize_name" "$EDITION" "$base") || return 1
preferred+=("$base${edition:+-$edition}")
done
if selectVersion "$versions_name" "$indexes_name" preferred "$result_name" "$index_name"; then
return 0
fi
warn "edition '$EDITION' is not supported by this image, using automatic selection instead."
fi
if [ -n "$suggested" ]; then
preferred=("$suggested")
if selectVersion "$versions_name" "$indexes_name" preferred "$result_name" "$index_name"; then
return 0
fi
fi
# First try each canonical edition in its configured order.
preferred=()
for entry in "${edition_order[@]}"; do
IFS='|' read -r suffix _ _ <<< "$entry"
for base in "${edition_bases[@]}"; do
preferred+=("$base$suffix")
done
done
if selectVersion "$versions_name" "$indexes_name" preferred "$result_name" "$index_name"; then
return 0
fi
# Then try noncanonical editions from the same preference groups.
seen=()
preferred=()
for entry in "${edition_order[@]}"; do
IFS='|' read -r _ priority _ <<< "$entry"
[[ -v "seen[$priority]" ]] && continue
seen["$priority"]="Y"
for ((i=0;i<${#edition_versions[@]};i++)); do
[[ "${edition_groups[$i]}" == "$priority" ]] || continue
preferred+=("${edition_versions[$i]}")
done
done
selectVersion "$versions_name" "$indexes_name" preferred "$result_name" "$index_name"
}
detectVersion() { detectVersion() {
local xml="$1" local xml="$1"
local suggested="${2:-}"
local -a bases=() local normalize="normalizeEditionID"
local -a groups=() local output record id index base edition suffix
local -a versions=() local -a images=() bases=() order=()
local -A image_indexes=()
local -a selection_order=()
local result="" index=""
getVersions "$xml" versions bases groups image_indexes || return output=$(getVersions "$xml") || return
[ -n "$output" ] && mapfile -t images <<< "$output"
if [ "${#versions[@]}" -eq 0 ]; then if [ "${#images[@]}" -eq 0 ]; then
printf '%s\n%s\n' "$result" "$index" printf '\n\n'
return 0 return 0
fi fi
local normalize="normalizeEditionID" for record in "${images[@]}"; do
IFS=$'\t' read -r id _ <<< "$record"
bases+=("${id%%-*}")
done
case "${bases[0],,}" in case "${bases[0],,}" in
"win20"* ) "win20"* )
normalize="normalizeServerEditionID" ;; normalize="normalizeServerEditionID"
order=(
"" "-datacenter" "-datacenter-azure" "-enterprise" "-web"
"-foundation" "-essentials" "-standard-core" "-datacenter-core"
"-datacenter-azure-core" "-enterprise-core" "-web-core" "-hv"
) ;;
* )
order=(
"-enterprise" "-ultimate" "" "-iot" "-ltsc" "-education"
"-home" "-home-premium" "-home-basic" "-starter"
) ;;
esac esac
mapfile -t selection_order < <(getEditionOrder "${bases[0]}") # An explicit EDITION always gets first choice.
if [ -n "$EDITION" ]; then
if selectEdition versions bases groups image_indexes "$suggested" result index "$normalize" selection_order; then for base in "${bases[@]}"; do
printf '%s\n%s\n' "$result" "$index"
edition=$("$normalize" "$EDITION" "$base") || return 1
if output=$(selectVersion "$base${edition:+-$edition}" "${images[@]}"); then
printf '%s\n' "$output"
return 0
fi
done
warn "edition '$EDITION' is not supported by this image, using automatic selection instead."
fi
# Prefer the normal editions in their established order.
for suffix in "${order[@]}"; do
for base in "${bases[@]}"; do
if output=$(selectVersion "$base$suffix" "${images[@]}"); then
printf '%s\n' "$output"
return 0
fi
done
done
# Otherwise keep the established family preference for noncanonical
# editions, while preserving WIM order within the same family.
local rank best_rank=99 result="" result_index=""
for record in "${images[@]}"; do
IFS=$'\t' read -r id index <<< "$record"
hasAnswerFile "$id" || continue
rank=$(getEditionRank "$id")
(( rank < best_rank )) || continue
best_rank="$rank"
result="$id"
result_index="$index"
done
if [ -n "$result" ]; then
printf '%s\n%s\n' "$result" "$result_index"
return 0 return 0
fi fi
# Keep the first detected image identity when no edition with a usable answer # Keep the first detected identity when only manual/generic fallback remains.
# file was found, so manual and generic fallback handling can still continue. IFS=$'\t' read -r id index <<< "${images[0]}"
result="${versions[0]}"
local key="${result,,}"
index="${image_indexes[$key]}"
printf '%s\n%s\n' "$result" "$index" printf '%s\n%s\n' "$id" "$index"
return 0 return 0
} }
@@ -513,22 +404,6 @@ getImageSize() {
printf '%s\n' "$size" printf '%s\n' "$size"
} }
hasVersion() {
local wanted="$1"
shift
local actual
for actual in "$@"; do
[[ "${actual,,}" == "${wanted,,}" ]] || continue
echo "$actual"
return 0
done
return 1
}
checkPlatform() { checkPlatform() {
local xml="$1" local xml="$1"
@@ -571,76 +446,53 @@ getPlatform() {
local xml="$1" local xml="$1"
local image_count invalid_count local arch current platform=""
local x86 x64 arm64 unknown value local image_count output records
local platform="" count=0 output local separator=$'\x1f'
local -a counts=()
if ! output=$(xmlstarlet sel \ if ! output=$(xmlstarlet sel \
-T -t \ -T -t \
-v 'count(/WIM/IMAGE)' -n \ -v 'count(/WIM/IMAGE)' -n \
-v 'count(/WIM/IMAGE[count(WINDOWS/ARCH) != 1])' -n \ -m '/WIM/IMAGE' \
-v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="0"])' -n \ -v 'count(WINDOWS/ARCH)' -o "$separator" \
-v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="9"])' -n \ -v 'normalize-space(WINDOWS/ARCH)' -n \
-v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)="12"])' -n \
-v 'count(/WIM/IMAGE/WINDOWS/ARCH[normalize-space(.)!="0" and normalize-space(.)!="9" and normalize-space(.)!="12"])' -n \
- 2>/dev/null <<< "$xml"); then - 2>/dev/null <<< "$xml"); then
error "Failed to read architecture metadata from WIM image!" error "Failed to read architecture metadata from WIM image!"
return 1 return 1
fi fi
mapfile -t counts <<< "$output" image_count="${output%%$'\n'*}"
if (( ${#counts[@]} != 6 )); then if [ "$image_count" = "0" ]; 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
image_count="${counts[0]}"
invalid_count="${counts[1]}"
x86="${counts[2]}"
x64="${counts[3]}"
arm64="${counts[4]}"
unknown="${counts[5]}"
if (( image_count == 0 )); then
error "No images were found in WIM metadata!" error "No images were found in WIM metadata!"
return 1 return 1
fi fi
if (( invalid_count > 0 )); then records="${output#*$'\n'}"
error "Missing or duplicate architecture metadata in WIM image!"
return 1
fi
if (( unknown > 0 )); then while IFS="$separator" read -r count arch; do
error "Unsupported architecture value in WIM metadata!"
return 1
fi
(( x86 > 0 )) && ((count += 1)) if [ "$count" != "1" ]; then
(( x64 > 0 )) && ((count += 1)) error "Missing or duplicate architecture metadata in WIM image!"
(( arm64 > 0 )) && ((count += 1)) return 1
fi
if (( count > 1 )); then case "$arch" in
platform="mixed" "0" ) current="x86" ;;
elif (( x86 > 0 )); then "9" ) current="x64" ;;
platform="x86" "12" ) current="arm64" ;;
elif (( x64 > 0 )); then * )
platform="x64" error "Unsupported architecture value in WIM metadata!"
elif (( arm64 > 0 )); then return 1 ;;
platform="arm64" esac
else
error "Failed to determine architecture from WIM metadata!" if [ -z "$platform" ]; then
return 1 platform="$current"
fi elif [ "$platform" != "$current" ]; then
platform="mixed"
fi
done <<< "$records"
echo "$platform" echo "$platform"
return 0 return 0