Skip to content

Out-Default -Transcript only suppresses the display of the success output stream #11135

Closed

Description

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.

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Nov 20, 2019
  2. SeeminglyScience commented on Nov 20, 2019

    @SeeminglyScience
    Contributor

    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:

    # "$null =" because -Transcript also works similar to PassThru
    $null = (Get-Item DoesNotExist) *>&1 | Out-Default -Transcript
  3. mklement0 commented on Nov 20, 2019

    @mklement0
    ContributorAuthor

    Thanks, Patrick Meinecke (@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)
  4. SeeminglyScience commented on Nov 20, 2019

    @SeeminglyScience
    Contributor

    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:

    switch (this.WriteStream)
    {
    case WriteStreamType.Error:
    _console.WriteErrorLine(s);
    break;
    case WriteStreamType.Warning:
    _console.WriteWarningLine(s);
    break;
    case WriteStreamType.Verbose:
    _console.WriteVerboseLine(s);
    break;
    case WriteStreamType.Debug:
    _console.WriteDebugLine(s);
    break;
    default:
    // If the host is in "transcribe only"
    // mode (due to an implicitly added call to Out-Default -Transcribe),
    // then don't call the actual host API.
    if (!_console.TranscribeOnly)
    {
    _console.WriteLine(s);
    }
    break;
    }

  5. mklement0 commented on Nov 20, 2019

    @mklement0
    ContributorAuthor

    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...

  6. mklement0 commented on Nov 20, 2019

    @mklement0
    ContributorAuthor

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

  7. SeeminglyScience commented on Nov 20, 2019

    @SeeminglyScience
    Contributor

    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.

  8. mklement0 commented on Nov 20, 2019

    @mklement0
    ContributorAuthor

    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

    Steve Lee (@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?

  9. SeeminglyScience commented on Nov 20, 2019

    @SeeminglyScience
    Contributor

    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.

  10. SeeminglyScience commented on Nov 20, 2019

    @SeeminglyScience
    Contributor

    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.

  11. BrucePay commented on Nov 21, 2019

    @BrucePay
    Collaborator

    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.)

  12. mklement0 commented on Nov 21, 2019

    @mklement0
    ContributorAuthor

    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 MicrosoftDocs/PowerShell-Docs#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.
    • Patrick Meinecke (@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, Steve Lee (@SteveL-MSFT) and Bruce Payette (@BrucePay): Can you confirm that Patrick Meinecke (@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.


    Bruce Payette (@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.

  13. SteveL-MSFT commented on Nov 21, 2019

    @SteveL-MSFT
    Member

    Michael Klement (@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; }
  14. mklement0 commented on Nov 21, 2019

    @mklement0
    ContributorAuthor

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

    So it sounds like Patrick Meinecke (@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.

  15. SeeminglyScience commented on Nov 21, 2019

    @SeeminglyScience
    Contributor
    • 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.

  16. mklement0 commented on Nov 21, 2019

    @mklement0
    ContributorAuthor

    Good point, Patrick Meinecke (@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.

  17. SeeminglyScience commented on Nov 22, 2019

    @SeeminglyScience
    Contributor

    Yeah something like that. Maybe borrow some wording from the remarks section of this:

    /// <summary>
    /// Serves as a property implemented with getter and setter scripts.
    /// </summary>
    /// <remarks>
    /// It is permitted to subclass <see cref="PSScriptProperty"/>
    /// but there is no established scenario for doing this, nor has it been tested.
    /// </remarks>
    public class PSScriptProperty : PSPropertyInfo

  18. mklement0 commented on Nov 22, 2019

    @mklement0
    ContributorAuthor

    Thanks, Patrick Meinecke (@SeeminglyScience).

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

    Time to close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aWG-Enginecore PowerShell engine, interpreter, and runtimeWG-Interactive-Consolethe console experience

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions