1
0
mirror of https://github.com/dockur/windows.git synced 2026-07-27 21:42:36 +07:00

feat: Refactor Windows answer file handling into separate script (#1923)

This commit is contained in:
Kroese
2026-07-21 14:05:28 +02:00
committed by GitHub
parent a831593327
commit ef49e7bb0b
4 changed files with 1381 additions and 1376 deletions
+1228
View File
File diff suppressed because it is too large Load Diff
-856
View File
@@ -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
+1
View File
@@ -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
+152 -520
View File
@@ -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|<DomainAccounts([[:space:]/>])' "$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 '
/<settings[^>]*pass="specialize"[^>]*>/ { section = "specialize" }
/<settings[^>]*pass="oobeSystem"[^>]*>/ { section = "oobeSystem" }
section == "oobeSystem" && /<UserAccounts([[:space:]>])/ { in_accounts = 1 }
section == "oobeSystem" && /<AutoLogon([[:space:]>])/ { in_autologon = 1 }
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
}
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["PW"] "</Value>"
password_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) }
' "$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 '
/<settings[^>]*pass="specialize"[^>]*>/ { section = "specialize" }
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
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|<VerticalResolution>[^<]*</VerticalResolution>|<VerticalResolution>$HEIGHT</VerticalResolution>|g" "$asset" || return 1
sed -i -E "s|<HorizontalResolution>[^<]*</HorizontalResolution>|<HorizontalResolution>$WIDTH</HorizontalResolution>|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|<UILanguage>en-US</UILanguage>|<UILanguage>$value</UILanguage>|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|<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
fi
keyboard="$KEYBOARD"
[ -z "$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
fi
if [ -n "$HOST" ]; then
host=$(escapeXMLSed "$HOST") || return 1
sed -i -E "s|<ComputerName>[^<]*</ComputerName>|<ComputerName>$host</ComputerName>|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|<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
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#(<Password>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$pw</Value>#g" "$asset" || return 1
sed -i -z -E "s#(<AdministratorPassword>[[:space:]]*<Value)([[:space:]]*/>|>[^<]*</Value>)#\1>$admin</Value>#g" "$asset" || return 1
sed -i -E "s|<PlainText>[^<]*</PlainText>|<PlainText>false</PlainText>|g" "$asset" || return 1
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:]]*<AutoLogon([[: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</Value>|SERVER$edition</Value>|g" "$asset" || return 1
fi
if [ -n "$KEY" ]; then
key=$(escapeXMLSed "$KEY") || return 1
sed -i -E '/^[[:space:]]*<ProductKey>[[:space:]]*$/,/^[[:space:]]*<\/ProductKey>[[:space:]]*$/d' "$asset" || return 1
sed -i -E "s|<ProductKey>[^<]*</ProductKey>|<ProductKey>$key</ProductKey>|g" "$asset" || return 1
sed -i "s|</UserData>| <ProductKey>\n <Key>$key</Key>\n <WillShowUI>OnError</WillShowUI>\n </ProductKey>\n </UserData>|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