Private/Invoke-ExternalCommand.ps1

function Invoke-ExternalCommand(
    [Parameter(Mandatory)]
    [scriptblock]$Command,
    [int[]]$SuccessExitCodes = @(0),
    [string]$ErrorMessage,
    [switch]$Passthru
) {
    $stdoutAndErr = & $Command 2>&1

    # Split stdout and stderr -- https://stackoverflow.com/a/68106198/33244
    # The [string[]] cast converts the [ErrorRecord] instances to strings too.
    $stdout, [string[]]$stderr = $stdoutAndErr.Where({ $_ -is [string] }, 'Split')
    if ($SuccessExitCodes -notcontains $LASTEXITCODE) {
        # If the command failed, we throw an exception with the stderr output.

        if (-not $ErrorMessage) {
            $ErrorMessage = "Command exited with code $LASTEXITCODE."
        }

        $exceptionMessages = @()
        if ($stderr) {
            $exceptionMessages = @("$ErrorMessage. Output is:") + $stderr
        }

        throw $($exceptionMessages -join [Environment]::NewLine)
    }

    if ($Passthru) {
        # If the Passthru switch is specified, we return both stdout and stderr.
        return [PSCustomObject]@{
            StdOut = $stdout
            StdErr = $stderr
            ExitCode = $LASTEXITCODE
        }
    }
}