diff --git a/docs/src/content/docs/guides/win11creator.mdx b/docs/src/content/docs/guides/win11creator.mdx index cb45363f..55ddc710 100644 --- a/docs/src/content/docs/guides/win11creator.mdx +++ b/docs/src/content/docs/guides/win11creator.mdx @@ -67,7 +67,7 @@ Then click **Run Windows ISO Modification and Creator** to start the customizati - **Disable Copilot and search box suggestions** **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. Extension-class packages and stale duplicates are excluded. 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, 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. 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. diff --git a/functions/private/Invoke-WinUtilISOScript.ps1 b/functions/private/Invoke-WinUtilISOScript.ps1 index d996a816..2b046d05 100644 --- a/functions/private/Invoke-WinUtilISOScript.ps1 +++ b/functions/private/Invoke-WinUtilISOScript.ps1 @@ -84,17 +84,6 @@ function Invoke-WinUtilISOScript { } } - function Test-WinUtilISODriverExtensionClass { - param ([Parameter(Mandatory)][System.IO.FileInfo]$InfFile) - - try { - return (Get-Content -LiteralPath $InfFile.FullName -Raw -ErrorAction Stop) -match '(?im)^\s*Class\s*=\s*"?Extension"?\s*(?:;.*)?$' - } catch { - $null = & $Logger "Warning: could not classify driver '$($InfFile.FullName)': $_" - return $false - } - } - function Get-WinUtilISODriverPackageVersion { param ([Parameter(Mandatory)][System.IO.FileInfo]$InfFile) @@ -160,15 +149,6 @@ function Invoke-WinUtilISOScript { foreach ($driverFolderGroup in $DriverFolderGroups) { $driverFolder = [string]$driverFolderGroup.Name - $isExtension = [bool]@($driverFolderGroup.Group | Where-Object { Test-WinUtilISODriverExtensionClass -InfFile $_ }).Count - - if ($isExtension) { - # $null = discards $Logger's own output; this function's return value is captured - # by the caller, and an emitting logger (e.g. this function's own default) would - # otherwise leak into the surviving-folder list. - $null = & $Logger "Excluding extension-class driver package '$driverFolder' from Add-Driver (Class=Extension is not a serviceable hardware driver)." - continue - } # DISM names exported package folders __; grouping on infname+arch # (dropping the hash) is what lets us recognize two exports of the same driver. When a @@ -346,103 +326,97 @@ function Invoke-WinUtilISOScript { $metadataBefore = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex Assert-WinUtilISOWimMetadata -Before $metadataBefore - if ($stagedDriverFolders.Count -eq 0) { - # Nothing safe to inject (e.g. every exported package was an Extension-class add-on) - # isn't a failure: leave install.wim untouched and continue building the ISO. - & $Logger 'No drivers found to inject: every exported package was excluded (Extension class or stale duplicate). Skipping driver injection; install.wim is unchanged.' - } else { - $excludedDriverFolderGroups = @($driverFolders | Where-Object { $_.Name -notin $stagedDriverFolders }) - foreach ($excludedDriverFolderGroup in $excludedDriverFolderGroups) { - $excludedFolder = [string]$excludedDriverFolderGroup.Name - $hasRetainedDescendant = [bool]@($stagedDriverFolders | Where-Object { - $_.StartsWith("$excludedFolder\", [System.StringComparison]::OrdinalIgnoreCase) - }).Count - if ($hasRetainedDescendant) { - try { - foreach ($excludedInf in $excludedDriverFolderGroup.Group) { - Remove-Item -LiteralPath $excludedInf.FullName -Force -ErrorAction Stop - } - } catch { - throw "Failed to remove excluded driver INF files from package '$excludedFolder' before injection: $_" + $excludedDriverFolderGroups = @($driverFolders | Where-Object { $_.Name -notin $stagedDriverFolders }) + foreach ($excludedDriverFolderGroup in $excludedDriverFolderGroups) { + $excludedFolder = [string]$excludedDriverFolderGroup.Name + $hasRetainedDescendant = [bool]@($stagedDriverFolders | Where-Object { + $_.StartsWith("$excludedFolder\", [System.StringComparison]::OrdinalIgnoreCase) + }).Count + if ($hasRetainedDescendant) { + try { + foreach ($excludedInf in $excludedDriverFolderGroup.Group) { + Remove-Item -LiteralPath $excludedInf.FullName -Force -ErrorAction Stop } + } catch { + throw "Failed to remove excluded driver INF files from package '$excludedFolder' before injection: $_" + } - & $Logger "Keeping excluded driver package directory '$excludedFolder' because it contains a retained nested package, after removing its excluded INF files." - continue + & $Logger "Keeping excluded driver package directory '$excludedFolder' because it contains a retained nested package, after removing its excluded INF files." + continue + } + + try { + Remove-Item -LiteralPath $excludedFolder -Recurse -Force -ErrorAction Stop + } catch { + throw "Failed to remove excluded driver package '$excludedFolder' before injection: $_" + } + } + + & $Logger "Exported $($stagedDriverFolders.Count) of $($driverFolders.Count) driver packages ($storageCount staged for WinPE, $($excludedDriverFolderGroups.Count) excluded)." + + Set-ItemProperty -LiteralPath $InstallImagePath -Name IsReadOnly -Value $false + New-Item -Path $mountDir -ItemType Directory -Force | Out-Null + + # Add each package separately so one bad driver cannot fail the rest. Because + # /Recurse covers descendants, only the highest surviving folder in each tree + # needs its own DISM call. + $rootPackageFolders = @($stagedDriverFolders | Where-Object { + $candidate = $_ + -not ($stagedDriverFolders | Where-Object { $candidate.StartsWith("$_\", [System.StringComparison]::OrdinalIgnoreCase) }) + }) + + & $Logger "Adding $($rootPackageFolders.Count) root driver packages to install.wim." + $remainingDriverFolders = @($rootPackageFolders) + while ($remainingDriverFolders.Count -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 { - Remove-Item -LiteralPath $excludedFolder -Recurse -Force -ErrorAction Stop + Invoke-WinUtilISODism -Arguments @('/English', "/Image:$mountDir", '/Add-Driver', "/Driver:$driverFolder", '/Recurse') -Operation "add-driver:$driverName" | Out-Null } catch { - throw "Failed to remove excluded driver package '$excludedFolder' before injection: $_" - } - } - - & $Logger "Exported $($stagedDriverFolders.Count) of $($driverFolders.Count) driver packages ($storageCount staged for WinPE, $($excludedDriverFolderGroups.Count) excluded)." - - Set-ItemProperty -LiteralPath $InstallImagePath -Name IsReadOnly -Value $false - New-Item -Path $mountDir -ItemType Directory -Force | Out-Null - - # Add each package separately so one bad driver cannot fail the rest. Because - # /Recurse covers descendants, only the highest surviving folder in each tree - # needs its own DISM call. - $rootPackageFolders = @($stagedDriverFolders | Where-Object { - $candidate = $_ - -not ($stagedDriverFolders | Where-Object { $candidate.StartsWith("$_\", [System.StringComparison]::OrdinalIgnoreCase) }) - }) - - & $Logger "Adding $($rootPackageFolders.Count) root driver packages to install.wim." - $remainingDriverFolders = @($rootPackageFolders) - while ($remainingDriverFolders.Count -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) { + & $Logger "Warning: failed to add driver package '$driverName': $_" + $failedDriverFolder = $driverFolder 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 + 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 - - $metadataAfter = Get-WinUtilISOWimMetadata -ImagePath $InstallImagePath -Index $InstallImageIndex - Assert-WinUtilISOWimMetadata -Before $metadataBefore -After $metadataAfter - & $Logger 'Driver injection complete; install.wim metadata validation passed.' - $DriversInjected.Value = $true + } 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 + Assert-WinUtilISOWimMetadata -Before $metadataBefore -After $metadataAfter + & $Logger 'Driver injection complete; install.wim metadata validation passed.' + $DriversInjected.Value = $true } } finally { if ($imageMounted -or (Test-WinUtilISOMountedImage -Path $mountDir)) { diff --git a/pester/win11creator.Tests.ps1 b/pester/win11creator.Tests.ps1 index eb904b4a..e100949a 100644 --- a/pester/win11creator.Tests.ps1 +++ b/pester/win11creator.Tests.ps1 @@ -473,11 +473,11 @@ Describe "Win11 Creator setup media" { 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 '/Add-Driver' }).Count | Should -Be $script:expectedRootPackages + @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 9 @($script:dismCalls | Where-Object { $_ -match '/Unmount-Image\|.*\|/Commit' }).Count | Should -Be 1 @($script:dismCalls | Where-Object { $_ -match '/Get-WimInfo' }).Count | Should -Be 2 ($script:dismCalls -join "`n") | Should -Not -Match '/Cleanup-Image|/Export-Image' - ($logs -join '|') | Should -Match "Added $script:expectedRootPackages of $script:expectedRootPackages driver packages" + ($logs -join '|') | Should -Match "Added 9 of 9 driver packages" # nested_pkg holds INFs at its root and under x64. The root add already recurses # into x64, so the subfolder must not get an /Add-Driver call of its own. @@ -488,14 +488,13 @@ Describe "Win11 Creator setup media" { $nsMgr = New-Object System.Xml.XmlNamespaceManager($answerFile.NameTable) $nsMgr.AddNamespace('sg', 'https://schneegans.de/windows/unattend-generator/') $answerFile.SelectSingleNode('//sg:File[@path="C:\Windows\Setup\Scripts\WinUtil-InstallDrivers.ps1"]', $nsMgr) | Should -BeNullOrEmpty - ($logs -join '|') | Should -Match 'Exported 9 of 11 driver packages \(2 staged for WinPE, 2 excluded\)' - ($logs -join '|') | Should -Match "Excluding extension-class driver package '.*hdx_asusext_apot_g5-tse.*'" + ($logs -join '|') | Should -Match 'Exported 10 of 11 driver packages \(2 staged for WinPE, 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 'install.wim metadata validation passed' ($logs -join '|') | Should -Match 'DISM mount completed.' ($logs -join '|') | Should -Not -Match '100.0%' - $script:exportRootAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'hdx_asusext_apot_g5-tse.inf_amd64_aabbccddeeff0011') + $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'hdx_asusext_apot_g5-tse.inf_amd64_aabbccddeeff0011') $script:exportRootAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'ntprint.inf_x86_7426e1b60aa62272') $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'ntprint.inf_x86_58e7118cdecb935e') $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'system_pkg') @@ -506,8 +505,8 @@ Describe "Win11 Creator setup media" { } } - It "excludes Class=Extension driver packages from Add-Driver regardless of vendor or case" { - $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoExtensionExclude_$([guid]::NewGuid())" + It "injects Class=Extension driver packages like any other export" { + $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoExtensionInject_$([guid]::NewGuid())" $installWim = Join-Path $contentRoot 'sources\install.wim' $template = Get-Content -Path $script:autoUnattendPath -Raw $logs = [System.Collections.Generic.List[string]]::new() @@ -528,15 +527,12 @@ Describe "Win11 Creator setup media" { $logs.Add([string]$message) } - @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 1 - ($logs -join '|') | Should -Match 'Exported 1 of 3 driver packages \(0 staged for WinPE, 2 excluded\)' - ($logs -join '|') | Should -Match "Excluding extension-class driver package '.*ext_pkg_lower'" - ($logs -join '|') | Should -Match "Excluding extension-class driver package '.*ext_pkg_quoted'" - ($logs -join '|') | Should -Not -Match "Excluding extension-class driver package '.*net_pkg'" + @($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\)' $driversInjected.Value | Should -BeTrue - $script:exportRootAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'ext_pkg_lower') - $script:exportRootAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'ext_pkg_quoted') + $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'ext_pkg_lower') + $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'ext_pkg_quoted') $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'net_pkg') } finally { Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue @@ -544,15 +540,16 @@ Describe "Win11 Creator setup media" { } } - It "preserves a retained nested package when its parent package is excluded" { - $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoNestedRetained_$([guid]::NewGuid())" + It "keeps a stale duplicate folder that contains a retained nested package" { + $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoStaleNestedRetained_$([guid]::NewGuid())" $installWim = Join-Path $contentRoot 'sources\install.wim' $template = Get-Content -Path $script:autoUnattendPath -Raw $logs = [System.Collections.Generic.List[string]]::new() New-WinUtilDriverExportHarness -Fixtures @( - @{ Path = 'parent_pkg'; Name = 'extension.inf'; Class = 'Extension' }, - @{ Path = 'parent_pkg\retained_child'; Name = 'network.inf'; Class = 'Net' } + @{ Path = 'main.inf_amd64_11111111aaaaaaaa'; Name = 'main.inf'; Class = 'Net'; DriverVer = '1/1/2023,1.0.0.0' }, + @{ Path = 'main.inf_amd64_22222222bbbbbbbb'; Name = 'main.inf'; Class = 'Net'; DriverVer = '6/1/2024,2.0.0.0' }, + @{ Path = 'main.inf_amd64_11111111aaaaaaaa\retained_child'; Name = 'network.inf'; Class = 'Net' } ) try { @@ -565,14 +562,12 @@ Describe "Win11 Creator setup media" { $logs.Add([string]$message) } - $addDriverCalls = @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }) - $addDriverCalls.Count | Should -Be 1 - $addDriverCalls[0] | Should -Match ([regex]::Escape('parent_pkg\retained_child')) - $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'parent_pkg') - $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'parent_pkg\retained_child') - $script:exportedInfsAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'parent_pkg\extension.inf') - $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'parent_pkg\retained_child\network.inf') - ($logs -join '|') | Should -Match "Keeping excluded driver package directory '.*parent_pkg' because it contains a retained nested package" + ($logs -join '|') | Should -Match "Excluding stale duplicate driver package '.*main\.inf_amd64_11111111aaaaaaaa' \(DriverVer 1/1/2023,1\.0\.0\.0\) superseded by '.*main\.inf_amd64_22222222bbbbbbbb' \(DriverVer 6/1/2024,2\.0\.0\.0\)" + ($logs -join '|') | Should -Match "Keeping excluded driver package directory '.*main\.inf_amd64_11111111aaaaaaaa' because it contains a retained nested package" + @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 2 + $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'main.inf_amd64_11111111aaaaaaaa') + $script:exportedInfsAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'main.inf_amd64_11111111aaaaaaaa\main.inf') + $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'main.inf_amd64_11111111aaaaaaaa\retained_child\network.inf') $driversInjected.Value | Should -BeTrue } finally { Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue @@ -580,15 +575,16 @@ Describe "Win11 Creator setup media" { } } - It "removes an excluded nested INF before recursively adding its retained ancestor" { - $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoNestedExcluded_$([guid]::NewGuid())" + It "removes a stale duplicate INF before recursively adding its retained ancestor" { + $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoStaleNestedExcluded_$([guid]::NewGuid())" $installWim = Join-Path $contentRoot 'sources\install.wim' $template = Get-Content -Path $script:autoUnattendPath -Raw New-WinUtilDriverExportHarness -Fixtures @( @{ Path = 'root_pkg'; Name = 'root.inf'; Class = 'Net' }, - @{ Path = 'root_pkg\excluded_child'; Name = 'extension.inf'; Class = 'Extension' }, - @{ Path = 'root_pkg\excluded_child\retained_grandchild'; Name = 'storage.inf'; Class = 'SCSIAdapter' } + @{ Path = 'root_pkg\dup.inf_amd64_11111111aaaaaaaa'; Name = 'dup.inf'; Class = 'Media'; DriverVer = '1/1/2020,1.0.0.0' }, + @{ Path = 'root_pkg\dup.inf_amd64_22222222bbbbbbbb'; Name = 'dup.inf'; Class = 'Media'; DriverVer = '6/1/2024,2.0.0.0' }, + @{ Path = 'root_pkg\dup.inf_amd64_11111111aaaaaaaa\retained_grandchild'; Name = 'storage.inf'; Class = 'SCSIAdapter' } ) try { @@ -600,43 +596,11 @@ Describe "Win11 Creator setup media" { $addDriverCalls = @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }) $addDriverCalls.Count | Should -Be 1 $addDriverCalls[0] | Should -Match ([regex]::Escape('/Driver:' + (Join-Path $script:driverExportRoot 'root_pkg'))) - $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\excluded_child') - $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\excluded_child\retained_grandchild') + $script:exportRootAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\dup.inf_amd64_11111111aaaaaaaa') $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\root.inf') - $script:exportedInfsAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'root_pkg\excluded_child\extension.inf') - $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\excluded_child\retained_grandchild\storage.inf') - } finally { - Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue - Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue - } - } - - It "validates WIM metadata and reports no injection when every package is excluded" { - $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoAllExcluded_$([guid]::NewGuid())" - $installWim = Join-Path $contentRoot 'sources\install.wim' - $template = Get-Content -Path $script:autoUnattendPath -Raw - $logs = [System.Collections.Generic.List[string]]::new() - - New-WinUtilDriverExportHarness -Fixtures @( - @{ Path = 'ext_pkg_a'; Name = 'a.inf'; Class = 'Extension' }, - @{ Path = 'ext_pkg_b'; Name = 'b.inf'; Class = 'Extension' } - ) - - try { - New-Item -Path (Split-Path $installWim -Parent) -ItemType Directory -Force | Out-Null - Set-Content -Path $installWim -Value 'mock-wim' - . $script:isoScriptPath - $driversInjected = [ref]$true - Invoke-WinUtilISOScript -ISOContentsDir $contentRoot -AutoUnattendXml $template -InjectCurrentSystemDrivers $true -InstallImagePath $installWim -InstallImageIndex 6 -InstallEditionId 'Professional' -DriversInjected $driversInjected -Log { - param($message) - $logs.Add([string]$message) - } - - $driversInjected.Value | Should -BeFalse - ($logs -join '|') | Should -Match 'No drivers found to inject: every exported package was excluded' - @($script:dismCalls | Where-Object { $_ -match '/Get-WimInfo' }).Count | Should -Be 1 - @($script:dismCalls | Where-Object { $_ -match '/Mount-Image' }).Count | Should -Be 0 - @($script:dismCalls | Where-Object { $_ -match '/Add-Driver' }).Count | Should -Be 0 + $script:exportedInfsAtAddDriver | Should -Not -Contain (Join-Path $script:driverExportRoot 'root_pkg\dup.inf_amd64_11111111aaaaaaaa\dup.inf') + $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\dup.inf_amd64_11111111aaaaaaaa\retained_grandchild\storage.inf') + $script:exportedInfsAtAddDriver | Should -Contain (Join-Path $script:driverExportRoot 'root_pkg\dup.inf_amd64_22222222bbbbbbbb\dup.inf') } finally { Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue