Adding Pipeline Support to Your Scripts!

You’ve probably used the pipeline 1,000 times and not thought about how exactly you can send the output of one command to another. It’s actually, incredibly simple to do, and enables you to have a LOT of flexibility in your scripts!

Pipeline overview

If you are just starting out in Powershell and stumbled across this blog post, I’d like to take a moment to explain just exactly the Powershell Pipeline is, and how it works.

The pipeline is one of the most powerful parts of Powershell! Here’s a good analogy to use to give a visual representation of the pipeline. Think of it like a colored stack of lego blocks. Each part of the stack is an individual piece of the whole stack, thus each colored block (cmdlet) is an individual segment of the the entire stack (Pipeline)!

The pipeline character is the | (pipe), and pipeline elements are the cmdlets you are running on each side of it.

When working with the pipeline it’s important to remember the idea of “Filter Left,Format Right”. This means that you make your object (pipeline element) as small as possible at the very beginning of your pipeline, before sending it on to the next command, and finally, outputting it in whatever format you wish (note, this could be any of the Format-* cmdlets, or writing to the screen or a file,etc.)

Using the pipeline for discovery

The pipeline is one of, no probably the best tool for discovering things inside of Powershell. Take this for example:

You wish to find all of the “stuff” attached to an object emitted by Get-Process

Run this in a console: Get-Service | Get-Member


PS C:\> Get-Service | Get-Member


   TypeName: System.ServiceProcess.ServiceController

Name                      MemberType    Definition
----                      ----------    ----------
Name                      AliasProperty Name = ServiceName
RequiredServices          AliasProperty RequiredServices = ServicesDependedOn
Disposed                  Event         System.EventHandler Disposed(System.Object, System.EventArgs)
Close                     Method        void Close()
Continue                  Method        void Continue()
CreateObjRef              Method        System.Runtime.Remoting.ObjRef CreateObjRef(type requestedType)
Dispose                   Method        void Dispose(), void IDisposable.Dispose()
Equals                    Method        bool Equals(System.Object obj)
ExecuteCommand            Method        void ExecuteCommand(int command)
GetHashCode               Method        int GetHashCode()
GetLifetimeService        Method        System.Object GetLifetimeService()
GetType                   Method        type GetType()
InitializeLifetimeService Method        System.Object InitializeLifetimeService()
Pause                     Method        void Pause()
Refresh                   Method        void Refresh()
Start                     Method        void Start(), void Start(string[] args)
Stop                      Method        void Stop()
WaitForStatus             Method        void WaitForStatus(System.ServiceProcess.ServiceControllerStatus desiredStatus), void Wait...
CanPauseAndContinue       Property      bool CanPauseAndContinue {get;}
CanShutdown               Property      bool CanShutdown {get;}
CanStop                   Property      bool CanStop {get;}
Container                 Property      System.ComponentModel.IContainer Container {get;}
DependentServices         Property      System.ServiceProcess.ServiceController[] DependentServices {get;}
DisplayName               Property      string DisplayName {get;set;}
MachineName               Property      string MachineName {get;set;}
ServiceHandle             Property      System.Runtime.InteropServices.SafeHandle ServiceHandle {get;}
ServiceName               Property      string ServiceName {get;set;}
ServicesDependedOn        Property      System.ServiceProcess.ServiceController[] ServicesDependedOn {get;}
ServiceType               Property      System.ServiceProcess.ServiceType ServiceType {get;}
Site                      Property      System.ComponentModel.ISite Site {get;set;}
StartType                 Property      System.ServiceProcess.ServiceStartMode StartType {get;}
Status                    Property      System.ServiceProcess.ServiceControllerStatus Status {get;}
ToString                  ScriptMethod  System.Object ToString();

The screen output you see contains useful information such as all the properties, and methods attached to the object, and also what Type of object it returns.

Leveraging the pipeline in code

Ah, finally. With a bit of background out of the way, let’s dig into the fun stuff, writing code to leverage the pipeline!

Consider the following Param() block declaration

[cmdletBinding()]
Param(
    [Parameter(Mandatory,Position=0,ValueFromPipeline)]
    [string]
    $Name
)

The above param block tells your script that the Name parameter accepts pipeline input. The caveat here is that ValueFromPipeline expects the pipeline input to be of the same type as what it expects (string in this instance), or that it can be converted to the type that it expects.

For something like a script to work with computer names you may do something like this:

Get-ADComputer randompc | YourFunction -Name $_.Name

By using the pipeline, you access the current object’s ($_) Name property in the pipeline, and it happily accepts it into Name parameter.

Another option is ValueFromPipelineByPropertyName, which like the first option, expects input of the same type, but also has the requirement that the object contain a property of the same name as the parameter you wish to pass it too.

You can use a named expression to coerce your pipeline into working if the object you receive doesn’t have the same name, but is of the same type. Here’s an example (let’s pretend name doesn’t exist on Get-ADComputer, and use something else in its place)

Get-ADComputer | Select-Object @{Name='Name';Expression={$_.DNSName}} | YourFunction

That’s all there is to adding pipeline support to your functions! Give it a try, and take your scripts to higher levels of functionality! Have questions or feedback? Use the links below to contact me. I’d love to hear from you!

Written on November 18, 2018