Idera nSoftware Compellent

Chapter 5. The PowerShell Pipeline

The PowerShell pipeline chains together a number of commands similar to a production assembly. So, one command hands over its result to the next, and at the end, you receive the result.

Topics Covered:

Using the PowerShell Pipeline

Instruction chains are really nothing new. The old console was able to forward (or "pipe") the results of a command to the next with the "pipe" operator "|". One of the more known usages was to pipe data to the tool more, which then would present the data screen page by screen page:

Dir | more

In contrast to the traditional concept of text piping, the PowerShell pipeline takes an object-oriented approach and implements it in real time. Have a look:

Dir | Sort-Object Length | Select-Object Name, Length |
ConvertTo-Html | Out-File report.htm
.\report.htm

It returns an HTML report on the current directory contents sorted by file size. All of this starts with a Dir command, which then passes its result to Sort-Object. The sorted result then gets limited to only the properties you want in the report. ConvertTo-Html converts the objects to HTML which is then written to a file.

Object-oriented Pipeline

What you see here is a true object-oriented pipeline so the results from a command remain rich objects. Only at the end of the pipeline will the results be reduced to text or HTML or whatever you choose for output. Take a look at Sort-Object. It sorts the directory listing by file size. If the pipeline had simply fed plain text into Sort-Object, you would have had to tell Sort-Object just where the file size information was to be found in the raw text. You would also have had to tell Sort-Object to sort this information numerically and not alphabetically. Not so here. All you need to do is tell Sort-Object which object property you want to sort. The object nature tells Sort-Object all it needs to know: where the information you want to sort is found, and whether it is numeric or letters.

You only have to tell Sort-Object which object property to use for sorting because PowerShell sends results as rich .NET objects through the pipeline. Sort-Objectdoes all the rest automatically. Simply replace Length with another object property, such as Name or LastWriteTime, to sort according to these criteria. Unlike text, information in an object is clearly structured: this is a crucial PowerShell pipeline advantage.

Text Not Converted Until the End

The PowerShell pipeline is always used, even when you provide only a single command. PowerShell attaches to your input the cmdlet Out-Default which converts the resulting objects into text at the end of the pipeline.

Even a simple Dir command is appended internally and converted into a pipeline command:

Dir | Out-Default

Of course, the real pipeline benefits show only when you start adding more commands. The chaining of several commands allows you to use commands like Lego building blocks to assemble a complete solution from single commands. The following command would output only a directory's text files listing in alphabetical order:

Dir *.txt | Sort-Object

The cmdlets in Table 5.1 have been specially developed for the pipeline and the tasks frequently performed in it. They will all be demonstrated in the following pages of this chapter.

Just make sure that the commands you use in a pipeline actually do process information from the pipeline. The following line, while it is technically OK, is really useless because notepad.exe cannot process pipeline results:

Dir | Sort-Object | notepad

If you'd like to open pipeline results in an editor, you should put the results in a file first and then open the file with the editor:

Dir | Sort-Object | Out-File result.txt; notepad result.txt
Cmdlet/Function Description
Compare-Object Compares two objects or object collections and marks their differences
ConvertTo-Html Converts objects into HTML code
Export-Clixml Saves objects to a file (serialization)
Export-Csv Saves objects in a comma-separated values file
ForEach-Object Returns each pipeline object one after the other
Format-List Outputs results as a list
Format-Table Outputs results as a table
Format-Wide Outputs results in several columns
Get-Unique Removes duplicates from a list of values
Group-Object Groups results according to a criterion
Import-Clixml Imports objects from a file and creates objects out of them (deserialization)
Measure-Object Calculates the statistical frequency distribution of object values or texts
more Returns text one page at a time
Out-File Writes results to a file
Out-Host Outputs results in the console
Out-Host -paging Returns text one page at a time
Out-Null Deletes results
Out-Printer Sends results to printer
Out-String Converts results into plain text
Select-Object Filters properties of an object and limits number of results as requested
Sort-Object Sorts results
Tee-Object Copies the pipeline's contents and saves it to a file or a variable
Where-Object Filters results according to a criterion

Table 5.1: Typical pipeline cmdlets and functions

Streaming: Real-time Processing or Not?

When you combine several commands in a pipeline, you'll want to ask when each separate command will actually be processed: consecutively or at the same time? The pipeline processes the results in real time, at least when the commands chained together in the pipeline support real-time processing. That's why there are two pipeline modes:

  • Sequential (slow) mode: In sequential mode, pipeline commands are executed one at a time. So the command's results are passed on to the next one only after the command has completely performed its task. This mode is slow and hogs memory because results are returned only after all commands finish their work and the pipeline has to store the entire results of each command. The sequential mode basically corresponds to the variable concept that first saves the result of a command to a variable before forwarding it to the next command.
  • Streaming Mode (quick): The streaming mode immediately processes each command result. Every single result is directly passed onto the subsequent command. It rushes through the entire pipeline and is immediately output. This quick mode saves memory because results are output while the pipeline commands are still performing their tasks. The pipeline doesn't have to store all of the command's results, but only one single result at a time.

"Blocking" Pipeline Commands

You can sort pipeline results through a blocking operation because sorting can only take place when all results are available. This also means there can be long processing times and it can even cause instability if you don't pay attention to memory requirements:

# Attention: danger!
Dir C:\ -recurse | Sort-Object

If you execute this extreme example, you won't see any signs of life from PowerShell for a long time. If you let the command run too long, you may even lose control of your computer and have to reboot it because it runs out of memory. What's going on here?

In this example Dir returns all files and directors of C:\. These results are passed by the pipeline to Sort-Object, and because Sort-Objectcan only sort the results when all of them are available, it collects the results as they come in. Those results then create a "data jam" in the pipeline. The two problem areas in sequential mode are:

First problem: You won't see any activity as long as data is being collected. The more data that has to be acquired, the longer the wait time will be. In the above example, it can take several minutes.

Second problem: Because enormous amounts of data have to be stored temporarily before Sort-Object can process them, the memory space requirement is very high. In this case, it's even higher that the entire Windows system will respond more and more clumsily until finally you won't be able to control it any longer.

That's not all. In this specific case, confusing error messages will pile up: if you have Dir output a complete recurse folder listing, it may encounter subdirectories where you have no access rights. This will lead to (benign) error messages that will always be immediately output. Since the results of the Dir command are passed along the pipeline to the following command, which collects it before outputting it, error messages will appear out of the blue.

So, if you use sequential pipeline commands like Sort-Object, which block the pipeline and wait for all results, make sure the pipeline is not processing excessive amount of data.

Whether a command supports streaming is up to the programmer. For Sort-Object, there are technical reasons why this command must first wait for all results. Otherwise, it wouldn't be able to sort the results. If you use commands that are not designed for PowerShell then clearly their original programmers could not have taken into account the special demands of PowerShell. For example, if you use the traditional command more.com to output information one page at a time, it will work but more.com is also a blocking command that could interrupt pipeline streaming:

# If the preceding command can execute its task quickly,
# you may not notice that it can be a block:
Dir | more.com

# If the preceding command requires much time,
# its blocking action may cause issues:
Dir c:\ -recurse | more.com

But also genuine PowerShell cmdlets, functions, or scripts can block pipelines if the programmer doesn't use streaming. Surprisingly, PowerShell developers forgot to add streaming support to the integrated more function. This is why more essentially doesn't behave much differently than the ancient more.com command:

# The more function doesn't support streaming, either,
# and that means you'll have to wait:
Dir c:\ -recurse | more

The cmdlet Out-Host means you don't have to wait. Its parameter -paging also supports page-by-page outputs. Because this cmdlet supports streaming, you won't have to sit in front of the console twiddling your thumbs:

Dir c:\ -recurse | Out-Host -paging

In Chapters 9 and 10, you'll learn what a programmer has to watch out for so that PowerShell cmdlets, functions, or scripts will support the pipeline streaming mode.

Converting Objects into Text

At the end of a day, you want commands to return visible results, not objects. So, while results stay rich data objects while traveling the pipeline, at the end of the pipeline, they must be converted into text. This is done by (internally) adding Out-Default to your input. The following commands are identical:

Dir
Dir | Out-Default

Out-Default transforms the pipeline result into visible text. To do so, it first calls Format-Table (or Format-List when there are more than five properties to output) internally, followed by Out-Host. Out-Host outputs the text in the console. So, this is what happens internally:

Dir | Format-Table | Out-Host

Making Object Properties Visible

To really see all the object properties and not just the ones PowerShell "thinks" are important, use Format-Table and add a "*" to select all object properties.

Dir | Format-Table *
PSPat PSPar PSChi PSDri PSPro PSIsC Mode Name Pare Exis Root Full
h entPa ldNam ve vider ontai nt ts Name
th e ner


----- ----- ----- ----- ----- ----- ---- ---- ---- ---- ---- ----
Mi... Mi... Ap... C Mi... True d... A... T... True C:\ C...
Mi... Mi... Ba... C Mi... True d... B... T... True C:\ C...
Mi... Mi... Co... C Mi... True d... C... T... True C:\ C...
Mi... Mi... Debug C Mi... True d... D... T... True C:\ C...
Mi... Mi... De... C Mi... True d... D... T... True C:\ C...

You now get so much information that columns shrink to an unreadable format.

For example, if you'd prefer not to reduce visual display because of lacking space, use the -wrap parameter, like this:

Dir | Format-Table * -wrap

Still, the horizontal table design is unsuitable for more than just a handful of properties. This is why PowerShell uses Format-List instead of Format-Table whenever there are more than five properties to display, and you should do the same:

Dir | Format-List *

You will now see a list of several lines for each object's property. For a folder, it might look like this:

PSPath : Microsoft.PowerShell.Core\FileSystem::C:\
Users\Tobias Weltner\Music
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\
Users\Tobias Weltner
PSChildName : Music
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : True
Mode : d-r--
Name : Music
Parent : Tobias Weltner
Exists : True
Root : C:\
FullName : C:\Users\Tobias Weltner\Music
Extension :
CreationTime : 13.04.2007 01:54:53
CreationTimeUtc : 12.04.2007 23:54:53
LastAccessTime : 10.05.2007 21:37:26
LastAccessTimeUtc : 10.05.2007 19:37:26
LastWriteTime : 10.05.2007 21:37:26
LastWriteTimeUtc : 10.05.2007 19:37:26
Attributes : ReadOnly, Directory

A file has slightly different properties:

PSPath : Microsoft.PowerShell.Core\FileSystem::C:\
Users\Tobias Weltner\views.PS1
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\
Users\Tobias Weltner
PSChildName : views.PS1
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -a---
Name : views.PS1
Length : 4045
DirectoryName : C:\Users\Tobias Weltner
Directory : C:\Users\Tobias Weltner
IsReadOnly : False

