Fun With PowerShell Pipelined Functions

Introduction

In my previous post, I covered the use of PowerShell Advanced Functions. I highly suggest you read it if you haven’t, it provides some foundational knowledge that will be important to understand for this post.

In this post, we’ll see how to pipeline enable your functions. Just like a cmdlet, you’ll be able to take input from the pipeline, work with it, then send it out your function back into the pipeline.

For all of the examples, we’ll display the code, then under it the result of our code. In this article I’ll be using PowerShell Core, 7.1.3, and VSCode. The examples should work in PowerShell 5.1 in the PowerShell IDE, although they’ve not been tested there.

To run a snippet of code highlight the lines you want to execute, then in VSCode press F8 or in the IDE F5. You can display the contents of any variable by highlighting it and using F8/F5.

Pipelining Your Advanced Functions

Pipelining is what gives PowerShell it’s real power. The ability to have small, focused cmdlets (or functions) that you can chain together to produce a useful output. Let’s see this simple, common example:

Get-ChildItem | Sort-Object -Property Length

Result:
Mode  LastWriteTime       Length Name
----  -------------       ------ ----
la--- 11/24/2020  3:37 PM   1115 ReadMe.md
la--- 11/24/2020  3:42 PM   1819 02 - Providers.ps1
la--- 11/24/2020  3:42 PM   3125 09 - Examples.ps1
la--- 11/24/2020  3:42 PM   3732 01 - Cmdlets.ps1
la---  7/15/2021  4:48 PM   4315 06 - Logic Branching and Looping.ps1
la--- 11/24/2020  3:42 PM   4717 03 - Variables.ps1
la--- 11/24/2020  3:42 PM   6210 08 - Classes.ps1
la---  7/20/2021  4:35 PM   6843 07 - Functions.ps1
la---  7/18/2021  8:18 PM   7970 04 - Strings.ps1
la---  7/9/2021   8:00 PM   8486 05 - Arrays and Hashtables.ps1
la--- 11/20/2020 12:58 AM   9694 Company.csv
la--- 11/20/2020 12:58 AM  19479 Company.json

As you can see, Get-ChildItem‘s output was piped into the Sort-Object cmdlet using the pipe symbol, the vertical bar | . Sort-Object took a parameter of a property name, in this case Length, and sorted its output based on that property name.

So how fun would it be to write your own functions that could work with the pipeline? Well as it turns out it’s not only fun but easy too. Here’s an example of a simple pipeline enabled function.

function Get-PSFiles ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipeline) ]
          $file
        )

  begin  { }

  process
  {
    if ($file.Name -like "*.ps1")
    {
      $retval = "  PowerShell file is $($file.Name)"
      # This is the equivalent of: return $retval
      $retval
    }
  }

  end { }
}

The purpose of Get-PSFiles is to examine each file being passed in from the pipeline. If it finds the file name ends in .ps1, it will pass it onto the next item in the pipeline. Otherwise it gets ignored.

First off is the [CmdletBinding()], which you know by now is needed to let Powershell know this is an advanced function.

Now we have our param block. Note that the parameter block has an attribute of (ValueFromPipeline). When an object comes in from the pipeline it will get copied into the variable with the (ValueFromPipeline) attribute, in this case $file.

The next block in the function is the begin { } block. The begin block executes once, and only once, when the function is first called. Here you could do things like set variables or perform a calculation that will be used later in the function.

In my experience the begin block isn’t used a lot, and if yours is empty you can actually omit it completely from your function.

The next section is the process block. This is where all the fun happens! Here we can work with the objects as they are passed in, one by one, from the pipeline.

In this simple script, I look at the Name property of the item being passed in, which is stored in the variable $file. If it ends in .ps1, I set the return value ($retVal) to a string which has some text including the file name. If it doesn’t end in .ps1, I just don’t do anything with it.

I then have a simple line of $retVal. Since it’s not consumed by any other code PowerShell pushes it out into the next item in the pipeline. If there is no next item, it is displayed to the screen.

Once all the incoming items from the pipeline have been processed, the function continues to the end { } block. This is where you would do any cleanup. Similar to the begin block, end does not get used a lot, and you can omit it entirely if it is empty.

To use our Get-PSFiles function, we first need to highlight it and use F5/F8 to load it into memory. Once done we’ll be ready to call it. In this case when we call it we’ll save the output into a variable then display the contents of the variable.

$output = Get-ChildItem | Get-PSFiles
$output

