Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c5abac9bf | ||
|
|
cb01d2c120 |
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 boots
|
||||||
|
into audit mode (no account and no credentials are needed) and runs
|
||||||
|
`sysprep /generalize` there 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.
|
|
||||||
+207
-141
@@ -1,60 +1,68 @@
|
|||||||
#!/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);
|
# The answer file then boots into audit mode, where it runs sysprep
|
||||||
# 2. once SSH answers, this script runs sysprep /generalize over SSH, which
|
# /generalize, 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 answer file holds no credentials either: audit mode needs no
|
||||||
|
# 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 +72,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 +169,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 +199,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,140 @@
|
|||||||
|
<?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>
|
||||||
|
<!-- After Setup, boot into audit mode instead of OOBE: Windows logs on as the
|
||||||
|
built-in Administrator (no password, no account is created) and runs the
|
||||||
|
auditUser commands below, the last of which generalizes the image and powers
|
||||||
|
off. So the answer file holds no credentials. -->
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS">
|
||||||
|
<Reseal>
|
||||||
|
<Mode>Audit</Mode>
|
||||||
|
</Reseal>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
<settings pass="auditUser">
|
||||||
|
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64"
|
||||||
|
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
|
||||||
|
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
|
||||||
|
<RunSynchronous>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>1</Order>
|
||||||
|
<Description>Disable the built-in Administrator again</Description>
|
||||||
|
<Path>net.exe user Administrator /active:no</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>2</Order>
|
||||||
|
<Description>Turn hibernation off, which deletes hiberfil.sys (3 GB) from the image</Description>
|
||||||
|
<Path>powercfg.exe /hibernate off</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>3</Order>
|
||||||
|
<Description>Remove the cached copies of this answer file (Windows would read
|
||||||
|
them before the Unattend.xml on a seed ISO) and the setup logs</Description>
|
||||||
|
<Path>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"</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
<RunSynchronousCommand wcm:action="add">
|
||||||
|
<Order>4</Order>
|
||||||
|
<Description>Generalize the image and power off</Description>
|
||||||
|
<Path>C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdown /quiet</Path>
|
||||||
|
</RunSynchronousCommand>
|
||||||
|
</RunSynchronous>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
Reference in new issue
Block a user