mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Runs builds in pwsh process for reliable cancel
Improves UI responsiveness and interactive behavior by running build/cleanup in a separate PowerShell process instead of background jobs. Fixes cancellation reliability by terminating the full process tree (including child tools) and using process exit codes for success/failure reporting. Reduces noisy output by suppressing type-add return values and standardizes cleanup argument passing to avoid switch/boolean binding issues.
This commit is contained in:
@@ -584,7 +584,7 @@ public static extern uint GetPrivateProfileSection(
|
|||||||
uint nSize,
|
uint nSize,
|
||||||
string lpFileName);
|
string lpFileName);
|
||||||
'@
|
'@
|
||||||
Add-Type -MemberDefinition $definition -Namespace Win32 -Name Kernel32 -PassThru
|
Add-Type -MemberDefinition $definition -Namespace Win32 -Name Kernel32 -PassThru | Out-Null
|
||||||
|
|
||||||
#Check if Hyper-V feature is installed (requires only checks the module)
|
#Check if Hyper-V feature is installed (requires only checks the module)
|
||||||
$osInfo = Get-CimInstance -ClassName win32_OperatingSystem
|
$osInfo = Get-CimInstance -ClassName win32_OperatingSystem
|
||||||
|
|||||||
+156
-146
@@ -45,6 +45,7 @@ $script:uiState = [PSCustomObject]@{
|
|||||||
logData = $null;
|
logData = $null;
|
||||||
logStreamReader = $null;
|
logStreamReader = $null;
|
||||||
pollTimer = $null;
|
pollTimer = $null;
|
||||||
|
currentBuildProcess = $null;
|
||||||
lastConfigFilePath = $null
|
lastConfigFilePath = $null
|
||||||
};
|
};
|
||||||
Flags = @{
|
Flags = @{
|
||||||
@@ -148,7 +149,7 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
if ($script:uiState.Flags.isBuilding -and -not $script:uiState.Flags.isCleanupRunning) {
|
if ($script:uiState.Flags.isBuilding -and -not $script:uiState.Flags.isCleanupRunning) {
|
||||||
$btnRun.IsEnabled = $false
|
$btnRun.IsEnabled = $false
|
||||||
$script:uiState.Controls.txtStatus.Text = "Cancel requested. Stopping build..."
|
$script:uiState.Controls.txtStatus.Text = "Cancel requested. Stopping build..."
|
||||||
WriteLog "Cancel requested by user. Stopping background build job."
|
WriteLog "Cancel requested by user. Stopping background build process."
|
||||||
|
|
||||||
# Stop the timer
|
# Stop the timer
|
||||||
if ($null -ne $script:uiState.Data.pollTimer) {
|
if ($null -ne $script:uiState.Data.pollTimer) {
|
||||||
@@ -163,92 +164,71 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
$script:uiState.Data.logStreamReader = $null
|
$script:uiState.Data.logStreamReader = $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Stop and remove the running build job
|
# Stop the running build process
|
||||||
$jobToStop = $script:uiState.Data.currentBuildJob
|
$processToStop = $script:uiState.Data.currentBuildProcess
|
||||||
$script:uiState.Data.currentBuildJob = $null
|
$script:uiState.Data.currentBuildProcess = $null
|
||||||
if ($null -ne $jobToStop) {
|
|
||||||
try {
|
if ($null -ne $processToStop) {
|
||||||
# Attempt graceful stop first
|
# Recursively terminate the build process and any children (DISM, setup tools, etc.)
|
||||||
Stop-Job -Job $jobToStop -ErrorAction SilentlyContinue
|
function Stop-ProcessTree {
|
||||||
Wait-Job -Job $jobToStop -Timeout 5 -ErrorAction SilentlyContinue | Out-Null
|
param([int]$parentPid)
|
||||||
}
|
$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=$parentPid" -ErrorAction SilentlyContinue
|
||||||
catch {
|
foreach ($child in $children) {
|
||||||
WriteLog "Stop-Job threw: $($_.Exception.Message)"
|
Stop-ProcessTree -parentPid $child.ProcessId
|
||||||
|
}
|
||||||
|
try { Stop-Process -Id $parentPid -Force -ErrorAction SilentlyContinue } catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the job's hosting process is still alive, kill its process tree to stop child tools like DISM
|
|
||||||
try {
|
try {
|
||||||
$jobProcId = $null
|
Stop-ProcessTree -parentPid $processToStop.Id
|
||||||
if ($null -ne $jobToStop.ChildJobs -and $jobToStop.ChildJobs.Count -gt 0) {
|
WriteLog "Background build process stopped (PID: $($processToStop.Id))."
|
||||||
$jobProcId = $jobToStop.ChildJobs[0].ProcessId
|
}
|
||||||
}
|
catch {
|
||||||
if ($jobProcId) {
|
WriteLog "Error terminating build process tree: $($_.Exception.Message)"
|
||||||
# Recursively terminate the job process and any children
|
}
|
||||||
function Stop-ProcessTree {
|
}
|
||||||
param([int]$parentPid)
|
|
||||||
$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=$parentPid" -ErrorAction SilentlyContinue
|
# Safety net: kill any active DISM capture still running
|
||||||
foreach ($child in $children) {
|
try {
|
||||||
Stop-ProcessTree -parentPid $child.ProcessId
|
$dismCaptures = Get-CimInstance Win32_Process -Filter "Name='DISM.EXE'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match '/Capture-FFU' }
|
||||||
}
|
foreach ($p in $dismCaptures) {
|
||||||
try { Stop-Process -Id $parentPid -Force -ErrorAction SilentlyContinue } catch {}
|
try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Error stopping DISM capture processes: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also stop Office ODT setup.exe if running (to avoid recreating files after cleanup)
|
||||||
|
try {
|
||||||
|
$officePathForKill = $null
|
||||||
|
|
||||||
|
# Prefer explicit UI path
|
||||||
|
$uiOfficePath = $script:uiState.Controls.txtOfficePath.Text
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($uiOfficePath)) {
|
||||||
|
$officePathForKill = $uiOfficePath
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Fall back to the last config path only if known
|
||||||
|
$lastConfigPathLocal = $script:uiState.Data.lastConfigFilePath
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($lastConfigPathLocal)) {
|
||||||
|
$ffuDevRoot = Split-Path (Split-Path $lastConfigPathLocal -Parent) -Parent
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($ffuDevRoot)) {
|
||||||
|
$officePathForKill = Join-Path $ffuDevRoot 'Apps\Office'
|
||||||
}
|
}
|
||||||
Stop-ProcessTree -parentPid $jobProcId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
|
||||||
WriteLog "Error terminating job process tree: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Safety net: kill any active DISM capture still running
|
# Only proceed when a valid Office folder exists
|
||||||
try {
|
if ($officePathForKill -and (Test-Path -LiteralPath $officePathForKill -PathType Container)) {
|
||||||
$dismCaptures = Get-CimInstance Win32_Process -Filter "Name='DISM.EXE'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match '/Capture-FFU' }
|
$setupProcs = Get-CimInstance Win32_Process -Filter "Name='setup.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -like "$officePathForKill*" }
|
||||||
foreach ($p in $dismCaptures) {
|
foreach ($p in $setupProcs) {
|
||||||
try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {}
|
try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
}
|
||||||
WriteLog "Error stopping DISM capture processes: $($_.Exception.Message)"
|
catch {
|
||||||
}
|
WriteLog "Error stopping Office setup.exe processes: $($_.Exception.Message)"
|
||||||
|
|
||||||
# Also stop Office ODT setup.exe if running (to avoid recreating files after cleanup)
|
|
||||||
try {
|
|
||||||
$officePathForKill = $null
|
|
||||||
|
|
||||||
# Prefer explicit UI path
|
|
||||||
$uiOfficePath = $script:uiState.Controls.txtOfficePath.Text
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($uiOfficePath)) {
|
|
||||||
$officePathForKill = $uiOfficePath
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Fall back to the last config path only if known
|
|
||||||
$lastConfigPathLocal = $script:uiState.Data.lastConfigFilePath
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($lastConfigPathLocal)) {
|
|
||||||
$ffuDevRoot = Split-Path (Split-Path $lastConfigPathLocal -Parent) -Parent
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($ffuDevRoot)) {
|
|
||||||
$officePathForKill = Join-Path $ffuDevRoot 'Apps\Office'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only proceed when a valid Office folder exists
|
|
||||||
if ($officePathForKill -and (Test-Path -LiteralPath $officePathForKill -PathType Container)) {
|
|
||||||
$setupProcs = Get-CimInstance Win32_Process -Filter "Name='setup.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -like "$officePathForKill*" }
|
|
||||||
foreach ($p in $setupProcs) {
|
|
||||||
try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Error stopping Office setup.exe processes: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Remove-Job -Job $jobToStop -Force -ErrorAction SilentlyContinue
|
|
||||||
WriteLog "Background build job stopped and removed."
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Error removing background build job: $($_.Exception.Message)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start cleanup using the same BuildFFUVM.ps1 via -Cleanup short-circuit
|
# Start cleanup using the same BuildFFUVM.ps1 via -Cleanup short-circuit
|
||||||
@@ -289,13 +269,39 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
CleanupCurrentRunDownloads = $removeCurrentRunToo
|
CleanupCurrentRunDownloads = $removeCurrentRunToo
|
||||||
}
|
}
|
||||||
|
|
||||||
$cleanupScriptBlock = {
|
# Start cleanup in a separate pwsh process so the UI stays responsive
|
||||||
param($buildParams, $PSScriptRoot)
|
$pwshPath = Join-Path -Path $PSHOME -ChildPath 'pwsh.exe'
|
||||||
& "$PSScriptRoot\BuildFFUVM.ps1" @buildParams
|
if (-not (Test-Path -Path $pwshPath)) {
|
||||||
|
$pwshPath = 'pwsh'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start cleanup job
|
$cleanupScriptPath = Join-Path -Path $PSScriptRoot -ChildPath 'BuildFFUVM.ps1'
|
||||||
$script:uiState.Data.currentBuildJob = Start-Job -ScriptBlock $cleanupScriptBlock -ArgumentList @($cleanupParams, $PSScriptRoot)
|
|
||||||
|
# Build argument list for cleanup.
|
||||||
|
# -Cleanup is a [switch] in BuildFFUVM.ps1, so do not pass a value after it.
|
||||||
|
# Use -Param:$true/$false syntax for boolean parameters to avoid argument transformation errors.
|
||||||
|
$cleanupArgs = @(
|
||||||
|
'-NoProfile',
|
||||||
|
'-ExecutionPolicy', 'Bypass',
|
||||||
|
'-File', $cleanupScriptPath,
|
||||||
|
'-ConfigFile', $cleanupParams.ConfigFile,
|
||||||
|
'-Cleanup',
|
||||||
|
"-RemoveApps:$($cleanupParams.RemoveApps)",
|
||||||
|
"-RemoveUpdates:$($cleanupParams.RemoveUpdates)",
|
||||||
|
"-CleanupDrivers:$($cleanupParams.CleanupDrivers)",
|
||||||
|
"-CleanupCurrentRunDownloads:$($cleanupParams.CleanupCurrentRunDownloads)"
|
||||||
|
)
|
||||||
|
|
||||||
|
$startCleanupParams = @{
|
||||||
|
FilePath = $pwshPath
|
||||||
|
ArgumentList = $cleanupArgs
|
||||||
|
PassThru = $true
|
||||||
|
}
|
||||||
|
if ($Host.Name -eq 'ConsoleHost') {
|
||||||
|
$startCleanupParams['NoNewWindow'] = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:uiState.Data.currentBuildProcess = Start-Process @startCleanupParams
|
||||||
|
|
||||||
# Wait for log file to appear (or open immediately if it exists)
|
# Wait for log file to appear (or open immediately if it exists)
|
||||||
$logWaitTimeout = 60
|
$logWaitTimeout = 60
|
||||||
@@ -315,14 +321,14 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
WriteLog "Warning: Main log file not found at $mainLogPath after waiting. Monitor tab will not update during cleanup."
|
WriteLog "Warning: Main log file not found at $mainLogPath after waiting. Monitor tab will not update during cleanup."
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a timer to poll the cleanup job
|
# Create a timer to poll the cleanup process
|
||||||
$script:uiState.Data.pollTimer = New-Object System.Windows.Threading.DispatcherTimer
|
$script:uiState.Data.pollTimer = New-Object System.Windows.Threading.DispatcherTimer
|
||||||
$script:uiState.Data.pollTimer.Interval = [TimeSpan]::FromSeconds(1)
|
$script:uiState.Data.pollTimer.Interval = [TimeSpan]::FromSeconds(1)
|
||||||
$script:uiState.Flags.isCleanupRunning = $true
|
$script:uiState.Flags.isCleanupRunning = $true
|
||||||
|
|
||||||
$script:uiState.Data.pollTimer.Add_Tick({
|
$script:uiState.Data.pollTimer.Add_Tick({
|
||||||
param($sender, $e)
|
param($sender, $e)
|
||||||
$currentJob = $script:uiState.Data.currentBuildJob
|
$currentProcess = $script:uiState.Data.currentBuildProcess
|
||||||
|
|
||||||
# Read new lines from log
|
# Read new lines from log
|
||||||
if ($null -ne $script:uiState.Data.logStreamReader) {
|
if ($null -ne $script:uiState.Data.logStreamReader) {
|
||||||
@@ -335,13 +341,13 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($null -eq $currentJob -or $null -eq $script:uiState.Data.pollTimer) {
|
if ($null -eq $currentProcess -or $null -eq $script:uiState.Data.pollTimer) {
|
||||||
if ($null -ne $sender) { $sender.Stop() }
|
if ($null -ne $sender) { $sender.Stop() }
|
||||||
$script:uiState.Data.pollTimer = $null
|
$script:uiState.Data.pollTimer = $null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($currentJob.State -in 'Completed', 'Failed', 'Stopped') {
|
if ($currentProcess.HasExited) {
|
||||||
if ($null -ne $sender) { $sender.Stop() }
|
if ($null -ne $sender) { $sender.Stop() }
|
||||||
$script:uiState.Data.pollTimer = $null
|
$script:uiState.Data.pollTimer = $null
|
||||||
|
|
||||||
@@ -364,10 +370,8 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
|
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
|
||||||
$script:uiState.Controls.pbOverallProgress.Value = 0
|
$script:uiState.Controls.pbOverallProgress.Value = 0
|
||||||
|
|
||||||
# Receive and remove cleanup job
|
# Clear cleanup process state
|
||||||
$currentJob | Receive-Job -ErrorAction SilentlyContinue | Out-Null
|
$script:uiState.Data.currentBuildProcess = $null
|
||||||
Remove-Job -Job $currentJob -Force
|
|
||||||
$script:uiState.Data.currentBuildJob = $null
|
|
||||||
|
|
||||||
# Reset flags and button
|
# Reset flags and button
|
||||||
$script:uiState.Flags.isCleanupRunning = $false
|
$script:uiState.Flags.isCleanupRunning = $false
|
||||||
@@ -425,33 +429,44 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
$txtStatus.Text = "Executing BuildFFUVM.ps1 in the background..."
|
$txtStatus.Text = "Executing BuildFFUVM.ps1 in the background..."
|
||||||
WriteLog "Executing BuildFFUVM.ps1 in the background..."
|
WriteLog "Executing BuildFFUVM.ps1 in the background..."
|
||||||
|
|
||||||
# Prepare parameters for splatting
|
# Start BuildFFUVM.ps1 in a separate pwsh process.
|
||||||
$buildParams = @{
|
# This keeps the UI responsive and restores console interaction (Write-Host / Read-Host) when available.
|
||||||
ConfigFile = $configFilePath
|
$pwshPath = Join-Path -Path $PSHOME -ChildPath 'pwsh.exe'
|
||||||
|
if (-not (Test-Path -Path $pwshPath)) {
|
||||||
|
$pwshPath = 'pwsh'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$buildScriptPath = Join-Path -Path $PSScriptRoot -ChildPath 'BuildFFUVM.ps1'
|
||||||
|
$pwshArgs = @(
|
||||||
|
'-NoProfile',
|
||||||
|
'-ExecutionPolicy', 'Bypass',
|
||||||
|
'-File', $buildScriptPath,
|
||||||
|
'-ConfigFile', $configFilePath
|
||||||
|
)
|
||||||
if ($config.Verbose) {
|
if ($config.Verbose) {
|
||||||
$buildParams['Verbose'] = $true
|
$pwshArgs += '-Verbose'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Define the script block to run in the background job
|
# Delete the old log file before starting the build process to ensure we don't read stale content.
|
||||||
$scriptBlock = {
|
|
||||||
param($buildParams, $PSScriptRoot)
|
|
||||||
|
|
||||||
# This script runs in a new process. BuildFFUVM.ps1 is expected to handle its own module imports.
|
|
||||||
& "$PSScriptRoot\BuildFFUVM.ps1" @buildParams
|
|
||||||
}
|
|
||||||
|
|
||||||
# Delete the old log file before starting the build job to ensure we don't read stale content.
|
|
||||||
$mainLogPath = Join-Path $config.FFUDevelopmentPath "FFUDevelopment.log"
|
$mainLogPath = Join-Path $config.FFUDevelopmentPath "FFUDevelopment.log"
|
||||||
if (Test-Path $mainLogPath) {
|
if (Test-Path $mainLogPath) {
|
||||||
WriteLog "Removing old FFUDevelopment.log file."
|
WriteLog "Removing old FFUDevelopment.log file."
|
||||||
Remove-Item -Path $mainLogPath -Force
|
Remove-Item -Path $mainLogPath -Force
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start the job and store it in the shared state object
|
$startBuildParams = @{
|
||||||
$script:uiState.Data.currentBuildJob = Start-Job -ScriptBlock $scriptBlock -ArgumentList @($buildParams, $PSScriptRoot)
|
FilePath = $pwshPath
|
||||||
|
ArgumentList = $pwshArgs
|
||||||
|
PassThru = $true
|
||||||
|
}
|
||||||
|
if ($Host.Name -eq 'ConsoleHost') {
|
||||||
|
$startBuildParams['NoNewWindow'] = $true
|
||||||
|
}
|
||||||
|
|
||||||
# Wait for the new log file to be created by the background job.
|
# Start the build process and store it in the shared state object
|
||||||
|
$script:uiState.Data.currentBuildProcess = Start-Process @startBuildParams
|
||||||
|
|
||||||
|
# Wait for the new log file to be created by the background process.
|
||||||
$logWaitTimeout = 15 # seconds
|
$logWaitTimeout = 15 # seconds
|
||||||
$watch = [System.Diagnostics.Stopwatch]::StartNew()
|
$watch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||||
while (-not (Test-Path $mainLogPath) -and $watch.Elapsed.TotalSeconds -lt $logWaitTimeout) {
|
while (-not (Test-Path $mainLogPath) -and $watch.Elapsed.TotalSeconds -lt $logWaitTimeout) {
|
||||||
@@ -476,7 +491,7 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
$script:uiState.Data.pollTimer.Add_Tick({
|
$script:uiState.Data.pollTimer.Add_Tick({
|
||||||
param($sender, $e)
|
param($sender, $e)
|
||||||
# This scriptblock runs on the UI thread, so it can safely access script-scoped variables
|
# This scriptblock runs on the UI thread, so it can safely access script-scoped variables
|
||||||
$currentJob = $script:uiState.Data.currentBuildJob
|
$currentProcess = $script:uiState.Data.currentBuildProcess
|
||||||
|
|
||||||
# Read from log stream
|
# Read from log stream
|
||||||
if ($null -ne $script:uiState.Data.logStreamReader) {
|
if ($null -ne $script:uiState.Data.logStreamReader) {
|
||||||
@@ -500,8 +515,8 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# If job is somehow null or the timer has been nulled out, stop the timer
|
# If process is somehow null or the timer has been nulled out, stop the timer
|
||||||
if ($null -eq $currentJob -or $null -eq $script:uiState.Data.pollTimer) {
|
if ($null -eq $currentProcess -or $null -eq $script:uiState.Data.pollTimer) {
|
||||||
if ($null -ne $sender) {
|
if ($null -ne $sender) {
|
||||||
$sender.Stop()
|
$sender.Stop()
|
||||||
}
|
}
|
||||||
@@ -509,8 +524,8 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if the job has reached a terminal state
|
# Check if the build process has exited
|
||||||
if ($currentJob.State -in 'Completed', 'Failed', 'Stopped') {
|
if ($currentProcess.HasExited) {
|
||||||
# Stop the timer, we're done polling
|
# Stop the timer, we're done polling
|
||||||
if ($null -ne $sender) {
|
if ($null -ne $sender) {
|
||||||
$sender.Stop()
|
$sender.Stop()
|
||||||
@@ -546,42 +561,26 @@ $script:uiState.Controls.btnRun.Add_Click({
|
|||||||
$script:uiState.Data.logStreamReader = $null
|
$script:uiState.Data.logStreamReader = $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine final status based on job result and whether cleanup was running (should be false here)
|
$exitCode = $currentProcess.ExitCode
|
||||||
|
|
||||||
|
# Determine final status based on process exit code
|
||||||
$finalStatusText = "FFU build completed successfully."
|
$finalStatusText = "FFU build completed successfully."
|
||||||
if ($currentJob.State -eq 'Failed') {
|
if ($exitCode -ne 0) {
|
||||||
$reason = $null
|
|
||||||
|
|
||||||
Receive-Job -Job $currentJob -Keep -ErrorVariable jobErrors -ErrorAction SilentlyContinue | Out-Null
|
|
||||||
|
|
||||||
if ($null -ne $jobErrors -and $jobErrors.Count -gt 0) {
|
|
||||||
$reason = ($jobErrors | Select-Object -Last 1).ToString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($reason) -and $currentJob.JobStateInfo.Reason) {
|
|
||||||
$reason = $currentJob.JobStateInfo.Reason.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($reason)) {
|
|
||||||
$reason = "An unknown error occurred. The job failed without a specific reason."
|
|
||||||
}
|
|
||||||
|
|
||||||
$finalStatusText = "FFU build failed. Check FFUDevelopment.log for details."
|
$finalStatusText = "FFU build failed. Check FFUDevelopment.log for details."
|
||||||
WriteLog "BuildFFUVM.ps1 job failed. Reason: $reason"
|
WriteLog "BuildFFUVM.ps1 process failed with exit code: $exitCode"
|
||||||
[System.Windows.MessageBox]::Show("The build process failed. Please check the $FFUDevelopmentPath\FFUDevelopment.log file for details.`n`nError: $reason", "Build Error", "OK", "Error") | Out-Null
|
[System.Windows.MessageBox]::Show("The build process failed. Please check the $FFUDevelopmentPath\FFUDevelopment.log file for details.`n`nExit code: $exitCode", "Build Error", "OK", "Error") | Out-Null
|
||||||
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
|
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
WriteLog "BuildFFUVM.ps1 job completed successfully."
|
WriteLog "BuildFFUVM.ps1 process completed successfully."
|
||||||
$script:uiState.Controls.pbOverallProgress.Value = 100
|
$script:uiState.Controls.pbOverallProgress.Value = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update UI elements
|
# Update UI elements
|
||||||
$script:uiState.Controls.txtStatus.Text = $finalStatusText
|
$script:uiState.Controls.txtStatus.Text = $finalStatusText
|
||||||
|
|
||||||
# Receive & remove job and clear state
|
# Clear process state
|
||||||
$currentJob | Receive-Job -ErrorAction SilentlyContinue | Out-Null
|
$script:uiState.Data.currentBuildProcess = $null
|
||||||
Remove-Job -Job $currentJob -Force
|
|
||||||
$script:uiState.Data.currentBuildJob = $null
|
|
||||||
|
|
||||||
# Reset button and flags for next run
|
# Reset button and flags for next run
|
||||||
$script:uiState.Flags.isBuilding = $false
|
$script:uiState.Flags.isBuilding = $false
|
||||||
@@ -640,9 +639,9 @@ $window.Add_SourceInitialized({
|
|||||||
|
|
||||||
# Register cleanup to reclaim memory and revert LongPathsEnabled setting when the UI window closes
|
# Register cleanup to reclaim memory and revert LongPathsEnabled setting when the UI window closes
|
||||||
$window.Add_Closed({
|
$window.Add_Closed({
|
||||||
# Stop any running build job if the window is closed
|
# Stop any running build process if the window is closed
|
||||||
if ($null -ne $script:uiState.Data.currentBuildJob) {
|
if ($null -ne $script:uiState.Data.currentBuildProcess) {
|
||||||
WriteLog "UI closing, stopping background build job."
|
WriteLog "UI closing, stopping background build process."
|
||||||
|
|
||||||
# Stop the timer
|
# Stop the timer
|
||||||
if ($null -ne $script:uiState.Data.pollTimer) {
|
if ($null -ne $script:uiState.Data.pollTimer) {
|
||||||
@@ -657,17 +656,28 @@ $window.Add_Closed({
|
|||||||
$script:uiState.Data.logStreamReader = $null
|
$script:uiState.Data.logStreamReader = $null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Stop and remove the job
|
$processToStop = $script:uiState.Data.currentBuildProcess
|
||||||
$jobToStop = $script:uiState.Data.currentBuildJob
|
$script:uiState.Data.currentBuildProcess = $null
|
||||||
$script:uiState.Data.currentBuildJob = $null # Clear it from state first
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Stop-Job -Job $jobToStop
|
# Terminate the build process and any children
|
||||||
Remove-Job -Job $jobToStop
|
function Stop-ProcessTree {
|
||||||
WriteLog "Background job stopped and removed."
|
param([int]$parentPid)
|
||||||
|
$children = Get-CimInstance Win32_Process -Filter "ParentProcessId=$parentPid" -ErrorAction SilentlyContinue
|
||||||
|
foreach ($child in $children) {
|
||||||
|
Stop-ProcessTree -parentPid $child.ProcessId
|
||||||
|
}
|
||||||
|
try { Stop-Process -Id $parentPid -Force -ErrorAction SilentlyContinue } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $processToStop -and -not $processToStop.HasExited) {
|
||||||
|
Stop-ProcessTree -parentPid $processToStop.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLog "Background process stopped."
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error stopping or removing background job: $($_.Exception.Message)"
|
WriteLog "Error stopping background build process: $($_.Exception.Message)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user