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: Suprisingly 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-Called FromPrompt. It’s hacky but works 🙂

Stay tuned

Advertisements

About mnaoumov

Senior .NET Developer in Readify
This entry was posted in Uncategorized. Bookmark the permalink.

2 Responses to Execution of external commands (native applications) in PowerShell done right – Part 2

  1. Pingback: Execution of external commands (native applications) in PowerShell done right – Part 3 | mnaoumov.NET

  2. Pingback: Execution of external commands in PowerShell done right | mnaoumov.NET

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s