From d9707fd57b163f3efb72aee967563bcb9a63207c Mon Sep 17 00:00:00 2001 From: Guofang Date: Mon, 19 Oct 2026 13:26:24 +0800 Subject: [PATCH] Stage the Windows ISO files with rsync before copying them into the image Copy the ISO without install.wim to a staging directory, split install.wim into it, and fill the image with one recursive mcopy instead of a mmd and mcopy per directory. rsync --chmod keeps the staged directories writable so that cleanup can remove them. Co-Authored-By: Claude Sonnet 5.5 --- build-windows-install-disk.sh | 63 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/build-windows-install-disk.sh b/build-windows-install-disk.sh index 3660642..d406df4 100755 --- a/build-windows-install-disk.sh +++ b/build-windows-install-disk.sh @@ -15,7 +15,7 @@ # # Dependencies # macOS (Homebrew) wimlib dosfstools gptfdisk mtools -# Debian wimtools dosfstools gdisk mtools udisks2 +# Debian wimtools dosfstools gdisk mtools rsync udisks2 # # Usage: build-windows-install-disk.sh [--force] ISO [OUTPUT.img] # OUTPUT defaults to the ISO path with ".img" in place of ".iso". @@ -46,8 +46,8 @@ die() { exit 1 } -# The build has four steps; step prints a numbered heading for each of them. -total_steps=4 +# The build has five steps; step prints a numbered heading for each of them. +total_steps=5 step_number=0 step() { step_number=$((step_number + 1)) @@ -88,7 +88,7 @@ for dir in ${brew_prefix:+"$brew_prefix/sbin"} /opt/homebrew/sbin /usr/local/sbi [[ -d "$dir" ]] && PATH="$PATH:$dir" done -tools=(wimlib-imagex mkfs.fat sgdisk mcopy mmd) +tools=(wimlib-imagex mkfs.fat sgdisk mcopy rsync) [[ "$os" == Darwin ]] && tools+=(hdiutil) missing=() for tool in "${tools[@]}"; do @@ -98,7 +98,7 @@ if ((${#missing[@]} > 0)); then if [[ "$os" == Darwin ]]; then hint="brew install wimlib dosfstools gptfdisk mtools" else - hint="apt install wimtools dosfstools gdisk mtools" + hint="apt install wimtools dosfstools gdisk mtools rsync" fi die "missing tools: ${missing[*]} ($hint)" fi @@ -128,6 +128,8 @@ cleanup() { udisksctl unmount -b "$loop_device" >/dev/null 2>&1 || true udisksctl loop-delete -b "$loop_device" >/dev/null 2>&1 || true ;; esac + # Staged directories may be read-only when the run was cancelled early. + chmod -R u+rwX "$work_dir" >/dev/null 2>&1 || true rm -rf "$work_dir" } trap cleanup EXIT @@ -202,20 +204,27 @@ install_wim="$mount_dir/sources/install.wim" [[ -d "$mount_dir/efi/boot" ]] || die "efi/boot was not found in the ISO; is it a UEFI Windows ISO?" note "install.wim: $(stat "${size_args[@]}" "$install_wim") bytes" +# The staging directory holds everything that goes into the image: the ISO files +# without install.wim, and the install.swm parts in sources/. +stage_dir="$work_dir/stage" +mkdir "$stage_dir" +step "Staging the ISO files without install.wim" +# The UDF mount is read-only, so its directories are too; --chmod keeps the copies +# writable, otherwise cleanup cannot remove them. +run rsync -rt --chmod=u+rwX --exclude=/sources/install.wim "$mount_dir/" "$stage_dir/" +run mkdir -p "$stage_dir/sources" + # Parts must stay below the 4 GiB FAT32 file limit; wimlib takes MiB. step "Splitting install.wim into install.swm parts (FAT32 files must stay below 4 GiB)" -split_dir="$work_dir/split" -mkdir "$split_dir" -run wimlib-imagex split "$install_wim" "$split_dir/install.swm" 3800 >/dev/null -for part in "$split_dir"/install*.swm; do +run wimlib-imagex split "$install_wim" "$stage_dir/sources/install.swm" 3800 >/dev/null +for part in "$stage_dir"/sources/install*.swm; do note "$(basename "$part"): $(stat "${size_args[@]}" "$part") bytes" done -# Size the image: everything but install.wim, the split parts, and 5% plus 64 MiB -# of room for FAT32 metadata and partition alignment. +# Size the image: the staged files, and 5% plus 64 MiB of room for FAT32 +# metadata and partition alignment. content_bytes=$( - { find "$mount_dir" -type f ! -path "$install_wim" -exec stat "${size_args[@]}" {} + - find "$split_dir" -type f -exec stat "${size_args[@]}" {} +; } | + find "$stage_dir" -type f -exec stat "${size_args[@]}" {} + | awk '{ total += $1 } END { printf "%d", total }' ) mib=$((1024 * 1024)) @@ -240,29 +249,13 @@ mtools_image="$image@@$((2048 * 512))" note "partition: $sectors sectors, formatted as FAT32 (label WINPE)" export MTOOLS_SKIP_CHECK=1 -copy_dir() { # copy_dir SOURCE_DIR TARGET_DIR_IN_IMAGE - local source="$1" target="$2" files=() entry - while IFS= read -r -d '' entry; do - files+=("$entry") - done < <(find "$source" -maxdepth 1 -type f ! -path "$install_wim" -print0) - if ((${#files[@]} > 0)); then - note "\$ mcopy -Q -m -i $mtools_image <${#files[@]} files from ${source#"$mount_dir"} > ::$target/" - mcopy -Q -m -i "$mtools_image" "${files[@]}" "::$target/" - fi -} - step "Copying the installation files into the image" -copy_dir "$mount_dir" "" -dir_count=0 -while IFS= read -r -d '' dir; do - relative="${dir#"$mount_dir"/}" - dir_count=$((dir_count + 1)) - run mmd -i "$mtools_image" "::$relative" - copy_dir "$dir" "$relative" -done < <(find "$mount_dir" -mindepth 1 -type d -print0 | sort -z) -note "copied $dir_count directories" -note "copying the install*.swm parts to sources/" -run mcopy -Q -m -i "$mtools_image" "$split_dir"/install*.swm "::sources/" +entries=() +while IFS= read -r -d '' entry; do + entries+=("$entry") +done < <(find "$stage_dir" -mindepth 1 -maxdepth 1 -print0) +note "\$ mcopy -s -Q -m -i $mtools_image <${#entries[@]} entries from the staging directory> ::/" +mcopy -s -Q -m -i "$mtools_image" "${entries[@]}" "::/" partial_output="" printf 'Built Windows 11 installation disk: %s\n' "$output"