Exists : True
FullName : C:\Users\Tobias Weltner\views.PS1
Extension : .PS1
CreationTime : 18.09.2007 16:30:13
CreationTimeUtc : 18.09.2007 14:30:13
LastAccessTime : 18.09.2007 16:30:13
LastAccessTimeUtc : 18.09.2007 14:30:13
LastWriteTime : 18.09.2007 16:46:12
LastWriteTimeUtc : 18.09.2007 14:46:12
Attributes : Archive

The property names are located on the left and their content on the right. You now know how to find out which properties an object contains.

Formatting Pipeline Results

Transforming objects produced by the pipeline is carried out by formatting cmdlets. There are four choices:

Get-Command -verb format
CommandType Name Definition
----------- ---- ----------
Cmdlet Format-Custom Format-Custom [[-Property] <Objec...
Cmdlet Format-List Format-List [[-Property] <Object[...
Cmdlet Format-Table Format-Table [[-Property] <Object...
Cmdlet Format-Wide Format-Wide [[-Property] <Object>...

These formatting cmdlets are not just useful for converting all of an object's properties into text but you can also select the properties you want to see.

Displaying Particular Properties

To do so, you should type the property that you want to see and not just an asterisk behind the cmdlet. The next instruction gets you a directory listing with only Name and Length. Because subdirectories don't have a property called Length, the Length column for the subdirectory is empty:

Dir | Format-Table Name, Length
Name Length
---- ------
Sources
Test
172.16.50.16150.dat 16
172.16.50.17100.dat 16
output.htm 10834
output.txt 1338

Using Wildcard Characters

Wildcard characters are allowed so the next command outputs all running processes that begin with "l". All properties starting with "pe" and ending in "64" are output:

Get-Process i* | Format-Table name,pe*64
Name PeakPagedMemory PeakWorkingSet64 PeakVirtualMemory
Size64 Size64
---- --------------- ---------------- -----------------
IAAnotif 3432448 6496256 81596416
IAANTmon 761856 2363392 25346048
Idle 0 0 0
ieuser 12193792 25616384 180887552
iexplore 37224448 52764672 203845632
IfxPsdSv 1396736 3436544 43646976
IFXSPMGT 3670016 9932800 73412608
IFXTCS 3375104 7675904 72654848
iPodService 3231744 5177344 57401344
iTunesHelper 2408448 5935104 70582272

If you want to use even more complex wildcards, regular expressions are permitted (more information coming in Chapter 13). For example, WMI objects that are returned by Get-WmiObject contain a number of properties that PowerShell returns and that all begin with the "__" character. To exclude these properties, you should use a wildcard like this one:

Get-WmiObject Win32_Share | Format-List [a-z]*
Status : OK
Type : 2147483648
Name : ADMIN$
AccessMask :
AllowMaximum : True
Caption : Remote Admin
Description : Remote Admin
InstallDate :
MaximumAllowed :
Path : C:\Windows

Status : OK
Type : 2147483648
Name : C$
AccessMask :
AllowMaximum : True
Caption : Default share
Description : Default share
InstallDate :
MaximumAllowed :
Path : C:\
(...)

Scriptblocks and "Synthetic" Properties

Scriptblocks can be used as columns as they basically act as PowerShell instructions included in braces that work like synthetic properties to calculate their value. Within a scriptblock, the variable $_ contains the actual object. The scriptblock could convert the Length property into kilobytes if you'd like to output file sizes in kilobytes rather than bytes:

Dir | Format-Table Name, { [int]($_.Length/1KB) }
Name [int]($_.Length/1KB)
---- --------------------
output.htm 11
output.txt 13
backup.pfx 2
cmdlet.txt 23

Or perhaps you'd like your directory listing to denote how many days have passed since a file or a subdirectory was last modified. While the file object doesn't furnish such information, you could calculate this by means of available properties and provide it its own new property. In the LastWriteTime property, the date of the last modification is indicated. By using the New-TimeSpan cmdlet, you can calculate how much time has elapsed up to the current date. To see how this works, look at the line below as an example that calculates the time difference between January 1, 2000, and the current date:

New-TimeSpan "01/01/2000" (Get-Date)
Days : 2818
Hours : 11
Minutes : 59
Seconds : 3
Milliseconds : 699
Ticks : 2435183436996134
TotalDays : 2818,49934837516
TotalHours : 67643,9843610037
TotalMinutes : 4058639,06166022
TotalSeconds : 243518343,699613
TotalMilliseconds : 243518343699,613

Use this scriptblock to output how many days have elapsed from the LastWriteTime property up to the current date and to read it out in its own column:

{(New-TimeSpan $_.LastWriteTime (Get-Date)).Days}

Dir would then return a subdirectory listing that shows how old the file is in days:

Dir | Format-Table Name, Length, `
{(New-TimeSpan $_.LastWriteTime (Get-Date)).Days} -autosize
Name Length (New-TimeSpan
$_.LastWriteTime
(Get-Date)).Days
---- ------ ----------------
Application Data 61
Backup 55
Contacts 158
Debug 82
Desktop 19
Documents 1
(...)

Changing Column Headings

As you use synthetic properties, you'll notice that column headings look strange because PowerShell puts code in them that computes the column contents. However, after reading the last chapter, you know that you can use a hash table to format columns more effectively and that you can also rename them:

$column = @{Expression={ [int]($_.Length/1KB) }; Label="KB" }
Dir | Format-Table Name, $column
Name KB
---- --
output.htm 11
output.txt 13
backup.pfx 2
cmdlet.txt 23

Optimizing Column Width

Text output conforms to the width of your PowerShell console's display buffer as it tries to accommodate as much data as possible. Because the pipeline processes results in real time, Format-Table cannot know how wide of a space the column elements will occupy. As a result, the cmdlet tends to be generous in sizing columns. If you specify the -auto parameter, Format-Table will collect all results first before setting the maximum width for all elements. You can optimize output, but the results will no longer be output in real time:

$column = @{Expression={ [int]($_.Length/1KB) }; Label="KB" }
Dir | Format-Table Name, $column -auto
Name KB
---- --
output.htm 11
output.txt 13
backup.pfx 2
cmdlet.txt 23

PropertySets and Views

If you don't specify any particular properties behind the formatting cmdlet, PowerShell will determine which object properties to convert into text. This automatic feature comes from what is known as the Extended Type System (ETS), which you'll learn more about a bit later. For many commands, PowerShell supplies PropertySets,which are compilations of especially important object properties. They make it unnecessary to specify properties manually, yet still receive basic information.

If you output the result of Get-Process without further specifications, PowerShell will routinely convert the following Process properties objects into text:

Get-Process
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
36 2 712 48 21 2616 agrsmsvc
328 9 16620 3752 114 464 AppSvc32
105 3 1044 592 37 1228 Ati2evxx

You can set quite a different priority by specifying a PropertySet like PSResources after Format-Table:

Get-Process | Format-Table PSResources
Name Id Handle Working PagedMem Private VirtualMe Total
Count Set orySize Memory morySize Process
Size or Time
---- -- ------ ------- -------- -------- --------- -------
agrsmsvc 2616 36 49152 729088 729088 21884928
AppSvc32 464 328 3842048 17018880 17018880 119091200
Ati2evxx 1228 105 606208 1069056 1069056 38473728
Ati2evxx 1732 130 3743744 2097152 2097152 50249728
ATSwpNav 2064 79 1069056 4808704 4808704 60739584 00:09
(...)

And PowerShell will select other properties when you use the PropertySet PSConfiguration:

Get-Process | Format-Table PSConfiguration
Name Id PriorityClass FileVersion
---- -- ------------- -----------
agrsmsvc 2616
AppSvc32 464
Ati2evxx 1228
Ati2evxx 1732
ATSwpNav 2064 Normal 7, 7, 0, 25

This raises the question of what exactly is a PropertySet and how to find out what they are about. PropertySets are defined for each cmdlet. If you want to see which PropertySets are available for the Get-Process cmdlet, use Get-Member to list all members of the PropertySet type:

Get-Process | Get-Member -MemberType PropertySet
TypeName: System.Diagnostics.Process

Name MemberType Definition
---- ---------- ----------
PSConfiguration PropertySet PSConfiguration {Name, Id,
PriorityClass, FileVersion}
PSResources PropertySet PSResources {Name, Id, Handlecount,
WorkingSet, NonPagedMemorySize,
PagedMemory...

The properties that make a PropertySetvisible are listed after the respective PropertySet. As you see, two PropertySets exist for the Get-Process cmdlet. No practical PropertySets are defined for most other cmdlets, but you make up for that. In the section about the ETS toward the end of this chapter, you'll learn how to define a command's properties that are most important for you as a PropertySet.

Views work in a similar way as they set not just the properties that are to be converted into text, but they can also specify column names or widths and even group information.

# All running processes grouped after start time:
Get-Process | Format-Table -view StartTime

# All running processes grouped according to priority:
Get-Process | Format-Table -view Priority

Views are highly specific and always apply to particular object types and particular formatting cmdlets. The Priority view applies only to Format-Table and only when you display Process objects with it. This view doesn't work for Format-List:

Get-Process | Format-List -view Priority
Format-List : View name Priority cannot be found.
At line:1 char:26
+ Get-Process | Format-List <<<< -view Priority

You'll get an error message if you try to use it to format a file listing and not processes,:

Dir | Format-Table -view Priority
Format-Table : View name Priority cannot be found.
At line:1 char:19
+ Dir | Format-Table <<<< -view Priority

Unfortunately, there is no built-in option for finding out which views are available. In the section on the ETS, you'll learn solutions to this problem, and you'll also read about how to define your own views.

Sorting and Grouping Pipeline Results

Your first task is to process and concentrate this information since PowerShell commands often return large amounts of data. Using the cmdlets Sort-Object and Group-Object, you can sort and group other command results. In the simplest scenario, just append Sort-Object to a pipeline command and your output will already be sorted. It's really very simple:

Dir | Sort-Object

When you do that, Sort-Object selects the property it uses for sorting. It's better to choose the sorting criterion yourself as every object property may be used as a sorting criterion. For example, you could use one to create a descending list of a subdirectory's largest files:

Dir | Sort-Object -property Length -descending

So that you can make good use of Sort-Object and all the other following cmdlets, you must also know which properties are available for the objects traveling through the pipeline. In the last section, you learned how to do that. Send the result of Dir to Format-List * first, then you'll see all properties and you can select one to use for subsequent sorting:

Dir | Format-List *

The parameter -property allows you to use any object property as a sorting criterion. In this case, Length is used and Sort-Object does the rest of the work itself. You need only describe where the file size is located (it is clearly available in the Length object property). You do not have to state explicitly that the file size is numeric and so has to be sorted numerically, not alphabetically. Sort-Object can sort by more than one property at the same time. For example, if you'd like to alphabetize all the files in a subdirectory by type first (Extension property) and then by name (Name property), specify both properties:

Dir | Sort-Object Extension, Name

Sort Object and Hash Tables

Sort-Object not only uses properties for sorting operations. You may also use hash tables as an alternative. Let's assume that you want to sort a subdirectory listing by file size and name, while the file size must be sorted in descending and names in ascending order. How do you accomplish that? In any case, not like this:

Dir | Sort-Object Length, Name -descending, -ascending
Sort-Object : A parameter could not be found that matches
parameter name "System.Object[]".
At line:1 char:18
+ Dir | Sort-Object <<<< Length, Name -descending, -ascending

You can solve this problem by passing Sort-Object to a hash table (see Chapter 4).

Dir | Sort-Object @{expression="Length";Descending=$true}, `
@{expression="Name";Ascending=$true}

The hash table makes it possible to append additional information to a property, so you can separately specify for each property the sorting sequence you prefer.

Apropos hash tables: can you sort these, too? At first glance, it would seem so:

$hash=@{"Tobias"=90;"Martina"=90;"Cofi"=80;"Zumsel"=100}
$hash | Sort-Object Value -descending
Name Value
---- -----
Tobias 90
McGuffin 100
Cofi 80
Martina 90

Yet it does work if you pass the enumerator directly to Sort-Object. This is what you'll get with GetEnumerator():

$hash.GetEnumerator() | Sort-Object Value -descending
Name Value
---- -----
Zumsel 100
Martina 90
Tobias 90
Cofi 80

Grouping Information

Group-Object works by grouping similar objects and then reporting their number. You only need specify the property to Group-Object as your grouping criterion. The next line returns a good status overview of services:

Get-Service | Group-Object Status
Count Name Group
----- ---- -----
91 Running {AeLookupSvc, AgereModemAudio, Appinfo, Ati
External Event Utility...}
67 Stopped {ALG, AppMgmt, Automatic LiveUpdate - Scheduler,
BthServ...}

In this case, Group-Object returns an object for every group. The number of groups depends only on how many different values could be found in the property specified in the grouping operation. The Status property always returns either the values "running" or "stopped" for services. This is why Group-Object returned exactly two objects in this example.

The results' object always contains the properties Count, Name, and Group. Services are grouped according to the desired criteria in the Group property. The following shows how you could obtain a list of all currently running services:

$result = Get-Service | Group-Object Status
$result[0].Group

It works in a very similar way for other objects. In a file system, Group-Object would put file types in a subdirectory and list their frequency if you use Extension as grouping property:

Dir | Group-Object Extension

Of course, you could subsequently also sort the result:

Dir | Group-Object Extension | Sort-Object Count -descending
Count Name Group
----- ---- -----
22 {Application Data, Backup, Contacts, Debug...}
16 .ps1 {filter.ps1, findview.PS1, findview2.PS1, findvi...}
12 .txt {output.txt, cmdlet.txt, ergebnis.txt, error.txt...}
4 .csv {ergebnis.csv, history.csv, test.csv, test1.csv}
3 .bat {ping.bat, safetycopy.bat, test.bat}
2 .xml {export.xml, now.xml}
2 .htm {output.htm, report.htm}

Using Grouping Expressions

Group-Object not only groups by set properties but also can use PowerShell expressions. These must be specified in braces behind Group-Object. The respective object is within the expression as is customary for a $_ variable. The expression can report back on any results. Then Group-Object groups the objects accordingly.

In the following line, the expression returns True if the file size exceeds 100 KB or False as the line returns two groups, True and False. All files larger than 100KB are in the True group:

Dir | Group-Object {$_.Length -gt 100KB}
Count Name Group
----- ---- -----
67 False {Application Data, Backup, Contacts, Debug...}
2 True {export.xml, now.xml} in the column Count...

However, the expression's return value doesn't have to be either True or False, but is arbitrary. In the next line, the expression determines the file name's first letter and returns this in capitals. The result: Group-Object groups the subdirectory contents by first letters:

Dir | Group-Object {$_.name.SubString(0,1).toUpper()}
Count Name Group
----- ---- -----
4 A {Application Data, alias1, output.htm, output.txt}
2 B {Backup, backup.pfx}
2 C {Contacts, cmdlet.txt}
5 D {Debug, Desktop, Documents, Downloads...}
5 F {Favorites, filter.ps1, findview.PS1, findview2.PS1...}
3 L {Links, layout.lxy, liste.txt}
3 M {MSI, Music, meinskript.ps1}
3 P {Pictures, p1.nrproj, ping.bat}
7 S {Saved Games, Searches, Sources, SyntaxEditor...}
15 T {Test, test.bat, test.csv, test.ps1...}
2 V {Videos, views.PS1}
1 [ {[test]}
1 1 {1}
4 E {result.csv, result.txt, error.txt, export.xml}
4 H {mainscript.ps1, help.txt, help2.txt, history.csv}
1 I {info.txt}
2 N {netto.ps1, now.xml}
3 R {countfunctions.ps1, report.htm, root.cer}
2 U {unsigned.ps1, .ps1}

If you take a closer look at the Group-Object result, you'll notice that after each group name is an array in which single group objects are summarized. So, you could output a practical, alphabetically grouped directory view from this result:

Dir | Group-Object {$_.name.SubString(0,1).toUpper()} |
ForEach-Object { ($_.Name)*7; "======="; $_.Group}
(...)
BBBBBBB
=======
d---- 26.07.2007 11:03 Backup
-a--- 17.09.2007 16:05 1732 backup.pfx
CCCCCCC
=======
d-r-- 13.04.2007 15:05 Contacts
-a--- 13.08.2007 13:41 23586 cmdlet.txt
DDDDDDD
=======
d---- 28.06.2007 18:33 Debug
d-r-- 30.08.2007 15:56 Desktop
d-r-- 17.09.2007 13:29 Documents
d-r-- 24.09.2007 11:22 Downloads
-a--- 26.04.2007 11:43 1046 drive.vbs
(...)

Of course, it will cost a little memory space to store the grouped objects in arrays. Use the parameter -noelement if you don't need the grouped objects.. You could then receive a quick listing of how many processes of which companies are running on your computer. However, because of the -noelement parameter, you will not be able to see any longer which processes these are in detail:

Get-Process | Group-Object -property Company -noelement
Count Name
----- ----
50
1 AuthenTec, Inc.
2 LG Electronics Inc.
1 Symantec Corporation
2 ATI Technologies Inc.
30 Microsoft Corporation
1 Adobe Systems, Inc.
1 BIT LEADER
1 LG Electronics
1 Intel Corporation
2 Apple Inc.
1 BlazeVideo Company
1 ShellTools LLC
2 Infineon Technologies AG
1 Just Great Software
1 Realtek Semiconductor
1 Synaptics, Inc.

Using Formatting Cmdlets to Form Groups

Group-Object isn't the only option for grouping information. Formatting cmdlets like Format-Table or Format-List can also group information if you use the -groupBy parameter. You can specify the property that you want to use as a grouping criterion after it. For example, if you'd like to group a subdirectory's contents by file type, use the Extension property:

Dir | Format-Table -groupBy Extension

The result appears to be correct at first glance. However, if you look more carefully, you'll find many groups repeated as Format-Table tried not to disrupt the streaming pipeline and processed the files unleashed by Dir running through the pipeline in real time. This leads to a continual accumulation of new groups as files pass through the pipeline that no longer fit into the current group. So, if you want to form groups, you will need to interrupt pipeline streaming and sort the files first, based on the criterion you want to group them afterwards:

Dir | Sort-Object Extension, Name | Format-Table -groupBy Extension
Directory: Microsoft.PowerShell.Core\FileSystem::C:\Users\
Tobias Weltner

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 10.08.2007 11:28 116 ping.bat
-a--- 18.09.2006 23:43 24 backupcopy.bat
-a--- 15.08.2007 20:00 569 test.bat

Directory: Microsoft.PowerShell.Core\FileSystem::C:\Users\
Tobias Weltner

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 15.08.2007 08:44 307 history.csv
-a--- 15.08.2007 09:35 8160 test.csv

In this example, the result of Dir is directly sorted by Sort-Object according to two properties, first by extension and then by name. The result is that the groups are sorted alphabetically by name.

Filtering Pipeline Results

Pipeline filters allow only certain objects or object properties through the pipeline. That's practical, because often you will want all results that a command returns. Where-Object permits only those objects to pass through that meet certain criterion. Select-Object also allows only certain object properties to travel through the pipeline. You can use ForEach-Object to process all objects in the pipeline sequentially, enabling you to make your own filters. Finally, Get-Unique removes pipeline duplicates. Let's take a closer look at filters.

Filtering Objects Out of the Pipeline

If you're only interested in certain objects, assign Where-Object the task of closely examining all objects and allowing only those through that meet your criterion, which consists of object properties. For example, if you don't want to view all services returned by Get-Service, but only currently running services, you'll first have to know which service object property reveals whether the service is running or not. You will need more detailed knowledge about the properties supported by an object.

You already know how to ferret out these properties. If you'll recall, Format-List neatly lists all of an object's properties when you use the asterisk character as an argument. You will only need an object example that you can examine with Format-List.

To do so, use the same command that you will want to use later in your pipeline, such as Get-Service and save its result to a variable. As commands return their results in arrays and store each object in it as array elements, you can take the first element you find out of the array and pass it to Format-List:

$result = Get-Service

$result[0] | Format-List *
Name : AeLookupSvc
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
DisplayName : Applicationlookup
DependentServices : {}
MachineName : .
ServiceName : AeLookupSvc
ServicesDependedOn : {}
ServiceHandle :
Status : Running
ServiceType : Win32ShareProcess
Site :
Container :

Now, you can already see all of the object's properties and then its current values. It should be obvious right away that the information sought can be found in the Status property, so you only want to view the objects whose Status property contains the "running" value. You're now ready to use the pipeline filter:

Get-Service | Where-Object { $_.Status -eq "Running" }
Status Name DisplayName
------ ---- -----------
Running AeLookupSvc Applicationlookup
Running AgereModemAudio Agere Modem Call Progress Audio
Running Appinfo Applicationinformation
Running AppMgmt Applicationmanagement
Running Ati External Ev... Ati External Event Utility
Running AudioEndpointBu... Windows-Audio-Endpoint-building
Running Audiosrv Windows-Audio
Running BFE Basis Filter Engine
Running BITS Intelligent Background Transmiss...
(...)

In fact, it works just the way you want it to work so that now you can see only those services that are actually running. How does Where-Object function? The cmdlet expects you to type a PowerShell command in braces and evaluate the command for every pipeline object. The object that Where-Object was just examining can always be found in the variable $_. $_.Status returns the Status property content and needs only be compared to the value that you want to let through.

In reality, the instruction behind Where-Object works like a condition (see Chapter 7): if the expression results in $true, the object will be let through. That's why you may formulate conditions as complex as you like, but you must only make sure that your instruction results in either $true or $false.

The pipeline filter's principle may be applied to all object types and works in the same way everywhere. As an experienced administrator, you may be a little disappointed that the service objects returned by Get-Service contain relatively little information. If you want to list all services that would automatically start, but at the moment aren't running, you can leverage the built-in Windows Management Instrumentation (WMI) infrastructure as an information source to supply more data. You'll harvest much more information when you ask it about services:

$services = Get-WmiObject Win32_Service
$services[0] | Format-List *
Name : AeLookupSvc
Status : OK
ExitCode : 0
DesktopInteract : False
ErrorControl : Normal
PathName : C:\Windows\system32\svchost.exe -k netsvcs
ServiceType : Share Process
StartMode : Auto
__GENUS : 2
__CLASS : Win32_Service
__SUPERCLASS : Win32_BaseService
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_Service.Name="AeLookupSvc"
__PROPERTY_COUNT : 25
__DERIVATION : {Win32_BaseService, CIM_Service, CIM_Logic
alElement, CIM_ManagedSystemElement}
__SERVER : TOBIASWELTNE-PC
__NAMESPACE : root\cimv2
__PATH : \\TOBIASWELTNE-PC\root\cimv2:Win32_Service.
Name="AeLookupSvc"
AcceptPause : False
AcceptStop : True
Caption : Applicationlookup
CheckPoint : 0
CreationClassName : Win32_Service
Description : Processes application compatibility cache
requirements when applications start.
DisplayName : Applicationlookup
InstallDate :
ProcessId : 1276
ServiceSpecificExitCode : 0
Started : True
StartName : localSystem
State : Running
SystemCreationClassName : Win32_ComputerSystem
SystemName : TOBIASWELTNE-PC
TagId : 0
WaitHint : 0

The information needed for your criteria are located in the Started and StartMode properties. And because the Where-Object pipeline filter is used very often, there exists a practical abbreviation for it: "?". Here is an example of what your pipeline filter could look like:

Get-WmiObject Win32_Service |
? {($_.Started -eq $false) -and ($_.StartMode -eq "Auto")} |
Format-Table
ExitCode Name ProcessId StartMode State Status
-------- ---- --------- --------- ----- ------
0 Automatic... 0 Auto Stopped OK
0 ehstart 0 Auto Stopped OK
0 LiveUpdate... 0 Auto Stopped OK
0 WinDefend 0 Auto Stopped OK

If everything works properly, these lines shouldn't report any services at all because services in the "auto" start-up mode are automatically started and, for this reason, should be running. If you're notified of services, you should verify whether these services are (no longer) running despite auto start. One cause could be that the service has completed its task and was then ended as scheduled.

Incidentally, because WMI objects are not on the internal PowerShell list, results are always displayed as lists. For this reason, at the end our above example, we set a table format using Format-Table, which is much clearer.

The internal WMI service will provide you with helpful information about your computer in response to almost any question. You'll find out exactly what it is in Chapter 18. If you use the -query parameter, you can pass SQL-type queries to this service so that the command will automatically return only the information sought and make pipeline filtering superfluous. You should always keep in mind when using any command that the pipeline filter is practical and easy to use, but not particularly economical. It limits results that are already available. It is better right at the beginning to ask only about the information needed, as it is not as easy to do for all commands as it is for Get-WmiObject:

Get-WmiObject -query "select * from win32_Service where `
Started=false and StartMode='Auto'" | Format-Table
ExitCode Name ProcessId StartMode State Status
-------- ---- --------- --------- ----- ------
0 Automatic Li... 0 Auto Stopped OK
0 ehstart 0 Auto Stopped OK
0 LiveUpdate N... 0 Auto Stopped OK
0 WinDefend 0 Auto Stopped OK

Selecting Object Properties

The information contained in individual objects may be limited as well. You've just seen that some objects, depending on type, may contain many properties of which you often need only a few. By using Select-Object, you can select those properties that really interest you. All other properties will not be allowed through by Select-Object. For example, the following lines will acquire the user object for the integrated Guest account of your computer:

Get-WmiObject Win32_UserAccount -filter `
"LocalAccount=True AND Name='guest'"
AccountType : 512
Caption : TobiasWeltne-PC\guest
Domain : TobiasWeltne-PC
SID : S-1-5-21-3347592486-2700198336-2512522042-501
FullName :
Name : guest

Most of these properties won't interest you, so Select-Object was able to remove them. In the following, just three of your specified properties were returned:

Get-WmiObject Win32_UserAccount -filter `
"LocalAccount=True AND Name='guest'" |
Select-Object Name, Disabled, Description
Name Disabled Description
---- -------- -----------
guest True Default account for guests...

You could have had the same result if you had used a formatting cmdlet. That would even have been to your advantage since you could use the -autosize parameter to optimize column width:

Get-WmiObject Win32_UserAccount -filter `
"LocalAccount=True AND Name='guest'" |
Format-Table Name, Disabled, Description -autosize
Name Disabled Description
---- -------- -----------
guest True Default account for guest access to computer or domain

The significant difference: Format-Table converts properties specified to the object into text. In contrast, Select-Object creates a completely new object containing just these specified properties:

Get-WmiObject Win32_UserAccount -filter `
"LocalAccount=True AND Name='guest'" |
Select-Object Name, Disabled, Description |
Format-Table *
Name Disabled Description
---- -------- -----------
guest True Default account for guest access to computer or domain

You should make sparing use of Select-Object because it takes a disproportionate effort to create a new object. Instead, use formatting cmdlets to specify which object properties are to be displayed. Select-Object is particularly useful when you don't want to convert a pipeline result into text, but instead want to output a comma-separated list using Export-Csv or HTML code using ConvertTo-Html.

If you type an asterisk as wildcard character after Select-Object, all properties will be marked as relevant. Formatting cmdlets will now output all object properties:

Dir | Select-Object * | Format-Table -wrap

If you'd like to view nearly all of an object's properties, it's easier to display only the properties you don't want by typing the parameter -exclude to specify those properties you want to remove from the object. The next line will output all of a file's properties and directory objects, except for those beginning with "PS" (and show internal PowerShell help properties):

Dir | Select-Object * -exclude PS*

Limiting Number of Objects

Select-Object filters not only object properties but can also, if you prefer, reduce the number of objects allowed to traverse the pipeline. This function is considerably more interesting because it allows you to view, among others, the five largest files of a directory or the five processes that have been running the longest:

# List the five largest files in a directory:
Dir | Sort-Object Length -descending |
Select-Object -first 5

# List the five longest-running processes:
Get-Process | Sort-Object StartTime |
Select-Object -last 5 | Format-Table ProcessName, StartTime

# Alias shortcuts make the line shorter but also harder to read:
gps | sort StartTime -ea SilentlyContinue |
select -last 5 | ft ProcessName, StartTime
ProcessName StartTime
----------- ---------
iexplore 20.09.2007 15:00:20
iexplore 20.09.2007 15:05:26
iexplore 20.09.2007 15:30:51
PowerShellPlus.vshost 20.09.2007 16:07:54
iexplore 20.09.2007 16:56:20

A couple of things are interesting here. For example, if you sort a list of processes by StartTime, you'll presumably get several error messages. If you aren't logged on with administrator privileges, you may not retrieve the information from some processes. However, you can avoid this difficulty by setting Sort-Object with parameter -ErrorAction (in short: -ea) to SilentlyContinue. This option is available for nearly every cmdlet and makes sure that error messages won't be displayed.

As a result of such restricted access, not all processes will have any control at all over StartTime. Wherever you can't read the start time because you don't have administrator privileges, a null value will be returned, which messes up the sorting result. You wouldn't get the right results if you wanted to use -first to view the processes that last started running,:

Get-Process | Sort-Object StartTime |
Select-Object -first 5 |
Format-Table ProcessName, StartTime
ProcessName StartTime
----------- ---------
services
SLsvc
SearchIndexer
opvapp
sdclt

Sort-Object uses the value 0 for empty properties. That's why PowerShell gives you the processes for which it couldn't find any start times. This is interesting since those would be exactly the processes which you have no full access rights. However, this is a problem you can solve, and you already know how: by using the pipeline as just previously described. You should simply filter all of the pipeline's objects out that have an empty StartTime property so that you can better understand what those processes actually are, and then add the Description property in the output. That's where process objects record a brief description of the process:

Get-Process | Where-Object {$_.StartTime -ne $null} |
Sort-Object StartTime | Select-Object -first 5 |
Format-Table ProcessName, StartTime, Description
ProcessName StartTime Description
----------- --------- -----------
taskeng 19.09.2007 09:35:19 Task planning module
dwm 19.09.2007 09:35:19 Desktop window manager
explorer 19.09.2007 09:35:19 Windows Explorer
GiljabiStart 19.09.2007 09:35:21 Giljabi Start
ATSwpNav 19.09.2007 09:35:21 ATSwpNav Application

If you concatenate several commands in the pipeline, you can use Tee-Object to skim off intermediate results: either because you need this information somewhere else, too, or because you want to check how the pipeline is working.

Get-Process | Tee-Object -variable a1 |
Select-Object Name, Description |
Tee-Object -variable a2 |
Sort-Object Name

Get-Process first returns all running processes in this pipeline. Select-Object removes every object property except for Name and Description. It then sorts the processes by name. At two locations in this pipeline, Tee-Object accesses the current pipeline result and stores it in a variable without further slowing or influencing pipeline execution. After the pipeline has done its work, you'll find the intermediate result in the variables $a1 and $a2, and you'll be able to analyze it in more depth or use it somewhere else.

If you decide not to set Tee-Object to the -variable parameter, the intermediate result will be saved to a file, and Tee-Object will expect you to provide a file path name. The same applies if you expressly specify the -filePath parameter.

Processing All Pipeline Results Simultaneously

If you prefer, you may also submit the results separately to the pipeline and then decide on a case-by-case basis what to do with them. The right tool is the ForEach-Object cmdlet that can convert objects into text:

Get-Service | ForEach-Object {
"The service {0} is called '{1}': {2}" -f `
$_.Name, $_.DisplayName, $_.Status }
The service AeLookupSvc is called 'Application Lookup': Running
The service AgereModemAudio is called 'Agere Modem Call Progress
Audio': Running
The service ALG is called 'Application Layer Gateway Service': Stopped
The service Appinfo is called 'Application Information': Running
The service AppMgmt is called 'Application Management': Stopped
The service Ati External Event Utility is called 'Ati External Event
Utility': Running
The service AudioEndpointBuilder is called 'Windows-Audio-Endpoint-
Generator': Running
(...)

An instruction block in braces follows ForEach-Object so you can execute as many PowerShell commands as you like as long as you separate the commands by ";". This statement block is executed for every single pipeline object: within the block the current object is available in the $_ variable. In the example, ForEach-Object output a text for every service retrieved by Get-Service and inserts into the text the three properties Name, DisplayName, and Status.

In case you're asking yourself right now what "-f" is and how to insert information into text: look it up in Chapter 13, where all the tasks involving text are explained in detail.

ForEach-Object is actually just Where-Object's big brother; ForEach-Object, Where-Object, can filter out pipeline objects by criterion. To enable ForEach-Object to do this, you merely use a condition. That is, only if the condition is met will the object you want be back in the pipeline. The following lines all lead to the same result:

Get-Service | Where-Object { $_.Status -eq "Running" }
Get-Service | ? { $_.Status -eq "Running" }
Get-Service | ForEach-Object { if ($_.Status -eq "Running") { $_ } }
Get-Service | % { if ($_.Status -eq "Running") { $_ } }

All four lines retrieve a list of currently running services. You see that Where-Object can be shortened with "?" and ForEach-Object with "%". You also can see that Where-Object is actually only ForEach-Object with a built-in condition. For Where-Object, the condition is directly within the braces, and for ForEach-Object in parentheses after the If statement. The rationale for the existence of Where-Object is comfort and clarity.

ForEach-Object actually executes three script blocks, not just one. If you specify only one script block in braces after ForEach-Object, it will be executed once for every pipeline object. If you specify two script blocks, the first will be executed once and before the first pipeline object. If you specify three script blocks, the last will be executed once after the last pipeline object. The following will help you carry out initialization and tidying tasks or simply output initial and ending messages:

Get-Service | ForEach-Object {"Running services:"}{
if ($_.Status -eq "Running") { $_ } }{"Done."}

The three script blocks of ForEach-Object actually correspond to the three script blocks begin, process, and end, which you'll examine in more detail in Chapters 9 and 12. You'll understand after reading these chapters that functions, cmdlets like ForEach-Object and script blocks, are all three basically the same.

Removing Doubles

Get-Unique removes duplicate entries from a sorted list as it presumes that the list was initially sorted according to criterion to make things easier. Get-Unique goes through every element on the list and compares it with the preceding ones. If two are identical, the new object is discarded. So, if you haven't done any sorting, Get-Unique won't work:

1,2,3,1,2,3,1,2,3 | Get-Unique
1,2,3,1,2,3,1,2,3

Only after you sort the list—in this case, an array—will doubles be removed:

1,2,3,1,2,3,1,2,3 | Sort-Object | Get-Unique
1,2,3

This method is particularly interesting when you break down text files' contents into single words. You can use the following line to do so:

$filename = "c:\autoexec.bat"
$(foreach ($line in Get-Content $filename) {
$line.tolower().split(" ")})

Then, you could sort this list of each word of a file and then either send it to Get-Unique (the list of all words that are in a text) or to Group-Object (the number of words used in a text):

$filename = "c:\autoexec.bat"
$(foreach ($line in Get-Content $filename) {
$line.tolower().split(" ")}) | Sort-Object | Get-Unique
$(foreach ($line in Get-Content $filename) {
$line.tolower().split(" ")}) | Sort-Object | Group-Object

Analyzing and Comparing Results

Using the cmdlets Measure-Object and Compare-Object, you can measure and evaluate PowerShell command results. For example, Measure-Object allows you to determine how often particular object properties are distributed. Compare-Object enables you to compare before-and-after snapshots.

Statistical Calculations

Using the Measure-Object cmdlet, you can carry out statistical calculations so you can work out minimal, maximal, and average values for a particular object property. For example, if you want to know how files sizes are distributed in a directory, let Dir give you a directory listing and then examine the Length property:

Dir | Measure-Object Length
Count : 50
Average :
Sum :
Maximum :
Minimum :
Property : Length

Measure-Object counts by default only the specified property's frequency. You should now know that there are 50 objects that have the Length property. Use the relevant parameters if you'd also like to receive the other statistical statements:

Dir | Measure-Object Length -average -maximum -minimum -sum
Count : 50
Average : 36771,76
Sum : 1838588
Maximum : 794050
Minimum : 0
Property : Length

Measure-Object can also search through other text files and ascertain the frequency of characters, words, and lines in them:

Get-Content c:\autoexec.bat | Measure-Object -character -line -word
Lines Words Characters Property
----- ----- ---------- --------
1 5 24

Comparing Objects

You may often want to compare "before-and-after" conditions to find out which processes have restarted since a certain point in time, or which services have changed in comparison to a particular initial state. The Compare-Object cmdlet can perform this task by making use of the fact that PowerShell commands do not retrieve text internally, but real objects.

Comparing Before-and-After Conditions

For example, you should take a snapshot first if you want to find out whether new processes have started up, or running processes, have terminated in a certain period of time::

$before = Get-Process

All processes will now be stored in the variable $before. To be exact, $before is an array in which every process is represented by a process object. You can now compare the current state at any time you like with this snapshot. Just pass the snapshot list and the list of currently running processes to Compare-Object,which will subsequently establish the differences between the two lists:

Compare-Object -referenceObject $before `
-differenceObject (Get-Process)
InputObject SideIndicator
----------- -------------
System.Diagnostics.Process (regedit) =>
System.Diagnostics.Process (SearchFilterHost) <=
System.Diagnostics.Process (SearchProtocolHost) <=

If you're wondering right now why the current list of processes after -differenceObject is enclosed in parentheses, just remember that parameters expect actual results. In the example, the list of currently running processes is acquired as they are newly generated by the Get-Process cmdlet. This command must be placed between parentheses because Get-Process is a cmdlet and the list in question... Everything in parentheses will be executed by PowerShell first and the call result returned afterwards. Compare-Object can work with this result. If you had left out the parentheses, -differenceObject wouldn't have known what to do with the Get-Process specification.

Alternatively, you could, of course, have stored the list of current processes in a variable first, and then passed this variable, even without parentheses, to Compare-Object. It's not absolutely necessary to specify the parameter name if you state the arguments in the right order at the very beginning, that is, first the list with the "before" state, and then the list with the "after" state:

$after = Get-Process
Compare-Object $before $after

The SideIndicator column (line?) reports whether a new process has started running ("=>") or has been ended in the meantime ("<="). Consequently, Compare-Object returns only those processes that are different. Use -includeEqual as an additional parameter, if you want to see the processes that have not been changed. Use the additional parameter -excludeDifferent, if you'd like to see only those processes that have not been modified.

Detecting Changes to Objects

If you use Compare-Object as described above, it will only check whether every object in one list is matched in another list. While comparing them to their initial state, may be sufficient to determine whether objects were removed or added, you can't use this approach to establish whether an object's inner status has changed.

For example, if you'd like to verify whether services have stopped or started in comparison to their defined initial state, Compare-Object won't initially help you because when a service is stopped it still exists: only its inner status has changed. You should instead instruct Compare-Object to compare one or more of the object's properties by using Format-List to easily determine which properties are available to you. You should. first acquire a service object and experiment around with it a little:

# Pick out Windows Update Service:
$service = Get-Service wuauserv

# Inspect all properties of this services:
$service | Format-List *
Name : wuauserv
CanPauseAndContinue : False
CanShutdown : True
CanStop : True
DisplayName : Windows Update
DependentServices : {}
MachineName : .
ServiceName : wuauserv
ServicesDependedOn : {rpcss}
ServiceHandle :
Status : Running
ServiceType : Win32ShareProcess
Site :
Container :

It quickly turns out that the Status property retrieves the desired information. So, you could first make another snapshot of all services, stop a service subsequently, and then instruct Compare-Object to use the Status property to ascertain differences:

# Save current state:
$before = Get-Service

# Pick out a service and stop this service:
# (Note: this usually requires administrator rights.
# Stop services only if you are sure that the service
# is absolutely not required.
$service = Get-Service wuauserv
$service.Stop()

# Record after state:
$after = Get-Service

# A simple comparison will not find differences because
# the service existed before and after:
Compare-Object $before $after

# A comparison of the Status property reports the halted
# service but not its name:
Compare-Object $before $after -Property Status
Status SideIndicator
------ -------------
Stopped =>
Running <=
# A comparison with the Status and Name properties returns
# the required information:
Compare-Object $before $after -Property Status, Name
Status Name SideIndicator
------ ---- -------------
Stopped wuauserv =>
Running wuauserv <=

If you instruct Compare-Object with the parameter -property to compare the Status and Name properties, you'll receive the information you want: the service wuauserv was executed in the list in $before, but not in the list in $after. So it was stopped.

This example shows how to stop services. In the next chapter, you'll learn more about the methods (commands) built into objects. What's important to note here is only that you change the state of any service. You could also accomplish that by using the Microsoft Management Console Snapin for services:

services.msc

Start or stop only those services that you know won't incur any risk when you start or stop them. If an error message pops up when you try to modify a service, this is usually because you don't have administrator rights. Just remember that for Vista, or when group policies are in effect, that you must start up PowerShell with administrator rights. Otherwise, you're only a normal user, even if you log on with an administrator account.

Since the Compare-Object results consist of objects, you could make a further analysis of the result. Perhaps all that interests you are executed modifications. Use a pipeline filter, such as Where-Object, to specify to the filter that you're interested in only those objects in which the SideIndicator property corresponds to the value "=":

Compare-Object $before $after -property Status, Name |
Where-Object { $_.SideIndicator -eq "=>" }
Status Name SideIndicator
------ ---- -------------
Stopped wuauserv =>

If you'd like to formulate the result in plain text, use a loop, such as, Where-Object, and use the information in the retrieved objects to put together the plain text:

Compare-Object $before $after -property Status, Name |
Where-Object { $_.SideIndicator -eq "=>" } |
ForEach-Object { "The service {0} has changed its status to {1}" `
-f $_.Name, $_.Status}
The service wuauserv has changed its status to Stopped

You can use this same procedure for widely varying monitoring tasks. Think in advance about which command you could use to determine an object's status to be monitored and, which of the object's properties will describe its status. For example, if you want to find out whether files in a directory have changed, the right command would be Dir and the property could be Length (because of the changed file size) or LastWriteTime (the contents could have been changed even if its size is just as large as it was before). Here's an example:

# Create test file and Before snapshot of the directory:
"Hello" > test.txt
$before = Dir

# Modify test file and create After snapshot of the directory:
"Hello world" > test.txt
$after = Dir

# Compare-Object reports all files whose size has changed:
Compare-Object $before $after -property Length, Name
Length Name SideIndicator
------ ---- -------------
26 test.txt =>
16 test.txt <=
# Files whose size is unchanged, however, were not recognized
# although they were changed:
"Hey!" > test.txt
$after = Dir
Compare-Object $before $after -property Length, Name

# So, when comparing, it is crucial to select a meaningful
# property, e.g., LastWriteTime:
Compare-Object $before $after -property Length, LastWriteTime, Name
Length LastWriteTime Name SideIndicator
------ ------------- ---- -------------
16 20.09.2007 14:13:09 test.txt =>
16 20.09.2007 14:13:02 test.txt <=

Comparing File Contents

A special form of the "snapshot" is a file's text contents. If you read text contents using Get-Content, you'll get an array with lines of text. Compare-Object can compare this array again and determine which lines within text files have changed: Here's another example:

# Create first test file:
@"
>> Hello
>> world
>> "@ > test1.txt
>>

# Create second test file:
@"
>> Hello
>> beautiful
>> world
>> "@ > test2.txt
>>

# Compare both files and show only differing lines:
Compare-Object -referenceObject $(Get-Content test1.txt) `
-differenceObject $(Get-Content test2.txt)
InputObject SideIndicator
----------- -------------
beautiful =>
Compare-Object -referenceObject $(Get-Content test1.txt) `
-differenceObject $(Get-Content test2.txt) -includeEqual
InputObject SideIndicator
----------- -------------
Hello ==
world ==
beautiful =>

Saving Snapshots for Later Use

Some before-and-after comparisons may not be able to be completed in one day. Perhaps you would like to compare operating states over a longer time period, and are not sure if the computer (and your PowerShell) is running the entire time without interruptions. Or maybe you would like to use the same precisely set initial state. In this case, you can "serialize" the objects in the initial state. In other words, the objects are stored as a file in a special data format, more or less "frozen." Later, you can load the object at any time from the file and use them for comparison.

The Export-Clixml cmdlet carries out serialization. All you need to do is to specify a file name under which the objects can be saved. For example, the following line saves a list of all running processes to the file before.xml:

Get-Process | Export-Clixml before.xml

Because the initial state is now stored as a file, you could close PowerShell and reboot your computer. As soon as you are ready to compare the current processes with the stored initial status, you can load the file back in PowerShell:

$before = Import-Clixml before.xml

However, if you try to compare the contents of $before with the current list of processes, Compare-Object will output an endless list of deviations:

$after = Get-Process
Compare-Object $before $after

In the simplest scenario, Compare-Object only verifies whether the objects are in both lists. But as soon as you serialize or "freeze" objects, your object type changes. If you use Import-Clixml later to input these objects, the information will be brought back to life in a different type while the objects will continue to contain all information. Why? Because the re-input objects no longer correspond to running processes but are the "unfrozen" older processes.

You already know the solution to the problem: simply instruct Compare-Object to compare particular properties because the revived objects continue to contain all the important information. As soon as you compare objects, Compare-Object doesn't care at all about the object type as long as the objects to be compared support the same properties:

Compare-Object $before $after -property Name
Name SideIndicator
---- -------------
notepad =>
regedit <=

You now know that a process called notepad has been added since the snapshot and a process called regedit was ended. However, you wouldn't yet know whether the processes that have the same name are in fact identical. To find out, you would have to include additional object properties in the comparison, such as the process ID, which clearly identifies processes:

Compare-Object $basis (Get-Process) -property Id, Name
Id Name SideIndicator
-- ---- -------------

7788 notepad =>
8004 PowerShellPlus.vshost =>
3032 PowerShellPlus.vshost <=
344 regedit <=

Now, you can see as well that PowerShell was started up again once. The instance of PowerShell with the process ID 8004 was ended and in its place a new instance of PowerShell with the process ID 3032 was started.

Exporting Pipeline Results

You have learned that pipeline results are converted into text when they reach the pipeline's end at the latest and are output in the console because PowerShell appends the Out-Default cmdlet to the end of every entry. As a result, this cmdlet decided where pipeline results will be output. Along with Out-Default, there are a number of additional output cmdlets that you can put at your pipeline's end so the result is redirected to a file or printed out rather than output in the console. The pipeline stops its work on reaching the first output cmdlet; if you enter one, Out-Host, which PowerShell appends automatically, won't go into operation:

Get-Command -verb out
CommandType Name Definition
----------- ---- ----------
Cmdlet Out-Default Out-Default [-InputObject <PSObject>]...
Cmdlet Out-File Out-File [-FilePath] <String> [[-Enco...
Cmdlet Out-Host Out-Host [-Paging] [-InputObject <PSO...
Cmdlet Out-Null Out-Null [-InputObject <PSObject>] [-...
Cmdlet Out-Printer Out-Printer [[-Name] <String>] [-Inpu...
Cmdlet Out-String Out-String [-Stream] [-Width <Int32>]...
Dir | Out-File output.txt
.\output.txt
Dir | Out-Printer

Out-File supports the parameter -encoding, which you can use to determine the format in which information is written to a file. If you don't remember which encoding formats are allowed, just specify a value which you know is absolutely false, and then the error message will tell you which values are allowed:

Dir | Out-File -encoding Dunno
Out-File : Cannot validate argument "Dunno" because it does not
belong to the set "unicode, utf7, utf8, utf32, ascii,
bigendianunicode, default, oem".
At line:1 char:25
+ Dir | Out-File -encoding <<<< Dunno

An alternative to Out-File is Export-Csv. You can specify comma-separated lists with this cmdlet. You'll read more about that a little later on.

Suppressing Results

Send the output to Out-Null if you want to suppress command output:

# This command not only creates a new directory but also returns
# the new directory:
md testdirectory
Directory: Microsoft.PowerShell.Core\FileSystem::C:\Users\
Tobias Weltner
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 19.09.2007 14:31 testdirectory
rm testdirectory

# Here the command output is sent to "nothing"
md testdirectory | Out-Null
rm testdirectory

# That matches the following redirection:
md testdirectory > $null
rm testdirectory

Changing Pipeline Formatting

At first glance, Out-Host seems somewhat superfluous since all results will end up in the console when you don't specify any output cmdlet. So what's the use of Out-Host? On the one hand, this cmdlet supports optional parameters like -paging, which can be used to output information page by page. You already tried that at the beginning of this chapter. In addition, you can use Out-Host to control pipeline formatting, which in itself is much more important.

The reason is that all output cmdlets not only output all pipeline results to the relevant output device, but also automatically convert pipeline objects into readable text. You've already learned how this conversion works by formatting cmdlets like Format-Table. This gets interesting when you specify neither a formatting nor an output cmdlet in your pipeline. Then, PowerShell takes action automatically, though sometimes the result can be confusing.

For example, can you explain why the next instruction outputs all services in table form, but the following in list form?

# Outputs services in table form:
Get-Service

# Outputs services in list form:
Get-Location; Get-Service

In the second line, the results of two commands are mixed. That's permitted, and you just have to remember to separate individual commands by a semicolon. None of the two commands outputs its results by using an output cmdlet. That's why all results remain in the pipeline and are automatically processed at the end by Out-Host. That's exactly what causes the problem since PowerShell extends the line in the following way behind the scenes:

& {Get-Location; Get-Service} | Out-Default

Out-Default determines whether you gave one of the formatting cmdlets a particular format. If not, it tries to find an appropriate format. In doing so, it takes a cue from the first object in the result, the path name of Get-Location. However, an unexpectedly colorful series of Get-Service services follows so no predefined format exists with which this muddled medley can be displayed, Out-Default falls back on the list format. You can encounter the problem described here in many places. It also affects, among others, functions or scripts:

# Example of problem when using a function:
function test {
Get-Location
Get-Service
}
test

# Example of problem when using a script:
@"
Get-Location
Get-Service
"@ > test.ps1
.\test.ps1

The solution to this problem: either specify a format for the pipeline yourself or send the results of individual commands to the console:

# Specify the output format yourself so that PowerShell won't need
# to specify the format:
Get-Location | Format-Table; Get-Service

# Or send the intermediate results to the console so that no mixed
# results appear:
Get-Location | Out-Host; Get-Service

Forcing Text Display

PowerShell delays conversion until the last possible moment and converts pipeline objects into text only until they reach the end of the pipeline since information is typically lost when objects are converted into text. However, by using Out-String, you can force PowerShell to convert objects into text any time you like. Out-String is the only output cmdlet that continues the pipeline instead of terminating it. Out-String puts the objects it receives back into the pipeline as text. You can assign the result to a variable Because it behaves like a normal pipeline command.:

$text = Dir | Out-String
$text.toUpper()

The result of Out-String is always a single, complete text. That also means that Out-String blocks the pipeline stream and waits until all results arrive. If you'd prefer getting the text line by line in an array, use the -stream parameter; then Out-String will transform incoming objects into single blocks of text in real time and won't block the pipeline:

Dir | Out-String -stream | ForEach-Object { $_.toUpper() }

If possible, you should avoid turning objects into text because that makes them lose the structure and many options that only original objects offer.

Excel: Exporting Objects

All output cmdlets convert pipeline results into text that may be displayed haphazardly. An alternative are comma-separated lists generated by Export-Csv. Comma-Separated Value (CSV) files that can then be opened in programs like Microsoft Excel allows you to continue working smoothly with the data retrieved by PowerShell. You can then turn columns of numbers into expressive graphics.

Dir | Export-Csv test.csv
.\test.csv

The objects returned by Dir are converted into text along with all their properties. Open the resulting CSV file and, if you have installed Microsoft Excel, the information will be displayed column-by-column as an Excel spreadsheet. You could also display the information in a text editor if you don't have Excel.

While Excel can open a CSV file, but cannot identify the columns correctly, the fault may lie with your country settings. Export-CSV uses as default separator the list separator "," that is internationally customary. For example, if you're using a German system, the Windows control panel country settings would use the not very customary tab character as list separator. So that Excel can import comma-separated lists correctly, you must change either the list separator character in your regional settings or change the separator character from a comma to a tab in the resulting CSV file:

# Make a comma-separated list
Dir | Export-Csv test1.csv

# Replace a comma by a tab respectively in this list
Get-Content test1.csv | ForEach-Object { $_.replace(',', "`t") } |
Out-File test2.csv

# A German system will now assign columns correctly in Excel:
.\test2.csv

However, this is a case of a very simple replacement so it doesn't take into consideration the commas that could be found in column text.

Export-Csv consequently takes care of the formatting data job by writing all object properties as arrays in comma-separated files. What happens when you mess things up by using a formatting cmdlet is shown by the next example:

Dir | Format-Table | Export-Csv test.csv
.\test.csv

The information in the CSV file is now nearly unreadable, and it becomes clear how formatting cmdlets do their work behind the scenes by embedding objects in their own formatting instructions. That's why you may never use formatting cmdlets if you want to use Export-Csv to store raw information in a file. In general, you should also use formatting cmdlets only at the end of your pipeline so that formatting instructions will not disrupt other commands.

A question remains: if you use formatting cmdlets to specify which of an object's properties you're interested in, how then can you determine which properties are written into the CSV file? The answer is to strip away the unwanted properties from the objects by using Select-Object. You can then state the property that you want to keep. All the others will be removed from the object. That's the solution, for Export-Csv always writes all (remaining) properties into the CSV file:

Dir | Select-Object Name, Length, LastWriteTime | Export-Csv test.csv
.\test.csv

HTML Outputs

If you'd like, PowerShell can also pack its results into (rudimentary) HTML files. Converting objects into HTML formats is done by ConvertTo-Html:

Get-Process | ConvertTo-Html | Out-File output.htm
.\output.htm

But don't be alarmed if the procedure takes a while because PowerShell has to read out all of the objects' properties and save them as a HTML table. If you want to see only particular properties as a HTML report, as in the case of Export-Csv, you should never use formatting cmdlets. It would be better for you to use Select-Object here. You could also take this opportunity to give the HTML page a title by using the -title parameter. The title will turn up later on the title bar of the browser that is displaying your file. Unfortunately, the cmdlet doesn't have formatting options that go beyond this:

Get-Process | Select-Object Name, Description |
ConvertTo-Html -title "Process Report" |
Out-File output.htm
.\output.htm

The Extended Type System (Part One)

One of the PowerShell console's most remarkable capabilities is converting any object into text. You have seen how different formatting cmdlets can turn object properties into text and output them as text either beside or below each other.

What is striking in this connection is above all that PowerShell succeeds in only converting an object's essential properties into text. PowerShell would have to fail right from the beginning if it had to convert absolutely all of an object's properties into text, for then even a simple directory listing would generate a confusing amount of information:

Dir | Format-Table * -wrap
PSPat PSPar PSChi PSDri PSPro PSIsC Mode Name Paren Exist Root Full
h entPa ldNam ve vider ontai t s Name
th e ner


----- ----- ----- ----- ----- ----- ---- ---- ----- ----- ---- ----
Micro Micro Appli C Micro True d---- Appli Tobia True C:\ C:\U
soft. soft. catio soft. catio s Wel sers
Power Power n Dat Power n Dat tner \Tob
Shell Shell a Shell a ias
.Core .Core .Core Welt
\File \File \File ner\
Syste Syste Syste Appl
m::C: m::C: m icat
\User \User ion
s\Tob s\Tob Data
ias W ias W
eltne eltne
r\App r
licit
ion D
ata
Micro Micro Backu C Micro True d---- Backu Tobia True C:\ C:\U
soft. soft. p soft. p s Wel sers
Power Power Power tner \Tob
Shell Shell Shell ias
.Core .Core .Core Welt
\File \File \File ner\
Syste Syste Syste Back
m::C: m::C: m up
\User \User
s\Tob s\Tob
ias W ias W
eltne eltne
r\Bac r
kup
(...)

You don't have to make do with this raw, completely unserviceable text conversion of object properties. You can convert text in a way that makes sense in a practical way by using the Extended Type System (ETS),. Only the ETS can enable PowerShell to process internal objects, waiting until they reach the end of the pipeline before transforming them into understandable text.

Dir
Directory: Microsoft.PowerShell.Core\FileSystem::C:\Users\
Tobias Weltner

Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 01.10.2007 16:09 Application Data
d---- 26.07.2007 11:03 Backup
(...)

The ETS consists of two parts. One part takes care of formatting objects and will be described next. The other part attends to object properties and will be explained in the next chapter.

Rendering Text as Text and Only Text

The ETS goes into action only when objects are output in the console. The ETS does nothing if the data is already available as text. So, if you wanted to use Out-String to convert a directory listing into text right from the beginning and then pass it through one of the formatting cmdlets, it would not be rendered any differently:

# Convert directory listing objects into plain text:
$text = Dir | Out-String

# All additional outputs will return the identical result,
# for text will not be converted:

$text
$text | Format-Table
$text | Format-List

Your Wish Has Priority

The ETS will still remain inactive if you specify after a formatting cmdlet like Format-Table which properties should be converted into text., The conversion of objects into text is not the problem, but the selection and differentiation of important and unimportant properties is the issue. If you specify which properties should be converted, you won't let the ETS make this decision:

# If you specify the properties, ETS will no longer select them:
Dir | Format-Table Name, Length, LastWriteTime

Known Objects and Formatting

If you use a formatting cmdlet like Format-Table without selecting properties after it, the ETS will go into action for the first time, because the way in which these objects are to be displayed and which properties are to be shown now must be selected automatically. To do this, the ETS first determines what kinds of objects are to be converted into text:

Dir | ForEach-Object { $_.GetType().FullName }

Dir returns files in a System.IO.FileInfo object and files in a System.IO.DirectoryInfo object. Then, the ETS looks in its own internal records to see how these objects must be converted into text. The records are stored in the form of XML files that have the file extension ".ps1xml":

Dir $pshome\*.format.ps1xml
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 13.04.2007 19:40 22120 Certificate.format.ps1xml
-a--- 13.04.2007 19:40 60703 DotNetTypes.format.ps1xml
-a--- 13.04.2007 19:40 19730 FileSystem.format.ps1xml
-a--- 13.04.2007 19:40 250197 Help.format.ps1xml
-a--- 13.04.2007 19:40 65283 PowerShellCore.format.ps1xml
-a--- 13.04.2007 19:40 13394 PowerShellTrace.format.ps1xml
-a--- 13.04.2007 19:40 13540 Registry.format.ps1xml

Every object is precisely defined in these XML files. Among others, the definition includes which object properties are supposed to be converted into text and whether the object should be displayed in the form of a list or table.

The ETS runs into trouble only when you mix several object types that don't really fit together, as is the case here:

Get-Process; Dir | Format-Table
(...)
out-lineoutput : Object of type "Microsoft.PowerShell.Commands.
Internal.Format.FormatStartData" is not legal or not in the
correct sequence. This is likely caused by a user-specified
"format-table" command which is conflicting with the default
formatting.

The files and directories that Dir outputs cannot be displayed by the formatting that PowerShell uses for Processes. So, they won't allow themselves to be mixed. One solution would be to send the objects individually to the fitting formatter:

Get-Process | Format-Table; Dir | Format-Table

Another solution would be not to use any formatting cmdlets at all, because then the ETS would nose around automatically until it found the fitting format—as you will see soon.

Unknown Objects

If the object that the ETS is supposed to convert into text is unknown because it isn't defined in one of the ps1xml records, the ETS will flatly convert all properties of the object into text. Then, the question becomes whether the object is to be displayed as a table or a list. If there are fewer than five, the ETS uses a table view, otherwise a list view. You can verify that easily enough yourself by fabricating your own "homemade" objects:

# Create a new empty object:
$object = New-Object PSObject

# Attach a new property:
Add-Member NoteProperty "a" 1 -inputObject $object

# Powershell outputs the object with Format-Table and show the
# single property:
$object
a
-
1
# Add three additional properties:
Add-Member NoteProperty "b" 1 -inputObject $object
Add-Member NoteProperty "c" 1 -inputObject $object
Add-Member NoteProperty "d" 1 -inputObject $object

# The object is still shown as a table:
$object
a b c d
- - - -
1 1 1 1
# The fifth property makes a difference:
Add-Member NoteProperty "e" 1 -inputObject $object

# Now the object is converted with Format-List (properties below
# and not beside each other):
$object
a : 1
b : 1
c : 1
d : 1
e : 1

Emergency Mode

If during output the ETS discovers a critical condition, it will automatically switch over to list view. Such a critical condition can arise, for example, when the ETS encounters unexpected objects. The following instruction will initially output the list of running processes in table view, but because file system objects turn up suddenly and unexpectedly, during the output the ETS switches over to emergency mode and lines up the remaining objects in list view.

Get-Process; Dir

"The Case of the Vanished Column"

When encountering unknown objects, the ETS always takes its cue from the first object that it outputs. That can cause a strange phenomenon. The ETS always shows all object properties for an unknown object, but only all object properties of the first object that the ETS outputs. If further objects follow with more properties, the present selection of properties remains and information is suppressed.

The following example shows how information can be withheld: Get-Process returns a list of running processes. They are sorted by the property StartTime and subsequently the only properties that are output are Name and StartTime:

Get-Process | Sort-Object StartTime | Select-Object Name,StartTime

When you execute these lines, you may possibly get a lot of error messages, but that's not your fault. Without administrator privileges, you aren't allowed to access many processes: you can't even ask what the start-up time was. As a result, you'll get a list of processes of which only a few are listed with their start times. Only the process names are output. The start times of all processes is simply suppressed. Why?

Whenever you use Select-Object to take a property away from an object, you change the object type. Get-Process retrieves Process objects, and you cannot simply cancel the properties of these objects. That's why Select-Object wraps the information of the incoming Process objects in new objects, which it creates new:

Get-Process | Sort-Object StartTime |
Select-Object Name,StartTime |
ForEach-Object { $_.GetType().FullName }
System.Management.Automation.PSCustomObject
(...)

The new objects are of the PSCustomObject type. There is no entry in the ETS record for this object type, and so the ETS outputs all the properties of the first object. Because you had used Sort-Object to sort the output by ascending start times, the list begins with the objects that have no start time because of access restrictions.

As a result, the ETS recognizes only one property, Name, in the first object. It doesn't find the start time in the first object and so start times are not output for the following objects. You can solve this problem by not relying on the ETS, but instead selecting the object you want:

Get-Process | Sort-Object StartTime |
Select-Object Name,StartTime |
Format-Table Name, StartTime

ETS Enhancement

If the ETS is familiar with a certain object type, it can convert it into text optimally. For unknown objects, conversion is far less elegant, possibly even useless. Fortunately, the ETS can be enhanced: all you need to do is to teach ETS how to handle new object types so that they, too, can be displayed as text optimally.

Planning Enhancement

The first step of ETS enhancement is to determine which object type you want to display better. You may frequently use Get-WmiObject to get information from the WMI service, but you're not happy with the way PowerShell displays these objects:

Get-WmiObject Win32_Processor
__GENUS : 2
__CLASS : Win32_Processor
__SUPERCLASS : CIM_Processor
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_Processor.DeviceID="CPU0"
__PROPERTY_COUNT : 48
__DERIVATION : {CIM_Processor, CIM_LogicalDevice,
CIM_LogicalElement, CIM_Managed
SystemElement}
__SERVER : TOBIASWELTNE-PC
__NAMESPACE : root\cimv2
__PATH : \\TOBIASWELTNE-PC\root\cimv2:Win32_
Processor.DeviceID="CPU0"
AddressWidth : 32
Architecture : 9
Availability : 3
Caption : x64 Family 6 Model 15 Stepping 6
ConfigManagerErrorCode :
ConfigManagerUserConfig :
CpuStatus : 1
CreationClassName : Win32_Processor
CurrentClockSpeed : 1000
CurrentVoltage : 12
DataWidth : 64
Description : x64 Family 6 Model 15 Stepping 6
DeviceID : CPU0
ErrorCleared :
ErrorDescription :
ExtClock :
Family : 1
InstallDate :
L2CacheSize : 4096
L2CacheSpeed :
L3CacheSize : 0
L3CacheSpeed : 0
LastErrorCode :
Level : 6
LoadPercentage :
Manufacturer : GenuineIntel
MaxClockSpeed : 2167
Name : Intel(R) Core(TM)2 CPU T7400 @ 2.16GHz
NumberOfCores : 2
NumberOfLogicalProcessors : 2
OtherFamilyDescription :
PNPDeviceID :
PowerManagementCapabilities :
PowerManagementSupported : False
ProcessorId : BFEBFBFF000006F6
ProcessorType : 3
Revision : 3846
Role : CPU
SocketDesignation : U1
Status : OK
StatusInfo : 3
Stepping : 6
SystemCreationClassName : Win32_ComputerSystem
SystemName : TOBIASWELTNE-PC
UniqueId :
UpgradeMethod : 8
Version : Modell 15, Stepping 6
VoltageCaps :

First, find out what type of object is returned by the command:

$object = Get-WmiObject Win32_Processor | Select-Object -first 1
$object.GetType().FullName
System.Management.ManagementObject

This shows you that you need an ETS enhancement for objects of the type System.Management.ManagementObject. Next, take a look at this object's properties and select one that you want the ETS to convert into text. For example, DeviceID, Name, and ProcessorID. Then, formulate the definition of the object in XML. In the TableHeaders area, set column headers, and in the TableRowEntries area, set object properties.

<Configuration>
<ViewDefinitions>
<View>
<Name>CustomView</Name>
<ViewSelectedBy>
<TypeName>System.Management.ManagementObject</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>12</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Description</Label>
<Width>30</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>ID</Label>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>DeviceID</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Description</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>ProcessorID</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>

Store this XML code in a file called Win32_Processor.format.ps1xml.Thhen, use Update-FormatData to read it into the ETS:

Update-FormatData Win32_Processor.format.ps1xml

Now, the result will be much easier to understand when you output Win32_Processorobjects again:

Get-WmiObject Win32_Processor
Name Description ID
---- ----------- --
CPU0 x64 Family 6 Model 15 Stepp... BFEBFBFF000006F6

However, in this particular instance a mishap occurred. When you acquire other WMI objects, these will now also be displayed in the format that you just defined:

Get-WmiObject Win32_Share
Name Description ID
---- ----------- --
Remote Admin
Default share
Default share
Remote IPC
Default share

The reason has to do with special features of the WMI. It returns all WMI objects in a System.Management.ManagementObject type.

$object = Get-WmiObject Win32_Service | Select-Object -first 1
$object.GetType().FullName
System.Management.ManagementObject

So, the ETS didn't make a mistake. Instead, the culprit is the WMI as for WMI objects (and only for these), ETS enhancements must be more specific since the type name alone is not enough. That's why WMI objects are assigned to additional object types that you can find in the PSTypeNames property:

$object = Get-WmiObject Win32_Processor | Select-Object -first 1
$object.PSTypeNames
System.Management.ManagementObject#root\cimv2\Win32_Processor
System.Management.ManagementObject
System.Management.ManagementBaseObject
System.ComponentModel.Component
System.MarshalByRefObject
System.Object

The object name that is specific to Win32_Processor objects is called System.Management.ManagementObject#root\cimv2\Win32_Processor. So, you would have to specify this object name in your ETS enhancement so that the enhancement applies only to Win32_Processor WMI objects:

<Configuration>
<ViewDefinitions>
<View>
<Name>CustomView</Name>
<ViewSelectedBy>
<TypeName>System.Management.ManagementObject#root
\cimv2\Win32_Processor</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
(...)

Modify your enhancement accordingly, and read it again with Update-FormatData. You can safely ignore the resulting error message. After updating, your enhancement will be valid only for Win32_Process WMI objects.

Summary

PowerShell uses a pipeline for all command entries, which feeds the results of the preceding command directly into the subsequent command. The pipeline is active even when you enter only a single command because PowerShell always automatically adds the Out-Default cmdlet at the pipeline's end so that it always results in a two-member instruction chain.

Single command results are passed as objects. The cmdlets shown in Table 5.1 can filter, sort, compare, measure, expand, and restrict pipeline elements. All cmdlets accomplish this on the basis of object properties. In the process, the pipeline distinguishes between sequential and streaming modes. In streaming mode, command results are each collected, and then passed in mass onto the next command. Which mode you use depends solely on the pipeline commands used. Output cmdlets dispose of output. If you specify none, PowerShell automatically uses Out-Host to output the results in the console. However, you could just as well send results to a file or printer.

All output cmdlets convert objects into readable text while formatting cmdlets are responsible for conversion. Normally, formatting cmdlets convert only the most important, but if requested, all objects into text. The Extended Type System (ETS) helps convert objects into text. The ETS uses internal records that specify the best way of converting a particular object type into text. If an object type isn't in an ETS internal record, the ETS resorts to a heuristic method, which is guided by, among other things, how many properties are contained in the unknown object.

In addition to traditional output cmdlets, export cmdlets store objects either as comma-separated lists that can be opened in Excel or serialized in an XML format. Serialized objects can be comfortably converted back into objects at a later time. Because when exporting, in contrast to outputting, only plain object properties, without cosmetic formatting, are stored so that no formatting cmdlets are used.


Posted Nov 23 2008, 12:00 PM by ps1

Comments

Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 11-24-2008 2:25 AM

Pingback from  Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 11-26-2008 8:23 AM

Pingback from  Chapter 5. The PowerShell Pipeline - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 7. Conditions
on 03-08-2009 6:24 PM

You'll need a condition first to compose intelligent PowerShell code capable of making decisions. That's why you'll learn in the first part of this Chapter how to formulate questions as conditions. In the second part, you'll employ conditions

on 03-08-2009 6:24 PM

Loops are a good example that iterations do not have to be boring. They repeat particular PowerShell statements with the pipeline being one of the areas where you can benefit from loops. Most PowerShell commands wrap their results in arrays, and you'll

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 9. Functions
on 04-01-2009 2:31 PM

PowerShell has the purpose of solving problems, and the smallest tool it comes equipped with for this is commands. By now you should be able to appreciate the great diversity of the PowerShell command repertoire: in the first two chapters, you already

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 12. Command Discovery and Scriptblocks
on 04-01-2009 2:33 PM

In previous chapters you learned step by step how to use various PowerShell command types and mechanisms. After 11 chapters, we have reached the end of the list. You'll now put together everything you've seen. All of it can actually be reduced

on 04-01-2009 2:34 PM

Raw information used to be stored in comma-separated lists or .ini files, but for some years the XML standard has prevailed. XML is an acronym for Extensible Markup Language and is a descriptive language for any structured information. In the past, handling

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 15. The File System
on 04-01-2009 2:34 PM

The file system has special importance within the PowerShell console. One obvious reason is that administrators perform many tasks that involve the file system. Another is that the file system is the prototype of a hierarchically structured information

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 17. Processes, Services, Event Logs
on 04-10-2009 5:38 PM

In your daily work as an administrator, you often have to deal with programs (processes), services, and innumerable entries in event logs so this is a good opportunity to put into practice the basic knowledge you gained from the first 12 chapters. The

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 18. WMI: Windows Management Instrumentation
on 04-10-2009 5:40 PM

It might have escaped your attention, but the Windows Management Instrumentation (WMI) service introduced with Windows 2000 has been part of every Windows version since then. The WMI service is important because it can retrieve information about nearly

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 19. User Management
on 04-10-2009 5:41 PM

For many administrators, managing users is an important part of their work. PowerShell v1 does not contain any cmdlets to manage users. However, you can add them from third-party vendors. But if you do not want any dependencies on third-party tools and

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 1. The PowerShell Console
on 04-30-2009 12:50 AM

Welcome to PowerShell! This chapter will teach you about all aspects of the PowerShell console from A to Z. You'll also learn how to configure the console to suit your personal preferences including font colors and sizes, editing and display options

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 2. Interactive PowerShell
on 04-30-2009 12:51 AM

PowerShell has two faces: interactivity and script automation. In this chapter, you will first learn how to work with PowerShell interactively. Then, we will take a look at PowerShell scripts.

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 3. Variables
on 04-30-2009 12:52 AM

It is time to combine commands whenever a single PowerShell command can't solve your problem. One way of doing this is by using variables. PowerShell can store results of one command in a variable and then pass the variable to another command. In

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 4. Arrays and Hashtables
on 04-30-2009 12:53 AM

No matter how many results a command returns, you can always store the results in a variable because of a clever trick. PowerShell automatically wraps results into an array when there is more than one result. In this chapter, you'll learn how arrays

KodefuGuru wrote Free PowerShell EBook
on 07-24-2009 3:24 PM

Free PowerShell EBook

udubnate wrote re: Chapter 5. The PowerShell Pipeline
on 03-19-2010 1:25 PM

Great book-

Found a issue with this command

Compare-Object $basis (Get-Process) -property Id, Name

$basis must have been a variable you forgot to document. Let me know if you have questions. Thanks!

Nate

Richard Giles wrote re: Chapter 5. The PowerShell Pipeline
on 03-19-2010 3:30 PM

$basis = $before

A typo must crept into the translation from German to English

Copyright 2010 PowerShell.com. All rights reserved.