#!/usr/bin/env bash set -Eeuo pipefail SIFInstall() { local dir="$2" local desc="$3" local driver="$4" local shortcut="Y" local drivers="/tmp/drivers" local msg="Preparing $desc installation..." info "$msg" && html "$msg" if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then shortcut="N" fi 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 # Legacy media uses directory names rather than metadata to identify the # architecture and the text-mode setup source tree. local arch="amd64" [ ! -d "$dir/AMD64" ] && arch="x86" local target="$dir/AMD64" [[ "${arch,,}" == "x86" ]] && target="$dir/I386" if [ ! -d "$target" ]; then error "Failed to locate directory \"$target\" in $desc ISO image!" return 1 fi case "${driver,,}" in "2k" ) # Windows 2000 keeps its existing storage/network path, but still needs # the QBochs display package staged for Plug and Play setup. extractDrivers "$drivers" || return 1 if ! addDisplayDriver "$dir" "$driver" "$arch" "$drivers"; then rm -rf "$drivers" || : return 1 fi if ! rm -rf "$drivers"; then warn "failed to clean temporary driver files!" fi ;; "xp" ) addLegacyDrivers "$dir" "$target" "$driver" "$arch" "$drivers" || return 1 ;; "2k3" ) if [[ "${arch,,}" == "x86" ]]; then error "The 32-bit version of $desc is not supported!" && return 1 fi addLegacyDrivers "$dir" "$target" "$driver" "$arch" "$drivers" || return 1 ;; esac disableAutoReboot "$target" || return 1 setLegacyKey "$target" "$driver" "$arch" "$desc" || return 1 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="" local oem_dir="$dir/\$OEM\$/\$1/OEM" if [ -d "$oem_dir" ]; then install=$(find \ "$oem_dir" -maxdepth 1 -type f -iname install.bat -print -quit ) || return 1 fi oem=$(writeCommand "$install") || return 1 [ -z "$WIDTH" ] && WIDTH="1024" [ -z "$HEIGHT" ] && HEIGHT="768" 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 if [[ "$driver" == "2k" ]]; then validateLegacyEncoding "APP" "$APP" "$desc" || return 1 validateLegacyEncoding "ENGINE" "$ENGINE" "$desc" || return 1 fi 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 culture region keyboard timezone local localeID inputLocaleID keyboardID culture=$(getLanguage "$LANGUAGE" "culture") || return 1 [ -z "$culture" ] && culture="en-US" region="${REGION:-$culture}" keyboard="${KEYBOARD:-en-US}" localeID=$(getLocaleID "$region") || return 1 inputLocaleID=$(getInputLocaleID "$keyboard") || return 1 keyboardID=$(getKeyboardID "$keyboard") || return 1 timezone=$(getTimeZone "$region" "nt5") || return 1 local sifHost sifUsername sifPassword sifOrganization sifWorkgroup local regUsername regPassword validateLegacyUsername "$username" "$desc" || return 1 validatePassword "$password" "$desc" || return 1 if [[ "$driver" == "2k" ]]; then validateLegacyEncoding "USERNAME" "$username" "$desc" || return 1 validateLegacyEncoding "PASSWORD" "$password" "$desc" || return 1 validateLegacyEncoding "WORKGROUP" "$workgroup" "$desc" || return 1 fi # WINNT.SIF and .reg files use different escaping rules, so prepare their # values independently before generating either file. 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 writeSIF \ "$target" \ "$driver" \ "$product" "$sifHost" "$sifUsername" "$sifPassword" "$sifOrganization" "$sifWorkgroup" \ "$localeID" "$inputLocaleID" "$keyboardID" "$timezone" || return 1 writeRegistry "$dir" "$shortcut" "$oem" "$regUsername" "$regPassword" "$driver" || return 1 appendRegistry "$dir" "$driver" || return 1 writeVBS "$dir" "$username" "$shortcut" "$driver" || return 1 return 0 } addLegacyDrivers() { local dir="$1" local target="$2" local driver="$3" local arch="$4" local drivers="$5" local msg="Adding drivers to image..." info "$msg" && html "$msg" extractDrivers "$drivers" || return 1 copyStorageDriver "$dir" "$target" "$driver" "$arch" "$drivers" || return 1 addNetworkDriver "$dir" "$driver" "$arch" "$drivers" || return 1 addQXLDriver "$dir" "$driver" "$arch" "$drivers" || return 1 addDisplayDriver "$dir" "$driver" "$arch" "$drivers" || return 1 disableGenericDisplay "$target" "$driver" "$arch" "$drivers" || return 1 addBalloonDriver "$dir" "$driver" "$arch" "$drivers" || return 1 local file 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 patchStorageDriver "$file" "$arch" || return 1 addSataDriver "$dir" "$target" "$arch" "$drivers" "$file" || return 1 if ! rm -rf "$drivers"; then warn "failed to clean temporary driver files!" fi return 0 } copyStorageDriver() { local dir="$1" local target="$2" local driver="$3" local arch="$4" local drivers="$5" local destination="$dir/\$OEM\$/\$1/Drivers/viostor" 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 "$destination" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$destination" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$destination" || return 1 cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$destination" || return 1 return 0 } addNetworkDriver() { local dir="$1" local driver="$2" local arch="$3" local drivers="$4" local destination="$dir/\$OEM\$/\$1/Drivers/NetKVM" if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then error "Failed to locate required network drivers!" return 1 fi mkdir -p "$destination" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$destination" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$destination" || return 1 cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$destination" || return 1 return 0 } addQXLDriver() { local dir="$1" local driver="$2" local arch="$3" local drivers="$4" local source="$drivers/qxl/$driver/$arch" local destination="$dir/\$OEM\$/\$1/Drivers/QXL" # The legacy QXL package is named for XP but its x86 INF targets XP and # later NT x86 releases, so it is also suitable to keep around for 2003 x86. if [ ! -d "$source" ] && [[ "${arch,,}" == "x86" ]]; then source="$drivers/qxl/xp/x86" fi [ -d "$source" ] || return 0 local file for file in qxl.cat qxl.inf qxl.sys qxldd.dll; do if [ ! -f "$source/$file" ]; then error "Failed to locate required QXL display driver file: $file" return 1 fi done mkdir -p "$destination" || return 1 cp -Lr "$source/." "$destination" || return 1 return 0 } addDisplayDriver() { local dir="$1" local driver="$2" local arch="$3" local drivers="$4" local qbochs_arch="$arch" [[ "${qbochs_arch,,}" == "amd64" ]] && qbochs_arch="x64" local source="$drivers/qbochs/$driver/$qbochs_arch" local destination="$dir/\$OEM\$/\$1/Drivers/QBochs" if [ ! -d "$source" ]; then error "Failed to locate required QBochs display driver directory: $source" return 1 fi local files="qbochs.inf qbochs.sys" [[ "$driver" == "2k3" ]] && files+=" qbochs.cat" local file for file in $files; do if [ ! -f "$source/$file" ]; then error "Failed to locate required QBochs display driver file: $file" return 1 fi done mkdir -p "$destination" || return 1 for file in $files; do cp -L "$source/$file" "$destination/$file" || return 1 done return 0 } disableGenericDisplay() { local target="$1" local driver="$2" local arch="$3" local drivers="$4" [[ "$driver" == "2k3" && "${arch,,}" == "amd64" ]] || return 0 local qbochs_inf="$drivers/qbochs/$driver/x64/qbochs.inf" local qbochs_id='PCI\VEN_1234&DEV_1111&SUBSYS_11001AF4' # Do not remove the generic VGA match unless the exact QBochs device is # present in the driver package that was just staged for this installation. if [ ! -f "$qbochs_inf" ] || ! grep -Fqi "$qbochs_id" "$qbochs_inf"; then error "The QBochs driver does not contain the expected device ID: $qbochs_id" return 1 fi local cab cab=$(find "$target" -maxdepth 1 -type f -iname DISPLAY.IN_ -print -quit) || return 1 if [ -z "$cab" ] || [ ! -s "$cab" ]; then error "The file DISPLAY.IN_ could not be found!" return 1 fi local temp temp=$(mktemp -d) || return 1 if ! cabextract -q -d "$temp" "$cab"; then rm -rf "$temp" || : error "Failed to extract DISPLAY.IN_!" return 1 fi local inf inf=$(find "$temp" -maxdepth 1 -type f -iname display.inf -print -quit) || { rm -rf "$temp" || : return 1 } if [ -z "$inf" ] || [ ! -s "$inf" ]; then rm -rf "$temp" || : error "DISPLAY.IN_ does not contain display.inf!" return 1 fi local active='^%stdVga%[[:space:]]*=[[:space:]]*vga,PCI\\CC_0300' local disabled='^;%stdVga%[[:space:]]*=[[:space:]]*vga,PCI\\CC_0300' local active_count disabled_count active_count=$(grep -c "$active" "$inf") || active_count=0 disabled_count=$(grep -c "$disabled" "$inf") || disabled_count=0 if (( active_count == 0 && disabled_count == 1 )); then rm -rf "$temp" || return 1 return 0 fi if (( active_count != 1 || disabled_count != 0 )); then rm -rf "$temp" || : error "Failed to identify the Server 2003 Standard VGA PCI\\CC_0300 entry!" return 1 fi if ! sed -i \ '/^%stdVga%[[:space:]]*=[[:space:]]*vga,PCI\\CC_0300/s/^/;/' "$inf"; then rm -rf "$temp" || : error "Failed to disable the Server 2003 Standard VGA PCI\\CC_0300 entry!" return 1 fi active_count=$(grep -c "$active" "$inf") || active_count=0 disabled_count=$(grep -c "$disabled" "$inf") || disabled_count=0 if (( active_count != 0 || disabled_count != 1 )); then rm -rf "$temp" || : error "Failed to verify the Server 2003 Standard VGA change!" return 1 fi local rebuilt="$temp/DISPLAY.IN_.new" if ! gcab --create --nopath --zip "$rebuilt" "$inf"; then rm -rf "$temp" || : error "Failed to rebuild DISPLAY.IN_!" return 1 fi local verify="$temp/verify" mkdir -p "$verify" || { rm -rf "$temp" || : return 1 } if ! cabextract -q -d "$verify" "$rebuilt"; then rm -rf "$temp" || : error "Failed to verify the rebuilt DISPLAY.IN_!" return 1 fi local verify_inf verify_inf=$(find "$verify" -maxdepth 1 -type f -iname display.inf -print -quit) || { rm -rf "$temp" || : return 1 } if [ -z "$verify_inf" ] || ! cmp -s "$inf" "$verify_inf"; then rm -rf "$temp" || : error "The rebuilt DISPLAY.IN_ did not preserve the modified display.inf!" return 1 fi chmod --reference="$cab" "$rebuilt" || { rm -rf "$temp" || : return 1 } touch --reference="$cab" "$rebuilt" || { rm -rf "$temp" || : return 1 } if ! mv -f "$rebuilt" "$cab"; then rm -rf "$temp" || : error "Failed to replace DISPLAY.IN_!" return 1 fi rm -rf "$temp" || return 1 return 0 } addBalloonDriver() { local dir="$1" local driver="$2" local arch="$3" local drivers="$4" local source="$drivers/Balloon/$driver/$arch" local destination="$dir/\$OEM\$/\$1/Drivers/Balloon" local file for file in balloon.cat balloon.inf balloon.sys blnsvr.exe; do if [ ! -f "$source/$file" ]; then error "Failed to locate required Balloon driver file: $file" return 1 fi done mkdir -p "$destination" || return 1 cp -Lr "$source/." "$destination" || return 1 return 0 } patchStorageDriver() { local file="$1" local arch="$2" # Text-mode setup reads TXTSETUP.SIF before Plug and Play is available, so the # VirtIO storage service and hardware IDs must be registered there explicitly. addSIFEntry "$file" "SCSI.Load" 'viostor=viostor.sys,4' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'viostor.sys=1,,,,,,4_,4,1,,,1,4' || return 1 addSIFEntry "$file" "SCSI" 'viostor="Red Hat VirtIO SCSI Disk Device"' || return 1 addSIFEntry "$file" "HardwareIdsDatabase" 'PCI\VEN_1AF4&DEV_1001&SUBSYS_00000000="viostor"' || return 1 addSIFEntry "$file" "HardwareIdsDatabase" 'PCI\VEN_1AF4&DEV_1001&SUBSYS_00020000="viostor"' || return 1 addSIFEntry "$file" "HardwareIdsDatabase" 'PCI\VEN_1AF4&DEV_1001&SUBSYS_00021AF4="viostor"' || return 1 return 0 } addSataDriver() { local dir="$1" local target="$2" local arch="$3" local drivers="$4" local file="$5" local destination="$dir/\$OEM\$/\$1/Drivers/sata" if [ ! -d "$drivers/sata/xp/$arch" ]; then error "Failed to locate required SATA drivers!" return 1 fi mkdir -p "$destination" || return 1 cp -Lr "$drivers/sata/xp/$arch/." "$destination" || return 1 cp -Lr "$drivers/sata/xp/$arch/." "$target" || return 1 addSIFEntry "$file" "SCSI.Load" 'iaStor=iaStor.sys,4' || return 1 addSIFEntry "$file" "FileFlags" 'iaStor.sys = 16' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaStor.cat = 1,,,,,,,1,0,0' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaStor.inf = 1,,,,,,,1,0,0' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaStor.sys = 1,,,,,,4_,4,1,,,1,4' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaStor.sys = 1,,,,,,,1,0,0' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaahci.cat = 1,,,,,,,1,0,0' || return 1 addSIFEntry "$file" "SourceDisksFiles.$arch" 'iaAHCI.inf = 1,,,,,,,1,0,0' || return 1 addSIFEntry "$file" "SCSI" 'iaStor="Intel(R) SATA RAID/AHCI Controller"' || return 1 addSIFEntry "$file" "HardwareIdsDatabase" 'PCI\VEN_8086&DEV_2922&CC_0106="iaStor"' || return 1 return 0 } addSIFEntry() { local file="$1" local section="$2" local entry="$3" local header="[$section]" local line ending="lf" line=$(grep -Fnx -m 1 "$header" "$file" | cut -d: -f1) || line="" if [ -z "$line" ]; then line=$(grep -Fnx -m 1 "$header"$'\r' "$file" | cut -d: -f1) || line="" ending="crlf" fi if [ -z "$line" ]; then error "Failed to locate section \"$header\" in \"$file\" !" return 1 fi local rc=0 grep -Fqx -e "$entry" -e "$entry"$'\r' "$file" || rc=$? if (( rc == 0 )); then return 0 fi if (( rc != 1 )); then error "Failed to inspect section \"$header\" in \"$file\" !" return 1 fi if [[ "$ending" == "crlf" ]]; then printf '%s\r\n' "$entry" else printf '%s\n' "$entry" fi | sed -i "${line}r /dev/stdin" "$file" || return 1 return 0 } disableAutoReboot() { local target="$1" local file rc=0 local pattern='^[[:space:]]*HKLM[[:space:]]*,[[:space:]]*"SYSTEM\\CurrentControlSet\\Control\\CrashControl"[[:space:]]*,[[:space:]]*"AutoReboot"[[:space:]]*,[[:space:]]*[^,]*,' file=$(find "$target" -maxdepth 1 -type f -iname HIVESYS.INF -print -quit) || return 1 if [ -z "$file" ]; then error "The file HIVESYS.INF could not be found!" return 1 fi # Keep setup crashes visible instead of immediately rebooting into an # opaque installation loop. grep -Eqi "${pattern}[[:space:]]*[^,;[:space:]]+" "$file" || rc=$? case "$rc" in 0 ) sed -i -E "s|(${pattern})[[:space:]]*[^,;[:space:]]+|\\1 0|I" "$file" || : ;; 1 ) printf '%s\n' \ 'HKLM,"SYSTEM\CurrentControlSet\Control\CrashControl","AutoReboot",0x00010001,0' | unix2dos >> "$file" || : ;; esac return 0 } setLegacyKey() { local target="$1" local driver="$2" local arch="$3" local desc="$4" local setup pid file block setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1 [[ -n "$setup" ]] || return 0 [[ -z "$KEY" ]] || return 0 pid=$(<"$setup") || return 1 pid="${pid%$'\r'}" if [[ "$driver" == "2k" ]]; then [ "${#pid}" -ge 3 ] || return 0 echo "${pid::-3}270" > "$setup" || : return 0 fi if [[ "$pid" == *"270" ]]; then warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation." return 0 fi file=$(find "$target" -maxdepth 1 -type f -iname PID.INF -print -quit) || return 1 if [[ -n "$file" ]]; then local key="" # Prefer a staging or OEM key already shipped on the media before falling # back to Microsoft's documented generic installation keys. if [[ "$driver" == "2k3" ]]; then block=$(grep -i -A 2 "StagingKey" "$file") || block="" if [ -n "$block" ]; then key=$(printf '%s\n' "$block" | tail -n 2 | head -n 1) || key="" fi else key="${pid: -8:5}" if [[ "${pid^^}" == *"OEM" ]]; then block=$(grep -i -A 2 "$key" "$file") || block="" else block=$(grep -i -m 1 -A 2 "$key" "$file") || block="" fi if [ -n "$block" ]; then key=$(printf '%s\n' "$block" | tail -n 2 | head -n 1) || key="" fi key="${key#*= }" fi if [ -n "$key" ]; then key="${key%$'\r'}" [[ "${#key}" == "29" ]] && KEY="$key" fi fi [[ -n "$KEY" ]] && return 0 # 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 if [ "${#pid}" -ge 3 ]; then echo "${pid::-3}000" > "$setup" || : fi return 0 } writeCommand() { local install="$1" [ -f "$install" ] || return 0 if enabled "${LOG:-}"; then printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" cmd.exe /D /C \\\"\\\"C:\\\\OEM\\\\install.bat\\\" > \\\"C:\\\\OEM\\\\install.log\\\" 2>&1\\\"\"" else printf '%s' "\"Script\"=\"cmd /C start \\\"Install\\\" cmd.exe /D /C \\\"\\\"C:\\\\OEM\\\\install.bat\\\"\\\"\"" fi return 0 } writeSIF() { local target="$1" local driver="$2" local product="$3" local sifHost="$4" local sifUsername="$5" local sifPassword="$6" local sifOrganization="$7" local sifWorkgroup="$8" local localeID="$9" local inputLocaleID="${10}" local keyboardID="${11}" local timezone="${12}" local bitsPerPel=32 [[ "$driver" == "2k3" ]] && bitsPerPel=16 find "$target" -maxdepth 1 -type f -iname winnt.sif -delete || return 1 { printf '%s\n' \ '[Data]' \ ' AutoPartition=1' \ ' MsDosInitiated="0"' \ ' UnattendedInstall="Yes"' \ ' AutomaticUpdates="No"' \ '' \ '[Unattended]' \ ' UnattendSwitch=Yes' \ ' UnattendMode=FullUnattended' \ ' OemSkipEula=Yes' \ ' OemPreinstall=Yes' \ ' Repartition=Yes' \ ' WaitForReboot="No"' \ ' DriverSigningPolicy="Ignore"' \ ' NonDriverSigningPolicy="Ignore"' \ ' OemPnPDriversPath="Drivers\viostor;Drivers\NetKVM;Drivers\sata;Drivers\QXL;Drivers\QBochs;Drivers\Balloon"' \ ' NoWaitAfterTextMode=1' \ ' NoWaitAfterGUIMode=1' \ ' FileSystem=ConvertNTFS' \ ' ExtendOemPartition=0' \ ' Hibernation="No"' \ '' \ '[GuiUnattended]' \ ' OEMSkipRegional=1' \ ' OemSkipWelcome=1' \ " AdminPassword=\"$sifPassword\"" \ " TimeZone=$timezone" if disabled "$AUTOLOGIN"; then printf '%s\n' \ ' AutoLogon=No' else printf '%s\n' \ ' AutoLogon=Yes' \ ' AutoLogonCount=65432' fi printf '%s\n' \ '' \ '[RegionalSettings]' \ " InputLocale=$inputLocaleID:$keyboardID" \ " SystemLocale=$localeID" \ " UserLocale=$localeID" \ '' \ '[UserData]' \ " FullName=\"$sifUsername\"" \ " ComputerName=\"$sifHost\"" \ " OrgName=\"$sifOrganization\"" \ " $product" \ '' \ '[Identification]' \ " JoinWorkgroup = \"$sifWorkgroup\"" \ '' \ '[Display]' \ " BitsPerPel=$bitsPerPel" \ " XResolution=$WIDTH" \ " YResolution=$HEIGHT" \ '' \ '[Networking]' \ ' InstallDefaultComponents=Yes' \ '' \ '[Branding]' \ ' BrandIEUsingUnattended=Yes' \ '' \ '[URL]' \ ' Home_Page = http://www.google.com' \ ' Search_Page = http://www.google.com' \ '' \ '[TerminalServices]' \ ' AllowConnections=1' \ ' NoHelpPopup=1' \ '' } | unix2dos > "$target/WINNT.SIF" || return 1 if [[ "$driver" == "2k3" ]]; then { printf '%s\n' \ '[Components]' \ ' TerminalServer=On' \ '' \ '[LicenseFilePrintData]' \ ' AutoMode=PerServer' \ ' AutoUsers=5' \ '' } | unix2dos >> "$target/WINNT.SIF" || return 1 fi return 0 } writeRegistry() { local dir="$1" local shortcut="$2" local oem="$3" local regUsername="$4" local regPassword="$5" local driver="$6" local bitsPerPelHex="00000020" [[ "$driver" == "2k3" ]] && bitsPerPelHex="00000010" { printf '%s\n' \ 'Windows Registry Editor Version 5.00' \ '' \ '[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security]' \ '"FirstRunDisabled"=dword:00000001' \ '"UpdatesDisableNotify"=dword:00000001' \ '"FirewallDisableNotify"=dword:00000001' \ '"AntiVirusDisableNotify"=dword:00000001' \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\wscsvc]' \ '"Start"=dword:00000004' \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NetworkProvider]' \ '"RestoreConnection"=dword:00000000' \ '' \ '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU]' \ '"NoAutoUpdate"=dword:00000001' \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\GloballyOpenPorts\List]' \ '"3389:TCP"="3389:TCP:*:Enabled:@xpsp2res.dll,-22009"' \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\Tour]' \ '"RunCount"=dword:00000000' \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]' \ '"HideFileExt"=dword:00000000' \ '' \ '[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer]' \ '"NoWelcomeScreen"="1"' \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Internet Connection Wizard]' \ '"Completed"="1"' \ '"Desktopchanged"="1"' '' '[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]' if disabled "$AUTOLOGIN"; then printf '%s\n' '"AutoAdminLogon"="0"' else printf '%s\n' \ '"AutoAdminLogon"="1"' "\"DefaultUserName\"=\"$regUsername\"" "\"DefaultPassword\"=\"$regPassword\"" fi if enabled "$shortcut"; then printf '%s\n' \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\DOS Devices]' \ '"Z:"="\\Device\\LanmanRedirector\\;Z:00000000000003e7\\host.lan\\Data"' \ '' fi printf '%s\n' \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]' \ "\"DefaultSettings.BitsPerPel\"=dword:$bitsPerPelHex" \ "\"DefaultSettings.XResolution\"=dword:$XHEX" \ "\"DefaultSettings.YResolution\"=dword:$YHEX" \ '' \ '[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current\System\CurrentControlSet\Control\VIDEO\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]' \ "\"DefaultSettings.BitsPerPel\"=dword:$bitsPerPelHex" \ "\"DefaultSettings.XResolution\"=dword:$XHEX" \ "\"DefaultSettings.YResolution\"=dword:$YHEX" \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]' printf '%s\n' "$oem" '' } | unix2dos > "$dir/\$OEM\$/install.reg" || return 1 return 0 } appendRegistry() { local dir="$1" local driver="$2" if [[ "$driver" == "2k" || "$driver" == "xp" || "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Control Panel\Desktop]' \ '"SCRNSAVE.EXE"="off"' \ '"ScreenSaveActive"="0"' \ '"DragFullWindows"="1"' \ '"MenuShowDelay"="0"' if [[ "$driver" == "2k" ]]; then printf '%s\n' '"UserPreferencesMask"=hex:9c,32,00,80' else printf '%s\n' '"UserPreferencesMask"=hex:9c,32,07,80' fi printf '%s\n' \ '' \ '[HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics]' \ '"MinAnimate"="0"' \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]' \ '"link"=hex:00,00,00,00' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi if [[ "$driver" == "xp" || "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]' \ '"PowerPolicy"="Wscript.exe C:\\OEM\\NT5POWER.VBS"' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi if [[ "$driver" == "2k" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Control Panel\PowerCfg]' \ '"CurrentPowerPolicy"="3"' \ '' \ '[HKEY_CURRENT_USER\Control Panel\PowerCfg\PowerPolicies\3]' \ '"Policies"=hex:01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,04,00,00,00,04,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,01,64,64,64,64,00,00' \ '' \ '[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]' \ '"^SetupICWDesktop"=-' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi if [[ "$driver" == "2k" || "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]' \ '"NoActiveDesktop"=dword:00000001' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi if [[ "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows NT\Reliability]' \ '"ShutdownReasonOn"=dword:00000000' \ '' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\srvWiz]' \ '@=dword:00000000' \ '' \ '[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ServerOOBE\SecurityOOBE]' \ '"DontLaunchSecurityOOBE"=dword:00000000' \ '' \ '[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\R2Setup]' \ '"cd2chain"=dword:00000000' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi return 0 } writeVBS() { local dir="$1" local username="$2" local shortcut="$3" local driver="$4" local balloonExe="$dir/\$OEM\$/\$1/Drivers/Balloon/blnsvr.exe" local power="$dir/\$OEM\$/\$1/OEM/NT5POWER.VBS" # Locate the built-in Administrator by its RID 500 SID rather than its # localized display name, then rename that account to the requested username. { printf '%s\n' \ 'Set WshShell = WScript.CreateObject("WScript.Shell")' \ 'Set WshNetwork = WScript.CreateObject("WScript.Network")' \ 'Set Domain = GetObject("WinNT://" & WshNetwork.ComputerName)' \ '' \ 'Function DecodeSID(binSID)' \ ' ReDim o(LenB(binSID))' \ '' \ ' For i = 1 To LenB(binSID)' \ ' o(i-1) = AscB(MidB(binSID, i, 1))' \ ' Next' \ '' \ ' sid = "S-" & CStr(o(0)) & "-" & OctetArrayToString _' \ ' (Array(o(2), o(3), o(4), o(5), o(6), o(7)))' \ ' For i = 8 To (4 * o(1) + 4) Step 4' \ ' sid = sid & "-" & OctetArrayToString _' \ ' (Array(o(i+3), o(i+2), o(i+1), o(i)))' \ ' Next' \ '' \ ' DecodeSID = sid' \ 'End Function' \ '' \ 'Function OctetArrayToString(arr)' \ ' v = 0' \ ' For i = 0 To UBound(arr)' \ ' v = v * 256 + arr(i)' \ ' Next' \ '' \ ' OctetArrayToString = CStr(v)' \ 'End Function' \ '' \ 'For Each DomainItem in Domain' \ ' If DomainItem.Class = "User" Then' \ ' sid = DecodeSID(DomainItem.Get("objectSID"))' \ ' If Left(sid, 9) = "S-1-5-21-" And Right(sid, 4) = "-500" Then' \ ' LocalAdminADsPath = DomainItem.ADsPath' \ ' Exit For' \ ' End If' ' End If' 'Next' '' "Call Domain.MoveHere(LocalAdminADsPath, \"$username\")" '' if enabled "$shortcut"; then printf '%s\n' \ 'Set oLink = WshShell.CreateShortcut(WshShell.SpecialFolders("Desktop") & "\Shared.lnk")' \ 'With oLink' ' .TargetPath = "\\host.lan\Data"' ' .Save' 'End With' 'Set oLink = Nothing' '' fi if [ -f "$balloonExe" ]; then printf '%s\n' \ 'Result = WshShell.Run("sc.exe query BalloonService", 0, True)' \ 'If Result <> 0 Then' \ ' Result = WshShell.Run(Chr(34) & "C:\Drivers\Balloon\blnsvr.exe" & Chr(34) & " -i", 0, True)' \ 'End If' \ '' fi } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1 if [[ "$driver" == "xp" || "$driver" == "2k3" ]]; then mkdir -p "$(dirname "$power")" || return 1 { printf '%s\n' \ 'On Error Resume Next' \ 'Set Shell = WScript.CreateObject("WScript.Shell")' \ 'Set FSO = WScript.CreateObject("Scripting.FileSystemObject")' \ 'PowerCfg = Shell.ExpandEnvironmentStrings("%SystemRoot%\System32\POWERCFG.EXE")' \ '' \ 'Shell.RegWrite "HKCU\Control Panel\Desktop\SCRNSAVE.EXE", "off", "REG_SZ"' \ 'Shell.RegWrite "HKCU\Control Panel\Desktop\ScreenSaveActive", "0", "REG_SZ"' \ '' \ 'If FSO.FileExists(PowerCfg) Then' \ ' Policy = 3' \ ' Cmd = Chr(34) & PowerCfg & Chr(34) & " /CHANGE " & Policy & " /NUMERICAL "' \ ' For Each Setting In Array("/monitor-timeout-ac", "/monitor-timeout-dc", "/disk-timeout-ac", "/disk-timeout-dc", "/standby-timeout-ac", "/standby-timeout-dc")' \ ' Shell.Run Cmd & Setting & " 0", 0, True' \ ' Next' \ ' Shell.Run Chr(34) & PowerCfg & Chr(34) & " /SETACTIVE 3 /NUMERICAL", 0, True' \ 'End If' \ '' } | unix2dos > "$power" || return 1 else rm -f -- "$power" || return 1 fi { printf '%s\n' \ '[COMMANDS]' \ '"REGEDIT /s install.reg"' \ '"Wscript install.vbs"' \ '' } | unix2dos > "$dir/\$OEM\$/cmdlines.txt" || return 1 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 case "${value^^}" in "NONE" ) error "The USERNAME value \"NONE\" is reserved by Windows$suffix!" return 1 ;; "ADMINISTRATOR" | "GUEST" | "DEFAULTACCOUNT" | "WDAGUTILITYACCOUNT" | "WSIACCOUNT" ) error "The USERNAME value \"$value\" is reserved for a built-in Windows account$suffix!" return 1 ;; esac return 0 } validateLegacyEncoding() { local name="$1" local value="$2" local desc="${3:-}" local suffix="" [ -n "$desc" ] && suffix=" for $desc" if LC_ALL=C grep -q '[^ -~]' <<< "$value"; then error "The $name variable may only contain printable ASCII characters$suffix!" return 1 fi return 0 } getInputLocaleID() { local input="${1//_/-}" local result input="${input,,}" # NT5 InputLocale stores the input-language ID separately from the keyboard # layout ID. For a raw KLID, its low word is the corresponding language ID. if [[ "$input" =~ ^keyboard[-_]([0-9a-f]{8})$ ]]; then result="${BASH_REMATCH[1]:4:4}" printf '%s\n' "${result^^}" return 0 fi if [[ "$input" =~ ^([0-9a-f]{8})$ ]]; then result="${BASH_REMATCH[1]:4:4}" printf '%s\n' "${result^^}" return 0 fi getLocaleID "$input" return $? } return 0