mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-12 19:11:18 +10:00
Compare commits
2
Commits
d0012449ad
...
3bc14e9b64
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bc14e9b64 | ||
|
|
8db5e6b461 |
@@ -223,6 +223,8 @@ function Invoke-WinUtilISOModify {
|
||||
$runspace.ApartmentState = "STA"
|
||||
$runspace.ThreadOptions = "ReuseThread"
|
||||
$runspace.Open()
|
||||
$injectDrivers = $sync["WPFWin11ISOInjectDrivers"].IsChecked -eq $true
|
||||
|
||||
$runspace.SessionStateProxy.SetVariable("sync", $sync)
|
||||
$runspace.SessionStateProxy.SetVariable("isoPath", $isoPath)
|
||||
$runspace.SessionStateProxy.SetVariable("driveLetter", $driveLetter)
|
||||
@@ -231,6 +233,7 @@ function Invoke-WinUtilISOModify {
|
||||
$runspace.SessionStateProxy.SetVariable("selectedWimIndex", $selectedWimIndex)
|
||||
$runspace.SessionStateProxy.SetVariable("selectedEditionName", $selectedEditionName)
|
||||
$runspace.SessionStateProxy.SetVariable("autounattendContent", $autounattendContent)
|
||||
$runspace.SessionStateProxy.SetVariable("injectDrivers", $injectDrivers)
|
||||
|
||||
# Serialize functions so they are available inside the runspace
|
||||
$isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + `
|
||||
@@ -324,7 +327,7 @@ function Invoke-WinUtilISOModify {
|
||||
|
||||
# ── Apply all WinUtil modifications via Invoke-WinUtilISOScript ──
|
||||
Log "Applying WinUtil modifications to install.wim..."
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -Log { param($m) Log $m }
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -InjectCurrentSystemDrivers $injectDrivers -Log { param($m) Log $m }
|
||||
|
||||
# ── 4b. DISM component store cleanup ──
|
||||
# /ResetBase removes all superseded component versions from WinSxS,
|
||||
|
||||
@@ -4,13 +4,22 @@ function Invoke-WinUtilISOScript {
|
||||
Applies WinUtil modifications to a mounted Windows 11 install.wim image.
|
||||
|
||||
.DESCRIPTION
|
||||
Removes AppX bloatware and OneDrive, injects hardware drivers (NVMe, Precision
|
||||
Touchpad/HID, and network) exported from the running system, optionally injects
|
||||
extended Storage & Network drivers from the ChrisTitusTech/storage-lan-drivers
|
||||
repository (requires git, installed via winget if absent), applies offline registry
|
||||
tweaks (hardware bypass, privacy, OOBE, telemetry, update suppression), deletes
|
||||
CEIP/WU scheduled-task definition files, and optionally drops autounattend.xml and
|
||||
removes the support\ folder from the ISO contents directory.
|
||||
Removes AppX bloatware and OneDrive, optionally injects all drivers exported from
|
||||
the running system into install.wim and boot.wim (controlled by the
|
||||
-InjectCurrentSystemDrivers switch), applies offline registry tweaks (hardware
|
||||
bypass, privacy, OOBE, telemetry, update suppression), deletes CEIP/WU
|
||||
scheduled-task definition files, and optionally writes autounattend.xml to the ISO
|
||||
root and removes the support\ folder from the ISO contents directory.
|
||||
|
||||
All setup scripts embedded in the autounattend.xml <Extensions><File> nodes
|
||||
(Specialize.ps1, DefaultUser.ps1, FirstLogon.ps1, UserOnce.ps1, etc.) are written
|
||||
directly into the WIM at their target paths under C:\Windows\Setup\Scripts\. This
|
||||
pre-staging is necessary because Windows Setup strips unrecognised-namespace XML
|
||||
elements — including the Schneegans <Extensions> block — when copying the answer
|
||||
file to %WINDIR%\Panther\unattend.xml. Without pre-staging the [scriptblock] that
|
||||
tries to extract scripts from the Panther copy receives $null, no scripts reach
|
||||
disk, and both the specialize-pass actions and FirstLogonCommands silently fail.
|
||||
|
||||
Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO).
|
||||
|
||||
.PARAMETER ScratchDir
|
||||
@@ -28,6 +37,11 @@ function Invoke-WinUtilISOScript {
|
||||
in dev mode it is read from tools\autounattend.xml.
|
||||
If empty, the OOBE bypass file is skipped and a warning is logged.
|
||||
|
||||
.PARAMETER InjectCurrentSystemDrivers
|
||||
Optional. When $true, exports all drivers from the running system and injects
|
||||
them into install.wim and boot.wim index 2 (Windows Setup PE).
|
||||
Defaults to $false — no drivers are injected.
|
||||
|
||||
.PARAMETER Log
|
||||
Optional ScriptBlock used for progress/status logging.
|
||||
Receives a single [string] message argument.
|
||||
@@ -46,12 +60,13 @@ function Invoke-WinUtilISOScript {
|
||||
.NOTES
|
||||
Author : Chris Titus @christitustech
|
||||
GitHub : https://github.com/ChrisTitusTech
|
||||
Version : 26.02.25b
|
||||
Version : 26.03.02
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory)][string]$ScratchDir,
|
||||
[string]$ISOContentsDir = "",
|
||||
[string]$AutoUnattendXml = "",
|
||||
[bool]$InjectCurrentSystemDrivers = $false,
|
||||
[scriptblock]$Log = { param($m) Write-Output $m }
|
||||
)
|
||||
|
||||
@@ -80,32 +95,6 @@ function Invoke-WinUtilISOScript {
|
||||
}
|
||||
}
|
||||
|
||||
# Copies driver package folders (one per exported .inf) whose online class
|
||||
# matches any of the supplied class names into a new temp staging directory.
|
||||
# Returns the staging directory path — caller must delete it when done.
|
||||
# Using a staging dir lets DISM inject all drivers in a single /Recurse
|
||||
# call instead of one DISM process launch per .inf file.
|
||||
function New-DriverStagingDir {
|
||||
param ([string]$ExportRoot, [string[]]$Classes)
|
||||
$stagingDir = Join-Path $env:TEMP "WinUtil_DriverStage_$(Get-Random)"
|
||||
New-Item -Path $stagingDir -ItemType Directory -Force | Out-Null
|
||||
Get-WindowsDriver -Online |
|
||||
Where-Object { $_.ClassName -in $Classes } |
|
||||
ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($_.OriginalFileName) } |
|
||||
Select-Object -Unique |
|
||||
ForEach-Object {
|
||||
Get-ChildItem -Path $ExportRoot -Filter "$_.inf" -Recurse -ErrorAction SilentlyContinue |
|
||||
Select-Object -ExpandProperty DirectoryName -Unique |
|
||||
ForEach-Object {
|
||||
# Each exported driver lives in its own sub-folder;
|
||||
# copy that folder (with its binary files) into staging.
|
||||
$dest = Join-Path $stagingDir (Split-Path $_ -Leaf)
|
||||
Copy-Item -Path $_ -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
return $stagingDir
|
||||
}
|
||||
|
||||
# Injects all drivers from $DriverDir into a DISM-mounted image in one call.
|
||||
function Add-DriversToImage {
|
||||
param (
|
||||
@@ -207,35 +196,30 @@ function Invoke-WinUtilISOScript {
|
||||
}
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
# 2. Inject hardware drivers (NVMe / Trackpad / Network)
|
||||
# Injected into BOTH install.wim (OS) AND boot.wim index 2 (Setup).
|
||||
# Without storage drivers in boot.wim, Windows Setup cannot see the
|
||||
# target disk on systems with unsupported NVMe / SATA controllers.
|
||||
# 2. Inject current system drivers (optional)
|
||||
# Enabled by the "Inject current system drivers" checkbox at the
|
||||
# Mount & Verify step. Exports ALL drivers from the running system
|
||||
# and injects them into install.wim AND boot.wim index 2 so Windows
|
||||
# Setup can see the target disk on systems with unsupported NVMe or
|
||||
# SATA controllers.
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
& $Log "Exporting hardware drivers from running system (NVMe, HID/Trackpad, Network)..."
|
||||
|
||||
if ($InjectCurrentSystemDrivers) {
|
||||
& $Log "Exporting all drivers from running system..."
|
||||
$driverExportRoot = Join-Path $env:TEMP "WinUtil_DriverExport_$(Get-Random)"
|
||||
New-Item -Path $driverExportRoot -ItemType Directory -Force | Out-Null
|
||||
|
||||
try {
|
||||
Export-WindowsDriver -Online -Destination $driverExportRoot | Out-Null
|
||||
|
||||
# Stage matching driver folders then do a single DISM /Recurse call.
|
||||
# install.wim: SCSIAdapter + HIDClass + Net
|
||||
$installStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','HIDClass','Net')
|
||||
& $Log "Injecting staged drivers into install.wim (single DISM call)..."
|
||||
Add-DriversToImage -MountPath $ScratchDir -DriverDir $installStage -Label "install" -Logger $Log
|
||||
Remove-Item -Path $installStage -Recurse -Force -ErrorAction SilentlyContinue
|
||||
& $Log "Injecting current system drivers into install.wim..."
|
||||
Add-DriversToImage -MountPath $ScratchDir -DriverDir $driverExportRoot -Label "install" -Logger $Log
|
||||
& $Log "install.wim driver injection complete."
|
||||
|
||||
# boot.wim: SCSIAdapter + Net only (HID not needed in WinPE)
|
||||
# Also inject into boot.wim so Windows Setup can see the target disk.
|
||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||
$bootWim = Join-Path $ISOContentsDir "sources\boot.wim"
|
||||
if (Test-Path $bootWim) {
|
||||
$bootStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','Net')
|
||||
& $Log "Injecting staged drivers into boot.wim (single DISM call)..."
|
||||
Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $bootStage -Logger $Log
|
||||
Remove-Item -Path $bootStage -Recurse -Force -ErrorAction SilentlyContinue
|
||||
& $Log "Injecting current system drivers into boot.wim..."
|
||||
Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $driverExportRoot -Logger $Log
|
||||
} else {
|
||||
& $Log "Warning: boot.wim not found — skipping boot.wim driver injection."
|
||||
}
|
||||
@@ -245,66 +229,8 @@ function Invoke-WinUtilISOScript {
|
||||
} finally {
|
||||
Remove-Item -Path $driverExportRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# ── 2c. Optional: extended Storage & Network drivers from community repo ──
|
||||
$extDriverChoice = [System.Windows.MessageBox]::Show(
|
||||
"Would you like to add extended Storage and Network drivers?`n`n" +
|
||||
"This installs EVERY Storage and Networking device driver " +
|
||||
"in EXISTANCE into the image. (~1000 drivers)`n`n" +
|
||||
"No Wireless drivers only Ethernet, use for stubborn systems " +
|
||||
"with unsupported NVMe or Ethernet controllers.",
|
||||
"Extended Drivers", "YesNo", "Question")
|
||||
|
||||
if ($extDriverChoice -eq 'Yes') {
|
||||
& $Log "Extended driver injection requested."
|
||||
|
||||
# Ensure git is available
|
||||
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||
if (-not $gitCmd) {
|
||||
& $Log "Git not found — installing via winget..."
|
||||
winget install --id Git.Git -e --source winget `
|
||||
--accept-package-agreements --accept-source-agreements | Out-Null
|
||||
# Refresh PATH so git is visible in this session
|
||||
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' +
|
||||
[System.Environment]::GetEnvironmentVariable('PATH', 'User')
|
||||
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
if (-not $gitCmd) {
|
||||
& $Log "Warning: git could not be found after install attempt — skipping extended drivers."
|
||||
} else {
|
||||
$extRepoDir = Join-Path $env:TEMP "WinUtil_ExtDrivers_$(Get-Random)"
|
||||
try {
|
||||
& $Log "Cloning storage-lan-drivers repository..."
|
||||
& git clone --depth 1 `
|
||||
"https://github.com/ChrisTitusTech/storage-lan-drivers" `
|
||||
$extRepoDir 2>&1 | ForEach-Object { & $Log " git: $_" }
|
||||
|
||||
if (Test-Path $extRepoDir) {
|
||||
& $Log "Injecting extended drivers into install.wim (this may take several minutes)..."
|
||||
Add-DriversToImage -MountPath $ScratchDir -DriverDir $extRepoDir -Label "install" -Logger $Log
|
||||
& $Log "Extended driver injection into install.wim complete."
|
||||
|
||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||
$bootWimExt = Join-Path $ISOContentsDir "sources\boot.wim"
|
||||
if (Test-Path $bootWimExt) {
|
||||
& $Log "Injecting extended drivers into boot.wim..."
|
||||
Invoke-BootWimInject -BootWimPath $bootWimExt -DriverDir $extRepoDir -Logger $Log
|
||||
} else {
|
||||
& $Log "Warning: boot.wim not found — skipping extended driver injection into boot.wim."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
& $Log "Warning: repository clone directory not found — skipping extended drivers."
|
||||
}
|
||||
} catch {
|
||||
& $Log "Error during extended driver injection: $_"
|
||||
} finally {
|
||||
Remove-Item -Path $extRepoDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
& $Log "Extended driver injection skipped."
|
||||
& $Log "Driver injection skipped."
|
||||
}
|
||||
|
||||
# ═════════════════════════════════════════════════════════════════════════
|
||||
@@ -366,10 +292,55 @@ function Invoke-WinUtilISOScript {
|
||||
Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1'
|
||||
|
||||
if ($AutoUnattendXml) {
|
||||
# ── Place autounattend.xml inside the WIM (Sysprep) ──────────────────
|
||||
$sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml"
|
||||
Set-Content -Path $sysprepDest -Value $AutoUnattendXml -Encoding UTF8 -Force
|
||||
& $Log "Written autounattend.xml to Sysprep directory."
|
||||
# ── Pre-stage embedded setup scripts directly into the WIM ────────────
|
||||
# The autounattend.xml (Schneegans generator format) embeds all setup
|
||||
# scripts as <Extensions><File> nodes. The specialize-pass command that
|
||||
# is supposed to extract them reads C:\Windows\Panther\unattend.xml, but
|
||||
# Windows Setup strips unrecognised-namespace elements (including the
|
||||
# entire <Extensions> block) when it copies the answer file to that path.
|
||||
# As a result [scriptblock]::Create($null) throws, no scripts are written
|
||||
# to C:\Windows\Setup\Scripts\, Specialize.ps1 and DefaultUser.ps1 never
|
||||
# run, and FirstLogon.ps1 is absent so FirstLogonCommands silently fails.
|
||||
#
|
||||
# Writing the scripts directly into the WIM guarantees they are present
|
||||
# on the target drive after Windows Setup applies the image, regardless
|
||||
# of whether the Panther extraction step succeeds.
|
||||
try {
|
||||
$xmlDoc = [xml]::new()
|
||||
$xmlDoc.LoadXml($AutoUnattendXml)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("sg", "https://schneegans.de/windows/unattend-generator/")
|
||||
|
||||
$fileNodes = $xmlDoc.SelectNodes("//sg:File", $nsMgr)
|
||||
if ($fileNodes -and $fileNodes.Count -gt 0) {
|
||||
foreach ($fileNode in $fileNodes) {
|
||||
# Paths in the XML are absolute Windows paths (e.g. C:\Windows\Setup\Scripts\…).
|
||||
# Strip the drive-letter prefix so we can root them under $ScratchDir.
|
||||
$absPath = $fileNode.GetAttribute("path")
|
||||
$relPath = $absPath -replace '^[A-Za-z]:[/\\]', ''
|
||||
$destPath = Join-Path $ScratchDir $relPath
|
||||
$destDir = Split-Path $destPath -Parent
|
||||
|
||||
New-Item -Path $destDir -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
# Match the encoding logic used by the original ExtractScript.
|
||||
$ext = [IO.Path]::GetExtension($destPath).ToLower()
|
||||
$encoding = switch ($ext) {
|
||||
{ $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8 }
|
||||
{ $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new($false, $true) }
|
||||
default { [System.Text.Encoding]::Default }
|
||||
}
|
||||
$bytes = $encoding.GetPreamble() + $encoding.GetBytes($fileNode.InnerText.Trim())
|
||||
[System.IO.File]::WriteAllBytes($destPath, $bytes)
|
||||
& $Log "Pre-staged setup script: $relPath"
|
||||
}
|
||||
} else {
|
||||
& $Log "Warning: no <Extensions><File> nodes found in autounattend.xml — setup scripts not pre-staged."
|
||||
}
|
||||
} catch {
|
||||
& $Log "Warning: could not pre-stage setup scripts from autounattend.xml: $_"
|
||||
}
|
||||
|
||||
# ── Place autounattend.xml at the ISO / USB root ──────────────────────
|
||||
# Windows Setup reads this file first (before booting into the OS),
|
||||
|
||||
@@ -1494,6 +1494,13 @@
|
||||
HorizontalAlignment="Left"
|
||||
Width="Auto" Padding="12,0"
|
||||
Height="{DynamicResource ButtonHeight}"/>
|
||||
<CheckBox Name="WPFWin11ISOInjectDrivers"
|
||||
Content="Inject current system drivers"
|
||||
FontSize="{DynamicResource FontSize}"
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
IsChecked="False"
|
||||
Margin="0,8,0,0"
|
||||
ToolTip="Exports all drivers from this machine and injects them into install.wim and boot.wim. Recommended for systems with unsupported NVMe or network controllers."/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Verification results panel -->
|
||||
|
||||
Reference in New Issue
Block a user