iso save success

This commit is contained in:
Chris Titus Tech
2026-02-23 10:51:59 -06:00
parent 751b7ef79c
commit df75cd8c6f
4 changed files with 321 additions and 110 deletions

View File

@@ -98,10 +98,10 @@ function Invoke-WinUtilISOMountAndVerify {
# ── Read edition / architecture info ── # ── Read edition / architecture info ──
Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55 Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55
$editions = Get-WindowsImage -ImagePath $activeWim | Select-Object -ExpandProperty ImageName $imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName
# ── Verify at least one Win11 edition is present ── # ── Verify at least one Win11 edition is present ──
$isWin11 = $editions | Where-Object { $_ -match "Windows 11" } $isWin11 = $imageInfo | Where-Object { $_.ImageName -match "Windows 11" }
if (-not $isWin11) { if (-not $isWin11) {
Dismount-DiskImage -ImagePath $isoPath | Out-Null Dismount-DiskImage -ImagePath $isoPath | Out-Null
Write-Win11ISOLog "ERROR: No 'Windows 11' edition found in the image." Write-Win11ISOLog "ERROR: No 'Windows 11' edition found in the image."
@@ -112,9 +112,20 @@ function Invoke-WinUtilISOMountAndVerify {
return return
} }
# Store edition info for later index lookup
$sync["Win11ISOImageInfo"] = $imageInfo
# ── Populate UI ── # ── Populate UI ──
$sync["WPFWin11ISOMountDriveLetter"].Text = "Mounted at: $driveLetter | Image file: $(Split-Path $activeWim -Leaf)" $sync["WPFWin11ISOMountDriveLetter"].Text = "Mounted at: $driveLetter | Image file: $(Split-Path $activeWim -Leaf)"
$sync["WPFWin11ISOEditionList"].Text = ($editions -join "`n") $sync["WPFWin11ISOEditionComboBox"].Dispatcher.Invoke([action]{
$sync["WPFWin11ISOEditionComboBox"].Items.Clear()
foreach ($img in $imageInfo) {
[void]$sync["WPFWin11ISOEditionComboBox"].Items.Add("$($img.ImageIndex): $($img.ImageName)")
}
if ($sync["WPFWin11ISOEditionComboBox"].Items.Count -gt 0) {
$sync["WPFWin11ISOEditionComboBox"].SelectedIndex = 0
}
})
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Visible" $sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Visible"
# Store for later steps # Store for later steps
@@ -126,7 +137,7 @@ function Invoke-WinUtilISOMountAndVerify {
$sync["WPFWin11ISOModifySection"].Visibility = "Visible" $sync["WPFWin11ISOModifySection"].Visibility = "Visible"
Set-WinUtilProgressBar -Label "ISO verified ✔" -Percent 100 Set-WinUtilProgressBar -Label "ISO verified ✔" -Percent 100
Write-Win11ISOLog "ISO verified OK. Editions found: $($editions.Count)" Write-Win11ISOLog "ISO verified OK. Editions found: $($imageInfo.Count)"
} }
catch { catch {
Write-Win11ISOLog "ERROR during mount/verify: $_" Write-Win11ISOLog "ERROR during mount/verify: $_"
@@ -162,21 +173,55 @@ function Invoke-WinUtilISOModify {
return return
} }
# ── Resolve selected edition index from the ComboBox ──
$selectedItem = $sync["WPFWin11ISOEditionComboBox"].SelectedItem
$selectedWimIndex = 1 # default fallback
if ($selectedItem -and $selectedItem -match '^(\d+):') {
$selectedWimIndex = [int]$Matches[1]
} elseif ($sync["Win11ISOImageInfo"]) {
$selectedWimIndex = $sync["Win11ISOImageInfo"][0].ImageIndex
}
$selectedEditionName = if ($selectedItem) { ($selectedItem -replace '^\d+:\s*', '') } else { "Unknown" }
Write-Win11ISOLog "Selected edition: $selectedEditionName (Index $selectedWimIndex)"
# Disable the modify button to prevent double-click # Disable the modify button to prevent double-click
$sync["WPFWin11ISOModifyButton"].IsEnabled = $false $sync["WPFWin11ISOModifyButton"].IsEnabled = $false
$workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')" $existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
$workDir = if ($existingWorkDir) {
Write-Win11ISOLog "Reusing existing temp directory: $($existingWorkDir.FullName)"
$existingWorkDir.FullName
} else {
Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
}
# ── Resolve autounattend.xml content ──────────────────────────────────────
# Compiled winutil.ps1 sets $WinUtilAutounattendXml before main.ps1 runs.
# In dev/source mode fall back to reading tools\autounattend.xml directly.
$autounattendContent = if ($WinUtilAutounattendXml) {
$WinUtilAutounattendXml
} else {
$toolsXml = Join-Path $PSScriptRoot "..\..\tools\autounattend.xml"
if (Test-Path $toolsXml) { Get-Content $toolsXml -Raw } else { "" }
}
# ── Run modification in a background runspace ── # ── Run modification in a background runspace ──
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA" $runspace.ApartmentState = "STA"
$runspace.ThreadOptions = "ReuseThread" $runspace.ThreadOptions = "ReuseThread"
$runspace.Open() $runspace.Open()
$runspace.SessionStateProxy.SetVariable("sync", $sync) $runspace.SessionStateProxy.SetVariable("sync", $sync)
$runspace.SessionStateProxy.SetVariable("isoPath", $isoPath) $runspace.SessionStateProxy.SetVariable("isoPath", $isoPath)
$runspace.SessionStateProxy.SetVariable("driveLetter", $driveLetter) $runspace.SessionStateProxy.SetVariable("driveLetter", $driveLetter)
$runspace.SessionStateProxy.SetVariable("wimPath", $wimPath) $runspace.SessionStateProxy.SetVariable("wimPath", $wimPath)
$runspace.SessionStateProxy.SetVariable("workDir", $workDir) $runspace.SessionStateProxy.SetVariable("workDir", $workDir)
$runspace.SessionStateProxy.SetVariable("selectedWimIndex", $selectedWimIndex)
$runspace.SessionStateProxy.SetVariable("selectedEditionName", $selectedEditionName)
$runspace.SessionStateProxy.SetVariable("autounattendContent", $autounattendContent)
# Serialize functions so they are available inside the runspace # Serialize functions so they are available inside the runspace
$isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + ` $isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + `
@@ -217,6 +262,15 @@ function Invoke-WinUtilISOModify {
} }
try { try {
# ── Hide Steps 1-3 while modification is running; expand log to fill screen ──
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync["WPFWin11ISOSelectSection"].Visibility = "Collapsed"
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
$expandedHeight = [Math]::Max(400, $sync.Window.ActualHeight - 100)
$sync["WPFWin11ISOStatusLog"].Height = $expandedHeight
})
# ── 1. Create working directory structure ── # ── 1. Create working directory structure ──
Log "Creating working directory: $workDir" Log "Creating working directory: $workDir"
$isoContents = Join-Path $workDir "iso_contents" $isoContents = Join-Path $workDir "iso_contents"
@@ -240,14 +294,14 @@ function Invoke-WinUtilISOModify {
# Ensure the file is writable # Ensure the file is writable
Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false
# ── 4. Mount the first index of install.wim ── # ── 4. Mount the selected edition of install.wim ──
Log "Mounting install.wim (Index 1) at $mountDir..." Log "Mounting install.wim (Index ${selectedWimIndex}: $selectedEditionName) at $mountDir..."
Mount-WindowsImage -ImagePath $localWim -Index 1 -Path $mountDir -ErrorAction Stop | Out-Null Mount-WindowsImage -ImagePath $localWim -Index $selectedWimIndex -Path $mountDir -ErrorAction Stop | Out-Null
SetProgress "Modifying install.wim..." 45 SetProgress "Modifying install.wim..." 45
# ── Apply all WinUtil modifications via Invoke-WinUtilISOScript ── # ── Apply all WinUtil modifications via Invoke-WinUtilISOScript ──
Log "Applying WinUtil modifications to install.wim..." Log "Applying WinUtil modifications to install.wim..."
Invoke-WinUtilISOScript -ScratchDir $mountDir -Log { param($m) Log $m } Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -Log { param($m) Log $m }
# ── 5. Save and dismount the WIM ── # ── 5. Save and dismount the WIM ──
SetProgress "Saving modified install.wim..." 65 SetProgress "Saving modified install.wim..." 65
@@ -265,16 +319,54 @@ function Invoke-WinUtilISOModify {
$sync["Win11ISOContentsDir"] = $isoContents $sync["Win11ISOContentsDir"] = $isoContents
SetProgress "Modification complete ✔" 100 SetProgress "Modification complete ✔" 100
Log "install.wim modification complete. Select an output option in Step 4." Log "install.wim modification complete. Choose an output option in Step 4."
# ── Reveal Step 4 on the UI thread ── # ── Reveal Step 4 on the UI thread ──
# Note: USB drive enumeration (Get-Disk) is intentionally deferred to
# when the user explicitly selects the USB option, to avoid blocking
# the UI thread here.
$sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{
$sync["WPFWin11ISOOutputSection"].Visibility = "Visible" $sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
Invoke-WinUtilISORefreshUSBDrives
}) })
} }
catch { catch {
Log "ERROR during modification: $_" Log "ERROR during modification: $_"
# ── Cleanup: dismount WIM if still mounted ──
try {
if (Test-Path $mountDir) {
$mountedImages = Get-WindowsImage -Mounted -ErrorAction SilentlyContinue |
Where-Object { $_.Path -eq $mountDir }
if ($mountedImages) {
Log "Cleaning up: dismounting install.wim (discarding changes)..."
Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null
}
}
} catch {
Log "Warning: could not dismount install.wim during cleanup: $_"
}
# ── Cleanup: dismount the source ISO ──
try {
$mountedISO = Get-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue
if ($mountedISO -and $mountedISO.Attached) {
Log "Cleaning up: dismounting source ISO..."
Dismount-DiskImage -ImagePath $isoPath -ErrorAction SilentlyContinue | Out-Null
}
} catch {
Log "Warning: could not dismount ISO during cleanup: $_"
}
# ── Cleanup: remove temp working directory ──
try {
if (Test-Path $workDir) {
Log "Cleaning up: removing temp directory $workDir..."
Remove-Item -Path $workDir -Recurse -Force -ErrorAction SilentlyContinue
}
} catch {
Log "Warning: could not remove temp directory during cleanup: $_"
}
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
[System.Windows.MessageBox]::Show( [System.Windows.MessageBox]::Show(
"An error occurred during install.wim modification:`n`n$_", "An error occurred during install.wim modification:`n`n$_",
@@ -288,6 +380,15 @@ function Invoke-WinUtilISOModify {
$sync.progressBarTextBlock.ToolTip = "" $sync.progressBarTextBlock.ToolTip = ""
$sync.ProgressBar.Value = 0 $sync.ProgressBar.Value = 0
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true $sync["WPFWin11ISOModifyButton"].IsEnabled = $true
# ── Only restore steps 1-3 if Step 4 was NOT successfully shown ──
# When modification succeeds, Step 4 is visible and steps 1-3 stay
# hidden until the user clicks Clean & Reset.
if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") {
$sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
$sync["WPFWin11ISOMountSection"].Visibility = "Visible"
$sync["WPFWin11ISOModifySection"].Visibility = "Visible"
}
$sync["WPFWin11ISOStatusLog"].Height = 140
}) })
} }
}) | Out-Null }) | Out-Null
@@ -295,6 +396,53 @@ function Invoke-WinUtilISOModify {
$script.BeginInvoke() | Out-Null $script.BeginInvoke() | Out-Null
} }
function Invoke-WinUtilISOCleanAndReset {
<#
.SYNOPSIS
Deletes the temporary working directory created during ISO modification
and resets the entire ISO UI back to its initial state (Step 1 only).
#>
$workDir = $sync["Win11ISOWorkDir"]
if ($workDir -and (Test-Path $workDir)) {
$confirm = [System.Windows.MessageBox]::Show(
"This will delete the temporary working directory:`n`n$workDir`n`nAnd reset the interface back to the start.`n`nContinue?",
"Clean & Reset", "YesNo", "Warning")
if ($confirm -ne "Yes") { return }
try {
Write-Win11ISOLog "Deleting temp directory: $workDir"
Remove-Item -Path $workDir -Recurse -Force -ErrorAction Stop
Write-Win11ISOLog "Temp directory deleted."
} catch {
Write-Win11ISOLog "WARNING: could not fully delete temp directory: $_"
}
}
# Clear all stored ISO state
$sync["Win11ISOWorkDir"] = $null
$sync["Win11ISOContentsDir"] = $null
$sync["Win11ISOImagePath"] = $null
$sync["Win11ISODriveLetter"] = $null
$sync["Win11ISOWimPath"] = $null
$sync["Win11ISOImageInfo"] = $null
$sync["Win11ISOUSBDisks"] = $null
# Reset the UI to the initial state
$sync["WPFWin11ISOPath"].Text = "No ISO selected..."
$sync["WPFWin11ISOFileInfo"].Visibility = "Collapsed"
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
$sync["WPFWin11ISOOptionUSB"].Visibility = "Collapsed"
$sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
$sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
$sync["WPFWin11ISOStatusLog"].Height = 140
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true
}
function Invoke-WinUtilISOExport { function Invoke-WinUtilISOExport {
<# <#
.SYNOPSIS .SYNOPSIS
@@ -330,12 +478,28 @@ function Invoke-WinUtilISOExport {
Select-Object -First 1 -ExpandProperty FullName Select-Object -First 1 -ExpandProperty FullName
if (-not $oscdimg) { if (-not $oscdimg) {
Set-WinUtilProgressBar -Label "" -Percent 0 Write-Win11ISOLog "oscdimg.exe not found. Attempting to install via winget..."
Write-Win11ISOLog "oscdimg.exe not found. Install Windows ADK to enable ISO export." Set-WinUtilProgressBar -Label "Installing oscdimg..." -Percent 5
[System.Windows.MessageBox]::Show( try {
"oscdimg.exe was not found.`n`nTo export an ISO you need the Windows Assessment and Deployment Kit (ADK).`n`nDownload it from: https://learn.microsoft.com/windows-hardware/get-started/adk-install", $winget = Get-Command winget -ErrorAction Stop
"Windows ADK Required", "OK", "Warning") $result = & $winget install -e --id Microsoft.OSCDIMG --accept-package-agreements --accept-source-agreements 2>&1
return Write-Win11ISOLog "winget output: $result"
# Re-scan for oscdimg after install
$oscdimg = Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName
} catch {
Write-Win11ISOLog "winget not available or install failed: $_"
}
if (-not $oscdimg) {
Set-WinUtilProgressBar -Label "" -Percent 0
Write-Win11ISOLog "oscdimg.exe still not found after install attempt."
[System.Windows.MessageBox]::Show(
"oscdimg.exe could not be found or installed automatically.`n`nPlease install it manually:`n winget install -e --id Microsoft.OSCDIMG`n`nOr install the Windows ADK from:`nhttps://learn.microsoft.com/windows-hardware/get-started/adk-install",
"oscdimg Not Found", "OK", "Warning")
return
}
Write-Win11ISOLog "oscdimg.exe installed successfully."
} }
# Build boot parameters (BIOS + UEFI dual-boot) # Build boot parameters (BIOS + UEFI dual-boot)

View File

@@ -32,6 +32,12 @@ function Invoke-WinUtilISOScript {
#> #>
param ( param (
[Parameter(Mandatory)][string]$ScratchDir, [Parameter(Mandatory)][string]$ScratchDir,
# Root directory of the extracted ISO contents. When supplied, autounattend.xml
# is written here so Windows Setup picks it up automatically at boot.
[string]$ISOContentsDir = "",
# Autounattend XML content. In compiled winutil.ps1 this comes from the embedded
# $WinUtilAutounattendXml here-string; in dev mode it is read from tools\autounattend.xml.
[string]$AutoUnattendXml = "",
[scriptblock]$Log = { param($m) Write-Output $m } [scriptblock]$Log = { param($m) Write-Output $m }
) )
@@ -131,11 +137,6 @@ function Invoke-WinUtilISOScript {
# ═════════════════════════════════════════════════════════════════════════ # ═════════════════════════════════════════════════════════════════════════
& $Log "Removing Edge..." & $Log "Removing Edge..."
Remove-Item -Path "$ScratchDir\Program Files (x86)\Microsoft\Edge" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "$ScratchDir\Program Files (x86)\Microsoft\Edge" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path "$ScratchDir\Program Files (x86)\Microsoft\EdgeUpdate" -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -Path "$ScratchDir\Program Files (x86)\Microsoft\EdgeCore" -Recurse -Force -ErrorAction SilentlyContinue
& takeown /f "$ScratchDir\Windows\System32\Microsoft-Edge-Webview" /r | Out-Null
& icacls "$ScratchDir\Windows\System32\Microsoft-Edge-Webview" /grant "$($adminGroup.Value):(F)" /T /C | Out-Null
Remove-Item -Path "$ScratchDir\Windows\System32\Microsoft-Edge-Webview" -Recurse -Force -ErrorAction SilentlyContinue
# ═════════════════════════════════════════════════════════════════════════ # ═════════════════════════════════════════════════════════════════════════
# 3. Remove OneDrive # 3. Remove OneDrive
@@ -195,9 +196,23 @@ function Invoke-WinUtilISOScript {
& $Log "Enabling local accounts on OOBE..." & $Log "Enabling local accounts on OOBE..."
_ISOScript-SetReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1' _ISOScript-SetReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1'
$sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml" if ($AutoUnattendXml) {
Set-Content -Path $sysprepDest -Value $WinUtilAutounattendXml -Encoding UTF8 -Force # ── Place autounattend.xml inside the WIM (Sysprep) ──────────────────
& $Log "Written autounattend.xml to Sysprep directory." $sysprepDest = "$ScratchDir\Windows\System32\Sysprep\autounattend.xml"
Set-Content -Path $sysprepDest -Value $AutoUnattendXml -Encoding UTF8 -Force
& $Log "Written autounattend.xml to Sysprep directory."
# ── Place autounattend.xml at the ISO / USB root ──────────────────────
# Windows Setup reads this file first (before booting into the OS),
# which is what drives the local-account / OOBE bypass at install time.
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
$isoDest = Join-Path $ISOContentsDir "autounattend.xml"
Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force
& $Log "Written autounattend.xml to ISO root ($isoDest)."
}
} else {
& $Log "Warning: autounattend.xml content is empty — skipping OOBE bypass file."
}
& $Log "Disabling reserved storage..." & $Log "Disabling reserved storage..."
_ISOScript-SetReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0' _ISOScript-SetReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0'

View File

@@ -554,11 +554,18 @@ $sync["WPFWin11ISOModifyButton"].Add_Click({
Invoke-WinUtilISOModify Invoke-WinUtilISOModify
}) })
$sync["WPFWin11ISOExportButton"].Add_Click({ $sync["WPFWin11ISOChooseISOButton"].Add_Click({
Write-Debug "WPFWin11ISOExportButton clicked" Write-Debug "WPFWin11ISOChooseISOButton clicked"
$sync["WPFWin11ISOOptionUSB"].Visibility = "Collapsed"
Invoke-WinUtilISOExport Invoke-WinUtilISOExport
}) })
$sync["WPFWin11ISOChooseUSBButton"].Add_Click({
Write-Debug "WPFWin11ISOChooseUSBButton clicked"
$sync["WPFWin11ISOOptionUSB"].Visibility = "Visible"
Invoke-WinUtilISORefreshUSBDrives
})
$sync["WPFWin11ISORefreshUSBButton"].Add_Click({ $sync["WPFWin11ISORefreshUSBButton"].Add_Click({
Write-Debug "WPFWin11ISORefreshUSBButton clicked" Write-Debug "WPFWin11ISORefreshUSBButton clicked"
Invoke-WinUtilISORefreshUSBDrives Invoke-WinUtilISORefreshUSBDrives
@@ -569,6 +576,11 @@ $sync["WPFWin11ISOWriteUSBButton"].Add_Click({
Invoke-WinUtilISOWriteUSB Invoke-WinUtilISOWriteUSB
}) })
$sync["WPFWin11ISOCleanResetButton"].Add_Click({
Write-Debug "WPFWin11ISOCleanResetButton clicked"
Invoke-WinUtilISOCleanAndReset
})
# ────────────────────────────────────────────────────────────────────────────── # ──────────────────────────────────────────────────────────────────────────────
$sync["Form"].ShowDialog() | out-null $sync["Form"].ShowDialog() | out-null

View File

@@ -1353,7 +1353,7 @@
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ═══════════════════════════════════════════════════════════ -->
<!-- STEP 1 : Select Windows 11 ISO --> <!-- STEP 1 : Select Windows 11 ISO -->
<!-- ═══════════════════════════════════════════════════════════ --> <!-- ═══════════════════════════════════════════════════════════ -->
<Border Grid.Row="0" Style="{StaticResource BorderStyle}"> <Border Grid.Row="0" Name="WPFWin11ISOSelectSection" Style="{StaticResource BorderStyle}">
<Grid Margin="5"> <Grid Margin="5">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
@@ -1364,12 +1364,13 @@
<StackPanel Grid.Column="0" Margin="5,5,15,5"> <StackPanel Grid.Column="0" Margin="5,5,15,5">
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8"> Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8">
Step 1 Select Windows 11 ISO Step 1 - Select Windows 11 ISO
</TextBlock> </TextBlock>
<TextBlock FontSize="{DynamicResource FontSize}" Foreground="{DynamicResource MainForegroundColor}" <TextBlock FontSize="{DynamicResource FontSize}" Foreground="{DynamicResource MainForegroundColor}"
TextWrapping="Wrap" Margin="0,0,0,12"> TextWrapping="Wrap" Margin="0,0,0,12">
Browse to your locally saved Windows 11 ISO file. Only official ISOs Browse to your locally saved Windows 11 ISO file. Only official ISOs
downloaded from Microsoft are supported. downloaded from Microsoft are supported. This is only meant for FRESH
and NEW Windows installs.
</TextBlock> </TextBlock>
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -1387,7 +1388,7 @@
Background="{DynamicResource MainBackgroundColor}"/> Background="{DynamicResource MainBackgroundColor}"/>
<Button Grid.Column="1" <Button Grid.Column="1"
Name="WPFWin11ISOBrowseButton" Name="WPFWin11ISOBrowseButton"
Content="Browse" Content="Browse"
Width="Auto" Padding="12,0" Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"/> Height="{DynamicResource ButtonHeight}"/>
</Grid> </Grid>
@@ -1408,7 +1409,7 @@
<StackPanel> <StackPanel>
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="OrangeRed" Margin="0,0,0,10"> Foreground="OrangeRed" Margin="0,0,0,10">
You must use an official Microsoft ISO !!WARNING!! You must use an official Microsoft ISO
</TextBlock> </TextBlock>
<TextBlock FontSize="{DynamicResource FontSize}" <TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
@@ -1425,9 +1426,9 @@
<TextBlock FontSize="{DynamicResource FontSize}" <TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
TextWrapping="Wrap" Margin="12,0,0,12"> TextWrapping="Wrap" Margin="12,0,0,12">
Edition : Windows 11 - Edition : Windows 11
<LineBreak/> Language : your preferred language <LineBreak/>- Language : your preferred language
<LineBreak/> Architecture : 64-bit (x64) <LineBreak/>- Architecture : 64-bit (x64)
</TextBlock> </TextBlock>
<Button Name="WPFWin11ISODownloadLink" <Button Name="WPFWin11ISODownloadLink"
Content="Open Microsoft Download Page" Content="Open Microsoft Download Page"
@@ -1455,7 +1456,7 @@
<StackPanel Grid.Column="0" Margin="0,0,20,0" VerticalAlignment="Top"> <StackPanel Grid.Column="0" Margin="0,0,20,0" VerticalAlignment="Top">
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8"> Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8">
Step 2 Mount &amp; Verify ISO Step 2 - Mount &amp; Verify ISO
</TextBlock> </TextBlock>
<TextBlock FontSize="{DynamicResource FontSize}" <TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
@@ -1490,12 +1491,15 @@
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
Margin="0,6,0,4"> Margin="0,6,0,4">
Available Editions: Select Edition:
</TextBlock> </TextBlock>
<TextBlock Name="WPFWin11ISOEditionList" <ComboBox Name="WPFWin11ISOEditionComboBox"
FontSize="{DynamicResource FontSize}" FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
TextWrapping="Wrap"/> Background="{DynamicResource MainBackgroundColor}"
BorderBrush="{DynamicResource BorderColor}"
HorizontalAlignment="Stretch"
Margin="0,0,0,0"/>
</StackPanel> </StackPanel>
</Border> </Border>
</Grid> </Grid>
@@ -1511,7 +1515,7 @@
<StackPanel Margin="5"> <StackPanel Margin="5">
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8"> Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,8">
Step 3 Modify install.wim Step 3 - Modify install.wim
</TextBlock> </TextBlock>
<TextBlock FontSize="{DynamicResource FontSize}" <TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}" Foreground="{DynamicResource MainForegroundColor}"
@@ -1537,74 +1541,90 @@
Style="{StaticResource BorderStyle}" Style="{StaticResource BorderStyle}"
Visibility="Collapsed"> Visibility="Collapsed">
<StackPanel Margin="5"> <StackPanel Margin="5">
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold" <!-- Header row: title + Clean & Reset button -->
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,12"> <Grid Margin="0,0,0,12">
Step 4 — Output: What would you like to do with the modified ISO?
</TextBlock>
<Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontSize="{DynamicResource FontSize}" FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}"
VerticalAlignment="Center">
Step 4 - Output: What would you like to do with the modified image?
</TextBlock>
<Button Grid.Column="1"
Name="WPFWin11ISOCleanResetButton"
Content="🗑 Clean &amp; Reset"
Foreground="OrangeRed"
Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"
ToolTip="Delete the temporary working directory and reset the interface back to Step 1"
Margin="12,0,0,0"/>
</Grid>
<!-- ── Choice prompt buttons ── -->
<Grid Margin="0,0,0,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0"
<!-- Option 1: Export to ISO --> Name="WPFWin11ISOChooseISOButton"
<Border Grid.Column="0" Style="{StaticResource BorderStyle}"> Content="Save as an ISO File"
<StackPanel> HorizontalAlignment="Stretch"
<Button Name="WPFWin11ISOExportButton" Width="Auto" Padding="12,0"
Content="1 — Export to ISO" Height="{DynamicResource ButtonHeight}"/>
HorizontalAlignment="Stretch" <Button Grid.Column="2"
Width="Auto" Padding="12,0" Name="WPFWin11ISOChooseUSBButton"
Height="{DynamicResource ButtonHeight}" Content="Write Directly to a USB Drive (erases drive)"
Margin="0,0,0,10"/> Foreground="OrangeRed"
<TextBlock FontSize="{DynamicResource FontSize}" HorizontalAlignment="Stretch"
Foreground="{DynamicResource MainForegroundColor}" Width="Auto" Padding="12,0"
TextWrapping="Wrap"> Height="{DynamicResource ButtonHeight}"/>
Save the modified content as a new bootable ISO file.
You can store it, use it in a virtual machine, or later
burn it to USB with any tool of your choice.
</TextBlock>
</StackPanel>
</Border>
<!-- Option 2: Erase & Write to USB -->
<Border Grid.Column="1" Style="{StaticResource BorderStyle}">
<StackPanel>
<!-- USB drive selector row -->
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
Name="WPFWin11ISOUSBDriveComboBox"
Foreground="{DynamicResource MainForegroundColor}"
Background="{DynamicResource MainBackgroundColor}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<Button Grid.Column="1"
Name="WPFWin11ISORefreshUSBButton"
Content="↻ Refresh"
Width="Auto" Padding="8,0"
Height="{DynamicResource ButtonHeight}"/>
</Grid>
<Button Name="WPFWin11ISOWriteUSBButton"
Content="2 — Erase &amp; Write to USB"
Foreground="OrangeRed"
HorizontalAlignment="Stretch"
Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"
Margin="0,0,0,10"/>
<TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}"
TextWrapping="Wrap">
<Run FontWeight="Bold" Foreground="OrangeRed">!! All data on the selected USB drive will be erased !!</Run>
<LineBreak/>
Select a removable USB drive above, then click the button
to write the modified Windows 11 installation directly to it.
</TextBlock>
</StackPanel>
</Border>
</Grid> </Grid>
<!-- ── USB write sub-panel (revealed on USB choice) ── -->
<Border Name="WPFWin11ISOOptionUSB"
Style="{StaticResource BorderStyle}"
Visibility="Collapsed"
Margin="0,8,0,0">
<StackPanel>
<TextBlock FontSize="{DynamicResource FontSize}"
Foreground="{DynamicResource MainForegroundColor}"
TextWrapping="Wrap" Margin="0,0,0,8">
<Run FontWeight="Bold" Foreground="OrangeRed">!! All data on the selected USB drive will be permanently erased !!</Run>
<LineBreak/>
Select a removable USB drive below, then click Erase &amp; Write.
</TextBlock>
<!-- USB drive selector row -->
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0"
Name="WPFWin11ISOUSBDriveComboBox"
Foreground="{DynamicResource MainForegroundColor}"
Background="{DynamicResource MainBackgroundColor}"
VerticalAlignment="Center"
Margin="0,0,6,0"/>
<Button Grid.Column="1"
Name="WPFWin11ISORefreshUSBButton"
Content="↻ Refresh"
Width="Auto" Padding="8,0"
Height="{DynamicResource ButtonHeight}"/>
</Grid>
<Button Name="WPFWin11ISOWriteUSBButton"
Content="Erase &amp; Write to USB"
Foreground="OrangeRed"
HorizontalAlignment="Stretch"
Width="Auto" Padding="12,0"
Height="{DynamicResource ButtonHeight}"
Margin="0,0,0,10"/>
</StackPanel>
</Border>
</StackPanel> </StackPanel>
</Border> </Border>