Getting Started - Accepting Pipeline Input

Pipeline Review

The pipeline in PowerShell is what commands use when separated with the pipeline operator '|'. It allows commands to pass the output values to the next command in the sequence. You can pipe as many commands together as necessary to complete your task. 

Pipeline structure:

Command1 (output) -> | Command2 (output) -> | Command3

Using the pipeline

Let's start with Get-Process.

Get-Process

This command will give you a list of all running processes, and some information for each process.

What if we just cared about the first 10 processes, sorted by CPU usage?

Let's try:

Get-Process | Sort-Object CPU -Descending | Select-Object -First 10

The above command takes Get-Process, then pipes it to Sort-Object (which we have sorting by CPU, descending), and then pipes that to Select-Object so we can specify that we want to see the First 10 results only. 

We can use the same command, but specify -Last 10 to see the last 10 results.

Get-Process | Sort-Object CPU -Descending | Select-Object -Last 10

Pretty handy! What if we wanted to see this in a different way? You can take the above command, and then pipe it to Out-GridView.

Get-Process | Sort-Object CPU -Descending | Select-Object -Last 10 | Out-GridView

Accepting pipeline input

So now that we know how to work with the pipeline in PowerShell, how do we accept pipeline input in our code?

Accepting pipeline input is possible via parameter attributes available when using [cmdletbinding()].

There are two different parameter attributes we can use that will accept input from the pipeline. 

ValueFromPipeline

Let's take a look at ValueFromPipeline. This parameter attribute will take all values passed to it from the pipeline. To get started with this example, open the ISE and paste in the following code:

function Write-PipeLineInfoValue {
[cmdletbinding()]
param(
    [parameter(
        Mandatory         = $true,
        ValueFromPipeline = $true)]
    $pipelineInput
)

    Begin {

        Write-Host `n"The begin {} block runs once at the start, and is good for setting up variables."
        Write-Host "-------------------------------------------------------------------------------"

    }

    Process {

        ForEach ($input in $pipelineInput) {

            Write-Host "Process [$($input.Name)] information"

            if ($input.Path) {
        
                Write-Host "Path: $($input.Path)"`n
        
            } else {

                Write-Host "No path found!"`n -ForegroundColor Red

            }

        }

    }

    End {

        Write-Host "-------------------------------------------------------------------------------"
        Write-Host "The end {} block runs once at the end, and is good for cleanup tasks."`n

    }

}

Get-Process | Select-Object -First 10 | Write-PipeLineInfoValue

Go ahead and save it to C:\PowerShell\part8.ps1, and run the code.

Here are the results:

When accepting pipeline input, the input is handled in the Process {} block within the function. You can also use the Begin {} and End {} blocks as well. The Process block is mandatory, however Begin and End blocks are not.

The code in the begin block runs once when the function is invoked. It is where you can setup variables at the start of the code execution.

Begin {

        Write-Host `n"The begin {} block runs once at the start, and is good for setting up variables."
        Write-Host "-------------------------------------------------------------------------------"

    }

The code in the process block will process the pipeline input. It is cleanest, in my opinion, to handle each element of the pipeline (as it will typically come in as an array), in a ForEach loop. That way you can iterate through each item, and take action on it. 

Process {

        ForEach ($input in $pipelineInput) {

            Write-Host "Process [$($input.Name)] information"

            if ($input.Path) {
        
                Write-Host "Path: $($input.Path)"`n
        
            } else {

                Write-Host "No path found!"`n -ForegroundColor Red

            }

        }

    }

The code in the end block will run after all the elements in the pipeline are processed. This is a good place to cleanup variables, or anything else you need to do once, after the pipeline input is handled.

End {

        Write-Host "-------------------------------------------------------------------------------"
        Write-Host "The end {} block runs once at the end, and is good for cleanup tasks."`n

    }

ValueFromPipelineByPropertyName

This parameter attribute is a bit different than the one above. It will only accept the input from the pipeline if the incoming property name matches the parameter name. In this example, I will use the parameter name of $name to match any incoming property values of the property 'name'.

Go ahead and paste this code under the code we used earlier in part8.ps1

function Write-PipeLineInfoPropertyName {
[cmdletbinding()]
param(
    [parameter(
        Mandatory                       = $true,
        ValueFromPipelineByPropertyName = $true)]
    [string[]]
    $Name
)

    Begin {

        Write-Host `n"The begin {} block runs once at the start, and is good for setting up variables."
        Write-Host "-------------------------------------------------------------------------------"

    }

    Process {

        ForEach ($input in $name) {

            Write-Host "Value of input's Name property: [$($input)]"

        }

    } 

    End {

        Write-Host "-------------------------------------------------------------------------------"
        Write-Host "The end {} block runs once at the end, and is good for cleanup tasks."`n

    }

}

Get-Process | Select-Object -First 10 | Write-PipeLineInfoPropertyName

Let's run this code, and take a look at the results:

In this example we did not have to expand upon the incoming object's values, as it only gave us the property name values to begin with. That is, we didn't have to use $input.Name, we just had to use $input.

We can also run the above code with Get-Service in place of Get-Process, as it also returns a property name of 'name'.

Get-Service | Select-Object -First 10 | Write-PipeLineInfoPropertyName

Uses

There are many uses for accepting pipeline input. Let's say you need to get a list of specific computers that you need to perform actions on. You could simply pipe that to a function you created that would perform actions on each one.

If you create a logging function, you can also take bulk messages or commands and the pipe them to a function that creates or logs to a log file.

As with most things in PowerShell, there can be many ways to complete the same task. Sometimes piping commands together is cleaner than other methods, and sometimes it isn't. In the end it's up for you to decide.

Homework

  • Learn more about the pipeline!
    • Get-Help about_Pipelines
  • Try passing other commands to the functions we created today.

I hope you've enjoyed the series so far! As always, leave a comment if you have any feedback or questions!

-Ginger Ninja

[Back to top]