Wednesday, November 4, 2009

Professional Looking Scripts With Custom Objects

One of the things I've ran into frequently in my experiences with scripting is making them look professional, and have them taken seriously like most applications are.

Before I get all the developers out there mad at me I'm not saying that scripting is on the same level of say C++ or other languages. I am saying fervently, that scripts can do the work of applications in certain niches.

One of my pride and joy scripts, which one of these days I'll port to Powershell, is a VBScript which does the following:

1. Searches through Active Directory, and retrieves all Servers.
2. Reports back the name and creation date of the computer account. This list is sorted by Domain Controllers, Exchange Servers, then Member servers.
3. After that it brings back total disk size, free space, and reports on Operating System version, and Service Pack level.
4. Finally it will again loop through all servers, querying them for Error events in the System and Application logs in the last 7 days.

Oh, and did I mention all this output is put into a HTML document, complete with tables, coloring, and links to eventid.net for the Event Logs entries. I would say that approaches if not meets the capabilities of some programs. But I digress.

One of the best features of Powershell is the ability to create your own custom objects. Custom objects are a large topic, with many different uses, so I'm only going to focus on a part of them. How they can make your scripts look more professional. Take this output from Get-User, getting my user account with a few properties:

Get-User -Identity DHenshaw | Select-Object SamAccountName,Company,WhenCreated



SamAccountName      Company      WhenCreated
--------------      -------      -----------
DHenshaw            Company A    12/5/2006 14:31:27


That looks quite nice, you see the default format of Format-Table shows you the Property name, followed by the value below. Looks very professional and nicely formatted doesn't it? That's all well and good all the information you need can be gleamed from one cmdlet alone, but frequently we as scripters need to pull in data from multiple source, or multiple cmdlets. It would be possible to simply run the two cmdlets and send the output to the host, but it's quite simple(once you get the hang of it) to get all that information and display it in a professional manner.

As an example of how custom objects can help you in your scripts, take a look at this one below:

$mbxs = Get-Mailbox
$now = Get-Date
$colEmails = @()
foreach ($mbx in $mbxs)
{
      $ProgressPreference = "SilentlyContinue"
      $colmsgs = Get-ExchangeServer | Where-Object {$_.IsHubTransportServer -eq $true -or $_.IsMailboxServer -eq $true} | Get-MessageTrackingLog -Recipients $mbx.PrimarySmtpAddress -start $now.AddDays("-7") -End $now -ResultSize Unlimited | Where-Object {$_.EventId -eq "Receive"}

    if ($colmsgs.Count)
    {
        $objMail = New-Object System.Object
        $objMail | Add-Member -type NoteProperty -name Name -value $mbx.Name
        $objMail | Add-Member -type NoteProperty -name Emails -value $colmsgs.Count
        $colEmails += $objMail
    }
}
$colEmails | Sort-Object Emails -descending


This script was created for our Exchange Server 2007 environment as something we were curious to see, it basically pulls together information from the Message tracking logs and gets a list of the users who have sent the most emails over the last week. Now I'll say this now because there are people who'll notice this script is not bullet proof, but it did the job for us. Let's walk through it and you will see how it works, and how custom objects can help you, shall we:

Part 1 - Initialize and populate variables:

$mbxs = Get-Mailbox
$now = Get-Date
$colEmails = @()

With a quick glance you can see here that I create three variables, and populate two with data. The third $colEmails I simply create it as an empty array, this is huge later on in the script, but we'll get to that in a minute. The first variable, uses Get-Mailbox to get a collection of all mailboxes in my company. The next variable simply uses the Get-Date cmdlet to get the current date.

Part 2 - Looping and Getting the Real Data


