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--

No comments:

Post a Comment