mirror of
https://github.com/ChrisTitusTech/winutil
synced 2026-04-06 06:38:31 +00:00
Compare commits
11 Commits
nullsafe-i
...
d0012449ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0012449ad | ||
|
|
18594f6d2d | ||
|
|
50c4398394 | ||
|
|
ed5ec52767 | ||
|
|
319ee4e555 | ||
|
|
2ef7f2deb9 | ||
|
|
cca9bee107 | ||
|
|
669ecd9c64 | ||
|
|
64ea075727 | ||
|
|
773ea3a950 | ||
|
|
410d3c5056 |
@@ -261,6 +261,7 @@ function Invoke-WinUtilISOModify {
|
|||||||
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
||||||
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
||||||
})
|
})
|
||||||
|
Add-Content -Path (Join-Path $workDir "WinUtil_Win11ISO.log") -Value "[$ts] $msg" -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetProgress($label, $pct) {
|
function SetProgress($label, $pct) {
|
||||||
@@ -376,6 +377,7 @@ function Invoke-WinUtilISOModify {
|
|||||||
# the UI thread here.
|
# the UI thread here.
|
||||||
$sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{
|
$sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{
|
||||||
$sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
|
$sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Height = 300
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -446,11 +448,61 @@ function Invoke-WinUtilISOModify {
|
|||||||
$script.BeginInvoke() | Out-Null
|
$script.BeginInvoke() | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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."
|
||||||
|
Write-Win11ISOLog "Click 'Clean & Reset' if you want to start over with a new ISO."
|
||||||
|
|
||||||
|
[System.Windows.MessageBox]::Show(
|
||||||
|
"A previous WinUtil ISO working directory was found:`n`n$($existingWorkDir.FullName)`n`n(Last modified: $modified)`n`nStep 4 (output options) has been restored so you can save the already-modified image.`n`nClick 'Clean & Reset' in Step 4 if you want to start over.",
|
||||||
|
"Existing Work Found", "OK", "Info")
|
||||||
|
}
|
||||||
|
|
||||||
function Invoke-WinUtilISOCleanAndReset {
|
function Invoke-WinUtilISOCleanAndReset {
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Deletes the temporary working directory created during ISO modification
|
Deletes the temporary working directory created during ISO modification
|
||||||
and resets the entire ISO UI back to its initial state (Step 1 only).
|
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"]
|
$workDir = $sync["Win11ISOWorkDir"]
|
||||||
@@ -460,37 +512,133 @@ function Invoke-WinUtilISOCleanAndReset {
|
|||||||
"This will delete the temporary working directory:`n`n$workDir`n`nAnd reset the interface back to the start.`n`nContinue?",
|
"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")
|
"Clean & Reset", "YesNo", "Warning")
|
||||||
if ($confirm -ne "Yes") { return }
|
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
|
# Disable button so it cannot be clicked twice
|
||||||
$sync["Win11ISOWorkDir"] = $null
|
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false
|
||||||
$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
|
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||||
$sync["WPFWin11ISOPath"].Text = "No ISO selected..."
|
$runspace.ApartmentState = "STA"
|
||||||
$sync["WPFWin11ISOFileInfo"].Visibility = "Collapsed"
|
$runspace.ThreadOptions = "ReuseThread"
|
||||||
$sync["WPFWin11ISOVerifyResultPanel"].Visibility = "Collapsed"
|
$runspace.Open()
|
||||||
$sync["WPFWin11ISOOptionUSB"].Visibility = "Collapsed"
|
$runspace.SessionStateProxy.SetVariable("sync", $sync)
|
||||||
$sync["WPFWin11ISOOutputSection"].Visibility = "Collapsed"
|
$runspace.SessionStateProxy.SetVariable("workDir", $workDir)
|
||||||
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
|
|
||||||
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
|
$script = [Management.Automation.PowerShell]::Create()
|
||||||
$sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
|
$script.Runspace = $runspace
|
||||||
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
|
$script.AddScript({
|
||||||
$sync["WPFWin11ISOStatusLog"].Height = 140
|
|
||||||
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true
|
function Log($msg) {
|
||||||
|
$ts = (Get-Date).ToString("HH:mm:ss")
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
|
||||||
|
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
||||||
|
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
||||||
|
})
|
||||||
|
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
|
||||||
|
$sync.progressBarTextBlock.ToolTip = $label
|
||||||
|
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($workDir -and (Test-Path $workDir)) {
|
||||||
|
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
|
||||||
|
|
||||||
|
Log "Found $total items 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) {
|
||||||
|
try { Remove-Item -Path $f.FullName -Force -ErrorAction Stop } catch {}
|
||||||
|
$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 "Deleting files... $deleted of $total"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($d in $dirs) {
|
||||||
|
try { Remove-Item -Path $d.FullName -Force -Recurse -ErrorAction Stop } catch {}
|
||||||
|
$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 "Removing directories... $deleted of $total"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove the root work directory itself
|
||||||
|
try { Remove-Item -Path $workDir -Force -Recurse -ErrorAction Stop } catch {}
|
||||||
|
|
||||||
|
if (Test-Path $workDir) {
|
||||||
|
Log "WARNING: some items could not be deleted in $workDir"
|
||||||
|
} else {
|
||||||
|
Log "Temp directory deleted successfully."
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log "No temp directory found — resetting UI."
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
$sync["Win11ISODriveLetter"] = $null
|
||||||
|
$sync["Win11ISOWimPath"] = $null
|
||||||
|
$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.progressBarTextBlock.Text = ""
|
||||||
|
$sync.progressBarTextBlock.ToolTip = ""
|
||||||
|
$sync.ProgressBar.Value = 0
|
||||||
|
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Height = 140
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Log "ERROR during Clean & Reset: $_"
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
$sync.progressBarTextBlock.Text = ""
|
||||||
|
$sync.progressBarTextBlock.ToolTip = ""
|
||||||
|
$sync.ProgressBar.Value = 0
|
||||||
|
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}) | Out-Null
|
||||||
|
|
||||||
|
$script.BeginInvoke() | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-WinUtilISOExport {
|
function Invoke-WinUtilISOExport {
|
||||||
@@ -600,7 +748,7 @@ function Invoke-WinUtilISOExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($proc.ExitCode -eq 0) {
|
if ($proc.ExitCode -eq 0) {
|
||||||
Set-WinUtilProgressBar -Label "ISO exported ✔" -Percent 100
|
Set-WinUtilProgressBar -Label "ISO exported" -Percent 100
|
||||||
Write-Win11ISOLog "ISO exported successfully: $outputISO"
|
Write-Win11ISOLog "ISO exported successfully: $outputISO"
|
||||||
[System.Windows.MessageBox]::Show(
|
[System.Windows.MessageBox]::Show(
|
||||||
"ISO exported successfully!`n`n$outputISO",
|
"ISO exported successfully!`n`n$outputISO",
|
||||||
@@ -622,197 +770,3 @@ function Invoke-WinUtilISOExport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-WinUtilISORefreshUSBDrives {
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Populates the USB drive ComboBox with all currently attached removable drives.
|
|
||||||
#>
|
|
||||||
$combo = $sync["WPFWin11ISOUSBDriveComboBox"]
|
|
||||||
$combo.Items.Clear()
|
|
||||||
|
|
||||||
$removable = Get-Disk | Where-Object { $_.BusType -eq "USB" } | Sort-Object Number
|
|
||||||
|
|
||||||
if ($removable.Count -eq 0) {
|
|
||||||
$combo.Items.Add("No USB drives detected")
|
|
||||||
$combo.SelectedIndex = 0
|
|
||||||
Write-Win11ISOLog "No USB drives detected."
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($disk in $removable) {
|
|
||||||
$sizeGB = [math]::Round($disk.Size / 1GB, 1)
|
|
||||||
$label = "Disk $($disk.Number): $($disk.FriendlyName) [$sizeGB GB] — $($disk.PartitionStyle)"
|
|
||||||
$combo.Items.Add($label)
|
|
||||||
}
|
|
||||||
$combo.SelectedIndex = 0
|
|
||||||
Write-Win11ISOLog "Found $($removable.Count) USB drive(s)."
|
|
||||||
|
|
||||||
# Store disk objects for later use
|
|
||||||
$sync["Win11ISOUSBDisks"] = $removable
|
|
||||||
}
|
|
||||||
|
|
||||||
function Invoke-WinUtilISOWriteUSB {
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Erases the selected USB drive and writes the modified Windows 11 ISO
|
|
||||||
content as a bootable installation drive (using DISM / robocopy approach).
|
|
||||||
#>
|
|
||||||
$contentsDir = $sync["Win11ISOContentsDir"]
|
|
||||||
$usbDisks = $sync["Win11ISOUSBDisks"]
|
|
||||||
|
|
||||||
if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
|
|
||||||
[System.Windows.MessageBox]::Show(
|
|
||||||
"No modified ISO content found. Please complete Steps 1–3 first.",
|
|
||||||
"Not Ready", "OK", "Warning")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$selectedIndex = $sync["WPFWin11ISOUSBDriveComboBox"].SelectedIndex
|
|
||||||
if ($selectedIndex -lt 0 -or -not $usbDisks -or $selectedIndex -ge $usbDisks.Count) {
|
|
||||||
[System.Windows.MessageBox]::Show(
|
|
||||||
"Please select a USB drive from the dropdown.",
|
|
||||||
"No Drive Selected", "OK", "Warning")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$targetDisk = $usbDisks[$selectedIndex]
|
|
||||||
$diskNum = $targetDisk.Number
|
|
||||||
$sizeGB = [math]::Round($targetDisk.Size / 1GB, 1)
|
|
||||||
|
|
||||||
$confirm = [System.Windows.MessageBox]::Show(
|
|
||||||
"ALL data on Disk $diskNum ($($targetDisk.FriendlyName), $sizeGB GB) will be PERMANENTLY ERASED.`n`nAre you sure you want to continue?",
|
|
||||||
"Confirm USB Erase", "YesNo", "Warning")
|
|
||||||
|
|
||||||
if ($confirm -ne "Yes") {
|
|
||||||
Write-Win11ISOLog "USB write cancelled by user."
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
|
|
||||||
Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
|
|
||||||
|
|
||||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
|
||||||
$runspace.ApartmentState = "STA"
|
|
||||||
$runspace.ThreadOptions = "ReuseThread"
|
|
||||||
$runspace.Open()
|
|
||||||
$runspace.SessionStateProxy.SetVariable("sync", $sync)
|
|
||||||
$runspace.SessionStateProxy.SetVariable("diskNum", $diskNum)
|
|
||||||
$runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
|
|
||||||
|
|
||||||
$script = [Management.Automation.PowerShell]::Create()
|
|
||||||
$script.Runspace = $runspace
|
|
||||||
$script.AddScript({
|
|
||||||
|
|
||||||
function Log($msg) {
|
|
||||||
$ts = (Get-Date).ToString("HH:mm:ss")
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
|
|
||||||
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
|
||||||
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function SetProgress($label, $pct) {
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
|
||||||
$sync.progressBarTextBlock.Text = $label
|
|
||||||
$sync.progressBarTextBlock.ToolTip = $label
|
|
||||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
SetProgress "Formatting USB drive..." 10
|
|
||||||
|
|
||||||
# ── Diskpart script: clean, GPT, create ESP + data partitions ──
|
|
||||||
$dpScript = @"
|
|
||||||
select disk $diskNum
|
|
||||||
clean
|
|
||||||
convert gpt
|
|
||||||
create partition efi size=512
|
|
||||||
format quick fs=fat32 label="SYSTEM"
|
|
||||||
assign
|
|
||||||
create partition primary
|
|
||||||
format quick fs=fat32 label="WINPE"
|
|
||||||
assign
|
|
||||||
exit
|
|
||||||
"@
|
|
||||||
$dpFile = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
|
|
||||||
$dpScript | Set-Content -Path $dpFile -Encoding ASCII
|
|
||||||
Log "Running diskpart on Disk $diskNum..."
|
|
||||||
diskpart /s $dpFile | Out-Null
|
|
||||||
Remove-Item $dpFile -Force
|
|
||||||
|
|
||||||
SetProgress "Identifying USB partitions..." 30
|
|
||||||
Start-Sleep -Seconds 3 # let Windows assign drive letters
|
|
||||||
|
|
||||||
# Find newly assigned drive letter for the data partition
|
|
||||||
$usbVol = Get-Partition -DiskNumber $diskNum |
|
|
||||||
Where-Object { $_.Type -eq "Basic" } |
|
|
||||||
Get-Volume |
|
|
||||||
Where-Object { $_.FileSystemLabel -eq "WINPE" } |
|
|
||||||
Select-Object -First 1
|
|
||||||
|
|
||||||
if (-not $usbVol) {
|
|
||||||
throw "Could not locate the formatted USB data partition. Drive letter may not have been assigned automatically."
|
|
||||||
}
|
|
||||||
|
|
||||||
$usbDrive = "$($usbVol.DriveLetter):"
|
|
||||||
Log "USB data partition: $usbDrive"
|
|
||||||
SetProgress "Copying Windows 11 files to USB..." 45
|
|
||||||
|
|
||||||
# ── Copy files (split large install.wim if > 4 GB for FAT32) ──
|
|
||||||
$installWim = Join-Path $contentsDir "sources\install.wim"
|
|
||||||
if (Test-Path $installWim) {
|
|
||||||
$wimSizeMB = [math]::Round((Get-Item $installWim).Length / 1MB)
|
|
||||||
if ($wimSizeMB -gt 3800) {
|
|
||||||
# FAT32 limit – split with DISM
|
|
||||||
Log "install.wim is $wimSizeMB MB – splitting for FAT32 compatibility..."
|
|
||||||
$splitDest = Join-Path $usbDrive "sources\install.swm"
|
|
||||||
New-Item -ItemType Directory -Path (Split-Path $splitDest) -Force | Out-Null
|
|
||||||
Split-WindowsImage -ImagePath $installWim `
|
|
||||||
-SplitImagePath $splitDest `
|
|
||||||
-FileSize 3800 -CheckIntegrity | Out-Null
|
|
||||||
Log "install.wim split complete."
|
|
||||||
|
|
||||||
# Copy everything else (exclude install.wim)
|
|
||||||
$robocopyArgs = @($contentsDir, $usbDrive, "/E", "/XF", "install.wim", "/NFL", "/NDL", "/NJH", "/NJS")
|
|
||||||
& robocopy @robocopyArgs | Out-Null
|
|
||||||
} else {
|
|
||||||
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
SetProgress "Finalising USB drive..." 90
|
|
||||||
Log "Files copied to USB."
|
|
||||||
|
|
||||||
SetProgress "USB write complete ✔" 100
|
|
||||||
Log "USB drive is ready for use."
|
|
||||||
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
|
||||||
[System.Windows.MessageBox]::Show(
|
|
||||||
"USB drive created successfully!`n`nYou can now boot from this drive to install Windows 11.",
|
|
||||||
"USB Ready", "OK", "Info")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Log "ERROR during USB write: $_"
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
|
||||||
[System.Windows.MessageBox]::Show(
|
|
||||||
"USB write failed:`n`n$_",
|
|
||||||
"USB Write Error", "OK", "Error")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Start-Sleep -Milliseconds 800
|
|
||||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
|
||||||
$sync.progressBarTextBlock.Text = ""
|
|
||||||
$sync.progressBarTextBlock.ToolTip = ""
|
|
||||||
$sync.ProgressBar.Value = 0
|
|
||||||
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}) | Out-Null
|
|
||||||
|
|
||||||
$script.BeginInvoke() | Out-Null
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,30 +4,14 @@ function Invoke-WinUtilISOScript {
|
|||||||
Applies WinUtil modifications to a mounted Windows 11 install.wim image.
|
Applies WinUtil modifications to a mounted Windows 11 install.wim image.
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Performs the following operations against an already-mounted WIM image:
|
Removes AppX bloatware and OneDrive, injects hardware drivers (NVMe, Precision
|
||||||
|
Touchpad/HID, and network) exported from the running system, optionally injects
|
||||||
1. Removes provisioned AppX bloatware packages via DISM.
|
extended Storage & Network drivers from the ChrisTitusTech/storage-lan-drivers
|
||||||
2. Removes OneDriveSetup.exe from the system image.
|
repository (requires git, installed via winget if absent), applies offline registry
|
||||||
3. Loads offline registry hives (COMPONENTS, DEFAULT, NTUSER, SOFTWARE, SYSTEM)
|
tweaks (hardware bypass, privacy, OOBE, telemetry, update suppression), deletes
|
||||||
and applies the following tweaks:
|
CEIP/WU scheduled-task definition files, and optionally drops autounattend.xml and
|
||||||
- Bypasses hardware requirement checks (CPU, RAM, SecureBoot, Storage, TPM).
|
removes the support\ folder from the ISO contents directory.
|
||||||
- Disables sponsored-app delivery and ContentDeliveryManager features.
|
Mounting/dismounting the WIM is the caller's responsibility (e.g. Invoke-WinUtilISO).
|
||||||
- Enables local-account OOBE path (BypassNRO).
|
|
||||||
- Writes autounattend.xml to the Sysprep directory inside the WIM and,
|
|
||||||
optionally, to the ISO/USB root so Windows Setup picks it up at boot.
|
|
||||||
- Disables reserved storage.
|
|
||||||
- Disables BitLocker device encryption.
|
|
||||||
- Hides the Chat (Teams) taskbar icon.
|
|
||||||
- Disables OneDrive folder backup (KFM).
|
|
||||||
- Disables telemetry, advertising ID, and input personalization.
|
|
||||||
- Blocks post-install delivery of DevHome, Outlook, and Teams.
|
|
||||||
- Disables Windows Copilot.
|
|
||||||
- Disables Windows Update during OOBE.
|
|
||||||
4. Deletes unwanted scheduled-task XML definition files (CEIP, Appraiser, etc.).
|
|
||||||
5. Removes the support\ folder from the ISO contents directory (if supplied).
|
|
||||||
|
|
||||||
Mounting and dismounting the WIM is the responsibility of the caller
|
|
||||||
(e.g. Invoke-WinUtilISO).
|
|
||||||
|
|
||||||
.PARAMETER ScratchDir
|
.PARAMETER ScratchDir
|
||||||
Mandatory. Full path to the directory where the Windows image is currently mounted.
|
Mandatory. Full path to the directory where the Windows image is currently mounted.
|
||||||
@@ -62,15 +46,11 @@ function Invoke-WinUtilISOScript {
|
|||||||
.NOTES
|
.NOTES
|
||||||
Author : Chris Titus @christitustech
|
Author : Chris Titus @christitustech
|
||||||
GitHub : https://github.com/ChrisTitusTech
|
GitHub : https://github.com/ChrisTitusTech
|
||||||
Version : 26.02.22
|
Version : 26.02.25b
|
||||||
#>
|
#>
|
||||||
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 = "",
|
[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 = "",
|
[string]$AutoUnattendXml = "",
|
||||||
[scriptblock]$Log = { param($m) Write-Output $m }
|
[scriptblock]$Log = { param($m) Write-Output $m }
|
||||||
)
|
)
|
||||||
@@ -100,6 +80,69 @@ function Invoke-WinUtilISOScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Copies driver package folders (one per exported .inf) whose online class
|
||||||
|
# matches any of the supplied class names into a new temp staging directory.
|
||||||
|
# Returns the staging directory path — caller must delete it when done.
|
||||||
|
# Using a staging dir lets DISM inject all drivers in a single /Recurse
|
||||||
|
# call instead of one DISM process launch per .inf file.
|
||||||
|
function New-DriverStagingDir {
|
||||||
|
param ([string]$ExportRoot, [string[]]$Classes)
|
||||||
|
$stagingDir = Join-Path $env:TEMP "WinUtil_DriverStage_$(Get-Random)"
|
||||||
|
New-Item -Path $stagingDir -ItemType Directory -Force | Out-Null
|
||||||
|
Get-WindowsDriver -Online |
|
||||||
|
Where-Object { $_.ClassName -in $Classes } |
|
||||||
|
ForEach-Object { [IO.Path]::GetFileNameWithoutExtension($_.OriginalFileName) } |
|
||||||
|
Select-Object -Unique |
|
||||||
|
ForEach-Object {
|
||||||
|
Get-ChildItem -Path $ExportRoot -Filter "$_.inf" -Recurse -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -ExpandProperty DirectoryName -Unique |
|
||||||
|
ForEach-Object {
|
||||||
|
# Each exported driver lives in its own sub-folder;
|
||||||
|
# copy that folder (with its binary files) into staging.
|
||||||
|
$dest = Join-Path $stagingDir (Split-Path $_ -Leaf)
|
||||||
|
Copy-Item -Path $_ -Destination $dest -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $stagingDir
|
||||||
|
}
|
||||||
|
|
||||||
|
# Injects all drivers from $DriverDir into a DISM-mounted image in one call.
|
||||||
|
function Add-DriversToImage {
|
||||||
|
param (
|
||||||
|
[string]$MountPath,
|
||||||
|
[string]$DriverDir,
|
||||||
|
[string]$Label = "image",
|
||||||
|
[scriptblock]$Logger
|
||||||
|
)
|
||||||
|
& dism /English "/image:$MountPath" /Add-Driver "/Driver:$DriverDir" /Recurse 2>&1 |
|
||||||
|
ForEach-Object { & $Logger " dism[$Label]: $_" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mounts boot.wim index 2, injects all drivers from $DriverDir, saves, dismounts.
|
||||||
|
function Invoke-BootWimInject {
|
||||||
|
param (
|
||||||
|
[string]$BootWimPath,
|
||||||
|
[string]$DriverDir,
|
||||||
|
[scriptblock]$Logger
|
||||||
|
)
|
||||||
|
Set-ItemProperty -Path $BootWimPath -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue
|
||||||
|
$mountDir = Join-Path $env:TEMP "WinUtil_BootMount_$(Get-Random)"
|
||||||
|
New-Item -Path $mountDir -ItemType Directory -Force | Out-Null
|
||||||
|
try {
|
||||||
|
& $Logger "Mounting boot.wim (index 2 — Windows Setup) for driver injection..."
|
||||||
|
Mount-WindowsImage -ImagePath $BootWimPath -Index 2 -Path $mountDir -ErrorAction Stop | Out-Null
|
||||||
|
Add-DriversToImage -MountPath $mountDir -DriverDir $DriverDir -Label "boot" -Logger $Logger
|
||||||
|
& $Logger "Saving boot.wim..."
|
||||||
|
Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null
|
||||||
|
& $Logger "boot.wim driver injection complete."
|
||||||
|
} catch {
|
||||||
|
& $Logger "Warning: boot.wim driver injection failed: $_"
|
||||||
|
try { Dismount-WindowsImage -Path $mountDir -Discard -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Path $mountDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
# 1. Remove provisioned AppX packages
|
# 1. Remove provisioned AppX packages
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
@@ -164,7 +207,108 @@ function Invoke-WinUtilISOScript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
# 2. Remove OneDrive
|
# 2. Inject hardware drivers (NVMe / Trackpad / Network)
|
||||||
|
# Injected into BOTH install.wim (OS) AND boot.wim index 2 (Setup).
|
||||||
|
# Without storage drivers in boot.wim, Windows Setup cannot see the
|
||||||
|
# target disk on systems with unsupported NVMe / SATA controllers.
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
|
& $Log "Exporting hardware drivers from running system (NVMe, HID/Trackpad, Network)..."
|
||||||
|
|
||||||
|
$driverExportRoot = Join-Path $env:TEMP "WinUtil_DriverExport_$(Get-Random)"
|
||||||
|
New-Item -Path $driverExportRoot -ItemType Directory -Force | Out-Null
|
||||||
|
|
||||||
|
try {
|
||||||
|
Export-WindowsDriver -Online -Destination $driverExportRoot | Out-Null
|
||||||
|
|
||||||
|
# Stage matching driver folders then do a single DISM /Recurse call.
|
||||||
|
# install.wim: SCSIAdapter + HIDClass + Net
|
||||||
|
$installStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','HIDClass','Net')
|
||||||
|
& $Log "Injecting staged drivers into install.wim (single DISM call)..."
|
||||||
|
Add-DriversToImage -MountPath $ScratchDir -DriverDir $installStage -Label "install" -Logger $Log
|
||||||
|
Remove-Item -Path $installStage -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
& $Log "install.wim driver injection complete."
|
||||||
|
|
||||||
|
# boot.wim: SCSIAdapter + Net only (HID not needed in WinPE)
|
||||||
|
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||||
|
$bootWim = Join-Path $ISOContentsDir "sources\boot.wim"
|
||||||
|
if (Test-Path $bootWim) {
|
||||||
|
$bootStage = New-DriverStagingDir -ExportRoot $driverExportRoot -Classes @('SCSIAdapter','Net')
|
||||||
|
& $Log "Injecting staged drivers into boot.wim (single DISM call)..."
|
||||||
|
Invoke-BootWimInject -BootWimPath $bootWim -DriverDir $bootStage -Logger $Log
|
||||||
|
Remove-Item -Path $bootStage -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
} else {
|
||||||
|
& $Log "Warning: boot.wim not found — skipping boot.wim driver injection."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
& $Log "Error during driver export/injection: $_"
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Path $driverExportRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── 2c. Optional: extended Storage & Network drivers from community repo ──
|
||||||
|
$extDriverChoice = [System.Windows.MessageBox]::Show(
|
||||||
|
"Would you like to add extended Storage and Network drivers?`n`n" +
|
||||||
|
"This installs EVERY Storage and Networking device driver " +
|
||||||
|
"in EXISTANCE into the image. (~1000 drivers)`n`n" +
|
||||||
|
"No Wireless drivers only Ethernet, use for stubborn systems " +
|
||||||
|
"with unsupported NVMe or Ethernet controllers.",
|
||||||
|
"Extended Drivers", "YesNo", "Question")
|
||||||
|
|
||||||
|
if ($extDriverChoice -eq 'Yes') {
|
||||||
|
& $Log "Extended driver injection requested."
|
||||||
|
|
||||||
|
# Ensure git is available
|
||||||
|
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||||
|
if (-not $gitCmd) {
|
||||||
|
& $Log "Git not found — installing via winget..."
|
||||||
|
winget install --id Git.Git -e --source winget `
|
||||||
|
--accept-package-agreements --accept-source-agreements | Out-Null
|
||||||
|
# Refresh PATH so git is visible in this session
|
||||||
|
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine') + ';' +
|
||||||
|
[System.Environment]::GetEnvironmentVariable('PATH', 'User')
|
||||||
|
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $gitCmd) {
|
||||||
|
& $Log "Warning: git could not be found after install attempt — skipping extended drivers."
|
||||||
|
} else {
|
||||||
|
$extRepoDir = Join-Path $env:TEMP "WinUtil_ExtDrivers_$(Get-Random)"
|
||||||
|
try {
|
||||||
|
& $Log "Cloning storage-lan-drivers repository..."
|
||||||
|
& git clone --depth 1 `
|
||||||
|
"https://github.com/ChrisTitusTech/storage-lan-drivers" `
|
||||||
|
$extRepoDir 2>&1 | ForEach-Object { & $Log " git: $_" }
|
||||||
|
|
||||||
|
if (Test-Path $extRepoDir) {
|
||||||
|
& $Log "Injecting extended drivers into install.wim (this may take several minutes)..."
|
||||||
|
Add-DriversToImage -MountPath $ScratchDir -DriverDir $extRepoDir -Label "install" -Logger $Log
|
||||||
|
& $Log "Extended driver injection into install.wim complete."
|
||||||
|
|
||||||
|
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||||
|
$bootWimExt = Join-Path $ISOContentsDir "sources\boot.wim"
|
||||||
|
if (Test-Path $bootWimExt) {
|
||||||
|
& $Log "Injecting extended drivers into boot.wim..."
|
||||||
|
Invoke-BootWimInject -BootWimPath $bootWimExt -DriverDir $extRepoDir -Logger $Log
|
||||||
|
} else {
|
||||||
|
& $Log "Warning: boot.wim not found — skipping extended driver injection into boot.wim."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
& $Log "Warning: repository clone directory not found — skipping extended drivers."
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
& $Log "Error during extended driver injection: $_"
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Path $extRepoDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
& $Log "Extended driver injection skipped."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
|
# 3. Remove OneDrive
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
& $Log "Removing OneDrive..."
|
& $Log "Removing OneDrive..."
|
||||||
& takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null
|
& takeown /f "$ScratchDir\Windows\System32\OneDriveSetup.exe" | Out-Null
|
||||||
@@ -172,7 +316,7 @@ function Invoke-WinUtilISOScript {
|
|||||||
Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue
|
Remove-Item -Path "$ScratchDir\Windows\System32\OneDriveSetup.exe" -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
# 3. Registry tweaks
|
# 4. Registry tweaks
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
& $Log "Loading offline registry hives..."
|
& $Log "Loading offline registry hives..."
|
||||||
reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
|
reg load HKLM\zCOMPONENTS "$ScratchDir\Windows\System32\config\COMPONENTS"
|
||||||
@@ -305,7 +449,7 @@ function Invoke-WinUtilISOScript {
|
|||||||
reg unload HKLM\zSYSTEM
|
reg unload HKLM\zSYSTEM
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
# 4. Delete scheduled task definition files
|
# 5. Delete scheduled task definition files
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
& $Log "Deleting scheduled task definition files..."
|
& $Log "Deleting scheduled task definition files..."
|
||||||
$tasksPath = "$ScratchDir\Windows\System32\Tasks"
|
$tasksPath = "$ScratchDir\Windows\System32\Tasks"
|
||||||
@@ -325,7 +469,7 @@ function Invoke-WinUtilISOScript {
|
|||||||
& $Log "Scheduled task files deleted."
|
& $Log "Scheduled task files deleted."
|
||||||
|
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
# 5. Remove ISO support folder (fresh-install only; not needed)
|
# 6. Remove ISO support folder (fresh-install only; not needed)
|
||||||
# ═════════════════════════════════════════════════════════════════════════
|
# ═════════════════════════════════════════════════════════════════════════
|
||||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||||
& $Log "Removing ISO support\ folder..."
|
& $Log "Removing ISO support\ folder..."
|
||||||
|
|||||||
251
functions/private/Invoke-WinUtilISOUSB.ps1
Normal file
251
functions/private/Invoke-WinUtilISOUSB.ps1
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
function Invoke-WinUtilISORefreshUSBDrives {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Populates the USB drive ComboBox with all currently attached removable drives.
|
||||||
|
#>
|
||||||
|
$combo = $sync["WPFWin11ISOUSBDriveComboBox"]
|
||||||
|
$combo.Items.Clear()
|
||||||
|
|
||||||
|
$removable = Get-Disk | Where-Object { $_.BusType -eq "USB" } | Sort-Object Number
|
||||||
|
|
||||||
|
if ($removable.Count -eq 0) {
|
||||||
|
$combo.Items.Add("No USB drives detected")
|
||||||
|
$combo.SelectedIndex = 0
|
||||||
|
Write-Win11ISOLog "No USB drives detected."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($disk in $removable) {
|
||||||
|
$sizeGB = [math]::Round($disk.Size / 1GB, 1)
|
||||||
|
$label = "Disk $($disk.Number): $($disk.FriendlyName) [$sizeGB GB] - $($disk.PartitionStyle)"
|
||||||
|
$combo.Items.Add($label)
|
||||||
|
}
|
||||||
|
$combo.SelectedIndex = 0
|
||||||
|
Write-Win11ISOLog "Found $($removable.Count) USB drive(s)."
|
||||||
|
|
||||||
|
# Store disk objects for later use
|
||||||
|
$sync["Win11ISOUSBDisks"] = $removable
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-WinUtilISOWriteUSB {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Erases the selected USB drive and writes the modified Windows 11 ISO
|
||||||
|
content as a bootable installation drive (using DISM / robocopy approach).
|
||||||
|
#>
|
||||||
|
$contentsDir = $sync["Win11ISOContentsDir"]
|
||||||
|
$usbDisks = $sync["Win11ISOUSBDisks"]
|
||||||
|
|
||||||
|
if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
|
||||||
|
[System.Windows.MessageBox]::Show(
|
||||||
|
"No modified ISO content found. Please complete Steps 1-3 first.",
|
||||||
|
"Not Ready", "OK", "Warning")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedIndex = $sync["WPFWin11ISOUSBDriveComboBox"].SelectedIndex
|
||||||
|
if ($selectedIndex -lt 0 -or -not $usbDisks -or $selectedIndex -ge $usbDisks.Count) {
|
||||||
|
[System.Windows.MessageBox]::Show(
|
||||||
|
"Please select a USB drive from the dropdown.",
|
||||||
|
"No Drive Selected", "OK", "Warning")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetDisk = $usbDisks[$selectedIndex]
|
||||||
|
$diskNum = $targetDisk.Number
|
||||||
|
$sizeGB = [math]::Round($targetDisk.Size / 1GB, 1)
|
||||||
|
|
||||||
|
$confirm = [System.Windows.MessageBox]::Show(
|
||||||
|
"ALL data on Disk $diskNum ($($targetDisk.FriendlyName), $sizeGB GB) will be PERMANENTLY ERASED.`n`nAre you sure you want to continue?",
|
||||||
|
"Confirm USB Erase", "YesNo", "Warning")
|
||||||
|
|
||||||
|
if ($confirm -ne "Yes") {
|
||||||
|
Write-Win11ISOLog "USB write cancelled by user."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
|
||||||
|
Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
|
||||||
|
|
||||||
|
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||||
|
$runspace.ApartmentState = "STA"
|
||||||
|
$runspace.ThreadOptions = "ReuseThread"
|
||||||
|
$runspace.Open()
|
||||||
|
$runspace.SessionStateProxy.SetVariable("sync", $sync)
|
||||||
|
$runspace.SessionStateProxy.SetVariable("diskNum", $diskNum)
|
||||||
|
$runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
|
||||||
|
|
||||||
|
$script = [Management.Automation.PowerShell]::Create()
|
||||||
|
$script.Runspace = $runspace
|
||||||
|
$script.AddScript({
|
||||||
|
|
||||||
|
function Log($msg) {
|
||||||
|
$ts = (Get-Date).ToString("HH:mm:ss")
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
|
||||||
|
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
|
||||||
|
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function SetProgress($label, $pct) {
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
$sync.progressBarTextBlock.Text = $label
|
||||||
|
$sync.progressBarTextBlock.ToolTip = $label
|
||||||
|
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SetProgress "Formatting USB drive..." 10
|
||||||
|
|
||||||
|
# ── Helper: find a free drive letter (D-Z) ──────────────────────────
|
||||||
|
function Get-FreeDriveLetter {
|
||||||
|
$used = (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue).Name
|
||||||
|
foreach ($c in [char[]](68..90)) { # D..Z
|
||||||
|
if ($used -notcontains [string]$c) { return $c }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Phase 1: Clean the disk via diskpart ────────────────────────────
|
||||||
|
# Only run "clean" here. "convert gpt" in diskpart requires the disk to
|
||||||
|
# already be MBR; after a clean the disk is RAW, so convert gpt fails on
|
||||||
|
# many systems. We use Initialize-Disk (which accepts RAW disks) instead.
|
||||||
|
$dpScript1 = @"
|
||||||
|
select disk $diskNum
|
||||||
|
clean
|
||||||
|
exit
|
||||||
|
"@
|
||||||
|
$dpFile1 = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
|
||||||
|
$dpScript1 | Set-Content -Path $dpFile1 -Encoding ASCII
|
||||||
|
Log "Running diskpart clean on Disk $diskNum..."
|
||||||
|
$dpOut1 = diskpart /s $dpFile1 2>&1
|
||||||
|
Remove-Item $dpFile1 -Force -ErrorAction SilentlyContinue
|
||||||
|
$dpOut1 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
|
||||||
|
|
||||||
|
# ── Phase 2: Initialize as GPT via PowerShell ────────────────────────
|
||||||
|
# After "clean", Windows may still see the disk as initialized (stale
|
||||||
|
# metadata). Initialize-Disk only accepts RAW disks; Set-Disk handles
|
||||||
|
# already-initialized (MBR/GPT) disks with no partitions. Try both.
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
|
||||||
|
$diskObj = Get-Disk -Number $diskNum -ErrorAction Stop
|
||||||
|
if ($diskObj.PartitionStyle -eq 'RAW') {
|
||||||
|
Initialize-Disk -Number $diskNum -PartitionStyle GPT -ErrorAction Stop
|
||||||
|
Log "Disk $diskNum initialized as GPT."
|
||||||
|
} else {
|
||||||
|
Set-Disk -Number $diskNum -PartitionStyle GPT -ErrorAction Stop
|
||||||
|
Log "Disk $diskNum converted to GPT (was $($diskObj.PartitionStyle))."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Phase 3: Create partitions via diskpart ──────────────────────────
|
||||||
|
# "create partition efi" is not supported on removable media.
|
||||||
|
# A single FAT32 primary partition is all that is needed for a UEFI-
|
||||||
|
# bootable Windows install USB - the firmware locates \EFI\Boot\bootx64.efi
|
||||||
|
# on any FAT32 volume regardless of GPT partition type.
|
||||||
|
# FAT32 label limit is 11 chars; "W11-yyMMdd" = 10, fits without trimming.
|
||||||
|
$volLabel = "W11-" + (Get-Date).ToString('yyMMdd')
|
||||||
|
$dpScript2 = @"
|
||||||
|
select disk $diskNum
|
||||||
|
create partition primary
|
||||||
|
format quick fs=fat32 label="$volLabel"
|
||||||
|
exit
|
||||||
|
"@
|
||||||
|
$dpFile2 = Join-Path $env:TEMP "winutil_diskpart2_$(Get-Random).txt"
|
||||||
|
$dpScript2 | Set-Content -Path $dpFile2 -Encoding ASCII
|
||||||
|
Log "Creating partitions on Disk $diskNum..."
|
||||||
|
$dpOut2 = diskpart /s $dpFile2 2>&1
|
||||||
|
Remove-Item $dpFile2 -Force -ErrorAction SilentlyContinue
|
||||||
|
$dpOut2 | Where-Object { $_ -match '\S' } | ForEach-Object { Log " diskpart: $_" }
|
||||||
|
|
||||||
|
SetProgress "Assigning drive letters..." 30
|
||||||
|
Start-Sleep -Seconds 3 # allow Windows to settle after partition creation
|
||||||
|
Update-Disk -Number $diskNum -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# ── Explicitly assign drive letters via PowerShell ───────────────────
|
||||||
|
# This is reliable regardless of registry state, unlike diskpart assign.
|
||||||
|
$partitions = Get-Partition -DiskNumber $diskNum -ErrorAction Stop
|
||||||
|
Log "Partitions on Disk $diskNum after format: $($partitions.Count)"
|
||||||
|
foreach ($p in $partitions) {
|
||||||
|
Log " Partition $($p.PartitionNumber) Type=$($p.Type) Letter=$($p.DriveLetter) Size=$([math]::Round($p.Size/1MB))MB"
|
||||||
|
}
|
||||||
|
|
||||||
|
$winpePart = $partitions | Where-Object { $_.Type -eq "Basic" } | Select-Object -Last 1
|
||||||
|
|
||||||
|
if (-not $winpePart) {
|
||||||
|
throw "Could not find the WINPE (Basic) partition on Disk $diskNum after format."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove stale letter first (noops if none), then assign a fresh one
|
||||||
|
try { Remove-PartitionAccessPath -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -AccessPath "$($winpePart.DriveLetter):" -ErrorAction SilentlyContinue } catch {}
|
||||||
|
$usbLetter = Get-FreeDriveLetter
|
||||||
|
if (-not $usbLetter) { throw "No free drive letters (D-Z) available to assign to the USB data partition." }
|
||||||
|
Set-Partition -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -NewDriveLetter $usbLetter
|
||||||
|
Log "Assigned drive letter $usbLetter to WINPE partition (Partition $($winpePart.PartitionNumber))."
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
|
||||||
|
$usbDrive = "${usbLetter}:"
|
||||||
|
if (-not (Test-Path $usbDrive)) {
|
||||||
|
throw "Drive $usbDrive is not accessible after letter assignment."
|
||||||
|
}
|
||||||
|
Log "USB data partition: $usbDrive"
|
||||||
|
SetProgress "Copying Windows 11 files to USB..." 45
|
||||||
|
|
||||||
|
# ── Copy files (split large install.wim if > 4 GB for FAT32) ──
|
||||||
|
$installWim = Join-Path $contentsDir "sources\install.wim"
|
||||||
|
if (Test-Path $installWim) {
|
||||||
|
$wimSizeMB = [math]::Round((Get-Item $installWim).Length / 1MB)
|
||||||
|
if ($wimSizeMB -gt 3800) {
|
||||||
|
# FAT32 limit – split with DISM
|
||||||
|
Log "install.wim is $wimSizeMB MB - splitting for FAT32 compatibility...This will take several minutes."
|
||||||
|
$splitDest = Join-Path $usbDrive "sources\install.swm"
|
||||||
|
New-Item -ItemType Directory -Path (Split-Path $splitDest) -Force | Out-Null
|
||||||
|
Split-WindowsImage -ImagePath $installWim `
|
||||||
|
-SplitImagePath $splitDest `
|
||||||
|
-FileSize 3800 -CheckIntegrity
|
||||||
|
Log "install.wim split complete."
|
||||||
|
|
||||||
|
# Copy everything else (exclude install.wim)
|
||||||
|
Log "Copying remaining files to USB..."
|
||||||
|
$robocopyArgs = @($contentsDir, $usbDrive, "/E", "/XF", "install.wim", "/NFL", "/NDL", "/NJH", "/NJS")
|
||||||
|
& robocopy @robocopyArgs
|
||||||
|
} else {
|
||||||
|
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS
|
||||||
|
}
|
||||||
|
|
||||||
|
SetProgress "Finalising USB drive..." 90
|
||||||
|
Log "Files copied to USB."
|
||||||
|
|
||||||
|
SetProgress "USB write complete" 100
|
||||||
|
Log "USB drive is ready for use."
|
||||||
|
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
[System.Windows.MessageBox]::Show(
|
||||||
|
"USB drive created successfully!`n`nYou can now boot from this drive to install Windows 11.",
|
||||||
|
"USB Ready", "OK", "Info")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Log "ERROR during USB write: $_"
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
[System.Windows.MessageBox]::Show(
|
||||||
|
"USB write failed:`n`n$_",
|
||||||
|
"USB Write Error", "OK", "Error")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Start-Sleep -Milliseconds 800
|
||||||
|
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||||
|
$sync.progressBarTextBlock.Text = ""
|
||||||
|
$sync.progressBarTextBlock.ToolTip = ""
|
||||||
|
$sync.ProgressBar.Value = 0
|
||||||
|
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}) | Out-Null
|
||||||
|
|
||||||
|
$script.BeginInvoke() | Out-Null
|
||||||
|
}
|
||||||
@@ -534,6 +534,13 @@ $sync["FontScalingApplyButton"].Add_Click({
|
|||||||
|
|
||||||
# ── Win11ISO Tab button handlers ──────────────────────────────────────────────
|
# ── Win11ISO Tab button handlers ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
$sync["WPFTab5BT"].Add_Click({
|
||||||
|
$sync["Form"].Dispatcher.BeginInvoke(
|
||||||
|
[System.Windows.Threading.DispatcherPriority]::Background,
|
||||||
|
[action]{ Invoke-WinUtilISOCheckExistingWork }
|
||||||
|
) | Out-Null
|
||||||
|
})
|
||||||
|
|
||||||
$sync["WPFWin11ISOBrowseButton"].Add_Click({
|
$sync["WPFWin11ISOBrowseButton"].Add_Click({
|
||||||
Write-Debug "WPFWin11ISOBrowseButton clicked"
|
Write-Debug "WPFWin11ISOBrowseButton clicked"
|
||||||
Invoke-WinUtilISOBrowse
|
Invoke-WinUtilISOBrowse
|
||||||
|
|||||||
@@ -273,22 +273,46 @@
|
|||||||
<Style TargetType="ComboBox">
|
<Style TargetType="ComboBox">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" />
|
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" />
|
||||||
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" />
|
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" />
|
||||||
|
<Setter Property="MinWidth" Value="{DynamicResource ButtonWidth}" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="ComboBox">
|
<ControlTemplate TargetType="ComboBox">
|
||||||
<Grid>
|
<Grid>
|
||||||
<ToggleButton x:Name="ToggleButton"
|
<!-- Outer border gives the combo a visible box -->
|
||||||
Background="{TemplateBinding Background}"
|
<Border x:Name="OuterBorder"
|
||||||
BorderBrush="{TemplateBinding Background}"
|
BorderBrush="{DynamicResource BorderColor}"
|
||||||
BorderThickness="0"
|
BorderThickness="1"
|
||||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
CornerRadius="{DynamicResource ButtonCornerRadius}"
|
||||||
ClickMode="Press">
|
Background="{TemplateBinding Background}">
|
||||||
<TextBlock Text="{TemplateBinding SelectionBoxItem}"
|
<ToggleButton x:Name="ToggleButton"
|
||||||
Foreground="{TemplateBinding Foreground}"
|
Background="Transparent"
|
||||||
Background="Transparent"
|
BorderThickness="0"
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2"
|
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
/>
|
ClickMode="Press">
|
||||||
</ToggleButton>
|
<!-- Text + arrow laid out in a two-column Grid -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="{TemplateBinding SelectionBoxItem}"
|
||||||
|
Foreground="{TemplateBinding Foreground}"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
|
Margin="6,3,2,3"/>
|
||||||
|
<!-- Scalable vector chevron -->
|
||||||
|
<Path Grid.Column="1"
|
||||||
|
Data="M 0,0 L 8,0 L 4,5 Z"
|
||||||
|
Fill="{TemplateBinding Foreground}"
|
||||||
|
Width="8" Height="5"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Margin="4,0,6,0"/>
|
||||||
|
</Grid>
|
||||||
|
</ToggleButton>
|
||||||
|
</Border>
|
||||||
<Popup x:Name="Popup"
|
<Popup x:Name="Popup"
|
||||||
IsOpen="{TemplateBinding IsDropDownOpen}"
|
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||||
Placement="Bottom"
|
Placement="Bottom"
|
||||||
@@ -297,11 +321,11 @@
|
|||||||
PopupAnimation="Slide">
|
PopupAnimation="Slide">
|
||||||
<Border x:Name="DropDownBorder"
|
<Border x:Name="DropDownBorder"
|
||||||
Background="{TemplateBinding Background}"
|
Background="{TemplateBinding Background}"
|
||||||
BorderBrush="{TemplateBinding Foreground}"
|
BorderBrush="{DynamicResource BorderColor}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<ScrollViewer>
|
<ScrollViewer>
|
||||||
<ItemsPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2"/>
|
<ItemsPresenter HorizontalAlignment="Left" VerticalAlignment="Center" Margin="4,2"/>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Border>
|
</Border>
|
||||||
</Popup>
|
</Popup>
|
||||||
@@ -1353,8 +1377,7 @@
|
|||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- STEP 1 : Select Windows 11 ISO -->
|
<!-- STEP 1 : Select Windows 11 ISO -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<Border Grid.Row="0" Name="WPFWin11ISOSelectSection" Style="{StaticResource BorderStyle}">
|
<Grid Grid.Row="0" Name="WPFWin11ISOSelectSection" Margin="5" HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||||
<Grid Margin="5">
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
@@ -1440,17 +1463,16 @@
|
|||||||
Height="{DynamicResource ButtonHeight}"/>
|
Height="{DynamicResource ButtonHeight}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- STEP 2 : Mount & Verify ISO -->
|
<!-- STEP 2 : Mount & Verify ISO -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<Border Grid.Row="1"
|
<Grid Grid.Row="1"
|
||||||
Name="WPFWin11ISOMountSection"
|
Name="WPFWin11ISOMountSection"
|
||||||
Style="{StaticResource BorderStyle}"
|
Margin="5"
|
||||||
Visibility="Collapsed">
|
Visibility="Collapsed"
|
||||||
<Grid Margin="5">
|
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
@@ -1500,22 +1522,20 @@
|
|||||||
FontSize="{DynamicResource FontSize}"
|
FontSize="{DynamicResource FontSize}"
|
||||||
Foreground="{DynamicResource MainForegroundColor}"
|
Foreground="{DynamicResource MainForegroundColor}"
|
||||||
Background="{DynamicResource MainBackgroundColor}"
|
Background="{DynamicResource MainBackgroundColor}"
|
||||||
BorderBrush="{DynamicResource BorderColor}"
|
HorizontalAlignment="Left"
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Margin="0,0,0,0"/>
|
Margin="0,0,0,0"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- STEP 3 : Modify install.wim -->
|
<!-- STEP 3 : Modify install.wim -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<Border Grid.Row="2"
|
<StackPanel Grid.Row="2"
|
||||||
Name="WPFWin11ISOModifySection"
|
Name="WPFWin11ISOModifySection"
|
||||||
Style="{StaticResource BorderStyle}"
|
Margin="5"
|
||||||
Visibility="Collapsed">
|
Visibility="Collapsed"
|
||||||
<StackPanel Margin="5">
|
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||||
<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
|
||||||
@@ -1533,16 +1553,16 @@
|
|||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Width="Auto" Padding="12,0"
|
Width="Auto" Padding="12,0"
|
||||||
Height="{DynamicResource ButtonHeight}"/>
|
Height="{DynamicResource ButtonHeight}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- STEP 4 : Output Options -->
|
<!-- STEP 4 : Output Options -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<Border Grid.Row="3"
|
<StackPanel Grid.Row="3"
|
||||||
Name="WPFWin11ISOOutputSection"
|
Name="WPFWin11ISOOutputSection"
|
||||||
Style="{StaticResource BorderStyle}">
|
Margin="5"
|
||||||
<StackPanel Margin="5">
|
Visibility="Collapsed"
|
||||||
|
HorizontalAlignment="Left" MinWidth="{DynamicResource ButtonWidth}">
|
||||||
<!-- Header row: title + Clean & Reset button -->
|
<!-- Header row: title + Clean & Reset button -->
|
||||||
<Grid Margin="0,0,0,12">
|
<Grid Margin="0,0,0,12">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -1579,7 +1599,7 @@
|
|||||||
Height="{DynamicResource ButtonHeight}"/>
|
Height="{DynamicResource ButtonHeight}"/>
|
||||||
<Button Grid.Column="2"
|
<Button Grid.Column="2"
|
||||||
Name="WPFWin11ISOChooseUSBButton"
|
Name="WPFWin11ISOChooseUSBButton"
|
||||||
Content="Write Directly to a USB Drive (erases drive)"
|
Content="Write Directly to a USB Drive (ERASES DRIVE)"
|
||||||
Foreground="OrangeRed"
|
Foreground="OrangeRed"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Width="Auto" Padding="12,0"
|
Width="Auto" Padding="12,0"
|
||||||
@@ -1613,7 +1633,7 @@
|
|||||||
Margin="0,0,6,0"/>
|
Margin="0,0,6,0"/>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
Name="WPFWin11ISORefreshUSBButton"
|
Name="WPFWin11ISORefreshUSBButton"
|
||||||
Content="↻ Refresh"
|
Content="Refresh"
|
||||||
Width="Auto" Padding="8,0"
|
Width="Auto" Padding="8,0"
|
||||||
Height="{DynamicResource ButtonHeight}"/>
|
Height="{DynamicResource ButtonHeight}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1627,14 +1647,12 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- Status / Log Output -->
|
<!-- Status / Log Output -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<Border Grid.Row="4" Style="{StaticResource BorderStyle}">
|
<StackPanel Grid.Row="4" Margin="5">
|
||||||
<StackPanel>
|
|
||||||
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
|
<TextBlock FontSize="{DynamicResource FontSize}" FontWeight="Bold"
|
||||||
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,6">
|
Foreground="{DynamicResource MainForegroundColor}" Margin="0,0,0,6">
|
||||||
Status Log
|
Status Log
|
||||||
@@ -1650,7 +1668,6 @@
|
|||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Text="Ready. Please select a Windows 11 ISO to begin."/>
|
Text="Ready. Please select a Windows 11 ISO to begin."/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
Reference in New Issue
Block a user