Mark Bertenshaw
Mark Bertenshaw

Reputation: 5689

Pipelining internally from one function to another

I have a module which has the following two functions, which are almost identical:

<#
    .SYNOPSIS
    Retrieves a VApp from VCloud.
#>
Function Get-VApp
{
    [CmdletBinding()]
    [OutputType([System.Xml.XmlElement])]
    Param(
        [Parameter(Mandatory = $true)]
        [System.Xml.XmlElement] $Session,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]] $VAppName
    )
    Begin {
        [System.Xml.XmlElement] $queryList = $Session.GetQueryList();
        [System.Xml.XmlElement[]] $vAppRecords = $queryList.GetVAppsByRecords().VAppRecord;
    }
    Process {
        ForEach ($VAN in $VAppName)
        {
            $vAppRecords |
            Where-Object { $_.name -eq $VAN } |
            ForEach-Object { $_.Get(); }
        }
    }
    End
    {
        #
    }
}

and

<#
    .SYNOPSIS
    Retrieves a VAppRecord from VCloud.
#>
Function Get-VAppRecord
{
    [CmdletBinding()]
    [OutputType([System.Xml.XmlElement])]
    Param(
        [Parameter(Mandatory = $true)]
        [System.Xml.XmlElement] $Session,
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]] $VAppName
    )
    Begin {
        [System.Xml.XmlElement] $queryList = $Session.GetQueryList();
        [System.Xml.XmlElement[]] $vAppRecords = $queryList.GetVAppsByRecords().VAppRecord;
    }
    Process {
        ForEach ($VAN in $VAppName)
        {
            $vAppRecords |
            Where-Object { $_.name -eq $VAN } |
            ForEach-Object { $_; }
        }
    }
    End
    {
        #
    }
}

Essentially, Get-VApp is like Get-VAppRecord, except that the former calls a Get() method on the returned object. This seems wasteful. If I wasn't bothering with pipelines, it would be easy:

Function Get-VApp
{
        [CmdletBinding()]
        [OutputType([System.Xml.XmlElement])]
        Param(
            [Parameter(Mandatory = $true)]
            [System.Xml.XmlElement] $Session,
            [Parameter(Mandatory = $true)]
            [string[]] $VAppName
        )
    Get-VAppRecord $Session $VAppName |
    ForEach-Object {
        $_.Get();
    }
}

But obviously the pipeline messes things up. I don't call the code in the Begin block multiple times for efficiency, and I would like to find a way to "play nice" with the pipeline without having to batch up records.

Upvotes: 1

Views: 130

Answers (1)

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174435

The SteppablePipeline class is designed for wrapping pipeline-enabled commands without messing up their pipeline support.

You don't even need to know how to set it up, ProxyCommand.Create() will generate the scaffolding for it!

So let's start out by creating a proxy function for Get-VAppRecord:

$GetVAppRecordCommand = Get-Command Get-VAppRecord
$GetVAppRecordCommandMetadata = [System.Management.Automation.CommandMetadata]::new($GetVAppRecordCommand)

# returns the body of the new proxy functions
[System.Management.Automation.ProxyCommand]::Create($GetVAppRecordCommandMetadata)

... and then we just need to add the Get() call in the process block of it:

function Get-VApp {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [System.Xml.XmlElement]
        ${Session},

        [Parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]
        [string[]]
        ${VAppName})

    begin
    {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-VAppRecord', [System.Management.Automation.CommandTypes]::Function)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($MyInvocation.ExpectingInput) # Many examples use $PSCmdlet; however setting this ensures that $steppablePipeline.Process() returns the output of the inner function.
        } catch {
            throw
        }
    }

    process
    {
        try {
            $steppablePipeline.Process($_) |ForEach-Object {
                # call Get() on the record
                $_.Get()
            }
        } catch {
            throw
        }
    }

    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
}

Upvotes: 2

Related Questions