From dc814ff379aafdadd6054eb6298534bf151f2a88 Mon Sep 17 00:00:00 2001 From: Stefan Knoblich Date: Sun, 24 May 2026 15:26:40 +0200 Subject: [PATCH] Import nano11 builder script for bootstrapping Reference: https://github.com/Tinnitus97/nano11 Signed-off-by: Stefan Knoblich --- bootstrap/nano11builder.ps1 | 523 ++++++++++++++++++++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 bootstrap/nano11builder.ps1 diff --git a/bootstrap/nano11builder.ps1 b/bootstrap/nano11builder.ps1 new file mode 100644 index 0000000..0853323 --- /dev/null +++ b/bootstrap/nano11builder.ps1 @@ -0,0 +1,523 @@ +# --- 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 +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) { + # 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 { + # 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': $_" + } +} + +Start-Transcript -Path "$PSScriptRoot\nano11.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 + +if (-not $SCRATCH) { + $ScratchDisk = $env:SystemDrive +} else { + $ScratchDisk = $SCRATCH + ":" +} + +$mainOSDrive = $env:SystemDrive +New-Item -ItemType Directory -Force -Path "$mainOSDrive\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:"$mainOSDrive\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 "$mainOSDrive\nano11" -Recurse -Force > $null +Remove-Item "$mainOSDrive\nano11\sources\install.esd" -ErrorAction SilentlyContinue + +Write-Host "Getting image information:" +$wimFilePath = "$mainOSDrive\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 {} + +New-Item -ItemType Directory -Force -Path "$mainOSDrive\scratchdir" +Mount-WindowsImage -ImagePath "$wimFilePath" -Index $index -Path "$mainOSDrive\scratchdir" + +# --- Taking ownership with the language-independent function --- +$scratchDir = "$mainOSDrive\scratchdir" +$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 + } + } +} +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 $mainOSDrive -ChildPath "\scratchdir\Windows\WinSxS_edit" +$sourceDirectory = "$mainOSDrive\scratchdir\Windows\WinSxS" +$destinationDirectory = "$mainOSDrive\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 "$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 + +Rename-Item -Path "$mainOSDrive\scratchdir\Windows\WinSxS_edit" -NewName "$mainOSDrive\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:$mainOSDrive\nano11\sources\install2.wim" /compress:max +Remove-Item -Path $wimFilePath -Force +Rename-Item -Path "$mainOSDrive\nano11\sources\install2.wim" -NewName "install.wim" + +Write-Host "Shrinking boot.wim..." +$bootWimPath = "$mainOSDrive\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" +& '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' /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 = "$mainOSDrive\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:"$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 + +Write-Host "Performing final cleanup of installation folder root..." +$isoRoot = "$mainOSDrive\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$mainOSdrive\nano11\boot\etfsboot.com#pEF,e,b$mainOSdrive\nano11\efi\microsoft\boot\efisys_noprompt.bin" ` + "$mainOSdrive\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 +Stop-Transcript +exit