Handling Input in PowerShell Functions

How to create a function that processes both pipeline and parameter input

Bill Stewart

January 17, 2011

6 Min Read
ITPro Today logo in a gray background | ITPro Today


PowerShell functions can accept input in one of two ways: Using parameters or the pipeline. Parameters are simple to understand because you define them as a part of a function. For example, the following code creates the Out-Item function, which has a parameter named $item:

function Out-Item($item) \{
  ...

\}

You can also create the function this way:

function Out-Item \{

  param($item)
  ...
\}

In this case, you're defining the function's parameter using the param statement.

 However, much of PowerShell's power and flexibility comes from its ability to process objects as they pass through the pipeline. In PowerShell’s pipeline processing, the output from one cmdlet, function, or script becomes the input for another cmdlet, function, or script. For example, in the code

Get-ChildItem C: |
Where-Object \{ $_.Length -eq 0 \}

the $_ variable represents each object as it passes through the pipeline. To have a function process pipeline input, you can use the $_ variable inside a process script block. For example, the function

function Out-Item \{
process \{

    $_
  \}
\}

uses a process script block to output every item it receives as pipeline input.

Alternatively, you can write a function that processes input from a parameter:

function Out-Item \{
param($item)

  $item
\}

However, this approach isn't very flexible because the function determines how you must pass input to it.

So far, the examples I've shown you demonstrate how to write two types of functions—one that processes input from the pipeline and one that processes input from a parameter. But what if you want to use a single function that can process input from the pipeline or a parameter? This is now possible with PowerShell 2.0.

The Basic Approach

Listing 1 shows the basic approach for writing a function that can accept input from either the pipeline or a parameter. This code uses a process script block to process input from the pipeline and a param statement to process input from a parameter. Notice the addition of the CmdletBinding attribute in the function's param statement (see callout A) and the Parameter attribute in the $item parameter (see callout B). The CmdletBinding attribute, which is new in PowerShell 2.0, enables cmdlet-like behavior for the function. This allows the function to accept input using either the pipeline or a parameter.

As callout C shows, the Out-Item function also includes begin and end script blocks to report how many items the function outputs. (The begin and end script blocks execute only once, whereas the process script block executes once per item.)

Figure 1 shows the Out-Item function in Listing 1 working with pipeline input (the first command) and parameter input (the second command). If you look carefully at the second command's output, you'll notice that the Out-Item function reports that it processed only one item. When you use this basic approach, all the items are processed and output in one pass. This can be problematic if there are numerous items or if the items are slow (e.g., you're retrieving the properties of files over a slow network connection). For these reasons, I don't recommend using this basic approach.

A Better Approach

Instead of the basic approach in Listing 1, which accesses all the items at once, it's better to access the parameter items one at a time, just as if you were using the pipeline. To do this, you need a way for the function to detect if its input is coming from the pipeline. One technique is to check whether the $item parameter is bound (i.e., used when calling the function). When the $item parameter is bound, the input comes from the parameter. When the $item parameter isn't bound, the function assumes the input is coming from the pipeline.

The $PSBoundParameters variable is a hash table of bound parameters, and you can use its ContainsKey method to check whether the $item parameter is bound, as Listing 2 shows. In callout A, the Out-Item function uses the $PSBoundParameters variable to check whether the $item parameter is bound. (Note that when you use the ContainsKey method, you don't include the $ character with the parameter name.) The $PipelineInput variable will be True when the parameter isn't bound and False when the parameter is bound.

Callout B shows how the function uses the $PipelineInput variable. If input is from the pipeline, the function outputs the items ($_). Otherwise, it uses the ForEach-Object cmdlet to access each item in the parameter.

Approach to Use for Defined Default Parameter Values

You can't use the approach demonstrated in Listing 2 if you need to define a default value for a parameter using code such as

function Out-Item \{

  \[CmdletBinding()\]
  param(
  \[Parameter(ValueFromPipeline=$TRUE)\]
    $item="Default"
  )
  ...
\}

Setting the $PipelineInput variable using the line of code from callout A in Listing 2 won't work because the parameter isn't bound. As a result, the function will erroneously assume the input is coming from the pipeline.

Instead, you can follow the approach demonstrated in Listing 3. The code in callout A performs two tests to verify whether the input comes from the pipeline. First, it makes sure that the parameter isn't bound. Second, it makes sure that the parameter doesn't exist.

Figure 2 shows this code in action. The first command shows the Out-Item function's default parameter value. The second command specifies the input from the pipeline. The third command provides the input from the parameter.

Two Useful Approaches

The two approaches I demonstrated are quite useful because they let your functions process input, no matter whether the input comes from a parameter or the pipeline. Which approach to follow depends on whether your function uses a default parameter:

  • If your function will be processing input from the pipeline or a parameter, and the parameter doesn't contain a default value, you can follow the approach in Listing 2.

  • If your function will be processing input from the pipeline or a parameter that contains a default value, you can follow the approach in Listing 3.

You can download the code for these two approaches by clicking the Download the Code Here button.

Listing 1: The Basic Approach
function Out-Item \{
  # BEGIN CALLOUT A
  \[CmdletBinding()\]
  # END CALLOUT A
  param(
  # BEGIN CALLOUT B
  \[Parameter(ValueFromPipeline=$TRUE)\]
  # END CALLOUT B
    $item
  )
  # END CALLOUT C
  begin \{ $n = 0 \}
  process \{
    $item
    $n++
  \}
  end \{ Write-Host "Output $n item(s)" \}
  # END CALLOUT C
\}

 

Listing 2: Approach to Use When You Don't Use Default Parameter Values
function Out-Item \{
  \[CmdletBinding()\]
  param(
  \[Parameter(ValueFromPipeline=$TRUE)\]
    $item
  )
  begin \{
    # BEGIN CALLOUT A
    $PipelineInput = `
      -not $PSBoundParameters.ContainsKey("item")
    # END CALLOUT A
    Write-Host "Pipeline input? $PipelineInput"
    $n = 0
  \}
  process \{
    # BEGIN CALLOUT B
    if ($PipelineInput) \{
      $_
      $n++
    \}
    else \{
      $item | ForEach-Object \{
        $_
        $n++
      \}
    \}
    # END CALLOUT B
  \}
  end \{ Write-Host "Output $n item(s)" \}
\}

 

 

Listing 3: Approach to Use When You Define Default Parameter Values
function Out-Item \{
  \[CmdletBinding()\]
  param(
  \[Parameter(ValueFromPipeline=$TRUE)\]
    $item="Default"
  )
  begin \{
    # BEGIN CALLOUT A
    $PipelineInput = `
      (-not $PSBoundParameters.ContainsKey("item")) `
      -and (-not $item)
    # END CALLOUT A
    Write-Host "Pipeline input? $PipelineInput"
    $n = 0
  \}
  process \{
    if ($PipelineInput) \{
      $_
      $n++
    \}
    else \{
      $item | ForEach-Object \{
        $_
        $n++
      \}
    \}
  \}
  end \{ Write-Host "Output $n item(s)" \}
\}

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like