Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Get-AmdGraphicsDrivers.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#Requires -Version 7.0
<#
.SYNOPSIS
Fetches the latest AMD Radeon GPU display drivers (consumer + Radeon Pro) from the Microsoft Update Catalog.

.DESCRIPTION
Thin shim over catalogscrape\CatalogScrape.psm1, driven by catalogscrape\amd-gfx.psd1. Drivers come from
the catalog (single-source): consumer Radeon RX and workstation Radeon Pro both publish "Advanced Micro
Devices, Inc. - Display" packages, listed per family because of AMD's current/legacy branch split.
Selection is highest-version-first (catalog date as tiebreak).

.NOTES
SCOPE: POST-BOOT CONVENIENCE drivers only. A GPU cannot serve iSCSI/PXE boot, so nothing here is
boot-critical — it only provides accelerated display after the OS is running. A bare INF install
(offline DISM or live pnputil) delivers a fully accelerated driver incl. the OpenGL/Vulkan/OpenCL/D3D
runtimes; the AMD Software (Adrenalin) app is not included and is out of scope. For Radeon Pro cards the
catalog serves the base WHQL display driver, NOT the ISV-certified PRO Edition (out of scope).

.EXAMPLE
.\Get-AmdGraphicsDrivers.ps1
.EXAMPLE
.\Get-AmdGraphicsDrivers.ps1 -Install
#>
[CmdletBinding()]
param(
[switch]$Install,
[string]$DownloadPath = 'C:\Temp',
[ValidateSet('x64', 'arm64', 'all')][string]$Architecture = 'x64'
)

$csDir = Join-Path $PSScriptRoot 'catalogscrape'
Import-Module (Join-Path $csDir 'CatalogScrape.psm1') -Force
$config = Import-PowerShellDataFile (Join-Path $csDir 'amd-gfx.psd1')
Invoke-DriverScrape -Config $config -Install:$Install -DownloadPath $DownloadPath -Architecture $Architecture
33 changes: 33 additions & 0 deletions Get-IntelGraphicsDrivers.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#Requires -Version 7.0
<#
.SYNOPSIS
Fetches the latest Intel GPU (Arc / Iris Xe / UHD) display drivers from the Microsoft Update Catalog.

.DESCRIPTION
Thin shim over catalogscrape\CatalogScrape.psm1, driven by catalogscrape\intel-gfx.psd1. Intel ships a
unified DCH display driver, so a single "Intel Corporation - Display" package covers Arc A/B-series,
Iris Xe, and recent integrated graphics. Selection is highest-version-first (catalog date as tiebreak).

.NOTES
SCOPE: POST-BOOT CONVENIENCE drivers only. A GPU cannot serve iSCSI/PXE boot, so nothing here is
boot-critical — it only provides accelerated display after the OS is running. A bare INF install
(offline DISM or live pnputil) delivers a fully accelerated driver incl. the OpenGL/Vulkan/OpenCL/D3D
runtimes; the optional Microsoft Store control-panel app (Intel Graphics Software) is not included and
is out of scope.

.EXAMPLE
.\Get-IntelGraphicsDrivers.ps1
.EXAMPLE
.\Get-IntelGraphicsDrivers.ps1 -Install
#>
[CmdletBinding()]
param(
[switch]$Install,
[string]$DownloadPath = 'C:\Temp',
[ValidateSet('x64', 'arm64', 'all')][string]$Architecture = 'x64'
)

$csDir = Join-Path $PSScriptRoot 'catalogscrape'
Import-Module (Join-Path $csDir 'CatalogScrape.psm1') -Force
$config = Import-PowerShellDataFile (Join-Path $csDir 'intel-gfx.psd1')
Invoke-DriverScrape -Config $config -Install:$Install -DownloadPath $DownloadPath -Architecture $Architecture
34 changes: 34 additions & 0 deletions Get-NvidiaGraphicsDrivers.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#Requires -Version 7.0
<#
.SYNOPSIS
Fetches the latest NVIDIA GPU display drivers (GeForce + RTX/Quadro) from the Microsoft Update Catalog.

