Elevenbuilder.ps1 aktualisiert
Signed-off-by: hax <hax@lainlounge.xyz>
This commit is contained in:
parent
6a58f48ed0
commit
d0e336d0a2
1 changed files with 567 additions and 149 deletions
|
@ -1,163 +1,581 @@
|
|||
# Enable debugging if needed
|
||||
# Set-PSDebug -Trace 1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tiny11 Image Creator – Full Workflow (ISO Download, Mount, Customization, and ISO Creation)
|
||||
|
||||
.DESCRIPTION
|
||||
This script uses DISM and other tools to create a customized Windows 11 image.
|
||||
It now supports obtaining the Windows 11 ISO from a user-supplied path or by auto‑downloading
|
||||
(using massgrave.dev as the source) if no ISO is provided. The ISO is then mounted and assigned
|
||||
a free drive letter. The script then copies installation files, processes install.wim and boot.wim,
|
||||
applies registry tweaks and removals, and finally creates an ISO using oscdimg.exe.
|
||||
|
||||
.PARAMETER ScratchDisk
|
||||
A drive letter (e.g. "D") or path where the working files will be stored.
|
||||
|
||||
.PARAMETER ISOPath
|
||||
(Optional) Full path to a Windows 11 ISO file. If not provided, the script will prompt for
|
||||
the desired language and auto‑download the ISO.
|
||||
|
||||
.PARAMETER Language
|
||||
(Optional) Desired language code for the ISO download (e.g. "en-US", "de-DE"). Only used if ISOPath is not provided.
|
||||
|
||||
.NOTES
|
||||
- This script requires administrative privileges.
|
||||
- It assumes that your original workflow (registry tweaks, application removals, etc.) must remain intact.
|
||||
- Some API endpoints (for downloading the ISO) are hypothetical and may need adjustment.
|
||||
#>
|
||||
|
||||
param (
|
||||
[ValidatePattern('^[c-zC-Z]$')]
|
||||
[string]$ScratchDisk
|
||||
[string]$ScratchDisk,
|
||||
[string]$ISOPath, # Full path to a Windows 11 ISO (optional)
|
||||
[string]$Language # Desired language code (e.g., "en-US", "de-DE")
|
||||
)
|
||||
|
||||
# Determine scratch disk location
|
||||
if (-not $ScratchDisk) {
|
||||
$ScratchDisk = $PSScriptRoot -replace '[\\]+$', ''
|
||||
} else {
|
||||
$ScratchDisk = "$ScratchDisk:"
|
||||
}
|
||||
#region Helper Functions (ISO download and mount)
|
||||
|
||||
Write-Output "Scratch disk set to $ScratchDisk"
|
||||
|
||||
# Ensure script is running with proper execution policy
|
||||
if ((Get-ExecutionPolicy) -eq 'Restricted') {
|
||||
Write-Host "Your current PowerShell Execution Policy is 'Restricted'. This prevents scripts from running."
|
||||
$response = Read-Host "Do you want to change it to 'RemoteSigned'? (yes/no)"
|
||||
if ($response -eq 'yes') {
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Confirm:$false
|
||||
} else {
|
||||
Write-Host "The script cannot proceed without changing the execution policy. Exiting..."
|
||||
exit
|
||||
function Get-Win11DownloadLink {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Queries the API (via massgrave.dev) for the proper ISO download link.
|
||||
.PARAMETER Language
|
||||
The desired language code.
|
||||
.OUTPUTS
|
||||
The download URL as a string.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Language
|
||||
)
|
||||
# Adjust the endpoint and parameters as required.
|
||||
$apiBase = "https://api.gravesoft.dev/msdl/"
|
||||
$endpoint = "getDownloadLink" # Hypothetical endpoint.
|
||||
$url = "$apiBase$endpoint?language=$Language"
|
||||
|
||||
Write-Host "Querying download link for Windows 11 ISO for language: $Language" -ForegroundColor Cyan
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri $url -Method Get
|
||||
if ($response -and $response.downloadUrl) {
|
||||
Write-Host "Download URL obtained: $($response.downloadUrl)" -ForegroundColor Green
|
||||
return $response.downloadUrl
|
||||
}
|
||||
else {
|
||||
Write-Error "API did not return a valid download URL."
|
||||
return $null
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Error calling the download API: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure script is running as administrator
|
||||
$adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
|
||||
$myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
function Get-Windows11ISO {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Returns the path to a Windows 11 ISO. If a valid ISOPath is provided, that file is used.
|
||||
Otherwise, prompts (or uses the provided language) and downloads the ISO.
|
||||
.PARAMETER ISOPath
|
||||
User-supplied ISO path.
|
||||
.PARAMETER Language
|
||||
Desired language code.
|
||||
.OUTPUTS
|
||||
The full path to the Windows 11 ISO.
|
||||
#>
|
||||
param(
|
||||
[string]$ISOPath,
|
||||
[string]$Language
|
||||
)
|
||||
# Use provided ISO if valid
|
||||
if ($ISOPath -and (Test-Path $ISOPath -PathType Leaf)) {
|
||||
Write-Host "Using provided ISO: $ISOPath" -ForegroundColor Green
|
||||
return $ISOPath
|
||||
}
|
||||
|
||||
# If no ISO path, prompt for language (if not provided)
|
||||
if (-not $Language) {
|
||||
$Language = Read-Host "Enter your desired Windows 11 language (e.g., en-US, de-DE)"
|
||||
}
|
||||
|
||||
$downloadLink = Get-Win11DownloadLink -Language $Language
|
||||
if (-not $downloadLink) {
|
||||
Write-Error "Could not retrieve a valid download link. Exiting."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$DownloadPath = "$env:TEMP\Windows11_$Language.iso"
|
||||
Write-Host "Downloading Windows 11 ISO from $downloadLink ..." -ForegroundColor Cyan
|
||||
try {
|
||||
Invoke-WebRequest -Uri $downloadLink -OutFile $DownloadPath
|
||||
Write-Host "Download complete: $DownloadPath" -ForegroundColor Green
|
||||
return $DownloadPath
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to download the ISO: $_"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $myWindowsPrincipal.IsInRole($adminRole)) {
|
||||
Write-Host "Restarting the script as administrator..."
|
||||
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "PowerShell"
|
||||
$newProcess.Arguments = "-File `"$($MyInvocation.MyCommand.Definition)`""
|
||||
$newProcess.Verb = "runas"
|
||||
[System.Diagnostics.Process]::Start($newProcess)
|
||||
function Mount-ISOAndAssignDriveLetter {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Mounts an ISO image and assigns a free drive letter if none is already assigned.
|
||||
.PARAMETER ISOPath
|
||||
The full path to the ISO file.
|
||||
.OUTPUTS
|
||||
The drive letter (e.g., "E:") where the ISO is mounted.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ISOPath
|
||||
)
|
||||
if (-not (Test-Path $ISOPath)) {
|
||||
Write-Error "The ISO file '$ISOPath' does not exist."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Mounting ISO image: $ISOPath" -ForegroundColor Cyan
|
||||
$mountedImage = Mount-DiskImage -ImagePath $ISOPath -PassThru
|
||||
if (-not $mountedImage) {
|
||||
Write-Error "Failed to mount ISO image."
|
||||
return
|
||||
}
|
||||
Start-Sleep -Seconds 3 # Wait for the volume to become available
|
||||
|
||||
$diskImage = Get-DiskImage -ImagePath $ISOPath
|
||||
if (-not $diskImage) {
|
||||
Write-Error "Unable to retrieve disk image information."
|
||||
return
|
||||
}
|
||||
$disk = $diskImage | Get-Disk
|
||||
if (-not $disk) {
|
||||
Write-Error "Unable to retrieve disk information for the mounted image."
|
||||
return
|
||||
}
|
||||
$diskNumber = $disk.Number
|
||||
|
||||
# Retrieve the first partition (most ISOs contain a single partition)
|
||||
$partition = Get-Partition -DiskNumber $diskNumber | Select-Object -First 1
|
||||
if (-not $partition) {
|
||||
Write-Error "No partition found on the mounted ISO."
|
||||
return
|
||||
}
|
||||
|
||||
# If no drive letter is assigned, choose a free one (from C: to Z:)
|
||||
if (-not $partition.DriveLetter) {
|
||||
$freeLetters = [char[]](67..90) | ForEach-Object { [char]$_ }
|
||||
$usedLetters = (Get-Volume | Where-Object { $_.DriveLetter } | Select-Object -ExpandProperty DriveLetter)
|
||||
$availableLetters = $freeLetters | Where-Object { $usedLetters -notcontains $_ }
|
||||
if ($availableLetters.Count -eq 0) {
|
||||
Write-Error "No free drive letters available."
|
||||
return
|
||||
}
|
||||
$freeLetter = $availableLetters | Select-Object -First 1
|
||||
Write-Host "Assigning drive letter '$freeLetter' to the mounted ISO." -ForegroundColor Yellow
|
||||
Set-Partition -DiskNumber $diskNumber -PartitionNumber $partition.PartitionNumber -NewDriveLetter $freeLetter
|
||||
$driveLetter = "$freeLetter`:"
|
||||
}
|
||||
else {
|
||||
$driveLetter = "$($partition.DriveLetter):"
|
||||
}
|
||||
|
||||
Write-Host "ISO mounted at drive letter: $driveLetter" -ForegroundColor Green
|
||||
return $driveLetter
|
||||
}
|
||||
|
||||
#endregion Helper Functions
|
||||
|
||||
#region Environment Setup
|
||||
|
||||
function Setup-Environment {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Performs pre-flight checks, sets the scratch disk, adjusts execution policy,
|
||||
ensures admin rights, starts logging, and creates necessary directories.
|
||||
#>
|
||||
# Set ScratchDisk (if not provided, use the script folder)
|
||||
if (-not $ScratchDisk) {
|
||||
$global:ScratchDisk = $PSScriptRoot.TrimEnd('\')
|
||||
} else {
|
||||
$global:ScratchDisk = "$ScratchDisk`:" # Append colon if needed.
|
||||
}
|
||||
Write-Output "Scratch disk set to $global:ScratchDisk"
|
||||
|
||||
# Check and adjust execution policy
|
||||
if ((Get-ExecutionPolicy) -eq 'Restricted') {
|
||||
Write-Host "Your current PowerShell Execution Policy is 'Restricted'. Changing it to 'RemoteSigned'..."
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Confirm:$false
|
||||
}
|
||||
|
||||
# Ensure script is running as administrator
|
||||
$adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
|
||||
$principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
if (-not $principal.IsInRole($adminRole)) {
|
||||
Write-Host "Restarting the script with elevated privileges..."
|
||||
$arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`""
|
||||
Start-Process powershell -Verb RunAs -ArgumentList $arguments
|
||||
exit
|
||||
}
|
||||
|
||||
# Start logging and set window title
|
||||
Start-Transcript -Path "$global:ScratchDisk\tiny11.log"
|
||||
$Host.UI.RawUI.WindowTitle = "Tiny11 Image Creator"
|
||||
Clear-Host
|
||||
Write-Host "Welcome to the Tiny11 Image Creator! Release: 05-06-24" -ForegroundColor Cyan
|
||||
|
||||
# Create required directories
|
||||
$global:tiny11Folder = Join-Path $global:ScratchDisk "tiny11"
|
||||
$global:sourcesFolder = Join-Path $global:tiny11Folder "sources"
|
||||
$global:mountPath = Join-Path $global:ScratchDisk "scratchdir"
|
||||
New-Item -ItemType Directory -Force -Path $global:sourcesFolder | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $global:mountPath | Out-Null
|
||||
}
|
||||
|
||||
#endregion Environment Setup
|
||||
|
||||
#region Obtain & Mount Installation Media
|
||||
|
||||
function Get-InstallationMedia {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Obtains the Windows 11 ISO (using a provided path or by downloading it) and mounts it.
|
||||
.OUTPUTS
|
||||
The drive letter where the installation media is mounted.
|
||||
#>
|
||||
# Get the ISO (download if necessary)
|
||||
$global:win11ISO = Get-Windows11ISO -ISOPath $ISOPath -Language $Language
|
||||
if (-not $global:win11ISO) {
|
||||
Write-Error "Failed to obtain a Windows 11 ISO. Exiting."
|
||||
exit
|
||||
}
|
||||
|
||||
# Mount the ISO and retrieve the drive letter
|
||||
$mediaDrive = Mount-ISOAndAssignDriveLetter -ISOPath $global:win11ISO
|
||||
if (-not $mediaDrive) {
|
||||
Write-Error "Failed to mount the Windows 11 ISO. Exiting."
|
||||
exit
|
||||
}
|
||||
Write-Output "Installation media mounted at: $mediaDrive"
|
||||
return $mediaDrive
|
||||
}
|
||||
|
||||
#endregion Obtain & Mount Installation Media
|
||||
|
||||
#region Process install.wim Image
|
||||
|
||||
function Process-InstallImage {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Processes the Windows installation image (install.wim). This includes:
|
||||
- Validating that the necessary installation files exist (or converting install.esd)
|
||||
- Copying installation files from the installation media to the working folder
|
||||
- Mounting the install.wim, gathering image information, applying customizations,
|
||||
removing apps, tweaking registries, and finally unmounting the image.
|
||||
#>
|
||||
param(
|
||||
[string]$DriveLetter # Installation media drive letter
|
||||
)
|
||||
|
||||
# Validate Windows installation files
|
||||
if ((Test-Path "$DriveLetter\sources\boot.wim") -eq $false -or (Test-Path "$DriveLetter\sources\install.wim") -eq $false) {
|
||||
if (Test-Path "$DriveLetter\sources\install.esd") {
|
||||
Write-Host "Found install.esd, converting to install.wim..."
|
||||
Get-WindowsImage -ImagePath "$DriveLetter\sources\install.esd"
|
||||
$index = Read-Host "Please enter the image index to convert from install.esd"
|
||||
Write-Host "Converting install.esd to install.wim. This may take a while..."
|
||||
Export-WindowsImage -SourceImagePath "$DriveLetter\sources\install.esd" `
|
||||
-SourceIndex $index `
|
||||
-DestinationImagePath "$global:ScratchDisk\tiny11\sources\install.wim" `
|
||||
-CompressionType Maximum -CheckIntegrity
|
||||
} else {
|
||||
Write-Host "Cannot find Windows OS installation files on the installation media."
|
||||
exit
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Copying Windows installation files from $DriveLetter..."
|
||||
Copy-Item -Path "$DriveLetter\*" -Destination "$global:tiny11Folder" -Recurse -Force | Out-Null
|
||||
# Remove install.esd if present
|
||||
Set-ItemProperty -Path "$global:tiny11Folder\sources\install.esd" -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue
|
||||
Remove-Item "$global:tiny11Folder\sources\install.esd" -ErrorAction SilentlyContinue
|
||||
Write-Host "Copy complete!"
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
Clear-Host
|
||||
Write-Host "Retrieving image information from install.wim..."
|
||||
Get-WindowsImage -ImagePath (Join-Path $global:sourcesFolder "install.wim")
|
||||
$index = Read-Host "Please enter the desired image index"
|
||||
Write-Host "Mounting install.wim image. This may take a while..."
|
||||
$global:wimFilePath = Join-Path $global:sourcesFolder "install.wim"
|
||||
& takeown "/F" $global:wimFilePath
|
||||
& icacls $global:wimFilePath "/grant" "$($adminGroup.Value):(F)"
|
||||
try {
|
||||
Set-ItemProperty -Path $global:wimFilePath -Name IsReadOnly -Value $false -ErrorAction Stop
|
||||
} catch {
|
||||
# Suppress errors
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $global:mountPath > $null
|
||||
Mount-WindowsImage -ImagePath $global:wimFilePath -Index $index -Path $global:mountPath
|
||||
|
||||
# Retrieve system UI language from the mounted image
|
||||
$imageIntl = & dism /English /Get-Intl "/Image:$global:mountPath"
|
||||
$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."
|
||||
}
|
||||
|
||||
# Retrieve architecture information from the image
|
||||
$imageInfo = & dism /English /Get-WimInfo "/wimFile:$global:sourcesFolder\install.wim" "/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 "Install image mounted. Proceeding with application removals and customizations..."
|
||||
|
||||
# Remove unwanted applications (bloatware) via DISM
|
||||
$packages = & dism /English "/image:$global:mountPath" '/Get-ProvisionedAppxPackages' |
|
||||
ForEach-Object {
|
||||
if ($_ -match 'PackageName : (.*)') {
|
||||
$matches[1]
|
||||
}
|
||||
}
|
||||
$packagePrefixes = 'Clipchamp.Clipchamp_', 'Microsoft.BingNews_', 'Microsoft.BingWeather_', 'Microsoft.GamingApp_', 'Microsoft.GetHelp_', 'Microsoft.Getstarted_', 'Microsoft.MicrosoftOfficeHub_', 'Microsoft.MicrosoftSolitaireCollection_', 'Microsoft.People_', 'Microsoft.PowerAutomateDesktop_', 'Microsoft.Todos_', 'Microsoft.WindowsAlarms_', 'microsoft.windowscommunicationsapps_', 'Microsoft.WindowsFeedbackHub_', 'Microsoft.WindowsMaps_', 'Microsoft.WindowsSoundRecorder_', 'Microsoft.Xbox.TCUI_', 'Microsoft.XboxGamingOverlay_', 'Microsoft.XboxGameOverlay_', 'Microsoft.XboxSpeechToTextOverlay_', 'Microsoft.YourPhone_', 'Microsoft.ZuneMusic_', 'Microsoft.ZuneVideo_', 'MicrosoftCorporationII.MicrosoftFamily_', 'MicrosoftCorporationII.QuickAssist_', 'MicrosoftTeams_', 'Microsoft.549981C3F5F10_'
|
||||
$packagesToRemove = $packages | Where-Object {
|
||||
$packageName = $_
|
||||
$packagePrefixes -contains ($packagePrefixes | Where-Object { $packageName -like "$_*" })
|
||||
}
|
||||
foreach ($package in $packagesToRemove) {
|
||||
& dism /English "/image:$global:mountPath" '/Remove-ProvisionedAppxPackage' "/PackageName:$package"
|
||||
}
|
||||
|
||||
# Remove Microsoft Edge and its components
|
||||
Write-Host "Removing Microsoft Edge..."
|
||||
Remove-Item -Path "$global:mountPath\Program Files (x86)\Microsoft\Edge" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "$global:mountPath\Program Files (x86)\Microsoft\EdgeUpdate" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "$global:mountPath\Program Files (x86)\Microsoft\EdgeCore" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if ($architecture -eq 'amd64') {
|
||||
$folderPath = Get-ChildItem -Path "$global:mountPath\Windows\WinSxS" -Filter "amd64_microsoft-edge-webview_31bf3856ad364e35*" -Directory | Select-Object -ExpandProperty FullName
|
||||
if ($folderPath) {
|
||||
& takeown '/f' $folderPath '/r' | Out-Null
|
||||
& icacls $folderPath "/grant" "$($adminGroup.Value):(F)" '/T' '/C' | Out-Null
|
||||
Remove-Item -Path $folderPath -Recurse -Force | Out-Null
|
||||
} else {
|
||||
Write-Host "Edge WebView folder not found."
|
||||
}
|
||||
}
|
||||
elseif ($architecture -eq 'arm64') {
|
||||
$folderPath = Get-ChildItem -Path "$global:mountPath\Windows\WinSxS" -Filter "arm64_microsoft-edge-webview_31bf3856ad364e35*" -Directory | Select-Object -ExpandProperty FullName
|
||||
if ($folderPath) {
|
||||
& takeown '/f' $folderPath '/r' | Out-Null
|
||||
& icacls $folderPath "/grant" "$($adminGroup.Value):(F)" '/T' '/C' | Out-Null
|
||||
Remove-Item -Path $folderPath -Recurse -Force | Out-Null
|
||||
} else {
|
||||
Write-Host "Edge WebView folder not found."
|
||||
}
|
||||
}
|
||||
& takeown '/f' "$global:mountPath\Windows\System32\Microsoft-Edge-Webview" '/r' | Out-Null
|
||||
& icacls "$global:mountPath\Windows\System32\Microsoft-Edge-Webview" '/grant' "$($adminGroup.Value):(F)" '/T' '/C' | Out-Null
|
||||
Remove-Item -Path "$global:mountPath\Windows\System32\Microsoft-Edge-Webview" -Recurse -Force | Out-Null
|
||||
|
||||
# Remove OneDrive
|
||||
Write-Host "Removing OneDrive..."
|
||||
& takeown '/f' "$global:mountPath\Windows\System32\OneDriveSetup.exe" | Out-Null
|
||||
& icacls "$global:mountPath\Windows\System32\OneDriveSetup.exe" '/grant' "$($adminGroup.Value):(F)" '/T' '/C' | Out-Null
|
||||
Remove-Item -Path "$global:mountPath\Windows\System32\OneDriveSetup.exe" -Force | Out-Null
|
||||
Write-Host "Application removal complete!"
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
Clear-Host
|
||||
|
||||
# Load registry hives from the mounted image and apply tweaks
|
||||
Write-Host "Loading registry hives from the mounted image..."
|
||||
reg load HKLM\zCOMPONENTS "$global:mountPath\Windows\System32\config\COMPONENTS" | Out-Null
|
||||
reg load HKLM\zDEFAULT "$global:mountPath\Windows\System32\config\default" | Out-Null
|
||||
reg load HKLM\zNTUSER "$global:mountPath\Users\Default\ntuser.dat" | Out-Null
|
||||
reg load HKLM\zSOFTWARE "$global:mountPath\Windows\System32\config\SOFTWARE" | Out-Null
|
||||
reg load HKLM\zSYSTEM "$global:mountPath\Windows\System32\config\SYSTEM" | Out-Null
|
||||
|
||||
Write-Host "Applying registry tweaks to bypass system requirements..."
|
||||
& reg add 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV1' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV2' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV1' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV2' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& 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 "Registry tweaks complete. Unloading registry hives..."
|
||||
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 "Performing component cleanup on the image..."
|
||||
Repair-WindowsImage -Path $global:mountPath -StartComponentCleanup -ResetBase
|
||||
Write-Host "Unmounting install.wim image (saving changes)..."
|
||||
Dismount-WindowsImage -Path $global:mountPath -Save
|
||||
Clear-Host
|
||||
|
||||
Write-Host "Exporting updated install.wim..."
|
||||
Export-WindowsImage -SourceImagePath (Join-Path $global:sourcesFolder "install.wim") -SourceIndex $index `
|
||||
-DestinationImagePath (Join-Path $global:sourcesFolder "install2.wim") -CompressionType Fast
|
||||
Remove-Item -Path (Join-Path $global:sourcesFolder "install.wim") -Force | Out-Null
|
||||
Rename-Item -Path (Join-Path $global:sourcesFolder "install2.wim") -NewName "install.wim" -Force | Out-Null
|
||||
Write-Host "Install image processing complete. Proceeding with boot.wim..."
|
||||
}
|
||||
|
||||
#endregion Process install.wim Image
|
||||
|
||||
#region Process boot.wim Image
|
||||
|
||||
function Process-BootImage {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Processes the boot image (boot.wim). This includes mounting the boot image,
|
||||
applying necessary tweaks (if any), and then unmounting the image.
|
||||
#>
|
||||
Write-Host "Mounting boot.wim image..."
|
||||
$global:wimFilePath = Join-Path $global:sourcesFolder "boot.wim"
|
||||
& takeown "/F" $global:wimFilePath | Out-Null
|
||||
& icacls $global:wimFilePath "/grant" "$($adminGroup.Value):(F)" | Out-Null
|
||||
Set-ItemProperty -Path $global:wimFilePath -Name IsReadOnly -Value $false
|
||||
Mount-WindowsImage -ImagePath $global:wimFilePath -Index 2 -Path $global:mountPath
|
||||
Write-Host "Boot image mounted. Loading registry from boot image..."
|
||||
reg load HKLM\zCOMPONENTS "$global:mountPath\Windows\System32\config\COMPONENTS" | Out-Null
|
||||
reg load HKLM\zDEFAULT "$global:mountPath\Windows\System32\config\default" | Out-Null
|
||||
reg load HKLM\zNTUSER "$global:mountPath\Users\Default\ntuser.dat" | Out-Null
|
||||
reg load HKLM\zSOFTWARE "$global:mountPath\Windows\System32\config\SOFTWARE" | Out-Null
|
||||
reg load HKLM\zSYSTEM "$global:mountPath\Windows\System32\config\SYSTEM" | Out-Null
|
||||
|
||||
Write-Host "Applying tweaks to boot image registry..."
|
||||
& reg add 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV1' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV2' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV1' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& reg add 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV2' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null
|
||||
& 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 "Tweaks for boot image applied. Unloading registry hives..."
|
||||
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 "Unmounting boot image (saving changes)..."
|
||||
Dismount-WindowsImage -Path $global:mountPath -Save
|
||||
}
|
||||
|
||||
#endregion Process boot.wim Image
|
||||
|
||||
#region Finalize ISO Creation
|
||||
|
||||
function Finalize-ISO {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Uses oscdimg.exe to create the final Tiny11 ISO from the customized image.
|
||||
#>
|
||||
Write-Host "Copying unattended file for bypassing MS account on OOBE..."
|
||||
Copy-Item -Path "$PSScriptRoot\autounattend.xml" -Destination "$global:tiny11Folder\autounattend.xml" -Force | Out-Null
|
||||
|
||||
Write-Host "Creating final ISO image..."
|
||||
$ADKDepTools = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\$hostarchitecture\Oscdimg"
|
||||
$localOSCDIMGPath = "$PSScriptRoot\oscdimg.exe"
|
||||
|
||||
if ([System.IO.Directory]::Exists($ADKDepTools)) {
|
||||
Write-Host "Using oscdimg.exe from the system ADK."
|
||||
$OSCDIMG = Join-Path $ADKDepTools "oscdimg.exe"
|
||||
} else {
|
||||
Write-Host "ADK folder not found. Using bundled oscdimg.exe."
|
||||
if (-not (Test-Path -Path $localOSCDIMGPath)) {
|
||||
Write-Host "Downloading oscdimg.exe..."
|
||||
$url = "https://msdl.microsoft.com/download/symbols/oscdimg.exe/3D44737265000/oscdimg.exe"
|
||||
Invoke-WebRequest -Uri $url -OutFile $localOSCDIMGPath
|
||||
if (-not (Test-Path $localOSCDIMGPath)) {
|
||||
Write-Error "Failed to download oscdimg.exe."
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "oscdimg.exe already exists locally."
|
||||
}
|
||||
$OSCDIMG = $localOSCDIMGPath
|
||||
}
|
||||
|
||||
# Define boot data (adjust paths if necessary)
|
||||
$bootData = "2#p0,e,b$global:tiny11Folder\boot\etfsboot.com#pEF,e,b$global:tiny11Folder\efi\microsoft\boot\efisys.bin"
|
||||
$isoOutput = Join-Path $PSScriptRoot "tiny11.iso"
|
||||
& "$OSCDIMG" '-m' '-o' '-u2' '-udfver102' "-bootdata:$bootData" "$global:tiny11Folder" "$isoOutput"
|
||||
Write-Host "ISO creation complete: $isoOutput" -ForegroundColor Green
|
||||
}
|
||||
|
||||
#endregion Finalize ISO Creation
|
||||
|
||||
#region Cleanup
|
||||
|
||||
function Cleanup-Environment {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Cleans up temporary folders used during image processing.
|
||||
#>
|
||||
Write-Host "Performing cleanup..."
|
||||
Remove-Item -Path "$global:tiny11Folder" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -Path "$global:mountPath" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Stop-Transcript
|
||||
Write-Host "Cleanup complete."
|
||||
}
|
||||
|
||||
#endregion Cleanup
|
||||
|
||||
#region Main Flow
|
||||
|
||||
function Main {
|
||||
# Step 1: Setup environment (parameters, admin check, logging, directories)
|
||||
Setup-Environment
|
||||
|
||||
# Step 2: Obtain and mount installation media (ISO)
|
||||
$mediaDrive = Get-InstallationMedia
|
||||
|
||||
# Step 3: Process the install.wim image (copy files, convert ESD if needed, apply tweaks)
|
||||
Process-InstallImage -DriveLetter $mediaDrive
|
||||
|
||||
# Step 4: Process the boot.wim image
|
||||
Process-BootImage
|
||||
|
||||
# Step 5: Finalize ISO creation using oscdimg.exe
|
||||
Finalize-ISO
|
||||
|
||||
# Step 6: Cleanup temporary folders and stop logging
|
||||
Cleanup-Environment
|
||||
|
||||
Write-Host "Tiny11 image creation completed. Press Enter to exit."
|
||||
Read-Host
|
||||
exit
|
||||
}
|
||||
|
||||
# Start logging
|
||||
Start-Transcript -Path "$ScratchDisk\tiny11.log"
|
||||
# Start the main flow
|
||||
Main
|
||||
|
||||
# Set window title
|
||||
$Host.UI.RawUI.WindowTitle = "Tiny11 Image Creator"
|
||||
Clear-Host
|
||||
Write-Host "Welcome to Tiny11 Image Creator! Release: 05-06-24"
|
||||
|
||||
# Ensure necessary directories exist
|
||||
New-Item -ItemType Directory -Force -Path "$ScratchDisk\tiny11\sources" | Out-Null
|
||||
|
||||
# Prompt for Windows 11 installation media drive letter
|
||||
do {
|
||||
$DriveLetter = Read-Host "Enter the drive letter of the Windows 11 installation media"
|
||||
if ($DriveLetter -match '^[c-zC-Z]$') {
|
||||
$DriveLetter = "$DriveLetter:"
|
||||
Write-Output "Drive letter set to $DriveLetter"
|
||||
} else {
|
||||
Write-Output "Invalid drive letter. Enter a letter between C and Z."
|
||||
}
|
||||
} while ($DriveLetter -notmatch '^[c-zC-Z]:$')
|
||||
|
||||
# Validate Windows installation files
|
||||
if (-not (Test-Path "$DriveLetter\sources\boot.wim") -or -not (Test-Path "$DriveLetter\sources\install.wim")) {
|
||||
if (Test-Path "$DriveLetter\sources\install.esd") {
|
||||
Write-Host "install.esd found. Converting to install.wim..."
|
||||
Get-WindowsImage -ImagePath "$DriveLetter\sources\install.esd"
|
||||
$index = Read-Host "Enter the image index"
|
||||
Write-Host "Converting install.esd to install.wim..."
|
||||
Export-WindowsImage -SourceImagePath "$DriveLetter\sources\install.esd" `
|
||||
-SourceIndex $index `
|
||||
-DestinationImagePath "$ScratchDisk\tiny11\sources\install.wim" `
|
||||
-CompressionType Maximum -CheckIntegrity
|
||||
} else {
|
||||
Write-Host "Windows installation files not found. Please provide the correct drive letter."
|
||||
exit
|
||||
}
|
||||
}
|
||||
|
||||
# Copy Windows installation files
|
||||
Write-Host "Copying Windows installation files..."
|
||||
Copy-Item -Path "$DriveLetter\*" -Destination "$ScratchDisk\tiny11" -Recurse -Force | Out-Null
|
||||
Remove-Item "$ScratchDisk\tiny11\sources\install.esd" -ErrorAction SilentlyContinue
|
||||
Write-Host "Copy complete!"
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
Clear-Host
|
||||
Write-Host "Retrieving image information..."
|
||||
Get-WindowsImage -ImagePath "$ScratchDisk\tiny11\sources\install.wim"
|
||||
|
||||
# Prompt for image index
|
||||
$index = Read-Host "Enter the image index"
|
||||
|
||||
# Mount Windows image
|
||||
Write-Host "Mounting Windows image... This may take a while."
|
||||
New-Item -ItemType Directory -Force -Path "$ScratchDisk\scratchdir" | Out-Null
|
||||
Mount-WindowsImage -ImagePath "$ScratchDisk\tiny11\sources\install.wim" -Index $index -Path "$ScratchDisk\scratchdir"
|
||||
|
||||
# Retrieve system UI language
|
||||
$imageIntl = & dism /English /Get-Intl "/Image:$($ScratchDisk)\scratchdir"
|
||||
if ($imageIntl -match 'Default system UI language : ([a-zA-Z]{2}-[a-zA-Z]{2})') {
|
||||
Write-Host "Default system UI language: $($matches[1])"
|
||||
} else {
|
||||
Write-Host "System UI language could not be determined."
|
||||
}
|
||||
|
||||
# Retrieve architecture
|
||||
$imageInfo = & dism /English /Get-WimInfo "/wimFile:$($ScratchDisk)\tiny11\sources\install.wim" "/index:$index"
|
||||
if ($imageInfo -match "Architecture : (\S+)") {
|
||||
$architecture = $matches[1] -replace "x64", "amd64"
|
||||
Write-Host "Architecture: $architecture"
|
||||
} else {
|
||||
Write-Host "Architecture information not found."
|
||||
}
|
||||
|
||||
# Remove bloatware applications
|
||||
Write-Host "Removing preinstalled apps..."
|
||||
$appsToRemove = @(
|
||||
'Clipchamp.Clipchamp_', 'Microsoft.BingNews_', 'Microsoft.BingWeather_', 'Microsoft.GamingApp_',
|
||||
'Microsoft.GetHelp_', 'Microsoft.Getstarted_', 'Microsoft.MicrosoftOfficeHub_', 'Microsoft.MicrosoftSolitaireCollection_',
|
||||
'Microsoft.People_', 'Microsoft.PowerAutomateDesktop_', 'Microsoft.Todos_', 'Microsoft.WindowsAlarms_',
|
||||
'microsoft.windowscommunicationsapps_', 'Microsoft.WindowsFeedbackHub_', 'Microsoft.WindowsMaps_', 'Microsoft.WindowsSoundRecorder_',
|
||||
'Microsoft.Xbox.TCUI_', 'Microsoft.XboxGamingOverlay_', 'Microsoft.XboxGameOverlay_', 'Microsoft.XboxSpeechToTextOverlay_',
|
||||
'Microsoft.YourPhone_', 'Microsoft.ZuneMusic_', 'Microsoft.ZuneVideo_', 'MicrosoftCorporationII.MicrosoftFamily_',
|
||||
'MicrosoftCorporationII.QuickAssist_', 'MicrosoftTeams_', 'Microsoft.549981C3F5F10_'
|
||||
)
|
||||
|
||||
$installedPackages = & dism /English "/image:$($ScratchDisk)\scratchdir" /Get-ProvisionedAppxPackages |
|
||||
ForEach-Object { if ($_ -match 'PackageName : (.*)') { $matches[1] } }
|
||||
|
||||
foreach ($package in $appsToRemove) {
|
||||
if ($installedPackages -match "$package*") {
|
||||
& dism /English "/image:$($ScratchDisk)\scratchdir" /Remove-ProvisionedAppxPackage "/PackageName:$matches[0]"
|
||||
}
|
||||
}
|
||||
|
||||
# Remove Microsoft Edge
|
||||
Write-Host "Removing Microsoft Edge..."
|
||||
$edgeFolders = @(
|
||||
"Program Files (x86)\Microsoft\Edge",
|
||||
"Program Files (x86)\Microsoft\EdgeUpdate",
|
||||
"Program Files (x86)\Microsoft\EdgeCore"
|
||||
)
|
||||
|
||||
foreach ($folder in $edgeFolders) {
|
||||
Remove-Item -Path "$ScratchDisk\scratchdir\$folder" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Remove OneDrive
|
||||
Write-Host "Removing OneDrive..."
|
||||
$oneDriveSetup = "$ScratchDisk\scratchdir\Windows\System32\OneDriveSetup.exe"
|
||||
if (Test-Path $oneDriveSetup) {
|
||||
& takeown /f $oneDriveSetup | Out-Null
|
||||
& icacls $oneDriveSetup /grant "$($adminGroup.Value):(F)" /T /C | Out-Null
|
||||
Remove-Item -Path $oneDriveSetup -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "Optimizations complete!"
|
||||
Start-Sleep -Seconds 2
|
||||
Clear-Host
|
||||
Write-Host "Tiny11 Image preparation finished!"
|
||||
#endregion Main Flow
|
||||
|
|
Loading…
Add table
Reference in a new issue