Tuesday, May 29, 2012

I, Object

As discussed in the post on the pipeline, part of the power behind PowerShell is that the output from most cmdlets is an object or an array of objects.  And cmdlets are designed to process objects received from the pipeline.  Since PowerShell is built on the .Net framework everything is an object.

An object has properties that contain information about the object and is operated on using methods.  For example, I am an object and have properties such as my name and phone number.  To contact me you might use the method of calling my phone number and addressing me by name. 

You can see the properties and methods of an object by piping it through the Get-Member cmdlet.  Even a scalar such as a simple string is still an object.
My string variable has an attribute named length, the number of characters in the string, and a bunch of methods for slicing and dicing the string.

An array is an object that is a collection of objects.  If you pipe an array through Get-Member you get the properties and methods of the objects contained in the array, not the array itself.  Arrays are very accommodating that way since the attributes of the members are much more interesting.

Some objects have many of properties.  Usually you want to keep just the few of those properties so you will use the Select-Object cmdlet to create the subset.

But what if I need to go in the opposite direction?  What if I need to add some properties to an object?  The Add-Member cmdlet is the answer to your question.
This is my string from the previous example.  I use Add-Member to append a property to the string that contains the last character in the string. 

Sometimes it is handy to create an array of custom objects.  I use this technique when my goal is to use Export-CSV to create a spreadsheet of just the information requested by my PHB

$CSV = 'C:\Users\Administrator\Documents\DisabledUsers.csv'
$All = Get-QADUser -Disabled -SearchRoot 'OU=Locations,dc=toddp,dc=local' `
          -SearchScope Subtree


$Disabled = @()
foreach ($U in $All) {
    $U.Name
    $New = New-Object Object
    $New | Add-Member NoteProperty Name $U.name
    $New | Add-Member NoteProperty First $U.FirstName
    $New | Add-Member NoteProperty Last $U.LastName
    $New | Add-Member NoteProperty Full $U.DisplayName
    $DN = $U.DN.Split(',')
    $Site = $DN[$DN.Length-4].Substring(3)
    $New | Add-Member NoteProperty Site $Site
    $Disabled += $New
}
$Disabled | Export-Csv $CSV -NoTypeInformation -Encoding ASCII

In this example I'm using the Quest Active Directory cmdlet Get-QADUser to show some information on disabled user accounts.  First I create the empty array by assigning it the value @().  (In PowerShell the @ is called the splat operator.  Apparently the designers thought calling it "splunge" was a bit silly.)  I then use the New-PSObject cmdlet to create a custom object in my loop.  I use Add-Member to add properties for my report to the object, and then append the custom object to the array.  My test domain has an OU named Locations which contains several site OUs and within those site OUs there are OUs for users and computers in the site.  So I parse the DN (distinguished name) property of the user account to determine the site name.

That works but it looks kind of ugly and requires too much typing.  In PowerShell 2 the New-Object cmdlet has been revamped to allow a technique called "splatting".  So instead of a bunch of Add-Member statements you assign all of the properties in a script bock that is preceded by the splat operator.

$CSV = 'C:\Users\Administrator\Documents\DisabledUsers2.csv'
$All = Get-QADUser -Disabled -SearchRoot 'OU=Locations,dc=toddp,dc=local' `
         -SearchScope Subtree


$Disabled = @()
foreach ($U in $All) {
   $U.Name
   $DN = $U.DN.Split(',')
   $Site = $DN[$DN.Length-4].Substring(3)
   $New = New-Object PSObject -Property @{
       Name  = $U.name;
       First = $U.FirstName;
       Last  = $U.LastName;
       Full  = $U.DisplayName;
       Site  = $Site
   }
   $Disabled += $New
}

$Disabled | Export-Csv $CSV -NoTypeInformation -Encoding ASCII

That saves me some typing and it does improve legibility.  But splatting doesn't respect the order of my properties:

So if I need the properties in a specific order I use the Select-Object cmdlet:

$Enabled = $Enabled | Select-Object name,First,Last,Full,Site
$Enabled | Export-Csv $CSV -NoTypeInformation -Encoding ASCII

Which gives us the output like the first example:

This technique gets even easier in PowerShell 3 which includes the object type [PSCustomObject].  Shay Levy goes into detail on how this will work once PowerShell 3 is released.  For my example the script in PowerShell 3 would look like:

$CSV = 'C:\Users\Administrator\Documents\DisabledUsers2.csv'
$All = Get-QADUser -Disabled -SearchRoot 'OU=Locations,dc=toddp,dc=local' `
         -SearchScope Subtree


$Disabled = @()
foreach ($U in $All) {
   $U.Name
   $DN = $U.DN.Split(',')
   $Site = $DN[$DN.Length-4].Substring(3)
   $Disabled += [PSCustomObject]@{
       Name  = $U.name
       First = $U.FirstName
       Last  = $U.LastName
       Full  = $U.DisplayName
       Site  = $Site
   }
}

$Disabled | Export-Csv $CSV -NoTypeInformation -Encoding ASCII

Not only does this save me even more typing, but the [PSCustomObject] will preserve the order of the properties and perform more efficiently than the New-Object cmdlet.

There is no need to object to PowerShell objects.  You can augment and condense the objects you get from cmdlets and even create your own objects.  Just another example of the awesome power of PowerShell.

Commentors: How else do you use custom objects to improve your efficiency? 

--updated 26 Jun 2012 to correct formatting--

Sunday, May 13, 2012

Hip-hip, Array

PowerShell variables all start with a dollar sign ($) so they are easily identifiable and possibly to indicate they have value.  You can assign a static value:
Or you can assign the value from the pipeline:
 

Many times the results of a pipeline will be an array.  Array?
No, not a "Ray".

An array is a sequential collection of objects (sometimes referred to as elements).  Each object in the collection can be referenced by an index number of its place in the sequence.  Enclose the index number in square brackets ([]) to retrieve that particular element.
Note that counting elements starts at 0.  And you can use the Length attribute to determine how many elements are in the array.

You can assign static values by assigning a comma separated list after the equal sign.  You can also provide a range of numeric values surrounding an ellipsis (..):

You can add an element to the end of an array using the plus sign (+):
Combing the plus and equals sign implies that we are adding the value to the variable on the left of the equation:
Something useful to note is that in Powershell the array doesn't have to include elements of the same type:

Usually we will use an array that is returned from the pipeline:
Get-Process returns all of the running processes.  I assign that output to the array $Procs.  I'm showing just the first 6 to save screen real estate and showcase another use of the ellipsis.

What if I want to do something with each element of the array?  I could create a for loop using the c-styled structure.  for ($i=0;$i -lt $procs.length;$i++) {#commands}.  But that is an old-school way of looping through an array.

Instead use the ForEach-Object cmdlet which loops through the array retrieving one element at a time.  You can pipe the variable into the foreach and use the automatic variable $_ to process the array elements:

The automatic variable $_ is handy if you are processing something along the pipeline.  But if you need to run multiple commands against the elements or do more complex processing it is often easier to assign a variable to each element:
In this example I get the running processes.  I use the ForEach-Object cmdlet to loop through the array and find processes that consume a lot of memory and CPU.  I then exact my revenge on these processes through petty name calling.

If you will be working with PowerShell you need to get used to working with arrays.  Check out some of the resource links on the sidebar for more useful tips on working with them.

Commentors: Did I miss any good Ray's in the links?