Make build-windows-base.sh self-contained and produce a clean base image
The script no longer needs the parent repository or SSH: it builds an answer ISO from windows11/seed/Autounattend.xml, installs Windows from the ISO in QEMU (hardware in windows11/qemu uses only devices with inbox Windows drivers), and the answer file generalizes the image with sysprep at the end. The base holds only Windows: no account (the built-in Administrator is disabled), no SSH, no settings, no VirtIO drivers, no machine identity and no cached answer files. VMs are configured on first boot from an Unattend.xml on an attached CD-ROM. Add --window and --from-img; press a key at the ISO boot prompt with the z key, which neither UEFI nor Windows Setup ignores or acts on. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1dc40655df
commit
cb01d2c120
7 files changed
+525
-150
No files matched your search
@@ -112,19 +112,28 @@ otherwise `udisksctl`, otherwise `7z` extracts it into a temporary directory
|
|||||||
|
|
||||||
## Windows base image
|
## Windows base image
|
||||||
|
|
||||||
`run-windows11.py` creates a missing system disk as a QCOW2 overlay on the base
|
A VM disk can be created as a QCOW2 overlay on a Windows base image, so a new VM
|
||||||
image that `./download-images.sh --print-path windows11` prints, so a new VM does
|
does not run Windows Setup again. `build-windows-base.sh` makes that base image
|
||||||
not run Windows Setup again. `build-windows-base.sh` makes that base image: it
|
(by default at the path `./download-images.sh --print-path windows11` prints): it
|
||||||
installs Windows unattended from the ISO in `install-iso/`, generalizes it with
|
installs Windows unattended from the ISO in `install-iso/` (it does everything
|
||||||
`sysprep` over SSH, and stores the shut-down disk read-only.
|
itself: answer file in `windows11/seed/`, QEMU hardware in `windows11/qemu/`; this
|
||||||
|
directory has no dependency on any other repository). The answer file logs on once
|
||||||
|
with the built-in Administrator, which it disables again, and runs
|
||||||
|
`sysprep /generalize` as its last step; the script waits for the guest to power
|
||||||
|
off and stores the disk read-only. The image is clean and generic, like a cloud
|
||||||
|
image: nothing but Windows, with no account, no SSH, no settings, no VirtIO or
|
||||||
|
other hardware-specific drivers and no machine identity. Each VM is configured
|
||||||
|
on its first boot: Windows reads `Unattend.xml` from the root of an attached
|
||||||
|
CD-ROM during its specialize and OOBE passes (a VM launcher can mount such a
|
||||||
|
seed ISO when it creates a VM disk).
|
||||||
|
It needs `qemu`, `qemu-img`, an ISO tool (`xorriso`, `genisoimage` or `mkisofs`)
|
||||||
|
and `socat` or `nc`; there is no SSH to the guest.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build-windows-base.sh [--force] [--no-sysprep] [--compress] [OUTPUT.qcow2]
|
./build-windows-base.sh [--force] [--compress] [--from-img] [--window] [OUTPUT.qcow2]
|
||||||
```
|
```
|
||||||
|
|
||||||
It refuses to replace an existing base image without `--force`, because the
|
It refuses to replace an existing base image without `--force`, because the
|
||||||
overlays of existing VMs still point at that file. Build to another `OUTPUT`
|
overlays of existing VMs still point at that file. Build to another `OUTPUT`
|
||||||
(for example with a version in its name) and select it with
|
(for example with a version in its name) and point your VM launcher at it. The installation runs in a scratch directory
|
||||||
`WIN11_VM_BASE_IMAGE` instead. The installation runs in a scratch directory
|
`/var/tmp/build-windows-base.*` (set `BASE_BUILD_TMPDIR` to use another parent; about 25 GB are needed).
|
||||||
`/var/tmp/build-windows-base.*` (set `BASE_BUILD_TMPDIR` to use another parent; about 25 GB are needed) and needs a private key that matches
|
|
||||||
the public key put on the answer media.
|
|
||||||
+205
-140
@@ -1,60 +1,67 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Build the Windows 11 base QCOW2 image that run-windows11.py clones.
|
# Build a clean, generic Windows 11 base QCOW2 image.
|
||||||
#
|
#
|
||||||
# run-windows11.py creates a missing system disk as a QCOW2 overlay on this
|
# A VM disk can then be created as a QCOW2 overlay on this image, so a new VM
|
||||||
# base image, so a new VM starts in seconds instead of running Windows Setup
|
# starts in seconds instead of running Windows Setup again. The base image is
|
||||||
# again. The base image is made once:
|
# made once, and this script needs nothing outside this directory:
|
||||||
# 1. run-windows11.py --install-from-iso installs Windows unattended into a
|
# 1. an answer ISO is built from windows11/seed/Autounattend.xml;
|
||||||
# scratch state directory (Autounattend.xml, Configure-Windows.ps1: VirtIO
|
# 2. QEMU installs Windows unattended from the ISO into a scratch directory.
|
||||||
# drivers, OpenSSH, the administrator key from build-seed.sh);
|
# After the first logon, the answer file runs sysprep /generalize, which
|
||||||
# 2. once SSH answers, this script runs sysprep /generalize over SSH, which
|
|
||||||
# removes the machine identity and shuts the guest down;
|
# removes the machine identity and shuts the guest down;
|
||||||
# 3. the shut-down disk is converted into the output file and made read-only.
|
# 3. the shut-down disk is converted into the output file and made read-only.
|
||||||
# The first boot of every clone then runs the specialize and OOBE passes of the
|
# The image holds nothing but Windows itself, like a cloud image: no VirtIO or
|
||||||
# answer file below (host name, account, automatic logon) without any prompt.
|
# other hardware-specific drivers (the installation hardware, windows11/qemu,
|
||||||
|
# only uses devices with inbox Windows drivers), no machine identity, no user
|
||||||
|
# account (the built-in Administrator is disabled), no SSH, no settings. Every
|
||||||
|
# VM is configured on its first boot: Windows looks for an Unattend.xml in the
|
||||||
|
# root of an attached CD-ROM during its specialize and OOBE passes, and so
|
||||||
|
# takes the host name, account, auto logon, SSH and so on from a seed ISO.
|
||||||
#
|
#
|
||||||
# Needs the Windows ISO in install-iso/ (see README.md), qemu-img, ssh, scp and
|
# Needs qemu (qemu-system-x86_64 or qemu-system-aarch64), qemu-img, an ISO tool
|
||||||
# a private key that matches the public key put on the answer media (the
|
# (xorriso, genisoimage or mkisofs), socat or nc (to press a key at the boot
|
||||||
# default ~/.ssh keys, or SSH_PUBLIC_KEY_FILE for the public half and
|
# prompt of the ISO), the UEFI firmware for QEMU and the Windows ISO in
|
||||||
# SSH_PRIVATE_KEY_FILE for this script).
|
# install-iso/ (see README.md).
|
||||||
#
|
#
|
||||||
# Usage: build-windows-base.sh [--force] [--no-sysprep] [--keep-work] [--compress]
|
# Usage: build-windows-base.sh [--force] [--keep-work] [--compress] [--from-img]
|
||||||
# [--window] [--ssh-port PORT] [OUTPUT.qcow2]
|
# [--window] [OUTPUT.qcow2]
|
||||||
# OUTPUT defaults to the path that "download-images.sh --print-path windows11"
|
# OUTPUT defaults to the path that "download-images.sh --print-path windows11"
|
||||||
# prints, which is where run-windows11.py looks for the base image.
|
# prints.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
images_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
repo_dir="$(cd -- "${images_dir}/.." && pwd)"
|
|
||||||
launcher="${repo_dir}/run-windows11.py"
|
|
||||||
downloader="${images_dir}/download-images.sh"
|
downloader="${images_dir}/download-images.sh"
|
||||||
|
win_dir="${images_dir}/windows11"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: $(basename "$0") [--force] [--no-sysprep] [--keep-work] [--compress]
|
Usage: $(basename "$0") [--force] [--keep-work] [--compress] [--from-img]
|
||||||
[--window] [--ssh-port PORT] [OUTPUT.qcow2]
|
[--window] [OUTPUT.qcow2]
|
||||||
|
|
||||||
Install Windows 11 from the ISO, generalize it with sysprep and store the disk
|
Install Windows 11 from the ISO, generalize it with sysprep and store the disk
|
||||||
as the read-only base image that run-windows11.py clones.
|
as the read-only, generic base image that VM disks are cloned from.
|
||||||
|
|
||||||
--force Replace OUTPUT if it exists
|
--force Replace OUTPUT if it exists
|
||||||
--no-sysprep Skip sysprep and only shut the guest down; clones then share
|
|
||||||
the machine SID and host name (fine for stand-alone VMs)
|
|
||||||
--keep-work Keep the scratch state directory (also kept on failure)
|
--keep-work Keep the scratch state directory (also kept on failure)
|
||||||
--compress Compress the output (smaller, but clones read it slower)
|
--compress Compress the output (smaller, but clones read it slower)
|
||||||
|
--from-img Install from the disk image made by build-windows-install-disk.sh
|
||||||
|
(the ISO path ending in .img), which reads much faster than
|
||||||
|
the emulated CD-ROM
|
||||||
--window Show the guest in a QEMU window (needs a desktop session)
|
--window Show the guest in a QEMU window (needs a desktop session)
|
||||||
instead of the VNC display on 127.0.0.1:5990
|
instead of the VNC display on 127.0.0.1:5990
|
||||||
--ssh-port PORT Host port forwarded to the guest's SSH during the build
|
|
||||||
(default: 2299; overrides BASE_BUILD_SSH_PORT)
|
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
|
|
||||||
Environment variables:
|
Environment variables:
|
||||||
SSH_PRIVATE_KEY_FILE Private key to log in with (default: ssh's own choice)
|
|
||||||
SSH_PUBLIC_KEY_FILE Public key put on the answer media (see build-seed.sh)
|
|
||||||
VM_IMAGE_ARCH amd64 or arm64 (default: the host's)
|
VM_IMAGE_ARCH amd64 or arm64 (default: the host's)
|
||||||
|
WIN11_VM_INSTALL_ISO Windows 11 ISO (default: the path that
|
||||||
|
"download-images.sh --print-path windows11 --iso" prints)
|
||||||
|
WIN11_VM_DISK_SIZE Size of the system disk (default: 64G)
|
||||||
|
WIN11_VM_UEFI_CODE OVMF read-only firmware (auto-detected if unset)
|
||||||
|
WIN11_VM_UEFI_VARS OVMF variable-store template (auto-detected if unset)
|
||||||
|
WIN11_VM_DISPLAY QEMU display with --window (default: cocoa on macOS,
|
||||||
|
gtk elsewhere)
|
||||||
BASE_BUILD_TMPDIR Parent of the scratch state directory, which needs about
|
BASE_BUILD_TMPDIR Parent of the scratch state directory, which needs about
|
||||||
25 GB (default: /var/tmp)
|
25 GB (default: /var/tmp)
|
||||||
BASE_BUILD_TIMEOUT Seconds to wait for the installation (default: 5400)
|
BASE_BUILD_TIMEOUT Seconds to wait for installation and sysprep (default: 5400)
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,47 +71,91 @@ die() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
force=0
|
force=0
|
||||||
sysprep=1
|
|
||||||
keep_work=0
|
keep_work=0
|
||||||
compress=0
|
compress=0
|
||||||
|
from_img=0
|
||||||
window=0
|
window=0
|
||||||
ssh_port="${BASE_BUILD_SSH_PORT:-2299}"
|
|
||||||
output=""
|
output=""
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
--force) force=1 ;;
|
--force) force=1 ;;
|
||||||
--no-sysprep) sysprep=0 ;;
|
|
||||||
--keep-work) keep_work=1 ;;
|
--keep-work) keep_work=1 ;;
|
||||||
--compress) compress=1 ;;
|
--compress) compress=1 ;;
|
||||||
|
--from-img) from_img=1 ;;
|
||||||
--window) window=1 ;;
|
--window) window=1 ;;
|
||||||
--ssh-port) shift; ssh_port="${1:?--ssh-port needs a value}" ;;
|
|
||||||
--ssh-port=*) ssh_port="${1#*=}" ;;
|
|
||||||
-*) usage >&2; exit 2 ;;
|
-*) usage >&2; exit 2 ;;
|
||||||
*) [[ -z "$output" ]] || { usage >&2; exit 2; }; output="$1" ;;
|
*) [[ -z "$output" ]] || { usage >&2; exit 2; }; output="$1" ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
[[ "$ssh_port" =~ ^[0-9]+$ ]] || die "--ssh-port must be a number: ${ssh_port}"
|
|
||||||
|
|
||||||
for tool in qemu-img ssh scp python3; do
|
|
||||||
command -v "$tool" >/dev/null || die "${tool} is not installed."
|
|
||||||
done
|
|
||||||
[[ -x "$launcher" ]] || die "run-windows11.py was not found: ${launcher}"
|
|
||||||
[[ -x "$downloader" ]] || die "download-images.sh was not found: ${downloader}"
|
|
||||||
|
|
||||||
output="${output:-$("$downloader" --print-path windows11)}"
|
|
||||||
if [[ -e "$output" && $force -eq 0 ]]; then
|
|
||||||
die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT."
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "${VM_IMAGE_ARCH:-$(uname -m)}" in
|
case "${VM_IMAGE_ARCH:-$(uname -m)}" in
|
||||||
arm64|aarch64) arch=arm64 ;;
|
arm64|aarch64) arch=arm64 ;;
|
||||||
x86_64|amd64) arch=amd64 ;;
|
x86_64|amd64) arch=amd64 ;;
|
||||||
*) die "unsupported architecture: ${VM_IMAGE_ARCH:-$(uname -m)}" ;;
|
*) die "unsupported architecture: ${VM_IMAGE_ARCH:-$(uname -m)}" ;;
|
||||||
esac
|
esac
|
||||||
|
export VM_IMAGE_ARCH="$arch"
|
||||||
|
host_os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||||
|
[[ "$host_os" != darwin || "$arch" == arm64 ]] \
|
||||||
|
|| die "amd64 guests are not supported on macOS hosts; use an arm64 image"
|
||||||
|
if [[ "$arch" == arm64 ]]; then qemu_binary=qemu-system-aarch64; else qemu_binary=qemu-system-x86_64; fi
|
||||||
|
|
||||||
total_steps=5
|
for tool in "$qemu_binary" qemu-img; do
|
||||||
|
command -v "$tool" >/dev/null || die "${tool} is not installed."
|
||||||
|
done
|
||||||
|
[[ -x "$downloader" ]] || die "download-images.sh was not found: ${downloader}"
|
||||||
|
iso_tool=""
|
||||||
|
for tool in xorriso genisoimage mkisofs; do
|
||||||
|
command -v "$tool" >/dev/null && { iso_tool="$tool"; break; }
|
||||||
|
done
|
||||||
|
[[ -n "$iso_tool" ]] || die "install xorriso, genisoimage, or mkisofs."
|
||||||
|
if ((!from_img)); then
|
||||||
|
command -v socat >/dev/null || command -v nc >/dev/null \
|
||||||
|
|| die "install socat or nc (it presses a key at the boot prompt of the ISO)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
output="${output:-$("$downloader" --print-path windows11)}"
|
||||||
|
if [[ -e "$output" && $force -eq 0 ]]; then
|
||||||
|
die "${output} exists (it may be the backing file of existing VM disks); use --force or another OUTPUT."
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_iso="${WIN11_VM_INSTALL_ISO:-$("$downloader" --print-path windows11 --iso)}"
|
||||||
|
((!from_img)) || install_iso="${install_iso%.*}.img"
|
||||||
|
[[ -f "$install_iso" ]] || die "the Windows 11 installation medium was not found: ${install_iso} (see README.md)."
|
||||||
|
|
||||||
|
pick_firmware() {
|
||||||
|
local override="$1" candidate
|
||||||
|
shift
|
||||||
|
if [[ -n "$override" ]]; then printf '%s\n' "$override"; return; fi
|
||||||
|
for candidate in "$@"; do
|
||||||
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return; }
|
||||||
|
done
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
}
|
||||||
|
if [[ "$arch" == arm64 ]]; then
|
||||||
|
uefi_code="$(pick_firmware "${WIN11_VM_UEFI_CODE:-}" \
|
||||||
|
/usr/share/AAVMF/AAVMF_CODE.fd /usr/share/edk2/aarch64/QEMU_EFI.fd \
|
||||||
|
/opt/homebrew/share/qemu/edk2-aarch64-code.fd /usr/local/share/qemu/edk2-aarch64-code.fd)"
|
||||||
|
uefi_vars_template="$(pick_firmware "${WIN11_VM_UEFI_VARS:-}" \
|
||||||
|
/usr/share/AAVMF/AAVMF_VARS.fd /usr/share/edk2/aarch64/vars-template-pflash.raw \
|
||||||
|
/opt/homebrew/share/qemu/edk2-arm-vars.fd /usr/local/share/qemu/edk2-arm-vars.fd)"
|
||||||
|
else
|
||||||
|
uefi_code="$(pick_firmware "${WIN11_VM_UEFI_CODE:-}" \
|
||||||
|
/usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/edk2/ovmf/OVMF_CODE.fd \
|
||||||
|
/opt/homebrew/share/qemu/edk2-x86_64-code.fd /usr/local/share/qemu/edk2-x86_64-code.fd)"
|
||||||
|
uefi_vars_template="$(pick_firmware "${WIN11_VM_UEFI_VARS:-}" \
|
||||||
|
/usr/share/OVMF/OVMF_VARS_4M.fd /usr/share/edk2/ovmf/OVMF_VARS.fd \
|
||||||
|
/opt/homebrew/share/qemu/edk2-i386-vars.fd /usr/local/share/qemu/edk2-i386-vars.fd)"
|
||||||
|
fi
|
||||||
|
config_template="${win_dir}/qemu/config.ini"
|
||||||
|
platform_config="${win_dir}/qemu/${host_os}-${arch}.ini"
|
||||||
|
for file in "$uefi_code" "$uefi_vars_template" "$config_template" "$platform_config" \
|
||||||
|
"${win_dir}/seed/Autounattend.xml"; do
|
||||||
|
[[ -f "$file" ]] || die "required file was not found: ${file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
total_steps=3
|
||||||
step_number=0
|
step_number=0
|
||||||
step() {
|
step() {
|
||||||
step_number=$((step_number + 1))
|
step_number=$((step_number + 1))
|
||||||
@@ -117,13 +168,9 @@ note() {
|
|||||||
mkdir -p "${BASE_BUILD_TMPDIR:-/var/tmp}"
|
mkdir -p "${BASE_BUILD_TMPDIR:-/var/tmp}"
|
||||||
work="$(mktemp -d "${BASE_BUILD_TMPDIR:-/var/tmp}/build-windows-base.XXXXXX")"
|
work="$(mktemp -d "${BASE_BUILD_TMPDIR:-/var/tmp}/build-windows-base.XXXXXX")"
|
||||||
pid_file="${work}/qemu.pid"
|
pid_file="${work}/qemu.pid"
|
||||||
|
monitor_socket="${work}/qemu-monitor.sock"
|
||||||
succeeded=0
|
succeeded=0
|
||||||
|
|
||||||
ssh_options=(-o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
|
|
||||||
-o LogLevel=ERROR -o ConnectTimeout=5)
|
|
||||||
[[ -z "${SSH_PRIVATE_KEY_FILE:-}" ]] || ssh_options+=(-i "$SSH_PRIVATE_KEY_FILE")
|
|
||||||
guest_ssh() { ssh -n "${ssh_options[@]}" -p "$ssh_port" guofang@127.0.0.1 "$@"; }
|
|
||||||
|
|
||||||
vm_running() {
|
vm_running() {
|
||||||
[[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null
|
[[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null
|
||||||
}
|
}
|
||||||
@@ -151,104 +198,122 @@ wait_for() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only the SSH rule is forwarded so that a running VM's RDP/DSH ports do not clash.
|
# --- 1. The answer media -----------------------------------------------------
|
||||||
printf 'SSH %s 22\n' "$ssh_port" >"${work}/forwards.conf"
|
step "Building the answer ISO"
|
||||||
|
# The answer file's components name the processor architecture, so render a
|
||||||
|
# copy for the target architecture.
|
||||||
|
seed_dir="${work}/seed"
|
||||||
|
mkdir -p "$seed_dir"
|
||||||
|
sed "s/processorArchitecture=\"amd64\"/processorArchitecture=\"${arch}\"/" \
|
||||||
|
"${win_dir}/seed/Autounattend.xml" >"${seed_dir}/Autounattend.xml"
|
||||||
|
seed_iso="${work}/seed.iso"
|
||||||
|
case "$iso_tool" in
|
||||||
|
xorriso) xorriso -as mkisofs -quiet -o "$seed_iso" -V ANSWER -J -r "$seed_dir" ;;
|
||||||
|
*) "$iso_tool" -quiet -o "$seed_iso" -V ANSWER -J -r "$seed_dir" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- 2. The installation -----------------------------------------------------
|
||||||
|
step "Installing and generalizing Windows 11 (unattended; takes a while)"
|
||||||
|
disk_image="${work}/disk.qcow2"
|
||||||
|
uefi_vars="${work}/OVMF_VARS.fd"
|
||||||
|
qemu_config="${work}/qemu.ini"
|
||||||
|
cp -- "$uefi_vars_template" "$uefi_vars"
|
||||||
|
qemu-img create -q -f qcow2 "$disk_image" "${WIN11_VM_DISK_SIZE:-64G}"
|
||||||
|
|
||||||
|
# Fill in the file path placeholders of the config template. QEMU's config file
|
||||||
|
# parser has no escape syntax for quoted values.
|
||||||
|
config_text="$(<"$config_template")"
|
||||||
|
for placeholder in uefi_code uefi_vars disk_image monitor_socket; do
|
||||||
|
value="${!placeholder}"
|
||||||
|
[[ "$value" != *\"* ]] || die "${placeholder} must not contain double quotes: ${value}"
|
||||||
|
config_text="${config_text//"\${${placeholder}}"/"$value"}"
|
||||||
|
done
|
||||||
|
printf '%s\n' "$config_text" >"$qemu_config"
|
||||||
|
|
||||||
step "Installing Windows 11 from the ISO (unattended; takes a while)"
|
|
||||||
display_option=(--vnc=127.0.0.1:90)
|
|
||||||
if ((window)); then
|
if ((window)); then
|
||||||
[[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" || "$(uname -s)" == Darwin ]] \
|
[[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" || "$host_os" == darwin ]] \
|
||||||
|| die "--window needs a desktop session (DISPLAY or WAYLAND_DISPLAY is not set)."
|
|| die "--window needs a desktop session (DISPLAY or WAYLAND_DISPLAY is not set)."
|
||||||
display_option=()
|
if [[ -n "${WIN11_VM_DISPLAY:-}" ]]; then display="$WIN11_VM_DISPLAY"
|
||||||
|
elif [[ "$host_os" == darwin ]]; then display=cocoa
|
||||||
|
else display=gtk; fi
|
||||||
|
else
|
||||||
|
display="vnc=127.0.0.1:90"
|
||||||
fi
|
fi
|
||||||
WIN11_VM_FORWARDS="${work}/forwards.conf" "$launcher" \
|
|
||||||
--daemon "${display_option[@]}" --install-from-iso --arch "$arch" --state-dir "$work"
|
|
||||||
|
|
||||||
step "Waiting for the guest to finish its first-logon setup (SSH answers)"
|
qemu_args=(
|
||||||
|
-readconfig "$qemu_config" -readconfig "$platform_config"
|
||||||
|
# "max" is the host CPU under KVM/HVF and the fullest model under TCG.
|
||||||
|
-cpu max
|
||||||
|
# The network that the NIC in the config file uses is defined here; -nic
|
||||||
|
# none stops QEMU from adding a default NIC. Nothing is forwarded.
|
||||||
|
-netdev user,id=net0
|
||||||
|
-nic none
|
||||||
|
-display "$display"
|
||||||
|
-serial "file:${work}/console.log" -daemonize -pidfile "$pid_file"
|
||||||
|
)
|
||||||
|
if [[ "$host_os" == darwin && "$arch" == arm64 ]] && ((!from_img)); then
|
||||||
|
# With the HVF in-kernel GICv3, QEMU's usb-storage races on the status packet
|
||||||
|
# and Windows Setup stalls 10 s after every burst of reads (utmapp/UTM#7970),
|
||||||
|
# so use the emulated interrupt controller. The NVMe install disk of
|
||||||
|
# --from-img does not use usb-storage, and hangs with the emulated controller.
|
||||||
|
qemu_args+=(-machine kernel-irqchip=off)
|
||||||
|
fi
|
||||||
|
# q35 has IDE for the installation media; "virt" does not, so use USB.
|
||||||
|
attach_media() {
|
||||||
|
local id="$1" file="$2" boot_index="$3" ide_bus="$4"
|
||||||
|
qemu_args+=(-drive "file=${file},if=none,id=${id},media=cdrom,readonly=on")
|
||||||
|
if [[ "$arch" == amd64 ]]; then
|
||||||
|
qemu_args+=(-device "ide-cd,drive=${id},bus=${ide_bus},bootindex=${boot_index}")
|
||||||
|
else
|
||||||
|
qemu_args+=(-device "usb-storage,drive=${id},bootindex=${boot_index}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if ((from_img)); then
|
||||||
|
# snapshot=on: NVMe cannot report write protection, and Windows writes to the
|
||||||
|
# volume it mounts, so a read-only drive makes it fail. The writes go to a
|
||||||
|
# temporary overlay and the image stays unchanged.
|
||||||
|
qemu_args+=(-drive "file=${install_iso},if=none,id=install_media,format=raw,snapshot=on"
|
||||||
|
-device "nvme,drive=install_media,serial=installer,bootindex=2")
|
||||||
|
else
|
||||||
|
attach_media install_media "$install_iso" 2 ide.1
|
||||||
|
fi
|
||||||
|
attach_media answer_media "$seed_iso" 3 ide.2
|
||||||
|
|
||||||
|
# The Windows ISO boots through bootmgr, which waits for a key ("Press any key
|
||||||
|
# to boot from CD or DVD...") and gives up after a few seconds. Nobody is there
|
||||||
|
# to press it, so a background loop taps the "z" key through the QEMU monitor
|
||||||
|
# for the first 30 s. The key has to produce a character (UEFI ignores a lone
|
||||||
|
# Shift) and Windows Setup starts within that time, so it must not be one that
|
||||||
|
# Setup acts on: Space and Enter press its focused Cancel button, and Y, N and
|
||||||
|
# C are accelerators of its buttons.
|
||||||
|
press_key() {
|
||||||
|
if command -v socat >/dev/null; then
|
||||||
|
printf 'sendkey z\n' | socat -t1 - "UNIX-CONNECT:${monitor_socket}"
|
||||||
|
else
|
||||||
|
printf 'sendkey z\n' | nc -U -w1 "$monitor_socket"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
"$qemu_binary" "${qemu_args[@]}"
|
||||||
|
if ((!from_img)); then
|
||||||
|
(
|
||||||
|
deadline=$((SECONDS + 30))
|
||||||
|
while ((SECONDS < deadline)); do
|
||||||
|
[[ -S "$monitor_socket" ]] && press_key >/dev/null 2>&1 || true
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- 3. Wait for the guest to power off -------------------------------------
|
||||||
if ((window)); then
|
if ((window)); then
|
||||||
note "Screen: QEMU window; console log: ${work}/console.log"
|
note "Screen: QEMU window; console log: ${work}/console.log"
|
||||||
else
|
else
|
||||||
note "Screen: VNC 127.0.0.1:5990; console log: ${work}/console.log"
|
note "Screen: VNC 127.0.0.1:5990; console log: ${work}/console.log"
|
||||||
fi
|
fi
|
||||||
if ! wait_for "${BASE_BUILD_TIMEOUT:-5400}" guest_ssh 'echo ready' >/dev/null 2>&1; then
|
guest_off() { ! vm_running; }
|
||||||
die "SSH did not answer in time; inspect the guest over VNC."
|
if ! wait_for "${BASE_BUILD_TIMEOUT:-5400}" guest_off; then
|
||||||
fi
|
die "the guest did not power off in time; look at its screen. If sysprep failed, its log is C:\\Windows\\System32\\Sysprep\\Panther\\setuperr.log in the guest."
|
||||||
|
|
||||||
# The answer file the clones use on their first boot. generalize removes the
|
|
||||||
# machine identity but keeps the guofang account, so OOBE only needs the
|
|
||||||
# language, the EULA and the automatic logon; the specialize pass restores the
|
|
||||||
# settings Autounattend.xml applies (host name, time zone, Remote Desktop).
|
|
||||||
write_sysprep_answer() {
|
|
||||||
cat >"$1" <<EOF
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
|
||||||
<settings pass="specialize">
|
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="${arch}"
|
|
||||||
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
|
||||||
<ComputerName>WIN11</ComputerName>
|
|
||||||
<TimeZone>China Standard Time</TimeZone>
|
|
||||||
</component>
|
|
||||||
<component name="Microsoft-Windows-TerminalServices-LocalSessionManager"
|
|
||||||
processorArchitecture="${arch}" publicKeyToken="31bf3856ad364e35" language="neutral"
|
|
||||||
versionScope="nonSxS">
|
|
||||||
<fDenyTSConnections>false</fDenyTSConnections>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
<settings pass="oobeSystem">
|
|
||||||
<component name="Microsoft-Windows-International-Core" processorArchitecture="${arch}"
|
|
||||||
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
|
||||||
<InputLocale>en-US</InputLocale>
|
|
||||||
<SystemLocale>en-US</SystemLocale>
|
|
||||||
<UILanguage>en-US</UILanguage>
|
|
||||||
<UserLocale>en-US</UserLocale>
|
|
||||||
</component>
|
|
||||||
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="${arch}"
|
|
||||||
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
|
||||||
<OOBE>
|
|
||||||
<HideEULAPage>true</HideEULAPage>
|
|
||||||
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
|
||||||
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
|
||||||
<SkipMachineOOBE>true</SkipMachineOOBE>
|
|
||||||
<SkipUserOOBE>true</SkipUserOOBE>
|
|
||||||
<ProtectYourPC>3</ProtectYourPC>
|
|
||||||
</OOBE>
|
|
||||||
<AutoLogon>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<Username>guofang</Username>
|
|
||||||
<Password>
|
|
||||||
<Value>wang</Value>
|
|
||||||
<PlainText>true</PlainText>
|
|
||||||
</Password>
|
|
||||||
</AutoLogon>
|
|
||||||
</component>
|
|
||||||
</settings>
|
|
||||||
</unattend>
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((sysprep)); then
|
|
||||||
step "Generalizing the guest with sysprep"
|
|
||||||
write_sysprep_answer "${work}/sysprep-unattend.xml"
|
|
||||||
scp "${ssh_options[@]}" -P "$ssh_port" "${work}/sysprep-unattend.xml" \
|
|
||||||
guofang@127.0.0.1:C:/Windows/Temp/sysprep-unattend.xml
|
|
||||||
# The connection drops when the guest shuts down, which ssh reports as 255.
|
|
||||||
status=0
|
|
||||||
guest_ssh 'C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown /quiet /unattend:C:\Windows\Temp\sysprep-unattend.xml' || status=$?
|
|
||||||
if ((status != 0 && status != 255)); then
|
|
||||||
guest_ssh 'type C:\Windows\System32\Sysprep\Panther\setuperr.log' >&2 || true
|
|
||||||
die "sysprep failed (exit code ${status}); the log is above."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
step "Shutting the guest down (sysprep skipped)"
|
|
||||||
guest_ssh 'shutdown /s /t 5' || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
step "Waiting for the guest to power off"
|
|
||||||
if ! wait_for 900 bash -c '! kill -0 "$(cat "$1")" 2>/dev/null' _ "$pid_file"; then
|
|
||||||
if ((sysprep)); then
|
|
||||||
guest_ssh 'type C:\Windows\System32\Sysprep\Panther\setuperr.log' >&2 || true
|
|
||||||
fi
|
|
||||||
die "the guest did not power off; sysprep probably failed (log above, if SSH still answers)."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
step "Converting the disk to ${output}"
|
step "Converting the disk to ${output}"
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# QEMU configuration for the Windows 11 virtual machine. The accelerators and machine type
|
||||||
|
# are in <os>-<arch>.ini (os is linux or darwin), which QEMU reads after
|
||||||
|
# this file.
|
||||||
|
#
|
||||||
|
# build-windows-base.sh fills in the file path placeholders below, writes the result
|
||||||
|
# to <state dir>/qemu.ini and passes it to QEMU with -readconfig. Options that
|
||||||
|
# have no -readconfig equivalent (-cpu, -display), the network and the installation
|
||||||
|
# media are still added by the script.
|
||||||
|
#
|
||||||
|
# Every device here has an inbox Windows driver: the base image made from this
|
||||||
|
# installation contains no VirtIO drivers.
|
||||||
|
|
||||||
|
[name]
|
||||||
|
guest = "windows11"
|
||||||
|
|
||||||
|
[memory]
|
||||||
|
size = "8G"
|
||||||
|
|
||||||
|
[smp-opts]
|
||||||
|
cpus = "8"
|
||||||
|
|
||||||
|
# UEFI firmware: read-only code and the per-VM variable store.
|
||||||
|
[drive "pflash0"]
|
||||||
|
if = "pflash"
|
||||||
|
format = "raw"
|
||||||
|
readonly = "on"
|
||||||
|
file = "${uefi_code}"
|
||||||
|
|
||||||
|
[drive "pflash1"]
|
||||||
|
if = "pflash"
|
||||||
|
format = "raw"
|
||||||
|
file = "${uefi_vars}"
|
||||||
|
|
||||||
|
# System disk image; the device that exposes it (and the NIC, which is
|
||||||
|
# attached to the user-mode network "net0" that the installer defines with
|
||||||
|
# -netdev) is in the architecture file.
|
||||||
|
[drive "system_disk"]
|
||||||
|
if = "none"
|
||||||
|
format = "qcow2"
|
||||||
|
file = "${disk_image}"
|
||||||
|
|
||||||
|
# Register the USB tablet after vdagent so QEMU selects it as the active pointer.
|
||||||
|
[device "usb_controller"]
|
||||||
|
driver = "qemu-xhci"
|
||||||
|
|
||||||
|
[device "tablet0"]
|
||||||
|
driver = "usb-tablet"
|
||||||
|
bus = "usb_controller.0"
|
||||||
|
|
||||||
|
# The QEMU monitor, on a socket in the state directory: build-windows-base.sh
|
||||||
|
# presses a key through it at the "Press any key to boot from CD or DVD" prompt.
|
||||||
|
[chardev "monitor"]
|
||||||
|
backend = "socket"
|
||||||
|
path = "${monitor_socket}"
|
||||||
|
server = "on"
|
||||||
|
wait = "off"
|
||||||
|
|
||||||
|
[mon "monitor0"]
|
||||||
|
chardev = "monitor"
|
||||||
|
mode = "readline"
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# macOS host, arm64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||||
|
|
||||||
|
# Accelerators are tried in order: HVF on macOS, else emulation.
|
||||||
|
[accel]
|
||||||
|
accel = "hvf"
|
||||||
|
|
||||||
|
[accel]
|
||||||
|
accel = "tcg"
|
||||||
|
|
||||||
|
# build-windows-base.sh adds kernel-irqchip=off unless installing with
|
||||||
|
# --from-img (see there).
|
||||||
|
[machine]
|
||||||
|
type = "virt"
|
||||||
|
|
||||||
|
# The "virt" machine has no IDE. NVMe and USB mass storage (used by the
|
||||||
|
# installation media) both have inbox Windows drivers.
|
||||||
|
[device "system_disk-dev"]
|
||||||
|
driver = "nvme"
|
||||||
|
drive = "system_disk"
|
||||||
|
serial = "win11disk"
|
||||||
|
bootindex = "1"
|
||||||
|
|
||||||
|
# Windows has no inbox driver for virtio-net or virtio-gpu, so the network card
|
||||||
|
# is an Intel e1000e and the display is ramfb (a fixed-size framebuffer).
|
||||||
|
[device "nic0"]
|
||||||
|
driver = "e1000e"
|
||||||
|
netdev = "net0"
|
||||||
|
|
||||||
|
[device "gpu0"]
|
||||||
|
driver = "ramfb"
|
||||||
|
|
||||||
|
[device "kbd0"]
|
||||||
|
driver = "usb-kbd"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Linux host, amd64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||||
|
|
||||||
|
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||||
|
[accel]
|
||||||
|
accel = "kvm"
|
||||||
|
|
||||||
|
[accel]
|
||||||
|
accel = "tcg"
|
||||||
|
|
||||||
|
[machine]
|
||||||
|
type = "q35"
|
||||||
|
|
||||||
|
# The system disk is attached through IDE so that Windows can boot before the
|
||||||
|
# VirtIO storage driver is installed; the installation media use ide.1 to ide.3.
|
||||||
|
[device "system_disk-dev"]
|
||||||
|
driver = "ide-hd"
|
||||||
|
drive = "system_disk"
|
||||||
|
bus = "ide.0"
|
||||||
|
bootindex = "1"
|
||||||
|
|
||||||
|
[device "nic0"]
|
||||||
|
driver = "e1000e"
|
||||||
|
netdev = "net0"
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Linux host, arm64 part of the Windows 11 QEMU configuration; read after config.ini.
|
||||||
|
|
||||||
|
# Accelerators are tried in order: KVM on Linux, else emulation.
|
||||||
|
[accel]
|
||||||
|
accel = "kvm"
|
||||||
|
|
||||||
|
[accel]
|
||||||
|
accel = "tcg"
|
||||||
|
|
||||||
|
[machine]
|
||||||
|
type = "virt"
|
||||||
|
|
||||||
|
# The "virt" machine has no IDE. NVMe and USB mass storage (used by the
|
||||||
|
# installation media) both have inbox Windows drivers.
|
||||||
|
[device "system_disk-dev"]
|
||||||
|
driver = "nvme"
|
||||||
|
drive = "system_disk"
|
||||||
|
serial = "win11disk"
|
||||||
|
bootindex = "1"
|
||||||
|
|
||||||
|
# Windows has no inbox driver for virtio-net or virtio-gpu, so the network card
|
||||||
|
# is an Intel e1000e and the display is ramfb (a fixed-size framebuffer).
|
||||||
|
[device "nic0"]
|
||||||
|
driver = "e1000e"
|
||||||
|
netdev = "net0"
|
||||||
|
|
||||||
|
[device "gpu0"]
|
||||||
|
driver = "ramfb"
|
||||||
|
|
||||||
|
[device "kbd0"]
|
||||||
|
driver = "usb-kbd"
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
|
<settings pass="windowsPE">
|
||||||
|
<component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
|
<SetupUILanguage>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
</SetupUILanguage>
|
||||||
|
<InputLocale>en-US</InputLocale>
|
||||||
|
<SystemLocale>en-US</SystemLocale>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
<UserLocale>en-US</UserLocale>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
|
<DynamicUpdate>
|
||||||
|
<Enable>false</Enable>
|
||||||
|
<WillShowUI>Never</WillShowUI>
|
||||||
|
</DynamicUpdate>
|
||||||
|
<DiskConfiguration>
|
||||||
|
<Disk wcm:action="add">
|
||||||
|
<DiskID>0</DiskID>
|
||||||
|
<WillWipeDisk>true</WillWipeDisk>
|
||||||
|
<CreatePartitions>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Type>EFI</Type>
|
||||||
|
<Size>100</Size>
|
||||||
|
</CreatePartition>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Type>MSR</Type>
|
||||||
|
<Size>16</Size>
|
||||||
|
</CreatePartition>
|
||||||
|
<CreatePartition wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Type>Primary</Type>
|
||||||
|
<Extend>true</Extend>
|
||||||
|
</CreatePartition>
|
||||||
|
</CreatePartitions>
|
||||||
|
<ModifyPartitions>
|
||||||
|
<ModifyPartition wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<PartitionID>1</PartitionID>
|
||||||
|
<Format>FAT32</Format>
|
||||||
|
<Label>System</Label>
|
||||||
|
</ModifyPartition>
|
||||||
|
<ModifyPartition wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<PartitionID>3</PartitionID>
|
||||||
|
<Format>NTFS</Format>
|
||||||
|
<Label>Windows</Label>
|
||||||
|
<Letter>C</Letter>
|
||||||
|
</ModifyPartition>
|
||||||
|
</ModifyPartitions>
|
||||||
|
</Disk>
|
||||||
|
<WillShowUI>OnError</WillShowUI>
|
||||||
|
</DiskConfiguration>
|
||||||
|
<ImageInstall>
|
||||||
|
<OSImage>
|
||||||
|
<InstallFrom>
|
||||||
|
<MetaData wcm:action="add">
|
||||||
|
<Key>/IMAGE/INDEX</Key>
|
||||||
|
<Value>6</Value>
|
||||||
|
</MetaData>
|
||||||
|
</InstallFrom>
|
||||||
|
<InstallTo>
|
||||||
|
<DiskID>0</DiskID>
|
||||||
|
<PartitionID>3</PartitionID>
|
||||||
|
</InstallTo>
|
||||||
|
<WillShowUI>OnError</WillShowUI>
|
||||||
|
</OSImage>
|
||||||
|
</ImageInstall>
|
||||||
|
<UserData>
|
||||||
|
<AcceptEula>true</AcceptEula>
|
||||||
|
<ProductKey>
|
||||||
|
<Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
|
||||||
|
<WillShowUI>Never</WillShowUI>
|
||||||
|
</ProductKey>
|
||||||
|
</UserData>
|
||||||
|
<RunSynchronous>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
</RunSynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<InputLocale>en-US</InputLocale>
|
||||||
|
<SystemLocale>en-US</SystemLocale>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
<UserLocale>en-US</UserLocale>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
</OOBE>
|
||||||
|
<!-- The built-in Administrator only exists to log on once and run sysprep; it
|
||||||
|
is disabled again before that, and the password is a throwaway. -->
|
||||||
|
<UserAccounts>
|
||||||
|
<AdministratorPassword>
|
||||||
|
<Value>Build-Only-1</Value>
|
||||||
|
<PlainText>true</PlainText>
|
||||||
|
</AdministratorPassword>
|
||||||
|
</UserAccounts>
|
||||||
|
<AutoLogon>
|
||||||
|
<Enabled>true</Enabled>
|
||||||
|
<LogonCount>1</LogonCount>
|
||||||
|
<Username>Administrator</Username>
|
||||||
|
<Password>
|
||||||
|
<Value>Build-Only-1</Value>
|
||||||
|
<PlainText>true</PlainText>
|
||||||
|
</Password>
|
||||||
|
</AutoLogon>
|
||||||
|
<FirstLogonCommands>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Description>Disable the built-in Administrator again</Description>
|
||||||
|
<CommandLine>net.exe user Administrator /active:no</CommandLine>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Description>Remove the cached copies of this answer file (Windows would read
|
||||||
|
them before the Unattend.xml on a seed ISO, and one has the build password)
|
||||||
|
and the setup logs</Description>
|
||||||
|
<CommandLine>cmd.exe /c "del /f /q C:\Windows\Panther\unattend*.xml C:\Windows\Panther\Unattend\unattend*.xml C:\Windows\Panther\*.log & rd /s /q C:\Windows\Panther\UnattendGC & exit /b 0"</CommandLine>
|
||||||
|
</SynchronousCommand>
|
||||||
|
<SynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Description>Generalize the image and power off</Description>
|
||||||
|
<CommandLine>C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown /quiet</CommandLine>
|
||||||
|
</SynchronousCommand>
|
||||||
|
</FirstLogonCommands>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
Reference in new issue
Block a user