Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active August 17, 2026 04:25
Show Gist options
  • Select an option

  • Save jborean93/4c48bc95a3d9df26f796ef094b2cf56e to your computer and use it in GitHub Desktop.

Select an option

Save jborean93/4c48bc95a3d9df26f796ef094b2cf56e to your computer and use it in GitHub Desktop.
Check to see if the output is being captured.
# Copyright: (c) 2026, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
function Test-IsOutputCaptured {
<#
.SYNOPSIS
Tests if output is being captured by PowerShell or not.
.DESCRIPTION
This is used to see if the output is being captured or piped in any way
other than the default '| Out-Default' that PowerShell has. It replicates
the same logic that the NativeCommandProcessor does to determine whether to
redirect the stdout pipe of the subprocess it spawns or not.
.EXAMPLE
Test-IsOutputCaptured
# False
& { Test-IsOutputCaptured }
# False
This shows how the output is not considered to be captured when there is no
subsequent pipelines, variables, or redirection involved in the statement.
.EXAMPLE
Test-IsOutputCaptured | Out-String
# True
$var = Test-IsOutputCaptured
$var
# True
$var = & { Test-IsOutputCaptured }
$var
# True
& { Test-IsOutputCaptured } | Out-String
# True
This shows the various ways in which the output is considered to be
captured. Note it takes into account any downstream pipelining or
variables and not just the current statement.
.NOTES
This uses reflection to access non-public APIs. PowerShell does not expose
a public API to determine this. This may break in future PowerShell
versions if the API ever changes
#>
[OutputType([bool])]
[CmdletBinding()]
param ()
# If you truly wish to use this logic, extract this out into your function
# and use as you wish.
try {
$instancePrivate = [System.Reflection.BindingFlags]'Instance, NonPublic'
$staticPrivate = [System.Reflection.BindingFlags]'NonPublic, Static'
# First check can use public APIs, we check whether we are the last
# command in the current pipeline. If we aren't then the output is
# being piped/captured into the downstream command.
$pipelinePosition = $MyInvocation.PipelinePosition
$pipelineLength = $MyInvocation.PipelineLength
Write-Verbose "PipelinePosition [$pipelinePosition] - PipelineLength [$pipelineLength]"
if ($pipelinePosition -ne $pipelineLength) {
return $true
}
# Check if we are running in a PSRemoting session, this is treated as
# always being captured due to output going over the PSRemoting output
# payload and handled client side.
$nativeCommandProcessorType = [PSObject].Assembly.GetType(
'System.Management.Automation.NativeCommandProcessor')
$isServerSideProp = $nativeCommandProcessorType.GetProperty(
'IsServerSide',
$staticPrivate)
if (-not $isServerSideProp) {
throw "Failed to find NativeCommandProcessor.IsServerSide property"
}
$isServerSide = $isServerSideProp.GetValue($null)
Write-Verbose "NativeCommandProcess.IsServerSide [$isServerSide]"
if ($isServerSide) {
return $true
}
# Next check sees what the DownstreamCmdlet of the OutputPipe is set
# to. This is not a public API and is based on the logic in
# NativeCommandProcessor.IsDownstreamOutDefault.
$cmdRuntime = $PSCmdlet.CommandRuntime
$outputPipeProp = $cmdRuntime.GetType().GetProperty('OutputPipe', $instancePrivate)
if (-not $outputPipeProp) {
throw "Failed to find `$PSCmdlet.CommandRuntime OutputPipe property"
}
$outputPipe = $outputPipeProp.GetValue($cmdRuntime)
$downstreamCmdletProp = $outputPipe.GetType().GetProperty('DownstreamCmdlet', $instancePrivate)
if (-not $downstreamCmdletProp) {
throw "Failed to find OutputPipe DownstreamCmdlet property"
}
$downstreamCmdlet = $downstreamCmdletProp.GetValue($outputPipe)
# Now we have the DownstreamCmdlet we do a few checks. First sees if
# there is a downstream cmdlet. No downstream cmdlet means something
# is either capturing the output or it is redirected elsewhere.
if (-not $downstreamCmdlet) {
Write-Verbose "No downstream cmdlet detected, output is being stored in var or redirected"
return $true
}
# If there is a downstream cmdlet it means that somewhere down the
# chain this is being piped to something,
# e.g. & { Test-IsOutputCaptured } | ...
# We expect the implicit '| Out-Default' done by PowerShell that is
# always present, otherwise anything else is treated as being captured.
$cmdInfoProp = $downstreamCmdlet.GetType().GetProperty('CommandInfo', $instancePrivate)
if (-not $cmdInfoProp) {
throw "Failed to find DownstreamCmdlet CommandInfo property"
}
$cmdProp = $downstreamCmdlet.GetType().GetProperty('Command', $instancePrivate)
if (-not $cmdProp) {
throw "Failed to find DownstreamCmdlet Command property"
}
# PowerShell also checks whether transcription is enabled and treats
# that as output being captured.
$cmdInfo = $cmdInfoProp.GetValue($downstreamCmdlet)
$cmd = $cmdProp.GetValue($downstreamCmdlet)
Write-Verbose "Downstream Cmdlet Name [$($cmdInfo.Name)]"
Write-Verbose "Downstream Cmdlet Parameters Set [$($cmd.MyInvocation.BoundParameters.Keys -join ", ")]"
return $cmdInfo.Name -ne 'Out-Default' -or
$cmd.MyInvocation.BoundParameters.ContainsKey('Transcript')
}
catch {
$PSCmdlet.ThrowTerminatingError($_)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment