PSG2013 Beginner Event 0: System Uptime.

This is Beginner Event 0 (the warm up) for the 2013 PowerShell Scripting Games

I will attempt to answer both the beginner and advanced versions of this exercise.

This event centers on calculating system uptime, which is a realistic administrative task that I’ve written into a couple of different scripts I’ve developed at work. I’m not particularly fond of any of my implementations, so I’m going to start this from scratch.

Goal: A one-liner that returns the system uptime for each name or IP address specified in a text file. The return should be a pipeable/sortable object that contains the computer name (regardless of whether it was initially specified) as well as independent properties for uptime in days, hours, minutes, seconds.

In the Beginner track, the input is specified as a list of names or ip addresses in a text file. Emphasis placed on the most concise answer possible (speculated one-liner), so I’m going to have to find built-in/pipeable cmdlets to do my work.

Success! Here’s the Single line command to accomplish this task. (Our code engine doesn’t wrap long single lines, so I’ve put it in twice so you can see the whole thing without scrolling or copy the un-ticked version)

#Broken Lines
Get-WMIObject -class Win32_OperatingSystem -computername (Get-Content computers.txt) | 
ForEach {New-TimeSpan -start $_.ConvertToDateTime($_.lastbootuptime) -end (Get-Date) | 
Add-Member -MemberType NoteProperty -Name Computername -Value $_.CSName -passthru} | 
Select ComputerName, @{Name='Hours';Expression={[system.math]::truncate($_.TotalHours)}}, Minutes, Seconds
 
#Single Line
Get-WMIObject -class Win32_OperatingSystem -computername (Get-Content computers.txt) | ForEach {New-TimeSpan -start $_.ConvertToDateTime($_.lastbootuptime) -end (Get-Date) | Add-Member -MemberType NoteProperty -Name Computername -Value $_.CSName -passthru} | Select ComputerName, @{Name='Hours';Expression={[system.math]::truncate($_.TotalHours)}}, Minutes, Seconds

Special thanks to mjolinor at PowerShell.org for pointing out my Math error. I went ahead and fixed the issue I identified with pulling the wrong values. It said total hours, minutes, seconds. Not Days…

Here’s the dirty details I went through to get there…

Beginner Track Event 0: System Uptime

So I need to calculate uptime from a specified list of hosts in a text file, so I already know I will be feeding in Get-Content textfile somewhere in here. I also need to know where to get the information. The WMI class Win32_OperatingSystem has a last boot time property that’s stored in WMI’s funky format, so I can probably do some math. It also has CSName, which gives me the name even if I only had an IP to start with. The trick will be getting name and separated properties into an object I can pass on the shell in a one-line command.

—PowerShell Pause

So I went into PowerShell and pulled the WMI class from my machine and started playing with the properties. I knew there was a scriptmethod for converting that funky WMI date to something more readable, but I didn’t realize it put out a [System.DateTime] object (I guess I should have everything in PowerShell is an object!). Turns out, this object already has the hours/minutes/seconds I want.

(Get-WMIObject -computer (Get-Content textfile.txt) -class Win32_OperatingSystem).ConvertToDateTime([$lastboouptime]) 
#gives me back a readable object oriented date.
 
(Get-WMIObject -computer (Get-Content textfile.txt) -class Win32_OperatingSystem).lastbootuptime 
#gives me back a boot time in WMI Format

My problem at the moment is the ConvertToDateTime scriptproperty because it needs an argument, which is another member of the same object. Unless I store the object, I do not know how to get it there besides calling it again…

(Get-WMIObject …).ConvertToDateTime((Get-WMIObject…).lastbootuptime)?

I’m also not sure how to pass in the computername without storing it. I know I can add-member on the object that results from these commands, but I don’t know how to get the name fed in during my Get-Content other than the store it. Maybe that’s the key…

($computers = Get-Content computers.txt | `
ForEach {$tmp = Get-WMIObject -class Win32_OperatingSystem `
-computername $_;$tmp.ConvertToDateTime($tmp.lastbootuptime)})

This returned the readable date-time objects I was looking for! And I retain the computername as a passable string in the pipe. Now we’re getting somewhere.

I need to get uptime now, so I need to compare times. Playing with Get-Command *Time* found me an cmdlet called New-TimeSpan, which compares dates and times. So I can use that to get my uptime by simply comparing the date I got back to right now.

($computers = Get-Content computers.txt | ForEach {$tmp = `
Get-WMIObject -class Win32_OperatingSystem -computername $_; `
$tmp=$tmp.ConvertToDateTime($tmp.lastbootuptime);New-Timespan `
-start $tmp -end (Get-Date)})

This gets me back the uptime objects. I think I can feed the computernames in here. I need to pull it out of that WMI class…

($computers = Get-Content computers.txt | ForEach {$tmp = `
Get-WMIObject -class Win32_OperatingSystem -computername $_; `
$computername = $tmp.CSName; `
$tmp=$tmp.ConvertToDateTime($tmp.lastbootuptime);New-Timespan `
-start $tmp -end (Get-Date) | Add-Member -MemberType NoteProperty `
-Name ComputerName -value $computername -passthru})

Initially I thought this didn’t work, because I only got back the properties of the time span object. I piped the whole thing into Get-Member and found my new property, then I remembered the object has default properties. Piping to Select gives me what I want and cleaned up the output…

($computers = Get-Content computers.txt | ForEach {$tmp = `
Get-WMIObject -class Win32_OperatingSystem -computername $_; `
$computername = $tmp.CSName; `
$tmp=$tmp.ConvertToDateTime($tmp.lastbootuptime);New-Timespan `
-start $tmp -end (Get-Date) | Add-Member -MemberType NoteProperty `
-Name ComputerName -value $computername -passthru}) | Select `
ComputerName, Days, Hours, Minutes, Seconds

So there’s a one liner that gives me Days, Hours, Minutes, Seconds of uptime for each computer in a list. I’m not sure if its considered cheating to call this a one-liner, since each semi-colon technically delimits a new scriptblock line, but there you have it.

—break for about 30 seconds

It always hits me late! Here’s a version that is really a one-liner. Pipe within a pipe.

Get-WMIObject -class Win32_OperatingSystem -computername `
(Get-Content computers.txt) | ForEach {New-TimeSpan -start `
$_.ConvertToDateTime($_.lastbootuptime) -end (Get-Date) | Add-Member -MemberType `
NoteProperty -Name Computername -Value $_.CSName -passthru} | `
Select ComputerName, Days, Hours, Minutes, Seconds

I really enjoy playing with these as mind-teasers. Some people play Sudoku, I write PowerShell code. This one was fun because it forced me to change the way I think about the pipeline.

Tomorrow I’ll take a swing at the advanced track exercise. Until then, stay tuned!

Jeff is a system administrator for an enterprise of over 4000 devices. He is comfortable on Windows and *nix and participates in everything from desktop to network support. In addition, he develops in PowerShell to aid in the automation of administrative tasks.

When not at work, Jeff is adjunct faculty at the University of Alaska Anchorage, where he teaches PC Architecture to first year students in the Computer and Networking Technology Program. He is also working on new curriculum in PowerShell and Virtualization as well as leading department technology deployments.


View Jeff Liford's profile on LinkedIn



Posted in Desktop, PowerShell, Server