function Invoke-WinUtilISOScript { <# .SYNOPSIS Applies WinUtil modifications to a mounted Windows 11 install.wim image. .DESCRIPTION Removes AppX bloatware and OneDrive, injects hardware drivers (NVMe, Precision Touchpad/HID, and network) exported from the running system, optionally injects extended Storage & Network drivers from the ChrisTitusTech/storage-lan-drivers repository (requires git, installed via winget if absent), applies offline registry tweaks (hardware bypass, privacy, OOBE, telemetry, update suppression), deletes CEIP/WU scheduled-task definition files, and optionally writes autounattend.xml to the ISO root and removes the support\ folder from the ISO contents directory. All setup scripts embedded in the autounattend.xml nodes (Specialize.ps1, DefaultUser.ps1, FirstLogon.ps1, UserOnce.ps1, etc.) are written directly into the WIM at their target paths under C:\Windows\Setup\Scripts\. This pre-staging is necessary because Windows Setup strips unrecognised-namespace XML elements — including the Schneegans block — when copying the answer file to %WINDIR%\Panther\unattend.xml. Without pre-staging the [scriptblock] that tries to extract scripts from the Panther copy receives $null, no scripts reach disk, and both the specialize-pass actions and FirstLogonCommands silently fail. Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO). .PARAMETER ScratchDir 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.03.02 #> param ( [Parameter(Mandatory)][string]$ScratchDir, [string]$ISOContentsDir = "", [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: $_" } } # Copies driver package folders (one per exported .inf) whose online class # matches any of the supplied class names into a new temp staging directory. # Returns the staging directory path — caller must delete it when done. # Using a staging dir lets DISM inject all drivers in a single /Recurse # call instead of one DISM process launch per .inf file. function New-DriverStagingDir { param ([string]$ExportRoot, [string[]]$Classes) $stagingDir = Join-Path $env:TEMP "WinUtil_DriverStage_$(Get-Random)" New-Item -Path $stagingDir -ItemType Directory -Force | Out-Null Get-WindowsDriver -Online | Where-Object { $_.ClassName -in $Classes } | ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($_.OriginalFileName) } | Select-Object -Unique | ForEach-Object { Get-ChildItem -Path $ExportRoot -Filter "$_.inf" -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DirectoryName -Unique | ForEach-Object { # Each exported driver lives in its own sub-folder; # copy that folder (with its binary files) into staging. $dest = Join-Path $stagingDir (Split-Path $_ -Leaf) Copy-Item -Path $_ -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue } } return $stagingDir } # Injects all drivers from $DriverDir into a DISM-mounted image in one call. function Add-DriversToImage { param ( [string]$MountPath, [string]$DriverDir, [string]$Label = "image", [scriptblock]$Logger ) & dism /English "/image:$MountPath" /Add-Driver "/Driver:$DriverDir" /Recurse 2>&1 | ForEach-Object { & $Logger " dism[$Label]: $_" } } # Mounts boot.wim index 2, injects all drivers from $DriverDir, saves, dismounts. function Invoke-BootWimInject { param ( [string]$BootWimPath, [string]$DriverDir, [scriptblock]$Logger ) Set-ItemProperty -Path $BootWimPath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue $mountDir = Join-Path $env:TEMP "WinUtil_BootMount_$(Get-Random)" New-Item -Path $mountDir -ItemType Directory -Force | Out-Null try { & $Logger "Mounting boot.wim (index 2 — Windows Setup) for driver injection..." Mount-WindowsImage -ImagePath $BootWimPath -Index 2 -Path $mountDir -ErrorAction Stop | Out-Null Add-DriversToImage -MountPath $mountDir -DriverDir $DriverDir -Label "boot" -Logger $Logger & $Logger "Saving boot.wim..." Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null & $Logger "boot.wim driver injection complete." } catch { & $Logger "Warning: boot.wim driver injection failed: $_" try { Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null } catch {} } finally { Remove-Item -Path $mountDir -Recurse -Force -ErrorAction SilentlyContinue } } # ═════════════════════════════════════════════════════════════════════════ # 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.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. Inject hardware drivers (NVMe / Trackpad / Network) # Injected into BOTH install.wim (OS) AND boot.wim index 2 (Setup). # Without storage drivers in boot.wim, Windows Setup cannot see the # target disk on systems with unsupported NVMe / SATA controllers. # ═════════════════════════════════════════════════════════════════════════ & $Log "Exporting hardware drivers from running system (NVMe, HID/Trackpad, Network)..." $driverExportRoot = Join-Path $env:TEMP "WinUtil_DriverExport_$(Get-Random)" New-Item -Path $driverExportRoot -ItemType Directory -Force | Out-Null try { Export-WindowsDriver -Online -Destination $driverExportRoot | Out-Null # Stage matching driver folders then do a single DISM /Recurse call. # install.wim: SCSIAdapter + HIDClass + Net $installStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','HIDClass','Net') & $Log "Injecting staged drivers into install.wim (single DISM call)..." Add-DriversToImage -MountPath $ScratchDir -DriverDir $installStage -Label "install" -Logger $Log Remove-Item -Path $installStage -Recurse -Force -ErrorAction SilentlyContinue & $Log "install.wim driver injection complete." # boot.wim: SCSIAdapter + Net only (HID not needed in WinPE) if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) { $bootWim = Join-Path $ISOContentsDir "sources\boot.wim" if (Test-Path $bootWim) { $bootStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','Net') & $Log "Injecting staged drivers into boot.wim (single DISM call)..." Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $bootStage -Logger $Log Remove-Item -Path $bootStage -Recurse -Force -ErrorAction SilentlyContinue } else { & $Log "Warning: boot.wim not found — skipping boot.wim driver injection." } } } catch { & $Log "Error during driver export/injection: $_" } finally { Remove-Item -Path $driverExportRoot -Recurse -Force -ErrorAction SilentlyContinue } # ── 2c. Optional: extended Storage & Network drivers from community repo ── $extDriverChoice = [System.Windows.MessageBox]::Show( "Would you like to add extended Storage and Network drivers?`n`n" + "This installs EVERY Storage and Networking device driver " + "in EXISTANCE into the image. (~1000 drivers)`n`n" + "No Wireless drivers only Ethernet, use for stubborn systems " + "with unsupported NVMe or Ethernet controllers.", "Extended Drivers", "YesNo", "Question") if ($extDriverChoice -eq 'Yes') { & $Log "Extended driver injection requested." # Ensure git is available $gitCmd = Get-Command git -ErrorAction SilentlyContinue if (-not $gitCmd) { & $Log "Git not found — installing via winget..." winget install --id Git.Git -e --source winget ` --accept-package-agreements --accept-source-agreements | Out-Null # Refresh PATH so git is visible in this session $env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('PATH', 'User') $gitCmd = Get-Command git -ErrorAction SilentlyContinue } if (-not $gitCmd) { & $Log "Warning: git could not be found after install attempt — skipping extended drivers." } else { $extRepoDir = Join-Path $env:TEMP "WinUtil_ExtDrivers_$(Get-Random)" try { & $Log "Cloning storage-lan-drivers repository..." & git clone --depth 1 ` "https://github.com/ChrisTitusTech/storage-lan-drivers" ` $extRepoDir 2>&1 | ForEach-Object { & $Log " git: $_" } if (Test-Path $extRepoDir) { & $Log "Injecting extended drivers into install.wim (this may take several minutes)..." Add-DriversToImage -MountPath $ScratchDir -DriverDir $extRepoDir -Label "install" -Logger $Log & $Log "Extended driver injection into install.wim complete." if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) { $bootWimExt = Join-Path $ISOContentsDir "sources\boot.wim" if (Test-Path $bootWimExt) { & $Log "Injecting extended drivers into boot.wim..." Invoke-BootWimInject -BootWimPath $bootWimExt -DriverDir $extRepoDir -Logger $Log } else { & $Log "Warning: boot.wim not found — skipping extended driver injection into boot.wim." } } } else { & $Log "Warning: repository clone directory not found — skipping extended drivers." } } catch { & $Log "Error during extended driver injection: $_" } finally { Remove-Item -Path $extRepoDir -Recurse -Force -ErrorAction SilentlyContinue } } } else { & $Log "Extended driver injection skipped." } # ═════════════════════════════════════════════════════════════════════════ # 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) { # ── Pre-stage embedded setup scripts directly into the WIM ──────────── # The autounattend.xml (Schneegans generator format) embeds all setup # scripts as nodes. The specialize-pass command that # is supposed to extract them reads C:\Windows\Panther\unattend.xml, but # Windows Setup strips unrecognised-namespace elements (including the # entire block) when it copies the answer file to that path. # As a result [scriptblock]::Create($null) throws, no scripts are written # to C:\Windows\Setup\Scripts\, Specialize.ps1 and DefaultUser.ps1 never # run, and FirstLogon.ps1 is absent so FirstLogonCommands silently fails. # # Writing the scripts directly into the WIM guarantees they are present # on the target drive after Windows Setup applies the image, regardless # of whether the Panther extraction step succeeds. try { $xmlDoc = [xml]::new() $xmlDoc.LoadXml($AutoUnattendXml) $nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) $nsMgr.AddNamespace("sg", "https://schneegans.de/windows/unattend-generator/") $fileNodes = $xmlDoc.SelectNodes("//sg:File", $nsMgr) if ($fileNodes -and $fileNodes.Count -gt 0) { foreach ($fileNode in $fileNodes) { # Paths in the XML are absolute Windows paths (e.g. C:\Windows\Setup\Scripts\…). # Strip the drive-letter prefix so we can root them under $ScratchDir. $absPath = $fileNode.GetAttribute("path") $relPath = $absPath -replace '^[A-Za-z]:[/\\]', '' $destPath = Join-Path $ScratchDir $relPath $destDir = Split-Path $destPath -Parent New-Item -Path $destDir -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null # Match the encoding logic used by the original ExtractScript. $ext = [IO.Path]::GetExtension($destPath).ToLower() $encoding = switch ($ext) { { $_ -in '.ps1', '.xml' } { [System.Text.Encoding]::UTF8 } { $_ -in '.reg', '.vbs', '.js' } { [System.Text.UnicodeEncoding]::new($false, $true) } default { [System.Text.Encoding]::Default } } $bytes = $encoding.GetPreamble() + $encoding.GetBytes($fileNode.InnerText.Trim()) [System.IO.File]::WriteAllBytes($destPath, $bytes) & $Log "Pre-staged setup script: $relPath" } } else { & $Log "Warning: no nodes found in autounattend.xml — setup scripts not pre-staged." } } catch { & $Log "Warning: could not pre-stage setup scripts from autounattend.xml: $_" } # ── Place autounattend.xml at the ISO / USB root ────────────────────── # Windows Setup reads this file first (before booting into the OS), # 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 "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\AU' 'AUOptions' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'UseWUServer' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'DisableWindowsUpdateAccess' 'REG_DWORD' '1' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUServer' 'REG_SZ' 'http://localhost:8080' Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUStatusServer' 'REG_SZ' 'http://localhost:8080' Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\WindowsUpdate' 'workCompleted' 'REG_DWORD' '1' Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\WindowsUpdate' Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config' 'DODownloadMode' 'REG_DWORD' '0' Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\BITS' 'Start' 'REG_DWORD' '4' Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\wuauserv' 'Start' 'REG_DWORD' '4' Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\UsoSvc' 'Start' 'REG_DWORD' '4' Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\WaaSMedicSvc' 'Start' 'REG_DWORD' '4' & $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 Remove-Item "$tasksPath\Microsoft\Windows\InstallService" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\UpdateOrchestrator" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\UpdateAssistant" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\WaaSMedic" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\Windows\WindowsUpdate" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item "$tasksPath\Microsoft\WindowsUpdate" -Recurse -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." } }