From 03c8127bd31aa2f4583941bc9d102ee09876a5e2 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:17:52 -0700 Subject: [PATCH] Feat: Add support for custom success exit codes Refactors the process invocation logic to use the .NET Process class for more robust output stream handling and to avoid temporary files. Introduces a feature allowing users to specify additional success exit codes for applications in the UI. These codes are now considered successful during the installation process. Adds a 'PAUSE' command to halt script execution and wait for user input. --- .../Apps/Orchestration/Install-Win32Apps.ps1 | 100 ++++++++++++------ FFUDevelopment/BuildFFUVM_UI.xaml | 5 + .../FFUUI.Core/FFUUI.Core.Applications.psm1 | 20 ++-- .../FFUUI.Core/FFUUI.Core.Initialize.psm1 | 1 + 4 files changed, 87 insertions(+), 39 deletions(-) diff --git a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 index 98a6fbe..cf70a44 100644 --- a/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 +++ b/FFUDevelopment/Apps/Orchestration/Install-Win32Apps.ps1 @@ -12,42 +12,71 @@ function Invoke-Process { [Parameter()] [ValidateNotNullOrEmpty()] - [bool]$Wait = $true + [bool]$Wait = $true, + + [Parameter()] + [string[]]$AdditionalSuccessCodes + ) $ErrorActionPreference = 'Stop' try { - $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)" - $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)" - - $startProcessParams = @{ - FilePath = $FilePath - ArgumentList = $ArgumentList - RedirectStandardError = $stdErrTempFile - RedirectStandardOutput = $stdOutTempFile - Wait = $($Wait); - PassThru = $true; - NoNewWindow = $true; - } if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) { - $cmd = Start-Process @startProcessParams - $cmdOutput = Get-Content -Path $stdOutTempFile -Raw - $cmdError = Get-Content -Path $stdErrTempFile -Raw - if ($cmd.ExitCode -ne 0 -and $wait -eq $true) { - if ($cmdError) { - throw $cmdError.Trim() - } - if ($cmdOutput) { - throw $cmdOutput.Trim() + # Use .NET Process class for proper stream handling + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = $FilePath + if ($ArgumentList) { + $pinfo.Arguments = $ArgumentList -join ' ' + } + $pinfo.RedirectStandardOutput = $true + $pinfo.RedirectStandardError = $true + $pinfo.UseShellExecute = $false + $pinfo.CreateNoWindow = $true + + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + + # Start the process + $p.Start() | Out-Null + + # Read output and error streams + $cmdOutput = $p.StandardOutput.ReadToEnd() + $cmdError = $p.StandardError.ReadToEnd() + + if ($Wait) { + $p.WaitForExit() + } + + $exitCode = $p.ExitCode + # An exit code of 0 is always a success + if ($exitCode -ne 0) { + # Check if the non-zero exit code is in the list of additional success codes + if ($null -eq $AdditionalSuccessCodes -or $exitCode -notin $AdditionalSuccessCodes) { + if ($cmdError) { + throw $cmdError.Trim() + } + if ($cmdOutput) { + throw $cmdOutput.Trim() + } + # If there's no output, throw a generic error with the exit code + if (-not $cmdError -and -not $cmdOutput) { + throw "Process exited with non-zero code: $exitCode" + } } } else { if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) { # WriteLog $cmdOutput - Write-Host $cmdOutput } } + + # Create a simple object with exit code for compatibility + $result = [PSCustomObject]@{ + ExitCode = $exitCode + } + + return $result } } catch { @@ -55,12 +84,7 @@ function Invoke-Process { # WriteLog $_ # Write-Host "Script failed - $Logfile for more info" throw $_ - } - finally { - Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore - } - return $cmd } function Install-Applications { @@ -109,16 +133,32 @@ function Install-Applications { break } } + + # Check for 'PAUSE' command + if ($app.CommandLine -eq 'PAUSE') { + Write-Host "Pausing script as requested by '$($app.Name)'. Press Enter to continue..." + $null = Read-Host + continue + } try { # Construct the argument list properly, handling potential array vs string $argumentsToPass = if ($app.Arguments -is [array]) { $app.Arguments } else { @($app.Arguments) } - + + # Check for and parse AdditionalExitCodes + $additionalSuccessCodes = @() + if ($app.PSObject.Properties['AdditionalExitCodes'] -and -not [string]::IsNullOrWhiteSpace($app.AdditionalExitCodes)) { + $additionalSuccessCodes = $app.AdditionalExitCodes -split ',' | ForEach-Object { $_.Trim() } + Write-Host "Additional success exit codes for $($app.Name): $($additionalSuccessCodes -join ', ')" + } + Write-Host "Running command: $($app.CommandLine) $($argumentsToPass -join ' ')" - $result = Invoke-Process -FilePath $($app.CommandLine) -ArgumentList $argumentsToPass + $result = Invoke-Process -FilePath $($app.CommandLine) -ArgumentList $argumentsToPass -AdditionalSuccessCodes $additionalSuccessCodes Write-Host "$($app.Name) exited with exit code: $($result.ExitCode)`r`n" } catch { Write-Error "Error occurred while installing $($app.Name): $_" + Read-Host "An error occurred, and the script cannot continue. Press Enter to exit." + throw $_ } } } diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml index d485da0..a1e733a 100644 --- a/FFUDevelopment/BuildFFUVM_UI.xaml +++ b/FFUDevelopment/BuildFFUVM_UI.xaml @@ -373,6 +373,10 @@