Result:
  PowerShell file is 01 - Cmdlets.ps1
  PowerShell file is 02 - Providers.ps1
  PowerShell file is 03 - Variables.ps1
  PowerShell file is 04 - Strings.ps1
  PowerShell file is 05 - Arrays and Hashtables.ps1
  PowerShell file is 06 - Logic Branching and Looping.ps1
  PowerShell file is 07 - Functions.ps1
  PowerShell file is 08 - Classes.ps1
  PowerShell file is 09 - Examples.ps1

The directory I’m running from has other files in it besides PowerShell scripts, but the function we wrote filtered those out and only returns my PowerShell script files.

You may wonder, what datatype gets returned? We can find out by checking the type of the variable using the GetType() method built into all PowerShell variables.

$output.GetType()

Result:
IsPublic IsSerial Name     BaseType
-------- -------- ----     --------
True     True     Object[] System.Array

As you can see, it returns an array of objects.

So this function is useful, but limited in one way. It can only work with file type objects such as ones retrieved using Get-ChildItem. What if instead we wanted a function that only worked on a single property?

Line Continuation

Before we get into the next section, I want to bring up the topic of line continuation. Sometimes a line of PowerShell code will get very long. It can be useful to break it up across multiple lines for readability.

PowerShell uses the backtick character, the ` as its line continuation character. On US keyboard this is typically to the left of the number 1. If you are outside the US you’ll have to hunt around to find it, I’ll have to claim ignorance of keyboard layouts for other areas of our globe.

The line continuation character simply tells PowerShell “hey, this set of commands continues on the next line, treat it all as one line of code until you no longer find another backtick character”.

Here’s a simple example:

Write-Host 'Here are the names!' `
  -ForegroundColor Green

As you can see I have a simple Write-Host which displays information to the screen. At the very end of the line I have the backtick character. Then the Write-Host cmdlet continues, passing in the parameter of ForegroundColor.

I encourage you to use line continuation characters whenever you have a long line of code as it will make it much easier to read. It’s also useful to folks like myself who write blog posts or give presentations where my display area is limited. If you go look at some examples on my GitHub site, you’ll see I use them quite a bit.

On a large monitor such as the one I normally use it’s not as needed. But when I write a blog post like this one, or go present at a user group, the amount of space I have is much more limited.

It’s also worth mentioning that the pipe symbol | has line continuation built right into it. I could have taken my earlier example and entered into my editor like so:

Get-ChildItem |
  Sort-Object -Property Length

The pipe symbol has to be the very last character on the line. There cannot be any spaces, comments, or other code after it. On the next line I chose to indent the Sort-Object command by two spaces, this is strictly to make the code easier to read and is not required, but is a good habit.

OK that little detour complete, let’s continue our fun by using just a single property from the pipeline in our function.

Using a Single Property in your Pipeline Function

One way we can make our functions more reusable is to work with a single property. For example, almost all cmdlets return a set of objects that have a Name property. We could create a function that works with just that property, and thus use it with all cmdlets that return objects with a Name property.

This next example is a bit contrived, to show you the concepts, and isn’t something I’d normally code in “real life” but it will serve to show you how this concept works.

