eecf37cc76
Go back to takeown.exe and icacls.exe, the new approach does not seem to work, which causes some of the resources (e.g. cursors) to not be deleted. Signed-off-by: Stefan Knoblich <stkn@bitplumber.de>
583 lines
30 KiB
PowerShell
583 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
|
|
# 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
|