Skip to content

Commit b493737

Browse files
Win11 Creator USB and Log Fixes (#4139)
* initial usb fixes * fix full button width * Cleanup and Verbose output for copy * expand ui and fix clean and reset * Add minimal driver injection * initial driver support * fix verbage * fix syntax error * create log file on iso generation * inject to boot.wim for install * fix single driver install issues * fix first run probs * cleanup injection * improve clean up * Fix OSCDIMG output and cleanup comments * Fix Scrollviewer in Status Log * large drive support and change to Exfat * Fix BOOT for older UEFI and Add error checks for small usb drives * fix single usb drive error
1 parent 7ceb303 commit b493737

File tree

6 files changed

+737
-609
lines changed

6 files changed

+737
-609
lines changed

.github/workflows/docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- name: Setup Pages
4848
id: pages
4949
uses: actions/configure-pages@v5
50-
50+
5151
- name: Generate Dev Docs from JSON
5252
shell: pwsh
5353
run: |

functions/private/Invoke-WinUtilISO.ps1

Lines changed: 274 additions & 448 deletions
Large diffs are not rendered by default.

functions/private/Invoke-WinUtilISOScript.ps1

Lines changed: 121 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,38 @@ function Invoke-WinUtilISOScript {
44
Applies WinUtil modifications to a mounted Windows 11 install.wim image.
55
66
.DESCRIPTION
7-
Performs the following operations against an already-mounted WIM image:
8-
9-
1. Removes provisioned AppX bloatware packages via DISM.
10-
2. Removes OneDriveSetup.exe from the system image.
11-
3. Loads offline registry hives (COMPONENTS, DEFAULT, NTUSER, SOFTWARE, SYSTEM)
12-
and applies the following tweaks:
13-
- Bypasses hardware requirement checks (CPU, RAM, SecureBoot, Storage, TPM).
14-
- Disables sponsored-app delivery and ContentDeliveryManager features.
15-
- Enables local-account OOBE path (BypassNRO).
16-
- Writes autounattend.xml to the Sysprep directory inside the WIM and,
17-
optionally, to the ISO/USB root so Windows Setup picks it up at boot.
18-
- Disables reserved storage.
19-
- Disables BitLocker device encryption.
20-
- Hides the Chat (Teams) taskbar icon.
21-
- Disables OneDrive folder backup (KFM).
22-
- Disables telemetry, advertising ID, and input personalization.
23-
- Blocks post-install delivery of DevHome, Outlook, and Teams.
24-
- Disables Windows Copilot.
25-
- Disables Windows Update during OOBE.
26-
4. Deletes unwanted scheduled-task XML definition files (CEIP, Appraiser, etc.).
27-
5. Removes the support\ folder from the ISO contents directory (if supplied).
28-
29-
Mounting and dismounting the WIM is the responsibility of the caller
30-
(e.g. Invoke-WinUtilISO).
7+
Removes AppX bloatware and OneDrive, optionally injects all drivers exported from
8+
the running system into install.wim and boot.wim (controlled by the
9+
-InjectCurrentSystemDrivers switch), applies offline registry tweaks (hardware
10+
bypass, privacy, OOBE, telemetry, update suppression), deletes CEIP/WU
11+
scheduled-task definition files, and optionally writes autounattend.xml to the ISO
12+
root and removes the support\ folder from the ISO contents directory.
13+
14+
All setup scripts embedded in the autounattend.xml <Extensions><File> nodes are
15+
written directly into the WIM at their target paths under C:\Windows\Setup\Scripts\
16+
to ensure they survive Windows Setup stripping unrecognised-namespace XML elements
17+
from the Panther copy of the answer file.
18+
19+
Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO).
3120
3221
.PARAMETER ScratchDir
3322
Mandatory. Full path to the directory where the Windows image is currently mounted.
34-
Example: C:\Users\USERNAME\AppData\Local\Temp\WinUtil_Win11ISO_20260222\wim_mount
3523
3624
.PARAMETER ISOContentsDir
37-
Optional. Root directory of the extracted ISO contents.
38-
When supplied, autounattend.xml is also written here so Windows Setup picks it
39-
up automatically at boot, and the support\ folder is deleted from that location.
25+
Optional. Root directory of the extracted ISO contents. When supplied,
26+
autounattend.xml is written here and the support\ folder is removed.
4027
4128
.PARAMETER AutoUnattendXml
42-
Optional. Full XML content for autounattend.xml.
43-
In compiled winutil.ps1 this is the embedded $WinUtilAutounattendXml here-string;
44-
in dev mode it is read from tools\autounattend.xml.
45-
If empty, the OOBE bypass file is skipped and a warning is logged.
29+
Optional. Full XML content for autounattend.xml. If empty, the OOBE bypass
30+
file is skipped and a warning is logged.
31+
32+
.PARAMETER InjectCurrentSystemDrivers
33+
Optional. When $true, exports all drivers from the running system and injects
34+
them into install.wim and boot.wim index 2 (Windows Setup PE).
35+
Defaults to $false.
4636
4737
.PARAMETER Log
48-
Optional ScriptBlock used for progress/status logging.
49-
Receives a single [string] message argument.
50-
Defaults to { param($m) Write-Output $m } when not supplied.
38+
Optional ScriptBlock for progress/status logging. Receives a single [string] argument.
5139
5240
.EXAMPLE
5341
Invoke-WinUtilISOScript -ScratchDir "C:\Temp\wim_mount"
@@ -62,24 +50,19 @@ function Invoke-WinUtilISOScript {
6250
.NOTES
6351
Author : Chris Titus @christitustech
6452
GitHub : https://github.com/ChrisTitusTech
65-
Version : 26.02.22
53+
Version : 26.03.02
6654
#>
6755
param (
6856
[Parameter(Mandatory)][string]$ScratchDir,
69-
# Root directory of the extracted ISO contents. When supplied, autounattend.xml
70-
# is written here so Windows Setup picks it up automatically at boot.
7157
[string]$ISOContentsDir = "",
72-
# Autounattend XML content. In compiled winutil.ps1 this comes from the embedded
73-
# $WinUtilAutounattendXml here-string; in dev mode it is read from tools\autounattend.xml.
7458
[string]$AutoUnattendXml = "",
59+
[bool]$InjectCurrentSystemDrivers = $false,
7560
[scriptblock]$Log = { param($m) Write-Output $m }
7661
)
7762

78-
# ── Resolve admin group name (for takeown / icacls) ──────────────────────
7963
$adminSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
8064
$adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount])
8165

