<# .SYNOPSIS Headless script to build a minimized Windows 11 Nano image for CI/CD automation. .DESCRIPTION Automated build of an extremely streamlined Windows 11 Nano image without user interaction. This is the most aggressive Windows 11 optimization, removing drivers, fonts, services, and more. NOT suitable for any regular use - designed for rapid testing in VMs only. .PARAMETER ISO Drive letter of the mounted Windows 11 ISO (required, e.g., E) .PARAMETER INDEX Windows image index to process (required, e.g., 1 for Home, 6 for Pro) .PARAMETER SCRATCH Drive letter for scratch disk operations (optional, defaults to script root) .PARAMETER SkipCleanup Skip cleanup of temporary files after ISO creation (optional, for debugging) .EXAMPLE .\nano11builder-headless.ps1 -ISO E -INDEX 1 .\nano11builder-headless.ps1 -ISO E -INDEX 6 -SCRATCH D -SkipCleanup .NOTES Original Author: ntdevlabs Modified by: kelexine (https://github.com/kelexine) GitHub: https://github.com/kelexine/tiny11-automated Date: 2025-12-13 License: MIT This is a headless automation-ready version designed for CI/CD pipelines. #> #---------[ Parameters ]---------# [CmdletBinding()] param ( [Parameter(Mandatory=$true, HelpMessage="Drive letter of mounted Windows 11 ISO (e.g., E)")] [ValidatePattern('^[c-zC-Z]$')] [string]$ISO, [Parameter(Mandatory=$true, HelpMessage="Windows image index (1=Home, 6=Pro, etc.)")] [ValidateRange(1, 10)] [int]$INDEX, [Parameter(Mandatory=$false, HelpMessage="Scratch disk drive letter (defaults to script directory)")] [ValidatePattern('^[c-zC-Z]$')] [string]$SCRATCH, [Parameter(Mandatory=$false, HelpMessage="Skip cleanup of temporary files")] [switch]$SkipCleanup ) #---------[ Error Handling ]---------# $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest #---------[ Configuration ]---------# if (-not $SCRATCH) { $ScratchDisk = $PSScriptRoot -replace '[\\]+$', '' } else { $ScratchDisk = $SCRATCH + ":" } $DriveLetter = $ISO + ":" $wimFilePath = "$ScratchDisk\nano11\sources\install.wim" $scratchDir = "$ScratchDisk\scratchdir" $nano11Dir = "$ScratchDisk\nano11" $outputISO = "$PSScriptRoot\nano11.iso" $logFile = "$PSScriptRoot\nano11_$(Get-Date -Format yyyyMMdd_HHmmss).log" # Initialize admin identifiers for permission operations try { $adminSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544") $adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount]) } catch { Write-Warning "Failed to resolve Administrator group SID. Defaulting to 'Administrators'." $adminGroup = [PSCustomObject]@{ Value = "Administrators" } } #---------[ Helper Functions ]---------# function Write-Log { param([string]$Message, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logMessage = "[$timestamp] [$Level] $Message" Write-Output $logMessage Add-Content -Path $logFile -Value $logMessage -ErrorAction SilentlyContinue } function Set-RegistryValue { param ( [string]$path, [string]$name, [string]$type, [string]$value ) try { if ($name) { & 'reg' 'add' $path '/v' $name '/t' $type '/d' $value '/f' | Out-Null } else { & 'reg' 'add' $path '/ve' '/t' $type '/d' $value '/f' | Out-Null } Write-Log "Set registry: $path\$name = $value" } catch { Write-Log "Error setting registry $path\$name : $_" "ERROR" throw } } function Remove-RegistryKey { param([string]$path) try { & 'reg' 'delete' $path '/f' 2>&1 | Out-Null Write-Log "Removed registry key: $path" } catch { Write-Log "Registry key not found or error: $path" "WARN" } } #---------[ Core Functions ]---------# function Test-Prerequisites { Write-Log "Checking prerequisites..." # Check admin rights $myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent() $myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($myWindowsID) $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator if (-not $myWindowsPrincipal.IsInRole($adminRole)) { Write-Log "Script must run as Administrator!" "ERROR" throw "Administrative privileges required" } # Check ISO mount if (-not (Test-Path "$DriveLetter\sources\boot.wim")) { Write-Log "boot.wim not found at $DriveLetter\sources\" "ERROR" throw "Invalid Windows 11 ISO mount point" } # Check for install.wim or install.esd if (-not (Test-Path "$DriveLetter\sources\install.wim") -and -not (Test-Path "$DriveLetter\sources\install.esd")) { Write-Log "No install.wim or install.esd found" "ERROR" throw "Windows installation files not found" } # Check disk space (minimum 30GB recommended for Nano build) $disk = Get-PSDrive -Name $ScratchDisk[0] -ErrorAction SilentlyContinue if ($disk) { $freeGB = [math]::Round($disk.Free / 1GB, 2) Write-Log "Available space on ${ScratchDisk}: ${freeGB}GB" if ($freeGB -lt 30) { Write-Log "Low disk space warning: ${freeGB}GB (30GB+ recommended for Nano build)" "WARN" } } Write-Log "Prerequisites check passed" } function Initialize-Directories { Write-Log "Initializing directories..." New-Item -ItemType Directory -Force -Path "$nano11Dir\sources" | Out-Null New-Item -ItemType Directory -Force -Path $scratchDir | Out-Null Write-Log "Directories created" } function Convert-ESDToWIM { Write-Log "Converting install.esd to install.wim..." $esdPath = "$DriveLetter\sources\install.esd" $tempWimPath = "$nano11Dir\sources\install.wim" # Validate index exists in ESD $images = Get-WindowsImage -ImagePath $esdPath $validIndices = $images.ImageIndex if ($INDEX -notin $validIndices) { Write-Log "Invalid index $INDEX. Available: $($validIndices -join ', ')" "ERROR" throw "Image index $INDEX not found in install.esd" } Write-Log "Exporting image index $INDEX from ESD (this may take 10-20 minutes)..." Export-WindowsImage -SourceImagePath $esdPath -SourceIndex $INDEX ` -DestinationImagePath $tempWimPath -CompressionType Maximum -CheckIntegrity Write-Log "ESD conversion complete" } function Copy-WindowsFiles { Write-Log "Copying Windows installation files from $DriveLetter..." Copy-Item -Path "$DriveLetter\*" -Destination $nano11Dir -Recurse -Force -ErrorAction SilentlyContinue # Remove install.esd if present if (Test-Path "$nano11Dir\sources\install.esd") { Remove-Item "$nano11Dir\sources\install.esd" -Force -ErrorAction SilentlyContinue } Write-Log "File copy complete" } function Resolve-ImageIndex { Write-Log "Resolving and validating image index $INDEX..." $sourceImagePath = "" if (Test-Path "$DriveLetter\sources\install.wim") { $sourceImagePath = "$DriveLetter\sources\install.wim" } elseif (Test-Path "$DriveLetter\sources\install.esd") { $sourceImagePath = "$DriveLetter\sources\install.esd" } else { throw "Windows installation files not found on ISO" } $images = Get-WindowsImage -ImagePath $sourceImagePath # Standard Microsoft index mapping for Consumer ISOs $expectedNames = @{ 1 = "Windows 11 Home" 4 = "Windows 11 Education" 6 = "Windows 11 Pro" 7 = "Windows 11 Pro N" } $targetName = $expectedNames[$INDEX] if ($targetName) { $foundImage = $images | Where-Object { $_.ImageName -eq $targetName } if ($foundImage) { $actualIndex = $foundImage.ImageIndex if ($actualIndex -ne $INDEX) { Write-Log "Index shifted! Expected '$targetName' at $INDEX, but found at $actualIndex." "WARN" Write-Log "Automatically adjusting INDEX to $actualIndex." $script:INDEX = $actualIndex } else { Write-Log "Edition '$targetName' matched expected index $INDEX." } } else { Write-Log "Expected edition '$targetName' not found in ISO. Proceeding with literal index $INDEX." "WARN" } } else { Write-Log "No standard mapping for index $INDEX. Proceeding with literal index." } $validIndices = $images.ImageIndex if ($script:INDEX -notin $validIndices) { Write-Log "Invalid index $script:INDEX. Available indices:" "ERROR" $images | ForEach-Object { Write-Log " Index $($_.ImageIndex): $($_.ImageName)" } throw "Image index $script:INDEX not found" } $selectedImage = $images | Where-Object { $_.ImageIndex -eq $script:INDEX } Write-Log "Selected: Index $script:INDEX - $($selectedImage.ImageName)" } function Mount-WindowsImageFile { Write-Log "Mounting Windows image (Index: $INDEX)..." # Take ownership and set permissions & takeown /F $wimFilePath /A | Out-Null & icacls $wimFilePath /grant "$($adminGroup.Value):(F)" | Out-Null Set-ItemProperty -Path $wimFilePath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue & dism /English "/mount-image" "/imagefile:$wimFilePath" "/index:$INDEX" "/mountdir:$scratchDir" Write-Log "Image mounted at $scratchDir" } function Take-OwnershipOfFolders { Write-Log "Taking ownership of critical folders..." $foldersToOwn = @( "$scratchDir\Windows\System32\DriverStore\FileRepository", "$scratchDir\Windows\Fonts", "$scratchDir\Windows\Web", "$scratchDir\Windows\Help", "$scratchDir\Windows\Cursors", "$scratchDir\Program Files (x86)\Microsoft", "$scratchDir\Program Files\WindowsApps", "$scratchDir\Windows\System32\Microsoft-Edge-Webview", "$scratchDir\Windows\System32\Recovery", "$scratchDir\Windows\WinSxS", "$scratchDir\Windows\assembly", "$scratchDir\ProgramData\Microsoft\Windows Defender", "$scratchDir\Windows\System32\InputMethod", "$scratchDir\Windows\Speech", "$scratchDir\Windows\Temp" ) $filesToOwn = @( "$scratchDir\Windows\System32\OneDriveSetup.exe" ) foreach ($folder in $foldersToOwn) { if (Test-Path $folder) { Write-Log "Taking ownership: $folder" & takeown.exe /F $folder /R /D Y 2>&1 | Out-Null & icacls.exe $folder /grant "$($adminGroup.Value):(F)" /T /C 2>&1 | Out-Null } } foreach ($file in $filesToOwn) { if (Test-Path $file) { Write-Log "Taking ownership: $file" # Remove /D Y as it requires /R and is not needed for single files & takeown.exe /F $file 2>&1 | Out-Null & icacls.exe $file /grant "$($adminGroup.Value):(F)" /C 2>&1 | Out-Null } } Write-Log "Ownership taken" } function Get-ImageMetadata { Write-Log "Extracting image metadata..." # Get language $imageIntl = & dism /English /Get-Intl "/Image:$scratchDir" $languageLine = $imageIntl -split '\n' | Where-Object { $_ -match 'Default system UI language : ([a-zA-Z]{2}-[a-zA-Z]{2})' } if ($languageLine) { $script:languageCode = $Matches[1] Write-Log "Language: $script:languageCode" } else { Write-Log "Language code not found, using default" "WARN" $script:languageCode = "en-US" } # Get architecture $imageInfo = & dism /English /Get-WimInfo "/wimFile:$wimFilePath" "/index:$INDEX" $lines = $imageInfo -split '\r?\n' foreach ($line in $lines) { if ($line -like '*Architecture : *') { $script:architecture = $line -replace 'Architecture : ', '' if ($script:architecture -eq 'x64') { $script:architecture = 'amd64' } Write-Log "Architecture: $script:architecture" break } } if (-not $script:architecture) { Write-Log "Architecture not found, defaulting to amd64" "WARN" $script:architecture = 'amd64' } } #---------[ Nano11-Specific Removal Functions ]---------# function Remove-BloatwareApps { Write-Log "Removing provisioned appx packages (extended nano11 list)..." $packagesToRemove = Get-AppxProvisionedPackage -Path $scratchDir | Where-Object { $_.PackageName -like '*Zune*' -or $_.PackageName -like '*Bing*' -or $_.PackageName -like '*Clipchamp*' -or $_.PackageName -like '*Gaming*' -or $_.PackageName -like '*People*' -or $_.PackageName -like '*PowerAutomate*' -or $_.PackageName -like '*Teams*' -or $_.PackageName -like '*Todos*' -or $_.PackageName -like '*YourPhone*' -or $_.PackageName -like '*SoundRecorder*' -or $_.PackageName -like '*Solitaire*' -or $_.PackageName -like '*FeedbackHub*' -or $_.PackageName -like '*Maps*' -or $_.PackageName -like '*OfficeHub*' -or $_.PackageName -like '*Help*' -or $_.PackageName -like '*Family*' -or $_.PackageName -like '*Alarms*' -or $_.PackageName -like '*CommunicationsApps*' -or $_.PackageName -like '*Copilot*' -or $_.PackageName -like '*CompatibilityEnhancements*' -or $_.PackageName -like '*AV1VideoExtension*' -or $_.PackageName -like '*AVCEncoderVideoExtension*' -or $_.PackageName -like '*HEIFImageExtension*' -or $_.PackageName -like '*HEVCVideoExtension*' -or $_.PackageName -like '*MicrosoftStickyNotes*' -or $_.PackageName -like '*OutlookForWindows*' -or $_.PackageName -like '*RawImageExtension*' -or $_.PackageName -like '*SecHealthUI*' -or $_.PackageName -like '*VP9VideoExtensions*' -or $_.PackageName -like '*WebpImageExtension*' -or $_.PackageName -like '*DevHome*' -or $_.PackageName -like '*Photos*' -or $_.PackageName -like '*ScreenSketch*' -or $_.PackageName -like '*Camera*' -or $_.PackageName -like '*QuickAssist*' -or $_.PackageName -like '*CoreAI*' -or $_.PackageName -like '*PeopleExperienceHost*' -or $_.PackageName -like '*PinningConfirmationDialog*' -or $_.PackageName -like '*SecureAssessmentBrowser*' -or $_.PackageName -like '*Paint*' -or $_.PackageName -like '*Notepad*' -or $_.PackageName -like '*Recall*' -or $_.PackageName -like '*WebExperience*' -or $_.PackageName -like '*StorePurchaseApp*' -or $_.PackageName -like '*MPEG2VideoExtension*' -or $_.PackageName -like '*WebMediaExtensions*' -or $_.PackageName -like '*WindowsAI*' -or $_.PackageName -like '*AIFabric*' } $removeCount = 0 foreach ($package in $packagesToRemove) { Write-Log "Removing: $($package.DisplayName)" try { Remove-AppxProvisionedPackage -Path $scratchDir -PackageName $package.PackageName -ErrorAction Stop | Out-Null $removeCount++ } catch { Write-Log "Could not remove $($package.DisplayName): $($_.Exception.Message)" "WARN" } } # Clean up leftover WindowsApps folders Write-Log "Cleaning leftover WindowsApps folders..." foreach ($package in $packagesToRemove) { $folderPath = Join-Path "$scratchDir\Program Files\WindowsApps" $package.PackageName if (Test-Path $folderPath) { Remove-Item $folderPath -Recurse -Force -ErrorAction SilentlyContinue } } Write-Log "Removed $removeCount appx packages" } function Remove-SystemPackages { Write-Log "Removing system packages (extended nano11 list)..." $packagePatterns = @( # Legacy Components & Optional Apps "Microsoft-Windows-InternetExplorer-Optional-Package~", "Microsoft-Windows-MediaPlayer-Package~", "Microsoft-Windows-WordPad-FoD-Package~", "Microsoft-Windows-StepsRecorder-Package~", "Microsoft-Windows-MSPaint-FoD-Package~", "Microsoft-Windows-SnippingTool-FoD-Package~", "Microsoft-Windows-TabletPCMath-Package~", "Microsoft-Windows-Xps-Xps-Viewer-Opt-Package~", "Microsoft-Windows-PowerShell-ISE-FOD-Package~", "OpenSSH-Client-Package~", # Language & Input Features "Microsoft-Windows-LanguageFeatures-Handwriting-$($script:languageCode)-Package~", "Microsoft-Windows-LanguageFeatures-OCR-$($script:languageCode)-Package~", "Microsoft-Windows-LanguageFeatures-Speech-$($script:languageCode)-Package~", "Microsoft-Windows-LanguageFeatures-TextToSpeech-$($script:languageCode)-Package~", "*IME-ja-jp*", "*IME-ko-kr*", "*IME-zh-cn*", "*IME-zh-tw*", # Core OS Features "Windows-Defender-Client-Package~", "Microsoft-Windows-Search-Engine-Client-Package~", "Microsoft-Windows-Kernel-LA57-FoD-Package~", # Security & Identity "Microsoft-Windows-Hello-Face-Package~", "Microsoft-Windows-Hello-BioEnrollment-Package~", "Microsoft-Windows-BitLocker-DriveEncryption-FVE-Package~", "Microsoft-Windows-TPM-WMI-Provider-Package~", # Accessibility Tools "Microsoft-Windows-Narrator-App-Package~", "Microsoft-Windows-Magnifier-App-Package~", # Miscellaneous Features "Microsoft-Windows-Printing-PMCPPC-FoD-Package~", "Microsoft-Windows-WebcamExperience-Package~", "Microsoft-Media-MPEG2-Decoder-Package~", "Microsoft-Windows-Wallpaper-Content-Extended-FoD-Package~", "UserExperience-Recall-Package~", "Microsoft-Windows-AppManagement-AppV-Package~", "Microsoft-Edge-WebView-FOD-Package~" ) $allPackages = & dism /image:$scratchDir /Get-Packages /Format:Table $allPackages = $allPackages -split "`n" | Select-Object -Skip 1 $removeCount = 0 foreach ($packagePattern in $packagePatterns) { $packagesToRemove = $allPackages | Where-Object { $_ -like "$packagePattern*" } foreach ($package in $packagesToRemove) { $packageIdentity = ($package -split "\s+")[0] if ($packageIdentity) { Write-Log "Removing package: $packageIdentity" & dism /image:$scratchDir /Remove-Package /PackageName:$packageIdentity /Quiet /NoRestart 2>&1 | Out-Null $removeCount++ } } } Write-Log "Removed $removeCount system packages" } function Remove-NativeImages { Write-Log "Removing pre-compiled .NET assemblies (Native Images)..." $nativeImagesPath = "$scratchDir\Windows\assembly\NativeImages_*" Remove-Item -Path $nativeImagesPath -Recurse -Force -ErrorAction SilentlyContinue Write-Log ".NET Native Images removed" } function Slim-DriverStore { Write-Log "Slimming the DriverStore (removing non-essential driver classes)..." $driverRepo = "$scratchDir\Windows\System32\DriverStore\FileRepository" $patternsToRemove = @( 'prn*', # Printer drivers 'scan*', # Scanner drivers 'mfd*', # Multi-function device drivers 'wscsmd.inf*', # Smartcard readers 'tapdrv*', # Tape drives 'rdpbus.inf*', # Remote Desktop virtual bus 'tdibth.inf*' # Bluetooth Personal Area Network ) $removeCount = 0 Get-ChildItem -Path $driverRepo -Directory -ErrorAction SilentlyContinue | ForEach-Object { $driverFolder = $_.Name foreach ($pattern in $patternsToRemove) { if ($driverFolder -like $pattern) { Write-Log "Removing driver: $driverFolder" Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue $removeCount++ break } } } Write-Log "Removed $removeCount driver packages" } function Reduce-Fonts { Write-Log "Reducing fonts (keeping only essentials)..." $fontsPath = "$scratchDir\Windows\Fonts" if (Test-Path $fontsPath) { # Keep essential fonts, remove the rest Get-ChildItem -Path $fontsPath -Exclude "segoe*.*", "tahoma*.*", "marlett.ttf", "8541oem.fon", "segui*.*", "consol*.*", "lucon*.*", "calibri*.*", "arial*.*", "times*.*", "cou*.*", "8*.*" -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue # Remove CJK fonts explicitly Get-ChildItem -Path $fontsPath -Include "mingli*", "msjh*", "msyh*", "malgun*", "meiryo*", "yugoth*", "segoeuihistoric.ttf" -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue } Write-Log "Fonts reduced" } function Clean-InputMethods { Write-Log "Cleaning input methods (removing CJK)..." $inputMethodPaths = @( "$scratchDir\Windows\System32\InputMethod\CHS", "$scratchDir\Windows\System32\InputMethod\CHT", "$scratchDir\Windows\System32\InputMethod\JPN", "$scratchDir\Windows\System32\InputMethod\KOR" ) foreach ($path in $inputMethodPaths) { if (Test-Path $path) { Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue } } Write-Log "Input methods cleaned" } function Remove-MiscellaneousFiles { Write-Log "Performing aggressive file deletions..." # Speech (Full removal for Nano) Remove-Item -Path "$scratchDir\Windows\Speech" -Recurse -Force -ErrorAction SilentlyContinue # Windows Error Reporting (WER) Remove-Item -Path "$scratchDir\ProgramData\Microsoft\Windows\WER" -Recurse -Force -ErrorAction SilentlyContinue # Defender definitions Remove-Item -Path "$scratchDir\ProgramData\Microsoft\Windows Defender\Definition Updates" -Recurse -Force -ErrorAction SilentlyContinue # Temp files Remove-Item -Path "$scratchDir\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue # Web, Help, Cursors Remove-Item -Path "$scratchDir\Windows\Web" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\Help" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\Cursors" -Recurse -Force -ErrorAction SilentlyContinue # Windows Update binaries (NON-SERVICEABLE BUILD) Write-Log "Removing Windows Update binaries (this is a non-serviceable build)..." Remove-Item -Path "$scratchDir\Windows\System32\usoclient.exe" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\System32\UsoApiAll.dll" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\System32\UsoApi.dll" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\System32\UpdatePolicy.dll" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\System32\drivers\umbus.sys" -Force -ErrorAction SilentlyContinue Remove-Item -Path "$scratchDir\Windows\SoftwareDistribution" -Recurse -Force -ErrorAction SilentlyContinue Write-Log "Miscellaneous files removed" } function Remove-EdgeAndOneDrive { Write-Log "Removing Microsoft Edge and OneDrive..." # Remove Edge paths Remove-Item -Path "$scratchDir\Program Files (x86)\Microsoft\Edge*" -Recurse -Force -ErrorAction SilentlyContinue # Remove Edge WebView from WinSxS (covers amd64 and arm64) $winSxSPaths = Get-ChildItem -Path "$scratchDir\Windows\WinSxS" -Filter "*microsoft-edge-webview_31bf3856ad364e35*" -Directory -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName foreach ($winSxSPath in $winSxSPaths) { if (Test-Path $winSxSPath) { Remove-Item -Path $winSxSPath -Recurse -Force -ErrorAction SilentlyContinue } } Remove-Item -Path "$scratchDir\Windows\System32\Microsoft-Edge-Webview" -Recurse -Force -ErrorAction SilentlyContinue # Remove OneDrive Write-Log "Removing OneDrive..." $oneDrivePaths = @( "$scratchDir\Windows\System32\OneDriveSetup.exe", "$scratchDir\Windows\SysWOW64\OneDriveSetup.exe" ) foreach ($path in $oneDrivePaths) { if (Test-Path $path) { Write-Log "Deleting OneDrive setup: $path" & takeown.exe /f $path /a | Out-Null & icacls.exe $path /grant "$($adminGroup.Value):(F)" /T /C | Out-Null Remove-Item -Path $path -Force -ErrorAction SilentlyContinue } } Write-Log "Edge and OneDrive removed" # Clean up other remnants Write-Log "Cleaning up other remnants (GameBar, Copilot)..." $otherRemnants = @( "$scratchDir\Windows\GameBarPresenceWriter", "$scratchDir\Windows\System32\SettingsHandlers_Copilot.dll" ) foreach ($path in $otherRemnants) { if (Test-Path $path) { Write-Log "Deleting remnant: $path" & takeown.exe /f $path /a | Out-Null & icacls.exe $path /grant "$($adminGroup.Value):(F)" /T /C | Out-Null Remove-Item -Path $path -Recurse -Force -ErrorAction SilentlyContinue } } } function Remove-WinRE { Write-Log "Removing Windows Recovery Environment..." $winRE = "$scratchDir\Windows\System32\Recovery\winre.wim" if (Test-Path $winRE) { Remove-Item -Path $winRE -Recurse -Force -ErrorAction SilentlyContinue New-Item -Path $winRE -ItemType File -Force | Out-Null } Write-Log "WinRE removed" } function Optimize-WinSxS { Write-Log "Optimizing WinSxS folder..." $sourceDirectory = "$scratchDir\Windows\WinSxS" $destinationDirectory = "$scratchDir\Windows\WinSxS_edit" New-Item -Path $destinationDirectory -ItemType Directory -Force | Out-Null $dirsToCopy = @() if ($script:architecture -eq "amd64") { $dirsToCopy = @( "x86_microsoft.windows.common-controls_6595b64144ccf1df_*", "x86_microsoft.windows.gdiplus_6595b64144ccf1df_*", "x86_microsoft.windows.i..utomation.proxystub_6595b64144ccf1df_*", "x86_microsoft.windows.isolationautomation_6595b64144ccf1df_*", "x86_microsoft-windows-s..ngstack-onecorebase_31bf3856ad364e35_*", "x86_microsoft-windows-s..stack-termsrv-extra_31bf3856ad364e35_*", "x86_microsoft-windows-servicingstack_31bf3856ad364e35_*", "x86_microsoft-windows-servicingstack-inetsrv_*", "x86_microsoft-windows-servicingstack-onecore_*", "amd64_microsoft.vc80.crt_1fc8b3b9a1e18e3b_*", "amd64_microsoft.vc90.crt_1fc8b3b9a1e18e3b_*", "amd64_microsoft.windows.c..-controls.resources_6595b64144ccf1df_*", "amd64_microsoft.windows.common-controls_6595b64144ccf1df_*", "amd64_microsoft.windows.gdiplus_6595b64144ccf1df_*", "amd64_microsoft.windows.i..utomation.proxystub_6595b64144ccf1df_*", "amd64_microsoft.windows.isolationautomation_6595b64144ccf1df_*", "amd64_microsoft-windows-s..stack-inetsrv-extra_31bf3856ad364e35_*", "amd64_microsoft-windows-s..stack-msg.resources_31bf3856ad364e35_*", "amd64_microsoft-windows-s..stack-termsrv-extra_31bf3856ad364e35_*", "amd64_microsoft-windows-servicingstack_31bf3856ad364e35_*", "amd64_microsoft-windows-servicingstack-inetsrv_31bf3856ad364e35_*", "amd64_microsoft-windows-servicingstack-msg_31bf3856ad364e35_*", "amd64_microsoft-windows-servicingstack-onecore_31bf3856ad364e35_*", "Catalogs", "FileMaps", "Fusion", "InstallTemp", "Manifests", "x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_*", "x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_*", "x86_microsoft.windows.c..-controls.resources_6595b64144ccf1df_*" ) } elseif ($script:architecture -eq "arm64") { $dirsToCopy = @( "arm64_microsoft-windows-servicingstack-onecore_31bf3856ad364e35_*", "Catalogs", "FileMaps", "Fusion", "InstallTemp", "Manifests", "SettingsManifests", "Temp", "x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_*", "x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_*", "x86_microsoft.windows.c..-controls.resources_6595b64144ccf1df_*", "x86_microsoft.windows.common-controls_6595b64144ccf1df_*", "x86_microsoft.windows.gdiplus_6595b64144ccf1df_*", "arm_microsoft.windows.common-controls_6595b64144ccf1df_*", "arm64_microsoft.windows.common-controls_6595b64144ccf1df_*", "arm64_microsoft-windows-servicingstack_31bf3856ad364e35_*" ) } foreach ($dir in $dirsToCopy) { $sourceDirs = Get-ChildItem -Path $sourceDirectory -Filter $dir -Directory -ErrorAction SilentlyContinue foreach ($sourceDir in $sourceDirs) { $destDir = Join-Path -Path $destinationDirectory -ChildPath $sourceDir.Name Write-Log "Copying: $($sourceDir.Name)" Copy-Item -Path $sourceDir.FullName -Destination $destDir -Recurse -Force } } # Safety Check: Ensure we actually copied something before wiping original WinSxS $matchedCount = (Get-ChildItem -Path $destinationDirectory).Count if ($matchedCount -lt 5) { Write-Log "WinSxS optimization failed: Whitelist matched too few items ($matchedCount)." "ERROR" throw "WinSxS optimization verification failed - Aborting to prevent broken image" } Write-Log "Replacing WinSxS with minimal version..." # Re-assert ownership to ensure deletion is possible Write-Log "Ensuring ownership of WinSxS before deletion..." & takeown.exe /F $sourceDirectory /R /D Y 2>&1 | Out-Null & icacls.exe $sourceDirectory /grant "$($adminGroup.Value):(F)" /T /C 2>&1 | Out-Null $emptyDir = "$ScratchDisk\empty_temp" New-Item -Path $emptyDir -ItemType Directory -Force | Out-Null & robocopy $emptyDir $sourceDirectory /MIR /R:0 /W:0 /NFL /NDL /NJH /NJS | Out-Null Remove-Item -Path $emptyDir -Force Remove-Item -Path $sourceDirectory -Recurse -Force Rename-Item -Path $destinationDirectory -NewName "WinSxS" Write-Log "WinSxS optimization complete" } #---------[ Registry Functions ]---------# function Load-RegistryHives { Write-Log "Loading registry hives..." reg load HKLM\zCOMPONENTS "$scratchDir\Windows\System32\config\COMPONENTS" 2>&1 | Out-Null reg load HKLM\zDEFAULT "$scratchDir\Windows\System32\config\default" 2>&1 | Out-Null reg load HKLM\zNTUSER "$scratchDir\Users\Default\ntuser.dat" 2>&1 | Out-Null reg load HKLM\zSOFTWARE "$scratchDir\Windows\System32\config\SOFTWARE" 2>&1 | Out-Null reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" 2>&1 | Out-Null Write-Log "Registry hives loaded" } function Unload-RegistryHives { Write-Log "Unloading registry hives..." [GC]::Collect() [GC]::WaitForPendingFinalizers() Start-Sleep -Seconds 3 reg unload HKLM\zCOMPONENTS 2>&1 | Out-Null reg unload HKLM\zDEFAULT 2>&1 | Out-Null reg unload HKLM\zNTUSER 2>&1 | Out-Null reg unload HKLM\zSOFTWARE 2>&1 | Out-Null reg unload HKLM\zSYSTEM 2>&1 | Out-Null Write-Log "Registry hives unloaded" } function Apply-RegistryTweaks { Write-Log "Applying registry tweaks..." # Bypass system requirements Set-RegistryValue 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassCPUCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassRAMCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassSecureBootCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassStorageCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassTPMCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 'REG_DWORD' '1' # Disable sponsored apps Set-RegistryValue 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'OemPreInstalledAppsEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SilentInstalledAppsEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableWindowsConsumerFeatures' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'ContentDeliveryAllowed' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\PolicyManager\current\device\Start' 'ConfigureStartPins' 'REG_SZ' '{"pinnedList": [{}]}' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'FeatureManagementEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEverEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SoftLandingEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContentEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-310093Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338388Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338389Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338393Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353694Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353696Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SystemPaneSuggestionsEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\PushToInstall' 'DisablePushToInstall' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\MRT' 'DontOfferThroughWUAU' 'REG_DWORD' '1' Remove-RegistryKey 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\Subscriptions' Remove-RegistryKey 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\SuggestedApps' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableConsumerAccountStateContent' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableCloudOptimizedContent' 'REG_DWORD' '1' # Enable local accounts on OOBE Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1' # stkn: Disable autounattend.xml, we'll bring out own instead # Copy autounattend-nano.xml as autounattend.xml # $nanoAutoUnattend = Join-Path (Split-Path $PSScriptRoot -Parent) "autounattend-nano.xml" # if (Test-Path $nanoAutoUnattend) { # Copy-Item -Path $nanoAutoUnattend -Destination "$scratchDir\Windows\System32\Sysprep\autounattend.xml" -Force # Write-Log "Copied autounattend-nano.xml to Sysprep" # } # Disable reserved storage Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0' # Disable BitLocker Set-RegistryValue 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' 'PreventDeviceEncryption' 'REG_DWORD' '1' # Disable Chat icon Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Chat' 'ChatIcon' 'REG_DWORD' '3' Set-RegistryValue 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'TaskbarMn' 'REG_DWORD' '0' # Remove Edge registries Remove-RegistryKey 'HKLM\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge' Remove-RegistryKey 'HKLM\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update' # Disable OneDrive folder backup Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\OneDrive' 'DisableFileSyncNGSC' 'REG_DWORD' '1' # Remove OneDrive from Run keys (prevent auto-install on first login) Remove-RegistryKey "HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Run\OneDriveSetup" Remove-RegistryKey "HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\Run\OneDriveSetup" # Disable telemetry Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo' 'Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\Privacy' 'TailoredExperiencesWithDiagnosticDataEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' 'HasAccepted' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Input\TIPC' 'Enabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitInkCollection' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitTextCollection' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization\TrainedDataStore' 'HarvestContacts' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Software\Microsoft\Personalization\Settings' 'AcceptedPrivacyPolicy' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' 'AllowTelemetry' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSYSTEM\ControlSet001\Services\dmwappushservice' 'Start' 'REG_DWORD' '4' # Prevent DevHome and Outlook installation Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate' 'workCompleted' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\DevHomeUpdate' 'workCompleted' 'REG_DWORD' '1' Remove-RegistryKey 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' Remove-RegistryKey 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate' # Disable Copilot Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1' # Disable AI features (Recall, AI Fabric, Windows AI) Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsAI' 'DisableAIDataAnalysis' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsAI' 'TurnOffWindowsAI' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zNTUSER\Software\Policies\Microsoft\Windows\WindowsAI' 'DisableAIDataAnalysis' 'REG_DWORD' '1' # Enhanced telemetry removal Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' 'DoNotShowFeedbackNotifications' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' 'AllowDeviceNameInTelemetry' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack' 'ShowedToastAtLevel' 'REG_DWORD' '1' # Gaming optimization: Increase VRAM allocation Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\DirectDraw' 'EmulationOnly' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Direct3D' 'DisableVidMemVBs' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSYSTEM\ControlSet001\Control\GraphicsDrivers' 'DpiMapIommuContiguous' 'REG_DWORD' '1' # Prevent Teams installation Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Teams' 'DisableInstallation' 'REG_DWORD' '1' # Prevent new Outlook installation Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Mail' 'PreventRun' 'REG_DWORD' '1' # Disable Windows Update Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' 'StopWUPostOOBE1' 'REG_SZ' 'net stop wuauserv' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' 'StopWUPostOOBE2' 'REG_SZ' 'sc stop wuauserv' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' 'StopWUPostOOBE3' 'REG_SZ' 'sc config wuauserv start= disabled' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' 'DisbaleWUPostOOBE1' 'REG_SZ' 'reg add HKLM\SYSTEM\CurrentControlSet\Services\wuauserv /v Start /t REG_DWORD /d 4 /f' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' 'DisbaleWUPostOOBE2' 'REG_SZ' 'reg add HKLM\SYSTEM\ControlSet001\Services\wuauserv /v Start /t REG_DWORD /d 4 /f' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'DoNotConnectToWindowsUpdateInternetLocations' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'DisableWindowsUpdateAccess' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUServer' 'REG_SZ' 'localhost' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUStatusServer' 'REG_SZ' 'localhost' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'UpdateServiceUrlAlternate' 'REG_SZ' 'localhost' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'UseWUServer' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'NoAutoUpdate' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'DisableOnline' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\ControlSet001\Services\wuauserv' 'Start' 'REG_DWORD' '4' # Delete WaaS services Remove-RegistryKey 'HKLM\zSYSTEM\ControlSet001\Services\WaaSMedicSVC' Remove-RegistryKey 'HKLM\zSYSTEM\ControlSet001\Services\UsoSvc' # Disable Windows Defender Write-Log "Disabling Windows Defender services..." $servicePaths = @("WinDefend", "WdNisSvc", "WdNisDrv", "WdFilter", "Sense") foreach ($path in $servicePaths) { Set-RegistryValue "HKLM\zSYSTEM\ControlSet001\Services\$path" "Start" "REG_DWORD" "4" } # Hide settings pages Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' 'SettingsPageVisibility' 'REG_SZ' 'hide:virus;windowsupdate' # Easter Egg / Branding Write-Log "Adding Easter Egg branding..." Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' 'legalnoticecaption' 'REG_SZ' 'Tiny11 Automated' Set-RegistryValue 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' 'legalnoticetext' 'REG_SZ' 'This image was built using Tiny11 Automated by kelexine. Enjoy your lightweight Windows experience!' # Desktop Context Menu Link Set-RegistryValue 'HKLM\zSOFTWARE\Classes\DesktopBackground\Shell\Tiny11Info' 'MUIVerb' 'REG_SZ' 'Tiny11 Automated Info' Set-RegistryValue 'HKLM\zSOFTWARE\Classes\DesktopBackground\Shell\Tiny11Info' 'Icon' 'REG_SZ' 'shell32.dll,22' Set-RegistryValue 'HKLM\zSOFTWARE\Classes\DesktopBackground\Shell\Tiny11Info' 'Position' 'REG_SZ' 'Bottom' Set-RegistryValue 'HKLM\zSOFTWARE\Classes\DesktopBackground\Shell\Tiny11Info\command' '' 'REG_SZ' 'explorer.exe "https://github.com/kelexine/tiny11-automated"' Write-Log "Registry tweaks applied" } function Remove-ScheduledTasks { Write-Log "Removing telemetry scheduled tasks..." $tasksPath = "$scratchDir\Windows\System32\Tasks" $tasksToRemove = @( "$tasksPath\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser", "$tasksPath\Microsoft\Windows\Customer Experience Improvement Program", "$tasksPath\Microsoft\Windows\Application Experience\ProgramDataUpdater", "$tasksPath\Microsoft\Windows\Chkdsk\Proxy", "$tasksPath\Microsoft\Windows\Windows Error Reporting\QueueReporting" ) foreach ($task in $tasksToRemove) { if (Test-Path $task) { Remove-Item -Path $task -Recurse -Force -ErrorAction SilentlyContinue } } Write-Log "Scheduled tasks removed" } function Remove-Services { Write-Log "Removing non-essential services (nano11-specific)..." # Load SYSTEM hive separately for service removal reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" 2>&1 | Out-Null $servicesToRemove = @( 'Spooler', 'PrintNotify', 'Fax', 'RemoteRegistry', 'diagsvc', 'WerSvc', 'PcaSvc', 'MapsBroker', 'WalletService', 'BthAvctpSvc', 'BluetoothUserService', 'wuauserv', 'UsoSvc', 'WaaSMedicSvc' ) foreach ($service in $servicesToRemove) { Write-Log "Removing service: $service" try { & 'reg' 'delete' "HKLM\zSYSTEM\ControlSet001\Services\$service" /f 2>&1 | Out-Null } catch { Write-Log "Could not remove service $service : Registry key not found or error" "WARN" } } reg unload HKLM\zSYSTEM 2>&1 | Out-Null Write-Log "Services removed" } #---------[ Finalization Functions ]---------# function Optimize-WindowsImage { Write-Log "Cleaning up Windows image (this may take 10-15 minutes)..." & dism.exe /Image:$scratchDir /Cleanup-Image /StartComponentCleanup /ResetBase 2>&1 | Out-Null Write-Log "Image cleanup complete" } function Dismount-AndExport { Write-Log "Dismounting install.wim..." & dism /English /unmount-image "/mountdir:$scratchDir" /commit Write-Log "Exporting image with maximum compression..." $tempWim = "$nano11Dir\sources\install2.wim" & Dism.exe /English /Export-Image /SourceImageFile:$wimFilePath /SourceIndex:$INDEX /DestinationImageFile:$tempWim /Compress:max Remove-Item -Path $wimFilePath -Force Rename-Item -Path $tempWim -NewName "install.wim" Write-Log "Install.wim export complete" } function Process-BootImage { Write-Log "Processing boot.wim (nano11 shrinking)..." $bootWimPath = "$nano11Dir\sources\boot.wim" # Take ownership & takeown /F $bootWimPath /A 2>&1 | Out-Null & icacls $bootWimPath /grant "$($adminGroup.Value):(F)" 2>&1 | Out-Null Set-ItemProperty -Path $bootWimPath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue # Export only index 2 (setup image) Write-Log "Exporting boot.wim index 2..." $newBootWimPath = "$nano11Dir\sources\boot_new.wim" & dism /English /Export-Image /SourceImageFile:$bootWimPath /SourceIndex:2 /DestinationImageFile:$newBootWimPath # Mount the new boot image Write-Log "Mounting boot image for modifications..." & dism /English /mount-image "/imagefile:$newBootWimPath" /index:1 "/mountdir:$scratchDir" # Load registry and apply bypasses reg load HKLM\zDEFAULT "$scratchDir\Windows\System32\config\default" 2>&1 | Out-Null reg load HKLM\zNTUSER "$scratchDir\Users\Default\ntuser.dat" 2>&1 | Out-Null reg load HKLM\zSOFTWARE "$scratchDir\Windows\System32\config\SOFTWARE" 2>&1 | Out-Null reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" 2>&1 | Out-Null Write-Log "Applying system requirement bypasses to boot image..." Set-RegistryValue 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassCPUCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassRAMCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassSecureBootCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassStorageCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassTPMCheck' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 'REG_DWORD' '1' Set-RegistryValue 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' 'PreventDeviceEncryption' 'REG_DWORD' '1' # stkn: Switch to old-style setup application, which works for nano11 (same as tiny11core) Set-RegistryValue 'HKLM\zSYSTEM\Setup' 'CmdLine' 'REG_SZ' 'x:\sources\setup.exe' # Unload registry reg unload HKLM\zNTUSER 2>&1 | Out-Null reg unload HKLM\zDEFAULT 2>&1 | Out-Null reg unload HKLM\zSOFTWARE 2>&1 | Out-Null reg unload HKLM\zSYSTEM 2>&1 | Out-Null Start-Sleep -Seconds 5 # Dismount boot image Write-Log "Dismounting boot image..." & dism /English /unmount-image "/mountdir:$scratchDir" /commit # Replace original boot.wim with shrunk version Remove-Item -Path $bootWimPath -Force $finalBootWimPath = "$nano11Dir\sources\boot_final.wim" & dism /English /Export-Image /SourceImageFile:$newBootWimPath /SourceIndex:1 /DestinationImageFile:$finalBootWimPath /Compress:max Remove-Item -Path $newBootWimPath -Force Rename-Item -Path $finalBootWimPath -NewName "boot.wim" Write-Log "Boot image processing complete" } function Convert-ToESD { Write-Log "Converting to ESD format for maximum compression..." $esdPath = "$nano11Dir\sources\install.esd" & dism /Export-Image /SourceImageFile:$wimFilePath /SourceIndex:1 /DestinationImageFile:$esdPath /Compress:recovery Remove-Item $wimFilePath -Force -ErrorAction SilentlyContinue Write-Log "ESD conversion complete" } function Clean-IsoRoot { Write-Log "Cleaning ISO root (keeping only essentials)..." $keepList = @("boot", "efi", "sources", "bootmgr", "bootmgr.efi", "setup.exe") Get-ChildItem -Path $nano11Dir | Where-Object { $_.Name -notin $keepList } | ForEach-Object { Write-Log "Removing from ISO root: $($_.Name)" Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue } Write-Log "ISO root cleaned" } function Create-NanoISO { Write-Log "Creating ISO image..." # stkn: Disable autounattend.xml, we'll bring out own instead # Copy autounattend-nano.xml as autounattend.xml to ISO root # $nanoAutoUnattend = Join-Path (Split-Path $PSScriptRoot -Parent) "autounattend-nano.xml" # if (Test-Path $nanoAutoUnattend) { # Copy-Item -Path $nanoAutoUnattend -Destination "$nano11Dir\autounattend.xml" -Force # Write-Log "Copied autounattend-nano.xml to ISO root as autounattend.xml" # } # stkn: Switch to noprompt efi version # Verify boot files $bootFiles = @( "$nano11Dir\boot\etfsboot.com", "$nano11Dir\efi\microsoft\boot\efisys_noprompt.bin" ) foreach ($bootFile in $bootFiles) { if (-not (Test-Path $bootFile)) { throw "Required boot file not found: $bootFile" } } # Determine oscdimg.exe location $hostArchitecture = $Env:PROCESSOR_ARCHITECTURE $ADKDepTools = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\$hostArchitecture\Oscdimg" $localOSCDIMGPath = "$PSScriptRoot\oscdimg.exe" if (Test-Path "$ADKDepTools\oscdimg.exe") { Write-Log "Using oscdimg.exe from Windows ADK" $OSCDIMG = "$ADKDepTools\oscdimg.exe" } else { Write-Log "ADK not found, downloading oscdimg.exe..." $url = "https://msdl.microsoft.com/download/symbols/oscdimg.exe/3D44737265000/oscdimg.exe" if (-not (Test-Path $localOSCDIMGPath)) { Invoke-WebRequest -Uri $url -OutFile $localOSCDIMGPath -UseBasicParsing } $OSCDIMG = $localOSCDIMGPath } Write-Log "Building bootable ISO..." & $OSCDIMG '-m' '-o' '-u2' '-udfver102' ` "-bootdata:2#p0,e,b$nano11Dir\boot\etfsboot.com#pEF,e,b$nano11Dir\efi\microsoft\boot\efisys_noprompt.bin" ` $nano11Dir $outputISO if (Test-Path $outputISO) { $isoSize = [math]::Round((Get-Item $outputISO).Length / 1GB, 2) Write-Log "ISO created successfully: $outputISO (${isoSize}GB)" } else { throw "ISO creation failed" } } function Invoke-Cleanup { if ($SkipCleanup) { Write-Log "Skipping cleanup (SkipCleanup flag set)" "WARN" return } Write-Log "Performing cleanup..." # Ensure image is unmounted & dism /English /unmount-image "/mountdir:$scratchDir" /discard 2>&1 | Out-Null Remove-Item -Path $nano11Dir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path $scratchDir -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$PSScriptRoot\oscdimg.exe" -Force -ErrorAction SilentlyContinue Write-Log "Cleanup complete" } #---------[ Main Execution ]---------# try { Write-Log "=== Nano11 Headless Builder Started ===" "INFO" Write-Log "Author: kelexine (https://github.com/kelexine)" Write-Log "Parameters: ISO=$ISO, INDEX=$INDEX, SCRATCH=$ScratchDisk" Write-Log "WARNING: This creates the most minimal Windows 11 image - FOR TESTING ONLY!" Test-Prerequisites Initialize-Directories Resolve-ImageIndex # Handle install.esd conversion if needed if (Test-Path "$DriveLetter\sources\install.esd") { Write-Log "Found install.esd, conversion required" Convert-ESDToWIM Copy-WindowsFiles Write-Log "Resetting INDEX to 1 since ESD was exported to a new WIM" $script:INDEX = 1 } else { Write-Log "Found install.wim, no conversion needed" Copy-WindowsFiles } Mount-WindowsImageFile Take-OwnershipOfFolders Get-ImageMetadata # Customization phase Remove-BloatwareApps Remove-SystemPackages Remove-NativeImages Slim-DriverStore Reduce-Fonts Clean-InputMethods Remove-MiscellaneousFiles Remove-EdgeAndOneDrive Remove-WinRE # Registry phase Load-RegistryHives Apply-RegistryTweaks Remove-ScheduledTasks Unload-RegistryHives # Service removal (separate registry operation) Remove-Services # WinSxS optimization Optimize-WinSxS # Finalization phase Dismount-AndExport Process-BootImage Convert-ToESD Clean-IsoRoot Create-NanoISO # Cleanup Invoke-Cleanup Write-Log "=== Nano11 Build Completed Successfully ===" "INFO" Write-Log "Output: $outputISO" Write-Log "WARNING: This is AN EXTREMELY MINIMAL build - NOT for daily use!" exit 0 } catch { Write-Log "FATAL ERROR: $_" "ERROR" Write-Log "Stack trace: $($_.ScriptStackTrace)" "ERROR" # Emergency cleanup try { Get-WindowsImage -Mounted | ForEach-Object { Write-Log "Emergency dismount: $($_.Path)" "WARN" Dismount-WindowsImage -Path $_.Path -Discard -ErrorAction SilentlyContinue } @("zCOMPONENTS", "zDEFAULT", "zNTUSER", "zSOFTWARE", "zSYSTEM") | ForEach-Object { reg unload "HKLM\$_" 2>$null } } catch { Write-Log "Emergency cleanup failed: $_" "ERROR" } exit 1 }