mirror of
https://github.com/dockur/windows.git
synced 2026-08-03 12:37:20 +01:00
Update answer.sh
This commit is contained in:
+452
-287
@@ -120,65 +120,90 @@ generateAnswerFile() {
|
||||
local index="$4"
|
||||
local type="$5"
|
||||
local remove_selector="$6"
|
||||
local tmp
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||
local directory install_count install_to_count tmp
|
||||
|
||||
if [ -n "$index" ] && [[ ! "$index" =~ ^[1-9][0-9]*$ ]]; then
|
||||
error "Invalid $type image index: $index"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! tmp=$(mktemp -p /run/assets ".${id}.XXXXXX"); then
|
||||
directory=$(dirname "$target") || return 1
|
||||
|
||||
if ! tmp=$(mktemp -p "$directory" ".${id}.XXXXXX"); then
|
||||
error "Failed to create a temporary $type answer file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local expressions
|
||||
|
||||
if [ "$type" = "evaluation" ]; then
|
||||
expressions=(
|
||||
-e '/<ProductKey>.*<\/ProductKey>/d'
|
||||
-e '/<ProductKey>/,/<\/ProductKey>/d'
|
||||
)
|
||||
else
|
||||
expressions=(
|
||||
-e '/<InstallFrom>.*<\/InstallFrom>/d'
|
||||
-e '/<ProductKey>.*<\/ProductKey>/d'
|
||||
-e '/<InstallFrom>/,/<\/InstallFrom>/d'
|
||||
-e '/<ProductKey>/,/<\/ProductKey>/d'
|
||||
)
|
||||
fi
|
||||
|
||||
if ! sed "${expressions[@]}" "$source" > "$tmp"; then
|
||||
if ! cp -L -- "$source" "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to generate $type answer file from $source!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$type" = "evaluation" ] && [ "$remove_selector" = "Y" ]; then
|
||||
if ! sed -i \
|
||||
-e '/<InstallFrom>.*<\/InstallFrom>/d' \
|
||||
-e '/<InstallFrom>/,/<\/InstallFrom>/d' \
|
||||
"$tmp"; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to replace evaluation image selector!"
|
||||
return 1
|
||||
fi
|
||||
local -a args=(
|
||||
-L
|
||||
-N "u=$ns"
|
||||
-d '//u:ProductKey'
|
||||
)
|
||||
|
||||
if [ "$type" != "evaluation" ] || [ "$remove_selector" = "Y" ]; then
|
||||
args+=( -d '//u:InstallFrom' )
|
||||
fi
|
||||
|
||||
if [ -n "$index" ] && ! grep -q '<InstallFrom>' "$tmp"; then
|
||||
if ! sed -i \
|
||||
'0,/<InstallTo>/{ /<InstallTo>/i\
|
||||
<InstallFrom>\
|
||||
<MetaData wcm:action="add">\
|
||||
<Key>/IMAGE/INDEX</Key>\
|
||||
<Value>'"$index"'</Value>\
|
||||
</MetaData>\
|
||||
</InstallFrom>
|
||||
}' "$tmp"; then
|
||||
if ! xmlstarlet ed "${args[@]}" "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to generate $type answer file from $source!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -n "$index" ]; then
|
||||
install_count=$(xmlstarlet sel \
|
||||
-N "u=$ns" \
|
||||
-T -t \
|
||||
-v 'count(//u:ImageInstall/u:OSImage/u:InstallFrom)' \
|
||||
"$tmp") || {
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "$install_count" = "0" ]; then
|
||||
install_to_count=$(xmlstarlet sel \
|
||||
-N "u=$ns" \
|
||||
-T -t \
|
||||
-v 'count(//u:ImageInstall/u:OSImage/u:InstallTo)' \
|
||||
"$tmp") || {
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "$install_to_count" = "0" ]; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to select $type image index $index!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! xmlstarlet ed -L \
|
||||
-N "u=$ns" \
|
||||
-N "wcm=$wcm" \
|
||||
-i '(//u:ImageInstall/u:OSImage/u:InstallTo)[1]' \
|
||||
-t elem -n 'InstallFrom' \
|
||||
-s '//u:ImageInstall/u:OSImage/*[local-name()="InstallFrom"]' \
|
||||
-t elem -n 'MetaData' \
|
||||
-i '//u:ImageInstall/u:OSImage/*[local-name()="InstallFrom"]/*[local-name()="MetaData"]' \
|
||||
-t attr -n 'wcm:action' -v 'add' \
|
||||
-s '//u:ImageInstall/u:OSImage/*[local-name()="InstallFrom"]/*[local-name()="MetaData"]' \
|
||||
-t elem -n 'Key' -v '/IMAGE/INDEX' \
|
||||
-s '//u:ImageInstall/u:OSImage/*[local-name()="InstallFrom"]/*[local-name()="MetaData"]' \
|
||||
-t elem -n 'Value' -v "$index" \
|
||||
"$tmp"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
error "Failed to select $type image index $index!"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! markGeneratedXML "$tmp"; then
|
||||
@@ -339,8 +364,10 @@ updateXML() {
|
||||
updateLocalAccount "$asset" || return 1
|
||||
fi
|
||||
|
||||
sed -i -E \
|
||||
"s|<PlainText>[^<]*</PlainText>|<PlainText>false</PlainText>|g" \
|
||||
xmlstarlet ed -L \
|
||||
-N 'u=urn:schemas-microsoft-com:unattend' \
|
||||
-u '//u:PlainText' \
|
||||
-v 'false' \
|
||||
"$asset" || return 1
|
||||
|
||||
updateMembership \
|
||||
@@ -947,157 +974,233 @@ validateDomainName() {
|
||||
updateWorkgroup() {
|
||||
|
||||
local asset="$1"
|
||||
local workgroup arch tmp
|
||||
local workgroup="$2"
|
||||
local workgroup_xml
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local specialize='/u:unattend/u:settings[@pass="specialize"]'
|
||||
local component="$specialize/*[local-name()=\"component\" and @name=\"__QEMUS_UNATTENDED_JOIN__\"]"
|
||||
local arch counts settings_count join_count unnamed_count tmp
|
||||
|
||||
workgroup=$(escapeXML "$2") || return 1
|
||||
workgroup_xml=$(escapeXML "$workgroup") || return 1
|
||||
arch=$(getXMLArchitecture "$asset") || return 1
|
||||
|
||||
grep -q 'Microsoft-Windows-UnattendedJoin' "$asset" && return 1
|
||||
counts=$(xmlstarlet sel \
|
||||
-N "u=$ns" \
|
||||
-T -t \
|
||||
-v "count($specialize)" -o '|' \
|
||||
-v 'count(//u:component[@name="Microsoft-Windows-UnattendedJoin"])' -o '|' \
|
||||
-v "count($specialize/u:component[not(@name)])" \
|
||||
"$asset") || return 1
|
||||
|
||||
tmp=$(mktemp -d) || return 1
|
||||
local result="$tmp/answer.xml"
|
||||
IFS='|' read -r settings_count join_count unnamed_count <<< "$counts"
|
||||
|
||||
if ! WORKGROUP_XML="$workgroup" ARCH_XML="$arch" awk '
|
||||
/<settings[^>]*pass="specialize"[^>]*>/ { section = "specialize" }
|
||||
[ "$settings_count" = "1" ] || return 1
|
||||
[ "$join_count" = "0" ] || return 1
|
||||
[ "$unnamed_count" = "0" ] || return 1
|
||||
|
||||
section == "specialize" && !workgroup_added &&
|
||||
/^[[:space:]]*<\/settings>[[:space:]]*$/ {
|
||||
print " <component name=\"Microsoft-Windows-UnattendedJoin\" processorArchitecture=\"" ENVIRON["ARCH_XML"] "\" publicKeyToken=\"31bf3856ad364e35\" language=\"neutral\" versionScope=\"nonSxS\">\n" \
|
||||
" <Identification>\n" \
|
||||
" <JoinWorkgroup>" ENVIRON["WORKGROUP_XML"] "</JoinWorkgroup>\n" \
|
||||
" </Identification>\n" \
|
||||
" </component>"
|
||||
workgroup_added = 1
|
||||
}
|
||||
|
||||
{ print }
|
||||
|
||||
/^[[:space:]]*<\/settings>[[:space:]]*$/ { section = "" }
|
||||
END { exit !workgroup_added }
|
||||
' "$asset" > "$result" ||
|
||||
! mv -f "$result" "$asset"; then
|
||||
|
||||
rm -rf "$tmp" || true
|
||||
if ! tmp=$(mktemp "${asset}.XXXXXX"); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! xmlstarlet ed \
|
||||
-N "u=$ns" \
|
||||
-s "$specialize" \
|
||||
-t elem -n 'component' \
|
||||
-i "$specialize/*[local-name()='component' and not(@name)][last()]" \
|
||||
-t attr -n 'name' -v '__QEMUS_UNATTENDED_JOIN__' \
|
||||
-i "$component" \
|
||||
-t attr -n 'processorArchitecture' -v "$arch" \
|
||||
-i "$component" \
|
||||
-t attr -n 'publicKeyToken' -v '31bf3856ad364e35' \
|
||||
-i "$component" \
|
||||
-t attr -n 'language' -v 'neutral' \
|
||||
-i "$component" \
|
||||
-t attr -n 'versionScope' -v 'nonSxS' \
|
||||
-s "$component" \
|
||||
-t elem -n 'Identification' \
|
||||
-s "$component/*[local-name()='Identification']" \
|
||||
-t elem -n 'JoinWorkgroup' -v "$workgroup_xml" \
|
||||
-u "$component/@name" \
|
||||
-v 'Microsoft-Windows-UnattendedJoin' \
|
||||
"$asset" > "$tmp"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! chmod --reference="$asset" "$tmp" ||
|
||||
! mv -f "$tmp" "$asset"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
updateDomain() {
|
||||
|
||||
local asset="$1"
|
||||
local domain account auth pass
|
||||
local ou arch tmp
|
||||
local domain="$2"
|
||||
local account="$3"
|
||||
local auth="$4"
|
||||
local pass="$5"
|
||||
local ou="$6"
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||
local specialize='/u:unattend/u:settings[@pass="specialize"]'
|
||||
local shell='/u:unattend/u:settings[@pass="oobeSystem"]/u:component[@name="Microsoft-Windows-Shell-Setup"]'
|
||||
local accounts="$shell/u:UserAccounts"
|
||||
local autologon="$shell/u:AutoLogon"
|
||||
local domain_accounts="$accounts/*[local-name()=\"DomainAccounts\"]"
|
||||
local account_list="$domain_accounts/*[local-name()=\"DomainAccountList\"]"
|
||||
local domain_account="$account_list/*[local-name()=\"DomainAccount\"]"
|
||||
local component="$specialize/*[local-name()=\"component\" and @name=\"__QEMUS_UNATTENDED_JOIN__\"]"
|
||||
local identification="$component/*[local-name()=\"Identification\"]"
|
||||
local credentials="$identification/*[local-name()=\"Credentials\"]"
|
||||
local cred_domain="$domain"
|
||||
local domain_xml account_xml auth_xml pass_xml ou_xml cred_domain_xml
|
||||
local arch counts tmp
|
||||
local settings_count join_count unnamed_count shell_count
|
||||
local accounts_count domain_count admin_count autologon_count
|
||||
local username_count value_count plaintext_count
|
||||
|
||||
domain=$(escapeXML "$2") || return 1
|
||||
account=$(escapeXML "$3") || return 1
|
||||
auth=$(escapeXML "$4") || return 1
|
||||
pass=$(escapeXML "$5") || return 1
|
||||
ou=$(escapeXML "$6") || return 1
|
||||
domain_xml=$(escapeXML "$domain") || return 1
|
||||
account_xml=$(escapeXML "$account") || return 1
|
||||
auth_xml=$(escapeXML "$auth") || return 1
|
||||
pass_xml=$(escapeXML "$pass") || return 1
|
||||
ou_xml=$(escapeXML "$ou") || return 1
|
||||
arch=$(getXMLArchitecture "$asset") || return 1
|
||||
|
||||
local cred_domain="$domain"
|
||||
|
||||
case "$4" in
|
||||
case "$auth" in
|
||||
*@* ) cred_domain="" ;;
|
||||
esac
|
||||
|
||||
grep -Eq 'Microsoft-Windows-UnattendedJoin|<DomainAccounts([[:space:]/>])' "$asset" && return 1
|
||||
cred_domain_xml=$(escapeXML "$cred_domain") || return 1
|
||||
|
||||
tmp=$(mktemp -d) || return 1
|
||||
local result="$tmp/answer.xml"
|
||||
counts=$(xmlstarlet sel \
|
||||
-N "u=$ns" \
|
||||
-T -t \
|
||||
-v "count($specialize)" -o '|' \
|
||||
-v 'count(//u:component[@name="Microsoft-Windows-UnattendedJoin"])' -o '|' \
|
||||
-v "count($specialize/u:component[not(@name)])" -o '|' \
|
||||
-v "count($shell)" -o '|' \
|
||||
-v "count($accounts)" -o '|' \
|
||||
-v "count($accounts/u:DomainAccounts)" -o '|' \
|
||||
-v "count($accounts/u:AdministratorPassword)" -o '|' \
|
||||
-v "count($autologon)" -o '|' \
|
||||
-v "count($autologon/u:Username)" -o '|' \
|
||||
-v "count($autologon/u:Password/u:Value)" -o '|' \
|
||||
-v "count($autologon/u:Password/u:PlainText)" \
|
||||
"$asset") || return 1
|
||||
|
||||
if ! DOMAIN_XML="$domain" ACCOUNT_XML="$account" \
|
||||
AUTH_XML="$auth" PASS_XML="$pass" \
|
||||
CRED_DOMAIN="$cred_domain" OU_XML="$ou" \
|
||||
ARCH_XML="$arch" \
|
||||
awk '
|
||||
/<settings[^>]*pass="specialize"[^>]*>/ { section = "specialize" }
|
||||
/<settings[^>]*pass="oobeSystem"[^>]*>/ { section = "oobeSystem" }
|
||||
section == "oobeSystem" && /<UserAccounts([[:space:]>])/ { in_accounts = 1 }
|
||||
section == "oobeSystem" && /<AutoLogon([[:space:]>])/ { in_autologon = 1 }
|
||||
IFS='|' read -r \
|
||||
settings_count join_count unnamed_count shell_count \
|
||||
accounts_count domain_count admin_count autologon_count \
|
||||
username_count value_count plaintext_count <<< "$counts"
|
||||
|
||||
section == "oobeSystem" && in_accounts && !accounts_added &&
|
||||
/<AdministratorPassword([[:space:]>])/ {
|
||||
print " <DomainAccounts>\n" \
|
||||
" <DomainAccountList wcm:action=\"add\">\n" \
|
||||
" <DomainAccount wcm:action=\"add\">\n" \
|
||||
" <Name>" ENVIRON["ACCOUNT_XML"] "</Name>\n" \
|
||||
" <Group>Administrators</Group>\n" \
|
||||
" </DomainAccount>\n" \
|
||||
" <Domain>" ENVIRON["DOMAIN_XML"] "</Domain>\n" \
|
||||
" </DomainAccountList>\n" \
|
||||
" </DomainAccounts>"
|
||||
accounts_added = 1
|
||||
}
|
||||
[ "$settings_count" = "1" ] || return 1
|
||||
[ "$join_count" = "0" ] || return 1
|
||||
[ "$unnamed_count" = "0" ] || return 1
|
||||
[ "$shell_count" = "1" ] || return 1
|
||||
[ "$accounts_count" = "1" ] || return 1
|
||||
[ "$domain_count" = "0" ] || return 1
|
||||
[ "$admin_count" = "1" ] || return 1
|
||||
[ "$autologon_count" = "1" ] || return 1
|
||||
[ "$username_count" = "1" ] || return 1
|
||||
[ "$value_count" = "1" ] || return 1
|
||||
[ "$plaintext_count" = "1" ] || return 1
|
||||
|
||||
section == "oobeSystem" && in_autologon &&
|
||||
/^[[:space:]]*<Username>.*<\/Username>[[:space:]]*$/ {
|
||||
print " <Username>" ENVIRON["ACCOUNT_XML"] "</Username>\n" \
|
||||
" <Domain>" ENVIRON["DOMAIN_XML"] "</Domain>"
|
||||
autologon_added = 1
|
||||
next
|
||||
}
|
||||
|
||||
section == "oobeSystem" && in_autologon &&
|
||||
/^[[:space:]]*<Domain([[:space:]/>])/ { next }
|
||||
|
||||
section == "oobeSystem" && in_autologon &&
|
||||
/^[[:space:]]*<Value>.*<\/Value>[[:space:]]*$/ {
|
||||
print " <Value>" ENVIRON["PASS_XML"] "</Value>"
|
||||
password_added = 1
|
||||
next
|
||||
}
|
||||
|
||||
section == "oobeSystem" && in_autologon &&
|
||||
/^[[:space:]]*<PlainText([[:space:]/>])/ {
|
||||
print " <PlainText>true</PlainText>"
|
||||
plaintext_added = 1
|
||||
next
|
||||
}
|
||||
|
||||
section == "specialize" && !join_added &&
|
||||
/^[[:space:]]*<\/settings>[[:space:]]*$/ {
|
||||
print " <component name=\"Microsoft-Windows-UnattendedJoin\" processorArchitecture=\"" ENVIRON["ARCH_XML"] "\" publicKeyToken=\"31bf3856ad364e35\" language=\"neutral\" versionScope=\"nonSxS\">\n" \
|
||||
" <Identification>\n" \
|
||||
" <Credentials>"
|
||||
|
||||
if (ENVIRON["CRED_DOMAIN"] != "") {
|
||||
print " <Domain>" ENVIRON["CRED_DOMAIN"] "</Domain>"
|
||||
}
|
||||
|
||||
print " <Username>" ENVIRON["AUTH_XML"] "</Username>\n" \
|
||||
" <Password>" ENVIRON["PASS_XML"] "</Password>\n" \
|
||||
" </Credentials>\n" \
|
||||
" <JoinDomain>" ENVIRON["DOMAIN_XML"] "</JoinDomain>"
|
||||
|
||||
if (ENVIRON["OU_XML"] != "") {
|
||||
print " <MachineObjectOU>" ENVIRON["OU_XML"] "</MachineObjectOU>"
|
||||
}
|
||||
|
||||
print " </Identification>\n" \
|
||||
" </component>"
|
||||
|
||||
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 && plaintext_added) }
|
||||
' "$asset" > "$result" ||
|
||||
! mv -f "$result" "$asset"; then
|
||||
|
||||
rm -rf "$tmp" || true
|
||||
if ! tmp=$(mktemp "${asset}.XXXXXX"); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local -a args=(
|
||||
-N "u=$ns"
|
||||
-N "wcm=$wcm"
|
||||
-i "$accounts/u:AdministratorPassword"
|
||||
-t elem -n 'DomainAccounts'
|
||||
-s "$domain_accounts"
|
||||
-t elem -n 'DomainAccountList'
|
||||
-i "$account_list"
|
||||
-t attr -n 'wcm:action' -v 'add'
|
||||
-s "$account_list"
|
||||
-t elem -n 'DomainAccount'
|
||||
-i "$domain_account"
|
||||
-t attr -n 'wcm:action' -v 'add'
|
||||
-s "$domain_account"
|
||||
-t elem -n 'Name' -v "$account_xml"
|
||||
-s "$domain_account"
|
||||
-t elem -n 'Group' -v 'Administrators'
|
||||
-s "$account_list"
|
||||
-t elem -n 'Domain' -v "$domain_xml"
|
||||
-d "$autologon/u:Domain"
|
||||
-u "$autologon/u:Username"
|
||||
-v "$account"
|
||||
-a "$autologon/u:Username"
|
||||
-t elem -n 'Domain' -v "$domain_xml"
|
||||
-u "$autologon/u:Password/u:Value"
|
||||
-v "$pass"
|
||||
-u "$autologon/u:Password/u:PlainText"
|
||||
-v 'true'
|
||||
-s "$specialize"
|
||||
-t elem -n 'component'
|
||||
-i "$specialize/*[local-name()='component' and not(@name)][last()]"
|
||||
-t attr -n 'name' -v '__QEMUS_UNATTENDED_JOIN__'
|
||||
-i "$component"
|
||||
-t attr -n 'processorArchitecture' -v "$arch"
|
||||
-i "$component"
|
||||
-t attr -n 'publicKeyToken' -v '31bf3856ad364e35'
|
||||
-i "$component"
|
||||
-t attr -n 'language' -v 'neutral'
|
||||
-i "$component"
|
||||
-t attr -n 'versionScope' -v 'nonSxS'
|
||||
-s "$component"
|
||||
-t elem -n 'Identification'
|
||||
-s "$identification"
|
||||
-t elem -n 'Credentials'
|
||||
)
|
||||
|
||||
if [ -n "$cred_domain" ]; then
|
||||
args+=(
|
||||
-s "$credentials"
|
||||
-t elem -n 'Domain' -v "$cred_domain_xml"
|
||||
)
|
||||
fi
|
||||
|
||||
args+=(
|
||||
-s "$credentials"
|
||||
-t elem -n 'Username' -v "$auth_xml"
|
||||
-s "$credentials"
|
||||
-t elem -n 'Password' -v "$pass_xml"
|
||||
-s "$identification"
|
||||
-t elem -n 'JoinDomain' -v "$domain_xml"
|
||||
)
|
||||
|
||||
if [ -n "$ou" ]; then
|
||||
args+=(
|
||||
-s "$identification"
|
||||
-t elem -n 'MachineObjectOU' -v "$ou_xml"
|
||||
)
|
||||
fi
|
||||
|
||||
args+=(
|
||||
-u "$component/@name"
|
||||
-v 'Microsoft-Windows-UnattendedJoin'
|
||||
)
|
||||
|
||||
if ! xmlstarlet ed "${args[@]}" "$asset" > "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! chmod --reference="$asset" "$tmp" ||
|
||||
! mv -f "$tmp" "$asset"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1172,18 +1275,27 @@ prepareDomainAccount() {
|
||||
updateDisplayXML() {
|
||||
|
||||
local asset="$1"
|
||||
local app host
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local app="$APP for $ENGINE"
|
||||
local -a args=(
|
||||
-L
|
||||
-N "u=$ns"
|
||||
-u '//u:*[text()="Windows for Docker"]'
|
||||
-v "$app"
|
||||
-u '//u:VerticalResolution'
|
||||
-v "$HEIGHT"
|
||||
-u '//u:HorizontalResolution'
|
||||
-v "$WIDTH"
|
||||
)
|
||||
|
||||
app=$(escapeXMLSed "$APP for $ENGINE") || return 1
|
||||
if [ -n "${HOST:-}" ]; then
|
||||
args+=(
|
||||
-u '//u:ComputerName'
|
||||
-v "$HOST"
|
||||
)
|
||||
fi
|
||||
|
||||
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|<HorizontalResolution>[^<]*</HorizontalResolution>|<HorizontalResolution>$WIDTH</HorizontalResolution>|g" "$asset" || return 1
|
||||
|
||||
[ -n "${HOST:-}" ] || return 0
|
||||
|
||||
host=$(escapeXMLSed "$HOST") || return 1
|
||||
sed -i -E "s|<ComputerName>[^<]*</ComputerName>|<ComputerName>$host</ComputerName>|g" "$asset" || return 1
|
||||
xmlstarlet ed "${args[@]}" "$asset" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -1192,29 +1304,41 @@ updateLocaleXML() {
|
||||
|
||||
local asset="$1"
|
||||
local language="$2"
|
||||
local culture region keyboard value
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local culture region keyboard
|
||||
local -a args=( -L -N "u=$ns" )
|
||||
|
||||
culture=$(getLanguage "$language" "culture") || return 1
|
||||
|
||||
if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then
|
||||
value=$(escapeXMLSed "$culture") || return 1
|
||||
sed -i "s|<UILanguage>en-US</UILanguage>|<UILanguage>$value</UILanguage>|g" "$asset" || return 1
|
||||
args+=(
|
||||
-u '//u:UILanguage[text()="en-US"]'
|
||||
-v "$culture"
|
||||
)
|
||||
fi
|
||||
|
||||
region="${REGION:-$culture}"
|
||||
|
||||
if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then
|
||||
value=$(escapeXMLSed "$region") || return 1
|
||||
sed -i "s|<UserLocale>en-US</UserLocale>|<UserLocale>$value</UserLocale>|g" "$asset" || return 1
|
||||
sed -i "s|<SystemLocale>en-US</SystemLocale>|<SystemLocale>$value</SystemLocale>|g" "$asset" || return 1
|
||||
args+=(
|
||||
-u '//u:UserLocale[text()="en-US"]'
|
||||
-v "$region"
|
||||
-u '//u:SystemLocale[text()="en-US"]'
|
||||
-v "$region"
|
||||
)
|
||||
fi
|
||||
|
||||
keyboard="${KEYBOARD:-$culture}"
|
||||
|
||||
if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then
|
||||
value=$(escapeXMLSed "$keyboard") || return 1
|
||||
sed -i "s|<InputLocale>en-US</InputLocale>|<InputLocale>$value</InputLocale>|g" "$asset" || return 1
|
||||
sed -i "s|<InputLocale>0409:00000409</InputLocale>|<InputLocale>$value</InputLocale>|g" "$asset" || return 1
|
||||
args+=(
|
||||
-u '//u:InputLocale[text()="en-US" or text()="0409:00000409"]'
|
||||
-v "$keyboard"
|
||||
)
|
||||
fi
|
||||
|
||||
if (( ${#args[@]} > 3 )); then
|
||||
xmlstarlet ed "${args[@]}" "$asset" || return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
@@ -1225,17 +1349,11 @@ updateLocalAccount() {
|
||||
local asset="$1"
|
||||
local user="${USERNAME:-}"
|
||||
local pass="${PASSWORD:-admin}"
|
||||
local user_xml pw admin
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local pw admin
|
||||
|
||||
validateUsername "$user" "local" || return 1
|
||||
|
||||
if [ -n "$user" ]; then
|
||||
user_xml=$(escapeXMLSed "$user") || return 1
|
||||
sed -i "s|<Name>Docker</Name>|<Name>$user_xml</Name>|g" "$asset" || return 1
|
||||
sed -i "s|<FullName>Docker</FullName>|<FullName>$user_xml</FullName>|g" "$asset" || return 1
|
||||
sed -i "s|<Username>Docker</Username>|<Username>$user_xml</Username>|g" "$asset" || return 1
|
||||
fi
|
||||
|
||||
pw=$(printf '%s' "${pass}Password" |
|
||||
iconv -f utf-8 -t utf-16le |
|
||||
base64 -w 0) || return 1
|
||||
@@ -1244,13 +1362,27 @@ updateLocalAccount() {
|
||||
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
|
||||
local -a args=( -L -N "u=$ns" )
|
||||
|
||||
sed -i -z -E \
|
||||
"s#(<AdministratorPassword>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$admin</Value>#g" \
|
||||
"$asset" || return 1
|
||||
if [ -n "$user" ]; then
|
||||
args+=(
|
||||
-u '//u:Name[text()="Docker"]'
|
||||
-v "$user"
|
||||
-u '//u:FullName[text()="Docker"]'
|
||||
-v "$user"
|
||||
-u '//u:Username[text()="Docker"]'
|
||||
-v "$user"
|
||||
)
|
||||
fi
|
||||
|
||||
args+=(
|
||||
-u '//u:Password/u:Value'
|
||||
-v "$pw"
|
||||
-u '//u:AdministratorPassword/u:Value'
|
||||
-v "$admin"
|
||||
)
|
||||
|
||||
xmlstarlet ed "${args[@]}" "$asset" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -1293,11 +1425,13 @@ updateMembership() {
|
||||
updateAutologinXML() {
|
||||
|
||||
local asset="$1"
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
|
||||
disabled "${AUTOLOGIN:-}" || return 0
|
||||
|
||||
sed -i -E \
|
||||
'/^[[:space:]]*<AutoLogon([[:space:]>])/,/^[[:space:]]*<\/AutoLogon>[[:space:]]*$/d' \
|
||||
xmlstarlet ed -L \
|
||||
-N "u=$ns" \
|
||||
-d '//u:AutoLogon' \
|
||||
"$asset" || return 1
|
||||
|
||||
return 0
|
||||
@@ -1306,17 +1440,22 @@ updateAutologinXML() {
|
||||
updateEditionXML() {
|
||||
|
||||
local asset="$1"
|
||||
local edition
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
local edition xpath expression
|
||||
|
||||
[ -n "${EDITION:-}" ] || return 0
|
||||
|
||||
edition=$(normalizeServerEdition "$EDITION") || return 1
|
||||
edition="${edition//-/}"
|
||||
edition="${edition^^}"
|
||||
edition=$(escapeXMLSed "$edition") || return 1
|
||||
|
||||
sed -i \
|
||||
"s|SERVERSTANDARD</Value>|SERVER$edition</Value>|g" \
|
||||
xpath='//u:InstallFrom/u:MetaData[u:Key="/IMAGE/NAME"]/u:Value[substring(., string-length(.) - string-length("SERVERSTANDARD") + 1) = "SERVERSTANDARD"]'
|
||||
expression="concat(substring-before(., 'SERVERSTANDARD'), 'SERVER$edition', substring-after(., 'SERVERSTANDARD'))"
|
||||
|
||||
xmlstarlet ed -L \
|
||||
-N "u=$ns" \
|
||||
-u "$xpath" \
|
||||
-x "$expression" \
|
||||
"$asset" || return 1
|
||||
|
||||
return 0
|
||||
@@ -1348,7 +1487,8 @@ updateDiskID() {
|
||||
local disk_type="${2,,}"
|
||||
local mode="${3:-setup}"
|
||||
local target="0"
|
||||
local matches ids current count rc
|
||||
local value current
|
||||
local -a ids=()
|
||||
|
||||
[ -s "$asset" ] || return 1
|
||||
|
||||
@@ -1362,29 +1502,26 @@ updateDiskID() {
|
||||
* ) return 1 ;;
|
||||
esac
|
||||
|
||||
matches=$(grep -oE '<DiskID>[[:space:]]*[0-9]+[[:space:]]*</DiskID>' "$asset") || {
|
||||
rc=$?
|
||||
if [ "$rc" -eq 1 ]; then
|
||||
matches=""
|
||||
else
|
||||
error "Failed to read DiskID values from answer file: $asset"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
while IFS= read -r value; do
|
||||
[[ "$value" =~ ^[0-9]+$ ]] || continue
|
||||
ids+=( "$value" )
|
||||
done < <(
|
||||
xmlstarlet sel \
|
||||
-T -t \
|
||||
-m '//*[local-name()="DiskID"]' \
|
||||
-v 'normalize-space(.)' -n \
|
||||
"$asset"
|
||||
)
|
||||
|
||||
# Some custom answer files do not contain a disk configuration.
|
||||
[ -n "$matches" ] || return 0
|
||||
(( ${#ids[@]} > 0 )) || return 0
|
||||
|
||||
ids=$(printf '%s\n' "$matches" |
|
||||
sed -E 's#.*<DiskID>[[:space:]]*([0-9]+)[[:space:]]*</DiskID>.*#\1#' |
|
||||
sort -u) || return 1
|
||||
|
||||
count=$(printf '%s\n' "$ids" | wc -l) || return 1
|
||||
mapfile -t ids < <(printf '%s\n' "${ids[@]}" | sort -u)
|
||||
|
||||
# Leave explicit multi-disk configurations untouched.
|
||||
[ "$count" -eq 1 ] || return 0
|
||||
(( ${#ids[@]} == 1 )) || return 0
|
||||
|
||||
current="$ids"
|
||||
current="${ids[0]}"
|
||||
[ "$current" = "$target" ] && return 0
|
||||
|
||||
case "$current" in
|
||||
@@ -1395,8 +1532,11 @@ updateDiskID() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! sed -i -E \
|
||||
"s#<DiskID>[[:space:]]*${current}[[:space:]]*</DiskID>#<DiskID>$target</DiskID>#g" \ "$asset"; then
|
||||
if ! xmlstarlet ed -L \
|
||||
-u "//*[local-name()='DiskID' and normalize-space(.)='$current']" \
|
||||
-v "$target" \
|
||||
"$asset"; then
|
||||
|
||||
error "Failed to update DiskID in answer file: $asset"
|
||||
return 1
|
||||
fi
|
||||
@@ -1409,8 +1549,9 @@ getXMLArchitecture() {
|
||||
local asset="$1"
|
||||
local arch
|
||||
|
||||
arch=$(sed -n -E \
|
||||
'0,/processorArchitecture="/s/.*processorArchitecture="([^"]+)".*/\1/p' \
|
||||
arch=$(xmlstarlet sel \
|
||||
-T -t \
|
||||
-v 'string((//*[local-name()="component"]/@processorArchitecture)[1])' \
|
||||
"$asset") || return 1
|
||||
|
||||
[ -n "$arch" ] || return 1
|
||||
@@ -1424,89 +1565,125 @@ setConfigurationXML() {
|
||||
local asset="$1"
|
||||
local setup='/*[local-name()="unattend"]/*[local-name()="settings" and @pass="windowsPE"]/*[local-name()="component" and @name="Microsoft-Windows-Setup"]'
|
||||
local config="$setup/*[local-name()=\"UseConfigurationSet\"]"
|
||||
local userdata="$setup/*[local-name()=\"UserData\"]"
|
||||
local setup_count config_count userdata_count tmp
|
||||
local setup_count config_count result_count tmp
|
||||
|
||||
[ -s "$asset" ] || return 1
|
||||
|
||||
setup_count=$(xmlstarlet sel -t -v "count($setup)" "$asset") || return 1
|
||||
setup_count=$(xmlstarlet sel -T -t -v "count($setup)" "$asset") || return 1
|
||||
|
||||
if [ "$setup_count" != "1" ]; then
|
||||
error "Failed to find a unique Microsoft-Windows-Setup component: $asset"
|
||||
return 1
|
||||
fi
|
||||
|
||||
config_count=$(xmlstarlet sel -t -v "count($config)" "$asset") || return 1
|
||||
config_count=$(xmlstarlet sel -T -t -v "count($config)" "$asset") || return 1
|
||||
|
||||
if [ "$config_count" -gt 1 ]; then
|
||||
error "Multiple UseConfigurationSet entries found in answer file: $asset"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! tmp=$(mktemp "${asset}.XXXXXX"); then
|
||||
if ! tmp=$(mktemp -d); then
|
||||
error "Failed to create a temporary answer file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$config_count" -eq 1 ]; then
|
||||
local stylesheet="$tmp/configuration.xsl"
|
||||
local result="$tmp/answer.xml"
|
||||
|
||||
if ! xmlstarlet ed \
|
||||
-u "$config" \
|
||||
-v "true" \
|
||||
"$asset" > "$tmp"; then
|
||||
if ! cat > "$stylesheet" <<'XSL'
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsl:stylesheet version="1.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
|
||||
|
||||
rm -f "$tmp"
|
||||
<xsl:template match="@*|node()">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="
|
||||
*[local-name()='UseConfigurationSet' and
|
||||
parent::*[
|
||||
local-name()='component' and
|
||||
@name='Microsoft-Windows-Setup' and
|
||||
parent::*[local-name()='settings' and @pass='windowsPE']
|
||||
]
|
||||
]">
|
||||
<xsl:copy>
|
||||
<xsl:copy-of select="@*"/>
|
||||
<xsl:text>true</xsl:text>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="
|
||||
*[local-name()='UserData' and
|
||||
parent::*[
|
||||
local-name()='component' and
|
||||
@name='Microsoft-Windows-Setup' and
|
||||
parent::*[local-name()='settings' and @pass='windowsPE'] and
|
||||
not(*[local-name()='UseConfigurationSet'])
|
||||
]
|
||||
]">
|
||||
<xsl:element name="UseConfigurationSet" namespace="{namespace-uri()}">
|
||||
<xsl:text>true</xsl:text>
|
||||
</xsl:element>
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="
|
||||
*[local-name()='component' and
|
||||
@name='Microsoft-Windows-Setup' and
|
||||
parent::*[local-name()='settings' and @pass='windowsPE'] and
|
||||
not(*[local-name()='UseConfigurationSet']) and
|
||||
not(*[local-name()='UserData'])
|
||||
]">
|
||||
<xsl:copy>
|
||||
<xsl:apply-templates select="@*|node()"/>
|
||||
<xsl:element name="UseConfigurationSet" namespace="{namespace-uri()}">
|
||||
<xsl:text>true</xsl:text>
|
||||
</xsl:element>
|
||||
</xsl:copy>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
XSL
|
||||
then
|
||||
rm -rf "$tmp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! xmlstarlet tr "$stylesheet" "$asset" > "$result"; then
|
||||
rm -rf "$tmp"
|
||||
error "Failed to enable the Windows configuration set!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
userdata_count=$(xmlstarlet sel -t -v "count($userdata)" "$asset") || {
|
||||
rm -f "$tmp"
|
||||
result_count=$(xmlstarlet sel \
|
||||
-T -t \
|
||||
-v "count($config[normalize-space(.)='true'])" \
|
||||
"$result") || {
|
||||
rm -rf "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
if [ "$userdata_count" -gt 0 ]; then
|
||||
|
||||
if ! xmlstarlet ed \
|
||||
-i "${userdata}[1]" \
|
||||
-t elem \
|
||||
-n "UseConfigurationSet" \
|
||||
-v "true" \
|
||||
"$asset" > "$tmp"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
error "Failed to insert UseConfigurationSet into answer file!"
|
||||
if [ "$result_count" != "1" ]; then
|
||||
rm -rf "$tmp"
|
||||
error "Failed to enable the Windows configuration set!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
else
|
||||
if ! chmod --reference="$asset" "$result" ||
|
||||
! mv -f "$result" "$asset"; then
|
||||
|
||||
if ! xmlstarlet ed \
|
||||
-s "$setup" \
|
||||
-t elem \
|
||||
-n "UseConfigurationSet" \
|
||||
-v "true" \
|
||||
"$asset" > "$tmp"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
error "Failed to append UseConfigurationSet to answer file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
if ! chmod --reference="$asset" "$tmp" ||
|
||||
! mv -f "$tmp" "$asset"; then
|
||||
|
||||
rm -f "$tmp"
|
||||
rm -rf "$tmp"
|
||||
error "Failed to replace the updated answer file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1527,10 +1704,11 @@ removeSharedFolder() {
|
||||
removeLocalAccount() {
|
||||
|
||||
local asset="$1"
|
||||
local ns="urn:schemas-microsoft-com:unattend"
|
||||
|
||||
if ! sed -i -E \
|
||||
-e '/^[[:space:]]*<LocalAccounts([[:space:]>])/,/^[[:space:]]*<\/LocalAccounts>[[:space:]]*$/d' \
|
||||
-e '/^[[:space:]]*<AdministratorPassword([[:space:]>])/,/^[[:space:]]*<\/AdministratorPassword>[[:space:]]*$/d' \
|
||||
if ! xmlstarlet ed -L \
|
||||
-N "u=$ns" \
|
||||
-d '//u:LocalAccounts | //u:AdministratorPassword' \
|
||||
"$asset"; then
|
||||
|
||||
error "Failed to remove local account configuration from answer file!"
|
||||
@@ -1630,19 +1808,6 @@ validateLegacyUsername() {
|
||||
return 0
|
||||
}
|
||||
|
||||
escapeXMLSed() {
|
||||
|
||||
local s
|
||||
|
||||
s=$(escapeXML "$1") || return 1
|
||||
s=${s//\\/\\\\}
|
||||
s=${s//&/\\&}
|
||||
s=${s//|/\\|}
|
||||
|
||||
printf '%s' "$s"
|
||||
return 0
|
||||
}
|
||||
|
||||
escapeSIFValue() {
|
||||
|
||||
local s="$1"
|
||||
|
||||
Reference in New Issue
Block a user