feat: Refactor Windows answer file handling (#1983)

This commit is contained in:
Kroese
2026-07-26 05:34:24 +02:00
committed by GitHub
parent aa6240ff99
commit 8929f7cdcd
3 changed files with 723 additions and 460 deletions
+9
View File
@@ -487,6 +487,15 @@ jobs:
exit 1 exit 1
fi fi
if grep -Fqi \
'CDBOOT: Cannot boot from CD - Code: 5' \
<<< "$container_log"; then
echo
echo "------------------------------------------------------------"
echo "Detected an unbootable installation medium."
exit 1
fi
bios_starts="$( bios_starts="$(
grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true
)" )"
+395 -186
View File
@@ -141,30 +141,35 @@ validateUsername() {
local value="$1" local value="$1"
local type="$2" local type="$2"
local maximum local maximum length_suffix invalid_message
case "$type" in case "$type" in
"local" ) "local" )
maximum=20
[ -z "$value" ] && return 0 [ -z "$value" ] && return 0
;;
"domain" )
maximum=256
maximum=20
length_suffix=""
invalid_message="The USERNAME variable contains characters that are not supported by Windows local accounts!"
;;
"domain" )
if [ -z "$value" ]; then if [ -z "$value" ]; then
error "The USERNAME variable does not contain a valid domain account name!" error "The USERNAME variable does not contain a valid domain account name!"
return 1 return 1
fi ;; fi
maximum=256
length_suffix=" for a domain account"
invalid_message="The domain account name contains characters that are not supported by Windows unattended setup!"
;;
* ) * )
return 1 ;; return 1
;;
esac esac
if [ "${#value}" -gt "$maximum" ]; then if [ "${#value}" -gt "$maximum" ]; then
if [[ "$type" == "domain" ]]; then error "The USERNAME variable cannot contain more than $maximum characters$length_suffix!"
error "The USERNAME variable cannot contain more than $maximum characters for a domain account!"
else
error "The USERNAME variable cannot contain more than $maximum characters!"
fi
return 1 return 1
fi fi
@@ -175,12 +180,9 @@ validateUsername() {
case "$value" in case "$value" in
*'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* | *'%'* | *'@'* ) *'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* | *'%'* | *'@'* )
if [[ "$type" == "domain" ]]; then error "$invalid_message"
error "The domain account name contains characters that are not supported by Windows unattended setup!" return 1
else ;;
error "The USERNAME variable contains characters that are not supported by Windows local accounts!"
fi
return 1 ;;
esac esac
if [[ "$value" == *"." ]]; then if [[ "$value" == *"." ]]; then
@@ -196,12 +198,15 @@ validateUsername() {
case "${value^^}" in case "${value^^}" in
"NONE" ) "NONE" )
error "The USERNAME value \"NONE\" is reserved by Windows!" error "The USERNAME value \"NONE\" is reserved by Windows!"
return 1 ;; return 1
;;
"ADMINISTRATOR" | "GUEST" | "DEFAULTACCOUNT" | "WDAGUTILITYACCOUNT" | "WSIACCOUNT" ) "ADMINISTRATOR" | "GUEST" | "DEFAULTACCOUNT" | "WDAGUTILITYACCOUNT" | "WSIACCOUNT" )
if [[ "$type" == "local" ]]; then [[ "$type" == "domain" ]] && return 0
error "The USERNAME value \"$value\" is reserved for a built-in Windows account!" error "The USERNAME value \"$value\" is reserved for a built-in Windows account!"
return 1 return 1
fi ;; ;;
esac esac
return 0 return 0
@@ -234,14 +239,28 @@ validateDomainName() {
return 0 return 0
} }
getXMLArchitecture() {
local asset="$1"
local arch
arch=$(sed -n -E \
'0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' \
"$asset") || return 1
[ -n "$arch" ] || return 1
printf '%s' "$arch"
return 0
}
updateWorkgroup() { updateWorkgroup() {
local asset="$1" local asset="$1"
local workgroup arch tmp local workgroup arch tmp
workgroup=$(escapeXML "$2") || return 1 workgroup=$(escapeXML "$2") || return 1
arch=$(sed -n -E '0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' "$asset") || return 1 arch=$(getXMLArchitecture "$asset") || return 1
[ -z "$arch" ] && return 1
grep -q 'Microsoft-Windows-UnattendedJoin' "$asset" && return 1 grep -q 'Microsoft-Windows-UnattendedJoin' "$asset" && return 1
@@ -287,12 +306,7 @@ updateDomain() {
auth=$(escapeXML "$4") || return 1 auth=$(escapeXML "$4") || return 1
pass=$(escapeXML "$5") || return 1 pass=$(escapeXML "$5") || return 1
ou=$(escapeXML "$6") || return 1 ou=$(escapeXML "$6") || return 1
arch=$(getXMLArchitecture "$asset") || return 1
arch=$(sed -n -E \
'0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' \
"$asset") || return 1
[ -z "$arch" ] && return 1
local cred_domain="$domain" local cred_domain="$domain"
@@ -397,17 +411,44 @@ updateDomain() {
return 0 return 0
} }
removeLocalAccountXML() {
local asset="$1"
if ! sed -i -E \
-e '/^[[:space:]]*<LocalAccounts([[:space:]>])/,/^[[:space:]]*<\/LocalAccounts>[[:space:]]*$/d' \
-e '/^[[:space:]]*<AdministratorPassword([[:space:]>])/,/^[[:space:]]*<\/AdministratorPassword>[[:space:]]*$/d' \
"$asset"; then
error "Failed to remove local account configuration from answer file!"
return 1
fi
if ! sed -i -E '
/<SynchronousCommand([[:space:]>])/ {
:command
N
/<\/SynchronousCommand>/!b command
/<Description>Password Never Expires<\/Description>/d
}
' "$asset"; then
error "Failed to remove local account commands from answer file!"
return 1
fi
return 0
}
enableLog() { enableLog() {
local file="$1" local file="$1"
enabled "$LOG" || return 0
[ -f "$file" ] || return 1
local old='C:\OEM\install.bat"</CommandLine>' local old='C:\OEM\install.bat"</CommandLine>'
local new='C:\OEM\install.bat &gt; C:\OEM\install.log 2&gt;&amp;1"</CommandLine>'
local msg="failed to enable install logging in the answer file!" local msg="failed to enable install logging in the answer file!"
enabled "${LOG:-}" || return 0
[ -f "$file" ] || return 1
if ! grep -Fq "$old" "$file"; then if ! grep -Fq "$old" "$file"; then
enabled "$DEBUG" && warn "$msg" enabled "$DEBUG" && warn "$msg"
return 0 return 0
@@ -549,14 +590,15 @@ generateEvalXML() {
local id="$1" local id="$1"
local detected_index="${2:-}" local detected_index="${2:-}"
[[ "${id,,}" == *"-eval" ]] || return 1
local normal="${id::-5}" local normal="${id::-5}"
local source="/run/assets/$normal.xml" local source="/run/assets/$normal.xml"
local target="/run/assets/$id.xml" local target="/run/assets/$id.xml"
local index="$detected_index" local index="$detected_index"
local remove_selector="N" local remove_selector="N"
[[ "${id,,}" == *"-eval" ]] || return 1
removeGeneratedXML "$source" || return 1 removeGeneratedXML "$source" || return 1
if [ ! -s "$source" ]; then if [ ! -s "$source" ]; then
@@ -605,7 +647,7 @@ generateFallbackXML() {
setXML() { setXML() {
local file local file="$1"
local index="${2:-}" local index="${2:-}"
local target="/run/assets/$DETECTED.xml" local target="/run/assets/$DETECTED.xml"
@@ -634,17 +676,21 @@ setXML() {
file="$1" file="$1"
if [[ "${DETECTED,,}" == *"-eval" ]]; then if [[ "${DETECTED,,}" == *"-eval" ]] &&
if [ ! -f "$file" ] || [ ! -s "$file" ]; then { [ ! -f "$file" ] || [ ! -s "$file" ]; }; then
generateEvalXML "$DETECTED" "$index" || return 1
fi
fi
if [ ! -f "$file" ] || [ ! -s "$file" ]; then generateEvalXML "$DETECTED" "$index" || return 1
file="$target" file="$target"
elif [ ! -f "$file" ] || [ ! -s "$file" ]; then
file="$target"
elif [[ "$file" != "$target" ]]; then elif [[ "$file" != "$target" ]]; then
generateFallbackXML "$DETECTED" "$index" || return 1 generateFallbackXML "$DETECTED" "$index" || return 1
file="$target" file="$target"
fi fi
[ -f "$file" ] && [ -s "$file" ] || return 1 [ -f "$file" ] && [ -s "$file" ] || return 1
@@ -653,14 +699,7 @@ setXML() {
return 0 return 0
} }
updateXML() { validateXMLSettings() {
local asset="$1"
local language="$2"
local value user
[ -z "${WIDTH:-}" ] && WIDTH="1280"
[ -z "${HEIGHT:-}" ] && HEIGHT="720"
validateResolution "WIDTH" "$WIDTH" 320 || return 1 validateResolution "WIDTH" "$WIDTH" 320 || return 1
validateResolution "HEIGHT" "$HEIGHT" 200 || return 1 validateResolution "HEIGHT" "$HEIGHT" 200 || return 1
@@ -669,20 +708,34 @@ updateXML() {
validateProductKey "${KEY:-}" || return 1 validateProductKey "${KEY:-}" || return 1
validatePassword "${PASSWORD:-}" || return 1 validatePassword "${PASSWORD:-}" || return 1
local app return 0
}
updateDisplayXML() {
local asset="$1"
local app host
app=$(escapeXMLSed "$APP for $ENGINE") || return 1 app=$(escapeXMLSed "$APP for $ENGINE") || return 1
sed -i "s|>Windows for Docker<|>$app<|g" "$asset" || return 1 sed -i "s|>Windows for Docker<|>$app<|g" "$asset" || return 1
sed -i -E "s|<VerticalResolution>[^<]*</VerticalResolution>|<VerticalResolution>$HEIGHT</VerticalResolution>|g" "$asset" || return 1 sed -i -E "s|<VerticalResolution>[^<]*</VerticalResolution>|<VerticalResolution>$HEIGHT</VerticalResolution>|g" "$asset" || return 1
sed -i -E "s|<HorizontalResolution>[^<]*</HorizontalResolution>|<HorizontalResolution>$WIDTH</HorizontalResolution>|g" "$asset" || return 1 sed -i -E "s|<HorizontalResolution>[^<]*</HorizontalResolution>|<HorizontalResolution>$WIDTH</HorizontalResolution>|g" "$asset" || return 1
if [ -n "${HOST:-}" ]; then [ -n "${HOST:-}" ] || return 0
local host
host=$(escapeXMLSed "$HOST") || return 1 host=$(escapeXMLSed "$HOST") || return 1
sed -i -E "s|<ComputerName>[^<]*</ComputerName>|<ComputerName>$host</ComputerName>|g" "$asset" || return 1 sed -i -E "s|<ComputerName>[^<]*</ComputerName>|<ComputerName>$host</ComputerName>|g" "$asset" || return 1
fi
local culture return 0
}
updateLocaleXML() {
local asset="$1"
local language="$2"
local culture region keyboard value
culture=$(getLanguage "$language" "culture") || return 1 culture=$(getLanguage "$language" "culture") || return 1
if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then
@@ -690,8 +743,7 @@ updateXML() {
sed -i "s|<UILanguage>en-US</UILanguage>|<UILanguage>$value</UILanguage>|g" "$asset" || return 1 sed -i "s|<UILanguage>en-US</UILanguage>|<UILanguage>$value</UILanguage>|g" "$asset" || return 1
fi fi
local region="${REGION:-}" region="${REGION:-$culture}"
[ -z "$region" ] && region="$culture"
if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then
value=$(escapeXMLSed "$region") || return 1 value=$(escapeXMLSed "$region") || return 1
@@ -699,8 +751,7 @@ updateXML() {
sed -i "s|<SystemLocale>en-US</SystemLocale>|<SystemLocale>$value</SystemLocale>|g" "$asset" || return 1 sed -i "s|<SystemLocale>en-US</SystemLocale>|<SystemLocale>$value</SystemLocale>|g" "$asset" || return 1
fi fi
local keyboard="${KEYBOARD:-}" keyboard="${KEYBOARD:-$culture}"
[ -z "$keyboard" ] && keyboard="$culture"
if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then
value=$(escapeXMLSed "$keyboard") || return 1 value=$(escapeXMLSed "$keyboard") || return 1
@@ -708,12 +759,20 @@ updateXML() {
sed -i "s|<InputLocale>0409:00000409</InputLocale>|<InputLocale>$value</InputLocale>|g" "$asset" || return 1 sed -i "s|<InputLocale>0409:00000409</InputLocale>|<InputLocale>$value</InputLocale>|g" "$asset" || return 1
fi fi
local domain="${DOMAIN:-}" return 0
local workgroup="${WORKGROUP:-}" }
if [ -n "$domain" ]; then prepareDomainAccount() {
if [ -z "${USERNAME:-}" ]; then local domain="$1"
local -n account_ref="$2"
local -n auth_ref="$3"
local qualifier=""
auth_ref="${USERNAME:-}"
account_ref=""
if [ -z "$auth_ref" ]; then
error "The USERNAME variable must be specified when joining a domain!" error "The USERNAME variable must be specified when joining a domain!"
return 1 return 1
fi fi
@@ -725,20 +784,20 @@ updateXML() {
validateDomainName "$domain" || return 1 validateDomainName "$domain" || return 1
local auth_user="$USERNAME" if [[ "$auth_ref" == *\\* ]]; then
local qualifier=""
if [[ "$auth_user" == *\\* ]]; then
error "The USERNAME variable must use either \"user\" or \"user@domain\" format!" error "The USERNAME variable must use either \"user\" or \"user@domain\" format!"
return 1 return 1
fi fi
case "$auth_user" in case "$auth_ref" in
*@* ) *@* )
user="${auth_user%%@*}" account_ref="${auth_ref%%@*}"
qualifier="${auth_user#*@}" qualifier="${auth_ref#*@}"
if [ -z "$account_ref" ] ||
[ -z "$qualifier" ] ||
[[ "$qualifier" == *@* ]]; then
if [ -z "$user" ] || [ -z "$qualifier" ] || [[ "$qualifier" == *@* ]]; then
error "The USERNAME variable does not contain a valid domain account name!" error "The USERNAME variable does not contain a valid domain account name!"
return 1 return 1
fi fi
@@ -750,14 +809,15 @@ updateXML() {
return 1 return 1
fi fi
;; ;;
* ) * )
user="$auth_user" account_ref="$auth_ref"
;; ;;
esac esac
validateUsername "$user" "domain" || return 1 validateUsername "$account_ref" "domain" || return 1
if [[ "${user,,}" == "docker" ]]; then if [[ "${account_ref,,}" == "docker" ]]; then
error "The USERNAME variable must be changed from its default value when joining a domain!" error "The USERNAME variable must be changed from its default value when joining a domain!"
return 1 return 1
fi fi
@@ -767,13 +827,19 @@ updateXML() {
return 1 return 1
fi fi
else return 0
}
updateLocalAccountXML() {
local asset="$1"
local user="${USERNAME:-}"
local pass="${PASSWORD:-admin}"
local user_xml pw admin
user="${USERNAME:-}"
validateUsername "$user" "local" || return 1 validateUsername "$user" "local" || return 1
if [ -n "$user" ]; then if [ -n "$user" ]; then
local user_xml
user_xml=$(escapeXMLSed "$user") || return 1 user_xml=$(escapeXMLSed "$user") || return 1
sed -i "s|-name \"Docker\"|-name \"\$env:USERNAME\"|g" "$asset" || return 1 sed -i "s|-name \"Docker\"|-name \"\$env:USERNAME\"|g" "$asset" || return 1
@@ -783,85 +849,125 @@ updateXML() {
sed -i "s|<Username>Docker</Username>|<Username>$user_xml</Username>|g" "$asset" || return 1 sed -i "s|<Username>Docker</Username>|<Username>$user_xml</Username>|g" "$asset" || return 1
fi fi
local pass="${PASSWORD:-admin}" pw=$(printf '%s' "${pass}Password" |
local pw admin iconv -f utf-8 -t utf-16le |
base64 -w 0) || return 1
pw=$(printf '%s' "${pass}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1 admin=$(printf '%s' "${pass}AdministratorPassword" |
admin=$(printf '%s' "${pass}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1 iconv -f utf-8 -t utf-16le |
base64 -w 0) || return 1
sed -i -z -E "s#(<Password>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$pw</Value>#g" "$asset" || return 1 sed -i -z -E \
sed -i -z -E "s#(<AdministratorPassword>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$admin</Value>#g" "$asset" || return 1 "s#(<Password>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$pw</Value>#g" \
"$asset" || return 1
fi sed -i -z -E \
"s#(<AdministratorPassword>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$admin</Value>#g" \
"$asset" || return 1
sed -i -E "s|<PlainText>[^<]*</PlainText>|<PlainText>false</PlainText>|g" "$asset" || return 1 return 0
}
updateMembershipXML() {
local asset="$1"
local domain="$2"
local workgroup="$3"
local account="$4"
local auth="$5"
if [ -n "$domain" ]; then if [ -n "$domain" ]; then
if updateDomain "$asset" "$domain" "$user" \ if ! updateDomain \
"$auth_user" "$PASSWORD" "$DOMAIN_OU"; then "$asset" \
"$domain" \
"$account" \
"$auth" \
"$PASSWORD" \
"${DOMAIN_OU:-}"; then
if ! sed -i -E \
-e '/^[[:space:]]*<LocalAccounts([[:space:]>])/,/^[[:space:]]*<\/LocalAccounts>[[:space:]]*$/d' \
-e '/^[[:space:]]*<AdministratorPassword([[:space:]>])/,/^[[:space:]]*<\/AdministratorPassword>[[:space:]]*$/d' \
"$asset"; then
error "Failed to remove local account configuration from answer file!"
return 1
fi
if ! sed -i -E '
/<SynchronousCommand([[:space:]>])/ {
:command
N
/<\/SynchronousCommand>/!b command
/<Description>Password Never Expires<\/Description>/d
}
' "$asset"; then
error "Failed to remove local account commands from answer file!"
return 1
fi
else
warn "failed to add domain configuration to answer file!" warn "failed to add domain configuration to answer file!"
return 0
fi fi
elif [ -n "$workgroup" ]; then removeLocalAccountXML "$asset" || return 1
return 0
fi
[ -n "$workgroup" ] || return 0
if ! updateWorkgroup "$asset" "$workgroup"; then if ! updateWorkgroup "$asset" "$workgroup"; then
warn "failed to add workgroup configuration to answer file!" warn "failed to add workgroup configuration to answer file!"
fi fi
fi return 0
}
if disabled "${AUTOLOGIN:-}"; then updateAutologinXML() {
sed -i -E '/^[[:space:]]*<AutoLogon([[:space:]>])/,/^[[:space:]]*<\/AutoLogon>[[:space:]]*$/d' "$asset" || return 1
fi
if enabled "${LOG:-}"; then local asset="$1"
enableLog "$asset" || return 1
fi
if [ -n "${EDITION:-}" ]; then disabled "${AUTOLOGIN:-}" || return 0
sed -i -E \
'/^[[:space:]]*<AutoLogon([[:space:]>])/,/^[[:space:]]*<\/AutoLogon>[[:space:]]*$/d' \
"$asset" || return 1
return 0
}
updateEditionXML() {
local asset="$1"
local edition local edition
[ -n "${EDITION:-}" ] || return 0
edition=$(normalizeServerEdition "$EDITION") || return 1 edition=$(normalizeServerEdition "$EDITION") || return 1
edition="${edition//-/}" edition="${edition//-/}"
edition="${edition^^}" edition="${edition^^}"
edition=$(escapeXMLSed "$edition") || return 1 edition=$(escapeXMLSed "$edition") || return 1
sed -i "s|SERVERSTANDARD</Value>|SERVER$edition</Value>|g" "$asset" || return 1
fi sed -i \
"s|SERVERSTANDARD</Value>|SERVER$edition</Value>|g" \
"$asset" || return 1
if [ -n "${KEY:-}" ]; then return 0
}
updateProductKeyXML() {
local asset="$1"
local key local key
[ -n "${KEY:-}" ] || return 0
key=$(escapeXMLSed "$KEY") || return 1 key=$(escapeXMLSed "$KEY") || return 1
sed -i -E '/^[[:space:]]*<ProductKey>[[:space:]]*$/,/^[[:space:]]*<\/ProductKey>[[:space:]]*$/d' "$asset" || return 1
sed -i -E "s|<ProductKey>[^<]*</ProductKey>|<ProductKey>$key</ProductKey>|g" "$asset" || return 1 sed -i -E \
sed -i "s|</UserData>| <ProductKey>\n <Key>$key</Key>\n <WillShowUI>OnError</WillShowUI>\n </ProductKey>\n </UserData>|g" "$asset" || return 1 '/^[[:space:]]*<ProductKey>[[:space:]]*$/,/^[[:space:]]*<\/ProductKey>[[:space:]]*$/d' \
"$asset" || return 1
sed -i -E \
"s|<ProductKey>[^<]*</ProductKey>|<ProductKey>$key</ProductKey>|g" \
"$asset" || return 1
sed -i \
"s|</UserData>| <ProductKey>\n <Key>$key</Key>\n <WillShowUI>OnError</WillShowUI>\n </ProductKey>\n </UserData>|g" \
"$asset" || return 1
return 0
}
removeSharedFolderXML() {
local asset="$1"
if ! disabled "${SHORTCUT:-}" &&
! disabled "${SAMBA:-}"; then
return 0
fi fi
if disabled "${SHORTCUT:-}" || disabled "${SAMBA:-}"; then
if ! sed -i -E ' if ! sed -i -E '
/<SynchronousCommand([[:space:]>])/ { /<SynchronousCommand([[:space:]>])/ {
:command :command
@@ -871,10 +977,17 @@ updateXML() {
/<Description>Map shared folder<\/Description>/d /<Description>Map shared folder<\/Description>/d
} }
' "$asset"; then ' "$asset"; then
error "Failed to remove shared folder shortcuts from answer file!" error "Failed to remove shared folder shortcuts from answer file!"
return 1 return 1
fi fi
fi
return 0
}
validateGeneratedXML() {
local asset="$1"
if ! xmllint --nonet --noout "$asset"; then if ! xmllint --nonet --noout "$asset"; then
error "The generated answer file is not valid XML!" error "The generated answer file is not valid XML!"
@@ -884,6 +997,49 @@ updateXML() {
return 0 return 0
} }
updateXML() {
local asset="$1"
local language="$2"
local domain="${DOMAIN:-}"
local workgroup="${WORKGROUP:-}"
local account=""
local auth=""
[ -z "${WIDTH:-}" ] && WIDTH="1280"
[ -z "${HEIGHT:-}" ] && HEIGHT="720"
validateXMLSettings || return 1
updateDisplayXML "$asset" || return 1
updateLocaleXML "$asset" "$language" || return 1
if [ -n "$domain" ]; then
prepareDomainAccount "$domain" account auth || return 1
else
updateLocalAccountXML "$asset" || return 1
fi
sed -i -E \
"s|<PlainText>[^<]*</PlainText>|<PlainText>false</PlainText>|g" \
"$asset" || return 1
updateMembershipXML \
"$asset" \
"$domain" \
"$workgroup" \
"$account" \
"$auth" || return 1
updateAutologinXML "$asset" || return 1
enableLog "$asset" || return 1
updateEditionXML "$asset" || return 1
updateProductKeyXML "$asset" || return 1
removeSharedFolderXML "$asset" || return 1
validateGeneratedXML "$asset" || return 1
return 0
}
escapeSIFValue() { escapeSIFValue() {
local s="$1" local s="$1"
@@ -974,50 +1130,70 @@ validateLegacyUsername() {
return 0 return 0
} }
addLegacyDrivers() { extractLegacyDrivers() {
local drivers="$1"
rm -rf "$drivers" || return 1
mkdir -p "$drivers" || return 1
if ! bsdtar -xf /var/drivers.txz -C "$drivers"; then
error "Failed to extract drivers!"
return 1
fi
return 0
}
copyLegacyStorageDriver() {
local dir="$1" local dir="$1"
local target="$2" local target="$2"
local driver="$3" local driver="$3"
local arch="$4" local arch="$4"
local drivers="$5" local drivers="$5"
local file local destination="$dir/\$OEM\$/\$1/Drivers/viostor"
local msg="Adding drivers to image..."
info "$msg" && html "$msg"
rm -rf "$drivers" || return 1
mkdir -p "$drivers" || return 1
if ! bsdtar -xf /var/drivers.txz -C "$drivers"; then
error "Failed to extract drivers!" && return 1
fi
if [ ! -f "$drivers/viostor/$driver/$arch/viostor.sys" ]; then if [ ! -f "$drivers/viostor/$driver/$arch/viostor.sys" ]; then
error "Failed to locate required storage drivers!" && return 1 error "Failed to locate required storage drivers!"
return 1
fi fi
cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1
mkdir -p "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 mkdir -p "$destination" || return 1
cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$destination" || return 1
cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$destination" || return 1
cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$destination" || return 1
return 0
}
addLegacyNetworkDriver() {
local dir="$1"
local driver="$2"
local arch="$3"
local drivers="$4"
local destination="$dir/\$OEM\$/\$1/Drivers/NetKVM"
if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then
error "Failed to locate required network drivers!" && return 1 error "Failed to locate required network drivers!"
return 1
fi fi
mkdir -p "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 mkdir -p "$destination" || return 1
cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$destination" || return 1
cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$destination" || return 1
cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$destination" || return 1
file=$(find "$target" -maxdepth 1 -type f -iname TXTSETUP.SIF -print -quit) || return 1 return 0
}
if [ -z "$file" ]; then patchLegacyStorageDriver() {
error "The file TXTSETUP.SIF could not be found!" && return 1
fi local file="$1"
local arch="$2"
sed -i '/^\[SCSI.Load\]/s/$/\nviostor=viostor.sys,4/' "$file" || return 1 sed -i '/^\[SCSI.Load\]/s/$/\nviostor=viostor.sys,4/' "$file" || return 1
sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\nviostor.sys=1,,,,,,4_,4,1,,,1,4/' "$file" || return 1 sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\nviostor.sys=1,,,,,,4_,4,1,,,1,4/' "$file" || return 1
@@ -1026,12 +1202,25 @@ addLegacyDrivers() {
sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00020000=\"viostor\"/' "$file" || return 1 sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00020000=\"viostor\"/' "$file" || return 1
sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00021AF4=\"viostor\"/' "$file" || return 1 sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00021AF4=\"viostor\"/' "$file" || return 1
return 0
}
addLegacySataDriver() {
local dir="$1"
local target="$2"
local arch="$3"
local drivers="$4"
local file="$5"
local destination="$dir/\$OEM\$/\$1/Drivers/sata"
if [ ! -d "$drivers/sata/xp/$arch" ]; then if [ ! -d "$drivers/sata/xp/$arch" ]; then
error "Failed to locate required SATA drivers!" && return 1 error "Failed to locate required SATA drivers!"
return 1
fi fi
mkdir -p "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 mkdir -p "$destination" || return 1
cp -Lr "$drivers/sata/xp/$arch/." "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 cp -Lr "$drivers/sata/xp/$arch/." "$destination" || return 1
cp -Lr "$drivers/sata/xp/$arch/." "$target" || return 1 cp -Lr "$drivers/sata/xp/$arch/." "$target" || return 1
sed -i '/^\[SCSI.Load\]/s/$/\niaStor=iaStor.sys,4/' "$file" || return 1 sed -i '/^\[SCSI.Load\]/s/$/\niaStor=iaStor.sys,4/' "$file" || return 1
@@ -1045,6 +1234,35 @@ addLegacyDrivers() {
sed -i '/^\[SCSI\]/s/$/\niaStor=\"Intel\(R\) SATA RAID\/AHCI Controller\"/' "$file" || return 1 sed -i '/^\[SCSI\]/s/$/\niaStor=\"Intel\(R\) SATA RAID\/AHCI Controller\"/' "$file" || return 1
sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_8086\&DEV_2922\&CC_0106=\"iaStor\"/' "$file" || return 1 sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_8086\&DEV_2922\&CC_0106=\"iaStor\"/' "$file" || return 1
return 0
}
addLegacyDrivers() {
local dir="$1"
local target="$2"
local driver="$3"
local arch="$4"
local drivers="$5"
local file
local msg="Adding drivers to image..."
info "$msg" && html "$msg"
extractLegacyDrivers "$drivers" || return 1
copyLegacyStorageDriver "$dir" "$target" "$driver" "$arch" "$drivers" || return 1
addLegacyNetworkDriver "$dir" "$driver" "$arch" "$drivers" || return 1
file=$(find "$target" -maxdepth 1 -type f -iname TXTSETUP.SIF -print -quit) || return 1
if [ -z "$file" ]; then
error "The file TXTSETUP.SIF could not be found!"
return 1
fi
patchLegacyStorageDriver "$file" "$arch" || return 1
addLegacySataDriver "$dir" "$target" "$arch" "$drivers" "$file" || return 1
rm -rf "$drivers" || return 1 rm -rf "$drivers" || return 1
return 0 return 0
@@ -1060,34 +1278,31 @@ setLegacyKey() {
setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1 setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1
if [ -n "$setup" ] && [ -z "$KEY" ]; then [[ -n "$setup" ]] || return 0
[[ -z "$KEY" ]] || return 0
pid=$(<"$setup") || return 1 pid=$(<"$setup") || return 1
pid="${pid%$'\r'}" pid="${pid%$'\r'}"
if [[ "$driver" == "2k" ]]; then if [[ "$driver" == "2k" ]]; then
echo "${pid::-3}270" > "$setup" || return 1
echo "${pid:0:$((${#pid})) - 3}270" > "$setup" || return 1 return 0
fi
else
if [[ "$pid" == *"270" ]]; then if [[ "$pid" == *"270" ]]; then
warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation." warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation."
return 0
else fi
file=$(find "$target" -maxdepth 1 -type f -iname PID.INF -print -quit) || return 1 file=$(find "$target" -maxdepth 1 -type f -iname PID.INF -print -quit) || return 1
if [ -n "$file" ]; then if [[ -n "$file" ]]; then
if [[ "$driver" == "2k3" ]]; then if [[ "$driver" == "2k3" ]]; then
key=$(grep -i -A 2 "StagingKey" "$file" | tail -n 2 | head -n 1) || key="" key=$(grep -i -A 2 "StagingKey" "$file" | tail -n 2 | head -n 1) || key=""
else else
key="${pid:$((${#pid})) - 8:5}" key="${pid: -8:5}"
if [[ "${pid^^}" == *"OEM" ]]; then if [[ "${pid^^}" == *"OEM" ]]; then
key=$(grep -i -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key="" key=$(grep -i -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
@@ -1104,7 +1319,7 @@ setLegacyKey() {
fi fi
if [ -z "$KEY" ]; then [[ -n "$KEY" ]] && return 0
# These are NOT pirated keys, they come from official MS documentation. # These are NOT pirated keys, they come from official MS documentation.
@@ -1133,15 +1348,7 @@ setLegacyKey() {
esac esac
echo "${pid:0:$((${#pid})) - 3}000" > "$setup" || return 1 echo "${pid::-3}000" > "$setup" || return 1
fi
fi
fi
fi
return 0 return 0
} }
@@ -1150,13 +1357,12 @@ writeCommand() {
local install="$1" local install="$1"
[ -z "$install" ] && return 0 [ -f "$install" ] || return 0
[ ! -f "$install" ] && return 0
if ! enabled "${LOG:-}"; then if enabled "${LOG:-}"; then
printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat\\\"\""
else
printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat > C:\\\\OEM\\\\install.log 2>&1\\\"\"" printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat > C:\\\\OEM\\\\install.log 2>&1\\\"\""
else
printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat\\\"\""
fi fi
return 0 return 0
@@ -1458,7 +1664,8 @@ legacyInstall() {
ETFS="[BOOT]/Boot-NoEmul.img" ETFS="[BOOT]/Boot-NoEmul.img"
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
error "Failed to locate file \"$ETFS\" in $desc ISO image!" && return 1 error "Failed to locate file \"$ETFS\" in $desc ISO image!"
return 1
fi fi
local arch="amd64" local arch="amd64"
@@ -1468,7 +1675,8 @@ legacyInstall() {
[[ "${arch,,}" == "x86" ]] && target="$dir/I386" [[ "${arch,,}" == "x86" ]] && target="$dir/I386"
if [ ! -d "$target" ]; then if [ ! -d "$target" ]; then
error "Failed to locate directory \"$target\" in $desc ISO image!" && return 1 error "Failed to locate directory \"$target\" in $desc ISO image!"
return 1
fi fi
if [[ "${driver,,}" == "xp" || "${driver,,}" == "2k3" ]]; then if [[ "${driver,,}" == "xp" || "${driver,,}" == "2k3" ]]; then
@@ -1484,7 +1692,8 @@ legacyInstall() {
mkdir -p "$dir/\$OEM\$" || return 1 mkdir -p "$dir/\$OEM\$" || return 1
if ! addFolder "$dir"; then if ! addFolder "$dir"; then
error "Failed to add OEM folder to image!" && return 1 error "Failed to add OEM folder to image!"
return 1
fi fi
local oem="" local oem=""
+182 -137
View File
@@ -184,7 +184,7 @@ getVersions() {
groups_ref=() groups_ref=()
indexes_ref=() indexes_ref=()
platform=$(getPlatform "$xml") platform=$(getPlatform "$xml") || return 1
count=$(xmllint --nonet --xpath 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 0 count=$(xmllint --nonet --xpath 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 0
for ((i=1; i<=count; i++)); do for ((i=1; i<=count; i++)); do
@@ -245,10 +245,10 @@ getVersions() {
case "${candidate_base,,}" in case "${candidate_base,,}" in
"winvista"* | "win7"* | "win8"* | "win10"* | "win11"* ) "winvista"* | "win7"* | "win8"* | "win10"* | "win11"* )
structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base") structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base") || return 1
;; ;;
"win20"* ) "win20"* )
structured=$(normalizeServerEditionID "${flags:-$edition_id}") structured=$(normalizeServerEditionID "${flags:-$edition_id}") || return 1
# Some media use the same EDITIONID for Core and Desktop images. # Some media use the same EDITIONID for Core and Desktop images.
# INSTALLATIONTYPE provides the structural distinction without # INSTALLATIONTYPE provides the structural distinction without
@@ -342,7 +342,7 @@ selectEdition() {
if [ -n "$EDITION" ]; then if [ -n "$EDITION" ]; then
for base in "${edition_bases[@]}"; do for base in "${edition_bases[@]}"; do
edition=$("$normalize_name" "$EDITION" "$base") edition=$("$normalize_name" "$EDITION" "$base") || return 1
preferred+=("$base${edition:+-$edition}") preferred+=("$base${edition:+-$edition}")
done done
@@ -444,7 +444,7 @@ detectVersion() {
versions \ versions \
bases \ bases \
groups \ groups \
image_indexes image_indexes || return 1
[ "${#versions[@]}" -eq 0 ] && return 0 [ "${#versions[@]}" -eq 0 ] && return 0
@@ -479,7 +479,7 @@ detectLanguage() {
local xml="$1" local xml="$1"
local index="${2:-}" local index="${2:-}"
local xpath lang local xpath lang culture
if [[ "$index" =~ ^[0-9]+$ ]]; then if [[ "$index" =~ ^[0-9]+$ ]]; then
xpath="string((/WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])" xpath="string((/WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])"
@@ -494,9 +494,12 @@ detectLanguage() {
return 0 return 0
fi fi
local culture culture=$(getLanguage "$lang" "culture") || return 1
culture=$(getLanguage "$lang" "culture")
[ -n "$culture" ] && LANGUAGE="$lang" && return 0 if [ -n "$culture" ]; then
LANGUAGE="$lang"
return 0
fi
warn "Invalid language detected: \"$lang\"" warn "Invalid language detected: \"$lang\""
return 0 return 0
@@ -517,60 +520,105 @@ skipVersion() {
detectLegacy() { detectLegacy() {
local dir="$1" local dir="$1"
local find local marker
[[ "${PLATFORM,,}" != "x64" ]] && return 1 [[ "${PLATFORM,,}" == "x64" ]] || return 1
find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 1
[ -n "$find" ] && DETECTED="win95" && return 0
find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit) if [ -n "$marker" ]; then
[ -n "$find" ] && DETECTED="win98" && return 0 DETECTED="win95"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit) marker=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit) || return 1
[ -n "$find" ] && DETECTED="win9x" && return 0
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit) if [ -n "$marker" ]; then
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit) DETECTED="win98"
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit) return 0
[ -n "$find" ] && DETECTED="winnt4" && return 0 fi
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit) marker=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit) || return 1
if [ -n "$find" ]; then if [ -n "$marker" ]; then
DETECTED="win9x"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit) marker=$(find "$dir" -maxdepth 1 -type f \
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_ID.5 -print -quit) \( \
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IP.5 -print -quit) -iname CDROM_W.40 -o \
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit) -iname CDROM_S.40 -o \
[ -n "$find" ] && DETECTED="win2k" && return 0 -iname CDROM_TS.40 \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="winnt4"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit) || return 1
if [ -n "$marker" ]; then
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname CDROM_IA.5 -o \
-iname CDROM_ID.5 -o \
-iname CDROM_IP.5 -o \
-iname CDROM_IS.5 \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="win2k"
return 0
fi
fi fi
find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit) marker=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit) || return 1
[ -n "$marker" ] || return 1
if [ -n "$find" ]; then marker=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit) if [ -n "$marker" ]; then
[ -n "$find" ] && DETECTED="winxpx64" && return 0 DETECTED="winxpx64"
return 0
fi
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit) marker=$(find "$dir" -maxdepth 1 -type f \
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IP -print -quit) \( \
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname setupxp.htm -print -quit) -iname WIN51IC -o \
[ -n "$find" ] && DETECTED="winxpx86" && return 0 -iname WIN51IP -o \
-iname setupxp.htm \
\) \
-print -quit) || return 1
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit) if [ -n "$marker" ]; then
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit) DETECTED="winxpx86"
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit) return 0
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit) fi
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IL -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AD -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AS -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MA -print -quit)
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MD -print -quit)
[ -n "$find" ] && DETECTED="win2003r2" && return 0
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname WIN51IS -o \
-iname WIN51IA -o \
-iname WIN51IB -o \
-iname WIN51ID -o \
-iname WIN51IL -o \
-iname WIN51AA -o \
-iname WIN51AD -o \
-iname WIN51AS -o \
-iname WIN51MA -o \
-iname WIN51MD \
\) \
-print -quit) || return 1
if [ -n "$marker" ]; then
DETECTED="win2003r2"
return 0
fi fi
return 1 return 1
@@ -607,15 +655,15 @@ resolveImage() {
setImage() { setImage() {
skipVersion "${DETECTED,,}" && return 0 skipVersion "${DETECTED,,}" && return 0
setXML "" && return 0
enabled "$MANUAL" && return 0
if ! setXML "" && ! enabled "$MANUAL"; then
MANUAL="Y" MANUAL="Y"
local desc local desc
desc=$(printEdition "$DETECTED" "this version") desc=$(printEdition "$DETECTED" "this version") || return 1
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
fi
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
return 0 return 0
} }
@@ -677,20 +725,19 @@ getSuggestion() {
validateEdition() { validateEdition() {
[ -n "$EDITION" ] || return 0 [ -n "$EDITION" ] || return 0
[[ "${DETECTED,,}" == "win20"* ]] || return 0
case "${DETECTED,,}" in
"win20"* )
local edition local edition
edition=$(normalizeServerEditionID "$EDITION") edition=$(normalizeServerEditionID "$EDITION") || return 1
if [ -n "$edition" ] && [ -n "$edition" ] || return 0
[[ "${DETECTED,,}" != *"-${edition,,}" &&
"${DETECTED,,}" != *"-${edition,,}-eval" ]]; then if [[ "${DETECTED,,}" == *"-${edition,,}" ||
EDITION="" "${DETECTED,,}" == *"-${edition,,}-eval" ]]; then
return 0
fi fi
;;
esac
EDITION=""
return 0 return 0
} }
@@ -715,13 +762,12 @@ describeImage() {
local result_name="$3" local result_name="$3"
local result local result
result=$(printEdition "$DETECTED" "$DETECTED" "Y") result=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
detectLanguage "$info_xml" "$index" || return 1
detectLanguage "$info_xml" "$index"
if [[ "${LANGUAGE,,}" != "en" && "${LANGUAGE,,}" != "en-"* ]]; then if [[ "${LANGUAGE,,}" != "en" && "${LANGUAGE,,}" != "en-"* ]]; then
local language local language
language=$(getLanguage "$LANGUAGE" "desc") language=$(getLanguage "$LANGUAGE" "desc") || return 1
result+=" ($language)" result+=" ($language)"
fi fi
@@ -745,12 +791,17 @@ configureImage() {
local msg="the answer file for $desc was not found ($DETECTED.xml)" local msg="the answer file for $desc was not found ($DETECTED.xml)"
local fallback="/run/assets/${DETECTED%%-*}.xml" local fallback="/run/assets/${DETECTED%%-*}.xml"
if setXML "$fallback" "$index" || enabled "$MANUAL"; then if setXML "$fallback" "$index"; then
! enabled "$MANUAL" && warn "${msg}." if ! enabled "$MANUAL"; then
else warn "${msg}."
fi
return 0
fi
enabled "$MANUAL" && return 0
MANUAL="Y" MANUAL="Y"
warn "${msg}, $FB." warn "${msg}, $FB."
fi
return 0 return 0
} }
@@ -763,9 +814,7 @@ detectImage() {
XML="" XML=""
resolveImage "$version" || : if resolveImage "$version"; then
if [ -n "$DETECTED" ]; then
setImage || return 1 setImage || return 1
return 0 return 0
fi fi
@@ -773,7 +822,7 @@ detectImage() {
info "Detecting version from ISO image..." info "Detecting version from ISO image..."
if detectLegacy "$dir"; then if detectLegacy "$dir"; then
desc=$(printEdition "$DETECTED" "$DETECTED" "Y") desc=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
info "Detected: $desc" info "Detected: $desc"
return 0 return 0
fi fi
@@ -811,7 +860,6 @@ normalizeBatch() {
local file="$1" local file="$1"
local bom tmp encoding local bom tmp encoding
[ ! -f "$file" ] && return 0
[ ! -s "$file" ] && return 0 [ ! -s "$file" ] && return 0
bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1 bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1
@@ -842,6 +890,30 @@ normalizeBatch() {
return 0 return 0
} }
reportBatchMatches() {
local file="$1"
local source="$2"
local pattern="$3"
local message="$4"
local suggestion="$5"
local matches line
matches=$(grep -Pin "$pattern" "$file" || true)
[ -n "$matches" ] || return 0
warn "$message in $source:"
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf ' %s\n\n' "$suggestion" >&2
return 0
}
checkBatch() { checkBatch() {
local file="$1" local file="$1"
@@ -936,75 +1008,35 @@ EOC
fi fi
rm -rf "$tmp" rm -rf "$tmp" || true
matches=$( reportBatchMatches \
grep -Pin \ "$file" \
"$source" \
'(?<!\\)\\host[.]lan[\\]' \ '(?<!\\)\\host[.]lan[\\]' \
"$file" || true "invalid single-backslash UNC path detected" \
) 'Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".'
if [ -n "$matches" ]; then reportBatchMatches \
warn "invalid single-backslash UNC path detected in $source:" "$file" \
"$source" \
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".' >&2
fi
matches=$(
grep -Pin \
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \ '(?<![\\[:alnum:]._-])host[.]lan[\\]' \
"$file" || true "UNC path without leading backslashes detected" \
) 'Use "\\host.lan\Data\..." instead of "host.lan\Data\...".'
if [ -n "$matches" ]; then reportBatchMatches \
warn "UNC path without leading backslashes detected in $source:" "$file" \
"$source" \
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "host.lan\Data\...".' >&2
fi
matches=$(
grep -Pin \
'//host[.]lan/' \ '//host[.]lan/' \
"$file" || true "invalid forward-slash UNC path detected" \
) 'Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".'
if [ -n "$matches" ]; then reportBatchMatches \
warn "invalid forward-slash UNC path detected in $source:" "$file" \
"$source" \
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".' >&2
fi
matches=$(
grep -Pin \
'\\\\host[.]lan\\shared(?:[\\/]|$)' \ '\\\\host[.]lan\\shared(?:[\\/]|$)' \
"$file" || true "invalid Samba share name detected" \
) 'The "/shared" folder is exposed to Windows as "\\host.lan\Data".'
if [ -n "$matches" ]; then
warn "invalid Samba share name detected in $source:"
while IFS= read -r line; do
printf ' %s\n' "$line" >&2
done <<< "$matches"
printf '%s\n\n' \
' The "/shared" folder is exposed to Windows as "\\host.lan\Data".' >&2
fi
return 0 return 0
} }
@@ -1134,7 +1166,10 @@ buildImage() {
local hide="Warning: creating filesystem that does not conform to ISO-9660." local hide="Warning: creating filesystem that does not conform to ISO-9660."
[ -s "$log" ] && err="$(<"$log")" [ -s "$log" ] && err="$(<"$log")"
[[ "$err" != "$hide" ]] && echo "$err"
if [ -n "$err" ] && [[ "$err" != "$hide" ]]; then
echo "$err"
fi
mv -f "$out" "$BOOT" || return 1 mv -f "$out" "$BOOT" || return 1
@@ -1217,12 +1252,22 @@ extractBootImage() {
rm -rf "$tmp" || true rm -rf "$tmp" || true
if ! len=$(isoinfo -d -i "$iso" | grep "Nsect " | grep -o "[^ ]*$"); then local boot_info
if ! boot_info=$(isoinfo -d -i "$iso"); then
error "Failed to read boot image information from $desc ISO!"
return 1
fi
len=$(awk '/Nsect / { print $NF; exit }' <<< "$boot_info")
offset=$(awk '/Bootoff / { print $NF; exit }' <<< "$boot_info")
if [ -z "$len" ]; then
error "Failed to determine boot image size from $desc ISO!" error "Failed to determine boot image size from $desc ISO!"
return 1 return 1
fi fi
if ! offset=$(isoinfo -d -i "$iso" | grep "Bootoff " | grep -o "[^ ]*$"); then if [ -z "$offset" ]; then
error "Failed to determine boot image offset from $desc ISO!" error "Failed to determine boot image offset from $desc ISO!"
return 1 return 1
fi fi