Proxy command that tees

For a PowerShell feature request (whether this is a good idea or not), I tried to create a prototype using a proxy command named Write-Table based on the Format-Table cmdlet that directly writes to the host and depending on the PassThru switch should pass the current input object:

function Write-Table {
    [CmdletBinding(HelpUri='https://go.microsoft.com/fwlink/?LinkID=2096703')]
    param(
        [switch]
        ${AutoSize},
    
        [switch]
        ${RepeatHeader},
    
        [switch]
        ${HideTableHeaders},
    
        [switch]
        ${Wrap},
    
        [Parameter(Position=0)]
        [System.Object[]]
        ${Property},
    
        [System.Object]
        ${GroupBy},
    
        [string]
        ${View},
    
        [switch]
        ${ShowError},
    
        [switch]
        ${DisplayError},
    
        [switch]
        ${Force},
    
        [ValidateSet('CoreOnly','EnumOnly','Both')]
        [string]
        ${Expand},
    
        [Parameter(ValueFromPipeline=$true)]
        [psobject]
        ${InputObject},
    
        [switch]
        ${PassThru})
    
    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
    
            if ($PSBoundParameters.TryGetValue('PassThru', [ref]$outBuffer))
            {
                $Null = $PSBoundParameters.Remove('PassThru')
            }
    
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Utility\Format-Table', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
    
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }
    
    process
    {
        try {
            if ($PassThru) { $_ }
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }
    
    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
    
    clean
    {
        if ($null -ne $steppablePipeline) {
            $steppablePipeline.Clean()
        }
    }
    <#
    
    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Format-Table
    .ForwardHelpCategory Cmdlet
    
    #>
}

But somehow it appears that the current object is removed from the pipeline before it is also passed to the host (I have no clue how to prevent that)

gci *.txt | Write-Table -PassThru | Get-Item # Final intention: ... | Remove-Item

    Directory: C:\Users\Gebruiker\Downloads\temp

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           7/20/2023  3:44 PM              0 file1.txt
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.FormatStartData' because it does not exist.
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.GroupStartData' because it does not exist.
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData' because it does not exist.
-a---           7/20/2023  3:44 PM              0 file2.txt
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData' because it does not exist.
-a---           7/20/2023  3:44 PM              0 file3.txt
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData' because it does not exist.
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.GroupEndData' because it does not exist.
Get-Item: Cannot find path 'Microsoft.PowerShell.Commands.Internal.Format.FormatEndData' because it does not exist.

Does anyone have an explanation where this goes sideways and how I might resolve this?

>Solution :

It seems a little update to the wrapped command should solve your problem if I’m understanding correctly, the output generated by Format-Table should go directly to the host, thus adding Out-Host. Then if -PassThru is present you more than likely want to send the original object thru the pipeline so:

function Write-Table {
    [CmdletBinding(HelpUri = 'https://go.microsoft.com/fwlink/?LinkID=2096703')]
    param(
        [switch]
        ${AutoSize},

        [switch]
        ${RepeatHeader},

        [switch]
        ${HideTableHeaders},

        [switch]
        ${Wrap},

        [Parameter(Position = 0)]
        [System.Object[]]
        ${Property},

        [System.Object]
        ${GroupBy},

        [string]
        ${View},

        [switch]
        ${ShowError},

        [switch]
        ${DisplayError},

        [switch]
        ${Force},

        [ValidateSet('CoreOnly', 'EnumOnly', 'Both')]
        [string]
        ${Expand},

        [Parameter(ValueFromPipeline = $true)]
        [psobject]
        ${InputObject},

        [switch]
        ${PassThru})

    begin {
        $null = $PSBoundParameters.Remove('PassThru')
        $scriptCmd = { Microsoft.PowerShell.Utility\Format-Table @PSBoundParameters | Out-Host }
        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
    }

    process {
        if ($PassThru.IsPresent) {
            $InputObject
        }

        $steppablePipeline.Process($InputObject)
    }

    end {
        $steppablePipeline.End()
    }

    clean {
        if ($null -ne $steppablePipeline) {
            $steppablePipeline.Clean()
        }
    }
    <#

    .ForwardHelpTargetName Microsoft.PowerShell.Utility\Format-Table
    .ForwardHelpCategory Cmdlet

    #>
}

Then something like this:

$capture = 0..5 | ForEach-Object { [pscustomobject]@{ Item = $_ } } |
    Write-Table -PassThru |
    ForEach-Object Item

Works just fine:

PS ..\pwsh> $capture = 0..10 | ForEach-Object { [pscustom....

# Output sent to the host:
Item
----
   0
   1
   2
   3
   4
   5

PS ..\pwsh> $capture
   
0
1
2
3
4
5
6
7
8
9
10

Leave a Reply