Search code examples
powershellpipelinesort-object

Pipeline in Powershell


I was reading about how pipeline works in PowerShell at about_Pipelines, and got to know that pipeline delivers one object at a time.

So, this

Get-Service | Format-Table -Property Name, DependentServices

Is different from this

Format-Table -InputObject (Get-Service) -Property Name, DependentServices

So here, going by the explanation, in the first case, the Format-Table works on one object at at time and in the second example, Format-Table works on an array of objects. Please correct me if I am wrong.

If this is the case, then I wonder how does Sort-Object and other cmdlets that need to work on collections of data work with pipe character.

When I do :

Get-Service | Sort-Object

How is Sort-Object able to sort if it just gets to work with one object at a time. So, assume there are 100 service objects that are to be passed to Sort-Object. Will Sort-Object be called 100 times (each for one object) ? And, How will that yield in Sorted results that I see on the screen.


Solution

  • Sort-Object (and other cmdlets that need to evaluate all input objects before outputting anything) work by collecting the input objects one by one, and then not doing any actual work until the upstream cmdlet (Get-Service in this case) is done sending input.

    How does this work? Well, let's try and recreate Sort-Object with a PowerShell function.

    To do so, we first need to understand that a cmdlet consists of 3 separate routines:

    • Begin - the Begin routines of each cmdlet in a pipeline are invoked once before anything else occurs
    • Process - this routine is invoked on each cmdlet every time input is received from an upstream command
    • End - this is invoked once the upstream command has called End and there are no more input items for Process to process

    (These are the block label names used in PowerShell function definitions - in a binary cmdlet you'd override the implementation of BeginProcessing, ProcessRecord, EndProcessing methods of your cmdlet)

    So, to "collect" every input item, we need to add some logic to the Process block of our command, and then we can put the code that operates on all the items in the End block:

    function Sort-ObjectCustom
    {
      param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [object[]]$InputObject
      )
    
      begin {
        # Let's use the `begin` block to create a list that'll hold all the input items
        $list = [System.Collections.Generic.List[object]]::new()
    
        Write-Verbose "Begin was called"
      }
    
      process {
        # Here we simply collect all input to our list
        $list.AddRange($InputObject)
    
        Write-Verbose "Process was called [InputObject: $InputObject]"
      }
    
      end {
        # The `end` block is only ever called _after_ we've collected all input
        # Now we can safely sort it
        $list.Sort()
    
        Write-Verbose "End was called"
    
        # and output the results
        return $list
      }
    }
    

    If we invoke our new command with -Verbose, we will see how the input is collected one by one:

    PS ~> 10..1 |Sort-ObjectCustom -Verbose
    VERBOSE: Begin was called
    VERBOSE: Process was called [InputObject: 10]
    VERBOSE: Process was called [InputObject: 9]
    VERBOSE: Process was called [InputObject: 8]
    VERBOSE: Process was called [InputObject: 7]
    VERBOSE: Process was called [InputObject: 6]
    VERBOSE: Process was called [InputObject: 5]
    VERBOSE: Process was called [InputObject: 4]
    VERBOSE: Process was called [InputObject: 3]
    VERBOSE: Process was called [InputObject: 2]
    VERBOSE: Process was called [InputObject: 1]
    VERBOSE: End was called
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    For more information on how to implement pipeline input processing routines for binary cmdlets, see the "How to Override Input Processing".

    For more information on how to take advantage of the same pipeline semantics in functions, see the about_Functions_Advanced_Methods and related help topics