.DESCRIPTION
Thin shim over catalogscrape\CatalogScrape.psm1, driven by catalogscrape\nvidia-gfx.psd1. Drivers come
from the catalog (single-source), NOT nvidia.com: HWID targeting separates the unified consumer GeForce
package from the professional RTX/Quadro production-branch package. Selection is highest-version-first.

.NOTES
SCOPE: POST-BOOT CONVENIENCE drivers only. A GPU cannot serve iSCSI/PXE boot, so nothing here is
boot-critical — it only provides accelerated display after the OS is running. A bare INF install
(offline DISM or live pnputil) delivers a fully accelerated driver incl. the OpenGL/Vulkan/OpenCL/D3D
ICDs and the CUDA/NVENC runtime; the NVIDIA App / Control Panel (Microsoft Store) and the full CUDA
toolkit are not included and are out of scope. Catalog gives newest-WHQL, not day-0 Game Ready, with no
GRD-vs-Studio choice; Data Center (Tesla) and vGPU/GRID drivers are out of scope.

.EXAMPLE
.\Get-NvidiaGraphicsDrivers.ps1
.EXAMPLE
.\Get-NvidiaGraphicsDrivers.ps1 -Install
#>
[CmdletBinding()]
param(
[switch]$Install,
[string]$DownloadPath = 'C:\Temp',
[ValidateSet('x64', 'arm64', 'all')][string]$Architecture = 'x64'
)

