Creating Pipeline-Aware Functions

In part 1 of this series (Part 1) you learned a lot about creating functions that return results as real objects. You also created a function called Get-Shortcut which manages shortcuts in your start menu.

As promised, today I'll show you how to use that function to manage shortcuts, add or remote hotkeys or show property pages for the shortcuts. The real thing you learn though is how to create functions that accept information via pipeline so that you can create real PowerShell pipeline "chains" like this one to automatically add a hotkey to the PowerShell shortcut:

Get-Shortcut |
Where-Object { $_.LinkPath -like '*Windows PowerShell.lnk' } |
Set-Shortcut -hotkey F11

Creating Pipeline-Aware Functions

In order for functions to accept input over the pipeline, two things are necessary:

  • The function needs to assign a special attribute to the parameter that can be received from the incoming objects
  • The function needs a process script block that indicates which part of the function code needs to be iterated for each incoming object

Let's create a function that can open the property page of a file. This way, you could select a shortcut using Get-Shortcut and then pipe the result to the new function. Let's call it Show-Properties. This is what it looks like:

function Show-Properties {
param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('LinkPath')]
[Alias('FileName')]

$Path
)
begin {
$shell = New-Object -ComObject Shell.Application
}

process {
$parent = Split-Path $Path
$child
= Split-Path $Path -Leaf

$folder = $shell.NameSpace($parent)
$file = $folder.ParseName($child)
$file.InvokeVerb('Properties')
}
}

Download this script from our Library: Show-Properties
And don't forget to also get Get-Shortcut from the previous part of this series!

When you run both functions, you can now easily open the property page for any shortcut and manually assign hotkeys. Let's assume you'd like to add a new shortcut to your PowerShell link. Try this:

Get-Shortcut |
Where-Object { $_.LinkPath -like '*Windows PowerShell.lnk' } |
Show-Properties 

Voila, the property page of your PowerShell shortcut opens, and you can now click into the keyboard field and press F11 to set this key as new shortcut. Once you close the property dialog, you may be asked for Admin credentials as this shortcut affects all users. Once saved, pressing F11 will open PowerShell for you!

You can also open all dialogs for all shortcuts that have a hotkey, for example because you'd like to remove duplicate entries:

Get-Shortcut | Where-Object { $_.Hotkey } | Show-Properties

Note how a simple Where-Object is able to filter out all instances that have a Null value in a certain property - because Null always gets converted to a boolean value of $false. 

Examining the Magic: Binding to a Parameter

Let's check out how Show-Properties works. As you can see, it accepts the object provided by Get-Shortcut. This is done by an attribute called ValueFromPipelineByPropertyName. When you look at the beginning of that function, you'll see that the parameter Path was decorated with that attribute. 

function Show-Properties {
param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('LinkPath')]
[Alias('FileName')]

$Path
)
...

What this means is that if a preceeding command in the pipeline emits an object with a property called "Path", it is automatically bound to that parameter.

However, Get-Shortcut emits objects that have no property called Path. Instead, the link to a lnk-file is contained in a property called LinkPath. The function could have used that name as a parameter, but since LinkPath is specific for link files, we decided a more general parameter like Path would work better.

That's why the function defines two more alias names for this property called LinkPath and FileName. So the function accepts objects with properties called Path, LinkPath or FileName.

That's cool because this makes your function really versatile. You can open any file property dialog, and you can use a multitude of cmdlets to find that path name. For example, to open the dialog for all running PowerShell instances, try this:

Get-Process powershell -FileVersionInfo | Show-Properties 

Here, Get-Process finds the file path and returns objects with a FileName property. Because Show-Properties uses alias names for its Path property, it can still bind this information to its Path property. If you wanted, you could also call the function directly like this:

Show-Properties -Path $env:windir\explorer.exe

This would open the explorer.exe properties page, as you submitted the file path directly to the Path parameter.

Begin, Process, End - declaring which code is executed when...

Binding a parameter to the pipeline is only one of two requirements. The other one is that you declare which parts of your function code is actually executed for each object received. You do this by wrapping this code inside a process block. Note that once you add a process block to your function, no code is permitted outside this block anymore.

Optionally, you can also add a begin and an end block which is executed once when the function starts and ends. The function uses a begin block to instantiate the necessary COM object which is a good idea because if you instantiated this object inside the process block, it would get instantiated for every single object received. This would impose performance and resource issues that you can easily avoid by placing initialization code inside begin. Use an end block if you want to clean up behind your function or emit final messages or collected results.

What's next?

In the next part, we'll look at how you can manage your shortcuts automatically rather than manually. Of course, you'll again learn a lot about PowerShell intrinsics such as optional parameters and how to determine which parameters were actually submitted by a user.

Tobias

Microsoft MVP PowerShell Germany

P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de  


Posted Sep 29 2010, 07:30 PM by Tobias

Comments

Carsten Schwartz wrote re: Creating Pipeline-Aware Functions
on 10-13-2010 7:53 AM

Here's a simple construction, what about this:

function PrintPipe()

{

 if ($input)

 {

   foreach($val in $input)

   {

     $val

   }

 }

}

You can type:

"Hello World" |  Get-Members | PrintPipe

to see what I mean...

Concentrated Tech NSoftware Dell Compellent Sponsored by Idera and Concentrated Tech and NSoftware and Dell Compellent
Copyright 2011 PowerShell.com. All rights reserved.