1
0

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 <stkn@bitplumber.de>
This commit is contained in:
2026-05-24 20:59:38 +02:00
parent dc814ff379
commit d512416270
+116 -63
View File
@@ -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