mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
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.
This commit is contained in:
@@ -12,42 +12,71 @@ function Invoke-Process {
|
|||||||
|
|
||||||
[Parameter()]
|
[Parameter()]
|
||||||
[ValidateNotNullOrEmpty()]
|
[ValidateNotNullOrEmpty()]
|
||||||
[bool]$Wait = $true
|
[bool]$Wait = $true,
|
||||||
|
|
||||||
|
[Parameter()]
|
||||||
|
[string[]]$AdditionalSuccessCodes
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
try {
|
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)]")) {
|
if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) {
|
||||||
$cmd = Start-Process @startProcessParams
|
# Use .NET Process class for proper stream handling
|
||||||
$cmdOutput = Get-Content -Path $stdOutTempFile -Raw
|
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
|
||||||
$cmdError = Get-Content -Path $stdErrTempFile -Raw
|
$pinfo.FileName = $FilePath
|
||||||
if ($cmd.ExitCode -ne 0 -and $wait -eq $true) {
|
if ($ArgumentList) {
|
||||||
if ($cmdError) {
|
$pinfo.Arguments = $ArgumentList -join ' '
|
||||||
throw $cmdError.Trim()
|
}
|
||||||
}
|
$pinfo.RedirectStandardOutput = $true
|
||||||
if ($cmdOutput) {
|
$pinfo.RedirectStandardError = $true
|
||||||
throw $cmdOutput.Trim()
|
$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 {
|
else {
|
||||||
if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) {
|
if ([string]::IsNullOrEmpty($cmdOutput) -eq $false) {
|
||||||
# WriteLog $cmdOutput
|
# WriteLog $cmdOutput
|
||||||
Write-Host $cmdOutput
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create a simple object with exit code for compatibility
|
||||||
|
$result = [PSCustomObject]@{
|
||||||
|
ExitCode = $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -55,12 +84,7 @@ function Invoke-Process {
|
|||||||
# WriteLog $_
|
# WriteLog $_
|
||||||
# Write-Host "Script failed - $Logfile for more info"
|
# Write-Host "Script failed - $Logfile for more info"
|
||||||
throw $_
|
throw $_
|
||||||
|
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore
|
|
||||||
}
|
|
||||||
return $cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Install-Applications {
|
function Install-Applications {
|
||||||
@@ -110,15 +134,31 @@ function Install-Applications {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 {
|
try {
|
||||||
# Construct the argument list properly, handling potential array vs string
|
# Construct the argument list properly, handling potential array vs string
|
||||||
$argumentsToPass = if ($app.Arguments -is [array]) { $app.Arguments } else { @($app.Arguments) }
|
$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 ' ')"
|
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"
|
Write-Host "$($app.Name) exited with exit code: $($result.ExitCode)`r`n"
|
||||||
} catch {
|
} catch {
|
||||||
Write-Error "Error occurred while installing $($app.Name): $_"
|
Write-Error "Error occurred while installing $($app.Name): $_"
|
||||||
|
Read-Host "An error occurred, and the script cannot continue. Press Enter to exit."
|
||||||
|
throw $_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,6 +373,10 @@
|
|||||||
<Button x:Name="btnBrowseAppSource" Grid.Column="1" Content="Browse..." Width="80" Margin="5,0,0,0" VerticalAlignment="Center"/>
|
<Button x:Name="btnBrowseAppSource" Grid.Column="1" Content="Browse..." Width="80" Margin="5,0,0,0" VerticalAlignment="Center"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Additional Exit Codes -->
|
||||||
|
<TextBlock Text="Additional Exit Codes:" Margin="0,0,0,5"/>
|
||||||
|
<TextBox x:Name="txtAppAdditionalExitCodes" Margin="0,0,0,10" ToolTip="Enter a comma-separated list of additional success exit codes."/>
|
||||||
|
|
||||||
<!-- Add Application Button -->
|
<!-- Add Application Button -->
|
||||||
<Button x:Name="btnAddApplication" Content="Add Application" Width="120" HorizontalAlignment="Left" Margin="0,10,0,10" Padding="10,5" ToolTip="Add the application to the list"/>
|
<Button x:Name="btnAddApplication" Content="Add Application" Width="120" HorizontalAlignment="Left" Margin="0,10,0,10" Padding="10,5" ToolTip="Add the application to the list"/>
|
||||||
|
|
||||||
@@ -392,6 +396,7 @@
|
|||||||
<GridViewColumn Header="Command Line" DisplayMemberBinding="{Binding CommandLine}" Width="200"/>
|
<GridViewColumn Header="Command Line" DisplayMemberBinding="{Binding CommandLine}" Width="200"/>
|
||||||
<GridViewColumn Header="Arguments" DisplayMemberBinding="{Binding Arguments}" Width="200"/>
|
<GridViewColumn Header="Arguments" DisplayMemberBinding="{Binding Arguments}" Width="200"/>
|
||||||
<GridViewColumn Header="Source" DisplayMemberBinding="{Binding Source}" Width="150"/>
|
<GridViewColumn Header="Source" DisplayMemberBinding="{Binding Source}" Width="150"/>
|
||||||
|
<GridViewColumn Header="Exit Codes" DisplayMemberBinding="{Binding AdditionalExitCodes}" Width="100"/>
|
||||||
<GridViewColumn Header="Copy Status" DisplayMemberBinding="{Binding CopyStatus}" Width="150"/>
|
<GridViewColumn Header="Copy Status" DisplayMemberBinding="{Binding CopyStatus}" Width="150"/>
|
||||||
<GridViewColumn Header="Action" Width="85">
|
<GridViewColumn Header="Action" Width="85">
|
||||||
<GridViewColumn.CellTemplate>
|
<GridViewColumn.CellTemplate>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function Add-BYOApplication {
|
|||||||
$commandLine = $State.Controls.txtAppCommandLine.Text
|
$commandLine = $State.Controls.txtAppCommandLine.Text
|
||||||
$arguments = $State.Controls.txtAppArguments.Text
|
$arguments = $State.Controls.txtAppArguments.Text
|
||||||
$source = $State.Controls.txtAppSource.Text
|
$source = $State.Controls.txtAppSource.Text
|
||||||
|
$additionalExitCodes = $State.Controls.txtAppAdditionalExitCodes.Text
|
||||||
|
|
||||||
if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($commandLine)) {
|
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)
|
[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) {
|
if ($listView.Items.Count -gt 0) {
|
||||||
$priority = ($listView.Items | Measure-Object -Property Priority -Maximum).Maximum + 1
|
$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)
|
$listView.Items.Add($application)
|
||||||
$State.Controls.txtAppName.Text = ""
|
$State.Controls.txtAppName.Text = ""
|
||||||
$State.Controls.txtAppCommandLine.Text = ""
|
$State.Controls.txtAppCommandLine.Text = ""
|
||||||
$State.Controls.txtAppArguments.Text = ""
|
$State.Controls.txtAppArguments.Text = ""
|
||||||
$State.Controls.txtAppSource.Text = ""
|
$State.Controls.txtAppSource.Text = ""
|
||||||
|
$State.Controls.txtAppAdditionalExitCodes.Text = ""
|
||||||
Update-CopyButtonState -State $State
|
Update-CopyButtonState -State $State
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +163,7 @@ function Save-BYOApplicationList {
|
|||||||
try {
|
try {
|
||||||
# Ensure items are sorted by current priority before saving
|
# Ensure items are sorted by current priority before saving
|
||||||
# Exclude CopyStatus when saving and ensure Priority is an integer
|
# 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
|
$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)
|
[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
|
# Add items and sort by priority from the file
|
||||||
$sortedApps = $applications | Sort-Object Priority
|
$sortedApps = $applications | Sort-Object Priority
|
||||||
foreach ($app in $sortedApps) {
|
foreach ($app in $sortedApps) {
|
||||||
# Ensure all properties exist, add CopyStatus
|
|
||||||
$appObject = [PSCustomObject]@{
|
$appObject = [PSCustomObject]@{
|
||||||
Priority = $app.Priority # Keep original priority for now
|
Priority = $app.Priority
|
||||||
Name = $app.Name
|
Name = $app.Name
|
||||||
CommandLine = $app.CommandLine
|
CommandLine = $app.CommandLine
|
||||||
Arguments = if ($app.PSObject.Properties['Arguments']) { $app.Arguments } else { "" } # Handle missing Arguments
|
Arguments = if ($app.PSObject.Properties['Arguments']) { $app.Arguments } else { "" }
|
||||||
Source = $app.Source
|
Source = $app.Source
|
||||||
CopyStatus = "" # Initialize CopyStatus
|
AdditionalExitCodes = if ($app.PSObject.Properties['AdditionalExitCodes']) { $app.AdditionalExitCodes } else { "" }
|
||||||
|
CopyStatus = ""
|
||||||
}
|
}
|
||||||
$listView.Items.Add($appObject)
|
$listView.Items.Add($appObject)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ function Initialize-UIControls {
|
|||||||
$State.Controls.txtAppCommandLine = $window.FindName('txtAppCommandLine')
|
$State.Controls.txtAppCommandLine = $window.FindName('txtAppCommandLine')
|
||||||
$State.Controls.txtAppArguments = $window.FindName('txtAppArguments')
|
$State.Controls.txtAppArguments = $window.FindName('txtAppArguments')
|
||||||
$State.Controls.txtAppSource = $window.FindName('txtAppSource')
|
$State.Controls.txtAppSource = $window.FindName('txtAppSource')
|
||||||
|
$State.Controls.txtAppAdditionalExitCodes = $window.FindName('txtAppAdditionalExitCodes')
|
||||||
$State.Controls.btnAddApplication = $window.FindName('btnAddApplication')
|
$State.Controls.btnAddApplication = $window.FindName('btnAddApplication')
|
||||||
$State.Controls.btnSaveBYOApplications = $window.FindName('btnSaveBYOApplications')
|
$State.Controls.btnSaveBYOApplications = $window.FindName('btnSaveBYOApplications')
|
||||||
$State.Controls.btnLoadBYOApplications = $window.FindName('btnLoadBYOApplications')
|
$State.Controls.btnLoadBYOApplications = $window.FindName('btnLoadBYOApplications')
|
||||||
|
|||||||
Reference in New Issue
Block a user