public/Execute.ps1
|
function Execute { <# .SYNOPSIS Helper function for executing command-line programs. .DESCRIPTION This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode to see if an error occured. If an error is detected then an exception is thrown. This function allows you to run command-line programs without having to explicitly check the $lastexitcode variable. .PARAMETER Cmd The scriptblock to execute. This scriptblock will typically contain the command-line invocation. .PARAMETER ErrorMessage The error message to display if the external command returned a non-zero exit code. .PARAMETER MaxRetries The maximum number of times to retry the command before failing. .PARAMETER RetryTriggerErrorPattern If the external command raises an exception, match the exception against this regex to determine if the command can be retried. If a match is found, the command will be retried provided [MaxRetries] has not been reached. .PARAMETER WorkingDirectory The working directory to set before running the external command. .PARAMETER NewProcess If set, the command will be executed in a new process. This can be used to isolate the command's environment from the current process. .PARAMETER TimeoutSeconds If set, the command will be terminated if it runs longer than this number of seconds. Defaults to 300 seconds (5 minutes). .EXAMPLE Execute { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" This example calls the svn command-line client. #> [CmdletBinding()] [Alias("Exec")] param( [Parameter(Mandatory = $true)] [scriptblock]$Cmd, [string]$ErrorMessage = ($msgs.error_bad_command -f $Cmd), [int]$MaxRetries = 0, [string]$RetryTriggerErrorPattern = $null, [Alias("wd")] [string]$WorkingDirectory = $null, [switch]$NewProcess, [int]$TimeoutSeconds = 300 ) Write-Debug "Exec: running command$(if ($WorkingDirectory) { " in '$WorkingDirectory'" })$(if ($MaxRetries -gt 0) { " (max retries: $MaxRetries)" })" $tryCount = 1 do { try { if ($WorkingDirectory) { Push-Location -Path $WorkingDirectory } $global:lastexitcode = 0 if ($NewProcess.IsPresent) { # Determine which PowerShell executable to use based on the # current edition (Desktop vs Core) if ($PSVersionTable.PSEdition -eq 'Core') { $psExe = 'pwsh' } else { $psExe = 'powershell' } # Convert the scriptblock to a string and execute it in a new # process. This is necessary because ProcessStartInfo does not # support scriptblocks directly. GetNewClosure() is used to # capture the current variable scope and make those variables # available in the new process. $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $psExe $startInfo.Arguments = "-Command & { {0} }" -f $Cmd.GetNewClosure().ToString() $startInfo.RedirectStandardOutput = $true $startInfo.RedirectStandardError = $true $startInfo.UseShellExecute = $false $process = New-Object System.Diagnostics.Process $process.StartInfo = $startInfo $process.Start() | Out-Null if (-not $process.WaitForExit($TimeoutSeconds * 1000)) { $process.Kill() throw "Exec: $ErrorMessage (timeout)" } if ($process.ExitCode -ne 0) { Write-BuildMessage ($msgs.exec_standard_output -f $process.StandardOutput.ReadToEnd()) "Default" Write-BuildMessage ($msgs.exec_standard_error -f $process.StandardError.ReadToEnd()) "Error" throw "Exec: $ErrorMessage (exit code: $($process.ExitCode))" } Write-BuildMessage ($msgs.exec_standard_output -f $process.StandardOutput.ReadToEnd()) "Default" } else { & $Cmd } if ($global:lastexitcode -ne 0) { throw "Exec: $ErrorMessage" } break } catch [Exception] { if ($tryCount -gt $MaxRetries) { throw $_ } if ($RetryTriggerErrorPattern -ne $null) { $isMatch = [regex]::IsMatch($_.Exception.Message, $RetryTriggerErrorPattern) if ($isMatch -eq $false) { throw $_ } } Write-BuildMessage ($msgs.retrying_execute -f $tryCount) "Warning" $tryCount++ [System.Threading.Thread]::Sleep([System.TimeSpan]::FromSeconds(1)) } finally { if ($WorkingDirectory) { Pop-Location } } } while ($true) } |