diff --git a/Compile.ps1 b/Compile.ps1
index ab0ec00632..ba23652a04 100644
--- a/Compile.ps1
+++ b/Compile.ps1
@@ -103,6 +103,20 @@ $xaml
'@
"@)
+Update-Progress "Adding: autounattend.xml" 95
+$autounattendRaw = Get-Content "$workingdir\tools\autounattend.xml" -Raw
+# Strip XML comments (, including multi-line)
+$autounattendRaw = [regex]::Replace($autounattendRaw, '', '', [System.Text.RegularExpressions.RegexOptions]::Singleline)
+# Drop blank lines and trim trailing whitespace per line
+$autounattendXml = ($autounattendRaw -split "`r?`n" |
+ Where-Object { $_.Trim() -ne '' } |
+ ForEach-Object { $_.TrimEnd() }) -join "`r`n"
+$script_content.Add(@"
+`$WinUtilAutounattendXml = @'
+$autounattendXml
+'@
+"@)
+
$script_content.Add($(Get-Content "scripts\main.ps1"))
Update-Progress "Removing temporary files" 99
diff --git a/config/themes.json b/config/themes.json
index f6da35548b..bab0de627b 100644
--- a/config/themes.json
+++ b/config/themes.json
@@ -65,10 +65,12 @@
"ButtonTweaksBackgroundColor": "#F7F7F7",
"ButtonConfigBackgroundColor": "#F7F7F7",
"ButtonUpdatesBackgroundColor": "#F7F7F7",
+ "ButtonWin11ISOBackgroundColor": "#F7F7F7",
"ButtonInstallForegroundColor": "#232629",
"ButtonTweaksForegroundColor": "#232629",
"ButtonConfigForegroundColor": "#232629",
"ButtonUpdatesForegroundColor": "#232629",
+ "ButtonWin11ISOForegroundColor": "#232629",
"ButtonBackgroundColor": "#F5F5F5",
"ButtonBackgroundPressedColor": "#1A1A1A",
"ButtonBackgroundMouseoverColor": "#C2C2C2",
@@ -105,10 +107,12 @@
"ButtonTweaksBackgroundColor": "#333333",
"ButtonConfigBackgroundColor": "#444444",
"ButtonUpdatesBackgroundColor": "#555555",
+ "ButtonWin11ISOBackgroundColor": "#666666",
"ButtonInstallForegroundColor": "#F7F7F7",
"ButtonTweaksForegroundColor": "#F7F7F7",
"ButtonConfigForegroundColor": "#F7F7F7",
"ButtonUpdatesForegroundColor": "#F7F7F7",
+ "ButtonWin11ISOForegroundColor": "#F7F7F7",
"ButtonBackgroundColor": "#1E3747",
"ButtonBackgroundPressedColor": "#F7F7F7",
"ButtonBackgroundMouseoverColor": "#3B4252",
diff --git a/functions/private/Invoke-WinUtilISO.ps1 b/functions/private/Invoke-WinUtilISO.ps1
new file mode 100644
index 0000000000..284fe3064e
--- /dev/null
+++ b/functions/private/Invoke-WinUtilISO.ps1
@@ -0,0 +1,779 @@
+function Write-Win11ISOLog {
+ <#
+ .SYNOPSIS
+ Appends a timestamped message to the Win11ISO status log TextBox.
+ .PARAMETER Message
+ The message to append.
+ #>
+ param([string]$Message)
+ $timestamp = (Get-Date).ToString("HH:mm:ss")
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $current = $sync["WPFWin11ISOStatusLog"].Text
+ if ($current -eq "Ready. Please select a Windows 11 ISO to begin.") {
+ $sync["WPFWin11ISOStatusLog"].Text = "[$timestamp] $Message"
+ } else {
+ $sync["WPFWin11ISOStatusLog"].Text += "`n[$timestamp] $Message"
+ }
+ $sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
+ $sync["WPFWin11ISOStatusLog"].ScrollToEnd()
+ })
+}
+
+function Invoke-WinUtilISOBrowse {
+ <#
+ .SYNOPSIS
+ Opens an OpenFileDialog so the user can choose a Windows 11 ISO file.
+ Populates WPFWin11ISOPath and reveals the Mount & Verify section (Step 2).
+ #>
+ Add-Type -AssemblyName System.Windows.Forms
+
+ $dlg = [System.Windows.Forms.OpenFileDialog]::new()
+ $dlg.Title = "Select Windows 11 ISO"
+ $dlg.Filter = "ISO files (*.iso)|*.iso|All files (*.*)|*.*"
+ $dlg.InitialDirectory = [System.Environment]::GetFolderPath("Desktop")
+
+ if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
+
+ $isoPath = $dlg.FileName
+
+ # ── Basic size sanity-check (a Win11 ISO is typically > 4 GB) ──
+ $fileSizeGB = [math]::Round((Get-Item $isoPath).Length / 1GB, 2)
+
+ $sync["WPFWin11ISOPath"].Text = $isoPath
+ $sync["WPFWin11ISOFileInfo"].Text = "File size: $fileSizeGB GB"
+ $sync["WPFWin11ISOFileInfo"].Visibility = "Visible"
+
+ # Reveal Step 2
+ $sync["WPFWin11ISOMountSection"].Visibility = "Visible"
+
+ # Collapse all later steps whenever a new ISO is chosen
+ $sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
+
+ Write-Win11ISOLog "ISO selected: $isoPath ($fileSizeGB GB)"
+}
+
+function Invoke-WinUtilISOMountAndVerify {
+ <#
+ .SYNOPSIS
+ Mounts the selected ISO, verifies it is a valid Windows 11 image,
+ and populates the edition list. Reveals Step 3 on success.
+ #>
+ $isoPath = $sync["WPFWin11ISOPath"].Text
+
+ if ([string]::IsNullOrWhiteSpace($isoPath) -or $isoPath -eq "No ISO selected...") {
+ [System.Windows.MessageBox]::Show(
+ "Please select an ISO file first.",
+ "No ISO Selected", "OK", "Warning")
+ return
+ }
+
+ Write-Win11ISOLog "Mounting ISO: $isoPath"
+ Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10
+
+ try {
+ # Mount the ISO
+ $diskImage = Mount-DiskImage -ImagePath $isoPath -PassThru -ErrorAction Stop
+ $driveLetter = ($diskImage | Get-Volume).DriveLetter + ":"
+ Write-Win11ISOLog "Mounted at drive $driveLetter"
+
+ Set-WinUtilProgressBar -Label "Verifying ISO contents..." -Percent 30
+
+ # ── Verify install.wim / install.esd presence ──
+ $wimPath = Join-Path $driveLetter "sources\install.wim"
+ $esdPath = Join-Path $driveLetter "sources\install.esd"
+
+ if (-not (Test-Path $wimPath) -and -not (Test-Path $esdPath)) {
+ Dismount-DiskImage -ImagePath $isoPath | Out-Null
+ Write-Win11ISOLog "ERROR: install.wim/install.esd not found — not a valid Windows ISO."
+ [System.Windows.MessageBox]::Show(
+ "This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.",
+ "Invalid ISO", "OK", "Error")
+ Set-WinUtilProgressBar -Label "" -Percent 0
+ return
+ }
+
+ $activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath }
+
+ # ── Read edition / architecture info ──
+ Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55
+
+ $imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName
+
+ # ── Verify at least one Win11 edition is present ──
+ $isWin11 = $imageInfo | Where-Object { $_.ImageName -match "Windows 11" }
+ if (-not $isWin11) {
+ Dismount-DiskImage -ImagePath $isoPath | Out-Null
+ Write-Win11ISOLog "ERROR: No 'Windows 11' edition found in the image."
+ [System.Windows.MessageBox]::Show(
+ "No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.",
+ "Not a Windows 11 ISO", "OK", "Error")
+ Set-WinUtilProgressBar -Label "" -Percent 0
+ return
+ }
+
+ # Store edition info for later index lookup
+ $sync["Win11ISOImageInfo"] = $imageInfo
+
+ # ── Populate UI ──
+ $sync["WPFWin11ISOMountDriveLetter"].Text = "Mounted at: $driveLetter | Image file: $(Split-Path $activeWim -Leaf)"
+ $sync["WPFWin11ISOEditionComboBox"].Dispatcher.Invoke([action]{
+ $sync["WPFWin11ISOEditionComboBox"].Items.Clear()
+ foreach ($img in $imageInfo) {
+ [void]$sync["WPFWin11ISOEditionComboBox"].Items.Add("$($img.ImageIndex): $($img.ImageName)")
+ }
+ if ($sync["WPFWin11ISOEditionComboBox"].Items.Count -gt 0) {
+ $sync["WPFWin11ISOEditionComboBox"].SelectedIndex = 0
+ }
+ })
+ $sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Visible"
+
+ # Store for later steps
+ $sync["Win11ISODriveLetter"] = $driveLetter
+ $sync["Win11ISOWimPath"] = $activeWim
+ $sync["Win11ISOImagePath"] = $isoPath
+
+ # Reveal Step 3
+ $sync["WPFWin11ISOModifySection"].Visibility = "Visible"
+
+ Set-WinUtilProgressBar -Label "ISO verified ✔" -Percent 100
+ Write-Win11ISOLog "ISO verified OK. Editions found: $($imageInfo.Count)"
+ }
+ catch {
+ Write-Win11ISOLog "ERROR during mount/verify: $_"
+ [System.Windows.MessageBox]::Show(
+ "An error occurred while mounting or verifying the ISO:`n`n$_",
+ "Error", "OK", "Error")
+ }
+ finally {
+ Start-Sleep -Milliseconds 800
+ Set-WinUtilProgressBar -Label "" -Percent 0
+ }
+}
+
+function Invoke-WinUtilISOModify {
+ <#
+ .SYNOPSIS
+ Extracts ISO contents to a temp working directory, modifies install.wim,
+ then repackages the image. Reveals Step 4 (output options) on success.
+
+ .NOTES
+ This function runs inside a PowerShell runspace so the UI stays responsive.
+ Placeholder modification logic is provided; extend as needed.
+ #>
+
+ $isoPath = $sync["Win11ISOImagePath"]
+ $driveLetter= $sync["Win11ISODriveLetter"]
+ $wimPath = $sync["Win11ISOWimPath"]
+
+ if (-not $isoPath) {
+ [System.Windows.MessageBox]::Show(
+ "No verified ISO found. Please complete Steps 1 and 2 first.",
+ "Not Ready", "OK", "Warning")
+ return
+ }
+
+ # ── Resolve selected edition index from the ComboBox ──
+ $selectedItem = $sync["WPFWin11ISOEditionComboBox"].SelectedItem
+ $selectedWimIndex = 1 # default fallback
+ if ($selectedItem -and $selectedItem -match '^(\d+):') {
+ $selectedWimIndex = [int]$Matches[1]
+ } elseif ($sync["Win11ISOImageInfo"]) {
+ $selectedWimIndex = $sync["Win11ISOImageInfo"][0].ImageIndex
+ }
+ $selectedEditionName = if ($selectedItem) { ($selectedItem -replace '^\d+:\s*', '') } else { "Unknown" }
+ Write-Win11ISOLog "Selected edition: $selectedEditionName (Index $selectedWimIndex)"
+
+ # Disable the modify button to prevent double-click
+ $sync["WPFWin11ISOModifyButton"].IsEnabled = $false
+
+ $existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
+ Where-Object { $_.PSIsContainer } |
+ Sort-Object LastWriteTime -Descending |
+ Select-Object -First 1
+
+ $workDir = if ($existingWorkDir) {
+ Write-Win11ISOLog "Reusing existing temp directory: $($existingWorkDir.FullName)"
+ $existingWorkDir.FullName
+ } else {
+ Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
+ }
+
+ # ── Resolve autounattend.xml content ──────────────────────────────────────
+ # Compiled winutil.ps1 sets $WinUtilAutounattendXml before main.ps1 runs.
+ # In dev/source mode fall back to reading tools\autounattend.xml directly.
+ $autounattendContent = if ($WinUtilAutounattendXml) {
+ $WinUtilAutounattendXml
+ } else {
+ $toolsXml = Join-Path $PSScriptRoot "..\..\tools\autounattend.xml"
+ if (Test-Path $toolsXml) { Get-Content $toolsXml -Raw } else { "" }
+ }
+
+ # ── Run modification in a background runspace ──
+ $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
+ $runspace.ApartmentState = "STA"
+ $runspace.ThreadOptions = "ReuseThread"
+ $runspace.Open()
+ $runspace.SessionStateProxy.SetVariable("sync", $sync)
+ $runspace.SessionStateProxy.SetVariable("isoPath", $isoPath)
+ $runspace.SessionStateProxy.SetVariable("driveLetter", $driveLetter)
+ $runspace.SessionStateProxy.SetVariable("wimPath", $wimPath)
+ $runspace.SessionStateProxy.SetVariable("workDir", $workDir)
+ $runspace.SessionStateProxy.SetVariable("selectedWimIndex", $selectedWimIndex)
+ $runspace.SessionStateProxy.SetVariable("selectedEditionName", $selectedEditionName)
+ $runspace.SessionStateProxy.SetVariable("autounattendContent", $autounattendContent)
+
+ # Serialize functions so they are available inside the runspace
+ $isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + `
+ ${function:Invoke-WinUtilISOScript}.ToString() + "`n}"
+ $runspace.SessionStateProxy.SetVariable("isoScriptFuncDef", $isoScriptFuncDef)
+
+ $win11ISOLogFuncDef = "function Write-Win11ISOLog {`n" + `
+ ${function:Write-Win11ISOLog}.ToString() + "`n}"
+ $runspace.SessionStateProxy.SetVariable("win11ISOLogFuncDef", $win11ISOLogFuncDef)
+
+ $refreshUSBFuncDef = "function Invoke-WinUtilISORefreshUSBDrives {`n" + `
+ ${function:Invoke-WinUtilISORefreshUSBDrives}.ToString() + "`n}"
+ $runspace.SessionStateProxy.SetVariable("refreshUSBFuncDef", $refreshUSBFuncDef)
+
+ $script = [Management.Automation.PowerShell]::Create()
+ $script.Runspace = $runspace
+ $script.AddScript({
+
+ # Import helper functions into this runspace
+ . ([scriptblock]::Create($isoScriptFuncDef))
+ . ([scriptblock]::Create($win11ISOLogFuncDef))
+ . ([scriptblock]::Create($refreshUSBFuncDef))
+
+ function Log($msg) {
+ $ts = (Get-Date).ToString("HH:mm:ss")
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
+ $sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
+ $sync["WPFWin11ISOStatusLog"].ScrollToEnd()
+ })
+ }
+
+ function SetProgress($label, $pct) {
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync.progressBarTextBlock.Text = $label
+ $sync.progressBarTextBlock.ToolTip = $label
+ $sync.ProgressBar.Value = [Math]::Max($pct, 5)
+ })
+ }
+
+ try {
+ # ── Hide Steps 1-3 while modification is running; expand log to fill screen ──
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync["WPFWin11ISOSelectSection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
+ $expandedHeight = [Math]::Max(400, $sync["Form"].ActualHeight - 100)
+ $sync["WPFWin11ISOStatusLog"].Height = $expandedHeight
+ $sync["Win11ISOLogExpanded"] = $true
+ # Register the resize handler once so the log tracks window resizes
+ if (-not $sync["Win11ISOResizeHandlerAdded"]) {
+ $sync["Form"].add_SizeChanged({
+ if ($sync["Win11ISOLogExpanded"]) {
+ $sync["WPFWin11ISOStatusLog"].Height = [Math]::Max(400, $sync["Form"].ActualHeight - 100)
+ $sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
+ $sync["WPFWin11ISOStatusLog"].ScrollToEnd()
+ }
+ })
+ $sync["Win11ISOResizeHandlerAdded"] = $true
+ }
+ })
+
+ # ── 1. Create working directory structure ──
+ Log "Creating working directory: $workDir"
+ $isoContents = Join-Path $workDir "iso_contents"
+ $mountDir = Join-Path $workDir "wim_mount"
+ New-Item -ItemType Directory -Path $isoContents, $mountDir -Force | Out-Null
+ SetProgress "Copying ISO contents..." 10
+
+ # ── 2. Copy all ISO contents to the working directory ──
+ Log "Copying ISO contents from $driveLetter to $isoContents..."
+ $robocopyArgs = @($driveLetter, $isoContents, "/E", "/NFL", "/NDL", "/NJH", "/NJS")
+ & robocopy @robocopyArgs | Out-Null
+ Log "ISO contents copied."
+ SetProgress "Mounting install.wim..." 25
+
+ # ── 3. Copy install.wim to working dir (it may be read-only on the DVD) ──
+ $localWim = Join-Path $isoContents "sources\install.wim"
+ if (-not (Test-Path $localWim)) {
+ # ESD path
+ $localWim = Join-Path $isoContents "sources\install.esd"
+ }
+ # Ensure the file is writable
+ Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false
+
+ # ── 4. Mount the selected edition of install.wim ──
+ Log "Mounting install.wim (Index ${selectedWimIndex}: $selectedEditionName) at $mountDir..."
+ Mount-WindowsImage -ImagePath $localWim -Index $selectedWimIndex -Path $mountDir -ErrorAction Stop | Out-Null
+ SetProgress "Modifying install.wim..." 45
+
+ # ── 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 }
+
+ # ── 4b. DISM component store cleanup ──
+ # /ResetBase removes all superseded component versions from WinSxS,
+ # which is the single largest space saving possible (typically 300–800 MB).
+ # This must be done while the image is still mounted.
+ SetProgress "Cleaning up component store (WinSxS)..." 56
+ Log "Running DISM component store cleanup (/ResetBase)..."
+ & dism /English "/image:$mountDir" /Cleanup-Image /StartComponentCleanup /ResetBase | ForEach-Object { Log $_ }
+ Log "Component store cleanup complete."
+
+ # ── 5. Save and dismount the WIM ──
+ SetProgress "Saving modified install.wim..." 65
+ Log "Dismounting and saving install.wim. This will take several minutes..."
+ Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null
+ Log "install.wim saved."
+
+ # ── 5b. Strip unused editions — export only the selected index ──
+ # A standard multi-edition install.wim can be 4–5 GB; exporting a
+ # single index typically drops it to ~3 GB, saving 1–2 GB in the ISO.
+ SetProgress "Removing unused editions from install.wim..." 70
+ Log "Exporting edition '$selectedEditionName' (Index $selectedWimIndex) to a single-edition install.wim..."
+ $exportWim = Join-Path $isoContents "sources\install_export.wim"
+ Export-WindowsImage `
+ -SourceImagePath $localWim `
+ -SourceIndex $selectedWimIndex `
+ -DestinationImagePath $exportWim `
+ -ErrorAction Stop | Out-Null
+ Remove-Item -Path $localWim -Force
+ Rename-Item -Path $exportWim -NewName "install.wim" -Force
+ # Update local path so later steps (e.g. ISO build) reference the new file
+ $localWim = Join-Path $isoContents "sources\install.wim"
+ Log "Unused editions removed. install.wim now contains only '$selectedEditionName'."
+
+ SetProgress "Dismounting source ISO..." 80
+
+ # ── 6. Dismount the original ISO ──
+ Log "Dismounting original ISO..."
+ Dismount-DiskImage -ImagePath $isoPath | Out-Null
+
+ # Store work directory for output steps
+ $sync["Win11ISOWorkDir"] = $workDir
+ $sync["Win11ISOContentsDir"] = $isoContents
+
+ SetProgress "Modification complete ✔" 100
+ Log "install.wim modification complete. Choose an output option in Step 4."
+
+ # ── Reveal Step 4 on the UI thread ──
+ # Note: USB drive enumeration (Get-Disk) is intentionally deferred to
+ # when the user explicitly selects the USB option, to avoid blocking
+ # the UI thread here.
+ $sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{
+ $sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
+ })
+ }
+ catch {
+ Log "ERROR during modification: $_"
+
+ # ── Cleanup: dismount WIM if still mounted ──
+ try {
+ if (Test-Path $mountDir) {
+ $mountedImages = Get-WindowsImage -Mounted -ErrorAction SilentlyContinue |
+ Where-Object { $_.Path -eq $mountDir }
+ if ($mountedImages) {
+ Log "Cleaning up: dismounting install.wim (discarding changes)..."
+ Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null
+ }
+ }
+ } catch {
+ Log "Warning: could not dismount install.wim during cleanup: $_"
+ }
+
+ # ── Cleanup: dismount the source ISO ──
+ try {
+ $mountedISO = Get-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue
+ if ($mountedISO -and $mountedISO.Attached) {
+ Log "Cleaning up: dismounting source ISO..."
+ Dismount-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue | Out-Null
+ }
+ } catch {
+ Log "Warning: could not dismount ISO during cleanup: $_"
+ }
+
+ # ── Cleanup: remove temp working directory ──
+ try {
+ if (Test-Path $workDir) {
+ Log "Cleaning up: removing temp directory $workDir..."
+ Remove-Item -Path $workDir -Recurse -Force -ErrorAction SilentlyContinue
+ }
+ } catch {
+ Log "Warning: could not remove temp directory during cleanup: $_"
+ }
+
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ [System.Windows.MessageBox]::Show(
+ "An error occurred during install.wim modification:`n`n$_",
+ "Modification Error", "OK", "Error")
+ })
+ }
+ finally {
+ Start-Sleep -Milliseconds 800
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync.progressBarTextBlock.Text = ""
+ $sync.progressBarTextBlock.ToolTip = ""
+ $sync.ProgressBar.Value = 0
+ $sync["WPFWin11ISOModifyButton"].IsEnabled = $true
+ # ── Only restore steps 1-3 if Step 4 was NOT successfully shown ──
+ # When modification succeeds, Step 4 is visible and steps 1-3 stay
+ # hidden until the user clicks Clean & Reset.
+ if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") {
+ $sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
+ $sync["WPFWin11ISOMountSection"].Visibility = "Visible"
+ $sync["WPFWin11ISOModifySection"].Visibility = "Visible"
+ }
+ $sync["Win11ISOLogExpanded"] = $false
+ $sync["WPFWin11ISOStatusLog"].Height = 140
+ })
+ }
+ }) | Out-Null
+
+ $script.BeginInvoke() | Out-Null
+}
+
+function Invoke-WinUtilISOCleanAndReset {
+ <#
+ .SYNOPSIS
+ Deletes the temporary working directory created during ISO modification
+ and resets the entire ISO UI back to its initial state (Step 1 only).
+ #>
+
+ $workDir = $sync["Win11ISOWorkDir"]
+
+ if ($workDir -and (Test-Path $workDir)) {
+ $confirm = [System.Windows.MessageBox]::Show(
+ "This will delete the temporary working directory:`n`n$workDir`n`nAnd reset the interface back to the start.`n`nContinue?",
+ "Clean & Reset", "YesNo", "Warning")
+ if ($confirm -ne "Yes") { return }
+
+ try {
+ Write-Win11ISOLog "Deleting temp directory: $workDir"
+ Remove-Item -Path $workDir -Recurse -Force -ErrorAction Stop
+ Write-Win11ISOLog "Temp directory deleted."
+ } catch {
+ Write-Win11ISOLog "WARNING: could not fully delete temp directory: $_"
+ }
+ }
+
+ # Clear all stored ISO state
+ $sync["Win11ISOWorkDir"] = $null
+ $sync["Win11ISOContentsDir"] = $null
+ $sync["Win11ISOImagePath"] = $null
+ $sync["Win11ISODriveLetter"] = $null
+ $sync["Win11ISOWimPath"] = $null
+ $sync["Win11ISOImageInfo"] = $null
+ $sync["Win11ISOUSBDisks"] = $null
+
+ # Reset the UI to the initial state
+ $sync["WPFWin11ISOPath"].Text = "No ISO selected..."
+ $sync["WPFWin11ISOFileInfo"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOOptionUSB"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
+ $sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
+ $sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
+ $sync["WPFWin11ISOStatusLog"].Height = 140
+ $sync["WPFWin11ISOModifyButton"].IsEnabled = $true
+}
+
+function Invoke-WinUtilISOExport {
+ <#
+ .SYNOPSIS
+ Saves the modified ISO contents as a new bootable ISO file.
+ Uses oscdimg.exe (part of the Windows ADK) if present; falls back
+ to a reminder message if not installed.
+ #>
+ $contentsDir = $sync["Win11ISOContentsDir"]
+
+ if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
+ [System.Windows.MessageBox]::Show(
+ "No modified ISO content found. Please complete Steps 1–3 first.",
+ "Not Ready", "OK", "Warning")
+ return
+ }
+
+ Add-Type -AssemblyName System.Windows.Forms
+
+ $dlg = [System.Windows.Forms.SaveFileDialog]::new()
+ $dlg.Title = "Save Modified Windows 11 ISO"
+ $dlg.Filter = "ISO files (*.iso)|*.iso"
+ $dlg.FileName = "Win11_Modified_$(Get-Date -Format 'yyyyMMdd').iso"
+ $dlg.InitialDirectory = [System.Environment]::GetFolderPath("Desktop")
+
+ if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
+
+ $outputISO = $dlg.FileName
+ Write-Win11ISOLog "Exporting to ISO: $outputISO"
+ Set-WinUtilProgressBar -Label "Building ISO..." -Percent 10
+
+ # Locate oscdimg.exe (Windows ADK)
+ $oscdimg = Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue |
+ Select-Object -First 1 -ExpandProperty FullName
+
+ if (-not $oscdimg) {
+ Write-Win11ISOLog "oscdimg.exe not found. Attempting to install via winget..."
+ Set-WinUtilProgressBar -Label "Installing oscdimg..." -Percent 5
+ try {
+ $winget = Get-Command winget -ErrorAction Stop
+ $result = & $winget install -e --id Microsoft.OSCDIMG --accept-package-agreements --accept-source-agreements 2>&1
+ Write-Win11ISOLog "winget output: $result"
+ # Re-scan for oscdimg after install
+ $oscdimg = Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue |
+ Select-Object -First 1 -ExpandProperty FullName
+ } catch {
+ Write-Win11ISOLog "winget not available or install failed: $_"
+ }
+
+ if (-not $oscdimg) {
+ Set-WinUtilProgressBar -Label "" -Percent 0
+ Write-Win11ISOLog "oscdimg.exe still not found after install attempt."
+ [System.Windows.MessageBox]::Show(
+ "oscdimg.exe could not be found or installed automatically.`n`nPlease install it manually:`n winget install -e --id Microsoft.OSCDIMG`n`nOr install the Windows ADK from:`nhttps://learn.microsoft.com/windows-hardware/get-started/adk-install",
+ "oscdimg Not Found", "OK", "Warning")
+ return
+ }
+ Write-Win11ISOLog "oscdimg.exe installed successfully."
+ }
+
+ # Build boot parameters (BIOS + UEFI dual-boot)
+ $bootData = "2#p0,e,b`"$contentsDir\boot\etfsboot.com`"#pEF,e,b`"$contentsDir\efi\microsoft\boot\efisys.bin`""
+ $oscdimgArgs = @(
+ "-m", # ignore source path max size
+ "-o", # optimise storage
+ "-u2", # UDF 2.01
+ "-udfver102",
+ "-bootdata:$bootData",
+ "-l`"CTOS_MODIFIED`"",
+ "`"$contentsDir`"",
+ "`"$outputISO`""
+ )
+
+ try {
+ Write-Win11ISOLog "Running oscdimg..."
+ $proc = Start-Process -FilePath $oscdimg -ArgumentList $oscdimgArgs -Wait -PassThru -NoNewWindow
+ if ($proc.ExitCode -eq 0) {
+ Set-WinUtilProgressBar -Label "ISO exported ✔" -Percent 100
+ Write-Win11ISOLog "ISO exported successfully: $outputISO"
+ [System.Windows.MessageBox]::Show(
+ "ISO exported successfully!`n`n$outputISO",
+ "Export Complete", "OK", "Info")
+ } else {
+ Write-Win11ISOLog "oscdimg exited with code $($proc.ExitCode)."
+ [System.Windows.MessageBox]::Show(
+ "oscdimg exited with code $($proc.ExitCode).`nCheck the status log for details.",
+ "Export Error", "OK", "Error")
+ }
+ }
+ catch {
+ Write-Win11ISOLog "ERROR during ISO export: $_"
+ [System.Windows.MessageBox]::Show("ISO export failed:`n`n$_","Error","OK","Error")
+ }
+ finally {
+ Start-Sleep -Milliseconds 800
+ Set-WinUtilProgressBar -Label "" -Percent 0
+ }
+}
+
+function Invoke-WinUtilISORefreshUSBDrives {
+ <#
+ .SYNOPSIS
+ Populates the USB drive ComboBox with all currently attached removable drives.
+ #>
+ $combo = $sync["WPFWin11ISOUSBDriveComboBox"]
+ $combo.Items.Clear()
+
+ $removable = Get-Disk | Where-Object { $_.BusType -eq "USB" } | Sort-Object Number
+
+ if ($removable.Count -eq 0) {
+ $combo.Items.Add("No USB drives detected")
+ $combo.SelectedIndex = 0
+ Write-Win11ISOLog "No USB drives detected."
+ return
+ }
+
+ foreach ($disk in $removable) {
+ $sizeGB = [math]::Round($disk.Size / 1GB, 1)
+ $label = "Disk $($disk.Number): $($disk.FriendlyName) [$sizeGB GB] — $($disk.PartitionStyle)"
+ $combo.Items.Add($label)
+ }
+ $combo.SelectedIndex = 0
+ Write-Win11ISOLog "Found $($removable.Count) USB drive(s)."
+
+ # Store disk objects for later use
+ $sync["Win11ISOUSBDisks"] = $removable
+}
+
+function Invoke-WinUtilISOWriteUSB {
+ <#
+ .SYNOPSIS
+ Erases the selected USB drive and writes the modified Windows 11 ISO
+ content as a bootable installation drive (using DISM / robocopy approach).
+ #>
+ $contentsDir = $sync["Win11ISOContentsDir"]
+ $usbDisks = $sync["Win11ISOUSBDisks"]
+
+ if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
+ [System.Windows.MessageBox]::Show(
+ "No modified ISO content found. Please complete Steps 1–3 first.",
+ "Not Ready", "OK", "Warning")
+ return
+ }
+
+ $selectedIndex = $sync["WPFWin11ISOUSBDriveComboBox"].SelectedIndex
+ if ($selectedIndex -lt 0 -or -not $usbDisks -or $selectedIndex -ge $usbDisks.Count) {
+ [System.Windows.MessageBox]::Show(
+ "Please select a USB drive from the dropdown.",
+ "No Drive Selected", "OK", "Warning")
+ return
+ }
+
+ $targetDisk = $usbDisks[$selectedIndex]
+ $diskNum = $targetDisk.Number
+ $sizeGB = [math]::Round($targetDisk.Size / 1GB, 1)
+
+ $confirm = [System.Windows.MessageBox]::Show(
+ "ALL data on Disk $diskNum ($($targetDisk.FriendlyName), $sizeGB GB) will be PERMANENTLY ERASED.`n`nAre you sure you want to continue?",
+ "Confirm USB Erase", "YesNo", "Warning")
+
+ if ($confirm -ne "Yes") {
+ Write-Win11ISOLog "USB write cancelled by user."
+ return
+ }
+
+ $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
+ Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
+
+ $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
+ $runspace.ApartmentState = "STA"
+ $runspace.ThreadOptions = "ReuseThread"
+ $runspace.Open()
+ $runspace.SessionStateProxy.SetVariable("sync", $sync)
+ $runspace.SessionStateProxy.SetVariable("diskNum", $diskNum)
+ $runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
+
+ $script = [Management.Automation.PowerShell]::Create()
+ $script.Runspace = $runspace
+ $script.AddScript({
+
+ function Log($msg) {
+ $ts = (Get-Date).ToString("HH:mm:ss")
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
+ $sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
+ $sync["WPFWin11ISOStatusLog"].ScrollToEnd()
+ })
+ }
+ function SetProgress($label, $pct) {
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync.progressBarTextBlock.Text = $label
+ $sync.progressBarTextBlock.ToolTip = $label
+ $sync.ProgressBar.Value = [Math]::Max($pct, 5)
+ })
+ }
+
+ try {
+ SetProgress "Formatting USB drive..." 10
+
+ # ── Diskpart script: clean, GPT, create ESP + data partitions ──
+ $dpScript = @"
+select disk $diskNum
+clean
+convert gpt
+create partition efi size=512
+format quick fs=fat32 label="SYSTEM"
+assign
+create partition primary
+format quick fs=fat32 label="WINPE"
+assign
+exit
+"@
+ $dpFile = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
+ $dpScript | Set-Content -Path $dpFile -Encoding ASCII
+ Log "Running diskpart on Disk $diskNum..."
+ diskpart /s $dpFile | Out-Null
+ Remove-Item $dpFile -Force
+
+ SetProgress "Identifying USB partitions..." 30
+ Start-Sleep -Seconds 3 # let Windows assign drive letters
+
+ # Find newly assigned drive letter for the data partition
+ $usbVol = Get-Partition -DiskNumber $diskNum |
+ Where-Object { $_.Type -eq "Basic" } |
+ Get-Volume |
+ Where-Object { $_.FileSystemLabel -eq "WINPE" } |
+ Select-Object -First 1
+
+ if (-not $usbVol) {
+ throw "Could not locate the formatted USB data partition. Drive letter may not have been assigned automatically."
+ }
+
+ $usbDrive = "$($usbVol.DriveLetter):"
+ Log "USB data partition: $usbDrive"
+ SetProgress "Copying Windows 11 files to USB..." 45
+
+ # ── Copy files (split large install.wim if > 4 GB for FAT32) ──
+ $installWim = Join-Path $contentsDir "sources\install.wim"
+ if (Test-Path $installWim) {
+ $wimSizeMB = [math]::Round((Get-Item $installWim).Length / 1MB)
+ if ($wimSizeMB -gt 3800) {
+ # FAT32 limit – split with DISM
+ Log "install.wim is $wimSizeMB MB – splitting for FAT32 compatibility..."
+ $splitDest = Join-Path $usbDrive "sources\install.swm"
+ New-Item -ItemType Directory -Path (Split-Path $splitDest) -Force | Out-Null
+ Split-WindowsImage -ImagePath $installWim `
+ -SplitImagePath $splitDest `
+ -FileSize 3800 -CheckIntegrity | Out-Null
+ Log "install.wim split complete."
+
+ # Copy everything else (exclude install.wim)
+ $robocopyArgs = @($contentsDir, $usbDrive, "/E", "/XF", "install.wim", "/NFL", "/NDL", "/NJH", "/NJS")
+ & robocopy @robocopyArgs | Out-Null
+ } else {
+ & robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
+ }
+ } else {
+ & robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
+ }
+
+ SetProgress "Finalising USB drive..." 90
+ Log "Files copied to USB."
+
+ SetProgress "USB write complete ✔" 100
+ Log "USB drive is ready for use."
+
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ [System.Windows.MessageBox]::Show(
+ "USB drive created successfully!`n`nYou can now boot from this drive to install Windows 11.",
+ "USB Ready", "OK", "Info")
+ })
+ }
+ catch {
+ Log "ERROR during USB write: $_"
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ [System.Windows.MessageBox]::Show(
+ "USB write failed:`n`n$_",
+ "USB Write Error", "OK", "Error")
+ })
+ }
+ finally {
+ Start-Sleep -Milliseconds 800
+ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
+ $sync.progressBarTextBlock.Text = ""
+ $sync.progressBarTextBlock.ToolTip = ""
+ $sync.ProgressBar.Value = 0
+ $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
+ })
+ }
+ }) | Out-Null
+
+ $script.BeginInvoke() | Out-Null
+}
diff --git a/functions/private/Invoke-WinUtilISOScript.ps1 b/functions/private/Invoke-WinUtilISOScript.ps1
new file mode 100644
index 0000000000..8ca5268d50
--- /dev/null
+++ b/functions/private/Invoke-WinUtilISOScript.ps1
@@ -0,0 +1,331 @@
+function Invoke-WinUtilISOScript {
+ <#
+ .SYNOPSIS
+ Applies WinUtil modifications to a mounted Windows 11 install.wim image.
+
+ .DESCRIPTION
+ Performs the following operations against an already-mounted WIM image:
+
+ 1. Removes provisioned AppX bloatware packages via DISM.
+ 2. Deletes Microsoft Edge program files.
+ 3. Removes OneDriveSetup.exe from the system image.
+ 4. Loads offline registry hives (COMPONENTS, DEFAULT, NTUSER, SOFTWARE, SYSTEM)
+ and applies the following tweaks:
+ - Bypasses hardware requirement checks (CPU, RAM, SecureBoot, Storage, TPM).
+ - Disables sponsored-app delivery and ContentDeliveryManager features.
+ - Enables local-account OOBE path (BypassNRO).
+ - Writes autounattend.xml to the Sysprep directory inside the WIM and,
+ optionally, to the ISO/USB root so Windows Setup picks it up at boot.
+ - Disables reserved storage.
+ - Disables BitLocker device encryption.
+ - Hides the Chat (Teams) taskbar icon.
+ - Removes Edge uninstall registry entries.
+ - Disables OneDrive folder backup (KFM).
+ - Disables telemetry, advertising ID, and input personalization.
+ - Blocks post-install delivery of DevHome, Outlook, and Teams.
+ - Disables Windows Copilot.
+ - Disables Windows Update during OOBE.
+ 5. Deletes unwanted scheduled-task XML definition files (CEIP, Appraiser, etc.).
+ 6. Removes the support\ folder from the ISO contents directory (if supplied).
+
+ Mounting and dismounting the WIM is the responsibility of the caller
+ (e.g. Invoke-WinUtilISO).
+
+ .PARAMETER ScratchDir
+ Mandatory. Full path to the directory where the Windows image is currently mounted.
+ Example: C:\Users\USERNAME\AppData\Local\Temp\WinUtil_Win11ISO_20260222\wim_mount
+
+ .PARAMETER ISOContentsDir
+ Optional. Root directory of the extracted ISO contents.
+ When supplied, autounattend.xml is also written here so Windows Setup picks it
+ up automatically at boot, and the support\ folder is deleted from that location.
+
+ .PARAMETER AutoUnattendXml
+ Optional. Full XML content for autounattend.xml.
+ In compiled winutil.ps1 this is the embedded $WinUtilAutounattendXml here-string;
+ in dev mode it is read from tools\autounattend.xml.
+ If empty, the OOBE bypass file is skipped and a warning is logged.
+
+ .PARAMETER Log
+ Optional ScriptBlock used for progress/status logging.
+ Receives a single [string] message argument.
+ Defaults to { param($m) Write-Output $m } when not supplied.
+
+ .EXAMPLE
+ Invoke-WinUtilISOScript -ScratchDir "C:\Temp\wim_mount"
+
+ .EXAMPLE
+ Invoke-WinUtilISOScript `
+ -ScratchDir $mountDir `
+ -ISOContentsDir $isoRoot `
+ -AutoUnattendXml (Get-Content .\tools\autounattend.xml -Raw) `
+ -Log { param($m) Write-Host $m }
+
+ .NOTES
+ Author : Chris Titus @christitustech
+ GitHub : https://github.com/ChrisTitusTech
+ Version : 26.02.22
+ #>
+ param (
+ [Parameter(Mandatory)][string]$ScratchDir,
+ # Root directory of the extracted ISO contents. When supplied, autounattend.xml
+ # is written here so Windows Setup picks it up automatically at boot.
+ [string]$ISOContentsDir = "",
+ # Autounattend XML content. In compiled winutil.ps1 this comes from the embedded
+ # $WinUtilAutounattendXml here-string; in dev mode it is read from tools\autounattend.xml.
+ [string]$AutoUnattendXml = "",
+ [scriptblock]$Log = { param($m) Write-Output $m }
+ )
+
+ # ── Resolve admin group name (for takeown / icacls) ──────────────────────
+ $adminSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
+ $adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount])
+
+ # ── Local helpers ─────────────────────────────────────────────────────────
+ function Set-ISOScriptReg {
+ param ([string]$path, [string]$name, [string]$type, [string]$value)
+ try {
+ & reg add $path /v $name /t $type /d $value /f
+ & $Log "Set registry value: $path\$name"
+ } catch {
+ & $Log "Error setting registry value: $_"
+ }
+ }
+
+ function Remove-ISOScriptReg {
+ param ([string]$path)
+ try {
+ & reg delete $path /f
+ & $Log "Removed registry key: $path"
+ } catch {
+ & $Log "Error removing registry key: $_"
+ }
+ }
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 1. Remove provisioned AppX packages
+ # ═════════════════════════════════════════════════════════════════════════
+ & $Log "Removing provisioned AppX packages..."
+
+ $packages = & dism /English "/image:$ScratchDir" /Get-ProvisionedAppxPackages |
+ ForEach-Object {
+ if ($_ -match 'PackageName : (.*)') { $matches[1] }
+ }
+
+ $packagePrefixes = @(
+ 'AppUp.IntelManagementandSecurityStatus',
+ 'Clipchamp.Clipchamp',
+ 'DolbyLaboratories.DolbyAccess',
+ 'DolbyLaboratories.DolbyDigitalPlusDecoderOEM',
+ 'Microsoft.BingNews',
+ 'Microsoft.BingSearch',
+ 'Microsoft.BingWeather',
+ 'Microsoft.Copilot',
+ 'Microsoft.Windows.CrossDevice',
+ 'Microsoft.GamingApp',
+ 'Microsoft.GetHelp',
+ 'Microsoft.Getstarted',
+ 'Microsoft.Microsoft3DViewer',
+ 'Microsoft.MicrosoftOfficeHub',
+ 'Microsoft.MicrosoftSolitaireCollection',
+ 'Microsoft.MicrosoftStickyNotes',
+ 'Microsoft.MixedReality.Portal',
+ 'Microsoft.MSPaint',
+ 'Microsoft.Office.OneNote',
+ 'Microsoft.OfficePushNotificationUtility',
+ 'Microsoft.OutlookForWindows',
+ 'Microsoft.Paint',
+ 'Microsoft.People',
+ 'Microsoft.PowerAutomateDesktop',
+ 'Microsoft.SkypeApp',
+ 'Microsoft.StartExperiencesApp',
+ 'Microsoft.Todos',
+ 'Microsoft.Wallet',
+ 'Microsoft.Windows.DevHome',
+ 'Microsoft.Windows.Copilot',
+ 'Microsoft.Windows.Teams',
+ 'Microsoft.WindowsAlarms',
+ 'Microsoft.WindowsCamera',
+ 'microsoft.windowscommunicationsapps',
+ 'Microsoft.WindowsFeedbackHub',
+ 'Microsoft.WindowsMaps',
+ 'Microsoft.WindowsSoundRecorder',
+ 'Microsoft.ZuneMusic',
+ 'Microsoft.ZuneVideo',
+ 'MicrosoftCorporationII.MicrosoftFamily',
+ 'MicrosoftCorporationII.QuickAssist',
+ 'MSTeams',
+ 'MicrosoftTeams'
+ )
+
+ $packagesToRemove = $packages | Where-Object {
+ $pkg = $_
+ $packagePrefixes | Where-Object { $pkg -like "*$_*" }
+ }
+ foreach ($package in $packagesToRemove) {
+ & dism /English "/image:$ScratchDir" /Remove-ProvisionedAppxPackage "/PackageName:$package"
+ }
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 2. Remove Edge
+ # ═════════════════════════════════════════════════════════════════════════
+ & $Log "Removing Edge..."
+ Remove-Item -Path "$ScratchDir\Program Files (x86)\Microsoft\Edge" -Recurse -Force -ErrorAction SilentlyContinue
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 3. Remove OneDrive
+ # ═════════════════════════════════════════════════════════════════════════
+ & $Log "Removing OneDrive..."
+ & takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null
+ & icacls "$ScratchDir\Windows\System32\OneDriveSetup.exe" /grant "$($adminGroup.Value):(F)" /T /C | Out-Null
+ Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 4. Registry tweaks
+ # ═════════════════════════════════════════════════════════════════════════
+ & $Log "Loading offline registry hives..."
+ reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
+ reg load HKLM\zDEFAULT "$ScratchDir\Windows\System32\config\default"
+ reg load HKLM\zNTUSER "$ScratchDir\Users\Default\ntuser.dat"
+ reg load HKLM\zSOFTWARE "$ScratchDir\Windows\System32\config\SOFTWARE"
+ reg load HKLM\zSYSTEM "$ScratchDir\Windows\System32\config\SYSTEM"
+
+ & $Log "Bypassing system requirements..."
+ Set-ISOScriptReg 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassCPUCheck' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassRAMCheck' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassSecureBootCheck' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassStorageCheck' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassTPMCheck' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 'REG_DWORD' '1'
+
+ & $Log "Disabling sponsored apps..."
+ Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'OemPreInstalledAppsEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SilentInstalledAppsEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableWindowsConsumerFeatures' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'ContentDeliveryAllowed' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\PolicyManager\current\device\Start' 'ConfigureStartPins' 'REG_SZ' '{"pinnedList": [{}]}'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'FeatureManagementEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEverEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SoftLandingEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContentEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-310093Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338388Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338389Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338393Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353694Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353696Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SystemPaneSuggestionsEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\PushToInstall' 'DisablePushToInstall' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\MRT' 'DontOfferThroughWUAU' 'REG_DWORD' '1'
+ Remove-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\Subscriptions'
+ Remove-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\SuggestedApps'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableConsumerAccountStateContent' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableCloudOptimizedContent' 'REG_DWORD' '1'
+
+ & $Log "Enabling local accounts on OOBE..."
+ 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."
+
+ # ── Place autounattend.xml at the ISO / USB root ──────────────────────
+ # Windows Setup reads this file first (before booting into the OS),
+ # which is what drives the local-account / OOBE bypass at install time.
+ if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
+ $isoDest = Join-Path $ISOContentsDir "autounattend.xml"
+ Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force
+ & $Log "Written autounattend.xml to ISO root ($isoDest)."
+ }
+ } else {
+ & $Log "Warning: autounattend.xml content is empty — skipping OOBE bypass file."
+ }
+
+ & $Log "Disabling reserved storage..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0'
+
+ & $Log "Disabling BitLocker device encryption..."
+ Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' 'PreventDeviceEncryption' 'REG_DWORD' '1'
+
+ & $Log "Disabling Chat icon..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Chat' 'ChatIcon' 'REG_DWORD' '3'
+ Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'TaskbarMn' 'REG_DWORD' '0'
+
+ & $Log "Removing Edge registry entries..."
+ Remove-ISOScriptReg 'HKLM\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge'
+ Remove-ISOScriptReg 'HKLM\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update'
+
+ & $Log "Disabling OneDrive folder backup..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\OneDrive' 'DisableFileSyncNGSC' 'REG_DWORD' '1'
+
+ & $Log "Disabling telemetry..."
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo' 'Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\Privacy' 'TailoredExperiencesWithDiagnosticDataEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' 'HasAccepted' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Input\TIPC' 'Enabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitInkCollection' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitTextCollection' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization\TrainedDataStore' 'HarvestContacts' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Personalization\Settings' 'AcceptedPrivacyPolicy' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' 'AllowTelemetry' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\dmwappushservice' 'Start' 'REG_DWORD' '4'
+
+ & $Log "Preventing installation of DevHome and Outlook..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' 'workCompleted' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate' 'workCompleted' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\DevHomeUpdate' 'workCompleted' 'REG_DWORD' '1'
+ Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate'
+ Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate'
+
+ & $Log "Disabling Copilot..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1'
+
+ & $Log "Disabling Windows Update during OOBE (re-enabled on first logon via FirstLogon.ps1)..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'NoAutoUpdate' 'REG_DWORD' '1'
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'DisableWindowsUpdateAccess' 'REG_DWORD' '1'
+
+ & $Log "Preventing installation of Teams..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Teams' 'DisableInstallation' 'REG_DWORD' '1'
+
+ & $Log "Preventing installation of new Outlook..."
+ Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Mail' 'PreventRun' 'REG_DWORD' '1'
+
+ & $Log "Unloading offline registry hives..."
+ reg unload HKLM\zCOMPONENTS
+ reg unload HKLM\zDEFAULT
+ reg unload HKLM\zNTUSER
+ reg unload HKLM\zSOFTWARE
+ reg unload HKLM\zSYSTEM
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 5. Delete scheduled task definition files
+ # ═════════════════════════════════════════════════════════════════════════
+ & $Log "Deleting scheduled task definition files..."
+ $tasksPath = "$ScratchDir\Windows\System32\Tasks"
+
+ Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser" -Force -ErrorAction SilentlyContinue
+ Remove-Item "$tasksPath\Microsoft\Windows\Customer Experience Improvement Program" -Recurse -Force -ErrorAction SilentlyContinue
+ Remove-Item "$tasksPath\Microsoft\Windows\Application Experience\ProgramDataUpdater" -Force -ErrorAction SilentlyContinue
+ Remove-Item "$tasksPath\Microsoft\Windows\Chkdsk\Proxy" -Force -ErrorAction SilentlyContinue
+ Remove-Item "$tasksPath\Microsoft\Windows\Windows Error Reporting\QueueReporting" -Force -ErrorAction SilentlyContinue
+
+ & $Log "Scheduled task files deleted."
+
+ # ═════════════════════════════════════════════════════════════════════════
+ # 6. Remove ISO support folder (fresh-install only; not needed)
+ # ═════════════════════════════════════════════════════════════════════════
+ if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
+ & $Log "Removing ISO support\ folder..."
+ Remove-Item -Path (Join-Path $ISOContentsDir "support") -Recurse -Force -ErrorAction SilentlyContinue
+ & $Log "ISO support\ folder removed."
+ }
+}
diff --git a/scripts/main.ps1 b/scripts/main.ps1
index 6359933ecd..9353e8b9c9 100644
--- a/scripts/main.ps1
+++ b/scripts/main.ps1
@@ -281,6 +281,7 @@ $commonKeyEvents = {
"T" { Invoke-WPFButton "WPFTab2BT"; $keyEventArgs.Handled = $true } # Navigate to Tweaks tab
"C" { Invoke-WPFButton "WPFTab3BT"; $keyEventArgs.Handled = $true } # Navigate to Config tab
"U" { Invoke-WPFButton "WPFTab4BT"; $keyEventArgs.Handled = $true } # Navigate to Updates tab
+ "W" { Invoke-WPFButton "WPFTab5BT"; $keyEventArgs.Handled = $true } # Navigate to Win11ISO tab
}
}
# Handle Ctrl key combinations for specific actions
@@ -531,5 +532,56 @@ $sync["FontScalingApplyButton"].Add_Click({
Invoke-WPFPopup -Action "Hide" -Popups @("FontScaling")
})
+# ── Win11ISO Tab button handlers ──────────────────────────────────────────────
+
+$sync["WPFWin11ISOBrowseButton"].Add_Click({
+ Write-Debug "WPFWin11ISOBrowseButton clicked"
+ Invoke-WinUtilISOBrowse
+})
+
+$sync["WPFWin11ISODownloadLink"].Add_Click({
+ Write-Debug "WPFWin11ISODownloadLink clicked"
+ Start-Process "https://www.microsoft.com/software-download/windows11"
+})
+
+$sync["WPFWin11ISOMountButton"].Add_Click({
+ Write-Debug "WPFWin11ISOMountButton clicked"
+ Invoke-WinUtilISOMountAndVerify
+})
+
+$sync["WPFWin11ISOModifyButton"].Add_Click({
+ Write-Debug "WPFWin11ISOModifyButton clicked"
+ Invoke-WinUtilISOModify
+})
+
+$sync["WPFWin11ISOChooseISOButton"].Add_Click({
+ Write-Debug "WPFWin11ISOChooseISOButton clicked"
+ $sync["WPFWin11ISOOptionUSB"].Visibility = "Collapsed"
+ Invoke-WinUtilISOExport
+})
+
+$sync["WPFWin11ISOChooseUSBButton"].Add_Click({
+ Write-Debug "WPFWin11ISOChooseUSBButton clicked"
+ $sync["WPFWin11ISOOptionUSB"].Visibility = "Visible"
+ Invoke-WinUtilISORefreshUSBDrives
+})
+
+$sync["WPFWin11ISORefreshUSBButton"].Add_Click({
+ Write-Debug "WPFWin11ISORefreshUSBButton clicked"
+ Invoke-WinUtilISORefreshUSBDrives
+})
+
+$sync["WPFWin11ISOWriteUSBButton"].Add_Click({
+ Write-Debug "WPFWin11ISOWriteUSBButton clicked"
+ Invoke-WinUtilISOWriteUSB
+})
+
+$sync["WPFWin11ISOCleanResetButton"].Add_Click({
+ Write-Debug "WPFWin11ISOCleanResetButton clicked"
+ Invoke-WinUtilISOCleanAndReset
+})
+
+# ──────────────────────────────────────────────────────────────────────────────
+
$sync["Form"].ShowDialog() | out-null
Stop-Transcript
diff --git a/tools/autounattend.xml b/tools/autounattend.xml
new file mode 100644
index 0000000000..04a88d84f7
--- /dev/null
+++ b/tools/autounattend.xml
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+
+
+ 00000-00000-00000-00000-00000
+ Always
+
+ true
+
+ false
+
+
+ 1
+ reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassTPMCheck /t REG_DWORD /d 1 /f
+
+
+ 2
+ reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassSecureBootCheck /t REG_DWORD /d 1 /f
+
+
+ 3
+ reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassRAMCheck /t REG_DWORD /d 1 /f
+
+
+
+
+
+
+
+
+
+ 1
+ powershell.exe -WindowStyle "Normal" -NoProfile -Command "$xml = [xml]::new(); $xml.Load('C:\Windows\Panther\unattend.xml'); $sb = [scriptblock]::Create( $xml.unattend.Extensions.ExtractScript ); Invoke-Command -ScriptBlock $sb -ArgumentList $xml;"
+
+
+ 2
+ powershell.exe -WindowStyle "Normal" -ExecutionPolicy "Unrestricted" -NoProfile -File "C:\Windows\Setup\Scripts\Specialize.ps1"
+
+
+ 3
+ reg.exe load "HKU\DefaultUser" "C:\Users\Default\NTUSER.DAT"
+
+
+ 4
+ powershell.exe -WindowStyle "Normal" -ExecutionPolicy "Unrestricted" -NoProfile -File "C:\Windows\Setup\Scripts\DefaultUser.ps1"
+
+
+ 5
+ reg.exe unload "HKU\DefaultUser"
+
+
+
+
+
+
+
+
+
+ 3
+ true
+ true
+ true
+
+
+
+ 1
+ powershell.exe -WindowStyle "Normal" -ExecutionPolicy "Unrestricted" -NoProfile -File "C:\Windows\Setup\Scripts\FirstLogon.ps1"
+
+
+
+
+
+
+param(
+ [xml]$Document
+);
+
+foreach( $file in $Document.unattend.Extensions.File ) {
+ $path = [System.Environment]::ExpandEnvironmentVariables( $file.GetAttribute( 'path' ) );
+ mkdir -Path( $path | Split-Path -Parent ) -ErrorAction 'SilentlyContinue';
+ $encoding = switch( [System.IO.Path]::GetExtension( $path ) ) {
+ { $_ -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( $file.InnerText.Trim() );
+ [System.IO.File]::WriteAllBytes( $path, $bytes );
+}
+
+
+<LayoutModificationTemplate xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification" xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout" xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout" Version="1">
+ <CustomTaskbarLayoutCollection PinListPlacement="Replace">
+ <defaultlayout:TaskbarLayout>
+ <taskbar:TaskbarPinList>
+ <taskbar:DesktopApp DesktopApplicationLinkPath="#leaveempty" />
+ </taskbar:TaskbarPinList>
+ </defaultlayout:TaskbarLayout>
+ </CustomTaskbarLayoutCollection>
+</LayoutModificationTemplate>
+
+
+HKU = &H80000003
+Set reg = GetObject("winmgmts://./root/default:StdRegProv")
+Set fso = CreateObject("Scripting.FileSystemObject")
+
+If reg.EnumKey(HKU, "", sids) = 0 Then
+ If Not IsNull(sids) Then
+ For Each sid In sids
+ key = sid + "\Software\Policies\Microsoft\Windows\Explorer"
+ name = "LockedStartLayout"
+ If reg.GetDWORDValue(HKU, key, name, existing) = 0 Then
+ reg.SetDWORDValue HKU, key, name, 0
+ End If
+ Next
+ End If
+End If
+
+
+<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
+ <Triggers>
+ <EventTrigger>
+ <Enabled>true</Enabled>
+ <Subscription><QueryList><Query Id="0" Path="Application"><Select Path="Application">*[System[Provider[@Name='UnattendGenerator'] and EventID=1]]</Select></Query></QueryList></Subscription>
+ </EventTrigger>
+ </Triggers>
+ <Principals>
+ <Principal id="Author">
+ <UserId>S-1-5-18</UserId>
+ <RunLevel>LeastPrivilege</RunLevel>
+ </Principal>
+ </Principals>
+ <Settings>
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
+ <AllowHardTerminate>true</AllowHardTerminate>
+ <StartWhenAvailable>false</StartWhenAvailable>
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
+ <IdleSettings>
+ <StopOnIdleEnd>true</StopOnIdleEnd>
+ <RestartOnIdle>false</RestartOnIdle>
+ </IdleSettings>
+ <AllowStartOnDemand>true</AllowStartOnDemand>
+ <Enabled>true</Enabled>
+ <Hidden>false</Hidden>
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
+ <WakeToRun>false</WakeToRun>
+ <ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
+ <Priority>7</Priority>
+ </Settings>
+ <Actions Context="Author">
+ <Exec>
+ <Command>C:\Windows\System32\wscript.exe</Command>
+ <Arguments>C:\Windows\Setup\Scripts\UnlockStartLayout.vbs</Arguments>
+ </Exec>
+ </Actions>
+</Task>
+
+
+$json = '{"pinnedList":[]}';
+if( [System.Environment]::OSVersion.Version.Build -lt 20000 ) {
+ return;
+}
+$key = 'Registry::HKLM\SOFTWARE\Microsoft\PolicyManager\current\device\Start';
+New-Item -Path $key -ItemType 'Directory' -ErrorAction 'SilentlyContinue';
+Set-ItemProperty -LiteralPath $key -Name 'ConfigureStartPins' -Value $json -Type 'String';
+
+
+$lightThemeSystem = 0;
+$lightThemeApps = 0;
+$accentColorOnStart = 0;
+$enableTransparency = 0;
+$htmlAccentColor = '#0078D4';
+& {
+ $params = @{
+ LiteralPath = 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
+ Force = $true;
+ Type = 'DWord';
+ };
+ Set-ItemProperty @params -Name 'SystemUsesLightTheme' -Value $lightThemeSystem;
+ Set-ItemProperty @params -Name 'AppsUseLightTheme' -Value $lightThemeApps;
+ Set-ItemProperty @params -Name 'ColorPrevalence' -Value $accentColorOnStart;
+ Set-ItemProperty @params -Name 'EnableTransparency' -Value $enableTransparency;
+};
+& {
+ Add-Type -AssemblyName 'System.Drawing';
+ $accentColor = [System.Drawing.ColorTranslator]::FromHtml( $htmlAccentColor );
+
+ function ConvertTo-DWord {
+ param(
+ [System.Drawing.Color]
+ $Color
+ );
+
+ [byte[]]$bytes = @(
+ $Color.R;
+ $Color.G;
+ $Color.B;
+ $Color.A;
+ );
+ return [System.BitConverter]::ToUInt32( $bytes, 0);
+ }
+
+ $startColor = [System.Drawing.Color]::FromArgb( 0xD2, $accentColor );
+ Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent' -Name 'StartColorMenu' -Value( ConvertTo-DWord -Color $accentColor ) -Type 'DWord' -Force;
+ Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent' -Name 'AccentColorMenu' -Value( ConvertTo-DWord -Color $accentColor ) -Type 'DWord' -Force;
+ Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\DWM' -Name 'AccentColor' -Value( ConvertTo-DWord -Color $accentColor ) -Type 'DWord' -Force;
+ $params = @{
+ LiteralPath = 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent';
+ Name = 'AccentPalette';
+ };
+ $palette = Get-ItemPropertyValue @params;
+ $index = 20;
+ $palette[ $index++ ] = $accentColor.R;
+ $palette[ $index++ ] = $accentColor.G;
+ $palette[ $index++ ] = $accentColor.B;
+ $palette[ $index++ ] = $accentColor.A;
+ Set-ItemProperty @params -Value $palette -Type 'Binary' -Force;
+};
+
+
+$scripts = @(
+ {
+ reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f;
+ };
+ {
+ net.exe accounts /maxpwage:UNLIMITED;
+ };
+ {
+ reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f;
+ [System.Diagnostics.EventLog]::CreateEventSource( 'UnattendGenerator', 'Application' );
+ };
+ {
+ Register-ScheduledTask -TaskName 'UnlockStartLayout' -Xml $( Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\UnlockStartLayout.xml' -Raw );
+ };
+ {
+ reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\FileSystem" /v LongPathsEnabled /t REG_DWORD /d 1 /f
+ };
+ {
+ Remove-Item -LiteralPath 'C:\Users\Public\Desktop\Microsoft Edge.lnk' -ErrorAction 'SilentlyContinue' -Verbose;
+ };
+ {
+ reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Dsh" /v AllowNewsAndInterests /t REG_DWORD /d 0 /f;
+ };
+ {
+ reg.exe add "HKLM\Software\Policies\Microsoft\Edge" /v HideFirstRunExperience /t REG_DWORD /d 1 /f;
+ };
+ {
+ reg.exe add "HKLM\Software\Policies\Microsoft\Edge\Recommended" /v BackgroundModeEnabled /t REG_DWORD /d 0 /f;
+ reg.exe add "HKLM\Software\Policies\Microsoft\Edge\Recommended" /v StartupBoostEnabled /t REG_DWORD /d 0 /f;
+ };
+ {
+ & 'C:\Windows\Setup\Scripts\SetStartPins.ps1';
+ };
+ {
+ reg.exe add "HKU\.DEFAULT\Control Panel\Accessibility\StickyKeys" /v Flags /t REG_SZ /d 10 /f;
+ };
+ {
+ reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v NoAutoUpdate /t REG_DWORD /d 1 /f;
+ reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v DisableWindowsUpdateAccess /t REG_DWORD /d 1 /f;
+ };
+);
+
+& {
+ [float]$complete = 0;
+ [float]$increment = 100 / $scripts.Count;
+ foreach( $script in $scripts ) {
+ Write-Progress -Id 0 -Activity 'Running scripts to customize your Windows installation. Do not close this window.' -PercentComplete $complete;
+ '*** Will now execute command «{0}».' -f $(
+ $str = $script.ToString().Trim() -replace '\s+', ' ';
+ $max = 100;
+ if( $str.Length -le $max ) {
+ $str;
+ } else {
+ $str.Substring( 0, $max - 1 ) + '…';
+ }
+ );
+ $start = [datetime]::Now;
+ & $script;
+ '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds;
+ "`r`n" * 3;
+ $complete += $increment;
+ }
+} *>&1 | Out-String -Width 1KB -Stream >> "C:\Windows\Setup\Scripts\Specialize.log";
+
+
+$scripts = @(
+ {
+ [System.Diagnostics.EventLog]::WriteEntry( 'UnattendGenerator', "User '$env:USERNAME' has requested to unlock the Start menu layout.", [System.Diagnostics.EventLogEntryType]::Information, 1 );
+ };
+ {
+ Remove-Item -Path "${env:USERPROFILE}\Desktop\*.lnk" -Force -ErrorAction 'SilentlyContinue';
+ Remove-Item -Path "$env:HOMEDRIVE\Users\Default\Desktop\*.lnk" -Force -ErrorAction 'SilentlyContinue';
+ };
+ {
+ $taskbarPath = "$env:AppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar";
+ if( Test-Path $taskbarPath ) {
+ Get-ChildItem -Path $taskbarPath -File | Remove-Item -Force;
+ }
+ Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband' -Name 'FavoritesRemovedChanges' -Force -ErrorAction 'SilentlyContinue';
+ Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband' -Name 'FavoritesChanges' -Force -ErrorAction 'SilentlyContinue';
+ Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband' -Name 'Favorites' -Force -ErrorAction 'SilentlyContinue';
+ };
+ {
+ reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /ve /f;
+ };
+ {
+ Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name 'LaunchTo' -Type 'DWord' -Value 1;
+ };
+ {
+ Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Search' -Name 'SearchboxTaskbarMode' -Type 'DWord' -Value 0;
+ };
+ {
+ & 'C:\Windows\Setup\Scripts\SetColorTheme.ps1';
+ };
+ {
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.Suggested" /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.Suggested" /v Enabled /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.StartupApp" /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.StartupApp" /v Enabled /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Microsoft.SkyDrive.Desktop" /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Microsoft.SkyDrive.Desktop" /v Enabled /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.AccountHealth" /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Notifications\Settings\Windows.SystemToast.AccountHealth" /v Enabled /t REG_DWORD /d 0 /f;
+ };
+ {
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Start" /v AllAppsViewMode /t REG_DWORD /d 2 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v Start_IrisRecommendations /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v Start_AccountNotifications /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Start" /v ShowAllPinsList /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Start" /v ShowFrequentList /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Start" /v ShowRecentList /t REG_DWORD /d 0 /f;
+ reg.exe add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v Start_TrackDocs /t REG_DWORD /d 0 /f;
+ };
+ {
+ Add-Type -TypeDefinition @"
+using System;
+using System.Runtime.InteropServices;
+public class Win32Broadcast {
+ [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
+ public static extern IntPtr SendMessageTimeout(
+ IntPtr hWnd,
+ uint Msg,
+ IntPtr wParam,
+ string lParam,
+ uint fuFlags,
+ uint uTimeout,
+ out IntPtr lpdwResult);
+}
+"@;
+ [Win32Broadcast]::SendMessageTimeout( [IntPtr]0xffff, 0x1A, [IntPtr]::Zero, 'ImmersiveColorSet', 0x2, 100, [ref]([IntPtr]::Zero) );
+ };
+ {
+ Get-Process -Name 'explorer' -ErrorAction 'SilentlyContinue' | Where-Object -FilterScript {
+ $_.SessionId -eq ( Get-Process -Id $PID ).SessionId;
+ } | Stop-Process -Force;
+ };
+);
+
+& {
+ [float]$complete = 0;
+ [float]$increment = 100 / $scripts.Count;
+ foreach( $script in $scripts ) {
+ Write-Progress -Id 0 -Activity 'Running scripts to configure this user account. Do not close this window.' -PercentComplete $complete;
+ '*** Will now execute command «{0}».' -f $(
+ $str = $script.ToString().Trim() -replace '\s+', ' ';
+ $max = 100;
+ if( $str.Length -le $max ) {
+ $str;
+ } else {
+ $str.Substring( 0, $max - 1 ) + '…';
+ }
+ );
+ $start = [datetime]::Now;
+ & $script;
+ '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds;
+ "`r`n" * 3;
+ $complete += $increment;
+ }
+} *>&1 | Out-String -Width 1KB -Stream >> "$env:TEMP\UserOnce.log";
+
+
+$scripts = @(
+ {
+ reg.exe add "HKU\DefaultUser\Software\Policies\Microsoft\Windows\Explorer" /v "StartLayoutFile" /t REG_SZ /d "C:\Windows\Setup\Scripts\TaskbarLayoutModification.xml" /f;
+ reg.exe add "HKU\DefaultUser\Software\Policies\Microsoft\Windows\Explorer" /v "LockedStartLayout" /t REG_DWORD /d 1 /f;
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v ShowTaskViewButton /t REG_DWORD /d 0 /f;
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v TaskbarAl /t REG_DWORD /d 0 /f;
+ };
+ {
+ foreach( $root in 'Registry::HKU\.DEFAULT', 'Registry::HKU\DefaultUser' ) {
+ Set-ItemProperty -LiteralPath "$root\Control Panel\Keyboard" -Name 'InitialKeyboardIndicators' -Type 'String' -Value 2 -Force;
+ }
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\TaskbarDeveloperSettings" /v TaskbarEndTask /t REG_DWORD /d 1 /f;
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Control Panel\Accessibility\StickyKeys" /v Flags /t REG_SZ /d 10 /f;
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\DWM" /v ColorPrevalence /t REG_DWORD /d 0 /f;
+ };
+ {
+ reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\RunOnce" /v "UnattendedSetup" /t REG_SZ /d "powershell.exe -WindowStyle \""Normal\"" -ExecutionPolicy \""Unrestricted\"" -NoProfile -File \""C:\Windows\Setup\Scripts\UserOnce.ps1\""" /f;
+ };
+);
+
+& {
+ [float]$complete = 0;
+ [float]$increment = 100 / $scripts.Count;
+ foreach( $script in $scripts ) {
+ Write-Progress -Id 0 -Activity 'Running scripts to modify the default user’’s registry hive. Do not close this window.' -PercentComplete $complete;
+ '*** Will now execute command «{0}».' -f $(
+ $str = $script.ToString().Trim() -replace '\s+', ' ';
+ $max = 100;
+ if( $str.Length -le $max ) {
+ $str;
+ } else {
+ $str.Substring( 0, $max - 1 ) + '…';
+ }
+ );
+ $start = [datetime]::Now;
+ & $script;
+ '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds;
+ "`r`n" * 3;
+ $complete += $increment;
+ }
+} *>&1 | Out-String -Width 1KB -Stream >> "C:\Windows\Setup\Scripts\DefaultUser.log";
+
+
+$scripts = @(
+ {
+ cmd.exe /c "rmdir C:\Windows.old";
+ };
+ {
+ Remove-Item -LiteralPath @(
+ 'C:\Windows\Panther\unattend.xml';
+ 'C:\Windows\Panther\unattend-original.xml';
+ 'C:\Windows\Setup\Scripts\Wifi.xml';
+ ) -Force -ErrorAction 'SilentlyContinue' -Verbose;
+ };
+ {
+ reg.exe delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v NoAutoUpdate /f;
+ reg.exe delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /v DisableWindowsUpdateAccess /f;
+ };
+ {
+ $recallFeature = Get-WindowsOptionalFeature -Online -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Enabled' -and $_.FeatureName -like 'Recall' };
+ if( $recallFeature ) {
+ Disable-WindowsOptionalFeature -Online -FeatureName 'Recall' -Remove -ErrorAction SilentlyContinue;
+ }
+ };
+ {
+ try {
+ $viveDir = Join-Path $env:TEMP 'ViVeTool';
+ $viveZip = Join-Path $env:TEMP 'ViVeTool.zip';
+ Invoke-WebRequest 'https://github.com/thebookisclosed/ViVe/releases/download/v0.3.4/ViVeTool-v0.3.4-IntelAmd.zip' -OutFile $viveZip;
+ Expand-Archive -Path $viveZip -DestinationPath $viveDir -Force;
+ Remove-Item -Path $viveZip -Force;
+ Start-Process -FilePath (Join-Path $viveDir 'ViVeTool.exe') -ArgumentList '/disable /id:47205210' -Wait -NoNewWindow;
+ Remove-Item -Path $viveDir -Recurse -Force;
+ } catch {}
+ };
+ {
+ if( (Get-BitLockerVolume -MountPoint $Env:SystemDrive).ProtectionStatus -eq 'On' ) {
+ Disable-BitLocker -MountPoint $Env:SystemDrive;
+ }
+ };
+ {
+ if( (bcdedit | Select-String 'path').Count -eq 2 ) {
+ bcdedit /set `{bootmgr`} timeout 0;
+ }
+ };
+);
+
+& {
+ [float]$complete = 0;
+ [float]$increment = 100 / $scripts.Count;
+ foreach( $script in $scripts ) {
+ Write-Progress -Id 0 -Activity 'Running scripts to finalize your Windows installation. Do not close this window.' -PercentComplete $complete;
+ '*** Will now execute command «{0}».' -f $(
+ $str = $script.ToString().Trim() -replace '\s+', ' ';
+ $max = 100;
+ if( $str.Length -le $max ) {
+ $str;
+ } else {
+ $str.Substring( 0, $max - 1 ) + '…';
+ }
+ );
+ $start = [datetime]::Now;
+ & $script;
+ '*** Finished executing command after {0:0} ms.' -f [datetime]::Now.Subtract( $start ).TotalMilliseconds;
+ "`r`n" * 3;
+ $complete += $increment;
+ }
+} *>&1 | Out-String -Width 1KB -Stream >> "C:\Windows\Setup\Scripts\FirstLogon.log";
+
+
+
diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml
index b9725c31b6..06b1845223 100644
--- a/xaml/inputXML.xaml
+++ b/xaml/inputXML.xaml
@@ -970,6 +970,14 @@
+
+
+
+ Win11 Creator
+
+
+
@@ -1331,6 +1339,322 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 1 - Select Windows 11 ISO
+
+
+ Browse to your locally saved Windows 11 ISO file. Only official ISOs
+ downloaded from Microsoft are supported.
+
+
+ NOTE: This is only meant for Fresh and New Windows installs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ !!WARNING!! You must use an official Microsoft ISO
+
+
+ Download the Windows 11 ISO directly from Microsoft.com.
+ Third-party, pre-modified, or unofficial images are not supported
+ and may produce broken results.
+
+
+ On the Microsoft download page, choose:
+
+
+ - Edition : Windows 11
+ - Language : your preferred language
+ - Architecture : 64-bit (x64)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 2 - Mount & Verify ISO
+
+
+ Mount the ISO and confirm it contains a valid Windows 11
+ install.wim before any modifications are made.
+
+
+
+
+
+
+
+
+
+
+ Select Edition:
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 3 - Modify install.wim
+
+
+ The ISO contents will be extracted to a temporary working directory,
+ install.wim will be modified (components removed, tweaks applied),
+ and the result will be repackaged. This process may take several minutes
+ depending on your hardware.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step 4 - Output: What would you like to do with the modified image?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ !! All data on the selected USB drive will be permanently erased !!
+
+ Select a removable USB drive below, then click Erase & Write.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status Log
+
+
+
+
+
+
+
+