Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active August 25, 2020 06:46
Show Gist options
  • Save Jaykul/63c380aa3b992b6444a040fe343becab to your computer and use it in GitHub Desktop.
Save Jaykul/63c380aa3b992b6444a040fe343becab to your computer and use it in GitHub Desktop.
You can redirect the other output streams like *>&1 | Out-String.ps1 and these commands will capture them labelled (and optionally, in color), e.g. for | more or | less

PowerShell has a problem with it's extra output streams. The actual content of the Warning, Verbose, Debug, Information, and even Error streams doesn't have the label text like "WARNING: " or "VERBOSE: " that we're used to seeing in the host. That label is actually added by the host (hopefully, in a culture-aware way). However, this means that when you attempt to redirect all of this output, for example by redirecting all output streams to stdout, with *>&1, you don't get labels on them at all, which is confusing, and can make the output difficult to comprehend.

Take for example a function that writes in a loop:

if ($i % 5 -eq 0) {
    Write-Output $i
} else {
    Write-Verbose $i
}

If you redirect Verbose to StdOut, then outside of the host, you can no longer tell which line was the output line(s), and which were verbose. So if you, for instance, pipe to | Out-String or to a file, the extra information is lost.

The functions in this WithOut module fix that in one of the ways it could be fixed -- not necessarily the best way, but one way.

