d512416270
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>
577 lines
30 KiB
PowerShell
577 lines
30 KiB
PowerShell
# --- 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': $_"
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
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' /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
|