82-
# ── Local helpers ─────────────────────────────────────────────────────────
8366
function Set-ISOScriptReg {
8467
param ([string]$path, [string]$name, [string]$type, [string]$value)
8568
try {
@@ -100,15 +83,37 @@ function Invoke-WinUtilISOScript {
10083
}
10184
}
10285

103-
# ═════════════════════════════════════════════════════════════════════════
104-
# 1. Remove provisioned AppX packages
105-
# ═════════════════════════════════════════════════════════════════════════
86+
function Add-DriversToImage {
87+
param ([string]$MountPath, [string]$DriverDir, [string]$Label = "image", [scriptblock]$Logger)
88+
& dism /English "/image:$MountPath" /Add-Driver "/Driver:$DriverDir" /Recurse 2>&1 |
89+
ForEach-Object { & $Logger " dism[$Label]: $_" }
90+
}
91+
92+
function Invoke-BootWimInject {
93+
param ([string]$BootWimPath, [string]$DriverDir, [scriptblock]$Logger)
94+
Set-ItemProperty -Path $BootWimPath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue
95+
$mountDir = Join-Path $env:TEMP "WinUtil_BootMount_$(Get-Random)"
96+
New-Item -Path $mountDir -ItemType Directory -Force | Out-Null
97+
try {
98+
& $Logger "Mounting boot.wim (index 2) for driver injection..."
99+
Mount-WindowsImage -ImagePath $BootWimPath -Index 2 -Path $mountDir -ErrorAction Stop | Out-Null
100+
Add-DriversToImage -MountPath $mountDir -DriverDir $DriverDir -Label "boot" -Logger $Logger
101+
& $Logger "Saving boot.wim..."
102+
Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null
103+
& $Logger "boot.wim driver injection complete."
104+
} catch {
105+
& $Logger "Warning: boot.wim driver injection failed: $_"
106+
try { Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null } catch {}
107+
} finally {
108+
Remove-Item -Path $mountDir -Recurse -Force -ErrorAction SilentlyContinue
109+
}
110+
}
111+
112+
# ── 1. Remove provisioned AppX packages ──────────────────────────────────
106113
& $Log "Removing provisioned AppX packages..."
107114

108115
$packages = & dism /English "/image:$ScratchDir" /Get-ProvisionedAppxPackages |
109-
ForEach-Object {
110-
if ($_ -match 'PackageName : (.*)') { $matches[1] }
111-
}
116+
ForEach-Object { if ($_ -match 'PackageName : (.*)') { $matches[1] } }
112117

113118
$packagePrefixes = @(
114119
'AppUp.IntelManagementandSecurityStatus',
@@ -155,25 +160,46 @@ function Invoke-WinUtilISOScript {
155160
'MicrosoftTeams'
156161
)
157162

158-
$packagesToRemove = $packages | Where-Object {
159-
$pkg = $_
160-
$packagePrefixes | Where-Object { $pkg -like "*$_*" }
161-
}
162-
foreach ($package in $packagesToRemove) {
163-
& dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$package"
163+
$packages | Where-Object { $pkg = $_; $packagePrefixes | Where-Object { $pkg -like "*$_*" } } |
164+
ForEach-Object { & dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$_" }
165+
166+
# ── 2. Inject current system drivers (optional) ───────────────────────────
167+
if ($InjectCurrentSystemDrivers) {
168+
& $Log "Exporting all drivers from running system..."
169+
$driverExportRoot = Join-Path $env:TEMP "WinUtil_DriverExport_$(Get-Random)"
170+
New-Item -Path $driverExportRoot -ItemType Directory -Force | Out-Null
171+
try {
172+
Export-WindowsDriver -Online -Destination $driverExportRoot | Out-Null
173+
174+
& $Log "Injecting current system drivers into install.wim..."
175+
Add-DriversToImage -MountPath $ScratchDir -DriverDir $driverExportRoot -Label "install" -Logger $Log
176+
& $Log "install.wim driver injection complete."
177+
178+
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
179+
$bootWim = Join-Path $ISOContentsDir "sources\boot.wim"
180+
if (Test-Path $bootWim) {
181+
& $Log "Injecting current system drivers into boot.wim..."
182+
Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $driverExportRoot -Logger $Log
183+
} else {
184+
& $Log "Warning: boot.wim not found — skipping boot.wim driver injection."
185+
}
186+
}
187+
} catch {
188+
& $Log "Error during driver export/injection: $_"
189+
} finally {
190+
Remove-Item -Path $driverExportRoot -Recurse -Force -ErrorAction SilentlyContinue
191+
}
192+
} else {
193+
& $Log "Driver injection skipped."
164194
}
165195

166-
# ═════════════════════════════════════════════════════════════════════════
167-
# 2. Remove OneDrive
168-
# ═════════════════════════════════════════════════════════════════════════
196+
# ── 3. Remove OneDrive ────────────────────────────────────────────────────
169197
& $Log "Removing OneDrive..."
170198
& takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null
171199
& icacls "$ScratchDir\Windows\System32\OneDriveSetup.exe" /grant "$($adminGroup.Value):(F)" /T /C | Out-Null
172200
Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue
173201

174-
# ═════════════════════════════════════════════════════════════════════════
175-
# 3. Registry tweaks
176-
# ═════════════════════════════════════════════════════════════════════════
202+
# ── 4. Registry tweaks ────────────────────────────────────────────────────
177203
& $Log "Loading offline registry hives..."
178204
reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
179205
reg load HKLM\zDEFAULT "$ScratchDir\Windows\System32\config\default"
@@ -222,14 +248,37 @@ function Invoke-WinUtilISOScript {
222248
Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1'
223249

224250
if ($AutoUnattendXml) {
225-
# ── Place autounattend.xml inside the WIM (Sysprep) ──────────────────
226-
$sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml"
227-
Set-Content -Path $sysprepDest -Value $AutoUnattendXml -Encoding UTF8 -Force
228-
& $Log "Written autounattend.xml to Sysprep directory."
229-
230-
# ── Place autounattend.xml at the ISO / USB root ──────────────────────
231-
# Windows Setup reads this file first (before booting into the OS),
232-
# which is what drives the local-account / OOBE bypass at install time.
251+
try {
252+
$xmlDoc = [xml]::new()
253+
$xmlDoc.LoadXml($AutoUnattendXml)
254+
255+
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
256+
$nsMgr.AddNamespace("sg", "https://schneegans.de/windows/unattend-generator/")
257+
258+
$fileNodes = $xmlDoc.SelectNodes("//sg:File", $nsMgr)
259+
if ($fileNodes -and $fileNodes.Count -gt 0) {
260+
foreach ($fileNode in $fileNodes) {
261+
$absPath = $fileNode.GetAttribute("path")
262+
$relPath = $absPath -replace '^[A-Za-z]:[/\\]', ''
263+
$destPath = Join-Path $ScratchDir $relPath
264+
New-Item -Path (Split-Path $destPath -Parent) -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
265+
266+
$ext = [IO.Path]::GetExtension($destPath).ToLower()
267+
$encoding = switch ($ext) {
268+
{ $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8 }
269+
{ $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new($false, $true) }
270+
default { [System.Text.Encoding]::Default }
271+
}
272+
[System.IO.File]::WriteAllBytes($destPath, ($encoding.GetPreamble() + $encoding.GetBytes($fileNode.InnerText.Trim())))
273+
& $Log "Pre-staged setup script: $relPath"
274+
}
275+
} else {
276+
& $Log "Warning: no <Extensions><File> nodes found in autounattend.xml — setup scripts not pre-staged."
277+
}
278+
} catch {
279+
& $Log "Warning: could not pre-stage setup scripts from autounattend.xml: $_"
280+
}
281+
233282
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
234283
$isoDest = Join-Path $ISOContentsDir "autounattend.xml"
235284
Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force
@@ -272,8 +321,8 @@ function Invoke-WinUtilISOScript {
272321
Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate'
273322

274323
& $Log "Disabling Copilot..."
275-
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1'
276-
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0'
324+
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1'
325+
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0'
277326
Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1'
278327

279328
& $Log "Disabling Windows Update during OOBE (re-enabled on first logon via FirstLogon.ps1)..."
@@ -304,12 +353,9 @@ function Invoke-WinUtilISOScript {
304353
reg unload HKLM\zSOFTWARE
305354
reg unload HKLM\zSYSTEM
306355

307-
# ═════════════════════════════════════════════════════════════════════════
308-
# 4. Delete scheduled task definition files
309-
# ═════════════════════════════════════════════════════════════════════════
356+
# ── 5. Delete scheduled task definition files ─────────────────────────────
310357
& $Log "Deleting scheduled task definition files..."
311358
$tasksPath = "$ScratchDir\Windows\System32\Tasks"
312-
313359
Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" -Force -ErrorAction SilentlyContinue
314360
Remove-Item "$tasksPath\Microsoft\Windows\Customer Experience Improvement Program" -Recurse -Force -ErrorAction SilentlyContinue
315361
Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\ProgramDataUpdater" -Force -ErrorAction SilentlyContinue
@@ -321,12 +367,9 @@ function Invoke-WinUtilISOScript {
321367
Remove-Item "$tasksPath\Microsoft\Windows\WaaSMedic" -Recurse -Force -ErrorAction SilentlyContinue
322368
Remove-Item "$tasksPath\Microsoft\Windows\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
323369
Remove-Item "$tasksPath\Microsoft\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue
324-
325370
& $Log "Scheduled task files deleted."
326371

327-
# ═════════════════════════════════════════════════════════════════════════
328-
# 5. Remove ISO support folder (fresh-install only; not needed)
329-
# ═════════════════════════════════════════════════════════════════════════
372+
# ── 6. Remove ISO support folder ─────────────────────────────────────────
330373
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
331374
& $Log "Removing ISO support\ folder..."
332375
Remove-Item -Path (Join-Path $ISOContentsDir "support") -Recurse -Force -ErrorAction SilentlyContinue

0 commit comments

Comments
 (0)