oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Top 10 Tips for Using Windows PowerShell
Pages: 1, 2, 3, 4, 5

5. Understand the pipeline is object-based not text-based

Remember that objects are passed through the pipeline, not just text, which means a great deal of information is available to you at any point in the pipe.

If you've worked with other shells such as cmd in Windows or any of the Unix-based shells, you know that commands can output text, which can in turn be passed (piped) to other commands. For example, in cmd and DOS, the dir command prints out a list of files. This list is in the form of text. If you just run the dir command, you'll see the list appear in the console window. However, you can instead send that text as input into another command; this is called piping. The more command, for example, reads in text and writes out only a page worth, and then waits for a keypress before writing another page worth of text:

C:\WINDOWS >dir /ad | more
 Volume in drive C has no label.
 Volume Serial Number is BCFB-2313

 Directory of C:\WINDOWS

11/04/2006  10:59 PM    <DIR>          .
11/04/2006  10:59 PM    <DIR>          ..
06/07/2004  07:03 PM    <DIR>          $NtUninstallKB824105$
06/07/2004  07:05 PM    <DIR>          $NtUninstallKB824141$
10/15/2004  09:06 PM    <DIR>          $NtUninstallKB824151$
06/07/2004  07:01 PM    <DIR>          $NtUninstallKB825119$
06/07/2004  07:00 PM    <DIR>          $NtUninstallKB828035$
06/07/2004  06:58 PM    <DIR>          $NtUninstallKB828741$
06/07/2004  06:59 PM    <DIR>          $NtUninstallKB833407$
-- More  --

Over the years, people have taken the fact that this pipe is text and written some really powerful utilities to manipulate the text. Years ago, awk and grep arrived, which let you process the text. (awk, for example, could easily take the preceding text and break up the lines into records divided by spaces. grep lets you search through the text for patterns; people still use grep a lot.) Today we have even more powerful languages for text processing, such as Perl.

While simple commands in DOS such as dir use text, more powerful tools work with objects. If you're managing a set of processes on a Windows system, a single process is not just a line of text. A single process is an object made up of more objects such as the ID for the process, the security settings, and so on. These are more than just text.

If a tool retrieves a set of processes and then writes the information out just as text for another tool to use, the second tool is getting cheated. The tool is only getting a text-based form of the processes, not the actual objects.

A better approach, then, is to simply let the first tool pipe the actual objects on to the second tool. At first this might seem like a problem since ultimately we are dealing with a console window which displays text. But if you think about the dir | more commands working together, the only output that finally makes it to the console window is the output from the more. The information passed from dir to more is never displayed on the console.

Of course, eventually the objects do have to be displayed as text in the console window. That's where the ToString method comes in that I mentioned in the previous section. At the end of the pipeline the console can simply call ToString to get a text-representation of the object, and write that text to the console window.

Here's an example:

PS C:\WINDOWS> dir | sort LastWriteTime | more

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          7/4/1995   1:33 PM      11776 Ckrfresh.exe
-a---         7/31/1995   5:44 PM     212480 PCDLIB32.DLL
-a---          5/3/1996  10:36 AM      18432 Setup_ck.dll
-ar--          5/3/1996  12:21 PM      27648 Setup_ck.exe
-a---         7/22/1998  12:29 AM         21 CS_SETUP.ini
-a---        10/29/1998   3:45 PM     306688 IsUninst.exe
-a---         1/12/1999  10:39 AM       6656 delttsul.exe
-a---         1/12/1999  10:40 AM      29184 rmud.exe
-a---         6/18/1999   4:49 PM     165888 Ckconfig.exe
-a---        11/10/1999   3:05 PM      86016 unvise32qt.exe

(dir is an alias for Get-Children, sort is an alias for Sort-Object, and more is a function that calls Out-Host -paging. Thus, dir | sort LastWriteTime | more is shorthand for Get-ChildItem | Sort-Object LastWriteTime | Out-Host -Paging.)

This example lists the directory contents, sorts it on the LastWriteTime field, and then writes it out per page. But since the objects pass through the pipeline and are only converted to text at the end, you can save the results of any output into a variable, preserving the types, like so:

PS C:\WINDOWS> $a = dir | sort LastWriteTime

The variable $a now contains the results of the Sort-Object command, but the contents of $a is not text. The data is preserved. The dir command (Get-ChildItem) returned an array of FileInfo and DirectoryInfo objects, which was piped through the sort (Sort-Object) command. Thus, $a is an array of FileInfo and DirectoryInfo objects. You can access the individual elements using brackets like so:

