Pages

Friday, January 30, 2015

Re-usable PowerShell Scripts

I've always tried to make my PowerShell scripts re-usable by making them do VERY specific (singular) things.

For example, let's say that we are given a task to report on the up time of all Domain Controllers.  One could make a single script that would use ADSI or Get-ADComputer (or even the Get-QADComputer) to get all the Domain Controllers and then get and report on each of their up times via looping through all the Domain Controllers.

This is not very re-usable.  What happens if next week, you are given a new task to report on the up time of all Exchange Servers (because you did such a good job of getting it for all the Domain Controllers)?  You could easily take your script for Domain Controllers and create a new script for Exchange Servers.  But then, because you are on a roll, they provide you with a list of random servers  that they want you to report the up time on.  I guess you'll be copying and pasting to create another version of the script that reads servers from a text file.

The logic that actually gets the up time is the same, so a better approach (IMO), is to think about re-usability when creating the first script.  Just make your script get and output the up time of a system that is provided as input via parameters that accept pipeline input.  In this way, you use the same script for all three instances above and the only thing that changes is the input you provide to the script.

Another thing is I don't format the output for a particular task either.  Using the same example above, I would not format the output to give me "days" of up time.  Just because I want an output in days for this task, who's to say for the next I wouldn't want minutes.  This is especially true when looking at say file sizes.  Today I may want MB, but tomorrow I may want GB.  So I write my scripts as generic as possible and then use Select-Object to generate Calculated Properties.  Here's a link to a TechNet PowerShell tip about Calculated Properties.

Lastly, I almost always name my scripts using the Verb-Noun naming standard.  It makes it easier for me to find a script later.

So what would my Get-Uptime script look like?
[CmdletBinding()]
Param(
    [Parameter(
        Position=0,
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$True
    )]
    [Alias('Name')]
    [ValidateNotNullOrEmpty()] 
    [string[]]$ComputerName = "localhost"
)


PROCESS {
    foreach ($Computer in $ComputerName) {
        try {
            $wmi = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $Computer -ErrorAction Stop
            $lastBoot = $wmi.ConvertToDateTime($wmi.LastBootUpTime)

            $object = New-Object System.Object
            $object | Add-Member -Type NoteProperty -Name ComputerName -Value $Computer
            $object | Add-Member -Type NoteProperty -Name LastBootTime -Value $lastBoot
            $object | Add-Member -Type NoteProperty -Name Uptime -Value (New-TimeSpan $lastBoot (Get-Date))

            Write-Output $object
        } catch {
            Write-Error $error[0]
        }
    }
}

For the sake of simplicity, I left my Comment-Based Help out, but I also almost always use Comment-Based Help as it helps both me down the road and my co-workers to be able to use Get-Help Get-Uptime.ps1

This script gives me the flexibility to run the script multiple different ways.
Get-Uptime
Get-Uptime -ComputerName "SERVER1", "SERVER2"
"SERVER1", "SERVER2" | Get-Uptime
Get-Content Names.txt | Get-Uptime
Get-Uptime -ComputerName (Get-Content Names.txt)
Get-ADComputer -Filter * | Select-Object Name | Get-Uptime

All of those will work, so it doesn't matter if I just need to get the uptime from a single system, a text file with a list of systems, or if I have to query AD to get a list of systems.

The main key here is to utilize the pipeline and think about re-usability when you are writing your next script.

2 comments:

  1. Thanks...The wait is over. I hate to admit it, but I like reading your posts.

    ReplyDelete
  2. Thanks Mike. That must have been hard to admit.

    ReplyDelete