diff --git a/bootstrap/nano11builder-headless.ps1 b/bootstrap/nano11builder-headless.ps1 new file mode 100644 index 0000000..8974c56 --- /dev/null +++ b/bootstrap/nano11builder-headless.ps1 @@ -0,0 +1,1263 @@ +<# +.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' + + # 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-RegistryValue "HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Run\OneDriveSetup" + Remove-RegistryValue "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' + + # 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", "autounattend.xml") + 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..." + + # 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" + } + + # Verify boot files + $bootFiles = @( + "$nano11Dir\boot\etfsboot.com", + "$nano11Dir\efi\microsoft\boot\efisys.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.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 +} diff --git a/bootstrap/nano11builder.ps1 b/bootstrap/nano11builder.ps1 deleted file mode 100644 index 2be7674..0000000 --- a/bootstrap/nano11builder.ps1 +++ /dev/null @@ -1,582 +0,0 @@ -# --- Language-independent nano11 Builder Script --- -# Version 3.2 - Final, complete script with universal language compatibility and English comments - -#---------[ Parameters ]---------# -param ( - [ValidatePattern('^[c-zC-Z]$')][string]$ISO, - [ValidatePattern('^[c-zC-Z]$')][string]$SCRATCH -) - -# 1. Check and adjust Execution Policy (accepts 'yes') -if ((Get-ExecutionPolicy) -eq 'Restricted') { - Write-Host "Your current PowerShell Execution Policy is 'Restricted', which prevents scripts from running." - Write-Host "Do you want to change it to 'RemoteSigned'? (yes/no)" - $response = Read-Host - if ($response.ToLower() -eq 'yes') { - Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Confirm:$false - Write-Host "Execution Policy has been changed." - } else { - Write-Host "The script cannot be run without changing the execution policy. Exiting..." - exit - } -} - -# 2. Check for Admin rights and restart the script as admin if required -# NOTE: The method for checking the admin role is language-independent. -$myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent() -$myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($myWindowsID) -if (-not $myWindowsPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Host "Restarting the script with administrator rights in a new window..." - $newProcess = New-Object System.Diagnostics.ProcessStartInfo "PowerShell" - $newProcess.Arguments = "-File `"$($myInvocation.MyCommand.Definition)`"" - $newProcess.Verb = "runas" - [System.Diagnostics.Process]::Start($newProcess) - exit -} - -# 3. Get the Administrators group in a language-independent way via its well-known SID -# This works on any Windows system, regardless of the configured language. -$adminGroupSid = New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid, $null) -$adminGroup = $adminGroupSid.Translate([System.Security.Principal.NTAccount]) - -# --- Function to take ownership (language-independent) --- -# This function replaces all calls to takeown.exe and icacls.exe -# STKN: Does not seem to work, going back to takeown and icacls for now -function Set-ItemOwnershipAndAccess { - param( - [string]$Path, - [switch]$Recurse - ) - if (-not (Test-Path $Path)) { - Write-Warning "Path not found: $Path" - return - } - Write-Host "Taking ownership and setting permissions for: $Path" - try { - # $acl = Get-Acl $Path - # $acl.SetOwner($adminGroup) - if ($Recurse) { - & 'takeown' '/f' "$Path" '/r' | Out-Null - & 'icacls' "$Path" '/grant' "$($adminGroup.Value):(F)" '/T' '/C' | Out-Null - # Rule for folders: Full control, inherited by all subfolders and files. - # $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminGroup, [System.Security.AccessControl.FileSystemRights]::FullControl, "ContainerInherit, ObjectInherit", "None", "Allow") - } else { - & 'takeown' '/f' "$Path" | Out-Null - & 'icacls' "$Path" '/grant' "$($adminGroup.Value):(F)" | Out-Null - # Rule for single files (no inheritance) - # $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminGroup, [System.Security.AccessControl.FileSystemRights]::FullControl, "Allow") - } - # $acl.AddAccessRule($rule) - # Set-Acl -Path $Path -AclObject $acl - Write-Host " - Success." - } catch { - Write-Error "Error processing '$Path': $_" - } -} - -function Set-RegistryValue { - param ( - [string]$path, - [string]$name, - [string]$type, - [string]$value - ) - try { - & 'reg' 'add' $path '/v' $name '/t' $type '/d' $value '/f' | Out-Null - Write-Output "Set registry value: $path\$name" - } catch { - Write-Output "Error setting registry value: $_" - } -} - -function Remove-RegistryValue { - param ( - [string]$path - ) - try { - & 'reg' 'delete' $path '/f' | Out-Null - Write-Output "Removed registry value: $path" - } catch { - Write-Output "Error removing registry value: $_" - } -} - -Start-Transcript -Path "$PSScriptRoot\nano11-$(Get-Date -f yyyyMMdd_HHmmss).log" -# User prompt (accepts y/n) -Write-Host "Welcome to the nano11 Builder!" -Write-Host "This script generates a heavily reduced Windows 11 image. It's not intended for regular use as it cannot be serviced (no updates, languages, etc.)." -Write-Host "Do you want to continue? (y/n)" -$input = Read-Host - -if ($input.ToLower() -ne 'y') { - Write-Host "You cancelled the process. The script will now exit." - exit -} - -Write-Host "Off we go..." -Start-Sleep -Seconds 3 -Clear-Host - -$mainOSDrive = $env:SystemDrive -if (-not $SCRATCH) { - $scratchDrive = $mainOSDrive -} else { - $scratchDrive = $SCRATCH + ":" -} - -New-Item -ItemType Directory -Force -Path "$scratchDrive\nano11\sources" - -do { - if (-not $ISO) { - $DriveLetter = Read-Host "Please enter the drive letter for the Windows 11 image" - } else { - $DriveLetter = $ISO - } - if ($DriveLetter -match '^[c-zC-Z]$') { - $DriveLetter = $DriveLetter + ":" - Write-Output "Drive letter set to $DriveLetter" - } else { - Write-Output "Invalid drive letter. Please enter a letter between C and Z." - } -} while ($DriveLetter -notmatch '^[c-zC-Z]:$') - -if (-not (Test-Path "$DriveLetter\sources\install.wim")) { - if (Test-Path "$DriveLetter\sources\install.esd") { - Write-Host "install.esd found, converting to install.wim..." - & 'dism' /English /Get-WimInfo /WimFile:"$DriveLetter\sources\install.esd" - $index = Read-Host "Please enter the image index" - Write-Host 'Converting install.esd to install.wim. This may take a while...' - & 'dism' /English /Export-Image /SourceImageFile:"$DriveLetter\sources\install.esd" /SourceIndex:$index /DestinationImageFile:"$scratchDrive\nano11\sources\install.wim" /Compress:max /CheckIntegrity - } else { - Write-Host "No Windows installation files found on the specified drive. Exiting." - exit - } -} else { - Write-Host "install.wim found." -} - -Write-Host "Copying Windows image..." -Copy-Item -Path "$DriveLetter\*" -Destination "$scratchDrive\nano11" -Recurse -Force | Out-Null -Remove-Item "$scratchDrive\nano11\sources\install.esd" -ErrorAction SilentlyContinue - -Write-Host "Getting image information:" -$wimFilePath = "$scratchDrive\nano11\sources\install.wim" -$ImagesIndex = (Get-WindowsImage -ImagePath $wimFilePath).ImageIndex -while ($ImagesIndex -notcontains $index) { - Get-WindowsImage -ImagePath $wimFilePath - $index = Read-Host "Please enter the image index" -} - -Write-Host "Mounting Windows image. This may take a while." -Set-ItemOwnershipAndAccess -Path $wimFilePath -try { Set-ItemProperty -Path $wimFilePath -Name IsReadOnly -Value $false -ErrorAction Stop } catch {} - -$scratchDir = "$scratchDrive\scratchdir" -New-Item -ItemType Directory -Force -Path "$scratchDir" -Mount-WindowsImage -ImagePath "$wimFilePath" -Index $index -Path "$scratchDir" - -# --- Taking ownership with the language-independent function --- -$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) { - Set-ItemOwnershipAndAccess -Path "$folder" -Recurse -} -foreach ($file in $filesToOwn) { - Set-ItemOwnershipAndAccess -Path "$file" -} - -$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) { - $languageCode = $Matches[1]; - Write-Host "Default system UI language code: $languageCode" -} else { - Write-Host "Default system UI language code not found." -} - -$imageInfo = & 'dism' /English /Get-WimInfo "/wimFile:$wimFilePath" "/index:$index" -$lines = $imageInfo -split '\r?\n' -foreach ($line in $lines) { - if ($line -like '*Architecture : *') { - $architecture = $line -replace 'Architecture : ','' - if ($architecture -eq 'x64') { $architecture = 'amd64' } - Write-Host "Architecture: $architecture" - break - } -} -if (-not $architecture) { - Write-Host "Architecture information not found." -} - -Write-Host "Removing provisioned AppX packages (bloatware)..." -$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 '*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*' } -foreach ($package in $packagesToRemove) { - write-host "Removing: $($package.DisplayName)" - Remove-AppxProvisionedPackage -Path $scratchDir -PackageName $package.PackageName -} - -Write-Host "Attempting to remove leftover WindowsApps folders..." -foreach ($package in $packagesToRemove) { - $folderPath = Join-Path "$scratchDir\Program Files\WindowsApps" $package.PackageName - if (Test-Path $folderPath) { - Write-Host "Deleting folder: $($package.PackageName)" - Remove-Item $folderPath -Recurse -Force -ErrorAction SilentlyContinue - } -} - -Write-Host "Removing of system apps complete! Now proceeding to removal of system packages..." -Start-Sleep -Seconds 1 -Clear-Host - -$packagePatterns = @( - "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~", - "Microsoft-Windows-LanguageFeatures-Handwriting-$languageCode-Package~", - "Microsoft-Windows-LanguageFeatures-OCR-$languageCode-Package~", - "Microsoft-Windows-LanguageFeatures-Speech-$languageCode-Package~", - "Microsoft-Windows-LanguageFeatures-TextToSpeech-$languageCode-Package~", - "*IME-ja-jp*", - "*IME-ko-kr*", - "*IME-zh-cn*", - "*IME-zh-tw*", - "Windows-Defender-Client-Package~", - "Microsoft-Windows-Search-Engine-Client-Package~", - "Microsoft-Windows-Kernel-LA57-FoD-Package~", - "Microsoft-Windows-Hello-Face-Package~", - "Microsoft-Windows-Hello-BioEnrollment-Package~", - "Microsoft-Windows-BitLocker-DriveEncryption-FVE-Package~", - "Microsoft-Windows-TPM-WMI-Provider-Package~", - "Microsoft-Windows-Narrator-App-Package~", - "Microsoft-Windows-Magnifier-App-Package~", - "Microsoft-Windows-Printing-PMCPPC-FoD-Package~", - "Microsoft-Windows-WebcamExperience-Package~", - "Microsoft-Media-MPEG2-Decoder-Package~", - "Microsoft-Windows-Wallpaper-Content-Extended-FoD-Package~" -) -$allPackages = & 'dism' /English /image:$scratchDir /Get-Packages /Format:Table -$allPackages = $allPackages -split "`n" | Select-Object -Skip 1 -foreach ($packagePattern in $packagePatterns) { - $packagesToRemove = $allPackages | Where-Object { $_ -like "$packagePattern*" } - foreach ($package in $packagesToRemove) { - $packageIdentity = ($package -split "\s+")[0] - Write-Host "Removing $packageIdentity..." - & 'dism' /English /image:$scratchDir /Remove-Package /PackageName:$packageIdentity - } -} - -Write-Host "Removing pre-compiled .NET assemblies (Native Images)..." -Remove-Item -Path "$scratchDir\Windows\assembly\NativeImages_*" -Recurse -Force -ErrorAction SilentlyContinue - -Write-Host "Performing aggressive manual file deletions..." -$winDir = "$scratchDir\Windows" - -Write-Host "Slimming the DriverStore... (removing non-essential driver classes)" -$driverRepo = Join-Path -Path "$winDir" -ChildPath "System32\DriverStore\FileRepository" -$patternsToRemove = @('prn*', 'scan*', 'mfd*', 'wscsmd.inf*', 'tapdrv*', 'rdpbus.inf*', 'tdibth.inf*') -# FIX: Use a robust deletion method for protected driver folders. -$emptyDirForDrivers = Join-Path -Path "$scratchDir" -ChildPath "empty_drivers_delete" -New-Item -Path "$emptyDirForDrivers" -ItemType Directory -Force | Out-Null -Get-ChildItem -Path "$driverRepo" -Directory | ForEach-Object { - $driverFolder = $_ - foreach ($pattern in $patternsToRemove) { - if ($driverFolder.Name -like $pattern) { - Write-Host "Force-removing non-essential driver package: $($driverFolder.Name)" - Set-ItemOwnershipAndAccess -Path $driverFolder.FullName -Recurse - & 'robocopy' "$emptyDirForDrivers" $driverFolder.FullName /MIR /R:0 /W:0 | Out-Null - Remove-Item -Path $driverFolder.FullName -Recurse -Force - break # Move to the next folder once a match is found - } - } -} -Remove-Item -Path "$emptyDirForDrivers" -Recurse -Force - -$fontsPath = Join-Path -Path "$winDir" -ChildPath "Fonts" -if (Test-Path "$fontsPath") { - Write-Host "Slimming the Fonts folder..." - # FIX: Take ownership of each font file individually before attempting to delete it. - $fontsToRemoveExclude = Get-ChildItem -Path "$fontsPath" -Exclude "segoe*.*", "tahoma*.*", "marlett.ttf", "8541oem.fon", "segui*.*", "consol*.*", "lucon*.*", "calibri*.*", "arial*.*", "times*.*", "cou*.*", "8*.*" - $fontsToRemoveInclude = Get-ChildItem -Path "$fontsPath" -Include "mingli*", "msjh*", "msyh*", "malgun*", "meiryo*", "yugoth*", "segoeuihistoric.ttf" - ($fontsToRemoveExclude + $fontsToRemoveInclude) | ForEach-Object { - Write-Host " - Removing font: $($_.Name)" - Set-ItemOwnershipAndAccess -Path $_.FullName - Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue - } -} - -Remove-Item -Path (Join-Path -Path "$winDir" -ChildPath "Speech\Engines\TTS") -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\ProgramData\Microsoft\Windows Defender\Definition Updates" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\System32\InputMethod\CHS" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\System32\InputMethod\CHT" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\System32\InputMethod\JPN" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\System32\InputMethod\KOR" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path (Join-Path -Path "$winDir" -ChildPath "Web") -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path (Join-Path -Path "$winDir" -ChildPath "Help") -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path (Join-Path -Path "$winDir" -ChildPath "Cursors") -Recurse -Force -ErrorAction SilentlyContinue - -Write-Host "Removing Edge, WinRE, and OneDrive..." -Remove-Item -Path "$scratchDir\Program Files (x86)\Microsoft\Edge*" -Recurse -Force -ErrorAction SilentlyContinue - -# FIX: Robustly delete Edge WebView components from WinSxS to prevent "Access Denied" errors. -if ($architecture -eq 'amd64') { - $folderPaths = Get-ChildItem -Path "$scratchDir\Windows\WinSxS" -Filter "amd64_microsoft-edge-webview_31bf3856ad364e35*" -Directory - if ($folderPaths) { - $emptyDirForEdge = Join-Path -Path "$scratchDir" -ChildPath "empty_edge_delete" - New-Item -Path "$emptyDirForEdge" -ItemType Directory -Force | Out-Null - foreach ($folder in $folderPaths) { - Write-Host "Force-deleting Edge WebView folder: $($folder.FullName)" - Set-ItemOwnershipAndAccess -Path $folder.FullName -Recurse - & 'robocopy' "$emptyDirForEdge" $folder.FullName /MIR /R:0 /W:0 | Out-Null - Remove-Item -Path $folder.FullName -Recurse -Force - } - Remove-Item -Path "$emptyDirForEdge" -Recurse -Force - } -} - -Remove-Item -Path "$scratchDir\Windows\System32\Microsoft-Edge-Webview" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDir\Windows\System32\Recovery\winre.wim" -Recurse -Force -ErrorAction SilentlyContinue -New-Item -Path "$scratchDir\Windows\System32\Recovery\winre.wim" -ItemType File -Force | Out-Null -Remove-Item -Path "$scratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue - -& 'dism' /English "/image:$scratchDir" /Cleanup-Image /StartComponentCleanup /ResetBase - -Write-Host "Taking ownership of the WinSxS folder. This might take a while..." -Set-ItemOwnershipAndAccess -Path "$scratchDir\Windows\WinSxS" -Recurse -Write-host "Complete!" - -$folderPath = Join-Path -Path $scratchDrive -ChildPath "\scratchdir\Windows\WinSxS_edit" -$sourceDirectory = "$scratchDrive\scratchdir\Windows\WinSxS" -$destinationDirectory = "$scratchDrive\scratchdir\Windows\WinSxS_edit" -New-Item -Path "$folderPath" -ItemType Directory - -$dirsToCopy = @() -if ($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 ($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_*", - "x86_microsoft.windows.i..utomation.proxystub_6595b64144ccf1df_*", - "x86_microsoft.windows.isolationautomation_6595b64144ccf1df_*", - "arm_microsoft.windows.c..-controls.resources_6595b64144ccf1df_*", - "arm_microsoft.windows.common-controls_6595b64144ccf1df_*", - "arm_microsoft.windows.gdiplus_6595b64144ccf1df_*", - "arm_microsoft.windows.i..utomation.proxystub_6595b64144ccf1df_*", - "arm_microsoft.windows.isolationautomation_6595b64144ccf1df_*", - "arm64_microsoft.vc80.crt_1fc8b3b9a1e18e3b_*", - "arm64_microsoft.vc90.crt_1fc8b3b9a1e18e3b_*", - "arm64_microsoft.windows.c..-controls.resources_6595b64144ccf1df_*", - "arm64_microsoft.windows.common-controls_6595b64144ccf1df_*", - "arm64_microsoft.windows.gdiplus_6595b64144ccf1df_*", - "arm64_microsoft.windows.i..utomation.proxystub_6595b64144ccf1df_*", - "arm64_microsoft.windows.isolationautomation_6595b64144ccf1df_*", - "arm64_microsoft-windows-servicing-adm_31bf3856ad364e35_*", - "arm64_microsoft-windows-servicingcommon_31bf3856ad364e35_*", - "arm64_microsoft-windows-servicing-onecore-uapi_31bf3856ad364e35_*", - "arm64_microsoft-windows-servicingstack_31bf3856ad364e35_*", - "arm64_microsoft-windows-servicingstack-inetsrv_31bf3856ad364e35_*", - "arm64_microsoft-windows-servicingstack-msg_31bf3856ad364e35_*" - ) -} -foreach ($dir in $dirsToCopy) { - $sourceDirs = Get-ChildItem -Path "$sourceDirectory" -Filter "$dir" -Directory - foreach ($sourceDir in $sourceDirs) { - $destDir = Join-Path -Path "$destinationDirectory" -ChildPath $sourceDir.Name - Write-Host "Copying $($sourceDir.FullName) to $destDir" - Copy-Item -Path $sourceDir.FullName -Destination "$destDir" -Recurse -Force - } -} - -Write-Host "Deleting WinSxS. This may take a while..." -# FIX: Use robocopy to reliably delete the protected WinSxS folder contents. -# Remove-Item can fail with "Access Denied" due to TrustedInstaller permissions. -$emptyDir = Join-Path -Path "$scratchDir" -ChildPath "empty_temp_for_delete" -New-Item -Path "$emptyDir" -ItemType Directory -Force | Out-Null -& 'robocopy' "$emptyDir" "$scratchDir\Windows\WinSxS" /MIR /R:0 /W:0 | Out-Null -Remove-Item -Path "$scratchDir\Windows\WinSxS" -Recurse -Force -Remove-Item -Path "$emptyDir" -Recurse -Force - -Rename-Item -Path "$scratchDir\Windows\WinSxS_edit" -NewName "$scratchDir\Windows\WinSxS" -Write-Host "Complete!" - -reg load HKLM\zCOMPONENTS "$scratchDir\Windows\System32\config\COMPONENTS" | Out-Null -reg load HKLM\zDEFAULT "$scratchDir\Windows\System32\config\default" | Out-Null -reg load HKLM\zNTUSER "$scratchDir\Users\Default\ntuser.dat" | Out-Null -reg load HKLM\zSOFTWARE "$scratchDir\Windows\System32\config\SOFTWARE" | Out-Null -reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" | Out-Null - -Write-Host "Bypassing system requirements (on the system image):" -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassCPUCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassRAMCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassSecureBootCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassStorageCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassTPMCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\MoSetup' /v 'AllowUpgradesWithUnsupportedTPMOrCPU' /t REG_DWORD /d 1 /f | Out-Null - -Write-Host "Applying various tweaks..." -# Copy-Item -Path "$PSScriptRoot\autounattend.xml" -Destination "$scratchDir\Windows\System32\Sysprep\autounattend.xml" -Force | Out-Null -& 'reg' 'add' 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' /v 'ShippedWithReserves' /t REG_DWORD /d 0 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' /v 'PreventDeviceEncryption' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Chat' /v 'ChatIcon' /t REG_DWORD /d 3 /f | Out-Null -& 'reg' 'delete' "HKEY_LOCAL_MACHINE\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" /f | Out-Null -& 'reg' 'delete' "HKEY_LOCAL_MACHINE\zSOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update" /f | Out-Null - -# ... Add more registry tweaks as needed ... - -Write-Host "Unmounting Registry..." -reg unload HKLM\zCOMPONENTS | Out-Null -reg unload HKLM\zDEFAULT | Out-Null -reg unload HKLM\zNTUSER | Out-Null -reg unload HKLM\zSOFTWARE | Out-Null -reg unload HKLM\zSYSTEM | Out-Null - -Write-Host "Loading registry hives to remove services..." -reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" | Out-Null -$servicesToRemove = @( - 'Spooler', - 'PrintNotify', - 'Fax', - 'RemoteRegistry', - 'diagsvc', - 'WerSvc', - 'PcaSvc', - 'MapsBroker', - 'WalletService', - 'BthAvctpSvc', - 'BluetoothUserService', - 'wuauserv', - 'UsoSvc', - 'WaaSMedicSvc' -) -foreach ($service in $servicesToRemove) { - Write-Host "Removing service: $service" - & 'reg' 'delete' "HKLM\zSYSTEM\ControlSet001\Services\$service" /f | Out-Null -} -reg unload HKLM\zSYSTEM | Out-Null - -Write-Host "Cleaning up and unmounting install.wim..." -& 'dism' /English "/image:$scratchDir" /Cleanup-Image /StartComponentCleanup /ResetBase -& 'dism' /English /Unmount-Image "/mountdir:$scratchDir" /commit -& 'dism' /English /Export-Image "/SourceImageFile:$wimFilePath" "/SourceIndex:$index" "/DestinationImageFile:$scratchDrive\nano11\sources\install2.wim" /compress:max -Remove-Item -Path "$wimFilePath" -Force -Rename-Item -Path "$scratchDrive\nano11\sources\install2.wim" -NewName "install.wim" - -Write-Host "Shrinking boot.wim..." -$bootWimPath = "$scratchDrive\nano11\sources\boot.wim" -Set-ItemOwnershipAndAccess -Path $bootWimPath -try { Set-ItemProperty -Path $bootWimPath -Name IsReadOnly -Value $false -ErrorAction Stop } catch {} - -$newBootWimPath = "$scratchDrive\nano11\sources\boot_new.wim" -& 'dism' /English /Export-Image "/SourceImageFile:$bootWimPath" /SourceIndex:2 "/DestinationImageFile:$newBootWimPath" -& 'dism' /English /Mount-Image "/imagefile:$newBootWimPath" /index:1 "/mountdir:$scratchDir" - -reg load HKLM\zSYSTEM "$scratchDir\Windows\System32\config\SYSTEM" | Out-Null -Write-Host "Bypassing system requirements (on boot image):" -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassCPUCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassRAMCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassSecureBootCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassStorageCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' /v 'BypassTPMCheck' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup\MoSetup' /v 'AllowUpgradesWithUnsupportedTPMOrCPU' /t REG_DWORD /d 1 /f | Out-Null -& 'reg' 'add' 'HKLM\zSYSTEM\Setup' /v 'CmdLine' /t REG_SZ /d 'x:\sources\setup.exe' /f | Out-Null -reg unload HKLM\zSYSTEM | Out-Null - -& 'dism' /English /Unmount-Image "/mountdir:$scratchDir" /commit - -$finalBootWimPath = "$scratchDrive\nano11\sources\boot_final.wim" -Remove-Item -Path $bootWimPath -Force -& 'dism' /English /Export-Image "/SourceImageFile:$newBootWimPath" /SourceIndex:1 "/DestinationImageFile:$finalBootWimPath" /compress:max -Remove-Item -Path $newBootWimPath -Force -Rename-Item -Path $finalBootWimPath -NewName "boot.wim" -Clear-Host - -Write-Host "Exporting final image to highly compressed ESD format..." -& dism /English /Export-Image /SourceImageFile:"$scratchDrive\nano11\sources\install.wim" /SourceIndex:1 /DestinationImageFile:"$scratchDrive\nano11\sources\install.esd" /Compress:recovery -Remove-Item "$scratchDrive\nano11\sources\install.wim" -Force -ErrorAction SilentlyContinue - -Write-Host "Performing final cleanup of installation folder root..." -$isoRoot = "$scratchDrive\nano11" -$keepList = @("boot", "efi", "sources", "bootmgr", "bootmgr.efi", "setup.exe") -Get-ChildItem -Path $isoRoot | Where-Object { $_.Name -notin $keepList } | ForEach-Object { - Write-Host "Removing non-essential file/folder from ISO root: $($_.Name)" - Remove-Item -Path $_.FullName -Recurse -Force -} - -Write-Host "Creating bootable ISO image..." -$OSCDIMG = "$PSScriptRoot\oscdimg.exe" -if (-not (Test-Path $OSCDIMG)) { - $url = "https://msdl.microsoft.com/download/symbols/oscdimg.exe/3D44737265000/oscdimg.exe" - Invoke-WebRequest -Uri $url -OutFile $OSCDIMG -} - -& "$OSCDIMG" -m -o -u2 -udfver102 "-bootdata:2#p0,e,b$scratchDrive\nano11\boot\etfsboot.com#pEF,e,b$scratchDrive\nano11\efi\microsoft\boot\efisys_noprompt.bin" ` - "$scratchDrive\nano11" "$PSScriptRoot\nano11.iso" - -Write-Host "Creation complete! Your ISO is named nano11.iso" -Read-Host "Press Enter to clean up and exit." -Start-Process 'dism.exe' -ArgumentList @( '/English', '/Unmount-Image', "/MountDir:$scratchDir", '/discard' ) ` - -Wait -PassThru -ErrorAction SilentlyContinue -Remove-Item -Path "$scratchDrive\nano11" -Recurse -Force -Remove-Item -Path "$scratchDrive\scratchdir" -Recurse -Force -Stop-Transcript -exit