PS C:\WINDOWS> $a[0]

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---          7/4/1995   1:33 PM      11776 Ckrfresh.exe

PS C:\WINDOWS> $a[1]

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\WINDOWS

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         7/31/1995   5:44 PM     212480 PCDLIB32.DLL

In the next section I talk about calling methods on objects in more detail but here you can see that if you call GetType on the objects you can see for sure the type of objects you have (and that they definitely aren't text strings). Here I find out the type of my $a variable:

PS C:\WINDOWS> $a.GetType()

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

The $a object is an array. Let's see what the first element is:

PS C:\WINDOWS> $a[0].GetType()

IsPublic IsSerial Name                BaseType
-------- -------- ----                --------
True     True     FileInfo            System.IO.FileSystemInfo

6. Make use of the objects and their members

The objects that pass through the pipe are instances of classes. You can make use of their methods and data.

Don't forget that your objects, including those that are the results of the commands, have members that you can make use of. You can find an object's members using the Get-Member command:

PS C:\Documents and Settings\Jeff> $a = "abc"
PS C:\Documents and Settings\Jeff> Get-Member -InputObject $a

   TypeName: System.String

Name             MemberType            Definition
----             ----------            ----------
Clone            Method                System.Object Clone()
CompareTo        Method                System.Int32 CompareTo(Object value),...
Contains         Method                System.Boolean Contains(String value)
CopyTo           Method                System.Void CopyTo(Int32 sourceIndex,...
EndsWith         Method                System.Boolean EndsWith(String value)...

If you pipe the output from one command through Get-Member, Get-Member will list the members for each type of object that comes through. If two objects come through and they are of the same type, Get-Member will only list the members once for that type. For example, if you want to know what members are available of the objects returned by the Get-Process command, pipe the results through Get-Member, like so:

PS C:\Documents and Settings\Jeff> Get-Process | Get-Member

   TypeName: System.Diagnostics.Process

Name                           MemberType     Definition
----                           ----------     ----------
Handles                        AliasProperty  Handles = Handlecount
Name                           AliasProperty  Name = ProcessName
NPM                            AliasProperty  NPM = NonpagedSystemMemorySize
PM                             AliasProperty  PM = PagedMemorySize
VM                             AliasProperty  VM = VirtualMemorySize
WS                             AliasProperty  WS = WorkingSet
add_Disposed                   Method         System.Void add_Disposed(Event...
add_ErrorDataReceived          Method         System.Void add_ErrorDataRecei...

The Where-Object command can be used to filter objects in the pipeline, allowing only objects through that meet the criteria you specify. To specify criteria, you will access the members of the objects passing through the pipeline.

For example, although I didn't include the full output here, the Get-Process | Get-Member results showed me the objects in the results of Get-Process each have a member called VirtualMemorySize. Suppose, then, I want to list all processes that have a virtual memory size greater than 100 MB (which is 104,857,600 bytes). Here's how I can do it:

PS C:\Documents and Settings\Jeff> Get-Process | Where-Object {$_.VirtualMemorySize -gt 104857600}

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    948      15    38928      52788   346    12.23    712 dexplore
    256      10    72624      81592   194   136.00   2664 firefox
    350      14    30948      44268   133     7.06   3576 OUTLOOK
    309       8    31928       6404   833   221.14    356 sqlservr
    334       9    22188      14784   817     0.53    852 sqlservr
   1598      91    18668      27404   142   156.75   1864 svchost
    356      24    15708      23740   169   201.47   1536 WINWORD

The Where-Object command takes a parameter that is a script in curly braces. The script runs for each object passing through the pipeline. As each object passes through, it gets placed in the variable called $_. Thus, $_.VirtualMemorySize refers to the VirtualMemorySize member of the object currently in the pipeline. The -gt is the comparison operator for greater than; thus, I'm testing if the current object has a virtual memory size of greater than 104,857,600 bytes.

Get-ChildItem | foreach { $_.Fullname }

As you can see, you should be familiar with the members of the objects, which can be found in the online help. If you go to the online help page, scroll down and click on System, scroll down and click on String, you'll be at the help page for the String class. At the very bottom (as well as in the tree in the left pane) is an item called String Members. That page lists all the members for the String class.

For example, one member is EndsWith. This tells you if a string ends with a given set of characters. You're free to use this member. Here's a quick example:

PS C:\> $a = "This is a test"
PS C:\> $a.EndsWith("test")
PS C:\> $a.EndsWith("this")
PS C:\>

Pages: 1, 2, 3, 4, 5

Next Pagearrow