Powershell: Out-Default -Transcript only suppresses the display of the success output stream

Created on 20 Nov 2019  路  17Comments  路  Source: PowerShell/PowerShell

The purpose of Out-Default -Transcript is to record its input in the transcript _only_, without also _printing it to the host_ (console). [_Update_: At least that's the _inferred_ purpose, based on its behavior with success output].

Given that _all_ output streams are transcribed, it would make sense to also suppress host output for _all_ streams. However, only the _success_ output stream is currently silenced.

Steps to reproduce

$file = [IO.Path]::GetTempFileName()
Start-Transcript $file

Get-Item /, /NoSuch | Out-Default -Transcript

Stop-Transcript
Remove-Item $file

Expected behavior

The Get-Item command should produce no output.

Actual behavior

Only Get-Item success output is silenced, but the error message still displays:

Get-Item: Cannot find path '/NoSuch' because it does not exist.

Environment data

PowerShell Core 7.0.0-preview.5

Windows PowerShell is equally affected.

Issue-Question WG-Engine WG-Interactive-Console

All 17 comments

Note that this and the other related issues are likely due to exactly how Out-Default is utilized by the host. Whatever PSHost is currently in control of the REPL loop typically takes the typed string and creates a merged output Command from it. Out-Default is then added to the pipeline.

Here is more or less how both PowerShellEditorServices and pwsh's ConsoleHost handle pipeline creation:

using namespace System.Management.Automation
using namespace System.Management.Automation.Runspaces

function Invoke-HostLikePipeline {
    param([string] $InputString)
    end {
        $command = [Command]::new(
            <# command:       #> $InputString,
            <# isScript:      #> $true,
            <# useLocalScope: #> $false)

        $command.MergeMyResults(
            <# myResult: #> [PipelineResultTypes]::All,
            <# toResult: #> [PipelineResultTypes]::Output)

        $ps = $null
        try {
            $ps = [powershell]::Create([RunspaceMode]::CurrentRunspace)
            $null = $ps.Commands.
                AddCommand($command).
                AddCommand('Out-Default')

            $ps.Invoke()
        } finally {
            if ($null -ne $ps) {
                $ps.Dispose()
            }
        }
    }
}

I imagine you can also just do a standard redirect, I'm not sure if there would be any behavioral differences:

```powershell

"$null =" because -Transcript also works similar to PassThru

$null = (Get-Item DoesNotExist) *>&1 | Out-Default -Transcript

Thanks, @SeeminglyScience, that's helpful background information.

Re:

$null = (Get-Item DoesNotExist) *>&1 | Out-Default -Transcript

Unfortunately, this workaround still doesn't manage to silence the display of _all_ streams, which is the intent here: the error still prints.

That -Transcript _shouldn't_ pass streams through _to the host_ is the reason for creating this issue.

If you _omit_ the $null =, *>&1 then causes all streams other than the success streams to produce _duplicate_ host output.

PS> Write-Verbose -Verbose hi *>&1 | Out-Default -Transcript
VERBOSE: hi
VERBOSE: hi

The only scenario in which the *>&1 workaround _is_ effective is for _external programs_ (no $null = needed):

PS> cmd /c 'echo hi & nosuch' *>&1 | Out-Default -Transcript
# No output, as intended (but the output was transcribed)

Tbh I thought that parameter was meant to be used by application hosts, not necessarily for scripts. Do you know of a place that this use case is documented?

FWIW: It seems very purposeful that the other streams are not affected:

https://github.com/PowerShell/PowerShell/blob/2c8955eaebfa147e6973e24ef8495afc6d6ef104/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs#L310-L338

No, the Out-Default help topic is woefully terse.

Thanks for the source-code link; yes, it looks like it's intentional, but I think that behavior is problematic:

  • (a) conceptually, for its asymmetry: if _all_ streams are being transcribed, why would you only display-silence _one_ of them?

  • (b) due to the inability to display-silence the other streams even with workarounds.

Do you know how application hosts use it, and if changing the behavior to display-silence _all_ streams would be more than a hypothetical breaking change?

Of course, one way to resolve this would be to document the limitation and move on...

P.S.: It's perfectly possible that -Transcript was never designed for the use case I have in mind - again, documentation would help.

No, the Out-Default help topic is woefully terse.

Thanks for the source-code link; yes, it looks like it's intentional, but I think that behavior is problematic:

  • (a) conceptually, for its asymmetry: if _all_ streams are being transcribed, why would you only display-silence _one_ of them?

I presume it's because they're displaying the output stream in a different way. Like I said -Transcript also works like -PassThru does for most cmdlets.

Lets say there is a host application that displays output in a table, sorta like Out-GridView. In order for it to do that it would need the objects returned as they are. Typically, when the host is told to write output lines, it's already been transformed into strings by the format engine. With this switch, it can write the output to the transcript as if it were a normal console, while still displaying the results how it wants to.

In that scenario, the host is very unlikely to actually want a full ErrorRecord, it's probably perfectly happy just writing that error message out in a box somewhere in the same format it would be written to the console.

  • (b) due to the inability to display-silence the other streams even with workarounds.

Well sorta. The example I gave would still work if you added | Where-Object { $_ -isnot [ErrorRecord] } and etc for the other streams.

Do you know how application hosts use it, and if changing the behavior to display-silence _all_ streams would be more than a hypothetical breaking change?

Yeah probably. I don't think that parameter was meant to be used by anything other than hosts. In my opinion, it only makes sense for hosts because the intent is that the output is displayed some other way. A transcript is meant to be a recording of a session, purposefully avoiding that seems counter to the point of it to me. Are you trying to use transcripts for script logging?

Of course, one way to resolve this would be to document the limitation and move on...

Honestly I'd say throw DontShowAttribute on it, assuming that my understanding of it's purpose is correct. If this parameter doesn't make sense for any consumer other than a host application, maybe it shouldn't show up in tab completion.

Yeah, the use case I inferred is definitely unusual, and arguably it's at odds with the idea of a transcript, as you indicate.

With this switch, it can write the output to the transcript as if it were a normal console

Note, however, that _success_ output is _silenced_ - which is what made me infer the silence-all-display-output-and-transcribe-only behavior.

PS> 'hi' | Out-Default -Transcript
# NO output

@SteveL-MSFT, can you or a subject-matter expert shed light on the purpose of -Transcript?

Is excluding it from tab completion and documenting it as not for use by end users the right way to go?

Yeah, the use case I inferred is definitely unusual, and arguably it's at odds with the idea of a transcript, as you indicate.

With this switch, it can write the output to the transcript as if it were a normal console

Note, however, that _success_ output is _silenced_ - which is what made me infer the silence-all-display-output-and-transcribe-only behavior.

PS> 'hi' | Out-Default -Transcript
# NO output

Yeah because that's the only stream it actually returns:

$a = 'hi' | Out-Default -Transcript
$a
# outputs normally

That's what makes the example I gave work. You take responsibility for displaying the output stream, but you still write to the transcript as if it were displayed normally.

Just to be perfectly clear though, I've never once used it nor have I ever even seen it used. I'm realizing my wording may have implied differently. This is all just what I'm inferring based on it's behavior of the switch and the design of transcripts.

Some context: when we were designing the console host experience we realized that we had a problem. With split streams, displaying them individually (I.e. separated) meant that you wouldn't get errors in context which was bad for a console experience. To solve this we added a feature in the pipeline processor to merge the error output from all of the prior commands into the output stream. Then we created Out-Default to take the merged output, demultiplex the records and render them appropriately. This cmdlet was really only intended to be used by the host. I assume (but don't know) since everything went through Out-Default this was the natural place to insert transcription.

(Note: none of this applied to native commands in the terminal position in a pipeline. In that case, the subprocess received the console host io handles allowing them to write directly to a console. This was necessary so that things like vim.exe could work from PowerShell.exe. This also meant that output from native commands in this situation was not captured in the transcript.)

Taking a step back:

11134 and this issue are ultimately (also) about asking the following questions:

  • (a) Does it ever make sense to call Out-Default directly?
  • (b) If so, under what circumstance, with what parameters, specifically?

And to then properly document the findings in the Out-Default help topic: see https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5131

It seems that (b) can be answered for _parameter-less_ use of Out-Default: do not use it as an end user - the primary reason it exists as a user-callable cmdlet is so you can _override it_ with a custom implementation.

Additionally, we need to establish whether it makes sense for end users to call Out-Default with -Transcript (its only cmdlet-specific parameter):

  • I had _inferred_ the purpose of -Transcript as allowing end users to write to the transcript _only_, without also printing to the host.

    • You _can_ use it that way, but only for the _success_ output stream.
    • Based on my assumption about the purpose, I asked for _all_ streams to be host-silenced, to better embody that purpose.
  • @SeeminglyScience provided an alternative explanation, which sounds more plausible to me: that -Transcript is really meant for use by application hosts; to quote him: "You take responsibility for displaying the output stream, but you still write to the transcript as if it were displayed normally."

Therefore, @SteveL-MSFT and @BrucePay: Can you confirm that @SeeminglyScience's interpretation is correct or clarify the true purpose of -Transcript, so we can guide users properly in the documentation?

If we find that direct calls to Out-Default _never_ make sense, we can close this issue; unfortunately, #11134 remains an issue either way, because it also affects Out-Host, for which there _are_ legitimate uses.


@BrucePay

none of this applied to native commands in the terminal position in a pipeline.

It does apply to native commands in the context of _transcribing_, however, as evidenced by stderr output _by default_ properly showing up in transcripts.

In other words: while transcribing, the direct-to-host behavior is intentionally _disabled_ so as to be able to capture all output in the transcript.

Another way of demonstrating that is to start a transcript and then try to invoke vim; what you'll see is the following warning (on macOS), followed by a blank line:

Vim: Warning: Output is not to a terminal

(Type :q<Enter> to exit Vim or buy
image
).

As an aside: Note that after exiting Vim in this scenario the PSReadline bindings (arrow keys) don't work until you run cls.

@mklement0 the PSReadLine issue should be fixed in PSRL 2.0.0-beta6

From the source code:

        /// <summary>
        /// Determines whether objects should be sent to API consumers.
        /// This command is automatically added to the pipeline when PowerShell is transcribing and
        /// invoked via API. This ensures that the objects pass through the formatting and output
        /// system, but can still make it to the API consumer.
        /// </summary>
        [Parameter()]
        public SwitchParameter Transcript { get; set; }

Thank you, @SteveL-MSFT - embarrassingly, it hadn't even occurred to me to look there.

So it sounds like @SeeminglyScience was correct.

Regarding updating the Out-Default help topic: Do you agree that we should therefore say:

  • Out-Default is used internally and should not be called directly.

  • The only reason it exists as a public cmdlet is so you can override it with a custom implementation.

  • Out-Default is used internally and should not be called directly.

I wouldn't use the term internally. It's intended to be used by host applications. Said host applications do not have to be made internally for it's use to be considered supported.

Good point, @SeeminglyScience: How about:

Out-Default is only meant to be used by applications that implement a PowerShell host, including the hosts that are built into PowerShell itself.

Thanks, @SeeminglyScience.

I've updated the suggested wording at https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5131, and I invite y'all to take a look.

Time to close this issue.

Was this page helpful?
0 / 5 - 0 ratings