#!/usr/bin/env bash # Windows VM Creation Script # Description: Creates and manages Windows virtual machines using QEMU/KVM # Features: # - Supports Windows 10, 11, and Server # - Automatic dependency checking # - Multiple VM instance support # - Cache management # - Unattended installation # - SMB sharing support # - Network control # Usage: ./windows.sh [VERSION] [OPTIONS] # Versions: # 10 Windows 10 # 11 Windows 11 (default) # server Windows Server 2022 # server2019 Windows Server 2019 # server2016 Windows Server 2016 # Options: # --help Show this help message # --clean Clean dependency cache # --check-deps Only check dependencies and exit # --download-iso Download Windows ISO and exit # --name NAME Set custom VM name # --ram SIZE Set RAM size (default: 8G) # --cpu NUM Set number of CPUs (default: 6) # --disk SIZE Set disk size (default: 80G) # --enable-smb Enable SMB sharing (default: disabled) # --disable-net Disable network connectivity (default: enabled) # Parse command line arguments WINDOWS_VERSION="11" CLEAN_CACHE=false CHECK_DEPS_ONLY=false DOWNLOAD_ISO_ONLY=false VM_NAME="windows-11" VM_RAM="8G" VM_CPU="6" VM_SIZE="80G" ENABLE_SMB=false DISABLE_NET=false # Handle version argument if it's the first argument if [[ $# -gt 0 && ! $1 =~ ^-- ]]; then case "$1" in "10") WINDOWS_VERSION="10" VM_NAME="windows-10" ;; "server" | "server2022") WINDOWS_VERSION="server2022" VM_NAME="windows-server-2022" ;; "server2019") WINDOWS_VERSION="server2019" VM_NAME="windows-server-2019" ;; "server2016") WINDOWS_VERSION="server2016" VM_NAME="windows-server-2016" ;; *) echo "Unknown Windows version: $1" echo "Use --help for usage information" exit 1 ;; esac shift fi while [[ $# -gt 0 ]]; do case $1 in --help) echo "Windows VM Creation Script" echo "Usage: $0 [VERSION] [OPTIONS]" echo echo "Versions:" echo " 10 Windows 10" echo " 11 Windows 11 (default)" echo " server Windows Server 2022" echo " server2019 Windows Server 2019" echo " server2016 Windows Server 2016" echo echo "Options:" echo " --help Show this help message" echo " --clean Clean dependency cache" echo " --check-deps Only check dependencies and exit" echo " --download-iso Download Windows ISO and exit" echo " --name NAME Set custom VM name" echo " --ram SIZE Set RAM size (default: 8G)" echo " --cpu NUM Set number of CPUs (default: 6)" echo " --disk SIZE Set disk size (default: 80G)" echo " --enable-smb Enable SMB sharing (default: disabled)" echo " --disable-net Disable network connectivity (default: enabled)" exit 0 ;; --clean) CLEAN_CACHE=true shift ;; --check-deps) CHECK_DEPS_ONLY=true shift ;; --download-iso) DOWNLOAD_ISO_ONLY=true shift ;; --name) VM_NAME="$2" shift 2 ;; --ram) VM_RAM="$2" shift 2 ;; --cpu) VM_CPU="$2" shift 2 ;; --disk) VM_SIZE="$2" shift 2 ;; --enable-smb) ENABLE_SMB=true shift ;; --disable-net) DISABLE_NET=true shift ;; *) echo "Unknown option: $1" echo "Use --help for usage information" exit 1 ;; esac done # Set variables HOST_DIR="$HOME/virt-new-new" VM_DIR="$HOST_DIR/machines" IMAGE_DIR="$HOST_DIR/images" WIN_ISO_DIR="${IMAGE_DIR}/${VM_NAME}" # Directory for Windows ISO SOCKET_DIR="$VM_DIR" SHARED_DIR="${HOST_DIR}/shared" FIRMWARE_DIR="${HOST_DIR}/firmware" TPM_DIR="$WIN_ISO_DIR" TPM_SOCKET="${WIN_ISO_DIR}/${VM_NAME}.swtpm-sock" GUEST_PORT=22 QCOW2_FILE="${VM_DIR}/${VM_NAME}.qcow2" # Try to find an available host port starting from 22220 HOST_PORT_START=22220 HOST_PORT_END=22300 for ((port = HOST_PORT_START; port <= HOST_PORT_END; port++)); do if ! ss -tuln | grep -q ":$port\b"; then HOST_PORT=$port echo "Using available port: $HOST_PORT" break fi done if [[ $port -gt $HOST_PORT_END ]]; then echo "Error: No available ports found between $HOST_PORT_START and $HOST_PORT_END" >&2 exit 1 fi # Set SMP configuration CORES=$((VM_CPU / 2)) THREADS_PER_CORE=2 SOCKETS=1 SMP_CONFIG="cores=$CORES,threads=$THREADS_PER_CORE,sockets=$SOCKETS" # Create necessary directories mkdir -p "${HOME}/${HOST_DIR}" mkdir -p "$IMAGE_DIR" "$SHARED_DIR" "$FIRMWARE_DIR" mkdir -p "$WIN_ISO_DIR" "$VM_DIR" mkdir -p "${WIN_ISO_DIR}/unattended" # Define ISO paths and URLs ISO_VIRTIO="${WIN_ISO_DIR}/virtio-win.iso" ISO_UNATTENDED="${WIN_ISO_DIR}/unattended.iso" # Find Windows ISO with flexible pattern matching find_windows_iso() { # Check if directory exists if [[ ! -d "$WIN_ISO_DIR" ]]; then mkdir -p "$WIN_ISO_DIR" fi # Try to find any Windows ISO using case-insensitive patterns local found_iso found_iso=$(find "$WIN_ISO_DIR" -maxdepth 1 -type f \( \ -iname "*win11*.iso" -o \ -iname "*win*11*.iso" -o \ -iname "Win*.iso" -o \ -iname "Win11*.iso" -o \ -iname "Win*11*.iso" -o \ -iname "*windows*11*.iso" -o \ -iname "*windows11*.iso" \ \) -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) if [[ -n "$found_iso" && -f "$found_iso" ]]; then echo "$found_iso" return 0 fi return 1 } # Define download URLs VIRTIO_ISO_URL="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" SPICE_WEBDAVD_URL="https://www.spice-space.org/download/windows/spice-webdavd/spice-webdavd-x64-latest.msi" SPICE_VDAGENT_URL="https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-latest.msi" SPICE_VDAGENT_FALLBACK_URL="https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.10.0.msi" USBDK_URL="https://www.spice-space.org/download/windows/usbdk/UsbDk_1.0.22_x64.msi" # Fido download URL (Windows ISO downloader) #FIDO_URL="https://github.com/pbatard/Fido/raw/master/Fido.ps1" #FIDO_PATH="$WIN_DIR/Fido.ps1" # Print colored messages print_info() { echo -e "\033[1;34m[INFO]\033[0m $1" >&2; } print_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1" >&2; } print_warning() { echo -e "\033[1;33m[WARNING]\033[0m $1" >&2; } print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1" >&2; } # Helper: verify file integrity verify_file() { local file="$1" local expected_sha256="$2" if [[ ! -f "$file" ]]; then return 1 fi if [[ -n "$expected_sha256" ]]; then local actual_sha256 actual_sha256=$(sha256sum "$file" | cut -d' ' -f1) if [[ "$actual_sha256" != "$expected_sha256" ]]; then print_error "File integrity check failed for $file" return 1 fi fi return 0 } # Helper: download file with verification download_file() { local url="$1" local dest="$2" local expected_sha256="$3" local allow_failure="$4" # Check if file exists and is valid if [[ -f "$dest" ]]; then if verify_file "$dest" "$expected_sha256"; then print_info "File $dest already exists and verified." return 0 else print_warning "File $dest exists but failed verification. Redownloading..." rm -f "$dest" fi fi print_info "Downloading $url..." if ! curl -fL --progress-bar -o "$dest" "$url"; then print_error "Failed to download $url." if [[ "$allow_failure" != "true" ]]; then return 1 fi else # Verify downloaded file if ! verify_file "$dest" "$expected_sha256"; then print_error "Downloaded file failed verification" rm -f "$dest" return 1 fi print_success "Successfully downloaded and verified $dest" fi return 0 } # Download Windows 11 ISO using Microsoft's API download_windows_iso() { local windows_version="11" # Default to Windows 11 local language="English (United States)" # Default language # Parse arguments if provided if [[ -n "$1" ]]; then windows_version="$1" fi print_info "Attempting to download Windows $windows_version ISO from Microsoft..." # Set required variables local user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0" local session_id="$(uuidgen)" local profile="606624d44113" local url="https://www.microsoft.com/en-us/software-download/windows$windows_version" # Add ISO to URL for Windows 10 case "$windows_version" in 10) url="${url}ISO" ;; esac # Step 1: Get download page HTML print_info "Fetching download page: $url" local iso_download_page_html iso_download_page_html="$(curl --disable --silent --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || { handle_curl_error $? print_error "Failed to fetch the download page. Please download Windows $windows_version ISO manually from $url" return 1 } # Step 2: Extract Product Edition ID print_info "Getting Product Edition ID..." local product_edition_id product_edition_id="$(echo "$iso_download_page_html" | grep -Eo '