From d5124162700339affc13f68e44e3937783743a81 Mon Sep 17 00:00:00 2001 From: Stefan Knoblich Date: Sun, 24 May 2026 20:59:38 +0200 Subject: [PATCH] nano11 builder script refinement Some artifacts such as cursors, webview components etc. are not being removed during builds, leading to increased ISO sizes. Clean up scratch directory handling and code structure in an attempt to find the cause. Signed-off-by: Stefan Knoblich --- bootstrap/nano11builder.ps1 | 179 +++++++++++++++++++++++------------- 1 file changed, 116 insertions(+), 63 deletions(-) diff --git a/bootstrap/nano11builder.ps1 b/bootstrap/nano11builder.ps1 index 0853323..177e6b9 100644 --- a/bootstrap/nano11builder.ps1 +++ b/bootstrap/nano11builder.ps1 @@ -69,7 +69,34 @@ function Set-ItemOwnershipAndAccess { } } -Start-Transcript -Path "$PSScriptRoot\nano11.log" +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.)." @@ -85,14 +112,14 @@ Write-Host "Off we go..." Start-Sleep -Seconds 3 Clear-Host +$mainOSDrive = $env:SystemDrive if (-not $SCRATCH) { - $ScratchDisk = $env:SystemDrive + $scratchDrive = $mainOSDrive } else { - $ScratchDisk = $SCRATCH + ":" + $scratchDrive = $SCRATCH + ":" } -$mainOSDrive = $env:SystemDrive -New-Item -ItemType Directory -Force -Path "$mainOSDrive\nano11\sources" +New-Item -ItemType Directory -Force -Path "$scratchDrive\nano11\sources" do { if (-not $ISO) { @@ -114,7 +141,7 @@ if (-not (Test-Path "$DriveLetter\sources\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:"$mainOSDrive\nano11\sources\install.wim" /Compress:max /CheckIntegrity + & '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 @@ -124,11 +151,11 @@ if (-not (Test-Path "$DriveLetter\sources\install.wim")) { } Write-Host "Copying Windows image..." -Copy-Item -Path "$DriveLetter\*" -Destination "$mainOSDrive\nano11" -Recurse -Force > $null -Remove-Item "$mainOSDrive\nano11\sources\install.esd" -ErrorAction SilentlyContinue +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 = "$mainOSDrive\nano11\sources\install.wim" +$wimFilePath = "$scratchDrive\nano11\sources\install.wim" $ImagesIndex = (Get-WindowsImage -ImagePath $wimFilePath).ImageIndex while ($ImagesIndex -notcontains $index) { Get-WindowsImage -ImagePath $wimFilePath @@ -139,11 +166,11 @@ 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 {} -New-Item -ItemType Directory -Force -Path "$mainOSDrive\scratchdir" -Mount-WindowsImage -ImagePath "$wimFilePath" -Index $index -Path "$mainOSDrive\scratchdir" +$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 --- -$scratchDir = "$mainOSDrive\scratchdir" $foldersToOwn = @( "$scratchDir\Windows\System32\DriverStore\FileRepository", "$scratchDir\Windows\Fonts", @@ -165,8 +192,12 @@ $filesToOwn = @( "$scratchDir\Windows\System32\OneDriveSetup.exe" ) -foreach ($folder in $foldersToOwn) { Set-ItemOwnershipAndAccess -Path $folder -Recurse } -foreach ($file in $filesToOwn) { Set-ItemOwnershipAndAccess -Path $file } +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})' } @@ -244,14 +275,14 @@ $packagePatterns = @( "Microsoft-Media-MPEG2-Decoder-Package~", "Microsoft-Windows-Wallpaper-Content-Extended-FoD-Package~" ) -$allPackages = & dism /English /image:$scratchDir /Get-Packages /Format:Table +$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 + & 'dism' /English /image:$scratchDir /Remove-Package /PackageName:$packageIdentity } } @@ -262,18 +293,18 @@ 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" +$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 { +$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 + & 'robocopy' "$emptyDirForDrivers" $driverFolder.FullName /MIR /R:0 /W:0 | Out-Null Remove-Item -Path $driverFolder.FullName -Recurse -Force break } @@ -281,12 +312,12 @@ Get-ChildItem -Path $driverRepo -Directory | ForEach-Object { } Remove-Item -Path $emptyDirForDrivers -Recurse -Force -$fontsPath = Join-Path -Path $winDir -ChildPath "Fonts" -if (Test-Path $fontsPath) { +$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 = 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 @@ -294,16 +325,16 @@ if (Test-Path $fontsPath) { } } -Remove-Item -Path (Join-Path -Path $winDir -ChildPath "Speech\Engines\TTS") -Recurse -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 +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 @@ -312,15 +343,15 @@ Remove-Item -Path "$scratchDir\Program Files (x86)\Microsoft\Edge*" -Recurse -Fo 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 + $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 + & '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 "$emptyDirForEdge" -Recurse -Force } } @@ -335,10 +366,10 @@ 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 $mainOSDrive -ChildPath "\scratchdir\Windows\WinSxS_edit" -$sourceDirectory = "$mainOSDrive\scratchdir\Windows\WinSxS" -$destinationDirectory = "$mainOSDrive\scratchdir\Windows\WinSxS_edit" -New-Item -Path $folderPath -ItemType Directory +$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") { @@ -403,24 +434,24 @@ if ($architecture -eq "amd64") { ) } foreach ($dir in $dirsToCopy) { - $sourceDirs = Get-ChildItem -Path $sourceDirectory -Filter $dir -Directory + $sourceDirs = Get-ChildItem -Path "$sourceDirectory" -Filter "$dir" -Directory foreach ($sourceDir in $sourceDirs) { - $destDir = Join-Path -Path $destinationDirectory -ChildPath $sourceDir.Name + $destDir = Join-Path -Path "$destinationDirectory" -ChildPath $sourceDir.Name Write-Host "Copying $($sourceDir.FullName) to $destDir" - Copy-Item -Path $sourceDir.FullName -Destination $destDir -Recurse -Force + 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 "$mainOSDrive\scratchdir\Windows\WinSxS" /MIR /R:0 /W:0 | Out-Null -Remove-Item -Path "$mainOSDrive\scratchdir\Windows\WinSxS" -Recurse -Force -Remove-Item -Path $emptyDir -Recurse -Force +$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 "$mainOSDrive\scratchdir\Windows\WinSxS_edit" -NewName "$mainOSDrive\scratchdir\Windows\WinSxS" +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 @@ -456,23 +487,41 @@ 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 } +$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:$mainOSDrive\nano11\sources\install2.wim" /compress:max -Remove-Item -Path $wimFilePath -Force -Rename-Item -Path "$mainOSDrive\nano11\sources\install2.wim" -NewName "install.wim" +& '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 = "$mainOSDrive\nano11\sources\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 = "$mainOSDrive\nano11\sources\boot_new.wim" +$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" @@ -488,7 +537,7 @@ reg unload HKLM\zSYSTEM | Out-Null & 'dism' /English /Unmount-Image "/mountdir:$scratchDir" /commit -$finalBootWimPath = "$mainOSDrive\nano11\sources\boot_final.wim" +$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 @@ -496,13 +545,16 @@ Rename-Item -Path $finalBootWimPath -NewName "boot.wim" Clear-Host Write-Host "Exporting final image to highly compressed ESD format..." -& dism /English /Export-Image /SourceImageFile:"$mainOSdrive\nano11\sources\install.wim" /SourceIndex:1 /DestinationImageFile:"$mainOSdrive\nano11\sources\install.esd" /Compress:recovery -Remove-Item "$mainOSdrive\nano11\sources\install.wim" -Force -ErrorAction SilentlyContinue +& 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 = "$mainOSDrive\nano11" +$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 } +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" @@ -511,13 +563,14 @@ if (-not (Test-Path $OSCDIMG)) { Invoke-WebRequest -Uri $url -OutFile $OSCDIMG } -& "$OSCDIMG" -m -o -u2 -udfver102 "-bootdata:2#p0,e,b$mainOSdrive\nano11\boot\etfsboot.com#pEF,e,b$mainOSdrive\nano11\efi\microsoft\boot\efisys_noprompt.bin" ` - "$mainOSdrive\nano11" "$PSScriptRoot\nano11.iso" +& "$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." -& 'dism' /English /Unmount-Image /MountDir:$scratchDir /discard -ErrorAction SilentlyContinue -Remove-Item -Path "$mainOSdrive\nano11" -Recurse -Force -Remove-Item -Path "$mainOSdrive\scratchdir" -Recurse -Force +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