Skip to content

Commit 410d3c5

Browse files
initial usb fixes
1 parent 14ad9f7 commit 410d3c5

File tree

5 files changed

+280
-199
lines changed

5 files changed

+280
-199
lines changed

.github/workflows/docs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- name: Setup Pages
4848
id: pages
4949
uses: actions/configure-pages@v5
50-
50+
5151
- name: Generate Dev Docs from JSON
5252
shell: pwsh
5353
run: |

functions/private/Invoke-WinUtilISO.ps1

Lines changed: 48 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,53 @@ function Invoke-WinUtilISOModify {
446446
$script.BeginInvoke() | Out-Null
447447
}
448448

449+
function Invoke-WinUtilISOCheckExistingWork {
450+
<#
451+
.SYNOPSIS
452+
Called when the Win11ISO tab is opened. Checks for a pre-existing
453+
WinUtil_Win11ISO temp directory and, if found, restores the working-
454+
directory state so the user can proceed directly to Step 4 (output
455+
options) without repeating the modification.
456+
#>
457+
458+
# If state is already loaded (e.g. user just switched tabs mid-session)
459+
# do nothing so we don't overwrite in-progress work.
460+
if ($sync["Win11ISOContentsDir"] -and (Test-Path $sync["Win11ISOContentsDir"])) {
461+
return
462+
}
463+
464+
$existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") -ErrorAction SilentlyContinue |
465+
Where-Object { $_.PSIsContainer } |
466+
Sort-Object LastWriteTime -Descending |
467+
Select-Object -First 1
468+
469+
if (-not $existingWorkDir) { return }
470+
471+
$isoContents = Join-Path $existingWorkDir.FullName "iso_contents"
472+
if (-not (Test-Path $isoContents)) { return }
473+
474+
# Restore state
475+
$sync["Win11ISOWorkDir"] = $existingWorkDir.FullName
476+
$sync["Win11ISOContentsDir"] = $isoContents
477+
478+
# Show Step 4 and collapse steps 1-3 (modification already happened)
479+
$sync["WPFWin11ISOSelectSection"].Visibility = "Collapsed"
480+
$sync["WPFWin11ISOMountSection"].Visibility = "Collapsed"
481+
$sync["WPFWin11ISOModifySection"].Visibility = "Collapsed"
482+
$sync["WPFWin11ISOOutputSection"].Visibility = "Visible"
483+
484+
# Notify via the status log
485+
$dirName = $existingWorkDir.Name
486+
$modified = $existingWorkDir.LastWriteTime.ToString("yyyy-MM-dd HH:mm")
487+
Write-Win11ISOLog "Existing working directory found: $($existingWorkDir.FullName)"
488+
Write-Win11ISOLog "Last modified: $modified — Skipping Steps 1-3 and resuming at Step 4."
489+
Write-Win11ISOLog "Click 'Clean & Reset' if you want to start over with a new ISO."
490+
491+
[System.Windows.MessageBox]::Show(
492+
"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.",
493+
"Existing Work Found", "OK", "Info")
494+
}
495+
449496
function Invoke-WinUtilISOCleanAndReset {
450497
<#
451498
.SYNOPSIS
@@ -600,7 +647,7 @@ function Invoke-WinUtilISOExport {
600647
}
601648

602649
if ($proc.ExitCode -eq 0) {
603-
Set-WinUtilProgressBar -Label "ISO exported" -Percent 100
650+
Set-WinUtilProgressBar -Label "ISO exported" -Percent 100
604651
Write-Win11ISOLog "ISO exported successfully: $outputISO"
605652
[System.Windows.MessageBox]::Show(
606653
"ISO exported successfully!`n`n$outputISO",
@@ -622,197 +669,3 @@ function Invoke-WinUtilISOExport {
622669
}
623670
}
624671

625-
function Invoke-WinUtilISORefreshUSBDrives {
626-
<#
627-
.SYNOPSIS
628-
Populates the USB drive ComboBox with all currently attached removable drives.
629-
#>
630-
$combo = $sync["WPFWin11ISOUSBDriveComboBox"]
631-
$combo.Items.Clear()
632-
633-
$removable = Get-Disk | Where-Object { $_.BusType -eq "USB" } | Sort-Object Number
634-
635-
if ($removable.Count -eq 0) {
636-
$combo.Items.Add("No USB drives detected")
637-
$combo.SelectedIndex = 0
638-
Write-Win11ISOLog "No USB drives detected."
639-
return
640-
}
641-
642-
foreach ($disk in $removable) {
643-
$sizeGB = [math]::Round($disk.Size / 1GB, 1)
644-
$label = "Disk $($disk.Number): $($disk.FriendlyName) [$sizeGB GB] — $($disk.PartitionStyle)"
645-
$combo.Items.Add($label)
646-
}
647-
$combo.SelectedIndex = 0
648-
Write-Win11ISOLog "Found $($removable.Count) USB drive(s)."
649-
650-
# Store disk objects for later use
651-
$sync["Win11ISOUSBDisks"] = $removable
652-
}
653-
654-
function Invoke-WinUtilISOWriteUSB {
655-
<#
656-
.SYNOPSIS
657-
Erases the selected USB drive and writes the modified Windows 11 ISO
658-
content as a bootable installation drive (using DISM / robocopy approach).
659-
#>
660-
$contentsDir = $sync["Win11ISOContentsDir"]
661-
$usbDisks = $sync["Win11ISOUSBDisks"]
662-
663-
if (-not $contentsDir -or -not (Test-Path $contentsDir)) {
664-
[System.Windows.MessageBox]::Show(
665-
"No modified ISO content found. Please complete Steps 1–3 first.",
666-
"Not Ready", "OK", "Warning")
667-
return
668-
}
669-
670-
$selectedIndex = $sync["WPFWin11ISOUSBDriveComboBox"].SelectedIndex
671-
if ($selectedIndex -lt 0 -or -not $usbDisks -or $selectedIndex -ge $usbDisks.Count) {
672-
[System.Windows.MessageBox]::Show(
673-
"Please select a USB drive from the dropdown.",
674-
"No Drive Selected", "OK", "Warning")
675-
return
676-
}
677-
678-
$targetDisk = $usbDisks[$selectedIndex]
679-
$diskNum = $targetDisk.Number
680-
$sizeGB = [math]::Round($targetDisk.Size / 1GB, 1)
681-
682-
$confirm = [System.Windows.MessageBox]::Show(
683-
"ALL data on Disk $diskNum ($($targetDisk.FriendlyName), $sizeGB GB) will be PERMANENTLY ERASED.`n`nAre you sure you want to continue?",
684-
"Confirm USB Erase", "YesNo", "Warning")
685-
686-
if ($confirm -ne "Yes") {
687-
Write-Win11ISOLog "USB write cancelled by user."
688-
return
689-
}
690-
691-
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
692-
Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
693-
694-
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
695-
$runspace.ApartmentState = "STA"
696-
$runspace.ThreadOptions = "ReuseThread"
697-
$runspace.Open()
698-
$runspace.SessionStateProxy.SetVariable("sync", $sync)
699-
$runspace.SessionStateProxy.SetVariable("diskNum", $diskNum)
700-
$runspace.SessionStateProxy.SetVariable("contentsDir", $contentsDir)
701-
702-
$script = [Management.Automation.PowerShell]::Create()
703-
$script.Runspace = $runspace
704-
$script.AddScript({
705-
706-
function Log($msg) {
707-
$ts = (Get-Date).ToString("HH:mm:ss")
708-
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
709-
$sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $msg"
710-
$sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length
711-
$sync["WPFWin11ISOStatusLog"].ScrollToEnd()
712-
})
713-
}
714-
function SetProgress($label, $pct) {
715-
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
716-
$sync.progressBarTextBlock.Text = $label
717-
$sync.progressBarTextBlock.ToolTip = $label
718-
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
719-
})
720-
}
721-
722-
try {
723-
SetProgress "Formatting USB drive..." 10
724-
725-
# ── Diskpart script: clean, GPT, create ESP + data partitions ──
726-
$dpScript = @"
727-
select disk $diskNum
728-
clean
729-
convert gpt
730-
create partition efi size=512
731-
format quick fs=fat32 label="SYSTEM"
732-
assign
733-
create partition primary
734-
format quick fs=fat32 label="WINPE"
735-
assign
736-
exit
737-
"@
738-
$dpFile = Join-Path $env:TEMP "winutil_diskpart_$(Get-Random).txt"
739-
$dpScript | Set-Content -Path $dpFile -Encoding ASCII
740-
Log "Running diskpart on Disk $diskNum..."
741-
diskpart /s $dpFile | Out-Null
742-
Remove-Item $dpFile -Force
743-
744-
SetProgress "Identifying USB partitions..." 30
745-
Start-Sleep -Seconds 3 # let Windows assign drive letters
746-
747-
# Find newly assigned drive letter for the data partition
748-
$usbVol = Get-Partition -DiskNumber $diskNum |
749-
Where-Object { $_.Type -eq "Basic" } |
750-
Get-Volume |
751-
Where-Object { $_.FileSystemLabel -eq "WINPE" } |
752-
Select-Object -First 1
753-
754-
if (-not $usbVol) {
755-
throw "Could not locate the formatted USB data partition. Drive letter may not have been assigned automatically."
756-
}
757-
758-
$usbDrive = "$($usbVol.DriveLetter):"
759-
Log "USB data partition: $usbDrive"
760-
SetProgress "Copying Windows 11 files to USB..." 45
761-
762-
# ── Copy files (split large install.wim if > 4 GB for FAT32) ──
763-
$installWim = Join-Path $contentsDir "sources\install.wim"
764-
if (Test-Path $installWim) {
765-
$wimSizeMB = [math]::Round((Get-Item $installWim).Length / 1MB)
766-
if ($wimSizeMB -gt 3800) {
767-
# FAT32 limit – split with DISM
768-
Log "install.wim is $wimSizeMB MB – splitting for FAT32 compatibility..."
769-
$splitDest = Join-Path $usbDrive "sources\install.swm"
770-
New-Item -ItemType Directory -Path (Split-Path $splitDest) -Force | Out-Null
771-
Split-WindowsImage -ImagePath $installWim `
772-
-SplitImagePath $splitDest `
773-
-FileSize 3800 -CheckIntegrity | Out-Null
774-
Log "install.wim split complete."
775-
776-
# Copy everything else (exclude install.wim)
777-
$robocopyArgs = @($contentsDir, $usbDrive, "/E", "/XF", "install.wim", "/NFL", "/NDL", "/NJH", "/NJS")
778-
& robocopy @robocopyArgs | Out-Null
779-
} else {
780-
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
781-
}
782-
} else {
783-
& robocopy $contentsDir $usbDrive /E /NFL /NDL /NJH /NJS | Out-Null
784-
}
785-
786-
SetProgress "Finalising USB drive..." 90
787-
Log "Files copied to USB."
788-
789-
SetProgress "USB write complete ✔" 100
790-
Log "USB drive is ready for use."
791-
792-
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
793-
[System.Windows.MessageBox]::Show(
794-
"USB drive created successfully!`n`nYou can now boot from this drive to install Windows 11.",
795-
"USB Ready", "OK", "Info")
796-
})
797-
}
798-
catch {
799-
Log "ERROR during USB write: $_"
800-
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
801-
[System.Windows.MessageBox]::Show(
802-
"USB write failed:`n`n$_",
803-
"USB Write Error", "OK", "Error")
804-
})
805-
}
806-
finally {
807-
Start-Sleep -Milliseconds 800
808-
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
809-
$sync.progressBarTextBlock.Text = ""
810-
$sync.progressBarTextBlock.ToolTip = ""
811-
$sync.ProgressBar.Value = 0
812-
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
813-
})
814-
}
815-
}) | Out-Null
816-
817-
$script.BeginInvoke() | Out-Null
818-
}

0 commit comments

Comments
 (0)