1
0

Switch to headless nano11 builder script from ...

... https://github.com/kelexine/tiny11-automated
which is also based on ntdevlabs script but with
cleaned up code structure and more suited for our
use case.

Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
This commit is contained in:
2026-05-26 18:39:44 +02:00
parent eecf37cc76
commit f1c1713726
2 changed files with 1263 additions and 582 deletions
File diff suppressed because it is too large Load Diff
-582
View File
@@ -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