Powershell: Cannot redirect streams to anything else than output stream 1

Created on 23 Aug 2018  路  5Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

When running PowerShell, I sometimes need to redirect all "log" streams (including the information stream) to STDERR. For this, I am trying to redirect the information stream 6 to the warning stream 3:

'test' 6>&3

I also tried redirecting all streams:

'test' *>&3

And redirecting to the error stream:

'test' 6>&2
'test' *>&2



md5-18dd765b5337be82e320d4190d4c540e



At line:1 char:10
+ 'test' 6>&3
+          ~
Missing file specification after redirection operator.
+ CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingFileSpecification



md5-9665222a0a488f7b7cab23bae7854b38




Redirecting to stream 2 fails with:



md5-ac595b8d3d7256306e834bef08f7d516



At line:1 char:8

  • 'test' *>&2
  • ~~~~
    The '*>&2' operator is reserved for future use.
  • CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
  • FullyQualifiedErrorId : RedirectionNotSupported
The only thing that works properly is redirecting to the output stream like `*>&1` or `6>&1`, which makes `*>&1 | Write-Warning` possible as a warning, but that will put _everything_ on the warning stream, including the _actual output_ of the function.

Environment data
----------------

<!-- provide the output of $PSVersionTable -->

```powershell
> $PSVersionTable
Name                           Value
----                           -----
PSVersion                      6.0.2
PSEdition                      Core
GitCommitId                    v6.0.2
OS                             Darwin 17.7.0 Darwin Kernel Version 17.7.0: Thu Jun 21 22:53:14 PDT 2018; root:xnu-4570.71.2~1/...
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
Issue-Enhancement WG-Engine

Most helpful comment

@felixfbecker Something to keep in mind: in traditional text-based shells, the various output streams are just undifferentiated text so it doesn't matter what goes where.. In PowerShell, the streams are strongly typed. The error stream only contains ErrorRecords, the VerboseStream only contains VerboseRecords, etc. The only polymorphic stream is the output stream so it's the only one where merge semantics are clear. That said, the higher-level scenario is important and we should have a way to accommodate it. One of the ideas we had in the past is to allow you to redirect into an action (scriptblock) and the action can do things like logging, etc. Anyway, I'm going to mark this as an enhancement request.

All 5 comments

Linked documentation only show three forms of redirection operator: n>, n>> and n>&1. There are no documented operators n>&2 or n>&3.

That's true, but it's not very obvious, especially when the same operator in bash works for stderr too (>&2). The error messages are also confusing.

In Linux programs, often STDERR is used as the logging stream, especially if STDOUT is speaking an RPC protocol, e.g. needs to be valid JSON. PowerShell currently can't really be used for this because any information or warning logs will get sent to STDOUT.

@felixfbecker Something to keep in mind: in traditional text-based shells, the various output streams are just undifferentiated text so it doesn't matter what goes where.. In PowerShell, the streams are strongly typed. The error stream only contains ErrorRecords, the VerboseStream only contains VerboseRecords, etc. The only polymorphic stream is the output stream so it's the only one where merge semantics are clear. That said, the higher-level scenario is important and we should have a way to accommodate it. One of the ideas we had in the past is to allow you to redirect into an action (scriptblock) and the action can do things like logging, etc. Anyway, I'm going to mark this as an enhancement request.

That makes sense - in a way, it reminds me of ReactiveX Materialize - redirecting all event types to the primary stream, while maintaining the information about where it came from through the type info.

You should already be able to pipe it further into ForEach-Object and check if $_ is ErrorRecord, InformationRecord, and depending on the type, call Write-Error, Write-Warning, etc.

Here is an implementation of Dematerialize in PowerShell:

filter Out-StreamByType {
    if ($_ -is [ErrorRecord]) {
        $e = [ErrorRecord]$_
        Write-Error -ErrorRecord $e
    } elseif ($_ -is [InformationRecord]) {
        $i = [InformationRecord]$_
        Write-Information -MessageData $i.MessageData -Tags $i.Tags
    } elseif ($_ -is [WarningRecord]) {
        $w = [WarningRecord]$_
        Write-Warning -Message $w.Message
    } elseif ($_ -is [VerboseRecord]) {
        $v = [VerboseRecord]$_
        Write-Verbose -Message $v.Message
    } elseif ($_ -is [DebugRecord]) {
        $d = [DebugRecord]$d
        Write-Debug -Message $d.Message
    } else {
        $_
    }
}

If we extend this, we can write a function that redirects output streams arbitrarily:

# Writes the input to the given target stream
function Out-TargetStream {
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateRange(1, 6)]
        [int] $Target,

        # A union of all parameters of the Write cmdlets
        # Error
        [Parameter(ValueFromPipeline)]
        [ErrorRecord] $ErrorRecord,

        # Debug, Verbose, Warning
        [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)]
        [Alias('MessageData')] # Information
        $Message,

        # Information
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]] $Tags
    )
    switch ($Target) {
        1 { $Message }
        2 {
            if ($null -ne $ErrorRecord) {
                Write-Error -ErrorRecord $ErrorRecord
            } else {
                Write-Error -Message $Message
            }
        }
        3 { Write-Warning -Message $Message }
        4 { Write-Verbose -Message $Message }
        5 { Write-Debug -Message $Message }
        6 { Write-Information -MessageData $Message -Tags $Tags }
    }
}

# Redirects streams to other streams
filter Out-Stream {
    param(
        [int] $SuccessTarget = 1,
        [int] $ErrorTarget = 2,
        [int] $WarningTarget = 3,
        [int] $VerboseTarget = 4,
        [int] $DebugTarget = 5,
        [int] $InformationTarget = 6
    )
    if ($_ -is [ErrorRecord]) {
        $_ | Out-TargetStream -Target $ErrorTarget
    } elseif ($_ -is [InformationRecord]) {
        $_ | Out-TargetStream -Target $InformationTarget
    } elseif ($_ -is [WarningRecord]) {
        $_ | Out-TargetStream -Target $WarningTarget
    } elseif ($_ -is [VerboseRecord]) {
        $_ | Out-TargetStream -Target $VerboseTarget
    } elseif ($_ -is [DebugRecord]) {
        $_ | Out-TargetStream -Target $DebugTarget
    } else {
        $_ | Out-TargetStream -Target $SuccessTarget
    }
}

This makes use of PowerShell's ValueFromPipelineByPropertyName and ValueFromPipeline handling to automatically destructure Record objects from the pipeline into common properties (e.g. Message) and passing those into the required Write cmdlets as asked for by the user. Some properties may get lost (e.g. error -> warning, or information -> verbose). If the types are incompatible, e.g. an object in the success stream -> verbose stream, it will be coerced to a string by PowerShell automatically (calling .ToString()).

Usage:

# Redirect error to warning stream, and warning to verbose
Write-Error "test" *>&1 | Out-Stream -ErrorTarget 3 -WarningTarget 4

# Redirect output to information stream (useful for executing native commands!)
echo "Doing operation..." | Out-Stream -SuccessTarget 6

This is basically what I would expect the redirection operator to be able to do. PowerShell is a very dynamic language and has automatic type coercion, so it should be able to automatically coerce types between streams.

@felixfbecker Something to keep in mind: in traditional text-based shells, the various output streams are just undifferentiated text so it doesn't matter what goes where.. In PowerShell, the streams are strongly typed. The error stream only contains ErrorRecords, the VerboseStream only contains VerboseRecords, etc. The only polymorphic stream is the output stream so it's the only one where merge semantics are clear. That said, the higher-level scenario is important and we should have a way to accommodate it. One of the ideas we had in the past is to allow you to redirect into an action (scriptblock) and the action can do things like logging, etc. Anyway, I'm going to mark this as an enhancement request.

Good explanation. But I think it's better put into the document so that we don't get confused by the error.

Was this page helpful?
0 / 5 - 0 ratings