Powershell: Pipe the output to a method and separate the error stream

I’m trying to pipe the output of my script to a separate method and trying to separate the error stream there. Here is the pipeline.ps1

Function MyLogs{
    [CmdletBinding()]
    Param(
      [Parameter(ValueFromPipeline=$True)]
      [String[]] $Log
    )
  
    Begin {
       Write-Verbose "initialize stuff"
       # code to initialize stuff
    }
  
    Process {
       Write-Output " $Log"
       
    }
  
    End {    
       # code to clean stuff
    }
 }

#--- pipe the script output to it
& .\MyScript.ps1 | MyLogs

And here is MyScript.ps1

Write-Output "********** This is a normal text message ************* `n`r " 

# this would create a divide by zero error
1/0

Write-Warning "example warning"

Write-Output "after the errors"

The call & .\MyScript.ps1 | MyLogs would not pipe the error stream to MyLogs() however, errors would be displayed in the console:

PS D:\Learn\powershell> .\pipelines.ps1
VERBOSE: initialize stuff
 ********** This is a normal text message *************  
 
Attempted to divide by zero.
At D:\Learn\powershell\MyScript.ps1:3 char:1
+ 1/0
+ ~~~
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException
 

WARNING: example warning
 after the errors

And if I do like & .\MyScript.ps1 *>&2 | MyLogs, the error stream would be read as a normal text and I can’t figure-out how to separate the errors from normal text.

>Solution :

Mainly, you would need to redirect all streams (*>&1) or at least the Error Stream to the Success Stream (2>&1) so that your function can capture the objects coming from pipeline. In addition, your function’s parameter should take [object] or [object[]] instead of [string[]] so that you can have a reference of what type of object is coming from the pipeline.

Lastly, with the help of -is type comparison operator and a switch you can create a nice logging function.

using namespace System.Management.Automation

function MyLogs {
    [CmdletBinding()]
    Param(
      [Parameter(ValueFromPipeline = $True)]
      [object] $Log
    )

    begin {
       # code to initialize stuff
    }
    process {
        # this could be reduced to:
        # `@{ $Log.GetType().Name = $Log }`
        switch($Log) {
            { $_ -is [ErrorRecord] } {
                @{ 'Error Record' = $_ }
                break
            }
            { $_ -is [WarningRecord] } {
                @{ 'Warning Record' = $_ }
                break
            }
            { $_ -is [VerboseRecord] } {
                @{ 'Verbose Record' = $_ }
                break
            }
            { $_ -is [InformationRecord] } {
                @{ 'Information Record' = $_ }
                break
            }
            Default {
                @{ 'Success Stream' = $_ }
            }
        }
    }
    end {
       # code to clean stuff
    }
}

& {
    Write-Output 'This is a normal text message'
    1/0
    Write-Host 'Information here...'
    Write-Warning 'example warning'
    Write-Output 'after the errors'
    Write-Verbose 'Hello world!' -Verbose
} *>&1 | MyLogs

I’ll leave it to you further updates as an exercise, this is the output you can expect from this example:

Name                           Value
----                           -----
Success Stream                 This is a normal text message
Error Record                   Attempted to divide by zero.
Information Record             Information here...
Warning Record                 example warning
Success Stream                 after the errors
Verbose Record                 Hello world!

Leave a Reply