mirror of
https://github.com/ChrisTitusTech/winutil
synced 2026-04-06 06:38:31 +00:00
inital modification success
This commit is contained in:
572
functions/private/Invoke-WinUtilISO.ps1
Normal file
572
functions/private/Invoke-WinUtilISO.ps1
Normal file
@@ -0,0 +1,572 @@
|
||||
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)
|
||||
|
||||
# Serialize functions so they are available inside the runspace
|
||||
$isoScriptFuncDef = "function Invoke-WinUtilISOScript {`n" + `
|
||||
${function:Invoke-WinUtilISOScript}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("isoScriptFuncDef", $isoScriptFuncDef)
|
||||
|
||||
$win11ISOLogFuncDef = "function Write-Win11ISOLog {`n" + `
|
||||
${function:Write-Win11ISOLog}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("win11ISOLogFuncDef", $win11ISOLogFuncDef)
|
||||
|
||||
$refreshUSBFuncDef = "function Invoke-WinUtilISORefreshUSBDrives {`n" + `
|
||||
${function:Invoke-WinUtilISORefreshUSBDrives}.ToString() + "`n}"
|
||||
$runspace.SessionStateProxy.SetVariable("refreshUSBFuncDef", $refreshUSBFuncDef)
|
||||
|
||||
$script = [Management.Automation.PowerShell]::Create()
|
||||
$script.Runspace = $runspace
|
||||
$script.AddScript({
|
||||
|
||||
# Import helper functions into this runspace
|
||||
. ([scriptblock]::Create($isoScriptFuncDef))
|
||||
. ([scriptblock]::Create($win11ISOLogFuncDef))
|
||||
. ([scriptblock]::Create($refreshUSBFuncDef))
|
||||
|
||||
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]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# ── Apply all WinUtil modifications via Invoke-WinUtilISOScript ──
|
||||
Log "Applying WinUtil modifications to install.wim..."
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -Log { param($m) Log $m }
|
||||
|
||||
# ── 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]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 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]{
|
||||
$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
|
||||
}
|
||||
Reference in New Issue
Block a user