$csDir = Join-Path $PSScriptRoot 'catalogscrape'
Import-Module (Join-Path $csDir 'CatalogScrape.psm1') -Force
$config = Import-PowerShellDataFile (Join-Path $csDir 'nvidia-gfx.psd1')
Invoke-DriverScrape -Config $config -Install:$Install -DownloadPath $DownloadPath -Architecture $Architecture
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,20 @@ BitLocker auto-encryption, injects LabConfig hardware-check bypasses and `Bypass
and drops an `unattend.xml` (local `lan` admin account with autologon) plus a
`SetupComplete.cmd` that disables NIC power management.

- `-Drivers` runs every `Get-*Drivers.ps1` scraper in parallel and injects the
results with `DISM /Add-Driver`.
- `-Drivers` runs every NIC/Wi-Fi `Get-*Drivers.ps1` scraper in parallel (the
`Get-*GraphicsDrivers.ps1` shims are excluded — see `-GraphicsDrivers`) and injects
the results with `DISM /Add-Driver`.
- `-GraphicsDrivers Intel|AMD|NVIDIA|All` additionally injects GPU display drivers
(also catalog-sourced) into the same image. GPU CABs are large (~0.6–1.2 GB each),
so it is opt-in and separate from `-Drivers`; `All` injects the ~3–5 GB union of all
three vendors, so prefer a single vendor matching the target machine. GPU drivers are
post-boot-only (a GPU never serves iSCSI boot), so they get plain `DISM /Add-Driver`
with **no** boot-start promotion. A bare INF install yields a fully accelerated driver
(incl. the OpenGL/Vulkan/OpenCL/D3D and CUDA/NVENC runtimes); the vendor control-panel
apps (NVIDIA App, AMD Adrenalin, Intel Graphics Software — all Microsoft Store) are not
installed, and Radeon Pro cards get the base WHQL driver, not the ISV-certified PRO
Edition. x64 only (discrete GPUs have no ARM64 Windows driver; Qualcomm Adreno is not
on the catalog). The same shims run standalone on a live system (`-Install` → `pnputil`).
- `-Updates` runs `Get-Win11CumulativeUpdates.ps1` and injects the latest cumulative
update (with checkpoint prerequisites) via folder-based `DISM /Add-Package`.

Expand Down Expand Up @@ -96,6 +108,9 @@ Driver/update scrapers (Microsoft Update Catalog):
| `Get-IntelWiFiDrivers.ps1` | Intel Wi-Fi 6/6E/7 (post-boot convenience) |
| `Get-MediatekWiFiDrivers.ps1` | MediaTek MT79xx Wi-Fi (post-boot convenience) |
| `Get-QualcommWiFiDrivers.ps1` | Qualcomm WCN/FastConnect Wi-Fi (post-boot convenience) |
| `Get-IntelGraphicsDrivers.ps1` | Intel Arc / Iris Xe / UHD GPU (post-boot convenience) |
| `Get-NvidiaGraphicsDrivers.ps1` | NVIDIA GeForce + RTX/Quadro GPU (post-boot convenience) |
| `Get-AmdGraphicsDrivers.ps1` | AMD Radeon RX + Radeon Pro GPU (post-boot convenience) |
| `Get-Win11CumulativeUpdates.ps1` | Latest monthly CU + checkpoint chain + SSU per Windows version |

**Status:** the `0x7B INACCESSIBLE_BOOT_DEVICE` failure (DISM apply + service-key
Expand Down
50 changes: 46 additions & 4 deletions build_win11pxe.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ to allow the OS to boot from an iSCSI target (iBFT).

.EXAMPLE
.\build_win11pxe.ps1 -IsoPath .\Win11_25H2_English_x64.iso -OutPath .\win11_netboot.vhdx -ImageIndex 6

.EXAMPLE
.\build_win11pxe.ps1 -IsoPath .\Win11_25H2_English_x64.iso -Drivers -GraphicsDrivers NVIDIA -Updates
#>

param(
Expand All @@ -32,6 +35,15 @@ param(
[Parameter(Mandatory = $false)]
[switch]$Updates,

# Inject GPU display drivers (post-boot only; a GPU never serves iSCSI boot, so unlike the NIC
# scrapers these get NO boot-start promotion — see the $nicDriverDefs note). Catalog-sourced like
# every other scraper. GPU CABs are large (~0.6-1.2 GB each), so this is opt-in and separate from
# -Drivers. 'All' runs Intel+AMD+NVIDIA (heavier, ~3-5 GB union); pick a single vendor to inject only
# the target machine's GPU driver. x64 only — discrete GPUs have no ARM64 Windows driver.
[Parameter(Mandatory = $false)]
[ValidateSet('Intel', 'AMD', 'NVIDIA', 'All')]
[string]$GraphicsDrivers,

# Adapter GUID(s) of the boot NIC, for the offline DISM /Add-NetAdapter step
# that actually makes Windows able to boot over iSCSI (see notes below).
# Obtain on a live machine / WinPE where that NIC is present:
Expand Down Expand Up @@ -179,13 +191,39 @@ try {
throw "DISM /Apply-Image failed with exit code $LASTEXITCODE. Check log: $dismLogPath"
}

if ($Drivers) {
if ($Drivers -or $GraphicsDrivers) {
Write-Host ">>> Executing Driver Scrapers (parallel)..." -ForegroundColor Cyan
$driverTempPath = Join-Path $env:TEMP "Win11Drivers_$(Get-Random)"
# Pre-create the root so the post-run enumeration never hits a missing path
# when every scraper fails.
New-Item -ItemType Directory -Path $driverTempPath -Force | Out-Null
$driverScripts = Get-ChildItem -Path $PSScriptRoot -Filter "Get-*Drivers.ps1"

# NIC/Wi-Fi scrapers (-Drivers). Graphics shims are named Get-*GraphicsDrivers.ps1 and are
# EXCLUDED here so they run only on explicit -GraphicsDrivers opt-in (their CABs are large).
$driverScripts = @()
if ($Drivers) {
$driverScripts += Get-ChildItem -Path $PSScriptRoot -Filter "Get-*Drivers.ps1" |
Where-Object { $_.Name -notlike '*Graphics*' }
}
# GPU scrapers (-GraphicsDrivers Intel|AMD|NVIDIA|All). All feed the SAME $driverTempPath and the
# single DISM /Add-Driver below; a GPU is post-boot-only so none is added to $nicDriverDefs.
if ($GraphicsDrivers) {
$gpuShims = [ordered]@{
Intel = 'Get-IntelGraphicsDrivers.ps1'
AMD = 'Get-AmdGraphicsDrivers.ps1'
NVIDIA = 'Get-NvidiaGraphicsDrivers.ps1'
}
$wantedGpu = if ($GraphicsDrivers -eq 'All') { $gpuShims.Values } else { @($gpuShims[$GraphicsDrivers]) }
foreach ($gpuName in $wantedGpu) {
$gpuPath = Join-Path $PSScriptRoot $gpuName
if (Test-Path $gpuPath) { $driverScripts += Get-Item $gpuPath }
Comment on lines +218 to +219

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the default -Path parameter for Test-Path and Get-Item can cause failures if the repository is cloned into a directory path containing wildcard characters (such as [ or ]). Using -LiteralPath ensures that the path is treated as a literal string, preventing wildcard expansion issues.

                $gpuPath = Join-Path $PSScriptRoot $gpuName
                if (Test-Path -LiteralPath $gpuPath) { $driverScripts += Get-Item -LiteralPath $gpuPath }

else { Write-Warning " [!] Graphics scraper not found: $gpuName" }
}
}

# GPU CABs are far larger than NIC CABs, so allow more wall-clock when graphics are included.
# NOTE: this Wait-Job timeout is a single GLOBAL budget shared across all scrapers.
$scraperTimeoutSec = if ($GraphicsDrivers) { 3600 } else { 1800 }

# Launch all scrapers concurrently — each writes to its own subdirectory
$scraperJobs = foreach ($script in $driverScripts) {
Expand All @@ -200,8 +238,8 @@ try {
# must NOT abort the build — Receive-Job under $ErrorActionPreference='Stop'
# would otherwise rethrow a single flaky HTTP failure as terminating, after
# the hour-long image apply. -ErrorAction Continue keeps it non-fatal.
Write-Host " -> Waiting for $($scraperJobs.Count) scrapers to complete (max 30 min)..."
$scraperJobs | Wait-Job -Timeout 1800 | Out-Null
Write-Host " -> Waiting for $($scraperJobs.Count) scrapers to complete (max $([int]($scraperTimeoutSec / 60)) min)..."
$scraperJobs | Wait-Job -Timeout $scraperTimeoutSec | Out-Null
Comment on lines +241 to +242

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If no scraper scripts are found or executed (for example, if -GraphicsDrivers is specified but the corresponding scraper script is missing, and -Drivers is not set), $scraperJobs will be empty or $null. Piping an empty or $null collection to Wait-Job will throw a terminating error under $ErrorActionPreference = 'Stop', which aborts the entire VHDX build process after a lengthy image application. Wrapping the Wait-Job call in an if ($scraperJobs) block safely avoids this crash.

        if ($scraperJobs) {
            Write-Host "    -> Waiting for $($scraperJobs.Count) scrapers to complete (max $([int]($scraperTimeoutSec / 60)) min)..."
            $scraperJobs | Wait-Job -Timeout $scraperTimeoutSec | Out-Null
        }

foreach ($job in $scraperJobs) {
if ($job.State -eq 'Running') {
Write-Warning " [!] $($job.Name) timed out — stopping."
Expand Down Expand Up @@ -480,6 +518,10 @@ try {
# System32\drivers, and manually create the service registry key.
# NOTE: INF AddService names often differ from .sys basenames (e.g.
# e1dexpress → e1d.sys), so Service and Sys are specified independently.
# GPU drivers (-GraphicsDrivers: nvlddmkm/amdkmdag/igdkmd*) are deliberately
# NOT listed here — a GPU is on neither the boot nor the iSCSI data path
# (UEFI GOP → Basic Display Adapter → lazy PnP bind), so it must stay a plain
# DISM /Add-Driver with no boot-start promotion. Do not add one.
$nicDriverDefs = @(
# --- Virtual Ethernet ---
@{ Service = "netvsc"; Sys = "netvsc.sys" } # Hyper-V
Expand Down
14 changes: 14 additions & 0 deletions catalogscrape/Test-CatalogScrape.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ Assert { $null -ne $bestTie } 'equal-version tiebreak returns a deterministic pi
$expectKeys = @{
'intel-eth.psd1' = 10; 'intel-wifi.psd1' = 2; 'marvell.psd1' = 2
'realtek.psd1' = 8; 'qualcomm.psd1' = 3; 'mediatek.psd1' = 5; 'broadcom.psd1' = 3
# GPU graphics tables (catalog-sourced, post-boot convenience — same engine, no Source block)
'intel-gfx.psd1' = 1; 'nvidia-gfx.psd1' = 2; 'amd-gfx.psd1' = 5
}
foreach ($f in $expectKeys.Keys) {
$cfg = Import-PowerShellDataFile (Join-Path $PSScriptRoot $f)
Expand Down Expand Up @@ -151,5 +153,17 @@ $md = Import-PowerShellDataFile (Join-Path $PSScriptRoot 'mediatek.psd1')
$mt7925 = $md.Targets.Devices | Where-Object Key -eq '7925'
Assert { $mt7925.PreferredBranches[0] -eq '26.30' } 'MT7925 preferred branch refreshed to 26.30'

# GPU graphics tables: unified Intel device, NVIDIA consumer/pro split, AMD Radeon Pro present.
$ig = Import-PowerShellDataFile (Join-Path $PSScriptRoot 'intel-gfx.psd1')
$arcXe = $ig.Targets.Devices | Where-Object Key -eq 'Arc_Xe'
Assert { ($null -ne $arcXe) -and ($arcXe.Queries.Count -ge 2) -and ($arcXe.Queries -contains 'VEN_8086&DEV_E20B') } 'intel-gfx Arc_Xe unified (multi-HWID, incl Arc B580 E20B)'
$ng = Import-PowerShellDataFile (Join-Path $PSScriptRoot 'nvidia-gfx.psd1')
$nvGeForce = $ng.Targets.Devices | Where-Object Key -eq 'GeForce'
$nvPro = $ng.Targets.Devices | Where-Object Key -eq 'RTX_Pro'
Assert { ($nvGeForce.Queries -contains 'VEN_10DE&DEV_2684') -and ($nvPro.Queries -contains 'VEN_10DE&DEV_26B1') } 'nvidia-gfx splits consumer GeForce (2684) from RTX-pro (26B1)'
$ag = Import-PowerShellDataFile (Join-Path $PSScriptRoot 'amd-gfx.psd1')
$amdPro = $ag.Targets.Devices | Where-Object Key -eq 'ProW7000'
Assert { $amdPro.Queries[0] -eq 'VEN_1002&DEV_7448' } 'amd-gfx Radeon Pro W7900 (7448) present'

"`n== RESULT: $script:pass passed, $script:fail failed =="
if ($script:fail -gt 0) { exit 1 } else { exit 0 }
40 changes: 40 additions & 0 deletions catalogscrape/amd-gfx.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@{
# AMD Radeon GPU display drivers from the Microsoft Update Catalog — single-source, same engine as
# every other scraper. Verified (2026-06) that consumer HWIDs return "Advanced Micro Devices, Inc. -
# Display" WHQL CABs (amdkmdag.sys), and that Radeon Pro (W-series) HWIDs return a real Pro-keyed
# BASE WHQL display CAB (live: W7900 DEV_7448 -> 32.0.21036.18). The ISV-certified "PRO Edition /
# Radeon PRO Software for Enterprise" SKU is NOT published to the catalog — the base display driver
# drives the card fully (display + compute runtime) and is what we ship; ISV certification is out of
# scope. Selection is highest-version-first (catalog date as tiebreak).
#
# SCOPE: POST-BOOT CONVENIENCE only. A GPU cannot serve iSCSI/PXE boot, so nothing here is
# boot-critical and it gets NO boot-start promotion in build_win11pxe.ps1 (unlike the NIC scrapers).
#
# WHY ONE DEVICE PER FAMILY (not a single multi-HWID device like Intel/NVIDIA): AMD ships a current
# branch (RX 6000-9000 / RDNA2-4) and a separate legacy branch, and the catalog carries per-family
# submissions — so pooling families under one Key risks the highest-[version] winner being a package
# whose INF does not bind an older family. Keeping families separate guarantees each listed card gets
# a binding driver (cost: one download per family — trim this table to the cards you actually run).
# COVERAGE: catalog lags amd.com by ~1 quarter. The 25-relevance-row cap is sharper for AMD (many
# per-Windows-build submissions per HWID); the newest build normally still sits in the top rows.
# Representative DEV ids (VEN_1002): 7550 = RX 9000 (Navi48), 744C = RX 7900 (Navi31),
# 73BF = RX 6800/6900 (Navi21), 7448 = Radeon Pro W7900 (Navi31 GL), 73A3 = Radeon Pro W6800 (Navi21 GL).
VendorKey = 'AMD_Display'
Targets = @(
@{
Name = 'AMD_Radeon_Consumer'
Devices = @(
@{ Key = 'RX9000'; Label = 'Radeon RX 9000 (RDNA4)'; Queries = @('VEN_1002&DEV_7550') }
@{ Key = 'RX7000'; Label = 'Radeon RX 7000 (RDNA3)'; Queries = @('VEN_1002&DEV_744C') }
@{ Key = 'RX6000'; Label = 'Radeon RX 6000 (RDNA2)'; Queries = @('VEN_1002&DEV_73BF') }
)
}
@{
Name = 'AMD_Radeon_Pro'
Devices = @(
@{ Key = 'ProW7000'; Label = 'Radeon Pro W7000 (RDNA3)'; Queries = @('VEN_1002&DEV_7448') }
@{ Key = 'ProW6000'; Label = 'Radeon Pro W6000 (RDNA2)'; Queries = @('VEN_1002&DEV_73A3') }
)
}
)
}
30 changes: 30 additions & 0 deletions catalogscrape/intel-gfx.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
@{
# Intel GPU display drivers (DCH) from the Microsoft Update Catalog — single-source, same engine
# as every other scraper. Intel ships a UNIFIED DCH driver: one "Intel Corporation - Display" CAB
# (iigd_dch*.inf + igdkmd*64.sys) binds Arc A-series (DG2/Alchemist), Arc B-series (Battlemage),
# Iris Xe, and recent Core / Core Ultra integrated graphics. So a single device with several
# representative HWID queries dedups to ONE download (the Marvell AQC1xx pattern) and the winning
# INF binds the whole modern lineup. Selection is highest-version-first (catalog date as tiebreak).
#
# SCOPE: POST-BOOT CONVENIENCE only. A GPU cannot serve iSCSI/PXE boot, so nothing here is
# boot-critical and it gets NO boot-start promotion in build_win11pxe.ps1 (unlike the NIC scrapers).
#
# COVERAGE LIMITS (catalog-only, intentional):
# * Catalog lags intel.com by ~1-4 months and is WHQL-only (no beta). Fine for a homelab image.
# * Very old iGPUs (6th-10th gen) sit on a separate legacy branch (intel.com download 19344) and
# are NOT pulled here. Arc Pro ISV-certified drivers are not published to the catalog (out of scope).
# * Drive ONLY by HWID: a bare "Intel ... Display" NAME query returns 1000+ unpaginated rows and the
# engine parses only the first 25 relevance rows; a per-HWID query returns the Display CAB up top.
# Representative DEV ids (VEN_8086): E20B = Arc B580, 56A0 = Arc A770, 9A49 = Iris Xe (Tiger Lake),
# 7D55 = Arc iGPU (Meteor Lake). All resolve to the same unified package.
VendorKey = 'Intel_Display'
Targets = @(
@{
Name = 'Intel_Graphics_DCH'
Devices = @(
@{ Key = 'Arc_Xe'; Label = 'Intel Arc / Iris Xe / UHD (unified DCH)';
Queries = @('VEN_8086&DEV_E20B', 'VEN_8086&DEV_56A0', 'VEN_8086&DEV_9A49', 'VEN_8086&DEV_7D55') }
)
}
)
}
Loading
Loading