diff --git a/Config.psm1 b/Config.psm1
index 8317b51..74e1ae8 100644
--- a/Config.psm1
+++ b/Config.psm1
@@ -115,12 +115,22 @@ function Get-AvailableConfigOptions {
@{"Name" = "install_qemu_ga"; "GroupName" = "custom";"DefaultValue" = "False";
"Description" = "Installs QEMU guest agent services from the Fedora VirtIO website.
Defaults to 'False' (no installation will be performed).
- If set to 'True', the following MSI installer will be downloaded and installed:
+ If set to 'True', by default, the following MSI installer will be downloaded and installed:
* for x86: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-x86.msi
* for x64: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-x64.msi
The value can be changed to a custom URL, to allow other QEMU guest agent versions to be installed.
Note: QEMU guest agent requires VirtIO drivers to be present on the image.
"},
+ @{"Name" = "source"; "GroupName" = "virtio_qemu_guest_agent"; "DefaultValue" = "web";
+ "Description" = "Source for QEMU Guest Agent installation. Options:
+ 'iso' - Extract from VirtIO ISO (requires virtio_iso_path to be set),
+ 'web' - Download from Internet (default behavior),
+ 'auto' - Try ISO first, fallback to web if extraction fails.
+ Default: 'web'"},
+ @{"Name" = "url"; "GroupName" = "virtio_qemu_guest_agent";
+ "Description" = "Custom URL for QEMU Guest Agent MSI installer. Must be used together with 'checksum' parameter for SHA256 verification."},
+ @{"Name" = "checksum"; "GroupName" = "virtio_qemu_guest_agent";
+ "Description" = "SHA256 checksum of the QEMU Guest Agent MSI installer. Must be used together with 'url' parameter."},
@{"Name" = "drivers_path"; "GroupName" = "drivers";
"Description" = "The location where additional drivers that are needed for the image are located."},
@{"Name" = "install_updates"; "GroupName" = "updates"; "DefaultValue" = $false; "AsBoolean" = $true;
diff --git a/Examples/windows-image-config-example.ini b/Examples/windows-image-config-example.ini
index e0a7aa4..dd4f8a1 100644
--- a/Examples/windows-image-config-example.ini
+++ b/Examples/windows-image-config-example.ini
@@ -142,6 +142,25 @@ drivers_path=""
# The value can be changed to a custom URL, to allow other QEMU guest agent versions to be installed.
# Note: QEMU guest agent requires VirtIO drivers to be present on the image.
install_qemu_ga=False
+
+[virtio_qemu_guest_agent]
+# Source for QEMU Guest Agent installation. Options:
+# - 'iso': Extract from VirtIO ISO (requires virtio_iso_path to be set)
+# - 'web': Download from Internet (default behavior)
+# - 'auto': Try ISO first, fallback to web if extraction fails
+# Default: 'web'
+source=web
+# Custom URL for QEMU Guest Agent MSI installer.
+# Must be used together with 'checksum' parameter for SHA256 verification.
+# Example: url=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-VERSION/qemu-ga-x64.msi
+url=
+# SHA256 checksum of the QEMU Guest Agent MSI installer.
+# Must be used together with 'url' parameter.
+# To get the checksum on Windows: Get-FileHash -Path "qemu-ga-x64.msi" -Algorithm SHA256
+# To get the checksum on Linux/macOS: sha256sum qemu-ga-x64.msi
+checksum=
+
+[custom]
# Set a custom timezone for the Windows image.
time_zone=""
# Set custom ntp servers(space separated) for the Windows image
diff --git a/README.md b/README.md
index 56dd7ea..82c781b 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,173 @@ the resulting VHDX is shrinked to a minimum size and converted to the required f
You can find a PowerShell example to generate a raw OpenStack Ironic image that also works on KVM
in `Examples/create-windows-online-cloud-image.ps1`
+## QEMU Guest Agent Configuration
+
+### Overview
+
+The QEMU Guest Agent installation supports multiple configuration modes:
+
+- **Source selection**: Install from VirtIO ISO, web download, or automatic fallback
+- **Checksum verification**: Optional SHA256 verification for enhanced security
+- **Full backward compatibility**: All existing configurations continue to work
+
+### Installation Source Options
+
+The `source` parameter in the `[virtio_qemu_guest_agent]` section controls where the QEMU Guest Agent is obtained from:
+
+| Value | Description | Requires VirtIO ISO | Behavior |
+|-------|-------------|---------------------|----------|
+| `web` | Download from internet (default) | No | Downloads from fedorapeople.org |
+| `iso` | Extract from VirtIO ISO only | **Yes** | Fails if ISO not available or MSI not found |
+| `auto` | Try ISO first, fallback to web | No | Intelligent: uses ISO if available, otherwise downloads |
+
+### Configuration Examples
+
+#### 1. Default Installation (Simple)
+
+```ini
+[custom]
+install_qemu_ga=True
+```
+
+Uses the default version from the VirtIO archive (web download).
+
+#### 2. Extract from VirtIO ISO (Offline Mode)
+
+```ini
+[drivers]
+virtio_iso_path=/path/to/virtio-win.iso
+
+[custom]
+install_qemu_ga=True
+
+[virtio_qemu_guest_agent]
+source=iso
+```
+
+Extracts the QEMU Guest Agent from the VirtIO ISO. Useful for offline environments.
+
+#### 3. Automatic Mode (Recommended)
+
+```ini
+[drivers]
+virtio_iso_path=/path/to/virtio-win.iso
+
+[custom]
+install_qemu_ga=True
+
+[virtio_qemu_guest_agent]
+source=auto
+```
+
+Tries ISO extraction first, automatically falls back to web download if needed.
+
+#### 4. Secure Installation with Checksum
+
+```ini
+[custom]
+install_qemu_ga=True
+
+[virtio_qemu_guest_agent]
+source=web
+url=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-VERSION/qemu-ga-x64.msi
+checksum=
+```
+
+Downloads from a custom URL with SHA256 checksum verification. Both `url` and `checksum` must be specified together.
+
+#### 5. Legacy Custom URL
+
+```ini
+[custom]
+install_qemu_ga=https://example.com/custom-qemu-ga.msi
+```
+
+Downloads from a custom URL without checksum verification (backward compatibility).
+
+### Priority Order
+
+The system follows this priority order:
+
+1. **Custom URL + Checksum** (if both provided) → Always used, `source` is ignored
+2. **Source-based installation**:
+ - `source=iso` → Extract from ISO only (error if fails)
+ - `source=auto` → Try ISO, fallback to web
+ - `source=web` → Download from internet
+3. **Legacy behavior** (backward compatibility):
+ - `install_qemu_ga=True` → Default URL
+ - `install_qemu_ga=` → Custom URL without checksum
+
+### VirtIO ISO Structure
+
+When using `source=iso` or `source=auto`, the system searches for the QEMU Guest Agent MSI in these locations:
+
+- `guest-agent/qemu-ga-x86_64.msi` (for 64-bit) ✓ **Most common**
+- `guest-agent/qemu-ga-i386.msi` (for 32-bit) ✓ **Most common**
+- `guest-agent/qemu-ga-x64.msi` (alternative naming)
+- `guest-agent/qemu-ga-x86.msi` (alternative naming)
+- Additional fallback paths and recursive search
+
+The system automatically detects and uses the correct MSI file based on the image architecture.
+
+### Getting the SHA256 Checksum
+
+**Windows (PowerShell)**:
+```powershell
+Get-FileHash -Path "qemu-ga-x64.msi" -Algorithm SHA256
+```
+
+**Linux/macOS**:
+```bash
+sha256sum qemu-ga-x64.msi
+```
+
+### Benefits
+
+- **Offline environments**: Use `source=iso` for air-gapped systems
+- **Faster builds**: Avoid network downloads with local ISO
+- **Version consistency**: Match guest agent with VirtIO drivers version
+- **Security**: SHA256 checksum verification prevents tampering
+- **Flexibility**: `source=auto` works both online and offline
+- **Bandwidth savings**: Reuse ISO for multiple image builds
+
+### Best Practices
+
+1. **Use `source=auto` for flexibility**: Works both online and offline
+2. **Use `source=iso` for strict offline environments**: Ensures no internet access is attempted
+3. **Use `source=web` with checksum**: For maximum security when downloading
+4. **Match versions**: Keep guest agent version consistent with VirtIO drivers
+
+### Technical Details
+
+**ISO Mounting Process**:
+1. Creates a temporary backup copy of the ISO
+2. Mounts the ISO using Windows VirtualDisk API
+3. Searches for the QEMU Guest Agent MSI
+4. Copies the MSI to the resources directory
+5. Safely dismounts and cleans up the temporary ISO
+
+**Architecture Mapping**:
+
+| Windows Architecture | ISO Filename |
+|---------------------|--------------|
+| AMD64 (64-bit) | `qemu-ga-x86_64.msi` |
+| x86 (32-bit) | `qemu-ga-i386.msi` |
+
+### Troubleshooting
+
+**Q: The build fails with "QEMU Guest Agent MSI not found in VirtIO ISO"**
+
+A: Your VirtIO ISO might have a different structure. Use `source=auto` to fall back to web download, or `source=web` to skip ISO extraction entirely.
+
+**Q: I want to force internet download even though I have an ISO**
+
+A: Set `source=web` in the configuration.
+
+**Q: How do I ensure version consistency between VirtIO drivers and guest agent?**
+
+A: Use `source=iso` to extract the guest agent from the same VirtIO ISO used for drivers.
+
## Frequently Asked Questions (FAQ)
### The image generation never stops
diff --git a/WinImageBuilder.psm1 b/WinImageBuilder.psm1
index adb7cca..d780ddf 100755
--- a/WinImageBuilder.psm1
+++ b/WinImageBuilder.psm1
@@ -563,31 +563,219 @@ function Download-CloudbaseInit {
}
}
-function Download-QemuGuestAgent {
+function Copy-QemuGuestAgentFromISO {
Param(
[Parameter(Mandatory=$true)]
- [string]$QemuGuestAgentConfig,
+ [string]$IsoPath,
[Parameter(Mandatory=$true)]
[string]$ResourcesDir,
[Parameter(Mandatory=$true)]
[string]$OsArch
)
- $QemuGuestAgentUrl = $QemuGuestAgentConfig
- if ($QemuGuestAgentConfig -eq 'True') {
- $arch = "x86"
- if ($OsArch -eq "AMD64") {
- $arch = "x64"
+ Write-Log "Extracting QEMU Guest Agent from VirtIO ISO: $IsoPath..."
+
+ # Map architecture to ISO file naming convention
+ $arch = "i386"
+ $archAlt = "x86"
+ if ($OsArch -eq "AMD64") {
+ $arch = "x86_64"
+ $archAlt = "x64"
+ }
+
+ $isoPathBak = $IsoPath + (Get-Random) + ".iso"
+ Copy-Item $IsoPath $isoPathBak -Force
+ Write-Log "Using backed up ISO for safe dismount."
+ $IsoPath = $isoPathBak
+
+ $v = [WIMInterop.VirtualDisk]::OpenVirtualDisk($IsoPath)
+ try {
+ if (Is-IsoFile $IsoPath) {
+ $v.AttachVirtualDisk()
+ Get-PSDrive | Out-Null
+ $devicePath = $v.GetVirtualDiskPhysicalPath()
+ $isoDriveLetter = Execute-Retry {
+ $res = (Get-DiskImage -DevicePath $devicePath | Get-Volume).DriveLetter
+ if (!$res) {
+ throw "Failed to mount ISO ${IsoPath}"
+ }
+ return $res
+ }
+ $isoDriveLetter += ":"
+
+ Write-Log "Searching for QEMU Guest Agent in VirtIO ISO at ${isoDriveLetter}..."
+
+ # List root directory content for debugging
+ Write-Log "Listing ISO root directory content:"
+ try {
+ $rootItems = Get-ChildItem -Path "${isoDriveLetter}\" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name
+ Write-Log "Root items: $($rootItems -join ', ')"
+ } catch {
+ Write-Log "Could not list root directory: $_"
+ }
+
+ # Check if guest-agent directory exists and list its content
+ if (Test-Path "${isoDriveLetter}\guest-agent") {
+ Write-Log "guest-agent directory found, listing content:"
+ try {
+ $gaItems = Get-ChildItem -Path "${isoDriveLetter}\guest-agent" -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
+ foreach ($item in $gaItems) {
+ Write-Log " - $item"
+ }
+ } catch {
+ Write-Log "Could not list guest-agent directory: $_"
+ }
+ }
+
+ # Try multiple possible paths with both naming conventions
+ $possiblePaths = @(
+ "${isoDriveLetter}\guest-agent\qemu-ga-${arch}.msi",
+ "${isoDriveLetter}\guest-agent\qemu-ga-${archAlt}.msi",
+ "${isoDriveLetter}\guest-agent\qemu-ga-${arch}\qemu-ga-${arch}.msi",
+ "${isoDriveLetter}\guest-agent\${arch}\qemu-ga-${arch}.msi",
+ "${isoDriveLetter}\qemu-ga\qemu-ga-${arch}.msi",
+ "${isoDriveLetter}\qemu-ga-${arch}.msi",
+ "${isoDriveLetter}\${arch}\qemu-ga-${arch}.msi"
+ )
+
+ # Also try to find any MSI file containing "qemu-ga" and the architecture
+ Write-Log "Searching for QEMU Guest Agent MSI files..."
+ $qemuGaMsiPath = $null
+ foreach ($path in $possiblePaths) {
+ Write-Log "Checking path: $path"
+ if (Test-Path $path) {
+ $qemuGaMsiPath = $path
+ Write-Log "Found QEMU Guest Agent at: $path"
+ break
+ }
+ }
+
+ # If not found in standard paths, try to search recursively
+ if (!$qemuGaMsiPath) {
+ Write-Log "Not found in standard paths, searching recursively..."
+ try {
+ $foundFiles = Get-ChildItem -Path "${isoDriveLetter}\" -Recurse -Filter "*qemu-ga*${arch}*.msi" -ErrorAction SilentlyContinue
+ if ($foundFiles) {
+ $qemuGaMsiPath = $foundFiles[0].FullName
+ Write-Log "Found QEMU Guest Agent via recursive search at: $qemuGaMsiPath"
+ }
+ } catch {
+ Write-Log "Recursive search failed: $_"
+ }
+ }
+
+ if (!$qemuGaMsiPath) {
+ throw "QEMU Guest Agent MSI not found in VirtIO ISO for architecture: $arch. Searched paths: $($possiblePaths -join ', '). Please check the ISO structure and update the search paths if needed."
+ }
+
+ $dst = Join-Path $ResourcesDir "qemu-ga.msi"
+ Copy-Item -Path $qemuGaMsiPath -Destination $dst -Force
+ Write-Log "QEMU Guest Agent copied successfully to: $dst"
+
+ } else {
+ throw "The $IsoPath is not a valid ISO path."
}
- $QemuGuestAgentUrl = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads" + `
- "/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-{0}.msi" -f $arch
+ } catch {
+ throw $_
+ } finally {
+ if ($v) {
+ $v.DetachVirtualDisk()
+ $v.Close()
+ }
+ Remove-Item -Force $IsoPath
}
+}
- Write-Log "Downloading QEMU guest agent installer from ${QemuGuestAgentUrl} ..."
- $dst = Join-Path $ResourcesDir "qemu-ga.msi"
- Execute-Retry {
- (New-Object System.Net.WebClient).DownloadFile($QemuGuestAgentUrl, $dst)
+function Download-QemuGuestAgent {
+ Param(
+ [Parameter(Mandatory=$true)]
+ [string]$QemuGuestAgentConfig,
+ [Parameter(Mandatory=$true)]
+ [string]$ResourcesDir,
+ [Parameter(Mandatory=$true)]
+ [string]$OsArch,
+ [Parameter(Mandatory=$false)]
+ [string]$CustomUrl,
+ [Parameter(Mandatory=$false)]
+ [string]$Checksum,
+ [Parameter(Mandatory=$false)]
+ [string]$Source = "web",
+ [Parameter(Mandatory=$false)]
+ [string]$VirtioIsoPath
+ )
+
+ $useCustomConfig = $false
+
+ # Priority 1: Check if custom URL and checksum are provided (highest priority)
+ if ($CustomUrl -and $Checksum) {
+ $useCustomConfig = $true
+ Write-Log "Using custom QEMU Guest Agent configuration with checksum verification"
+ Write-Log "Downloading QEMU guest agent installer from ${CustomUrl} ..."
+ $dst = Join-Path $ResourcesDir "qemu-ga.msi"
+ Execute-Retry {
+ (New-Object System.Net.WebClient).DownloadFile($CustomUrl, $dst)
+ }
+
+ # Verify SHA256 checksum
+ Write-Log "Verifying SHA256 checksum..."
+ $fileHash = (Get-FileHash -Path $dst -Algorithm SHA256).Hash
+ if ($fileHash -ne $Checksum) {
+ throw "SHA256 checksum verification failed. Expected: $Checksum, Got: $fileHash"
+ }
+ Write-Log "SHA256 checksum verification successful"
+ }
+
+ # Priority 2: Source-based installation (iso/web/auto)
+ if (!$useCustomConfig) {
+ $useSourceConfig = $false
+
+ # Check if source is set to 'iso' or 'auto'
+ if ($Source -eq "iso" -or $Source -eq "auto") {
+ if ($VirtioIsoPath -and (Test-Path $VirtioIsoPath)) {
+ Write-Log "Source set to '$Source': Attempting to extract QEMU Guest Agent from VirtIO ISO"
+ try {
+ Copy-QemuGuestAgentFromISO -IsoPath $VirtioIsoPath -ResourcesDir $ResourcesDir -OsArch $OsArch
+ $useSourceConfig = $true
+ } catch {
+ if ($Source -eq "iso") {
+ # If source is explicitly 'iso', fail with error
+ throw "Failed to extract QEMU Guest Agent from VirtIO ISO: $_"
+ } else {
+ # If source is 'auto', log the error and fall back to web download
+ Write-Log "Failed to extract QEMU Guest Agent from VirtIO ISO: $_"
+ Write-Log "Falling back to web download..."
+ }
+ }
+ } else {
+ if ($Source -eq "iso") {
+ throw "Source is set to 'iso' but VirtIO ISO path is not provided or does not exist: $VirtioIsoPath"
+ } else {
+ Write-Log "VirtIO ISO not available, falling back to web download"
+ }
+ }
+ }
+
+ # Priority 3: Use web download (legacy behavior or fallback from 'auto')
+ if (!$useSourceConfig) {
+ Write-Log "Using web download for QEMU Guest Agent"
+ $QemuGuestAgentUrl = $QemuGuestAgentConfig
+ if ($QemuGuestAgentConfig -eq 'True') {
+ $arch = "x86"
+ if ($OsArch -eq "AMD64") {
+ $arch = "x64"
+ }
+ $QemuGuestAgentUrl = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads" + `
+ "/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-{0}.msi" -f $arch
+ }
+
+ Write-Log "Downloading QEMU guest agent installer from ${QemuGuestAgentUrl} ..."
+ $dst = Join-Path $ResourcesDir "qemu-ga.msi"
+ Execute-Retry {
+ (New-Object System.Net.WebClient).DownloadFile($QemuGuestAgentUrl, $dst)
+ }
+ }
}
+
Write-Log "QEMU guest agent installer path is: $dst"
}
@@ -1712,7 +1900,9 @@ function New-WindowsCloudImage {
}
if ($windowsImageConfig.install_qemu_ga -and $windowsImageConfig.install_qemu_ga -ne 'False') {
Download-QemuGuestAgent -QemuGuestAgentConfig $windowsImageConfig.install_qemu_ga `
- -ResourcesDir $resourcesDir -OsArch ([string]$image.ImageArchitecture)
+ -ResourcesDir $resourcesDir -OsArch ([string]$image.ImageArchitecture) `
+ -CustomUrl $windowsImageConfig.url -Checksum $windowsImageConfig.checksum `
+ -Source $windowsImageConfig.source -VirtioIsoPath $windowsImageConfig.virtio_iso_path
}
Download-CloudbaseInit -resourcesDir $resourcesDir -osArch ([string]$image.ImageArchitecture) `
-BetaRelease:$windowsImageConfig.beta_release -MsiPath $windowsImageConfig.msi_path `
@@ -1893,7 +2083,9 @@ function New-WindowsFromGoldenImage {
}
if ($windowsImageConfig.install_qemu_ga -and $windowsImageConfig.install_qemu_ga -ne 'False') {
Download-QemuGuestAgent -QemuGuestAgentConfig $windowsImageConfig.install_qemu_ga `
- -ResourcesDir $resourcesDir -OsArch $imageInfo.imageArchitecture
+ -ResourcesDir $resourcesDir -OsArch $imageInfo.imageArchitecture `
+ -CustomUrl $windowsImageConfig.url -Checksum $windowsImageConfig.checksum `
+ -Source $windowsImageConfig.source -VirtioIsoPath $windowsImageConfig.virtio_iso_path
}
Download-CloudbaseInit -resourcesDir $resourcesDir -osArch $imageInfo.imageArchitecture `
-BetaRelease:$windowsImageConfig.beta_release -MsiPath $windowsImageConfig.msi_path `