So far I've wrapped Out-String and Out-File as those are the two I've used. The new functions have switches to turn on color (-VtColored), to convert errors to the detailed view (-ExtendedError) and to -AddTimestamps (they're only added to non-output lines, that is, to Verbose, etc).

Note that in PowerShell, only the standard output stream goes down the pipeline, so these functions only capture all the output when you redirect it, like this:

Test-Output *>&1 | Out-String -VtColored | less

Or like this:

Test-Output -Verbose -Debug -Skip 2 -First 5 *>&1 | Out-File log.ansi -VtColored -ExtendedError -AddTimestamps
function Test-Output {
[CmdletBinding(SupportsPaging)]
param(
$Total = 10
)
begin {
$DebugPreference = if ($DebugPreference -in "Continue", "Inquire") {
"Continue"
Get-Content $pwd # produces an error, for fun
}
}
end {
$Count = 0
$Shown = 0
$PSCmdlet.PagingParameters | Out-String | Write-Warning
while ($Count -lt $Total) {
$Count++
if ($PSCmdlet.PagingParameters.Skip -ge $Count) {
Write-Debug "Skipping $Count"
} elseif ($PSCmdlet.PagingParameters.First -le $Shown) {
Write-Debug "Complete Without $Count"
} else {
$Shown++
$Count
}
Write-Verbose "There were $Count"
}
if ($PSCmdlet.PagingParameters.IncludeTotalCount) {
$PSCmdlet.PagingParameters.NewTotalCount($Count, 1.0)
}
}
}
#requires -Module @{ ModuleName = "Pansies"; ModuleVersion = "2.0.0" }
filter ConvertOutputRecord {
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline)]$InputObject
)
if ($InputObject -is [System.Management.Automation.InformationalRecord]) {
$Type = ($InputObject.PSTypeNames[0] -replace '.*\.([^\.]*)Record$', '$1').ToUpper()
$InputObject = "${Type}: $InputObject".Trim()
if ($AddTimestamps) {
$InputObject = [DateTimeOffSet]::UtcNow.ToString("yyyy-MM-ddThh:mm:ss.fffffffZ ") + $InputObject
}
if ($VtColored) {
$InputObject = $(
# Put VT colors on each line, instead of just once, because `less` et. al. don't allow them to affect multiple lines?
foreach ($line in $InputObject -split "\n") {
"$(New-Text $line -BackgroundColor $Host.PrivateData."${Type}BackgroundColor" -ForegroundColor $Host.PrivateData."${Type}ForegroundColor")".Trim()
}
) -join "`n"
}
}
if ($InputObject -is [System.Management.Automation.ErrorRecord]) {
if ($ExtendedError) {
$InputObject = $InputObject | Get-Error | Microsoft.PowerShell.Utility\Out-String -Stream | ForEach-Object TrimEnd | Where-Object { $_ }
if ($AddTimestamps) {
$InputObject[0] = "$(New-Text ([DateTimeOffSet]::UtcNow.ToString("yyyy-MM-ddThh:mm:ss.fffffffZ")) -BackgroundColor $Host.PrivateData.ErrorBackgroundColor -ForegroundColor $Host.PrivateData.ErrorForegroundColor) " + ($InputObject[0] -replace "Exception", "EXCEPTION")
}
} else {
if ($AddTimestamps) {
$InputObject = [DateTimeOffSet]::UtcNow.ToString("yyyy-MM-ddThh:mm:ss.fffffffZ") + " ERROR: " + $InputObject
} else {
$InputObject = "ERROR: " + "$InputObject".Trim()
}
$InputObject = "$(New-Text $InputObject -BackgroundColor $Host.PrivateData.ErrorBackgroundColor -ForegroundColor $Host.PrivateData.ErrorForegroundColor)".Trim()
}
}
$InputObject
}
function Out-String {
[CmdletBinding(DefaultParameterSetName = 'NoNewLineFormatting', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2097024', RemotingCapability = 'None')]
param(
[Parameter(ParameterSetName = 'StreamFormatting')]
[switch]
${Stream},
[ValidateRange(2, 2147483647)]
[int]
${Width},
[Parameter(ParameterSetName = 'NoNewLineFormatting')]
[switch]
${NoNewline},
# Adds VT Escape Sequences to color VERBOSE, DEBUG, and WARNING messages
[Parameter()]
[switch]
${VtColored},
# Forces Errors to display in #PSExtendedError format (as seen in Get-Error)
[Parameter()]
[switch]
${ExtendedError},
[Parameter()]
[switch]
${AddTimestamps},
[Parameter(ValueFromPipeline = $true)]
[psobject]
${InputObject}
)
begin {
$null = $PSBoundParameters.Remove("VtColored")
$null = $PSBoundParameters.Remove("ExtendedError")
$null = $PSBoundParameters.Remove("AddTimestamps")
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Out-String', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = { & $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process {
try {
$steppablePipeline.Process((ConvertOutputRecord $_))
} catch {
throw
}
}
end {
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Out-String
.ForwardHelpCategory Cmdlet
#>
}
function Out-File {
[CmdletBinding(DefaultParameterSetName = 'ByPath', SupportsShouldProcess = $true, ConfirmImpact = 'Medium', HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2096621')]
param(
[Parameter(ParameterSetName = 'ByPath', Mandatory = $true, Position = 0)]
[Alias('Path')]
[string]
${FilePath},
[Parameter(ParameterSetName = 'ByLiteralPath', Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('PSPath', 'LP')]
[string]
${LiteralPath},
[Parameter(Position = 1)]
[ValidateNotNullOrEmpty()]
[System.Text.Encoding]
${Encoding},
[switch]
${Append},
[switch]
${Force},
[Alias('NoOverwrite')]
[switch]
${NoClobber},
[ValidateRange(2, 2147483647)]
[int]
${Width},
[switch]
${NoNewline},
# Adds VT Escape Sequences to color VERBOSE, DEBUG, and WARNING messages
[Parameter()]
[switch]
${VtColored},
# Forces Errors to display in #PSExtendedError format (as seen in Get-Error)
[Parameter()]
[switch]
${ExtendedError},
[Parameter()]
[switch]
${AddTimestamps},
[Parameter(ValueFromPipeline = $true)]
[psobject]
${InputObject})
begin {
$null = $PSBoundParameters.Remove("VtColored")
$null = $PSBoundParameters.Remove("ExtendedError")
$null = $PSBoundParameters.Remove("AddTimestamps")
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Out-File', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = { & $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process {
try {
$steppablePipeline.Process((ConvertOutputRecord $_))
} catch {
throw
}
}
end {
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Utility\Out-File
.ForwardHelpCategory Cmdlet
#>
}
Export-ModuleMember -Function *-*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment