Search code examples
powershellpipeline

Split PowerShell pipelines to separate files


During testing, a function I'm creating is outputting both standard as well as verbose output. Right now, those both end up in the same log file. I would like to split them up.

Take this example:

Function Test-Output {
    [CmdletBinding()]
    Param ()
    Get-Process PowerShell, none
    Write-Warning "Test!"
    Write-Verbose "Test Verbose"
    Write-Debug "Test Debug"
}

If I run it:

Test-Output -Verbose -Debug

...I get the error-stream for the none-process (pipeline 2):

Get-Process : Cannot find a process with the name "none". Verify the process name and call the cmdlet again.
At line:4 char:1
+ Get-Process PowerShell, none
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (none:String) [Get-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

...my success-stream (pipeline 1):

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    360      33    98736     109172   673     5.44   6220 powershell 

...the warning-stream (pipeline 3): WARNING: Test!

...the verbose-stream (pipeline 4): VERBOSE: Test Verbose

...and the debug-stream (pipeline 5): DEBUG: Test Debug

So far, the only (crude) solution I have found to get each stream into its own file is this (I have replaced the Out-File with Write-Output for convenience here):

Test-Output -ErrorVariable ErrorVar -WarningVariable WarningVar -OutVariable OutVar -Debug -Verbose *> $Null
Write-Output "Error : $ErrorVar"
Write-Output "Warning : $WarningVar"
foreach ($Record in $Outvar) {
    If ($Record.GetType().Name -eq 'VerboseRecord') { Write-Output "Verbose : $($Record.Message)" }
    ElseIf ($Record.GetType().Name -eq 'DebugRecord') { Write-Output "Debug : $($Record.Message)" }
    Else { $Record }
}

When I try simpler solutions, I end up missing the error- and warning-stream (and I would still have to split them up using a foreach like above):

Test-Output -Verbose -Debug *> $OutVar
# $OutVar contains Pipeline(1), Verbose(4) and Debug (5)
# Error(2) and Warning (3) seem to be gone (not counting $error[0])

Test-Output -Verbose -Debug 2>&1 3>&1 *> $OutVar
# Error(2) and Warning (3) still gone

Test-Output -Verbose -Debug *>&1 > $OutVar
# Same Thing

So far, Tee-Object doesn't seem to do exactly what I want either.

So my question is: am I thinking too complex here, is there a simpler solution? If so, what?


Solution

  • One option is to use a runspace. Then you can handle the output and each of the stream outputs separately:

    Function Test-Output {
    [CmdletBinding()]
    Param ()
    
    $Code = {
    $VerbosePreference,$WarningPreference,$DebugPreference = 'Continue'
    Get-Process PowerShell, none
    Write-Warning "Test!"
    Write-Verbose "Test Verbose"
    Write-Debug "Test Debug"
    }
    
    $newPowerShell = [PowerShell]::Create().AddScript($code)
    $job = $newPowerShell.BeginInvoke()
    While (-Not $job.IsCompleted) {}
    
    [PSCustomObject]@{
      OutPut  = $newPowerShell.EndInvoke($job)
      Verbose = $newPowerShell.Streams.Verbose.ReadAll()
      Warning = $newPowerShell.Streams.Warning.ReadAll()
      Error   = $newPowerShell.Streams.Error.ReadAll()
     }
    
     $newPowerShell.Dispose()
    }
    
    $Result = Test-Output 
    

    That will return a custom object to $Result, with separate properties for each of the streams from the invocation.