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