foreach ($mbx in $mbxs)
{
      $ProgressPreference = "SilentlyContinue"
      $colmsgs = Get-ExchangeServer | Where-Object {$_.IsHubTransportServer -eq $true -or $_.IsMailboxServer -eq $true} | Get-MessageTrackingLog -Recipients $mbx.PrimarySmtpAddress -start $now.AddDays("-7") -End $now -ResultSize Unlimited | Where-Object {$_.EventId -eq "Receive"}

First off here we simply create the foreach loop to go through each of the mailboxes one by one. The next line of the script does the real work here, it starts by getting all the Exchange servers in my infrastructure, and filtering all but my HubTransport servers. I then pipe those objects, to the Get-MessageTrackingLog cmdlet, searching where the recipient is the Primary email address of the mailbox, starting from one week ago up until now, you'll note to get our start date we use the AddDays method of the System.DateTime object specifying a negative days, giving us one week in the past, sneaky huh? Following that we again use Where-Object to get those log entries were the EventID is Receive. This gives us all the receive entries for our users. Onward and downward??

Part 3 - Create my custom object and fill it up

    if ($colmsgs.Count)
    {
        $objMail = New-Object System.Object
        $objMail | Add-Member -type NoteProperty -name Name -value $mbx.Name
        $objMail | Add-Member -type NoteProperty -name Emails -value $colmsgs.Count
        $colEmails += $objMail
    }
}

As you can see the first here simply uses an if statement to check whether msgs.count is not Null, which could be possible if the user did not send any email during the last week. If the property actually has a value, then we proceed to the steps within the if statement.

Subsequently we actually create the our custom bject using the New-Object cmdlet, declaring it the type of System.Object(For more info on System.Object check out this link, http://msdn.microsoft.com/en-us/library/system.object.aspx). Think of System.Object as the end all be all of objects, since it can literally be all objects. All .NET classes are derived from System.Object and as such every method defined on System.Object is available to .NET objects.

Once we have the object created and have specified it's type, we have this nice little object sitting there looking very pretty, lets actually fill it with some fields and data shall we? Next off we simply pipe our object into the Add-Member cmdlet, this cmdlet allows us to add members(or properties) to objects, the first thing we add is the name of the mailbox we've got the message tracking data for. We are using a noteproperty to specify a property with a static value, once we set it it's not going to change, which is great for our purposes(There are many different types you can add, for a complete list check out the TechNet info on Add-Member here, http://technet.microsoft.com/en-us/library/dd347695.aspx). Once that property is set, we need to actually add the number of Receive events we got earlier. To do this we use the same cmdlet this time however, we specify a different name for this property, and the value is $msg.Count.

A side note here. How did I know that my variable $msg had a count property? Well that's where Add-Members big brother comes to play, Get-Member. Take any object in Powershell and pipe it to Get-Member, and see what you get? Go ahead, I'll wait until you do it . . . . . . . Pretty cool wasn't it? Get-Member will show you all properties and methods available for an object. I honestly, haven't seen it in use in a script, but for creating a script and working on the command line it is unbeatable, and must be in every Powersheller's repertoire. But I've digressed again, which is the great thing about having your own blog if you want to go on a tangent it's your call. Ahem, tally-ho?

So now we have our object populated with the properties we wanted, it shows us the Name of the mailbox that got the mail, and the number of receive events from the Message tracking log. Now what do we do with it? Well that is solved by the next line, in which we simply take our custom object and add it to the array we created earlier, using the += operator. This simply says take what's on the left, and add to it what's on the right. Now that we've stored our object in something more permanent, we close out our if statement and close our foreach loop as well.

Now one thing to note you may have noticed. You'll see above that if you follow the logic of the script my custom object, $objEmail, will get re-created each time the loop processes. This is fine and will work to our advantage, as it clears all properties we set before and give us a pristine empty object, ready to receive the next users information.



The final line simply takes our array, and outputs it to the console. The output you will receive will be something similar to this:

Name        Emails
----        ------
User, A     17072
User, B     8946
User, C     609
User, D     575
User, E     255

Looks pretty nice doesn't it? As you can see custom objects give you the ability to organize your data, and display it in such a way that makes it easier to read, and understand. With a little extra work, and some basic concepts you can take your scripting to the next level.

PS: Forgive me for any formatting issues, this is my second time and I'm getting better.