Skip to content
mnaoumov.dev
Go back

Execution of external commands (native applications) in PowerShell done right - Part 2

Part 1 Part 3

Hi folks in the previous blogpost I described a problem.

Today while fixing a defect in posh-git I found that the solution was not ideal and came up with improved version

function Invoke-NativeApplication
{
    param
    (
        [ScriptBlock] $ScriptBlock,
        [int[]] $AllowedExitCodes = @(0)
    )

    $backupErrorActionPreference = $ErrorActionPreference

    $ErrorActionPreference = "Continue"
    try
    {
        & $ScriptBlock 2>&1 | ForEach-Object -Process `
            {
                $isError = $_ -is [System.Management.Automation.ErrorRecord]
                "$_" | Add-Member -Name IsError -MemberType NoteProperty -Value $isError -PassThru
            }
        if ($AllowedExitCodes -notcontains $LASTEXITCODE)
        {
            throw "Execution failed with exit code $LASTEXITCODE"
        }
    }
    finally
    {
        $ErrorActionPreference = $backupErrorActionPreference
    }
}

function Invoke-NativeApplicationSafe
{
    param
    (
        [ScriptBlock] $ScriptBlock
    )

    Invoke-NativeApplication -ScriptBlock $ScriptBlock -AllowedExitCodes (0..255) | `
        Where-Object -FilterScript { -not $_.IsError }
}

Set-Alias -Name exec -Value Invoke-NativeApplication
Set-Alias -Name safeexec -Value Invoke-NativeApplicationSafe

The key thing is IsError property that is attached to each string returned from exec method

This gives us ability to perform any additional filtering easily, e.g.

# simulate some program which writes to both STDOUT and STDERR

$result = exec { cmd /c "echo message1 & echo message2 & echo error3 1>&2 & echo error4 1>&2 & echo message5" }

$result | ForEach-Object -Process `
    {
        if ($_.IsError)
        {
            Write-Host -Object "Error: $_" -ForegroundColor Red
        }
        else
        {
            Write-Host -Object $_ -ForegroundColor Green
        }
    }

I think I covers any scenarios that may be required

NOTE: Surprisingly I found that actually order is not guaranteed. You may receive STDOUT and STDERR messages not in exact order. I knew that before when I was working with System.Diagnostics.Process in .NET, but I thought it is better working in PowerShell, but nope.

Each run gives different results such as


message1 message2 message5 error3 error4

NOTE2: While I was working on the bug mentioned at the beginning of this blogpost, I found that exec and safeexec don’t work properly within prompt() function

function prompt() { exec { cmd /c "echo message1" } }

And I didn’t find a way to fix it so I had to fix the corresponding problem differently.

Stay tuned

UPD: Yeah, I’ve managed to fix for prompt as well!

function Invoke-NativeApplication
{
    param
    (
        [ScriptBlock] $ScriptBlock,
        [int[]] $AllowedExitCodes = @(0)
    )

    $backupErrorActionPreference = $ErrorActionPreference

    $ErrorActionPreference = "Continue"
    try
    {
        if (Test-CalledFromPrompt)
        {
            $lines = & $ScriptBlock
        }
        else
        {
            $lines = & $ScriptBlock 2>&1
        }

        $lines | ForEach-Object -Process `
            {
                $isError = $_ -is [System.Management.Automation.ErrorRecord]
                "$_" | Add-Member -Name IsError -MemberType NoteProperty -Value $isError -PassThru
            }
        if ($AllowedExitCodes -notcontains $LASTEXITCODE)
        {
            throw "Execution failed with exit code $LASTEXITCODE"
        }
    }
    finally
    {
        $ErrorActionPreference = $backupErrorActionPreference
    }
}

function Invoke-NativeApplicationSafe
{
    param
    (
        [ScriptBlock] $ScriptBlock
    )

    Invoke-NativeApplication -ScriptBlock $ScriptBlock -AllowedExitCodes (0..255) | `
        Where-Object -FilterScript { -not $_.IsError }
}

function Test-CalledFromPrompt
{
    (Get-PSCallStack)[-2].Command -eq "prompt"
}

Set-Alias -Name exec -Value Invoke-NativeApplication
Set-Alias -Name safeexec -Value Invoke-NativeApplicationSafe

I found that if you are calling exec from prompt() redirection STDERR to STDOUT 2>&1 doesn’t work. But somehow it works without redirection. That’s weird but works.

Another funny thing is to determine if we are calling from prompt() or not. See function Test-CalledFromPrompt. It’s hacky but works :)

Stay tuned


Share this post on:

Previous Post
Execution of external commands (native applications) in PowerShell done right - Part 3
Next Post
Another pull request to Git Extensions had been approved