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>&lt;QueryList&gt;&lt;Query Id="0" Path="Application"&gt;&lt;Select Path="Application"&gt;*[System[Provider[@Name='UnattendGenerator'] and EventID=1]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</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. + + + + + + + +