mirror of
https://github.com/ChrisTitusTech/winutil
synced 2026-04-06 06:38:31 +00:00
Fix OSCDIMG output and cleanup comments
This commit is contained in:
@@ -1,18 +1,12 @@
|
||||
function Write-Win11ISOLog {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Appends a timestamped message to the Win11ISO status log TextBox.
|
||||
.PARAMETER Message
|
||||
The message to append.
|
||||
#>
|
||||
param([string]$Message)
|
||||
$timestamp = (Get-Date).ToString("HH:mm:ss")
|
||||
$ts = (Get-Date).ToString("HH:mm:ss")
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$current = $sync["WPFWin11ISOStatusLog"].Text
|
||||
if ($current -eq "Ready. Please select a Windows 11 ISO to begin.") {
|
||||
$sync["WPFWin11ISOStatusLog"].Text = "[$timestamp] $Message"
|
||||
$sync["WPFWin11ISOStatusLog"].Text = "[$ts] $Message"
|
||||
} else {
|
||||
$sync["WPFWin11ISOStatusLog"].Text += "`n[$timestamp] $Message"
|
||||
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $Message"
|
||||
}
|
||||
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
||||
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
||||
@@ -20,52 +14,34 @@ function Write-Win11ISOLog {
|
||||
}
|
||||
|
||||
function Invoke-WinUtilISOBrowse {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Opens an OpenFileDialog so the user can choose a Windows 11 ISO file.
|
||||
Populates WPFWin11ISOPath and reveals the Mount & Verify section (Step 2).
|
||||
#>
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
$dlg = [System.Windows.Forms.OpenFileDialog]::new()
|
||||
$dlg.Title = "Select Windows 11 ISO"
|
||||
$dlg.Filter = "ISO files (*.iso)|*.iso|All files (*.*)|*.*"
|
||||
$dlg.Title = "Select Windows 11 ISO"
|
||||
$dlg.Filter = "ISO files (*.iso)|*.iso|All files (*.*)|*.*"
|
||||
$dlg.InitialDirectory = [System.Environment]::GetFolderPath("Desktop")
|
||||
|
||||
if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
|
||||
|
||||
$isoPath = $dlg.FileName
|
||||
|
||||
# ── Basic size sanity-check (a Win11 ISO is typically > 4 GB) ──
|
||||
$isoPath = $dlg.FileName
|
||||
$fileSizeGB = [math]::Round((Get-Item $isoPath).Length / 1GB, 2)
|
||||
|
||||
$sync["WPFWin11ISOPath"].Text = $isoPath
|
||||
$sync["WPFWin11ISOFileInfo"].Text = "File size: $fileSizeGB GB"
|
||||
$sync["WPFWin11ISOPath"].Text = $isoPath
|
||||
$sync["WPFWin11ISOFileInfo"].Text = "File size: $fileSizeGB GB"
|
||||
$sync["WPFWin11ISOFileInfo"].Visibility = "Visible"
|
||||
|
||||
# Reveal Step 2
|
||||
$sync["WPFWin11ISOMountSection"].Visibility = "Visible"
|
||||
|
||||
# Collapse all later steps whenever a new ISO is chosen
|
||||
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOMountSection"].Visibility = "Visible"
|
||||
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
|
||||
|
||||
Write-Win11ISOLog "ISO selected: $isoPath ($fileSizeGB GB)"
|
||||
}
|
||||
|
||||
function Invoke-WinUtilISOMountAndVerify {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Mounts the selected ISO, verifies it is a valid Windows 11 image,
|
||||
and populates the edition list. Reveals Step 3 on success.
|
||||
#>
|
||||
$isoPath = $sync["WPFWin11ISOPath"].Text
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($isoPath) -or $isoPath -eq "No ISO selected...") {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"Please select an ISO file first.",
|
||||
"No ISO Selected", "OK", "Warning")
|
||||
[System.Windows.MessageBox]::Show("Please select an ISO file first.", "No ISO Selected", "OK", "Warning")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,14 +49,12 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10
|
||||
|
||||
try {
|
||||
# Mount the ISO
|
||||
$diskImage = Mount-DiskImage -ImagePath $isoPath -PassThru -ErrorAction Stop
|
||||
$diskImage = Mount-DiskImage -ImagePath $isoPath -PassThru -ErrorAction Stop
|
||||
$driveLetter = ($diskImage | Get-Volume).DriveLetter + ":"
|
||||
Write-Win11ISOLog "Mounted at drive $driveLetter"
|
||||
|
||||
Set-WinUtilProgressBar -Label "Verifying ISO contents..." -Percent 30
|
||||
|
||||
# ── Verify install.wim / install.esd presence ──
|
||||
$wimPath = Join-Path $driveLetter "sources\install.wim"
|
||||
$esdPath = Join-Path $driveLetter "sources\install.esd"
|
||||
|
||||
@@ -96,14 +70,10 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
|
||||
$activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath }
|
||||
|
||||
# ── Read edition / architecture info ──
|
||||
Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55
|
||||
|
||||
$imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName
|
||||
|
||||
# ── Verify at least one Win11 edition is present ──
|
||||
$isWin11 = $imageInfo | Where-Object { $_.ImageName -match "Windows 11" }
|
||||
if (-not $isWin11) {
|
||||
if (-not ($imageInfo | Where-Object { $_.ImageName -match "Windows 11" })) {
|
||||
Dismount-DiskImage -ImagePath $isoPath | Out-Null
|
||||
Write-Win11ISOLog "ERROR: No 'Windows 11' edition found in the image."
|
||||
[System.Windows.MessageBox]::Show(
|
||||
@@ -113,10 +83,8 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
return
|
||||
}
|
||||
|
||||
# Store edition info for later index lookup
|
||||
$sync["Win11ISOImageInfo"] = $imageInfo
|
||||
|
||||
# ── Populate UI ──
|
||||
$sync["WPFWin11ISOMountDriveLetter"].Text = "Mounted at: $driveLetter | Image file: $(Split-Path $activeWim -Leaf)"
|
||||
$sync["WPFWin11ISOEditionComboBox"].Dispatcher.Invoke([action]{
|
||||
$sync["WPFWin11ISOEditionComboBox"].Items.Clear()
|
||||
@@ -124,12 +92,10 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
[void]$sync["WPFWin11ISOEditionComboBox"].Items.Add("$($img.ImageIndex): $($img.ImageName)")
|
||||
}
|
||||
if ($sync["WPFWin11ISOEditionComboBox"].Items.Count -gt 0) {
|
||||
# Default to Windows 11 Pro; fall back to first item if not found
|
||||
$proIndex = -1
|
||||
for ($i = 0; $i -lt $sync["WPFWin11ISOEditionComboBox"].Items.Count; $i++) {
|
||||
if ($sync["WPFWin11ISOEditionComboBox"].Items[$i] -match "Windows 11 Pro(?![\w ])") {
|
||||
$proIndex = $i
|
||||
break
|
||||
$proIndex = $i; break
|
||||
}
|
||||
}
|
||||
$sync["WPFWin11ISOEditionComboBox"].SelectedIndex = if ($proIndex -ge 0) { $proIndex } else { 0 }
|
||||
@@ -137,43 +103,28 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
})
|
||||
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Visible"
|
||||
|
||||
# Store for later steps
|
||||
$sync["Win11ISODriveLetter"] = $driveLetter
|
||||
$sync["Win11ISOWimPath"] = $activeWim
|
||||
$sync["Win11ISOImagePath"] = $isoPath
|
||||
|
||||
# Reveal Step 3
|
||||
$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: $($imageInfo.Count)"
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
Write-Win11ISOLog "ERROR during mount/verify: $_"
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"An error occurred while mounting or verifying the ISO:`n`n$_",
|
||||
"Error", "OK", "Error")
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
Set-WinUtilProgressBar -Label "" -Percent 0
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-WinUtilISOModify {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extracts ISO contents to a temp working directory, modifies install.wim,
|
||||
then repackages the image. Reveals Step 4 (output options) on success.
|
||||
|
||||
.NOTES
|
||||
This function runs inside a PowerShell runspace so the UI stays responsive.
|
||||
Placeholder modification logic is provided; extend as needed.
|
||||
#>
|
||||
|
||||
$isoPath = $sync["Win11ISOImagePath"]
|
||||
$driveLetter= $sync["Win11ISODriveLetter"]
|
||||
$wimPath = $sync["Win11ISOWimPath"]
|
||||
$isoPath = $sync["Win11ISOImagePath"]
|
||||
$driveLetter = $sync["Win11ISODriveLetter"]
|
||||
$wimPath = $sync["Win11ISOWimPath"]
|
||||
|
||||
if (-not $isoPath) {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
@@ -182,9 +133,8 @@ function Invoke-WinUtilISOModify {
|
||||
return
|
||||
}
|
||||
|
||||
# ── Resolve selected edition index from the ComboBox ──
|
||||
$selectedItem = $sync["WPFWin11ISOEditionComboBox"].SelectedItem
|
||||
$selectedWimIndex = 1 # default fallback
|
||||
$selectedItem = $sync["WPFWin11ISOEditionComboBox"].SelectedItem
|
||||
$selectedWimIndex = 1
|
||||
if ($selectedItem -and $selectedItem -match '^(\d+):') {
|
||||
$selectedWimIndex = [int]$Matches[1]
|
||||
} elseif ($sync["Win11ISOImageInfo"]) {
|
||||
@@ -193,13 +143,10 @@ function Invoke-WinUtilISOModify {
|
||||
$selectedEditionName = if ($selectedItem) { ($selectedItem -replace '^\d+:\s*', '') } else { "Unknown" }
|
||||
Write-Win11ISOLog "Selected edition: $selectedEditionName (Index $selectedWimIndex)"
|
||||
|
||||
# Disable the modify button to prevent double-click
|
||||
$sync["WPFWin11ISOModifyButton"].IsEnabled = $false
|
||||
|
||||
$existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PSIsContainer } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
Where-Object { $_.PSIsContainer } | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
|
||||
$workDir = if ($existingWorkDir) {
|
||||
Write-Win11ISOLog "Reusing existing temp directory: $($existingWorkDir.FullName)"
|
||||
@@ -208,9 +155,6 @@ function Invoke-WinUtilISOModify {
|
||||
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 {
|
||||
@@ -218,7 +162,6 @@ function Invoke-WinUtilISOModify {
|
||||
if (Test-Path $toolsXml) { Get-Content $toolsXml -Raw } else { "" }
|
||||
}
|
||||
|
||||
# ── Run modification in a background runspace ──
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
$runspace.ApartmentState = "STA"
|
||||
$runspace.ThreadOptions = "ReuseThread"
|
||||
@@ -235,24 +178,16 @@ function Invoke-WinUtilISOModify {
|
||||
$runspace.SessionStateProxy.SetVariable("autounattendContent", $autounattendContent)
|
||||
$runspace.SessionStateProxy.SetVariable("injectDrivers", $injectDrivers)
|
||||
|
||||
# Serialize functions so they are available inside the runspace
|
||||
$isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + `
|
||||
${function:Invoke-WinUtilISOScript}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("isoScriptFuncDef", $isoScriptFuncDef)
|
||||
|
||||
$win11ISOLogFuncDef = "function Write-Win11ISOLog {`n" + `
|
||||
${function:Write-Win11ISOLog}.ToString() + "`n}"
|
||||
$isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + ${function:Invoke-WinUtilISOScript}.ToString() + "`n}"
|
||||
$win11ISOLogFuncDef = "function Write-Win11ISOLog {`n" + ${function:Write-Win11ISOLog}.ToString() + "`n}"
|
||||
$refreshUSBFuncDef = "function Invoke-WinUtilISORefreshUSBDrives {`n" + ${function:Invoke-WinUtilISORefreshUSBDrives}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("isoScriptFuncDef", $isoScriptFuncDef)
|
||||
$runspace.SessionStateProxy.SetVariable("win11ISOLogFuncDef", $win11ISOLogFuncDef)
|
||||
|
||||
$refreshUSBFuncDef = "function Invoke-WinUtilISORefreshUSBDrives {`n" + `
|
||||
${function:Invoke-WinUtilISORefreshUSBDrives}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("refreshUSBFuncDef", $refreshUSBFuncDef)
|
||||
$runspace.SessionStateProxy.SetVariable("refreshUSBFuncDef", $refreshUSBFuncDef)
|
||||
|
||||
$script = [Management.Automation.PowerShell]::Create()
|
||||
$script.Runspace = $runspace
|
||||
$script.AddScript({
|
||||
|
||||
# Import helper functions into this runspace
|
||||
. ([scriptblock]::Create($isoScriptFuncDef))
|
||||
. ([scriptblock]::Create($win11ISOLogFuncDef))
|
||||
. ([scriptblock]::Create($refreshUSBFuncDef))
|
||||
@@ -276,15 +211,13 @@ function Invoke-WinUtilISOModify {
|
||||
}
|
||||
|
||||
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"
|
||||
$sync["WPFWin11ISOSelectSection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
|
||||
$expandedHeight = [Math]::Max(400, $sync["Form"].ActualHeight - 100)
|
||||
$sync["WPFWin11ISOStatusLog"].Height = $expandedHeight
|
||||
$sync["Win11ISOLogExpanded"] = $true
|
||||
# Register the resize handler once so the log tracks window resizes
|
||||
if (-not $sync["Win11ISOResizeHandlerAdded"]) {
|
||||
$sync["Form"].add_SizeChanged({
|
||||
if ($sync["Win11ISOLogExpanded"]) {
|
||||
@@ -297,146 +230,101 @@ function Invoke-WinUtilISOModify {
|
||||
}
|
||||
})
|
||||
|
||||
# ── 1. Create working directory structure ──
|
||||
Log "Creating working directory: $workDir"
|
||||
$isoContents = Join-Path $workDir "iso_contents"
|
||||
$mountDir = Join-Path $workDir "wim_mount"
|
||||
$mountDir = Join-Path $workDir "wim_mount"
|
||||
New-Item -ItemType Directory -Path $isoContents, $mountDir -Force | Out-Null
|
||||
SetProgress "Copying ISO contents..." 10
|
||||
|
||||
# ── 2. Copy all ISO contents to the working directory ──
|
||||
Log "Copying ISO contents from $driveLetter to $isoContents..."
|
||||
$robocopyArgs = @($driveLetter, $isoContents, "/E", "/NFL", "/NDL", "/NJH", "/NJS")
|
||||
& robocopy @robocopyArgs | Out-Null
|
||||
& robocopy $driveLetter $isoContents /E /NFL /NDL /NJH /NJS | Out-Null
|
||||
Log "ISO contents copied."
|
||||
SetProgress "Mounting install.wim..." 25
|
||||
|
||||
# ── 3. Copy install.wim to working dir (it may be read-only on the DVD) ──
|
||||
$localWim = Join-Path $isoContents "sources\install.wim"
|
||||
if (-not (Test-Path $localWim)) {
|
||||
# ESD path
|
||||
$localWim = Join-Path $isoContents "sources\install.esd"
|
||||
}
|
||||
# Ensure the file is writable
|
||||
if (-not (Test-Path $localWim)) { $localWim = Join-Path $isoContents "sources\install.esd" }
|
||||
Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false
|
||||
|
||||
# ── 4. Mount the selected edition of install.wim ──
|
||||
Log "Mounting install.wim (Index ${selectedWimIndex}: $selectedEditionName) at $mountDir..."
|
||||
Mount-WindowsImage -ImagePath $localWim -Index $selectedWimIndex -Path $mountDir -ErrorAction Stop | Out-Null
|
||||
SetProgress "Modifying install.wim..." 45
|
||||
|
||||
# ── Apply all WinUtil modifications via Invoke-WinUtilISOScript ──
|
||||
Log "Applying WinUtil modifications to install.wim..."
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -InjectCurrentSystemDrivers $injectDrivers -Log { param($m) Log $m }
|
||||
|
||||
# ── 4b. DISM component store cleanup ──
|
||||
# /ResetBase removes all superseded component versions from WinSxS,
|
||||
# which is the single largest space saving possible (typically 300–800 MB).
|
||||
# This must be done while the image is still mounted.
|
||||
SetProgress "Cleaning up component store (WinSxS)..." 56
|
||||
Log "Running DISM component store cleanup (/ResetBase)..."
|
||||
& dism /English "/image:$mountDir" /Cleanup-Image /StartComponentCleanup /ResetBase | ForEach-Object { Log $_ }
|
||||
Log "Component store cleanup complete."
|
||||
|
||||
# ── 5. Save and dismount the WIM ──
|
||||
SetProgress "Saving modified install.wim..." 65
|
||||
Log "Dismounting and saving install.wim. This will take several minutes..."
|
||||
Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null
|
||||
Log "install.wim saved."
|
||||
|
||||
# ── 5b. Strip unused editions — export only the selected index ──
|
||||
# A standard multi-edition install.wim can be 4–5 GB; exporting a
|
||||
# single index typically drops it to ~3 GB, saving 1–2 GB in the ISO.
|
||||
SetProgress "Removing unused editions from install.wim..." 70
|
||||
Log "Exporting edition '$selectedEditionName' (Index $selectedWimIndex) to a single-edition install.wim..."
|
||||
$exportWim = Join-Path $isoContents "sources\install_export.wim"
|
||||
Export-WindowsImage `
|
||||
-SourceImagePath $localWim `
|
||||
-SourceIndex $selectedWimIndex `
|
||||
-DestinationImagePath $exportWim `
|
||||
-ErrorAction Stop | Out-Null
|
||||
Export-WindowsImage -SourceImagePath $localWim -SourceIndex $selectedWimIndex -DestinationImagePath $exportWim -ErrorAction Stop | Out-Null
|
||||
Remove-Item -Path $localWim -Force
|
||||
Rename-Item -Path $exportWim -NewName "install.wim" -Force
|
||||
# Update local path so later steps (e.g. ISO build) reference the new file
|
||||
$localWim = Join-Path $isoContents "sources\install.wim"
|
||||
Log "Unused editions removed. install.wim now contains only '$selectedEditionName'."
|
||||
Log "Unused editions removed. install.wim now contains only '$selectedEditionName'."
|
||||
|
||||
SetProgress "Dismounting source ISO..." 80
|
||||
|
||||
# ── 6. Dismount the original ISO ──
|
||||
Log "Dismounting original ISO..."
|
||||
Dismount-DiskImage -ImagePath $isoPath | Out-Null
|
||||
|
||||
# Store work directory for output steps
|
||||
$sync["Win11ISOWorkDir"] = $workDir
|
||||
$sync["Win11ISOContentsDir"] = $isoContents
|
||||
$sync["Win11ISOWorkDir"] = $workDir
|
||||
$sync["Win11ISOContentsDir"] = $isoContents
|
||||
|
||||
SetProgress "Modification complete ✔" 100
|
||||
Log "install.wim modification complete. Choose an output option in Step 4."
|
||||
SetProgress "Modification complete" 100
|
||||
Log "install.wim modification complete. Choose an output option in Step 4."
|
||||
|
||||
# ── 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"].Visibility = "Visible"
|
||||
$sync["WPFWin11ISOStatusLog"].Height = 300
|
||||
})
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
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 }
|
||||
$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: $_"
|
||||
}
|
||||
} 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: $_"
|
||||
}
|
||||
} 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: $_"
|
||||
}
|
||||
} catch { Log "Warning: could not remove temp directory during cleanup: $_" }
|
||||
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"An error occurred during install.wim modification:`n`n$_",
|
||||
"Modification Error", "OK", "Error")
|
||||
})
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync.ProgressBar.Value = 0
|
||||
$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"
|
||||
@@ -452,43 +340,25 @@ function Invoke-WinUtilISOModify {
|
||||
}
|
||||
|
||||
function Invoke-WinUtilISOCheckExistingWork {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Called when the Win11ISO tab is opened. Checks for a pre-existing
|
||||
WinUtil_Win11ISO temp directory and, if found, restores the working-
|
||||
directory state so the user can proceed directly to Step 4 (output
|
||||
options) without repeating the modification.
|
||||
#>
|
||||
|
||||
# If state is already loaded (e.g. user just switched tabs mid-session)
|
||||
# do nothing so we don't overwrite in-progress work.
|
||||
if ($sync["Win11ISOContentsDir"] -and (Test-Path $sync["Win11ISOContentsDir"])) {
|
||||
return
|
||||
}
|
||||
if ($sync["Win11ISOContentsDir"] -and (Test-Path $sync["Win11ISOContentsDir"])) { return }
|
||||
|
||||
$existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PSIsContainer } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
Where-Object { $_.PSIsContainer } | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
|
||||
if (-not $existingWorkDir) { return }
|
||||
|
||||
$isoContents = Join-Path $existingWorkDir.FullName "iso_contents"
|
||||
if (-not (Test-Path $isoContents)) { return }
|
||||
|
||||
# Restore state
|
||||
$sync["Win11ISOWorkDir"] = $existingWorkDir.FullName
|
||||
$sync["Win11ISOContentsDir"] = $isoContents
|
||||
|
||||
# Show Step 4 and collapse steps 1-3 (modification already happened)
|
||||
$sync["WPFWin11ISOSelectSection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
|
||||
$sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
|
||||
$sync["WPFWin11ISOStatusLog"].Height = 300
|
||||
$sync["WPFWin11ISOStatusLog"].Height = 300
|
||||
|
||||
# Notify via the status log
|
||||
$dirName = $existingWorkDir.Name
|
||||
$modified = $existingWorkDir.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
|
||||
Write-Win11ISOLog "Existing working directory found: $($existingWorkDir.FullName)"
|
||||
Write-Win11ISOLog "Last modified: $modified - Skipping Steps 1-3 and resuming at Step 4."
|
||||
@@ -500,14 +370,6 @@ function Invoke-WinUtilISOCheckExistingWork {
|
||||
}
|
||||
|
||||
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).
|
||||
Deletion runs in a background runspace so the UI stays responsive and
|
||||
progress is reported to the user.
|
||||
#>
|
||||
|
||||
$workDir = $sync["Win11ISOWorkDir"]
|
||||
|
||||
if ($workDir -and (Test-Path $workDir)) {
|
||||
@@ -517,7 +379,6 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
if ($confirm -ne "Yes") { return }
|
||||
}
|
||||
|
||||
# Disable button so it cannot be clicked twice
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false
|
||||
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
@@ -540,6 +401,7 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
})
|
||||
Add-Content -Path (Join-Path $workDir "WinUtil_Win11ISO.log") -Value "[$ts] $msg" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
@@ -549,7 +411,6 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
}
|
||||
|
||||
try {
|
||||
# ── Dismount any WIM images still mounted under workDir ──────────────
|
||||
if ($workDir) {
|
||||
$mountDir = Join-Path $workDir "wim_mount"
|
||||
try {
|
||||
@@ -562,22 +423,15 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
Dismount-WindowsImage -Path $img.Path -Discard -ErrorAction Stop | Out-Null
|
||||
Log "WIM dismounted successfully."
|
||||
}
|
||||
} else {
|
||||
# Fallback: run dism /Cleanup-Wim in case metadata is corrupt
|
||||
# and Get-WindowsImage cannot enumerate the stuck mount
|
||||
if (Test-Path $mountDir) {
|
||||
Log "No mounted WIM reported by Get-WindowsImage, running DISM /Cleanup-Wim as a precaution..."
|
||||
SetProgress "Running DISM cleanup..." 3
|
||||
& dism /English /Cleanup-Wim 2>&1 | ForEach-Object { Log $_ }
|
||||
}
|
||||
} elseif (Test-Path $mountDir) {
|
||||
Log "No mounted WIM reported by Get-WindowsImage, running DISM /Cleanup-Wim as a precaution..."
|
||||
SetProgress "Running DISM cleanup..." 3
|
||||
& dism /English /Cleanup-Wim 2>&1 | ForEach-Object { Log $_ }
|
||||
}
|
||||
} catch {
|
||||
Log "Warning: could not dismount WIM cleanly, attempting DISM /Cleanup-Wim fallback: $_"
|
||||
try {
|
||||
& dism /English /Cleanup-Wim 2>&1 | ForEach-Object { Log $_ }
|
||||
} catch {
|
||||
Log "Warning: DISM /Cleanup-Wim also failed: $_"
|
||||
}
|
||||
try { & dism /English /Cleanup-Wim 2>&1 | ForEach-Object { Log $_ } }
|
||||
catch { Log "Warning: DISM /Cleanup-Wim also failed: $_" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,41 +439,28 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
Log "Scanning files to delete in: $workDir"
|
||||
SetProgress "Scanning files..." 5
|
||||
|
||||
$allItems = @(Get-ChildItem -Path $workDir -Recurse -Force -ErrorAction SilentlyContinue)
|
||||
$total = $allItems.Count
|
||||
$deleted = 0
|
||||
$allFiles = @(Get-ChildItem -Path $workDir -File -Recurse -Force -ErrorAction SilentlyContinue)
|
||||
$allDirs = @(Get-ChildItem -Path $workDir -Directory -Recurse -Force -ErrorAction SilentlyContinue |
|
||||
Sort-Object { $_.FullName.Length } -Descending)
|
||||
$total = $allFiles.Count
|
||||
$deleted = 0
|
||||
|
||||
Log "Found $total items to delete."
|
||||
Log "Found $total files to delete."
|
||||
|
||||
# Delete files first, then directories (deepest first)
|
||||
$files = $allItems | Where-Object { -not $_.PSIsContainer }
|
||||
$dirs = $allItems | Where-Object { $_.PSIsContainer } |
|
||||
Sort-Object { $_.FullName.Length } -Descending
|
||||
|
||||
foreach ($f in $files) {
|
||||
Log "Deleting file: $($f.FullName)"
|
||||
try { Remove-Item -Path $f.FullName -Force -ErrorAction Stop } catch { Log " WARNING: could not delete file: $_" }
|
||||
foreach ($f in $allFiles) {
|
||||
try { Remove-Item -Path $f.FullName -Force -ErrorAction Stop } catch { Log "WARNING: could not delete $($f.FullName): $_" }
|
||||
$deleted++
|
||||
if ($deleted % 100 -eq 0 -or $deleted -eq $files.Count) {
|
||||
$pct = [math]::Round(($deleted / [Math]::Max($total,1)) * 85) + 5
|
||||
SetProgress "Deleting files... ($deleted / $total)" $pct
|
||||
Log "Progress: $deleted of $total items processed."
|
||||
if ($deleted % 100 -eq 0 -or $deleted -eq $total) {
|
||||
$pct = [math]::Round(($deleted / [Math]::Max($total, 1)) * 85) + 5
|
||||
SetProgress "Deleting files in $($f.Directory.Name)... ($deleted / $total)" $pct
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($d in $dirs) {
|
||||
Log "Removing directory: $($d.FullName)"
|
||||
try { Remove-Item -Path $d.FullName -Force -Recurse -ErrorAction Stop } catch { Log " WARNING: could not remove directory: $_" }
|
||||
$deleted++
|
||||
if ($deleted % 50 -eq 0 -or $deleted -eq $total) {
|
||||
$pct = [math]::Round(($deleted / [Math]::Max($total,1)) * 85) + 5
|
||||
SetProgress "Removing directories... ($deleted / $total)" $pct
|
||||
Log "Progress: $deleted of $total items processed."
|
||||
}
|
||||
foreach ($d in $allDirs) {
|
||||
try { Remove-Item -Path $d.FullName -Force -ErrorAction SilentlyContinue } catch {}
|
||||
}
|
||||
|
||||
# Remove the root work directory itself
|
||||
try { Remove-Item -Path $workDir -Force -Recurse -ErrorAction Stop } catch {}
|
||||
try { Remove-Item -Path $workDir -Recurse -Force -ErrorAction Stop } catch {}
|
||||
|
||||
if (Test-Path $workDir) {
|
||||
Log "WARNING: some items could not be deleted in $workDir"
|
||||
@@ -633,9 +474,7 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
SetProgress "Resetting UI..." 95
|
||||
Log "Resetting interface..."
|
||||
|
||||
# ── Full UI reset on the dispatcher thread ──────────────────────
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
# Clear stored state
|
||||
$sync["Win11ISOWorkDir"] = $null
|
||||
$sync["Win11ISOContentsDir"] = $null
|
||||
$sync["Win11ISOImagePath"] = $null
|
||||
@@ -644,17 +483,16 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
$sync["Win11ISOImageInfo"] = $null
|
||||
$sync["Win11ISOUSBDisks"] = $null
|
||||
|
||||
# Reset UI elements
|
||||
$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["WPFWin11ISOModifyButton"].IsEnabled = $true
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
|
||||
$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["WPFWin11ISOModifyButton"].IsEnabled = $true
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
|
||||
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
@@ -663,8 +501,7 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
$sync["WPFWin11ISOStatusLog"].Height = 140
|
||||
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
|
||||
})
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
Log "ERROR during Clean & Reset: $_"
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
@@ -679,17 +516,11 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
}
|
||||
|
||||
function Invoke-WinUtilISOExport {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Saves the modified ISO contents as a new bootable ISO file.
|
||||
Uses oscdimg.exe (part of the Windows ADK) if present; falls back
|
||||
to a reminder message if not installed.
|
||||
#>
|
||||
$contentsDir = $sync["Win11ISOContentsDir"]
|
||||
|
||||
if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"No modified ISO content found. Please complete Steps 1–3 first.",
|
||||
"No modified ISO content found. Please complete Steps 1-3 first.",
|
||||
"Not Ready", "OK", "Warning")
|
||||
return
|
||||
}
|
||||
@@ -705,9 +536,6 @@ function Invoke-WinUtilISOExport {
|
||||
if ($dlg.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) { return }
|
||||
|
||||
$outputISO = $dlg.FileName
|
||||
Write-Win11ISOLog "Exporting to ISO: $outputISO"
|
||||
|
||||
Set-WinUtilProgressBar -Label "Building ISO..." -Percent 10
|
||||
|
||||
# Locate oscdimg.exe (Windows ADK or winget per-user install)
|
||||
$oscdimg = Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue |
|
||||
@@ -719,13 +547,11 @@ function Invoke-WinUtilISOExport {
|
||||
}
|
||||
|
||||
if (-not $oscdimg) {
|
||||
Write-Win11ISOLog "oscdimg.exe not found. Attempting to install via winget..."
|
||||
Set-WinUtilProgressBar -Label "Installing oscdimg..." -Percent 5
|
||||
Write-Win11ISOLog "oscdimg.exe not found. Attempting to install via winget..."
|
||||
try {
|
||||
$winget = Get-Command winget -ErrorAction Stop
|
||||
$result = & $winget install -e --id Microsoft.OSCDIMG --accept-package-agreements --accept-source-agreements 2>&1
|
||||
Write-Win11ISOLog "winget output: $result"
|
||||
# Re-scan after install
|
||||
$oscdimg = Get-ChildItem "$env:LOCALAPPDATA\Microsoft\WinGet\Packages" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -match 'Microsoft\.OSCDIMG' } |
|
||||
Select-Object -First 1 -ExpandProperty FullName
|
||||
@@ -734,7 +560,6 @@ function Invoke-WinUtilISOExport {
|
||||
}
|
||||
|
||||
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",
|
||||
@@ -744,65 +569,116 @@ function Invoke-WinUtilISOExport {
|
||||
Write-Win11ISOLog "oscdimg.exe installed successfully."
|
||||
}
|
||||
|
||||
# Build boot parameters (BIOS + UEFI dual-boot)
|
||||
$bootData = "2#p0,e,b`"$contentsDir\boot\etfsboot.com`"#pEF,e,b`"$contentsDir\efi\microsoft\boot\efisys.bin`""
|
||||
$oscdimgArgs = @(
|
||||
"-m", # ignore source path max size
|
||||
"-o", # optimise storage
|
||||
"-u2", # UDF 2.01
|
||||
"-udfver102",
|
||||
"-bootdata:$bootData",
|
||||
"-l`"CTOS_MODIFIED`"",
|
||||
"`"$contentsDir`"",
|
||||
"`"$outputISO`""
|
||||
)
|
||||
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $false
|
||||
|
||||
try {
|
||||
Write-Win11ISOLog "Running oscdimg..."
|
||||
$psi = [System.Diagnostics.ProcessStartInfo]::new()
|
||||
$psi.FileName = $oscdimg
|
||||
$psi.Arguments = $oscdimgArgs -join " "
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
$runspace.ApartmentState = "STA"
|
||||
$runspace.ThreadOptions = "ReuseThread"
|
||||
$runspace.Open()
|
||||
$runspace.SessionStateProxy.SetVariable("sync", $sync)
|
||||
$runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
|
||||
$runspace.SessionStateProxy.SetVariable("outputISO", $outputISO)
|
||||
$runspace.SessionStateProxy.SetVariable("oscdimg", $oscdimg)
|
||||
|
||||
$proc = [System.Diagnostics.Process]::new()
|
||||
$proc.StartInfo = $psi
|
||||
$proc.Start() | Out-Null
|
||||
$win11ISOLogFuncDef = "function Write-Win11ISOLog {`n" + ${function:Write-Win11ISOLog}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("win11ISOLogFuncDef", $win11ISOLogFuncDef)
|
||||
|
||||
# Stream stdout and stderr line-by-line to the status log
|
||||
$stdoutTask = $proc.StandardOutput.ReadToEndAsync()
|
||||
$stderrTask = $proc.StandardError.ReadToEndAsync()
|
||||
$proc.WaitForExit()
|
||||
[System.Threading.Tasks.Task]::WaitAll($stdoutTask, $stderrTask)
|
||||
$script = [Management.Automation.PowerShell]::Create()
|
||||
$script.Runspace = $runspace
|
||||
$script.AddScript({
|
||||
. ([scriptblock]::Create($win11ISOLogFuncDef))
|
||||
|
||||
foreach ($line in ($stdoutTask.Result -split "`r?`n")) {
|
||||
if ($line.Trim()) { Write-Win11ISOLog $line }
|
||||
}
|
||||
foreach ($line in ($stderrTask.Result -split "`r?`n")) {
|
||||
if ($line.Trim()) { Write-Win11ISOLog "[stderr]$line" }
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
if ($proc.ExitCode -eq 0) {
|
||||
Set-WinUtilProgressBar -Label "ISO exported" -Percent 100
|
||||
Write-Win11ISOLog "ISO exported successfully: $outputISO"
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"ISO exported successfully!`n`n$outputISO",
|
||||
"Export Complete", "OK", "Info")
|
||||
} else {
|
||||
Write-Win11ISOLog "oscdimg exited with code $($proc.ExitCode)."
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"oscdimg exited with code $($proc.ExitCode).`nCheck the status log for details.",
|
||||
"Export Error", "OK", "Error")
|
||||
# Expand the log to fill the screen while oscdimg runs
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$expandedHeight = [Math]::Max(400, $sync["Form"].ActualHeight - 100)
|
||||
$sync["WPFWin11ISOStatusLog"].Height = $expandedHeight
|
||||
$sync["Win11ISOLogExpanded"] = $true
|
||||
if (-not $sync["Win11ISOResizeHandlerAdded"]) {
|
||||
$sync["Form"].add_SizeChanged({
|
||||
if ($sync["Win11ISOLogExpanded"]) {
|
||||
$sync["WPFWin11ISOStatusLog"].Height = [Math]::Max(400, $sync["Form"].ActualHeight - 100)
|
||||
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
||||
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
||||
}
|
||||
})
|
||||
$sync["Win11ISOResizeHandlerAdded"] = $true
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
Write-Win11ISOLog "Exporting to ISO: $outputISO"
|
||||
SetProgress "Building ISO..." 10
|
||||
|
||||
$bootData = "2#p0,e,b`"$contentsDir\boot\etfsboot.com`"#pEF,e,b`"$contentsDir\efi\microsoft\boot\efisys.bin`""
|
||||
$oscdimgArgs = @("-m", "-o", "-u2", "-udfver102", "-bootdata:$bootData", "-l`"CTOS_MODIFIED`"", "`"$contentsDir`"", "`"$outputISO`"")
|
||||
|
||||
Write-Win11ISOLog "Running oscdimg..."
|
||||
|
||||
$psi = [System.Diagnostics.ProcessStartInfo]::new()
|
||||
$psi.FileName = $oscdimg
|
||||
$psi.Arguments = $oscdimgArgs -join " "
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
|
||||
$proc = [System.Diagnostics.Process]::new()
|
||||
$proc.StartInfo = $psi
|
||||
$proc.Start() | Out-Null
|
||||
|
||||
# Stream stdout line-by-line as oscdimg runs
|
||||
while (-not $proc.StandardOutput.EndOfStream) {
|
||||
$line = $proc.StandardOutput.ReadLine()
|
||||
if ($line.Trim()) { Write-Win11ISOLog $line }
|
||||
}
|
||||
|
||||
$proc.WaitForExit()
|
||||
|
||||
# Flush any stderr after process exits
|
||||
$stderr = $proc.StandardError.ReadToEnd()
|
||||
foreach ($line in ($stderr -split "`r?`n")) {
|
||||
if ($line.Trim()) { Write-Win11ISOLog "[stderr] $line" }
|
||||
}
|
||||
|
||||
if ($proc.ExitCode -eq 0) {
|
||||
SetProgress "ISO exported" 100
|
||||
Write-Win11ISOLog "ISO exported successfully: $outputISO"
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
[System.Windows.MessageBox]::Show("ISO exported successfully!`n`n$outputISO", "Export Complete", "OK", "Info")
|
||||
})
|
||||
} else {
|
||||
Write-Win11ISOLog "oscdimg exited with code $($proc.ExitCode)."
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"oscdimg exited with code $($proc.ExitCode).`nCheck the status log for details.",
|
||||
"Export Error", "OK", "Error")
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
Write-Win11ISOLog "ERROR during ISO export: $_"
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
[System.Windows.MessageBox]::Show("ISO export failed:`n`n$_", "Error", "OK", "Error")
|
||||
})
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["Win11ISOLogExpanded"] = $false
|
||||
$sync["WPFWin11ISOStatusLog"].Height = 300
|
||||
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $true
|
||||
})
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Win11ISOLog "ERROR during ISO export: $_"
|
||||
[System.Windows.MessageBox]::Show("ISO export failed:`n`n$_","Error","OK","Error")
|
||||
}
|
||||
finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
Set-WinUtilProgressBar -Label "" -Percent 0
|
||||
}
|
||||
}) | Out-Null
|
||||
|
||||
$script.BeginInvoke() | Out-Null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user