diff --git a/src/answer.sh b/src/answer.sh
new file mode 100644
index 00000000..87127fb9
--- /dev/null
+++ b/src/answer.sh
@@ -0,0 +1,1228 @@
+#!/usr/bin/env bash
+set -Eeuo pipefail
+
+validateResolution() {
+
+ local name="$1"
+ local value="$2"
+ local minimum="$3"
+ local number
+
+ if [[ ! "$value" =~ ^[0-9]+$ ]] || [ "${#value}" -gt 5 ]; then
+ error "The $name variable must be between $minimum and 16384!"
+ return 1
+ fi
+
+ number=$((10#$value))
+
+ if [ "$number" -lt "$minimum" ] || [ "$number" -gt 16384 ]; then
+ error "The $name variable must be between $minimum and 16384!"
+ return 1
+ fi
+
+ return 0
+}
+
+validateProductKey() {
+
+ local value="$1"
+
+ [ -z "$value" ] && return 0
+
+ if [[ ! "$value" =~ ^[A-Za-z0-9]{5}(-[A-Za-z0-9]{5}){4}$ ]]; then
+ error "The KEY variable must contain a valid 25-character product key!"
+ return 1
+ fi
+
+ return 0
+}
+
+validateComputerName() {
+
+ local value="$1"
+
+ [ -z "$value" ] && return 0
+
+ if [ "${#value}" -gt 15 ]; then
+ error "The HOST variable cannot contain more than 15 characters!"
+ return 1
+ fi
+
+ if [[ ! "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$ ]]; then
+ error "The HOST variable may only contain letters, digits, and hyphens, and cannot start or end with a hyphen!"
+ return 1
+ fi
+
+ if [[ "$value" =~ ^[0-9]+$ ]]; then
+ error "The HOST variable cannot contain only digits!"
+ return 1
+ fi
+
+ return 0
+}
+
+validateWorkgroup() {
+
+ local value="$1"
+ local safe=""
+
+ [ -z "$value" ] && return 0
+
+ if [ "${#value}" -gt 15 ]; then
+ error "The WORKGROUP variable cannot contain more than 15 characters!"
+ return 1
+ fi
+
+ safe=$(printf '%s' "$value" | tr -d '"/\\[]:;|=,+*?<>') || return 1
+
+ if [[ "$safe" != "$value" ]]; then
+ error "The WORKGROUP variable contains characters that are not valid in a NetBIOS name!"
+ return 1
+ fi
+
+ if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
+ error "The WORKGROUP variable cannot consist only of spaces or periods!"
+ return 1
+ fi
+
+ return 0
+}
+
+validateMembership() {
+
+ if [ -n "$DOMAIN" ] && [ -n "$WORKGROUP" ]; then
+ error "The DOMAIN and WORKGROUP variables cannot be used together!"
+ return 1
+ fi
+
+ if [ -n "$DOMAIN_OU" ] && [ -z "$DOMAIN" ]; then
+ error "The DOMAIN_OU variable requires DOMAIN to be specified!"
+ return 1
+ fi
+
+ validateWorkgroup "$WORKGROUP" || return 1
+ return 0
+}
+
+validatePassword() {
+
+ local value="$1"
+ local desc="${2:-}"
+ local suffix=""
+
+ [ -n "$desc" ] && suffix=" for $desc"
+
+ if [ "${#value}" -gt 127 ]; then
+ error "The PASSWORD variable cannot contain more than 127 characters$suffix!"
+ return 1
+ fi
+
+ if [[ "$value" =~ [[:cntrl:]] ]]; then
+ error "The PASSWORD variable cannot contain control characters$suffix!"
+ return 1
+ fi
+
+ return 0
+}
+
+escapeXMLSed() {
+
+ local s
+
+ s=$(escapeXML "$1") || return 1
+ s=${s//\\/\\\\}
+ s=${s//&/\\&}
+ s=${s//|/\\|}
+
+ printf '%s' "$s"
+ return 0
+}
+
+validateUsername() {
+
+ local value="$1"
+ local type="$2"
+ local maximum
+
+ case "$type" in
+ "local" )
+ maximum=20
+ [ -z "$value" ] && return 0
+ ;;
+ "domain" )
+ maximum=256
+
+ if [ -z "$value" ]; then
+ error "The USERNAME variable does not contain a valid domain account name!"
+ return 1
+ fi ;;
+ * )
+ return 1 ;;
+ esac
+
+ if [ "${#value}" -gt "$maximum" ]; then
+ if [[ "$type" == "domain" ]]; then
+ 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
+ fi
+
+ if [[ "$value" =~ [[:cntrl:]] ]]; then
+ error "The USERNAME variable cannot contain control characters!"
+ return 1
+ fi
+
+ case "$value" in
+ *'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* | *'%'* | *'@'* )
+ if [[ "$type" == "domain" ]]; then
+ error "The domain account name contains characters that are not supported by Windows unattended setup!"
+ else
+ error "The USERNAME variable contains characters that are not supported by Windows local accounts!"
+ fi
+ return 1 ;;
+ esac
+
+ if [[ "$value" == *"." ]]; then
+ error "The USERNAME variable cannot end with a period!"
+ return 1
+ fi
+
+ if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
+ error "The USERNAME variable cannot consist only of spaces or periods!"
+ return 1
+ fi
+
+ case "${value^^}" in
+ "NONE" )
+ error "The USERNAME value \"NONE\" is reserved by Windows!"
+ return 1 ;;
+ "ADMINISTRATOR" | "GUEST" | "DEFAULTACCOUNT" | "WDAGUTILITYACCOUNT" | "WSIACCOUNT" )
+ if [[ "$type" == "local" ]]; then
+ error "The USERNAME value \"$value\" is reserved for a built-in Windows account!"
+ return 1
+ fi ;;
+ esac
+
+ return 0
+}
+
+validateDomainName() {
+
+ local value="$1"
+ local name="${2:-DOMAIN}"
+
+ if [ -z "$value" ]; then
+ error "The $name variable must contain a valid domain name!"
+ return 1
+ fi
+
+ if [[ "$value" == *"://"* ]]; then
+ error "The $name variable must contain a domain name, not a URL!"
+ return 1
+ fi
+
+ if [ "${#value}" -gt 255 ] ||
+ [[ "$value" =~ [[:cntrl:]] ]] ||
+ [[ "$value" =~ [[:space:]] ]] ||
+ [[ ! "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$ ]]; then
+
+ error "The $name variable does not contain a valid domain name!"
+ return 1
+ fi
+
+ return 0
+}
+
+setXML() {
+
+ local file="/custom.xml"
+
+ CUSTOM_XML=""
+
+ if [ -d "$file" ]; then
+ error "The bind $file maps to a file that does not exist!" && exit 67
+ fi
+
+ [ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml"
+ [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml"
+ [ ! -f "$file" ] || [ ! -s "$file" ] && file="$1"
+ [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/$DETECTED.xml"
+ [ ! -f "$file" ] || [ ! -s "$file" ] && return 1
+
+ case "$file" in
+ "/custom.xml" | "$STORAGE/custom.xml" ) CUSTOM_XML="Y" ;;
+ esac
+
+ XML="$file"
+ return 0
+}
+
+updateDomain() {
+
+ local asset="$1"
+ local domain account auth pass pw
+ local cred_domain ou arch tmp result
+
+ domain=$(escapeXML "$2") || return 1
+ account=$(escapeXML "$3") || return 1
+ auth=$(escapeXML "$4") || return 1
+ pass=$(escapeXML "$5") || return 1
+ pw="$6"
+ ou=$(escapeXML "$7") || return 1
+
+ arch=$(sed -n -E \
+ '0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' \
+ "$asset") || return 1
+
+ [ -z "$arch" ] && return 1
+
+ cred_domain="$domain"
+
+ case "$4" in
+ *@* ) cred_domain="" ;;
+ esac
+
+ grep -Eq 'Microsoft-Windows-UnattendedJoin|])' "$asset" && return 1
+
+ tmp=$(mktemp -d) || return 1
+ result="$tmp/answer.xml"
+
+ if ! DOMAIN_XML="$domain" ACCOUNT_XML="$account" \
+ AUTH_XML="$auth" PASS_XML="$pass" \
+ CRED_DOMAIN="$cred_domain" PW="$pw" OU_XML="$ou" \
+ ARCH_XML="$arch" \
+ awk '
+ /]*pass="specialize"[^>]*>/ { section = "specialize" }
+ /]*pass="oobeSystem"[^>]*>/ { section = "oobeSystem" }
+ section == "oobeSystem" && /])/ { in_accounts = 1 }
+ section == "oobeSystem" && /])/ { in_autologon = 1 }
+
+ section == "oobeSystem" && in_accounts && !accounts_added &&
+ /])/ {
+ print " \n" \
+ " \n" \
+ " \n" \
+ " " ENVIRON["ACCOUNT_XML"] "\n" \
+ " Administrators\n" \
+ " \n" \
+ " " ENVIRON["DOMAIN_XML"] "\n" \
+ " \n" \
+ " "
+ accounts_added = 1
+ }
+
+ section == "oobeSystem" && in_autologon &&
+ /^[[:space:]]*.*<\/Username>[[:space:]]*$/ {
+ print " " ENVIRON["ACCOUNT_XML"] "\n" \
+ " " ENVIRON["DOMAIN_XML"] ""
+ autologon_added = 1
+ next
+ }
+
+ section == "oobeSystem" && in_autologon &&
+ /^[[:space:]]*])/ { next }
+
+ section == "oobeSystem" && in_autologon &&
+ /^[[:space:]]*.*<\/Value>[[:space:]]*$/ {
+ print " " ENVIRON["PW"] ""
+ password_added = 1
+ next
+ }
+
+ section == "specialize" && !join_added &&
+ /^[[:space:]]*<\/settings>[[:space:]]*$/ {
+ print " \n" \
+ " \n" \
+ " "
+
+ if (ENVIRON["CRED_DOMAIN"] != "") {
+ print " " ENVIRON["CRED_DOMAIN"] ""
+ }
+
+ print " " ENVIRON["AUTH_XML"] "\n" \
+ " " ENVIRON["PASS_XML"] "\n" \
+ " \n" \
+ " " ENVIRON["DOMAIN_XML"] ""
+
+ if (ENVIRON["OU_XML"] != "") {
+ print " " ENVIRON["OU_XML"] ""
+ }
+
+ print " \n" \
+ " "
+
+ join_added = 1
+ }
+
+ { print }
+
+ section == "oobeSystem" && /<\/AutoLogon>/ { in_autologon = 0 }
+ section == "oobeSystem" && /<\/UserAccounts>/ { in_accounts = 0 }
+ /^[[:space:]]*<\/settings>[[:space:]]*$/ { section = "" }
+
+ END { exit !(join_added && accounts_added && autologon_added && password_added) }
+ ' "$asset" > "$result" ||
+ ! mv -f "$result" "$asset"; then
+
+ rm -rf "$tmp" || true
+ return 1
+ fi
+
+ rm -rf "$tmp" || return 1
+ return 0
+}
+
+updateWorkgroup() {
+
+ local asset="$1"
+ local workgroup arch tmp result
+
+ workgroup=$(escapeXML "$2") || return 1
+ arch=$(sed -n -E '0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' "$asset") || return 1
+ [ -z "$arch" ] && return 1
+
+ grep -q 'Microsoft-Windows-UnattendedJoin' "$asset" && return 1
+
+ tmp=$(mktemp -d) || return 1
+ result="$tmp/answer.xml"
+
+ if ! WORKGROUP_XML="$workgroup" ARCH_XML="$arch" awk '
+ /]*pass="specialize"[^>]*>/ { section = "specialize" }
+
+ section == "specialize" && !workgroup_added &&
+ /^[[:space:]]*<\/settings>[[:space:]]*$/ {
+ print " \n" \
+ " \n" \
+ " " ENVIRON["WORKGROUP_XML"] "\n" \
+ " \n" \
+ " "
+ workgroup_added = 1
+ }
+
+ { print }
+
+ /^[[:space:]]*<\/settings>[[:space:]]*$/ { section = "" }
+ END { exit !workgroup_added }
+ ' "$asset" > "$result" ||
+ ! mv -f "$result" "$asset"; then
+
+ rm -rf "$tmp" || true
+ return 1
+ fi
+
+ rm -rf "$tmp" || return 1
+ return 0
+}
+
+updateXML() {
+
+ local asset="$1"
+ local language="$2"
+ local app value culture region keyboard edition
+ local user user_xml auth_user admin pass pw
+ local domain qualifier host workgroup key
+
+ [ -z "$WIDTH" ] && WIDTH="1280"
+ [ -z "$HEIGHT" ] && HEIGHT="720"
+
+ validateResolution "WIDTH" "$WIDTH" 320 || return 1
+ validateResolution "HEIGHT" "$HEIGHT" 200 || return 1
+ validateMembership || return 1
+ validateComputerName "$HOST" || return 1
+ validateProductKey "$KEY" || return 1
+ validatePassword "$PASSWORD" || return 1
+
+ app=$(escapeXMLSed "$APP for $ENGINE") || return 1
+
+ sed -i "s|>Windows for Docker<|>$app<|g" "$asset" || return 1
+ sed -i -E "s|[^<]*|$HEIGHT|g" "$asset" || return 1
+ sed -i -E "s|[^<]*|$WIDTH|g" "$asset" || return 1
+
+ culture=$(getLanguage "$language" "culture") || return 1
+
+ if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then
+ value=$(escapeXMLSed "$culture") || return 1
+ sed -i "s|en-US|$value|g" "$asset" || return 1
+ fi
+
+ region="$REGION"
+ [ -z "$region" ] && region="$culture"
+
+ if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then
+ value=$(escapeXMLSed "$region") || return 1
+ sed -i "s|en-US|$value|g" "$asset" || return 1
+ sed -i "s|en-US|$value|g" "$asset" || return 1
+ fi
+
+ keyboard="$KEYBOARD"
+ [ -z "$keyboard" ] && keyboard="$culture"
+
+ if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then
+ value=$(escapeXMLSed "$keyboard") || return 1
+ sed -i "s|en-US|$value|g" "$asset" || return 1
+ sed -i "s|0409:00000409|$value|g" "$asset" || return 1
+ fi
+
+ if [ -n "$HOST" ]; then
+ host=$(escapeXMLSed "$HOST") || return 1
+ sed -i -E "s|[^<]*|$host|g" "$asset" || return 1
+ fi
+
+ domain="$DOMAIN"
+ workgroup="$WORKGROUP"
+
+ if [ -n "$domain" ]; then
+
+ if [ -z "$USERNAME" ]; then
+ error "The USERNAME variable must be specified when joining a domain!"
+ return 1
+ fi
+
+ if [ -z "$PASSWORD" ]; then
+ error "The PASSWORD variable must be specified when joining a domain!"
+ return 1
+ fi
+
+ validateDomainName "$domain" || return 1
+
+ auth_user="$USERNAME"
+ qualifier=""
+
+ if [[ "$auth_user" == *\\* ]]; then
+ error "The USERNAME variable must use either \"user\" or \"user@domain\" format!"
+ return 1
+ fi
+
+ case "$auth_user" in
+ *@* )
+ user="${auth_user%%@*}"
+ qualifier="${auth_user#*@}"
+
+ if [ -z "$user" ] || [ -z "$qualifier" ] || [[ "$qualifier" == *@* ]]; then
+ error "The USERNAME variable does not contain a valid domain account name!"
+ return 1
+ fi
+
+ validateDomainName "$qualifier" "USERNAME" || return 1
+
+ if [[ "${qualifier,,}" != "${domain,,}" ]]; then
+ error "The domain in the USERNAME variable must match the DOMAIN variable!"
+ return 1
+ fi
+ ;;
+ * )
+ user="$auth_user"
+ ;;
+ esac
+
+ validateUsername "$user" "domain" || return 1
+
+ if [[ "${user,,}" == "docker" ]]; then
+ error "The USERNAME variable must be changed from its default value when joining a domain!"
+ return 1
+ fi
+
+ if [[ "$PASSWORD" == "admin" ]]; then
+ error "The PASSWORD variable must be changed from its default value when joining a domain!"
+ return 1
+ fi
+
+ else
+
+ user="$USERNAME"
+ validateUsername "$user" "local" || return 1
+
+ if [ -n "$user" ]; then
+ user_xml=$(escapeXMLSed "$user") || return 1
+
+ sed -i "s|-name \"Docker\"|-name \"\$env:USERNAME\"|g" "$asset" || return 1
+ sed -i 's|where name="Docker"|where name="%USERNAME%"|g' "$asset" || return 1
+ sed -i "s|Docker|$user_xml|g" "$asset" || return 1
+ sed -i "s|Docker|$user_xml|g" "$asset" || return 1
+ sed -i "s|Docker|$user_xml|g" "$asset" || return 1
+ fi
+
+ fi
+
+ if [ -n "$domain" ]; then
+ pass="admin"
+ else
+ [ -n "$PASSWORD" ] && pass="$PASSWORD" || pass="admin"
+ fi
+
+ pw=$(printf '%s' "${pass}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
+ admin=$(printf '%s' "${pass}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
+
+ sed -i -z -E "s#([[:space:]]*|>[^<]*)#\1>$pw#g" "$asset" || return 1
+ sed -i -z -E "s#([[:space:]]*|>[^<]*)#\1>$admin#g" "$asset" || return 1
+ sed -i -E "s|[^<]*|false|g" "$asset" || return 1
+
+ if [ -n "$domain" ]; then
+
+ pw=$(printf '%s' "${PASSWORD}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
+
+ if ! updateDomain "$asset" "$domain" "$user" \
+ "$auth_user" "$PASSWORD" "$pw" "$DOMAIN_OU"; then
+ error "Failed to add domain configuration to answer file!"
+ return 1
+ fi
+
+ elif [ -n "$workgroup" ]; then
+
+ if ! updateWorkgroup "$asset" "$workgroup"; then
+ error "Failed to add workgroup configuration to answer file!"
+ return 1
+ fi
+
+ fi
+
+ if disabled "$AUTOLOGIN"; then
+ sed -i -E '/^[[:space:]]*])/,/^[[:space:]]*<\/AutoLogon>[[:space:]]*$/d' "$asset" || return 1
+ fi
+
+ if [ -n "$EDITION" ]; then
+ case "${EDITION,,}" in
+ "core" ) edition="STANDARDCORE" ;;
+ * ) edition="${EDITION^^}" ;;
+ esac
+
+ edition=$(escapeXMLSed "$edition") || return 1
+ sed -i "s|SERVERSTANDARD|SERVER$edition|g" "$asset" || return 1
+ fi
+
+ if [ -n "$KEY" ]; then
+ key=$(escapeXMLSed "$KEY") || return 1
+ sed -i -E '/^[[:space:]]*[[:space:]]*$/,/^[[:space:]]*<\/ProductKey>[[:space:]]*$/d' "$asset" || return 1
+ sed -i -E "s|[^<]*|$key|g" "$asset" || return 1
+ sed -i "s|| \n $key\n OnError\n \n |g" "$asset" || return 1
+ fi
+
+ if ! xmllint --nonet --noout "$asset"; then
+ error "The generated answer file is not valid XML!"
+ return 1
+ fi
+
+ return 0
+}
+
+escapeSIFValue() {
+
+ local s="$1"
+
+ s=${s//%/%%}
+ s=${s//\"/\"\"}
+
+ printf '%s' "$s"
+ return 0
+}
+
+escapeRegistryValue() {
+
+ printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
+}
+
+validateLegacyText() {
+
+ local name="$1"
+ local value="$2"
+ local desc="${3:-}"
+ local suffix=""
+
+ [ -n "$desc" ] && suffix=" for $desc"
+
+ if [[ "$value" =~ [[:cntrl:]] ]]; then
+ error "The $name variable cannot contain control characters$suffix!"
+ return 1
+ fi
+
+ if [[ "$value" == *'"'* ]]; then
+ error "The $name variable cannot contain double quotes$suffix!"
+ return 1
+ fi
+
+ return 0
+}
+
+validateLegacyUsername() {
+
+ local value="$1"
+ local desc="${2:-}"
+ local suffix=""
+
+ [ -n "$desc" ] && suffix=" for $desc"
+
+ if [ -z "$value" ]; then
+ error "The USERNAME variable cannot be empty$suffix!"
+ return 1
+ fi
+
+ if [ "${#value}" -gt 20 ]; then
+ error "The USERNAME variable cannot contain more than 20 characters$suffix!"
+ return 1
+ fi
+
+ if [[ "$value" =~ [[:cntrl:]] ]]; then
+ error "The USERNAME variable cannot contain control characters$suffix!"
+ return 1
+ fi
+
+ case "$value" in
+ *'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* )
+ error "The USERNAME variable contains unsupported characters$suffix!"
+ return 1 ;;
+ esac
+
+ if [[ "$value" == *"." ]]; then
+ error "The USERNAME variable cannot end with a period$suffix!"
+ return 1
+ fi
+
+ if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
+ error "The USERNAME variable cannot consist only of spaces or periods$suffix!"
+ return 1
+ fi
+
+ if [[ "${value^^}" == "GUEST" ]]; then
+ error "The USERNAME value \"$value\" is reserved for a built-in Windows account$suffix!"
+ return 1
+ fi
+
+ return 0
+}
+
+detectLegacy() {
+
+ local dir="$1"
+ local find
+
+ find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit)
+ [ -n "$find" ] && DETECTED="win95" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit)
+ [ -n "$find" ] && DETECTED="win98" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit)
+ [ -n "$find" ] && DETECTED="win9x" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit)
+ [ -n "$find" ] && DETECTED="winnt4" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit)
+
+ if [ -n "$find" ]; then
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit)
+ [ -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)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit)
+ [ -n "$find" ] && DETECTED="win2k" && return 0
+
+ fi
+
+ find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit)
+
+ if [ -n "$find" ]; then
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit)
+ [ -n "$find" ] && DETECTED="winxpx64" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit)
+ [ -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)
+ [ -n "$find" ] && DETECTED="winxpx86" && return 0
+
+ find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
+ [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
+ [ -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
+
+ fi
+
+ return 1
+}
+
+legacyInstall() {
+
+ local pid=""
+ local file=""
+ local dir="$2"
+ local desc="$3"
+ local driver="$4"
+ local drivers="/tmp/drivers"
+
+ if [ -n "$DOMAIN" ]; then
+ error "The DOMAIN variable is not supported for $desc!"
+ return 1
+ fi
+
+ ETFS="[BOOT]/Boot-NoEmul.img"
+
+ if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
+ error "Failed to locate file \"$ETFS\" in $desc ISO image!" && return 1
+ fi
+
+ local arch target
+ [ -d "$dir/AMD64" ] && arch="amd64" || arch="x86"
+ [[ "${arch,,}" == "x86" ]] && target="$dir/I386" || target="$dir/AMD64"
+
+ if [ ! -d "$target" ]; then
+ error "Failed to locate directory \"$target\" in $desc ISO image!" && return 1
+ fi
+
+ if [[ "${driver,,}" == "xp" || "${driver,,}" == "2k3" ]]; then
+
+ 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
+ error "Failed to locate required storage drivers!" && return 1
+ fi
+
+ cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1
+
+ mkdir -p "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
+ cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
+ cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
+ cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
+
+ if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then
+ error "Failed to locate required network drivers!" && return 1
+ fi
+
+ mkdir -p "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
+ cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
+ cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
+ cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || 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
+
+ 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 '/^\[SCSI\]/s/$/\nviostor=\"Red Hat VirtIO SCSI Disk Device\"/' "$file" || return 1
+ sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"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
+
+ if [ ! -d "$drivers/sata/xp/$arch" ]; then
+ error "Failed to locate required SATA drivers!" && return 1
+ fi
+
+ mkdir -p "$dir/\$OEM\$/\$1/Drivers/sata" || return 1
+ cp -Lr "$drivers/sata/xp/$arch/." "$dir/\$OEM\$/\$1/Drivers/sata" || 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 '/^\[FileFlags\]/s/$/\niaStor.sys = 16/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.cat = 1,,,,,,,1,0,0/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.inf = 1,,,,,,,1,0,0/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,4_,4,1,,,1,4/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,,1,0,0/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaahci.cat = 1,,,,,,,1,0,0/' "$file" || return 1
+ sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaAHCI.inf = 1,,,,,,,1,0,0/' "$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
+
+ rm -rf "$drivers" || return 1
+
+ fi
+
+ local key setup
+ setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1
+
+ if [ -n "$setup" ] && [ -z "$KEY" ]; then
+
+ pid=$(<"$setup") || return 1
+ pid="${pid%$'\r'}"
+
+ if [[ "$driver" == "2k" ]]; then
+
+ echo "${pid:0:$((${#pid})) - 3}270" > "$setup" || return 1
+
+ else
+
+ if [[ "$pid" == *"270" ]]; then
+
+ warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation."
+
+ else
+
+ file=$(find "$target" -maxdepth 1 -type f -iname PID.INF -print -quit) || return 1
+
+ if [ -n "$file" ]; then
+
+ if [[ "$driver" == "2k3" ]]; then
+
+ key=$(grep -i -A 2 "StagingKey" "$file" | tail -n 2 | head -n 1) || key=""
+
+ else
+
+ key="${pid:$((${#pid})) - 8:5}"
+ if [[ "${pid^^}" == *"OEM" ]]; then
+ key=$(grep -i -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
+ else
+ key=$(grep -i -m 1 -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
+ fi
+ key="${key#*= }"
+
+ fi
+
+ key="${key%$'\r'}"
+ [[ "${#key}" == "29" ]] && KEY="$key"
+
+ fi
+
+ if [ -z "$KEY" ]; then
+
+ # These are NOT pirated keys, they come from official MS documentation.
+
+ case "${driver,,}" in
+ "xp" )
+
+ if [[ "${arch,,}" == "x86" ]]; then
+ # Windows XP Professional x86 generic trial key (no activation)
+ KEY="DR8GV-C8V6J-BYXHG-7PYJR-DB66Y"
+ else
+ # Windows XP Professional x64 generic trial key (no activation)
+ KEY="B2RBK-7KPT9-4JP6X-QQFWM-PJD6G"
+ fi ;;
+
+ "2k3" )
+
+ if [[ "${arch,,}" == "x86" ]]; then
+ # Windows Server 2003 Standard x86 generic trial key (no activation)
+ KEY="QKDCQ-TP2JM-G4MDG-VR6F2-P9C48"
+ else
+ # Windows Server 2003 Standard x64 generic trial key (no activation)
+ KEY="P4WJG-WK3W7-3HM8W-RWHCK-8JTRY"
+ fi ;;
+
+ esac
+
+ echo "${pid:0:$((${#pid})) - 3}000" > "$setup" || return 1
+
+ fi
+
+ fi
+ fi
+
+ fi
+
+ validateProductKey "$KEY" || return 1
+
+ local product=""
+ [ -n "$KEY" ] && product="ProductID=$KEY"
+
+ mkdir -p "$dir/\$OEM\$" || return 1
+
+ if ! addFolder "$dir"; then
+ error "Failed to add OEM folder to image!" && return 1
+ fi
+
+ local oem=""
+ local install="$dir/\$OEM\$/\$1/OEM/install.bat"
+ [ -f "$install" ] && oem="\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat\\\"\""
+
+ [ -z "$WIDTH" ] && WIDTH="1280"
+ [ -z "$HEIGHT" ] && HEIGHT="720"
+
+ validateResolution "WIDTH" "$WIDTH" 320 || return 1
+ validateResolution "HEIGHT" "$HEIGHT" 200 || return 1
+ validateMembership || return 1
+ validateComputerName "$HOST" || return 1
+ validateLegacyText "APP" "$APP" "$desc" || return 1
+ validateLegacyText "ENGINE" "$ENGINE" "$desc" || return 1
+
+ XHEX=$(printf '%08x\n' "$((10#$WIDTH))") || return 1
+ YHEX=$(printf '%08x\n' "$((10#$HEIGHT))") || return 1
+
+ local username="${USERNAME:-Docker}"
+ local password="${PASSWORD:-admin}"
+ local workgroup="${WORKGROUP:-WORKGROUP}"
+
+ local sifHost sifUsername sifPassword sifOrganization sifWorkgroup
+ local regUsername regPassword
+
+ validateLegacyUsername "$username" "$desc" || return 1
+ validatePassword "$password" "$desc" || return 1
+
+ sifHost=$(escapeSIFValue "${HOST:-*}") || return 1
+ sifUsername=$(escapeSIFValue "$username") || return 1
+ sifPassword=$(escapeSIFValue "$password") || return 1
+ sifOrganization=$(escapeSIFValue "$APP for $ENGINE") || return 1
+ sifWorkgroup=$(escapeSIFValue "$workgroup") || return 1
+ regUsername=$(escapeRegistryValue "$username") || return 1
+ regPassword=$(escapeRegistryValue "$password") || return 1
+
+ find "$target" -maxdepth 1 -type f -iname winnt.sif -delete || return 1
+
+ { echo "[Data]"
+ echo " AutoPartition=1"
+ echo " MsDosInitiated=\"0\""
+ echo " UnattendedInstall=\"Yes\""
+ echo " AutomaticUpdates=\"Yes\""
+ echo ""
+ echo "[Unattended]"
+ echo " UnattendSwitch=Yes"
+ echo " UnattendMode=FullUnattended"
+ echo " FileSystem=NTFS"
+ echo " OemSkipEula=Yes"
+ echo " OemPreinstall=Yes"
+ echo " Repartition=Yes"
+ echo " WaitForReboot=\"No\""
+ echo " DriverSigningPolicy=\"Ignore\""
+ echo " NonDriverSigningPolicy=\"Ignore\""
+ printf '%s\n' " OemPnPDriversPath=\"Drivers\viostor;Drivers\NetKVM;Drivers\sata\""
+ echo " NoWaitAfterTextMode=1"
+ echo " NoWaitAfterGUIMode=1"
+ echo " FileSystem=ConvertNTFS"
+ echo " ExtendOemPartition=0"
+ echo " Hibernation=\"No\""
+ echo ""
+ echo "[GuiUnattended]"
+ echo " OEMSkipRegional=1"
+ echo " OemSkipWelcome=1"
+ echo " AdminPassword=\"$sifPassword\""
+ echo " TimeZone=0"
+ if disabled "$AUTOLOGIN"; then
+ echo " AutoLogon=No"
+ else
+ echo " AutoLogon=Yes"
+ echo " AutoLogonCount=65432"
+ fi
+ echo ""
+ echo "[UserData]"
+ echo " FullName=\"$sifUsername\""
+ echo " ComputerName=\"$sifHost\""
+ echo " OrgName=\"$sifOrganization\""
+ echo " $product"
+ echo ""
+ echo "[Identification]"
+ echo " JoinWorkgroup = \"$sifWorkgroup\""
+ echo ""
+ echo "[Display]"
+ echo " BitsPerPel=32"
+ echo " XResolution=$WIDTH"
+ echo " YResolution=$HEIGHT"
+ echo ""
+ echo "[Networking]"
+ echo " InstallDefaultComponents=Yes"
+ echo ""
+ echo "[Branding]"
+ echo " BrandIEUsingUnattended=Yes"
+ echo ""
+ echo "[URL]"
+ echo " Home_Page = http://www.google.com"
+ echo " Search_Page = http://www.google.com"
+ echo ""
+ echo "[TerminalServices]"
+ echo " AllowConnections=1"
+ echo ""
+ } | unix2dos > "$target/WINNT.SIF" || return 1
+
+ if [[ "$driver" == "2k3" ]]; then
+ { echo "[Components]"
+ echo " TerminalServer=On"
+ echo ""
+ echo "[LicenseFilePrintData]"
+ echo " AutoMode=PerServer"
+ echo " AutoUsers=5"
+ echo ""
+ } | unix2dos >> "$target/WINNT.SIF" || return 1
+ fi
+
+ { echo "Windows Registry Editor Version 5.00"
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security]"
+ echo "\"FirstRunDisabled\"=dword:00000001"
+ echo "\"UpdatesDisableNotify\"=dword:00000001"
+ echo "\"FirewallDisableNotify\"=dword:00000001"
+ echo "\"AntiVirusDisableNotify\"=dword:00000001"
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wscsvc]"
+ echo "\"Start\"=dword:00000004"
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\GloballyOpenPorts\List]"
+ echo "\"3389:TCP\"=\"3389:TCP:*:Enabled:@xpsp2res.dll,-22009\""
+ echo ""
+ echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\Tour]"
+ echo "\"RunCount\"=dword:00000000"
+ echo ""
+ echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]"
+ echo "\"HideFileExt\"=dword:00000000"
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer]"
+ echo "\"NoWelcomeScreen\"=\"1\""
+ echo ""
+ echo "[HKEY_CURRENT_USER\Software\Microsoft\Internet Connection Wizard]"
+ echo "\"Completed\"=\"1\""
+ echo "\"Desktopchanged\"=\"1\""
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]"
+ if disabled "$AUTOLOGIN"; then
+ echo "\"AutoAdminLogon\"=\"0\""
+ else
+ echo "\"AutoAdminLogon\"=\"1\""
+ echo "\"DefaultUserName\"=\"$regUsername\""
+ echo "\"DefaultPassword\"=\"$regPassword\""
+ echo "\"DefaultDomainName\"=\"Dockur\""
+ fi
+ echo ""
+ printf '%s\n' "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
+ echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
+ echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
+ echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
+ echo ""
+ printf '%s\n' "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current\System\CurrentControlSet\Control\VIDEO\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
+ echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
+ echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
+ echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
+ echo ""
+ echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]"
+ printf '%s\n' "\"ScreenSaver\"=\"reg add \\\"HKCU\\\\Control Panel\\\\Desktop\\\" /f /v \\\"SCRNSAVE.EXE\\\" /t REG_SZ /d \\\"off\\\"\""
+ printf '%s\n' "\"ScreenSaverOff\"=\"reg add \\\"HKCU\\\\Control Panel\\\\Desktop\\\" /f /v \\\"ScreenSaveActive\\\" /t REG_SZ /d \\\"0\\\"\""
+ printf '%s\n' "\"SharedDrive\"=\"cmd /C net use Z: \\\\\\\\host.lan\\\\Data /persistent:yes\""
+ echo "$oem"
+ echo ""
+ } | unix2dos > "$dir/\$OEM\$/install.reg" || return 1
+
+ if [[ "$driver" == "2k" ]]; then
+ { echo "[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]"
+ echo "\"^SetupICWDesktop\"=-"
+ echo ""
+ } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1
+ fi
+
+ if [[ "$driver" == "2k3" ]]; then
+ { echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\srvWiz]"
+ echo "@=dword:00000000"
+ echo ""
+ echo "[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ServerOOBE\SecurityOOBE]"
+ echo "\"DontLaunchSecurityOOBE\"=dword:00000000"
+ echo ""
+ } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1
+ fi
+
+ { echo "Set WshShell = WScript.CreateObject(\"WScript.Shell\")"
+ echo "Set WshNetwork = WScript.CreateObject(\"WScript.Network\")"
+ echo "Set Domain = GetObject(\"WinNT://\" & WshNetwork.ComputerName)"
+ echo ""
+ echo "Function DecodeSID(binSID)"
+ echo " ReDim o(LenB(binSID))"
+ echo ""
+ echo " For i = 1 To LenB(binSID)"
+ echo " o(i-1) = AscB(MidB(binSID, i, 1))"
+ echo " Next"
+ echo ""
+ echo " sid = \"S-\" & CStr(o(0)) & \"-\" & OctetArrayToString _"
+ echo " (Array(o(2), o(3), o(4), o(5), o(6), o(7)))"
+ echo " For i = 8 To (4 * o(1) + 4) Step 4"
+ echo " sid = sid & \"-\" & OctetArrayToString _"
+ echo " (Array(o(i+3), o(i+2), o(i+1), o(i)))"
+ echo " Next"
+ echo ""
+ echo " DecodeSID = sid"
+ echo "End Function"
+ echo ""
+ echo "Function OctetArrayToString(arr)"
+ echo " v = 0"
+ echo " For i = 0 To UBound(arr)"
+ echo " v = v * 256 + arr(i)"
+ echo " Next"
+ echo ""
+ echo " OctetArrayToString = CStr(v)"
+ echo "End Function"
+ echo ""
+ echo "For Each DomainItem in Domain"
+ echo " If DomainItem.Class = \"User\" Then"
+ echo " sid = DecodeSID(DomainItem.Get(\"objectSID\"))"
+ echo " If Left(sid, 9) = \"S-1-5-21-\" And Right(sid, 4) = \"-500\" Then"
+ echo " LocalAdminADsPath = DomainItem.ADsPath"
+ echo " Exit For"
+ echo " End If"
+ echo " End If"
+ echo "Next"
+ echo ""
+ echo "Call Domain.MoveHere(LocalAdminADsPath, \"$username\")"
+ echo ""
+ echo "Set oLink = WshShell.CreateShortcut(WshShell.SpecialFolders(\"Desktop\") & \"\\Shared.lnk\")"
+ echo "With oLink"
+ printf '%s\n' " .TargetPath = \"\\\\host.lan\\Data\""
+ echo " .Save"
+ echo "End With"
+ echo "Set oLink = Nothing"
+ echo ""
+ } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1
+
+ { echo "[COMMANDS]"
+ echo "\"REGEDIT /s install.reg\""
+ echo "\"Wscript install.vbs\""
+ echo ""
+ } | unix2dos > "$dir/\$OEM\$/cmdlines.txt" || return 1
+
+ return 0
+}
+
+legacyPrepare() {
+
+ local iso="$1"
+ local dir="$2"
+ local desc="$3"
+
+ local tmp="$TMP/boot-images"
+ local image="$tmp/eltorito_img1_bios.img"
+
+ ETFS="boot.img"
+
+ [ -s "$dir/$ETFS" ] && return 0
+ rm -f "$dir/$ETFS" || return 1
+ rm -rf "$tmp" || return 1
+
+ if ! LC_ALL=C xorriso \
+ -no_rc \
+ -osirrox on \
+ -indev "$iso" \
+ -extract_boot_images "$tmp" >/dev/null 2>&1; then
+ rm -rf "$tmp" || true
+ error "Failed to extract boot image from $desc ISO!"
+ return 1
+ fi
+
+ if [ ! -s "$image" ]; then
+ rm -rf "$tmp" || true
+ error "Failed to locate BIOS boot image in $desc ISO!"
+ return 1
+ fi
+
+ if ! mv -f "$image" "$dir/$ETFS"; then
+ rm -rf "$tmp" || true
+ error "Failed to save boot image from $desc ISO!"
+ return 1
+ fi
+
+ rm -rf "$tmp" || return 1
+ return 0
+}
+
+return 0
diff --git a/src/define.sh b/src/define.sh
index 4e1aa6d4..8eab982b 100644
--- a/src/define.sh
+++ b/src/define.sh
@@ -1298,864 +1298,8 @@ validVersion() {
return 1
}
-validateResolution() {
-
- local name="$1"
- local value="$2"
- local minimum="$3"
- local number
-
- if [[ ! "$value" =~ ^[0-9]+$ ]] || [ "${#value}" -gt 5 ]; then
- error "The $name variable must be between $minimum and 16384!"
- return 1
- fi
-
- number=$((10#$value))
-
- if [ "$number" -lt "$minimum" ] || [ "$number" -gt 16384 ]; then
- error "The $name variable must be between $minimum and 16384!"
- return 1
- fi
-
- return 0
-}
-
-validateProductKey() {
-
- local value="$1"
-
- [ -z "$value" ] && return 0
-
- if [[ ! "$value" =~ ^[A-Za-z0-9]{5}(-[A-Za-z0-9]{5}){4}$ ]]; then
- error "The KEY variable must contain a valid 25-character product key!"
- return 1
- fi
-
- return 0
-}
-
-validateComputerName() {
-
- local value="$1"
-
- [ -z "$value" ] && return 0
-
- if [ "${#value}" -gt 15 ]; then
- error "The HOST variable cannot contain more than 15 characters!"
- return 1
- fi
-
- if [[ ! "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$ ]]; then
- error "The HOST variable may only contain letters, digits, and hyphens, and cannot start or end with a hyphen!"
- return 1
- fi
-
- if [[ "$value" =~ ^[0-9]+$ ]]; then
- error "The HOST variable cannot contain only digits!"
- return 1
- fi
-
- return 0
-}
-
-validateWorkgroup() {
-
- local value="$1"
- local safe=""
-
- [ -z "$value" ] && return 0
-
- if [ "${#value}" -gt 15 ]; then
- error "The WORKGROUP variable cannot contain more than 15 characters!"
- return 1
- fi
-
- safe=$(printf '%s' "$value" | tr -d '"/\\[]:;|=,+*?<>') || return 1
-
- if [[ "$safe" != "$value" ]]; then
- error "The WORKGROUP variable contains characters that are not valid in a NetBIOS name!"
- return 1
- fi
-
- if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
- error "The WORKGROUP variable cannot consist only of spaces or periods!"
- return 1
- fi
-
- return 0
-}
-
-validateMembership() {
-
- if [ -n "$DOMAIN" ] && [ -n "$WORKGROUP" ]; then
- error "The DOMAIN and WORKGROUP variables cannot be used together!"
- return 1
- fi
-
- if [ -n "$DOMAIN_OU" ] && [ -z "$DOMAIN" ]; then
- error "The DOMAIN_OU variable requires DOMAIN to be specified!"
- return 1
- fi
-
- validateWorkgroup "$WORKGROUP" || return 1
- return 0
-}
-
-validateLegacyText() {
-
- local name="$1"
- local value="$2"
- local desc="${3:-}"
- local suffix=""
-
- [ -n "$desc" ] && suffix=" for $desc"
-
- if [[ "$value" =~ [[:cntrl:]] ]]; then
- error "The $name variable cannot contain control characters$suffix!"
- return 1
- fi
-
- if [[ "$value" == *'"'* ]]; then
- error "The $name variable cannot contain double quotes$suffix!"
- return 1
- fi
-
- return 0
-}
-
-validateLegacyUsername() {
-
- local value="$1"
- local desc="${2:-}"
- local suffix=""
-
- [ -n "$desc" ] && suffix=" for $desc"
-
- if [ -z "$value" ]; then
- error "The USERNAME variable cannot be empty$suffix!"
- return 1
- fi
-
- if [ "${#value}" -gt 20 ]; then
- error "The USERNAME variable cannot contain more than 20 characters$suffix!"
- return 1
- fi
-
- if [[ "$value" =~ [[:cntrl:]] ]]; then
- error "The USERNAME variable cannot contain control characters$suffix!"
- return 1
- fi
-
- case "$value" in
- *'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* )
- error "The USERNAME variable contains unsupported characters$suffix!"
- return 1 ;;
- esac
-
- if [[ "$value" == *"." ]]; then
- error "The USERNAME variable cannot end with a period$suffix!"
- return 1
- fi
-
- if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
- error "The USERNAME variable cannot consist only of spaces or periods$suffix!"
- return 1
- fi
-
- if [[ "${value^^}" == "GUEST" ]]; then
- error "The USERNAME value \"$value\" is reserved for a built-in Windows account$suffix!"
- return 1
- fi
-
- return 0
-}
-
-validatePassword() {
-
- local value="$1"
- local desc="${2:-}"
- local suffix=""
-
- [ -n "$desc" ] && suffix=" for $desc"
-
- if [ "${#value}" -gt 127 ]; then
- error "The PASSWORD variable cannot contain more than 127 characters$suffix!"
- return 1
- fi
-
- if [[ "$value" =~ [[:cntrl:]] ]]; then
- error "The PASSWORD variable cannot contain control characters$suffix!"
- return 1
- fi
-
- return 0
-}
-
-escapeSIFValue() {
-
- local s="$1"
-
- s=${s//%/%%}
- s=${s//\"/\"\"}
-
- printf '%s' "$s"
- return 0
-}
-
-escapeRegistryValue() {
-
- printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
-}
-
-addFolder() {
-
- local src="$1"
- local folder="/oem" file=""
- local dest="$src/\$OEM\$/\$1/OEM"
-
- [ ! -d "$folder" ] && folder="/OEM"
- [ ! -d "$folder" ] && folder="$STORAGE/oem"
- [ ! -d "$folder" ] && folder="$STORAGE/OEM"
- [ ! -d "$folder" ] && folder=""
-
- [ -z "$folder" ] && [ -z "$COMMAND" ] && return 0
-
- local msg="Adding OEM files to image..."
- info "$msg" && html "$msg"
-
- mkdir -p "$dest" || return 1
-
- if [ -n "$folder" ]; then
- cp -Lr "$folder/." "$dest" || return 1
- fi
-
- file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
-
- if [ -n "$COMMAND" ]; then
-
- [ -z "$file" ] && file="$dest/install.bat"
-
- if [ -s "$file" ]; then
- printf '\n' >> "$file" || return 1
- fi
-
- printf '%s\n' "$COMMAND" >> "$file" || return 1
-
- fi
-
- if [ -f "$file" ]; then
- if ! unix2dos -q "$file"; then
- error "Failed to convert $file to DOS format!"
- return 1
- fi
- fi
-
- return 0
-}
-
-prepareInstall() {
-
- local pid=""
- local file=""
- local dir="$2"
- local desc="$3"
- local driver="$4"
- local drivers="/tmp/drivers"
-
- if [ -n "$DOMAIN" ]; then
- error "The DOMAIN variable is not supported for $desc!"
- return 1
- fi
-
- ETFS="[BOOT]/Boot-NoEmul.img"
-
- if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
- error "Failed to locate file \"$ETFS\" in $desc ISO image!" && return 1
- fi
-
- local arch target
- [ -d "$dir/AMD64" ] && arch="amd64" || arch="x86"
- [[ "${arch,,}" == "x86" ]] && target="$dir/I386" || target="$dir/AMD64"
-
- if [ ! -d "$target" ]; then
- error "Failed to locate directory \"$target\" in $desc ISO image!" && return 1
- fi
-
- if [[ "${driver,,}" == "xp" || "${driver,,}" == "2k3" ]]; then
-
- 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
- error "Failed to locate required storage drivers!" && return 1
- fi
-
- cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1
-
- mkdir -p "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
- cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
- cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
- cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1
-
- if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then
- error "Failed to locate required network drivers!" && return 1
- fi
-
- mkdir -p "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
- cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
- cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1
- cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || 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
-
- 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 '/^\[SCSI\]/s/$/\nviostor=\"Red Hat VirtIO SCSI Disk Device\"/' "$file" || return 1
- sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"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
-
- if [ ! -d "$drivers/sata/xp/$arch" ]; then
- error "Failed to locate required SATA drivers!" && return 1
- fi
-
- mkdir -p "$dir/\$OEM\$/\$1/Drivers/sata" || return 1
- cp -Lr "$drivers/sata/xp/$arch/." "$dir/\$OEM\$/\$1/Drivers/sata" || 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 '/^\[FileFlags\]/s/$/\niaStor.sys = 16/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.cat = 1,,,,,,,1,0,0/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.inf = 1,,,,,,,1,0,0/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,4_,4,1,,,1,4/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,,1,0,0/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaahci.cat = 1,,,,,,,1,0,0/' "$file" || return 1
- sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaAHCI.inf = 1,,,,,,,1,0,0/' "$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
-
- rm -rf "$drivers" || return 1
-
- fi
-
- local key setup
- setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1
-
- if [ -n "$setup" ] && [ -z "$KEY" ]; then
-
- pid=$(<"$setup") || return 1
- pid="${pid%$'\r'}"
-
- if [[ "$driver" == "2k" ]]; then
-
- echo "${pid:0:$((${#pid})) - 3}270" > "$setup" || return 1
-
- else
-
- if [[ "$pid" == *"270" ]]; then
-
- warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation."
-
- else
-
- file=$(find "$target" -maxdepth 1 -type f -iname PID.INF -print -quit) || return 1
-
- if [ -n "$file" ]; then
-
- if [[ "$driver" == "2k3" ]]; then
-
- key=$(grep -i -A 2 "StagingKey" "$file" | tail -n 2 | head -n 1) || key=""
-
- else
-
- key="${pid:$((${#pid})) - 8:5}"
- if [[ "${pid^^}" == *"OEM" ]]; then
- key=$(grep -i -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
- else
- key=$(grep -i -m 1 -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
- fi
- key="${key#*= }"
-
- fi
-
- key="${key%$'\r'}"
- [[ "${#key}" == "29" ]] && KEY="$key"
-
- fi
-
- if [ -z "$KEY" ]; then
-
- # These are NOT pirated keys, they come from official MS documentation.
-
- case "${driver,,}" in
- "xp" )
-
- if [[ "${arch,,}" == "x86" ]]; then
- # Windows XP Professional x86 generic trial key (no activation)
- KEY="DR8GV-C8V6J-BYXHG-7PYJR-DB66Y"
- else
- # Windows XP Professional x64 generic trial key (no activation)
- KEY="B2RBK-7KPT9-4JP6X-QQFWM-PJD6G"
- fi ;;
-
- "2k3" )
-
- if [[ "${arch,,}" == "x86" ]]; then
- # Windows Server 2003 Standard x86 generic trial key (no activation)
- KEY="QKDCQ-TP2JM-G4MDG-VR6F2-P9C48"
- else
- # Windows Server 2003 Standard x64 generic trial key (no activation)
- KEY="P4WJG-WK3W7-3HM8W-RWHCK-8JTRY"
- fi ;;
-
- esac
-
- echo "${pid:0:$((${#pid})) - 3}000" > "$setup" || return 1
-
- fi
-
- fi
- fi
-
- fi
-
- validateProductKey "$KEY" || return 1
-
- local product=""
- [ -n "$KEY" ] && product="ProductID=$KEY"
-
- mkdir -p "$dir/\$OEM\$" || return 1
-
- if ! addFolder "$dir"; then
- error "Failed to add OEM folder to image!" && return 1
- fi
-
- local oem=""
- local install="$dir/\$OEM\$/\$1/OEM/install.bat"
- [ -f "$install" ] && oem="\"Script\"=\"cmd /C start \\\"Install\\\" \\\"cmd /C C:\\\\OEM\\\\install.bat\\\"\""
-
- [ -z "$WIDTH" ] && WIDTH="1280"
- [ -z "$HEIGHT" ] && HEIGHT="720"
-
- validateResolution "WIDTH" "$WIDTH" 320 || return 1
- validateResolution "HEIGHT" "$HEIGHT" 200 || return 1
- validateMembership || return 1
- validateComputerName "$HOST" || return 1
- validateLegacyText "APP" "$APP" "$desc" || return 1
- validateLegacyText "ENGINE" "$ENGINE" "$desc" || return 1
-
- XHEX=$(printf '%08x\n' "$((10#$WIDTH))") || return 1
- YHEX=$(printf '%08x\n' "$((10#$HEIGHT))") || return 1
-
- local username="${USERNAME:-Docker}"
- local password="${PASSWORD:-admin}"
- local workgroup="${WORKGROUP:-WORKGROUP}"
-
- local sifHost sifUsername sifPassword sifOrganization sifWorkgroup
- local regUsername regPassword
-
- validateLegacyUsername "$username" "$desc" || return 1
- validatePassword "$password" "$desc" || return 1
-
- sifHost=$(escapeSIFValue "${HOST:-*}") || return 1
- sifUsername=$(escapeSIFValue "$username") || return 1
- sifPassword=$(escapeSIFValue "$password") || return 1
- sifOrganization=$(escapeSIFValue "$APP for $ENGINE") || return 1
- sifWorkgroup=$(escapeSIFValue "$workgroup") || return 1
- regUsername=$(escapeRegistryValue "$username") || return 1
- regPassword=$(escapeRegistryValue "$password") || return 1
-
- find "$target" -maxdepth 1 -type f -iname winnt.sif -delete || return 1
-
- { echo "[Data]"
- echo " AutoPartition=1"
- echo " MsDosInitiated=\"0\""
- echo " UnattendedInstall=\"Yes\""
- echo " AutomaticUpdates=\"Yes\""
- echo ""
- echo "[Unattended]"
- echo " UnattendSwitch=Yes"
- echo " UnattendMode=FullUnattended"
- echo " FileSystem=NTFS"
- echo " OemSkipEula=Yes"
- echo " OemPreinstall=Yes"
- echo " Repartition=Yes"
- echo " WaitForReboot=\"No\""
- echo " DriverSigningPolicy=\"Ignore\""
- echo " NonDriverSigningPolicy=\"Ignore\""
- printf '%s\n' " OemPnPDriversPath=\"Drivers\viostor;Drivers\NetKVM;Drivers\sata\""
- echo " NoWaitAfterTextMode=1"
- echo " NoWaitAfterGUIMode=1"
- echo " FileSystem=ConvertNTFS"
- echo " ExtendOemPartition=0"
- echo " Hibernation=\"No\""
- echo ""
- echo "[GuiUnattended]"
- echo " OEMSkipRegional=1"
- echo " OemSkipWelcome=1"
- echo " AdminPassword=\"$sifPassword\""
- echo " TimeZone=0"
- if disabled "$AUTOLOGIN"; then
- echo " AutoLogon=No"
- else
- echo " AutoLogon=Yes"
- echo " AutoLogonCount=65432"
- fi
- echo ""
- echo "[UserData]"
- echo " FullName=\"$sifUsername\""
- echo " ComputerName=\"$sifHost\""
- echo " OrgName=\"$sifOrganization\""
- echo " $product"
- echo ""
- echo "[Identification]"
- echo " JoinWorkgroup = \"$sifWorkgroup\""
- echo ""
- echo "[Display]"
- echo " BitsPerPel=32"
- echo " XResolution=$WIDTH"
- echo " YResolution=$HEIGHT"
- echo ""
- echo "[Networking]"
- echo " InstallDefaultComponents=Yes"
- echo ""
- echo "[Branding]"
- echo " BrandIEUsingUnattended=Yes"
- echo ""
- echo "[URL]"
- echo " Home_Page = http://www.google.com"
- echo " Search_Page = http://www.google.com"
- echo ""
- echo "[TerminalServices]"
- echo " AllowConnections=1"
- echo ""
- } | unix2dos > "$target/WINNT.SIF" || return 1
-
- if [[ "$driver" == "2k3" ]]; then
- { echo "[Components]"
- echo " TerminalServer=On"
- echo ""
- echo "[LicenseFilePrintData]"
- echo " AutoMode=PerServer"
- echo " AutoUsers=5"
- echo ""
- } | unix2dos >> "$target/WINNT.SIF" || return 1
- fi
-
- { echo "Windows Registry Editor Version 5.00"
- echo ""
- echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security]"
- echo "\"FirstRunDisabled\"=dword:00000001"
- echo "\"UpdatesDisableNotify\"=dword:00000001"
- echo "\"FirewallDisableNotify\"=dword:00000001"
- echo "\"AntiVirusDisableNotify\"=dword:00000001"
- echo ""
- echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wscsvc]"
- echo "\"Start\"=dword:00000004"
- echo ""
- echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\GloballyOpenPorts\List]"
- echo "\"3389:TCP\"=\"3389:TCP:*:Enabled:@xpsp2res.dll,-22009\""
- echo ""
- echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\Tour]"
- echo "\"RunCount\"=dword:00000000"
- echo ""
- echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]"
- echo "\"HideFileExt\"=dword:00000000"
- echo ""
- echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer]"
- echo "\"NoWelcomeScreen\"=\"1\""
- echo ""
- echo "[HKEY_CURRENT_USER\Software\Microsoft\Internet Connection Wizard]"
- echo "\"Completed\"=\"1\""
- echo "\"Desktopchanged\"=\"1\""
- echo ""
- echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]"
- if disabled "$AUTOLOGIN"; then
- echo "\"AutoAdminLogon\"=\"0\""
- else
- echo "\"AutoAdminLogon\"=\"1\""
- echo "\"DefaultUserName\"=\"$regUsername\""
- echo "\"DefaultPassword\"=\"$regPassword\""
- echo "\"DefaultDomainName\"=\"Dockur\""
- fi
- echo ""
- printf '%s\n' "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
- echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
- echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
- echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
- echo ""
- printf '%s\n' "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current\System\CurrentControlSet\Control\VIDEO\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]"
- echo "\"DefaultSettings.BitsPerPel\"=dword:00000020"
- echo "\"DefaultSettings.XResolution\"=dword:$XHEX"
- echo "\"DefaultSettings.YResolution\"=dword:$YHEX"
- echo ""
- echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]"
- printf '%s\n' "\"ScreenSaver\"=\"reg add \\\"HKCU\\\\Control Panel\\\\Desktop\\\" /f /v \\\"SCRNSAVE.EXE\\\" /t REG_SZ /d \\\"off\\\"\""
- printf '%s\n' "\"ScreenSaverOff\"=\"reg add \\\"HKCU\\\\Control Panel\\\\Desktop\\\" /f /v \\\"ScreenSaveActive\\\" /t REG_SZ /d \\\"0\\\"\""
- printf '%s\n' "\"SharedDrive\"=\"cmd /C net use Z: \\\\\\\\host.lan\\\\Data /persistent:yes\""
- echo "$oem"
- echo ""
- } | unix2dos > "$dir/\$OEM\$/install.reg" || return 1
-
- if [[ "$driver" == "2k" ]]; then
- { echo "[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]"
- echo "\"^SetupICWDesktop\"=-"
- echo ""
- } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1
- fi
-
- if [[ "$driver" == "2k3" ]]; then
- { echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\srvWiz]"
- echo "@=dword:00000000"
- echo ""
- echo "[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ServerOOBE\SecurityOOBE]"
- echo "\"DontLaunchSecurityOOBE\"=dword:00000000"
- echo ""
- } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1
- fi
-
- { echo "Set WshShell = WScript.CreateObject(\"WScript.Shell\")"
- echo "Set WshNetwork = WScript.CreateObject(\"WScript.Network\")"
- echo "Set Domain = GetObject(\"WinNT://\" & WshNetwork.ComputerName)"
- echo ""
- echo "Function DecodeSID(binSID)"
- echo " ReDim o(LenB(binSID))"
- echo ""
- echo " For i = 1 To LenB(binSID)"
- echo " o(i-1) = AscB(MidB(binSID, i, 1))"
- echo " Next"
- echo ""
- echo " sid = \"S-\" & CStr(o(0)) & \"-\" & OctetArrayToString _"
- echo " (Array(o(2), o(3), o(4), o(5), o(6), o(7)))"
- echo " For i = 8 To (4 * o(1) + 4) Step 4"
- echo " sid = sid & \"-\" & OctetArrayToString _"
- echo " (Array(o(i+3), o(i+2), o(i+1), o(i)))"
- echo " Next"
- echo ""
- echo " DecodeSID = sid"
- echo "End Function"
- echo ""
- echo "Function OctetArrayToString(arr)"
- echo " v = 0"
- echo " For i = 0 To UBound(arr)"
- echo " v = v * 256 + arr(i)"
- echo " Next"
- echo ""
- echo " OctetArrayToString = CStr(v)"
- echo "End Function"
- echo ""
- echo "For Each DomainItem in Domain"
- echo " If DomainItem.Class = \"User\" Then"
- echo " sid = DecodeSID(DomainItem.Get(\"objectSID\"))"
- echo " If Left(sid, 9) = \"S-1-5-21-\" And Right(sid, 4) = \"-500\" Then"
- echo " LocalAdminADsPath = DomainItem.ADsPath"
- echo " Exit For"
- echo " End If"
- echo " End If"
- echo "Next"
- echo ""
- echo "Call Domain.MoveHere(LocalAdminADsPath, \"$username\")"
- echo ""
- echo "Set oLink = WshShell.CreateShortcut(WshShell.SpecialFolders(\"Desktop\") & \"\\Shared.lnk\")"
- echo "With oLink"
- printf '%s\n' " .TargetPath = \"\\\\host.lan\\Data\""
- echo " .Save"
- echo "End With"
- echo "Set oLink = Nothing"
- echo ""
- } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1
-
- { echo "[COMMANDS]"
- echo "\"REGEDIT /s install.reg\""
- echo "\"Wscript install.vbs\""
- echo ""
- } | unix2dos > "$dir/\$OEM\$/cmdlines.txt" || return 1
-
- return 0
-}
-
-prepareLegacy() {
-
- local iso="$1"
- local dir="$2"
- local desc="$3"
-
- local tmp="$TMP/boot-images"
- local image="$tmp/eltorito_img1_bios.img"
-
- ETFS="boot.img"
-
- [ -s "$dir/$ETFS" ] && return 0
- rm -f "$dir/$ETFS" || return 1
- rm -rf "$tmp" || return 1
-
- if ! LC_ALL=C xorriso \
- -no_rc \
- -osirrox on \
- -indev "$iso" \
- -extract_boot_images "$tmp" >/dev/null 2>&1; then
- rm -rf "$tmp" || true
- error "Failed to extract boot image from $desc ISO!"
- return 1
- fi
-
- if [ ! -s "$image" ]; then
- rm -rf "$tmp" || true
- error "Failed to locate BIOS boot image in $desc ISO!"
- return 1
- fi
-
- if ! mv -f "$image" "$dir/$ETFS"; then
- rm -rf "$tmp" || true
- error "Failed to save boot image from $desc ISO!"
- return 1
- fi
-
- rm -rf "$tmp" || return 1
- return 0
-}
-
-detectLegacy() {
-
- local dir="$1"
- local find
-
- find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit)
- [ -n "$find" ] && DETECTED="win95" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit)
- [ -n "$find" ] && DETECTED="win98" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit)
- [ -n "$find" ] && DETECTED="win9x" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit)
- [ -n "$find" ] && DETECTED="winnt4" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit)
-
- if [ -n "$find" ]; then
-
- find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit)
- [ -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)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit)
- [ -n "$find" ] && DETECTED="win2k" && return 0
-
- fi
-
- find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit)
-
- if [ -n "$find" ]; then
-
- find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit)
- [ -n "$find" ] && DETECTED="winxpx64" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit)
- [ -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)
- [ -n "$find" ] && DETECTED="winxpx86" && return 0
-
- find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
- [ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
- [ -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
-
- fi
-
- return 1
-}
-
-skipVersion() {
-
- local id="$1"
-
- case "${id,,}" in
- "win9"* | "winxp"* | "win2k"* | "win2003"* )
- return 0 ;;
- esac
-
- return 1
-}
-
isCompatible() {
return 0
}
-setMachine() {
-
- local id="$1"
- local iso="$2"
- local dir="$3"
- local desc="$4"
-
- case "${id,,}" in
- "win9"* )
- ETFS="[BOOT]/Boot-1.44M.img" ;;
- "win2k"* )
- if ! prepareInstall "$iso" "$dir" "$desc" "2k"; then
- error "Failed to prepare $desc ISO!" && return 1
- fi ;;
- "winxp"* )
- if ! prepareInstall "$iso" "$dir" "$desc" "xp"; then
- error "Failed to prepare $desc ISO!" && return 1
- fi ;;
- "win2003"* )
- if ! prepareInstall "$iso" "$dir" "$desc" "2k3"; then
- error "Failed to prepare $desc ISO!" && return 1
- fi ;;
- esac
-
- case "${id,,}" in
- "win9"* )
- USB="no"
- VGA="cirrus"
- DISK_TYPE="auto"
- MACHINE="pc-i440fx-2.4"
- BOOT_MODE="windows_legacy"
- [ -z "${ADAPTER:-}" ] && ADAPTER="pcnet" ;;
- "win2k"* )
- VGA="cirrus"
- MACHINE="pc"
- USB="pci-ohci"
- DISK_TYPE="auto"
- BOOT_MODE="windows_legacy"
- [ -z "${ADAPTER:-}" ] && ADAPTER="rtl8139" ;;
- "winxp"* | "win2003"* )
- DISK_TYPE="blk"
- BOOT_MODE="windows_legacy"
- [ -z "${SOUND:-}" ] && SOUND="usb-audio" ;;
- "winvista"* | "win7"* | "win2008"* )
- BOOT_MODE="windows_legacy" ;;
- esac
-
- case "${id,,}" in
- "winxp"* | "win2003"* | "winvistax86"* | "win7x86"* | "win2008r2x86"* )
- # Prevent bluescreen if 64 bit PCI hole size is >2G.
- ARGS="-global q35-pcihost.x-pci-hole64-fix=false" ;;
- esac
-
- return 0
-}
-
return 0
diff --git a/src/entry.sh b/src/entry.sh
index a0d57f87..988c512b 100644
--- a/src/entry.sh
+++ b/src/entry.sh
@@ -14,6 +14,7 @@ cd /run
. server.sh # Start webserver
. define.sh # Define versions
. mido.sh # Download Windows
+. answer.sh # Setup answer files
. install.sh # Run installation
. disk.sh # Initialize disks
. display.sh # Initialize graphics
diff --git a/src/install.sh b/src/install.sh
index cd955298..d780508e 100644
--- a/src/install.sh
+++ b/src/install.sh
@@ -61,6 +61,57 @@ backup () {
return 0
}
+findFile() {
+
+ local dir file base
+ local fname="$1"
+ local boot="$STORAGE/windows.boot"
+
+ dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit)
+ [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit)
+
+ if [ -d "$dir" ]; then
+ if ! hasData || [ ! -f "$boot" ]; then
+ error "The bind $dir maps to a file that does not exist!" && return 1
+ fi
+ fi
+
+ file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit)
+ [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit)
+
+ if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then
+ base=$(basename "$VERSION")
+ file="$STORAGE/$base"
+ fi
+
+ if [ ! -f "$file" ] || [ ! -s "$file" ]; then
+ return 0
+ fi
+
+ local size
+ size="$(stat -c%s "$file")"
+ [ -z "$size" ] || [[ "$size" == "0" ]] && return 0
+
+ ISO="$file"
+ CUSTOM="$file"
+ BOOT="$STORAGE/windows.$size.iso"
+
+ return 0
+}
+
+detectCustom() {
+
+ CUSTOM=""
+
+ ! findFile "custom.iso" && return 1
+ [ -n "$CUSTOM" ] && return 0
+
+ ! findFile "boot.iso" && return 1
+ [ -n "$CUSTOM" ] && return 0
+
+ return 0
+}
+
skipInstall() {
local iso="$1"
@@ -193,24 +244,6 @@ startInstall() {
return 0
}
-checkFreeSpace() {
-
- local dir="$1"
- local size="$2"
- local size_gb space space_gb
-
- size_gb=$(formatBytes "$size")
- space=$(df --output=avail -B 1 "$dir" | tail -n 1)
- space_gb=$(formatBytes "$space")
-
- if (( size > space )); then
- error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb."
- return 1
- fi
-
- return 0
-}
-
finishInstall() {
local iso="$1"
@@ -330,54 +363,21 @@ abortInstall() {
return 1
}
-findFile() {
+checkFreeSpace() {
- local dir file base
- local fname="$1"
- local boot="$STORAGE/windows.boot"
+ local dir="$1"
+ local size="$2"
+ local size_gb space space_gb
- dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit)
- [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit)
+ size_gb=$(formatBytes "$size")
+ space=$(df --output=avail -B 1 "$dir" | tail -n 1)
+ space_gb=$(formatBytes "$space")
- if [ -d "$dir" ]; then
- if ! hasData || [ ! -f "$boot" ]; then
- error "The bind $dir maps to a file that does not exist!" && return 1
- fi
+ if (( size > space )); then
+ error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb."
+ return 1
fi
- file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit)
- [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit)
-
- if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then
- base=$(basename "$VERSION")
- file="$STORAGE/$base"
- fi
-
- if [ ! -f "$file" ] || [ ! -s "$file" ]; then
- return 0
- fi
-
- local size
- size="$(stat -c%s "$file")"
- [ -z "$size" ] || [[ "$size" == "0" ]] && return 0
-
- ISO="$file"
- CUSTOM="$file"
- BOOT="$STORAGE/windows.$size.iso"
-
- return 0
-}
-
-detectCustom() {
-
- CUSTOM=""
-
- ! findFile "custom.iso" && return 1
- [ -n "$CUSTOM" ] && return 0
-
- ! findFile "boot.iso" && return 1
- [ -n "$CUSTOM" ] && return 0
-
return 0
}
@@ -767,28 +767,16 @@ detectLanguage() {
return 0
}
-setXML() {
+skipVersion() {
- local file="/custom.xml"
+ local id="$1"
- CUSTOM_XML=""
-
- if [ -d "$file" ]; then
- error "The bind $file maps to a file that does not exist!" && exit 67
- fi
-
- [ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml"
- [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml"
- [ ! -f "$file" ] || [ ! -s "$file" ] && file="$1"
- [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/$DETECTED.xml"
- [ ! -f "$file" ] || [ ! -s "$file" ] && return 1
-
- case "$file" in
- "/custom.xml" | "$STORAGE/custom.xml" ) CUSTOM_XML="Y" ;;
+ case "${id,,}" in
+ "win9"* | "winxp"* | "win2k"* | "win2003"* )
+ return 0 ;;
esac
- XML="$file"
- return 0
+ return 1
}
detectImage() {
@@ -885,6 +873,62 @@ detectImage() {
return 0
}
+setMachine() {
+
+ local id="$1"
+ local iso="$2"
+ local dir="$3"
+ local desc="$4"
+
+ case "${id,,}" in
+ "win9"* )
+ ETFS="[BOOT]/Boot-1.44M.img" ;;
+ "win2k"* )
+ if ! legacyInstall "$iso" "$dir" "$desc" "2k"; then
+ error "Failed to prepare $desc ISO!" && return 1
+ fi ;;
+ "winxp"* )
+ if ! legacyInstall "$iso" "$dir" "$desc" "xp"; then
+ error "Failed to prepare $desc ISO!" && return 1
+ fi ;;
+ "win2003"* )
+ if ! legacyInstall "$iso" "$dir" "$desc" "2k3"; then
+ error "Failed to prepare $desc ISO!" && return 1
+ fi ;;
+ esac
+
+ case "${id,,}" in
+ "win9"* )
+ USB="no"
+ VGA="cirrus"
+ DISK_TYPE="auto"
+ MACHINE="pc-i440fx-2.4"
+ BOOT_MODE="windows_legacy"
+ [ -z "${ADAPTER:-}" ] && ADAPTER="pcnet" ;;
+ "win2k"* )
+ VGA="cirrus"
+ MACHINE="pc"
+ USB="pci-ohci"
+ DISK_TYPE="auto"
+ BOOT_MODE="windows_legacy"
+ [ -z "${ADAPTER:-}" ] && ADAPTER="rtl8139" ;;
+ "winxp"* | "win2003"* )
+ DISK_TYPE="blk"
+ BOOT_MODE="windows_legacy"
+ [ -z "${SOUND:-}" ] && SOUND="usb-audio" ;;
+ "winvista"* | "win7"* | "win2008"* )
+ BOOT_MODE="windows_legacy" ;;
+ esac
+
+ case "${id,,}" in
+ "winxp"* | "win2003"* | "winvistax86"* | "win7x86"* | "win2008r2x86"* )
+ # Prevent bluescreen if 64 bit PCI hole size is >2G.
+ ARGS="-global q35-pcihost.x-pci-hole64-fix=false" ;;
+ esac
+
+ return 0
+}
+
prepareImage() {
local iso="$1"
@@ -907,465 +951,53 @@ prepareImage() {
return 1
fi
- prepareLegacy "$iso" "$dir" "$desc" && return 0
+ legacyPrepare "$iso" "$dir" "$desc" && return 0
error "Failed to extract boot image from ISO image!"
return 1
}
-escapeXMLSed() {
+addFolder() {
- local s
+ local src="$1"
+ local folder="/oem" file=""
+ local dest="$src/\$OEM\$/\$1/OEM"
- s=$(escapeXML "$1") || return 1
- s=${s//\\/\\\\}
- s=${s//&/\\&}
- s=${s//|/\\|}
+ [ ! -d "$folder" ] && folder="/OEM"
+ [ ! -d "$folder" ] && folder="$STORAGE/oem"
+ [ ! -d "$folder" ] && folder="$STORAGE/OEM"
+ [ ! -d "$folder" ] && folder=""
- printf '%s' "$s"
- return 0
-}
+ [ -z "$folder" ] && [ -z "$COMMAND" ] && return 0
-validateUsername() {
+ local msg="Adding OEM files to image..."
+ info "$msg" && html "$msg"
- local value="$1"
- local type="$2"
- local maximum
+ mkdir -p "$dest" || return 1
- case "$type" in
- "local" )
- maximum=20
- [ -z "$value" ] && return 0
- ;;
- "domain" )
- maximum=256
+ if [ -n "$folder" ]; then
+ cp -Lr "$folder/." "$dest" || return 1
+ fi
- if [ -z "$value" ]; then
- error "The USERNAME variable does not contain a valid domain account name!"
- return 1
- fi ;;
- * )
- return 1 ;;
- esac
+ file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
- if [ "${#value}" -gt "$maximum" ]; then
- if [[ "$type" == "domain" ]]; then
- 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!"
+ if [ -n "$COMMAND" ]; then
+
+ [ -z "$file" ] && file="$dest/install.bat"
+
+ if [ -s "$file" ]; then
+ printf '\n' >> "$file" || return 1
fi
- return 1
+
+ printf '%s\n' "$COMMAND" >> "$file" || return 1
+
fi
- if [[ "$value" =~ [[:cntrl:]] ]]; then
- error "The USERNAME variable cannot contain control characters!"
- return 1
- fi
-
- case "$value" in
- *'"'* | *'/'* | *\\* | *'['* | *']'* | *':'* | *';'* | *'|'* | *'='* | *','* | *'+'* | *'*'* | *'?'* | *'<'* | *'>'* | *'%'* | *'@'* )
- if [[ "$type" == "domain" ]]; then
- error "The domain account name contains characters that are not supported by Windows unattended setup!"
- else
- error "The USERNAME variable contains characters that are not supported by Windows local accounts!"
- fi
- return 1 ;;
- esac
-
- if [[ "$value" == *"." ]]; then
- error "The USERNAME variable cannot end with a period!"
- return 1
- fi
-
- if [[ "$value" =~ ^[.[:space:]]+$ ]]; then
- error "The USERNAME variable cannot consist only of spaces or periods!"
- return 1
- fi
-
- case "${value^^}" in
- "NONE" )
- error "The USERNAME value \"NONE\" is reserved by Windows!"
- return 1 ;;
- "ADMINISTRATOR" | "GUEST" | "DEFAULTACCOUNT" | "WDAGUTILITYACCOUNT" | "WSIACCOUNT" )
- if [[ "$type" == "local" ]]; then
- error "The USERNAME value \"$value\" is reserved for a built-in Windows account!"
- return 1
- fi ;;
- esac
-
- return 0
-}
-
-updateDomain() {
-
- local asset="$1"
- local domain account auth pass pw
- local cred_domain ou arch tmp result
-
- domain=$(escapeXML "$2") || return 1
- account=$(escapeXML "$3") || return 1
- auth=$(escapeXML "$4") || return 1
- pass=$(escapeXML "$5") || return 1
- pw="$6"
- ou=$(escapeXML "$7") || return 1
-
- arch=$(sed -n -E \
- '0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' \
- "$asset") || return 1
-
- [ -z "$arch" ] && return 1
-
- cred_domain="$domain"
-
- case "$4" in
- *@* ) cred_domain="" ;;
- esac
-
- grep -Eq 'Microsoft-Windows-UnattendedJoin|])' "$asset" && return 1
-
- tmp=$(mktemp -d) || return 1
- result="$tmp/answer.xml"
-
- if ! DOMAIN_XML="$domain" ACCOUNT_XML="$account" \
- AUTH_XML="$auth" PASS_XML="$pass" \
- CRED_DOMAIN="$cred_domain" PW="$pw" OU_XML="$ou" \
- ARCH_XML="$arch" \
- awk '
- /]*pass="specialize"[^>]*>/ { section = "specialize" }
- /]*pass="oobeSystem"[^>]*>/ { section = "oobeSystem" }
- section == "oobeSystem" && /])/ { in_accounts = 1 }
- section == "oobeSystem" && /])/ { in_autologon = 1 }
-
- section == "oobeSystem" && in_accounts && !accounts_added &&
- /])/ {
- print " \n" \
- " \n" \
- " \n" \
- " " ENVIRON["ACCOUNT_XML"] "\n" \
- " Administrators\n" \
- " \n" \
- " " ENVIRON["DOMAIN_XML"] "\n" \
- " \n" \
- " "
- accounts_added = 1
- }
-
- section == "oobeSystem" && in_autologon &&
- /^[[:space:]]*.*<\/Username>[[:space:]]*$/ {
- print " " ENVIRON["ACCOUNT_XML"] "\n" \
- " " ENVIRON["DOMAIN_XML"] ""
- autologon_added = 1
- next
- }
-
- section == "oobeSystem" && in_autologon &&
- /^[[:space:]]*])/ { next }
-
- section == "oobeSystem" && in_autologon &&
- /^[[:space:]]*.*<\/Value>[[:space:]]*$/ {
- print " " ENVIRON["PW"] ""
- password_added = 1
- next
- }
-
- section == "specialize" && !join_added &&
- /^[[:space:]]*<\/settings>[[:space:]]*$/ {
- print " \n" \
- " \n" \
- " "
-
- if (ENVIRON["CRED_DOMAIN"] != "") {
- print " " ENVIRON["CRED_DOMAIN"] ""
- }
-
- print " " ENVIRON["AUTH_XML"] "\n" \
- " " ENVIRON["PASS_XML"] "\n" \
- " \n" \
- " " ENVIRON["DOMAIN_XML"] ""
-
- if (ENVIRON["OU_XML"] != "") {
- print " " ENVIRON["OU_XML"] ""
- }
-
- print " \n" \
- " "
-
- join_added = 1
- }
-
- { print }
-
- section == "oobeSystem" && /<\/AutoLogon>/ { in_autologon = 0 }
- section == "oobeSystem" && /<\/UserAccounts>/ { in_accounts = 0 }
- /^[[:space:]]*<\/settings>[[:space:]]*$/ { section = "" }
-
- END { exit !(join_added && accounts_added && autologon_added && password_added) }
- ' "$asset" > "$result" ||
- ! mv -f "$result" "$asset"; then
-
- rm -rf "$tmp" || true
- return 1
- fi
-
- rm -rf "$tmp" || return 1
- return 0
-}
-
-validateDomainName() {
-
- local value="$1"
- local name="${2:-DOMAIN}"
-
- if [ -z "$value" ]; then
- error "The $name variable must contain a valid domain name!"
- return 1
- fi
-
- if [[ "$value" == *"://"* ]]; then
- error "The $name variable must contain a domain name, not a URL!"
- return 1
- fi
-
- if [ "${#value}" -gt 255 ] ||
- [[ "$value" =~ [[:cntrl:]] ]] ||
- [[ "$value" =~ [[:space:]] ]] ||
- [[ ! "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$ ]]; then
-
- error "The $name variable does not contain a valid domain name!"
- return 1
- fi
-
- return 0
-}
-
-updateWorkgroup() {
-
- local asset="$1"
- local workgroup arch tmp result
-
- workgroup=$(escapeXML "$2") || return 1
- arch=$(sed -n -E '0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' "$asset") || return 1
- [ -z "$arch" ] && return 1
-
- grep -q 'Microsoft-Windows-UnattendedJoin' "$asset" && return 1
-
- tmp=$(mktemp -d) || return 1
- result="$tmp/answer.xml"
-
- if ! WORKGROUP_XML="$workgroup" ARCH_XML="$arch" awk '
- /]*pass="specialize"[^>]*>/ { section = "specialize" }
-
- section == "specialize" && !workgroup_added &&
- /^[[:space:]]*<\/settings>[[:space:]]*$/ {
- print " \n" \
- " \n" \
- " " ENVIRON["WORKGROUP_XML"] "\n" \
- " \n" \
- " "
- workgroup_added = 1
- }
-
- { print }
-
- /^[[:space:]]*<\/settings>[[:space:]]*$/ { section = "" }
- END { exit !workgroup_added }
- ' "$asset" > "$result" ||
- ! mv -f "$result" "$asset"; then
-
- rm -rf "$tmp" || true
- return 1
- fi
-
- rm -rf "$tmp" || return 1
- return 0
-}
-
-updateXML() {
-
- local asset="$1"
- local language="$2"
- local app value culture region keyboard edition
- local user user_xml auth_user admin pass pw
- local domain qualifier host workgroup key
-
- [ -z "$WIDTH" ] && WIDTH="1280"
- [ -z "$HEIGHT" ] && HEIGHT="720"
-
- validateResolution "WIDTH" "$WIDTH" 320 || return 1
- validateResolution "HEIGHT" "$HEIGHT" 200 || return 1
- validateMembership || return 1
- validateComputerName "$HOST" || return 1
- validateProductKey "$KEY" || return 1
- validatePassword "$PASSWORD" || return 1
-
- app=$(escapeXMLSed "$APP for $ENGINE") || return 1
-
- sed -i "s|>Windows for Docker<|>$app<|g" "$asset" || return 1
- sed -i -E "s|[^<]*|$HEIGHT|g" "$asset" || return 1
- sed -i -E "s|[^<]*|$WIDTH|g" "$asset" || return 1
-
- culture=$(getLanguage "$language" "culture") || return 1
-
- if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then
- value=$(escapeXMLSed "$culture") || return 1
- sed -i "s|en-US|$value|g" "$asset" || return 1
- fi
-
- region="$REGION"
- [ -z "$region" ] && region="$culture"
-
- if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then
- value=$(escapeXMLSed "$region") || return 1
- sed -i "s|en-US|$value|g" "$asset" || return 1
- sed -i "s|en-US|$value|g" "$asset" || return 1
- fi
-
- keyboard="$KEYBOARD"
- [ -z "$keyboard" ] && keyboard="$culture"
-
- if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then
- value=$(escapeXMLSed "$keyboard") || return 1
- sed -i "s|en-US|$value|g" "$asset" || return 1
- sed -i "s|0409:00000409|$value|g" "$asset" || return 1
- fi
-
- if [ -n "$HOST" ]; then
- host=$(escapeXMLSed "$HOST") || return 1
- sed -i -E "s|[^<]*|$host|g" "$asset" || return 1
- fi
-
- domain="$DOMAIN"
- workgroup="$WORKGROUP"
-
- if [ -n "$domain" ]; then
-
- if [ -z "$USERNAME" ]; then
- error "The USERNAME variable must be specified when joining a domain!"
+ if [ -f "$file" ]; then
+ if ! unix2dos -q "$file"; then
+ error "Failed to convert $file to DOS format!"
return 1
fi
-
- if [ -z "$PASSWORD" ]; then
- error "The PASSWORD variable must be specified when joining a domain!"
- return 1
- fi
-
- validateDomainName "$domain" || return 1
-
- auth_user="$USERNAME"
- qualifier=""
-
- if [[ "$auth_user" == *\\* ]]; then
- error "The USERNAME variable must use either \"user\" or \"user@domain\" format!"
- return 1
- fi
-
- case "$auth_user" in
- *@* )
- user="${auth_user%%@*}"
- qualifier="${auth_user#*@}"
-
- if [ -z "$user" ] || [ -z "$qualifier" ] || [[ "$qualifier" == *@* ]]; then
- error "The USERNAME variable does not contain a valid domain account name!"
- return 1
- fi
-
- validateDomainName "$qualifier" "USERNAME" || return 1
-
- if [[ "${qualifier,,}" != "${domain,,}" ]]; then
- error "The domain in the USERNAME variable must match the DOMAIN variable!"
- return 1
- fi
- ;;
- * )
- user="$auth_user"
- ;;
- esac
-
- validateUsername "$user" "domain" || return 1
-
- if [[ "${user,,}" == "docker" ]]; then
- error "The USERNAME variable must be changed from its default value when joining a domain!"
- return 1
- fi
-
- if [[ "$PASSWORD" == "admin" ]]; then
- error "The PASSWORD variable must be changed from its default value when joining a domain!"
- return 1
- fi
-
- else
-
- user="$USERNAME"
- validateUsername "$user" "local" || return 1
-
- if [ -n "$user" ]; then
- user_xml=$(escapeXMLSed "$user") || return 1
-
- sed -i "s|-name \"Docker\"|-name \"\$env:USERNAME\"|g" "$asset" || return 1
- sed -i 's|where name="Docker"|where name="%USERNAME%"|g' "$asset" || return 1
- sed -i "s|Docker|$user_xml|g" "$asset" || return 1
- sed -i "s|Docker|$user_xml|g" "$asset" || return 1
- sed -i "s|Docker|$user_xml|g" "$asset" || return 1
- fi
-
- fi
-
- if [ -n "$domain" ]; then
- pass="admin"
- else
- [ -n "$PASSWORD" ] && pass="$PASSWORD" || pass="admin"
- fi
-
- pw=$(printf '%s' "${pass}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
- admin=$(printf '%s' "${pass}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
-
- sed -i -z -E "s#([[:space:]]*|>[^<]*)#\1>$pw#g" "$asset" || return 1
- sed -i -z -E "s#([[:space:]]*|>[^<]*)#\1>$admin#g" "$asset" || return 1
- sed -i -E "s|[^<]*|false|g" "$asset" || return 1
-
- if [ -n "$domain" ]; then
-
- pw=$(printf '%s' "${PASSWORD}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) || return 1
-
- if ! updateDomain "$asset" "$domain" "$user" \
- "$auth_user" "$PASSWORD" "$pw" "$DOMAIN_OU"; then
- error "Failed to add domain configuration to answer file!"
- return 1
- fi
-
- elif [ -n "$workgroup" ]; then
-
- if ! updateWorkgroup "$asset" "$workgroup"; then
- error "Failed to add workgroup configuration to answer file!"
- return 1
- fi
-
- fi
-
- if disabled "$AUTOLOGIN"; then
- sed -i -E '/^[[:space:]]*])/,/^[[:space:]]*<\/AutoLogon>[[:space:]]*$/d' "$asset" || return 1
- fi
-
- if [ -n "$EDITION" ]; then
- case "${EDITION,,}" in
- "core" ) edition="STANDARDCORE" ;;
- * ) edition="${EDITION^^}" ;;
- esac
-
- edition=$(escapeXMLSed "$edition") || return 1
- sed -i "s|SERVERSTANDARD|SERVER$edition|g" "$asset" || return 1
- fi
-
- if [ -n "$KEY" ]; then
- key=$(escapeXMLSed "$KEY") || return 1
- sed -i -E '/^[[:space:]]*[[:space:]]*$/,/^[[:space:]]*<\/ProductKey>[[:space:]]*$/d' "$asset" || return 1
- sed -i -E "s|[^<]*|$key|g" "$asset" || return 1
- sed -i "s|| \n $key\n OnError\n \n |g" "$asset" || return 1
- fi
-
- if ! xmllint --nonet --noout "$asset"; then
- error "The generated answer file is not valid XML!"
- return 1
fi
return 0