function Show-BName ()
{
  [CmdletBinding()]
  param ( [Parameter (ValueFromPipelineByPropertyName)]
          $Name
        )

  begin
  {
    Write-Host 'Here are the names!' `
      -ForegroundColor Green
    $names = @()
  }

  process
  {
    $names += $name
    "  Here is the name: $name"
  }

  end
  {
    Write-Host "Those were the names you passed in:" `
      -ForegroundColor Green
    foreach ($n in $names)
    {
       Write-Host "  You passed in " `
         -ForegroundColor White -NoNewline
      Write-Host $n -ForegroundColor Yellow
    }
  }
}

In the parameter block, we use a slightly different attribute, ValueFromPipelineByPropertyName. This tells PowerShell to take each object being passed in, and copy it’s Name property into our variable, in this case $Name.

Note that the name of the parameter must match the name of the property you want to work with. Thus the Name property is copied into the $Name parameter. The rest of the object will be discarded, and not available to you in the function.

The function falls into our begin block, in which I’ve included a Write-Host statement to display a message our function is beginning. Let me stress, you normally do not include Write-Host messages in functions. Typically the script that calls the function will take the output, and display the results. I’m doing this here only to demonstrate how each piece of the advanced function works.

I then do something you might do, I create a variable called $names. I initialize it as an empty array.

Now the function moves into the process block. In it, I take the name that came in from the pipeline and add it to the array. I then embed the name in a string. Since I don’t do anything with the string, it is now passed out to return to the pipeline.

Finally we hit the end block. Here, I’m using some Write-Host statements to show we’re done and what names were processed. In a real world situation you might use this for logging or some other reason that fits your needs.

So let’s see this in action. As before, highlight the function and run it using F8/F5 to get it into memory. Then we can call it.

Get-ChildItem |
  Show-BName

Result:
Here are the names!
  Here is the name: 01 - Cmdlets.ps1
  Here is the name: 02 - Providers.ps1
  Here is the name: 03 - Variables.ps1
  Here is the name: 04 - Strings.ps1
  Here is the name: 05 - Arrays and Hashtables.ps1
  Here is the name: 06 - Logic Branching and Looping.ps1
  Here is the name: 07 - Functions.ps1
  Here is the name: 08 - Classes.ps1
  Here is the name: 09 - Examples.ps1
  Here is the name: Company.csv
  Here is the name: Company.json
  Here is the name: ReadMe.md
Those were the names you passed in:
  You passed in 01 - Cmdlets.ps1
  You passed in 02 - Providers.ps1
  You passed in 03 - Variables.ps1
  You passed in 04 - Strings.ps1
  You passed in 05 - Arrays and Hashtables.ps1
  You passed in 06 - Logic Branching and Looping.ps1
  You passed in 07 - Functions.ps1
  You passed in 08 - Classes.ps1
  You passed in 09 - Examples.ps1
  You passed in Company.csv
  You passed in Company.json
  You passed in ReadMe.md

The first set of “Here is the names…” is what came out of the function into the pipeline. Since we didn’t do anything with them, they were displayed on the screen. The second set, with “You passed in…” is from the end block.

We can have even more fun, let’s take the output of our function and pipe it into another cmdlet. We’ll use Sort-Object to display our results in descending order.

Get-ChildItem |
  Show-BName |
  Sort-Object -Descending

Result:
Here are the names!
Those were the names you passed in:
  You passed in 01 - Cmdlets.ps1
  You passed in 02 - Providers.ps1
  You passed in 03 - Variables.ps1
  You passed in 04 - Strings.ps1
  You passed in 05 - Arrays and Hashtables.ps1
  You passed in 06 - Logic Branching and Looping.ps1
  You passed in 07 - Functions.ps1
  You passed in 08 - Classes.ps1
  You passed in 09 - Examples.ps1
  You passed in Company.csv
  You passed in Company.json
  You passed in ReadMe.md
  Here is the name: ReadMe.md
  Here is the name: Company.json
  Here is the name: Company.csv
  Here is the name: 09 - Examples.ps1
  Here is the name: 08 - Classes.ps1
  Here is the name: 07 - Functions.ps1
  Here is the name: 06 - Logic Branching and Looping.ps1
  Here is the name: 05 - Arrays and Hashtables.ps1
  Here is the name: 04 - Strings.ps1
  Here is the name: 03 - Variables.ps1
  Here is the name: 02 - Providers.ps1
  Here is the name: 01 - Cmdlets.ps1

There are a couple of things to notice with this version. First, the result of the end block, the “You passed in…” comes first. That’s because the function ended, but the data it produced is now being consumed by Sort-Object.

Second, you can see Sort-Object took the output and sorted in reverse (descending) order.

As they say on TV, but wait, there’s more!

Because our function only acts on the Name property, it could care less what type of object is coming in as long as it has a Name property. Thus we could use it with any cmdlet as long as that cmdlet produces objects with a Name property!

Get-Process returns objects with a Name property, so let’s use it with our new function.

Get-Process | Show-BName

Result:
  Here is the name: 5KPlayer
  Here is the name: aesm_service
  Here is the name: Airplay
  Here is the name: AppleMobileDeviceProcess
  Here is the name: ApplicationFrameHost
  Here is the name: CepstralLicSrv
  Here is the name: Code
  (many more here, truncated for brevity)
Those were the names you passed in:
  You passed in 5KPlayer
  You passed in aesm_service
  You passed in Airplay
  You passed in AppleMobileDeviceProcess
  You passed in ApplicationFrameHost
  You passed in CepstralLicSrv
  You passed in Code
  (many more here too, again truncated for brevity)

Because we were able to keep the needs of our function tightly scoped to just the Name property, we were able to create a function that was highly flexible and reusable.

Conclusion

In this post we saw how to create a function that would work in the pipeline. We did it two ways, first by passing in entire objects, then by passing in a specific property of an object.

But there’s still more fun to be had! In a future post we’ll see how to implement two switches common to all cmdlets and advanced functions, Verbose and Debug.

The demos in this course came from my Pluralsight course PowerShell 7 Quick Start for Developers on Linux, macOS and Windows, one of many PowerShell courses I have on Pluralsight. All of my courses are linked on my About Me page.

If you don’t have a Pluralsight subscription, just go to my list of courses on Pluralsight. At the top is a Try For Free button you can use to get a free 10 day subscription to Pluralsight, with which you can watch my courses, or any other course on the site.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s