fix: inject Setup storage into boot.wim (#5102)

$WinpeDriver$ matched INF names, so RST companions reached Setup. Add SCSIAdapter and HDC packages to boot.wim index 2 with DISM.
This commit is contained in:
Omar
2026-09-28 12:35:50 -05:00
committed by GitHub
parent 311fde6f31
commit abcbc23144
6 changed files with 221 additions and 132 deletions
+1 -1
View File
@@ -183,7 +183,7 @@ When the user corrects an agent approach, add or tighten one concrete rule here
- Have each Pester file load the assemblies and dot-source the functions it needs; several passed only because an earlier file in alphabetical order happened to load them. - Have each Pester file load the assemblies and dot-source the functions it needs; several passed only because an earlier file in alphabetical order happened to load them.
- Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity. - Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity.
- For Win11 Creator, start each new ISO modification in a fresh `WinUtil_Win11ISO_*` temp directory; existing-work detection is only for resuming/exporting already modified media. - For Win11 Creator, start each new ISO modification in a fresh `WinUtil_Win11ISO_*` temp directory; existing-work detection is only for resuming/exporting already modified media.
- For Win11 Creator driver injection, keep offline WIM servicing to one mount and one commit: add each root package folder with its own `/Add-Driver /Recurse` so a single bad driver cannot fail the rest, and skip any folder whose ancestor is already in the set, since that ancestor's `/Recurse` covers it. Warn per failure and commit only when at least one package was added; when none were, warn and discard rather than throwing, so the run still produces an ISO. The discard in the cleanup block carries both orphaned mounts and that intentional zero-added case; keep it. Do not export editions or run unrelated WIM cleanup, and reject damaged metadata before ISO export. Use `-LiteralPath` for driver export paths, since `%TEMP%` can contain wildcard characters. - For Win11 Creator driver injection, inject storage controllers (`SCSIAdapter` / `HDC`) into `boot.wim` index 2 with DISM, not `$WinpeDriver$`. Keep `install.wim` on its own per-package `/Add-Driver` retry loop. For each image, add each root package folder with its own `/Add-Driver /Recurse` so a single bad driver cannot fail the rest, and skip any folder whose ancestor is already in the set, since that ancestor's `/Recurse` covers it. Warn per failure and commit only when at least one package was added; when none were, warn and discard rather than throwing, so the run still produces an ISO. The discard in the cleanup block carries both orphaned mounts and that intentional zero-added case; keep it. Do not export editions or run unrelated WIM cleanup, and reject damaged metadata before ISO export. Use `-LiteralPath` for driver export paths, since `%TEMP%` can contain wildcard characters.
- For Script Analyzer cleanup, fix actionable source warnings first and do not globally suppress accepted convention warnings such as plural names, `ShouldProcess` on UI helpers, `$global:sync`, or compile-time cross-file false positives. - For Script Analyzer cleanup, fix actionable source warnings first and do not globally suppress accepted convention warnings such as plural names, `ShouldProcess` on UI helpers, `$global:sync`, or compile-time cross-file false positives.
- For DNS DHCP reset, keep the cmdlet reset and explicitly set IPv4 and IPv6 DNS source to DHCP. - For DNS DHCP reset, keep the cmdlet reset and explicitly set IPv4 and IPv6 DNS source to DHCP.
- Public pull-request diffs may be sent to configured external review services without a separate privacy approval; do not block the review loop on upload authorization for this public repository. - Public pull-request diffs may be sent to configured external review services without a separate privacy approval; do not block the review loop on upload authorization for this public repository.
@@ -259,9 +259,9 @@ The `Invoke-WinUtilISOScript` function applies **50+ offline registry tweaks**:
### Driver Injection Feature ### Driver Injection Feature
**Optional Enhancement**: When enabled, exports all drivers from the running system and injects them into both: **Optional Enhancement**: When enabled, exports all drivers from the running system and injects them into:
- `install.wim` (main OS image) - `install.wim` (main OS image), excluding stale duplicates
- `boot.wim` index 2 (Windows Setup PE environment) - `boot.wim` index 2 (Windows Setup PE environment), storage controllers only (`SCSIAdapter` / `HDC`)
**Use Case**: Enables offline installation on systems with missing drivers. **Use Case**: Enables offline installation on systems with missing drivers.
@@ -67,7 +67,7 @@ Then click **Run Windows ISO Modification and Creator** to start the customizati
- **Disable Copilot and search box suggestions** - **Disable Copilot and search box suggestions**
**Optional: Driver Injection** **Optional: Driver Injection**
- If enabled, WinUtil exports the drivers from your current system, stages boot-storage drivers for Windows Setup, and injects eligible packages into the selected `install.wim` image. Stale duplicate packages are excluded before injection. Each remaining package is added separately, so one incompatible package does not stop the others. If a package fails, WinUtil discards the partial mount and retries the remaining packages against the original image. Check the live log for warnings: if every package fails, WinUtil keeps the original `install.wim` and still produces the ISO. - If enabled, WinUtil exports the drivers from your current system, injects boot-storage drivers into `boot.wim` (Windows Setup, index 2), and injects eligible packages into the selected `install.wim` image. Stale duplicate packages are excluded before injection. Each remaining package is added separately, so one incompatible package does not stop the others. If a package fails, WinUtil discards the partial mount and retries the remaining packages against the original image. Check the live log for warnings: if every package fails, WinUtil keeps the original `install.wim` and still produces the ISO.
A live log shows progress as each step completes. This stage usually takes **10–30 minutes** depending on disk speed. The WIM dismount near the end is the slowest part, so do not close WinUtil while it is running. A live log shows progress as each step completes. This stage usually takes **10–30 minutes** depending on disk speed. The WIM dismount near the end is the slowest part, so do not close WinUtil while it is running.
+1 -1
View File
@@ -401,7 +401,7 @@ function Invoke-WinUtilISOModify {
if ($driversInjected.Value) { if ($driversInjected.Value) {
Step-WinUtilJob -Status "Finalizing install image..." -Percent 70 Step-WinUtilJob -Status "Finalizing install image..." -Percent 70
Write-WinUtilISOLog "Added current-system drivers to $sourceImageFileName index $SelectedWimIndex with one mount and commit." Write-WinUtilISOLog "Added current-system drivers to $sourceImageFileName index $SelectedWimIndex."
} elseif ($InjectDrivers) { } elseif ($InjectDrivers) {
Step-WinUtilJob -Status "Preserving install image..." -Percent 70 Step-WinUtilJob -Status "Preserving install image..." -Percent 70
Write-WinUtilISOLog "No current-system drivers were injected into $sourceImageFileName index $SelectedWimIndex; install.wim was left unchanged. Review the warning log entries for details." Write-WinUtilISOLog "No current-system drivers were injected into $sourceImageFileName index $SelectedWimIndex; install.wim was left unchanged. Review the warning log entries for details."
+114 -108
View File
@@ -6,7 +6,8 @@ function Invoke-WinUtilISOScript {
.DESCRIPTION .DESCRIPTION
Stages WinUtil's AppX removal, registry tweaks, and scheduled-task cleanup Stages WinUtil's AppX removal, registry tweaks, and scheduled-task cleanup
in the answer file for first logon, writes sources\ei.cfg for the selected in the answer file for first logon, writes sources\ei.cfg for the selected
edition, and optionally adds current-system drivers to one install.wim index. edition, and optionally adds current-system drivers to boot.wim index 2 and
one install.wim index.
.PARAMETER ISOContentsDir .PARAMETER ISOContentsDir
Root directory of the copied ISO contents. Root directory of the copied ISO contents.
@@ -51,31 +52,9 @@ function Invoke-WinUtilISOScript {
) )
$DriversInjected.Value = $false $DriversInjected.Value = $false
function Copy-WinUtilISODriverFolder {
param (
[Parameter(Mandatory)][string]$Source,
[Parameter(Mandatory)][string]$Destination
)
$folderName = Split-Path $Source -Leaf
$targetPath = Join-Path $Destination $folderName
$suffix = 1
while (Test-Path -LiteralPath $targetPath) {
$targetPath = Join-Path $Destination "${folderName}_$suffix"
$suffix++
}
Copy-Item -LiteralPath $Source -Destination $targetPath -Recurse -Force -ErrorAction Stop
return $targetPath
}
function Test-WinUtilISOStorageDriver { function Test-WinUtilISOStorageDriver {
param ([Parameter(Mandatory)][System.IO.FileInfo]$InfFile) param ([Parameter(Mandatory)][System.IO.FileInfo]$InfFile)
if ($InfFile.BaseName -match '(?i)(iaahci|iastor|vmd|irst|rst)') {
return $true
}
try { try {
return (Get-Content -LiteralPath $InfFile.FullName -Raw -ErrorAction Stop) -match '(?im)^\s*Class\s*=\s*(SCSIAdapter|HDC)\s*(?:;.*)?$' return (Get-Content -LiteralPath $InfFile.FullName -Raw -ErrorAction Stop) -match '(?im)^\s*Class\s*=\s*(SCSIAdapter|HDC)\s*(?:;.*)?$'
} catch { } catch {
@@ -267,6 +246,93 @@ function Invoke-WinUtilISOScript {
return @(& dism.exe /English /Get-MountedImageInfo 2>$null) -match [regex]::Escape($Path) return @(& dism.exe /English /Get-MountedImageInfo 2>$null) -match [regex]::Escape($Path)
} }
function Get-WinUtilISODriverFolderName {
param ([Parameter(Mandatory)][string]$DriverFolder)
if ($DriverFolder.StartsWith($driverExportRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
return $DriverFolder.Substring($driverExportRoot.Length).TrimStart('\')
}
return $DriverFolder
}
function Get-WinUtilISORootDriverFolders {
param ([Parameter(Mandatory)][AllowEmptyCollection()][string[]]$DriverFolders)
return @($DriverFolders | Where-Object {
$candidate = $_
-not ($DriverFolders | Where-Object { $candidate.StartsWith("$_\", [System.StringComparison]::OrdinalIgnoreCase) })
})
}
function Add-WinUtilISODriversToImage {
param (
[Parameter(Mandatory)][string]$ImagePath,
[Parameter(Mandatory)][int]$ImageIndex,
[Parameter(Mandatory)][string]$MountDir,
[Parameter(Mandatory)][AllowEmptyCollection()][string[]]$DriverFolders,
[Parameter(Mandatory)][string]$ImageLabel,
[Parameter(Mandatory)][ref]$ImageMounted
)
if ($DriverFolders.Count -eq 0) {
& $Logger "No driver packages to add to ${ImageLabel}."
return 0
}
Set-ItemProperty -LiteralPath $ImagePath -Name IsReadOnly -Value $false
New-Item -Path $MountDir -ItemType Directory -Force | Out-Null
$remainingDriverFolders = @($DriverFolders)
while ($remainingDriverFolders.Count -gt 0) {
& $Logger "Mounting $ImageLabel index $ImageIndex for driver injection..."
Invoke-WinUtilISODism -Arguments @('/English', '/Mount-Image', "/ImageFile:$ImagePath", "/Index:$ImageIndex", "/MountDir:$MountDir") -Operation 'mount' | Out-Null
$ImageMounted.Value = $true
$failedDriverFolder = $null
$driverName = $null
foreach ($driverFolder in $remainingDriverFolders) {
$driverName = Get-WinUtilISODriverFolderName -DriverFolder $driverFolder
try {
Invoke-WinUtilISODism -Arguments @('/English', "/Image:$MountDir", '/Add-Driver', "/Driver:$driverFolder", '/Recurse') -Operation "add-driver:$driverName" | Out-Null
} catch {
& $Logger "Warning: failed to add driver package '$driverName': $_"
$failedDriverFolder = $driverFolder
break
}
}
if (-not $failedDriverFolder) {
break
}
& $Logger "Discarding the potentially partial $ImageLabel mount before continuing without '$driverName'."
try {
Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$MountDir", '/Discard') -Operation 'discard' | Out-Null
$ImageMounted.Value = $false
} catch {
throw "Failed to discard the potentially partial $ImageLabel mount after driver package '$driverName' failed: $_"
}
$remainingDriverFolders = @($remainingDriverFolders | Where-Object { $_ -ne $failedDriverFolder })
}
$addedCount = $remainingDriverFolders.Count
if ($addedCount -eq 0) {
if ($ImageLabel -eq 'install.wim') {
& $Logger "Warning: none of the $($DriverFolders.Count) exported driver packages could be added; continuing with an unmodified install.wim."
} else {
& $Logger "Warning: none of the $($DriverFolders.Count) driver packages could be added to ${ImageLabel}."
}
return 0
}
& $Logger "Added $addedCount of $($DriverFolders.Count) driver packages to ${ImageLabel}."
& $Logger "Committing the driver-only $ImageLabel change..."
Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$MountDir", '/Commit') -Operation 'commit' | Out-Null
$ImageMounted.Value = $false
return $addedCount
}
if ([IO.Path]::GetExtension($InstallImagePath) -ne '.wim') { if ([IO.Path]::GetExtension($InstallImagePath) -ne '.wim') {
throw 'Current-system driver injection requires install.wim; install.esd cannot be serviced in place.' throw 'Current-system driver injection requires install.wim; install.esd cannot be serviced in place.'
} }
@@ -287,7 +353,7 @@ function Invoke-WinUtilISOScript {
$imageMounted = $false $imageMounted = $false
try { try {
& $Logger "Exporting current system drivers before modifying install.wim..." & $Logger "Exporting current system drivers before WIM driver injection..."
$dismLog = Join-Path $env:TEMP "WinUtil_DismDriverExport_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" $dismLog = Join-Path $env:TEMP "WinUtil_DismDriverExport_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
Invoke-WinUtilISODism -Arguments @('/English', '/Online', '/Export-Driver', "/Destination:$driverExportRoot", "/LogPath:$dismLog") -Operation 'export-driver' | Out-Null Invoke-WinUtilISODism -Arguments @('/English', '/Online', '/Export-Driver', "/Destination:$driverExportRoot", "/LogPath:$dismLog") -Operation 'export-driver' | Out-Null
@@ -296,32 +362,6 @@ function Invoke-WinUtilISOScript {
throw 'DISM exported no driver INF files.' throw 'DISM exported no driver INF files.'
} }
$driverFolders = @($driverInfs | Group-Object { $_.Directory.FullName }) $driverFolders = @($driverInfs | Group-Object { $_.Directory.FullName })
$winpeDriverDir = Join-Path $ContentRoot '$WinpeDriver$'
$storageCount = 0
$copyFailures = 0
foreach ($driverFolderGroup in $driverFolders) {
$driverFolder = [string]$driverFolderGroup.Name
$storageInfs = @($driverFolderGroup.Group | Where-Object { Test-WinUtilISOStorageDriver -InfFile $_ })
if ($storageInfs.Count -eq 0) {
continue
}
try {
New-Item -Path $winpeDriverDir -ItemType Directory -Force | Out-Null
$winpeTarget = Copy-WinUtilISODriverFolder -Source $driverFolder -Destination $winpeDriverDir
$storageCount++
& $Logger "Staged boot-storage package '$driverFolder' for WinPE as '$winpeTarget'."
} catch {
$copyFailures++
& $Logger "Warning: failed to stage boot-storage package '$driverFolder': $_"
}
}
if ($copyFailures -gt 0) {
throw "Failed to stage $copyFailures boot-storage driver package folders."
}
$stagedDriverFolders = @(Select-WinUtilISOStagedDriverPackages -DriverFolderGroups $driverFolders -Logger $Logger) $stagedDriverFolders = @(Select-WinUtilISOStagedDriverPackages -DriverFolderGroups $driverFolders -Logger $Logger)
$metadataBefore = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex $metadataBefore = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex
Assert-WinUtilISOWimMetadata -Before $metadataBefore Assert-WinUtilISOWimMetadata -Before $metadataBefore
@@ -352,67 +392,33 @@ function Invoke-WinUtilISOScript {
} }
} }
& $Logger "Exported $($stagedDriverFolders.Count) of $($driverFolders.Count) driver packages ($storageCount staged for WinPE, $($excludedDriverFolderGroups.Count) excluded)." & $Logger "Exported $($stagedDriverFolders.Count) of $($driverFolders.Count) driver packages ($($excludedDriverFolderGroups.Count) excluded)."
Set-ItemProperty -LiteralPath $InstallImagePath -Name IsReadOnly -Value $false # Storage for Setup comes from the same surviving set as install.wim, so stale
New-Item -Path $mountDir -ItemType Directory -Force | Out-Null # duplicates never reach boot.wim.
$storageFolders = @(
$driverFolders |
Where-Object { $_.Name -in $stagedDriverFolders } |
Where-Object { @($_.Group | Where-Object { Test-WinUtilISOStorageDriver -InfFile $_ }).Count -gt 0 } |
ForEach-Object { [string]$_.Name }
)
$storageRootFolders = @(Get-WinUtilISORootDriverFolders -DriverFolders $storageFolders)
$rootPackageFolders = @(Get-WinUtilISORootDriverFolders -DriverFolders $stagedDriverFolders)
$imageMountedRef = [ref]$imageMounted
# Add each package separately so one bad driver cannot fail the rest. Because $bootWim = Join-Path $ContentRoot 'sources\boot.wim'
# /Recurse covers descendants, only the highest surviving folder in each tree if ($storageRootFolders.Count -gt 0) {
# needs its own DISM call. if (Test-Path -LiteralPath $bootWim) {
$rootPackageFolders = @($stagedDriverFolders | Where-Object { & $Logger "Adding $($storageRootFolders.Count) root storage driver packages to boot.wim."
$candidate = $_ $null = Add-WinUtilISODriversToImage -ImagePath $bootWim -ImageIndex 2 -MountDir $mountDir -DriverFolders $storageRootFolders -ImageLabel 'boot.wim' -ImageMounted $imageMountedRef
-not ($stagedDriverFolders | Where-Object { $candidate.StartsWith("$_\", [System.StringComparison]::OrdinalIgnoreCase) }) } else {
}) & $Logger 'Warning: boot.wim was not found; Windows Setup will not have injected storage drivers.'
}
}
& $Logger "Adding $($rootPackageFolders.Count) root driver packages to install.wim." & $Logger "Adding $($rootPackageFolders.Count) root driver packages to install.wim."
$remainingDriverFolders = @($rootPackageFolders) $addedCount = Add-WinUtilISODriversToImage -ImagePath $InstallImagePath -ImageIndex $InstallImageIndex -MountDir $mountDir -DriverFolders $rootPackageFolders -ImageLabel 'install.wim' -ImageMounted $imageMountedRef
while ($remainingDriverFolders.Count -gt 0) { if ($addedCount -gt 0) {
& $Logger "Mounting install.wim index $InstallImageIndex for driver injection..."
Invoke-WinUtilISODism -Arguments @('/English', '/Mount-Image', "/ImageFile:$InstallImagePath", "/Index:$InstallImageIndex", "/MountDir:$mountDir") -Operation 'mount' | Out-Null
$imageMounted = $true
$failedDriverFolder = $null
foreach ($driverFolder in $remainingDriverFolders) {
$driverName = $driverFolder
if ($driverFolder.StartsWith($driverExportRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
$driverName = $driverFolder.Substring($driverExportRoot.Length).TrimStart('\')
}
try {
Invoke-WinUtilISODism -Arguments @('/English', "/Image:$mountDir", '/Add-Driver', "/Driver:$driverFolder", '/Recurse') -Operation "add-driver:$driverName" | Out-Null
} catch {
& $Logger "Warning: failed to add driver package '$driverName': $_"
$failedDriverFolder = $driverFolder
break
}
}
if (-not $failedDriverFolder) {
break
}
& $Logger "Discarding the potentially partial install.wim mount before continuing without '$driverName'."
try {
Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$mountDir", '/Discard') -Operation 'discard' | Out-Null
$imageMounted = $false
} catch {
throw "Failed to discard the potentially partial install.wim mount after driver package '$driverName' failed: $_"
}
$remainingDriverFolders = @($remainingDriverFolders | Where-Object { $_ -ne $failedDriverFolder })
}
$addedCount = $remainingDriverFolders.Count
if ($addedCount -eq 0) {
# Boot-storage drivers staged for WinPE remain available to Windows Setup.
& $Logger "Warning: none of the $($rootPackageFolders.Count) exported driver packages could be added; continuing with an unmodified install.wim."
} else {
& $Logger "Added $addedCount of $($rootPackageFolders.Count) driver packages to install.wim."
& $Logger 'Committing the driver-only install.wim change...'
Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$mountDir", '/Commit') -Operation 'commit' | Out-Null
$imageMounted = $false
$metadataAfter = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex $metadataAfter = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex
Assert-WinUtilISOWimMetadata -Before $metadataBefore -After $metadataAfter Assert-WinUtilISOWimMetadata -Before $metadataBefore -After $metadataAfter
& $Logger 'Driver injection complete; install.wim metadata validation passed.' & $Logger 'Driver injection complete; install.wim metadata validation passed.'
@@ -423,7 +429,7 @@ function Invoke-WinUtilISOScript {
try { try {
Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$mountDir", '/Discard') -Operation 'discard' | Out-Null Invoke-WinUtilISODism -Arguments @('/English', '/Unmount-Image', "/MountDir:$mountDir", '/Discard') -Operation 'discard' | Out-Null
} catch { } catch {
& $Logger "Warning: could not discard the failed install.wim mount: $_" & $Logger "Warning: could not discard the failed WIM mount: $_"
} }
} }
Remove-Item -LiteralPath $mountDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $mountDir -Recurse -Force -ErrorAction SilentlyContinue
+102 -19
View File
@@ -214,7 +214,7 @@ Describe "Win11 Creator setup media" {
$script:modifyFunction | Should -Match 'if \(\$m -like "Warning:\*"\)[\s\S]*Write-WinUtilISOLog -Level "WARN" -Message \$m -SkipSessionLog[\s\S]*Write-Warning \$m' $script:modifyFunction | Should -Match 'if \(\$m -like "Warning:\*"\)[\s\S]*Write-WinUtilISOLog -Level "WARN" -Message \$m -SkipSessionLog[\s\S]*Write-Warning \$m'
} }
It "keeps WIM servicing limited to one driver-only mount and commit" { It "keeps WIM servicing limited to DISM Add-Driver without image export or cleanup" {
$isoScriptContent = Get-Content -Path $script:isoScriptPath -Raw $isoScriptContent = Get-Content -Path $script:isoScriptPath -Raw
foreach ($expectedText in @( foreach ($expectedText in @(
@@ -222,7 +222,8 @@ Describe "Win11 Creator setup media" {
"'/Add-Driver'", "'/Add-Driver'",
"'/Commit'", "'/Commit'",
"`$mountDir = Join-Path (Split-Path -Path `$ContentRoot -Parent) 'wim_mount'", "`$mountDir = Join-Path (Split-Path -Path `$ContentRoot -Parent) 'wim_mount'",
'install.wim metadata validation passed' 'install.wim metadata validation passed',
"Join-Path `$ContentRoot 'sources\boot.wim'"
)) { )) {
$isoScriptContent | Should -Match ([regex]::Escape($expectedText)) $isoScriptContent | Should -Match ([regex]::Escape($expectedText))
} }
@@ -233,17 +234,19 @@ Describe "Win11 Creator setup media" {
'Export-WindowsImage', 'Export-WindowsImage',
'Set-WindowsImage', 'Set-WindowsImage',
'/ResetBase', '/ResetBase',
'/Cleanup-Image' '/Cleanup-Image',
'$WinpeDriver$'
)) { )) {
$isoScriptContent | Should -Not -Match ([regex]::Escape($forbiddenText)) $isoScriptContent | Should -Not -Match ([regex]::Escape($forbiddenText))
} }
} }
It "stages only boot-storage drivers in WinPE" { It "injects only SCSIAdapter or HDC storage drivers into boot.wim" {
$isoScriptContent = Get-Content -Path $script:isoScriptPath -Raw $isoScriptContent = Get-Content -Path $script:isoScriptPath -Raw
$isoScriptContent | Should -Match ([regex]::Escape("Join-Path `$ContentRoot '`$WinpeDriver$'"))
$isoScriptContent | Should -Match 'SCSIAdapter\|HDC' $isoScriptContent | Should -Match 'SCSIAdapter\|HDC'
$isoScriptContent | Should -Match ([regex]::Escape("Join-Path `$ContentRoot 'sources\boot.wim'"))
$isoScriptContent | Should -Not -Match ([regex]::Escape("Join-Path `$ContentRoot '`$WinpeDriver$'"))
$isoScriptContent | Should -Not -Match ([regex]::Escape('sources\$OEM$\$$\Drivers')) $isoScriptContent | Should -Not -Match ([regex]::Escape('sources\$OEM$\$$\Drivers'))
$isoScriptContent | Should -Not -Match ([regex]::Escape('WinUtil-InstallDrivers.ps1')) $isoScriptContent | Should -Not -Match ([regex]::Escape('WinUtil-InstallDrivers.ps1'))
$isoScriptContent | Should -Not -Match ([regex]::Escape('SetupComplete.cmd')) $isoScriptContent | Should -Not -Match ([regex]::Escape('SetupComplete.cmd'))
@@ -435,7 +438,7 @@ Describe "Win11 Creator setup media" {
It "stages storage drivers for WinPE and adds all drivers to one install.wim index" { It "adds eligible drivers to one install.wim index and does not create `$WinpeDriver$" {
$contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoDrivers_$([guid]::NewGuid())" $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoDrivers_$([guid]::NewGuid())"
$installWim = Join-Path $contentRoot 'sources\install.wim' $installWim = Join-Path $contentRoot 'sources\install.wim'
$template = Get-Content -Path $script:autoUnattendPath -Raw $template = Get-Content -Path $script:autoUnattendPath -Raw
@@ -443,7 +446,7 @@ Describe "Win11 Creator setup media" {
New-WinUtilDriverExportHarness -Fixtures @( New-WinUtilDriverExportHarness -Fixtures @(
@{ Path = 'system_pkg'; Name = 'chipset.inf'; Class = 'System' }, @{ Path = 'system_pkg'; Name = 'chipset.inf'; Class = 'System' },
@{ Path = 'storage_pkg'; Name = 'iaStorAC.inf'; Class = 'System' }, @{ Path = 'storage_pkg'; Name = 'iaStorAC.inf'; Class = 'SCSIAdapter' },
@{ Path = 'scsi_pkg'; Name = 'controller.inf'; Class = 'SCSIAdapter' }, @{ Path = 'scsi_pkg'; Name = 'controller.inf'; Class = 'SCSIAdapter' },
@{ Path = 'net_pkg'; Name = 'network.inf'; Class = 'Net' }, @{ Path = 'net_pkg'; Name = 'network.inf'; Class = 'Net' },
@{ Path = 'group_a\duplicate'; Name = 'audio.inf'; Class = 'Media' }, @{ Path = 'group_a\duplicate'; Name = 'audio.inf'; Class = 'Media' },
@@ -465,12 +468,8 @@ Describe "Win11 Creator setup media" {
$logs.Add([string]$message) $logs.Add([string]$message)
} }
$winpeDriverRoot = Join-Path $contentRoot '$WinpeDriver$' Test-Path (Join-Path $contentRoot '$WinpeDriver$') | Should -BeFalse
@(Get-ChildItem -Path $winpeDriverRoot -Directory).Count | Should -Be 2 ($logs -join '|') | Should -Match 'Warning: boot.wim was not found'
Test-Path (Join-Path $winpeDriverRoot 'system_pkg\chipset.inf') | Should -BeFalse
Test-Path (Join-Path $winpeDriverRoot 'storage_pkg\iaStorAC.inf') | Should -BeTrue
Test-Path (Join-Path $winpeDriverRoot 'scsi_pkg\controller.inf') | Should -BeTrue
Test-Path (Join-Path $winpeDriverRoot 'net_pkg\network.inf') | Should -BeFalse
@($script:dismCalls | Where-Object { $_ -match '/Mount-Image' }).Count | Should -Be 1 @($script:dismCalls | Where-Object { $_ -match '/Mount-Image' }).Count | Should -Be 1
@($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 9 @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 9
@@ -488,7 +487,7 @@ Describe "Win11 Creator setup media" {
$nsMgr = New-Object System.Xml.XmlNamespaceManager($answerFile.NameTable) $nsMgr = New-Object System.Xml.XmlNamespaceManager($answerFile.NameTable)
$nsMgr.AddNamespace('sg', 'https://schneegans.de/windows/unattend-generator/') $nsMgr.AddNamespace('sg', 'https://schneegans.de/windows/unattend-generator/')
$answerFile.SelectSingleNode('//sg:File[@path="C:\Windows\Setup\Scripts\WinUtil-InstallDrivers.ps1"]', $nsMgr) | Should -BeNullOrEmpty $answerFile.SelectSingleNode('//sg:File[@path="C:\Windows\Setup\Scripts\WinUtil-InstallDrivers.ps1"]', $nsMgr) | Should -BeNullOrEmpty
($logs -join '|') | Should -Match 'Exported 10 of 11 driver packages \(2 staged for WinPE, 1 excluded\)' ($logs -join '|') | Should -Match 'Exported 10 of 11 driver packages \(1 excluded\)'
($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_7426e1b60aa62272' \(DriverVer 1/1/2023,10\.0\.26100\.8875\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e' \(DriverVer 6/1/2024,10\.0\.26100\.9168\)" ($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_7426e1b60aa62272' \(DriverVer 1/1/2023,10\.0\.26100\.8875\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e' \(DriverVer 6/1/2024,10\.0\.26100\.9168\)"
($logs -join '|') | Should -Match 'install.wim metadata validation passed' ($logs -join '|') | Should -Match 'install.wim metadata validation passed'
($logs -join '|') | Should -Match 'DISM mount completed.' ($logs -join '|') | Should -Match 'DISM mount completed.'
@@ -505,6 +504,91 @@ Describe "Win11 Creator setup media" {
} }
} }
It "injects SCSIAdapter storage drivers into boot.wim index 2 and not `$WinpeDriver`$" {
$contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoBootWim_$([guid]::NewGuid())"
$installWim = Join-Path $contentRoot 'sources\install.wim'
$bootWim = Join-Path $contentRoot 'sources\boot.wim'
$template = Get-Content -Path $script:autoUnattendPath -Raw
$logs = [System.Collections.Generic.List[string]]::new()
New-WinUtilDriverExportHarness -Fixtures @(
@{ Path = 'net_pkg'; Name = 'network.inf'; Class = 'Net' },
@{ Path = 'scsi_pkg'; Name = 'controller.inf'; Class = 'SCSIAdapter' },
@{ Path = 'hdc_pkg'; Name = 'ide.inf'; Class = 'HDC' },
@{ Path = 'name_only_pkg'; Name = 'iaStorAC.inf'; Class = 'System' },
@{ Path = 'iastorhsacomponent.inf_amd64_1b2a068a8496b6a2'; Name = 'iaStorHsaComponent.inf'; Class = 'SoftwareComponent' },
@{ Path = 'iastorhsa_ext.inf_amd64_ba71359697f80d4e'; Name = 'iaStorHsa_Ext.inf'; Class = 'Extension' }
)
try {
New-Item -Path (Split-Path $installWim -Parent) -ItemType Directory -Force | Out-Null
Set-Content -Path $installWim -Value 'mock-wim'
Set-Content -Path $bootWim -Value 'mock-boot'
. $script:isoScriptPath
Invoke-WinUtilISOScript -ISOContentsDir $contentRoot -AutoUnattendXml $template -InjectCurrentSystemDrivers $true -InstallImagePath $installWim -InstallImageIndex 6 -InstallEditionId 'Professional' -Log {
param($message)
$logs.Add([string]$message)
}
Test-Path (Join-Path $contentRoot '$WinpeDriver$') | Should -BeFalse
@($script:dismCalls | Where-Object { $_ -match '/Mount-Image' -and $_ -match 'boot\.wim' -and $_ -match '/Index:2' }).Count | Should -Be 1
@($script:dismCalls | Where-Object { $_ -match '/Mount-Image' -and $_ -match 'install\.wim' }).Count | Should -Be 1
@($script:dismCalls | Where-Object { $_ -match '/Unmount-Image\|.*\|/Commit' }).Count | Should -Be 2
$bootMountCall = $script:dismCalls | Where-Object { $_ -match '/Mount-Image' -and $_ -match 'boot\.wim' } | Select-Object -First 1
$installMountCall = $script:dismCalls | Where-Object { $_ -match '/Mount-Image' -and $_ -match 'install\.wim' } | Select-Object -First 1
$bootMountIndex = [Array]::IndexOf($script:dismCalls.ToArray(), $bootMountCall)
$installMountIndex = [Array]::IndexOf($script:dismCalls.ToArray(), $installMountCall)
$bootAdds = @($script:dismCalls[$bootMountIndex..($installMountIndex - 1)] | Where-Object { $_ -match '/Add-Driver' })
$bootAdds.Count | Should -Be 2
($bootAdds -join "`n") | Should -Match ([regex]::Escape('scsi_pkg'))
($bootAdds -join "`n") | Should -Match ([regex]::Escape('hdc_pkg'))
($bootAdds -join "`n") | Should -Not -Match ([regex]::Escape('net_pkg'))
($bootAdds -join "`n") | Should -Not -Match 'iastorhsa'
($bootAdds -join "`n") | Should -Not -Match ([regex]::Escape('name_only_pkg'))
@($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 8
($logs -join '|') | Should -Match 'Added 2 of 2 driver packages to boot.wim'
($logs -join '|') | Should -Match 'Added 6 of 6 driver packages to install.wim'
} finally {
Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue
Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
It "injects only the newest duplicate storage package into boot.wim" {
$contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoBootWimDedup_$([guid]::NewGuid())"
$installWim = Join-Path $contentRoot 'sources\install.wim'
$bootWim = Join-Path $contentRoot 'sources\boot.wim'
$template = Get-Content -Path $script:autoUnattendPath -Raw
$logs = [System.Collections.Generic.List[string]]::new()
New-WinUtilDriverExportHarness -Fixtures @(
@{ Path = 'iastorvd.inf_amd64_11111111aaaaaaaa'; Name = 'iaStorVD.inf'; Class = 'SCSIAdapter'; DriverVer = '1/1/2023,20.2.1.1016' },
@{ Path = 'iastorvd.inf_amd64_22222222bbbbbbbb'; Name = 'iaStorVD.inf'; Class = 'SCSIAdapter'; DriverVer = '6/1/2024,20.2.8.1028' }
)
try {
New-Item -Path (Split-Path $installWim -Parent) -ItemType Directory -Force | Out-Null
Set-Content -Path $installWim -Value 'mock-wim'
Set-Content -Path $bootWim -Value 'mock-boot'
. $script:isoScriptPath
Invoke-WinUtilISOScript -ISOContentsDir $contentRoot -AutoUnattendXml $template -InjectCurrentSystemDrivers $true -InstallImagePath $installWim -InstallImageIndex 6 -InstallEditionId 'Professional' -Log {
param($message)
$logs.Add([string]$message)
}
$bootAdds = @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' })
@($bootAdds | Where-Object { $_ -match '22222222bbbbbbbb' }).Count | Should -Be 2
@($bootAdds | Where-Object { $_ -match '11111111aaaaaaaa' }).Count | Should -Be 0
($logs -join '|') | Should -Match 'Added 1 of 1 driver packages to boot.wim'
($logs -join '|') | Should -Match 'Added 1 of 1 driver packages to install.wim'
} finally {
Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue
Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}
It "injects Class=Extension driver packages like any other export" { It "injects Class=Extension driver packages like any other export" {
$contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoExtensionInject_$([guid]::NewGuid())" $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoExtensionInject_$([guid]::NewGuid())"
$installWim = Join-Path $contentRoot 'sources\install.wim' $installWim = Join-Path $contentRoot 'sources\install.wim'
@@ -528,7 +612,7 @@ Describe "Win11 Creator setup media" {
} }
@($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 3 @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 3
($logs -join '|') | Should -Match 'Exported 3 of 3 driver packages \(0 staged for WinPE, 0 excluded\)' ($logs -join '|') | Should -Match 'Exported 3 of 3 driver packages \(0 excluded\)'
$driversInjected.Value | Should -BeTrue $driversInjected.Value | Should -BeTrue
$script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'ext_pkg_lower') $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'ext_pkg_lower')
@@ -628,7 +712,7 @@ Describe "Win11 Creator setup media" {
} }
@($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 2 @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 2
($logs -join '|') | Should -Match 'Exported 2 of 2 driver packages \(0 staged for WinPE, 0 excluded\)' ($logs -join '|') | Should -Match 'Exported 2 of 2 driver packages \(0 excluded\)'
($logs -join '|') | Should -Not -Match 'Excluding stale duplicate driver package' ($logs -join '|') | Should -Not -Match 'Excluding stale duplicate driver package'
$script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'device.inf_amd64_11111111aaaaaaaa') $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'device.inf_amd64_11111111aaaaaaaa')
@@ -669,7 +753,7 @@ Describe "Win11 Creator setup media" {
} }
@($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 3 @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 3
($logs -join '|') | Should -Match 'Exported 3 of 7 driver packages \(0 staged for WinPE, 4 excluded\)' ($logs -join '|') | Should -Match 'Exported 3 of 7 driver packages \(4 excluded\)'
($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_7426e1b60aa62272' \(DriverVer 1/1/2023,10\.0\.26100\.8875\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e'" ($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_7426e1b60aa62272' \(DriverVer 1/1/2023,10\.0\.26100\.8875\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e'"
($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_6688e7b66f8d9fb5' \(DriverVer 1/1/2024,10\.0\.26100\.8972\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e'" ($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*ntprint\.inf_x86_6688e7b66f8d9fb5' \(DriverVer 1/1/2024,10\.0\.26100\.8972\) superseded by '.*ntprint\.inf_x86_58e7118cdecb935e'"
($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*sample\.inf_amd64_11111111aaaaaaaa' \(DriverVer unknown\) superseded by '.*sample\.inf_amd64_22222222bbbbbbbb' \(DriverVer 3/1/2024,1\.2\.3\.4\)" ($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*sample\.inf_amd64_11111111aaaaaaaa' \(DriverVer unknown\) superseded by '.*sample\.inf_amd64_22222222bbbbbbbb' \(DriverVer 3/1/2024,1\.2\.3\.4\)"
@@ -753,8 +837,7 @@ Describe "Win11 Creator setup media" {
($logs -join '|') | Should -Match "none of the $script:expectedRootPackages exported driver packages could be added" ($logs -join '|') | Should -Match "none of the $script:expectedRootPackages exported driver packages could be added"
($logs -join '|') | Should -Not -Match "Added 0 of $script:expectedRootPackages" ($logs -join '|') | Should -Not -Match "Added 0 of $script:expectedRootPackages"
# WinPE staging is independent of WIM servicing, so it must survive the failure. Test-Path (Join-Path $contentRoot '$WinpeDriver$') | Should -BeFalse
@(Get-ChildItem -Path (Join-Path $contentRoot '$WinpeDriver$') -Directory).Count | Should -Be 2
$driversInjected.Value | Should -BeFalse $driversInjected.Value | Should -BeFalse
} finally { } finally {
Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue