From 81aee4ead924b688d22f59e208767f6aa80d0526 Mon Sep 17 00:00:00 2001 From: Chris Titus Date: Sun, 22 Feb 2026 17:37:03 -0600 Subject: [PATCH 01/17] Tab creation --- config/themes.json | 4 ++++ scripts/main.ps1 | 1 + xaml/inputXML.xaml | 14 ++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/config/themes.json b/config/themes.json index f6da35548b..bab0de627b 100644 --- a/config/themes.json +++ b/config/themes.json @@ -65,10 +65,12 @@ "ButtonTweaksBackgroundColor": "#F7F7F7", "ButtonConfigBackgroundColor": "#F7F7F7", "ButtonUpdatesBackgroundColor": "#F7F7F7", + "ButtonWin11ISOBackgroundColor": "#F7F7F7", "ButtonInstallForegroundColor": "#232629", "ButtonTweaksForegroundColor": "#232629", "ButtonConfigForegroundColor": "#232629", "ButtonUpdatesForegroundColor": "#232629", + "ButtonWin11ISOForegroundColor": "#232629", "ButtonBackgroundColor": "#F5F5F5", "ButtonBackgroundPressedColor": "#1A1A1A", "ButtonBackgroundMouseoverColor": "#C2C2C2", @@ -105,10 +107,12 @@ "ButtonTweaksBackgroundColor": "#333333", "ButtonConfigBackgroundColor": "#444444", "ButtonUpdatesBackgroundColor": "#555555", + "ButtonWin11ISOBackgroundColor": "#666666", "ButtonInstallForegroundColor": "#F7F7F7", "ButtonTweaksForegroundColor": "#F7F7F7", "ButtonConfigForegroundColor": "#F7F7F7", "ButtonUpdatesForegroundColor": "#F7F7F7", + "ButtonWin11ISOForegroundColor": "#F7F7F7", "ButtonBackgroundColor": "#1E3747", "ButtonBackgroundPressedColor": "#F7F7F7", "ButtonBackgroundMouseoverColor": "#3B4252", diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 6359933ecd..9f5a8fce95 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -281,6 +281,7 @@ $commonKeyEvents = { "T" { Invoke-WPFButton "WPFTab2BT"; $keyEventArgs.Handled = $true } # Navigate to Tweaks tab "C" { Invoke-WPFButton "WPFTab3BT"; $keyEventArgs.Handled = $true } # Navigate to Config tab "U" { Invoke-WPFButton "WPFTab4BT"; $keyEventArgs.Handled = $true } # Navigate to Updates tab + "W" { Invoke-WPFButton "WPFTab5BT"; $keyEventArgs.Handled = $true } # Navigate to Win11ISO tab } } # Handle Ctrl key combinations for specific actions diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index b9725c31b6..a34c86bc7d 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -970,6 +970,14 @@ + + + + Win11ISO + + + @@ -1331,6 +1339,12 @@ + + + + + + From 114f6712374b8f000723f2145a6051ef9921268a Mon Sep 17 00:00:00 2001 From: Chris Titus Date: Sun, 22 Feb 2026 17:49:45 -0600 Subject: [PATCH 02/17] scaffold outline for the iso tab --- functions/private/Invoke-Win11ISO.ps1 | 559 ++++++++++++++++++++++++++ scripts/main.ps1 | 39 ++ xaml/inputXML.xaml | 288 +++++++++++++ 3 files changed, 886 insertions(+) create mode 100644 functions/private/Invoke-Win11ISO.ps1 diff --git a/functions/private/Invoke-Win11ISO.ps1 b/functions/private/Invoke-Win11ISO.ps1 new file mode 100644 index 0000000000..d1baed6251 --- /dev/null +++ b/functions/private/Invoke-Win11ISO.ps1 @@ -0,0 +1,559 @@ +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") + $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" + } else { + $sync["WPFWin11ISOStatusLog"].Text += "`n[$timestamp] $Message" + } + $sync["WPFWin11ISOStatusLog"].ScrollToEnd() + }) +} + +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.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) ── + $fileSizeGB = [math]::Round((Get-Item $isoPath).Length / 1GB, 2) + + $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" + + 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") + return + } + + Write-Win11ISOLog "Mounting ISO: $isoPath" + Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10 + + try { + # Mount the ISO + $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" + + if (-not (Test-Path $wimPath) -and -not (Test-Path $esdPath)) { + Dismount-DiskImage -ImagePath $isoPath | Out-Null + Write-Win11ISOLog "ERROR: install.wim/install.esd not found — not a valid Windows ISO." + [System.Windows.MessageBox]::Show( + "This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.", + "Invalid ISO", "OK", "Error") + Set-WinUtilProgressBar -Label "" -Percent 0 + return + } + + $activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath } + + # ── Read edition / architecture info ── + Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55 + + $editions = Get-WindowsImage -ImagePath $activeWim | Select-Object -ExpandProperty ImageName + + # ── Verify at least one Win11 edition is present ── + $isWin11 = $editions | Where-Object { $_ -match "Windows 11" } + if (-not $isWin11) { + Dismount-DiskImage -ImagePath $isoPath | Out-Null + Write-Win11ISOLog "ERROR: No 'Windows 11' edition found in the image." + [System.Windows.MessageBox]::Show( + "No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.", + "Not a Windows 11 ISO", "OK", "Error") + Set-WinUtilProgressBar -Label "" -Percent 0 + return + } + + # ── Populate UI ── + $sync["WPFWin11ISOMountDriveLetter"].Text = "Mounted at: $driveLetter | Image file: $(Split-Path $activeWim -Leaf)" + $sync["WPFWin11ISOEditionList"].Text = ($editions -join "`n") + $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 + Write-Win11ISOLog "ISO verified OK. Editions found: $($editions.Count)" + } + 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 { + 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"] + + if (-not $isoPath) { + [System.Windows.MessageBox]::Show( + "No verified ISO found. Please complete Steps 1 and 2 first.", + "Not Ready", "OK", "Warning") + return + } + + # Disable the modify button to prevent double-click + $sync["WPFWin11ISOModifyButton"].IsEnabled = $false + + $workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + + # ── Run modification in a background runspace ── + $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() + $runspace.ApartmentState = "STA" + $runspace.ThreadOptions = "ReuseThread" + $runspace.Open() + $runspace.SessionStateProxy.SetVariable("sync", $sync) + $runspace.SessionStateProxy.SetVariable("isoPath", $isoPath) + $runspace.SessionStateProxy.SetVariable("driveLetter", $driveLetter) + $runspace.SessionStateProxy.SetVariable("wimPath", $wimPath) + $runspace.SessionStateProxy.SetVariable("workDir", $workDir) + + $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"].ScrollToEnd() + }) + } + + function SetProgress($label, $pct) { + $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ + Set-WinUtilProgressBar -Label $label -Percent $pct + }) + } + + try { + # ── 1. Create working directory structure ── + Log "Creating working directory: $workDir" + $isoContents = Join-Path $workDir "iso_contents" + $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 + 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 + Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false + + # ── 4. Mount the first index of install.wim ── + Log "Mounting install.wim (Index 1) at $mountDir..." + Mount-WindowsImage -ImagePath $localWim -Index 1 -Path $mountDir -ErrorAction Stop | Out-Null + SetProgress "Modifying install.wim..." 45 + + # ════════════════════════════════════════════════════════ + # >>> YOUR MODIFICATION LOGIC GOES HERE <<< + # + # Examples (uncomment or extend as needed): + # + # Remove-WindowsPackage -Path $mountDir -PackageName "Microsoft-Windows-…" + # Disable-WindowsOptionalFeature -Path $mountDir -FeatureName "…" + # Copy-Item "C:\path\to\custom.xml" "$mountDir\Windows\Panther\unattend.xml" + # reg load "HKLM\OFFLINE" "$mountDir\Windows\System32\config\SOFTWARE" + # … registry tweaks … + # reg unload "HKLM\OFFLINE" + # + # ════════════════════════════════════════════════════════ + + Log "Applying modifications to install.wim... (placeholder)" + Start-Sleep -Seconds 2 # replace with actual modification calls + + # ── 5. Save and dismount the WIM ── + SetProgress "Saving modified install.wim..." 65 + Log "Dismounting and saving install.wim..." + Dismount-WindowsImage -Path $mountDir -Save -ErrorAction Stop | Out-Null + Log "install.wim saved." + 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 + + SetProgress "Modification complete ✔" 100 + Log "install.wim modification complete. Select an output option in Step 4." + + # ── Reveal Step 4 on the UI thread ── + $sync["WPFWin11ISOOutputSection"].Dispatcher.Invoke([action]{ + $sync["WPFWin11ISOOutputSection"].Visibility = "Visible" + Invoke-WinUtilISORefreshUSBDrives + }) + } + catch { + Log "ERROR during modification: $_" + $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ + [System.Windows.MessageBox]::Show( + "An error occurred during install.wim modification:`n`n$_", + "Modification Error", "OK", "Error") + }) + } + finally { + Start-Sleep -Milliseconds 800 + $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ + Set-WinUtilProgressBar -Label "" -Percent 0 + $sync["WPFWin11ISOModifyButton"].IsEnabled = $true + }) + } + }) | Out-Null + + $script.BeginInvoke() | Out-Null +} + +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.", + "Not Ready", "OK", "Warning") + return + } + + Add-Type -AssemblyName System.Windows.Forms + + $dlg = [System.Windows.Forms.SaveFileDialog]::new() + $dlg.Title = "Save Modified Windows 11 ISO" + $dlg.Filter = "ISO files (*.iso)|*.iso" + $dlg.FileName = "Win11_Modified_$(Get-Date -Format 'yyyyMMdd').iso" + $dlg.InitialDirectory = [System.Environment]::GetFolderPath("Desktop") + + 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) + $oscdimg = Get-ChildItem "C:\Program Files (x86)\Windows Kits" -Recurse -Filter "oscdimg.exe" -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + + if (-not $oscdimg) { + Set-WinUtilProgressBar -Label "" -Percent 0 + Write-Win11ISOLog "oscdimg.exe not found. Install Windows ADK to enable ISO export." + [System.Windows.MessageBox]::Show( + "oscdimg.exe was not found.`n`nTo export an ISO you need the Windows Assessment and Deployment Kit (ADK).`n`nDownload it from: https://learn.microsoft.com/windows-hardware/get-started/adk-install", + "Windows ADK Required", "OK", "Warning") + return + } + + # 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`"" + ) + + try { + Write-Win11ISOLog "Running oscdimg..." + $proc = Start-Process -FilePath $oscdimg -ArgumentList $oscdimgArgs -Wait -PassThru -NoNewWindow + 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") + } + } + 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 + } +} + +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"].ScrollToEnd() + }) + } + function SetProgress($label, $pct) { + $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ + Set-WinUtilProgressBar -Label $label -Percent $pct + }) + } + + 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]{ + Set-WinUtilProgressBar -Label "" -Percent 0 + $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true + }) + } + }) | Out-Null + + $script.BeginInvoke() | Out-Null +} diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 9f5a8fce95..bbb7e4c1d7 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -532,5 +532,44 @@ $sync["FontScalingApplyButton"].Add_Click({ Invoke-WPFPopup -Action "Hide" -Popups @("FontScaling") }) +# ── Win11ISO Tab button handlers ────────────────────────────────────────────── + +$sync["WPFWin11ISOBrowseButton"].Add_Click({ + Write-Debug "WPFWin11ISOBrowseButton clicked" + Invoke-WinUtilISOBrowse +}) + +$sync["WPFWin11ISODownloadLink"].Add_Click({ + Write-Debug "WPFWin11ISODownloadLink clicked" + Start-Process "https://www.microsoft.com/software-download/windows11" +}) + +$sync["WPFWin11ISOMountButton"].Add_Click({ + Write-Debug "WPFWin11ISOMountButton clicked" + Invoke-WinUtilISOMountAndVerify +}) + +$sync["WPFWin11ISOModifyButton"].Add_Click({ + Write-Debug "WPFWin11ISOModifyButton clicked" + Invoke-WinUtilISOModify +}) + +$sync["WPFWin11ISOExportButton"].Add_Click({ + Write-Debug "WPFWin11ISOExportButton clicked" + Invoke-WinUtilISOExport +}) + +$sync["WPFWin11ISORefreshUSBButton"].Add_Click({ + Write-Debug "WPFWin11ISORefreshUSBButton clicked" + Invoke-WinUtilISORefreshUSBDrives +}) + +$sync["WPFWin11ISOWriteUSBButton"].Add_Click({ + Write-Debug "WPFWin11ISOWriteUSBButton clicked" + Invoke-WinUtilISOWriteUSB +}) + +# ────────────────────────────────────────────────────────────────────────────── + $sync["Form"].ShowDialog() | out-null Stop-Transcript diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index a34c86bc7d..e55929b3e9 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -1342,6 +1342,294 @@ + + + + + + + + + + + + + + + + + + + + + + Step 1 — Select Windows 11 ISO + + + Browse to your locally saved Windows 11 ISO file. Only official ISOs + downloaded from Microsoft are supported. + + + + + + + +