581 lines
26 KiB
PowerShell
581 lines
26 KiB
PowerShell
<#
|
||
.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]$ISOPath, # Full path to a Windows 11 ISO (optional)
|
||
[string]$Language # Desired language code (e.g., "en-US", "de-DE")
|
||
)
|
||
|
||
#region Helper Functions (ISO download and mount)
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
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 the main flow
|
||
Main
|
||
|
||
#endregion Main Flow
|