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 @@
+
+
+
+
@@ -392,6 +396,7 @@
+
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
index a660cc8..38749a7 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1
@@ -55,6 +55,7 @@ function Add-BYOApplication {
$commandLine = $State.Controls.txtAppCommandLine.Text
$arguments = $State.Controls.txtAppArguments.Text
$source = $State.Controls.txtAppSource.Text
+ $additionalExitCodes = $State.Controls.txtAppAdditionalExitCodes.Text
if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($commandLine)) {
[System.Windows.MessageBox]::Show("Please fill in all fields (Name and Command Line)", "Missing Information", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
@@ -71,12 +72,13 @@ function Add-BYOApplication {
if ($listView.Items.Count -gt 0) {
$priority = ($listView.Items | Measure-Object -Property Priority -Maximum).Maximum + 1
}
- $application = [PSCustomObject]@{ Priority = $priority; Name = $name; CommandLine = $commandLine; Arguments = $arguments; Source = $source; CopyStatus = "" }
+ $application = [PSCustomObject]@{ Priority = $priority; Name = $name; CommandLine = $commandLine; Arguments = $arguments; Source = $source; AdditionalExitCodes = $additionalExitCodes; CopyStatus = "" }
$listView.Items.Add($application)
$State.Controls.txtAppName.Text = ""
$State.Controls.txtAppCommandLine.Text = ""
$State.Controls.txtAppArguments.Text = ""
$State.Controls.txtAppSource.Text = ""
+ $State.Controls.txtAppAdditionalExitCodes.Text = ""
Update-CopyButtonState -State $State
}
@@ -161,7 +163,7 @@ function Save-BYOApplicationList {
try {
# Ensure items are sorted by current priority before saving
# Exclude CopyStatus when saving and ensure Priority is an integer
- $applications = $listView.Items | Sort-Object Priority | Select-Object @{N = 'Priority'; E = { [int]$_.Priority } }, Name, CommandLine, Arguments, Source
+ $applications = $listView.Items | Sort-Object Priority | Select-Object @{N = 'Priority'; E = { [int]$_.Priority } }, Name, CommandLine, Arguments, Source, AdditionalExitCodes
$applications | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Force -Encoding UTF8
[System.Windows.MessageBox]::Show("Applications saved successfully to `"$Path`".", "Save Applications", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
}
@@ -193,14 +195,14 @@ function Import-BYOApplicationList {
# Add items and sort by priority from the file
$sortedApps = $applications | Sort-Object Priority
foreach ($app in $sortedApps) {
- # Ensure all properties exist, add CopyStatus
$appObject = [PSCustomObject]@{
- Priority = $app.Priority # Keep original priority for now
- Name = $app.Name
- CommandLine = $app.CommandLine
- Arguments = if ($app.PSObject.Properties['Arguments']) { $app.Arguments } else { "" } # Handle missing Arguments
- Source = $app.Source
- CopyStatus = "" # Initialize CopyStatus
+ Priority = $app.Priority
+ Name = $app.Name
+ CommandLine = $app.CommandLine
+ Arguments = if ($app.PSObject.Properties['Arguments']) { $app.Arguments } else { "" }
+ Source = $app.Source
+ AdditionalExitCodes = if ($app.PSObject.Properties['AdditionalExitCodes']) { $app.AdditionalExitCodes } else { "" }
+ CopyStatus = ""
}
$listView.Items.Add($appObject)
}
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
index d2f15e9..6938da3 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1
@@ -89,6 +89,7 @@ function Initialize-UIControls {
$State.Controls.txtAppCommandLine = $window.FindName('txtAppCommandLine')
$State.Controls.txtAppArguments = $window.FindName('txtAppArguments')
$State.Controls.txtAppSource = $window.FindName('txtAppSource')
+ $State.Controls.txtAppAdditionalExitCodes = $window.FindName('txtAppAdditionalExitCodes')
$State.Controls.btnAddApplication = $window.FindName('btnAddApplication')
$State.Controls.btnSaveBYOApplications = $window.FindName('btnSaveBYOApplications')
$State.Controls.btnLoadBYOApplications = $window.FindName('btnLoadBYOApplications')