Windows PowerShellThe Drop Box

Don Jones

Contents

Be the Cmdlet
The Filtering Function
Think Physical
Practical Applications
Make Time for Play

At one of the Windows PowerShell classes I was recently teaching, some of the students were having a tough time visualizing what the shell's pipeline was all about. I'll admit that the pipeline isn't completely intuitive conceptually, and thus it can be quite difficult for visual learners to picture what exactly is going on. When I got to the

concept of filtering functions, which work directly in the pipeline, things got really sticky, and I could tell from looking at their faces that I was definitely losing a few folks.

To try and help them out, I showed up the next day with a box, some name tag stickers, and some ping-pong balls. (Hey, nobody said Windows PowerShell® can't be fun.) I decided to do a demonstration using the following command line:

Get-Process | Where { $_.VM –gt 1000 } | Sort VM –descending | Export-CSV C:\Procs.csv

Be the Cmdlet

Using the name tag stickers—you know, the "Hello, My Name Is …" stickers you're always made to wear at school reunions and similar dreaded events—I assigned a cmdlet name to each student. I explained that the ping-pong balls represented Windows® process objects (more specifically, objects of the System.Diagnostics.Process type), and I then asked the students to tell me which cmdlet name sounded like it was most likely to generate process objects.

Looking around at each others' tags, they settled for the obvious choice of Get-Process. That demonstrates one of the main reasons I really like the cmdlet names used in Windows PowerShell: they are usually very obvious.

So the student portraying Get-Process grabbed all the ping-pong balls and dumped them into the cardboard box. The box represents the shell's pipeline, and what the student did is pretty much what a cmdlet does. A cmd­let generates one or more objects and then dumps them in the pipeline.

The next cmdlet in the pipeline then takes over. In this example, the student representing the Where-Object cmdlet picked up all of the ping-pong balls and examined each, one at a time, checking to see if a given ball's VM property was greater than 1,000. In the shell, "VM" stands for virtual memory, and for this exercise I'd written in marker the various amounts of virtual memory on each ping-pong ball.

Every ball (otherwise known as the process) that had a VM of 1,000 or more went back into the box (sometimes called the pipeline), while anything of a lesser value was dropped in a trash can, never to be seen again. (Actually, that's not true, I rescued them to be used in future classes.)

Next up, the student pretending to be Sort-Object went through the box of ping-pong balls and put them in order, based on their VM property. I have to admit that this was one part of the exercise that wasn't so well thought-out—keeping the balls from rolling all over the place was a bit tricky! It seems I'll have to find ping-pong cubes for my next class, or else keep some egg cartons handy.

Finally, the student playing Export-CSV picked up the balls and wrote out the info to a CSV file. In the physical realm, this translates into writing the process's properties onto a flip chart that we pretended was a CSV file.

The Filtering Function

With a simple pipeline out of the way, we decided to look at filtering functions, which are a bit more complicated. I proposed the filtering function and command line shown in Figure 1.

Figure 1 Sample filtering function and command line

Function Do-Something { BEGIN { } PROCESS { $obj = New-Object PSObject $obj | Add-Member NoteProperty "TimeStamp" (Get-Date) $obj | Add-Member NoteProperty "ProcessName"($_.Name) Write-Output $obj } END {} } Get-Process | Do-Something | Format-Table *

First, I want to give a quick review of what filtering functions are supposed to do. The idea is that the function contains three script blocks, named BEGIN, PROCESS, and END.

When the function is used in the pipeline, the BEGIN script block is executed first. This script might do any set-up tasks, such as opening a database connection.

Then, the PROCESS script block executes once for each object that was piped into the function. Within the PROCESS script block, the $_ variable is automatically populated with the current pipeline object. So, if 10 objects are piped in, the PROCESS script block executes 10 times.

Finally, after all of the piped-in objects have been executed, the END script block executes and does any necessary clean-up tasks, such as closing a database connection.

Within any of these script blocks, anything written using Write-Output winds up in the pipeline for the next cmdlet. Anything not written using Write-Output is discarded.

Since our command started with Get-Process, our erstwhile student rounded up all the ping-pong balls (we'd taken a brief break and somehow the balls had been playfully tossed all over the room—go figure) and dropped them in the pipeline box. With her job complete, the student portraying the "Do-Something" filtering function then took over.

He first executed his BEGIN script block. That being empty, it didn't take much effort. Next, he grabbed all the pipeline objects (the ping-pong balls) and started looking at them one at a time. This was kind of fun, as there were about a dozen balls and he was trying to balance all of them on his lap—well, maybe you had to be there.

Anyway, he picked up one process object at a time and executed his PROCESS script block. That script block had him create a brand-new custom object. (I used special yellow ping-pong balls for this, and don't think those weren't tough to find at the local sporting goods store.) On these new ping-pong balls, he inscribed the current date and time along with the name of the process object he was currently looking at.

This new custom object was then written to the pipeline, meaning he placed the yellow ping-pong ball into the box, and the original process object ping-pong ball was discarded. He did this for each process object ping-pong ball that was in the box, about 12 in total. When he was finished, each white ping-pong ball had been replaced with a yellow custom object ping-pong ball. Finally, he executed his END script block, which was empty and didn't take any time at all.

To wrap things up, our Format-Table student took over. She picked up everything in the box—which was all yellow "custom" objects at this point—and started constructing a table, using both of the properties written on each ball. The result was a table, written on our flip chart, with two columns—TimeStamp and ProcessName—and about a dozen rows.

Think Physical

This exercise made the pipeline and filtering functions really hit home for everyone in the class. The ping-pong balls did a great job of representing objects, something that tends to get a bit abstract when you're just talking about the shell.

Everyone could see how the cmdlets manipulated those objects, how the results were placed in the pipeline, and how the next cmdlet picked up those results and manipulated the objects further. The sequence of events in a filtering function were more obvious, as was the PROCESS script block's technique of working with one input object at a time.

This also demonstrated the means by which a new custom object can be created and placed into the pipeline. Furthermore, it helps to demonstrate the advantages of producing custom objects as opposed to simple text—the new custom objects were able to be consumed by other cmdlets, such as Format-Table, offering a lot of flexibility in how the data could then be used and represented.

Practical Applications

The obvious question from the students was how can the pipeline and these filtering functions we just demonstrated be used in practical applications. The answer was an easy one for me to give, as I'd produced several examples for a recent conference session (you can download the examples for yourselves at scriptinganswers.com/essentials/index.php/2008/03/25/techmentor-2008-san-francisco-auditing-examples).

One example has a goal of listing several properties from the Win32_UserAccount class in Windows Management Instrumentation (WMI) from several computers. Assuming the computer names are listed in C:\Computers.txt, this simple command will do the job:

Gwmi win32_useraccount –comp (gc c:\computers.txt)

The problem is that the Win32_UserAccount class doesn't include a property that tells you which computer each instance came from, so the resulting list will be a useless hodge-podge of accounts from many computers. I solve the problem by creating a new custom object that includes the originating computer name, as well as select class properties that I am interested in. The code for this custom object is shown in Figure 2.

Figure 2 Using a custom object to gather computer names and select properties

function Get-UserInventory { PROCESS { # $_ is a computer name $users = gwmi win32_useraccount -ComputerName $_ foreach ($user in $users) { $obj = New-Object $obj | Add-MemberNoteProperty Computer $_ $obj | Add-Member NotePropertyPasswordExpires ($user.PasswordExpires) $obj | Add-Member NoteProperty Disabled ($user.Disabled) $obj | Add-Member NotePropertyFullName ($user.FullName) $obj | Add-Member NoteProperty Lockout ($user.Lockout) $obj | Add-Member NoteProperty Name ($user.Name) $obj | Add-Member NotePropertyPasswordRequired ($user.PasswordRequired) $obj | Add-Member NotePropertyPasswordChangeable ($user.PasswordChangeable) } } } Get-Content c:\computers.txt | Get-UserInventory | where { $_.PasswordRequired -eq $False } | selectComputer,Name | Export-Csv c:\BasUsersBad.csv

The final command line passes all the computer names to the filtering function, which produces custom objects. Next, I filtered out all the users except those whose PasswordRequired property is False (the idea is to produce an audit report of problem accounts). Then I simply keep the Computer and Name properties, so the final report is a list of computer names and account names that need attention due to having non-expiring passwords. The filtering function makes this multi-computer report possible because it adds the originating computer name to the output while trimming down the properties to just those that I am interesting in seeing.

While there are other similar ways to accomplish this task, this approach is perhaps the most straightforward. It also serves to illustrate important concepts and techniques.

Make Time for Play

Even if you are comfortable with the pipeline, there is a lesson to be learned here. Thinking in terms of physical objects can help you figure out what it is you're trying to do.

So the next time you're having difficulty with a Windows PowerShell concept, try stepping away from the computer and replicating the task using everyday objects as an illustration. I humbly recommend that you keep a small bag of ping-pong balls (or cubes, if you can find them somewhere) handy for just this purpose.

Cmdlet of the Month: Out-File

How many times have you directed a cmdlet's output to a file using the > symbol? That is something like Get-Process > Processes.txt. Did you know that you're really just using the Out-File cmdlet in disguise? Here's a command that performs the same exact function: Get-Process | Out-File Processes.txt.

Of course, that requires a bit more typing, so why bother typing out the full Out-File cmdlet? One reason is that Out-File is much clearer when read. Someone who comes along in, say, six months might look at one of your scripts and wonder what the > stands for (it is, after all, kind of a legacy thing).

By contrast, Out-File makes it pretty obvious that a file is going to be created and written to. Furthermore, Out-File gives you the -append parameter (pretty much the same as >>), along with -force and -noClobber parameters, letting you control whether existing files are overwritten. Finally, typing out Out-File also gives you access to the -whatif parameter. This handy parameter will show you what Out-File will do, without actually doing it! That's a great way to test a complicated command line with little or no danger.

Don Jones is the coauthor of Windows PowerShell: TFM and teaches Windows PowerShell classes (www.ScriptingTraining.com).

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.