Sunday, July 6, 2008

Piping and PSDrives

Two of the oft-touted features of PowerShell are the idea that you can pipe objects from one cmdlet to the next and PSDrives.

In bash or cmd, when you pipe the output of one command to another, it sends the text that is printed to STDOUT to the STDIN of the next process. This means that there are a lot of commands just to help you massage and manipulate the text on the screen so that you can get the right output.

In PowerShell, the objects themselves are passed from one cmdlet to the next. Here's an example:

If I type

cd HKLM:

I am now at the root of the HKEY_LOCAL_MACHINE, since HKLM: is a PSDrive set up so that I can navigate the registry just like a drive on my computer. You can see the whole list of PSDrives by typing in "psdrive" at the command prompt.

If I type

dir

I get this:

PS HKLM:\> dir


Hive: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE

SKC VC Name Property
--- -- ---- --------
7 3 COMPONENTS {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime}
4 0 HARDWARE {}
1 0 SAM {}
042 0 Schema {}
15 0 SOFTWARE {}
9 0 SYSTEM {}

(I'm on Vista running in non-administrator mode, so that's why I don't have access to the schema)

This appears to be sorted by the Name attribute, but let's say I don't want to sort on the Name attribute, I'm more interested in the SKC attribute (whatever that is):

PS HKLM:\> dir | sort SKC


Hive: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE

SKC VC Name Property
--- -- ---- --------
042 0 Schema {}
15 0 SOFTWARE {}
9 0 SYSTEM {}
7 3 COMPONENTS {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime}
4 0 HARDWARE {}
1 0 SAM {}


I have the data that I want, but I want to display it differently:


PS HKLM:\> dir | sort SKC | format-list
Get-ChildItem : Requested registry access is not allowed.
At line:1 char:4
+ dir <<<< | sort SKC | format-list


Name : Schema
ValueCount : 0
Property : {}
SubKeyCount : 1042

Name : SOFTWARE
ValueCount : 0
Property : {}
SubKeyCount : 15

Name : SYSTEM
ValueCount : 0
Property : {}
SubKeyCount : 9

Name : COMPONENTS
ValueCount : 3
Property : {StoreFormatVersion, StoreArchitecture, PublisherPolicyChangeTime}
SubKeyCount : 7

Name : HARDWARE
ValueCount : 0
Property : {}
SubKeyCount : 4

Name : SAM
ValueCount : 0
Property : {}
SubKeyCount : 1

If you want to pipe a non-cmdlet, then it will send the text output to the pipe just like you're used to, but you can manipulate it easily in PowerShell. $_ is a special "default" variable for the input, similar to $_ in Perl. This lets you do things like this:

cat xunlei.txt | where {$_ -match '\sBEJ-73716507\s'}
cat xunlei.txt | where {$_ -match '\sBEJ-73716507\s'} | foreach {echo("Found it!!! " + $_)}

The first command pipes the output of cat (cat and echo are aliases for Write-Output) to the where statement that filters out everything that doesn't match the regular expression, kind of like grep. The second command does the same, but then prepends the string "Found it!!! " to the beginning of each line before printing it to the screen.


NOTE: When using the + operator to concatenate strings it is usually necessary to put parentheses around the whole string. Otherwise the + operator uses some funky overloaded function and it won't do what you expect.

No comments: