<<

Hey, Scripting Guy! Calculating

p is up and down is down. THE SCRIPTING GUYS ANSWER Seems rather obvious – - U cept, that is, when you’re How can I use the System event talking about server uptime. To know uptime, you need to know downtime. log to server uptime? Nearly every network administrator is concerned about server uptime. (Ex- it generally makes sense to see what When you subtract one value cept, that is, when he is worried about of data is being used. from another, you are left with an in- server downtime.) administrators To examine the data the script is stance of a System.TimeSpan object. have uptime goals and need to provide using, you can simply it to the This means you can choose how to dis- uptime reports to upper management. screen. Here’s what you get: play your uptime information without

What’s the big deal? It seems like C:\> $wmi.LocalDateTime having to perform a lot of arithmetic. you could use the Win32_Operating- 20080905184214.290000-240 You need only choose proper- System WMI class, which has two ty to display (and hopefully you count The number seems a bit strange. properties that should such an your uptime in TotalDays and not in What kind of a date is this? To find out, operation pretty easy: LastBootUp- TotalMilliseconds). The default dis- use the GetType method. The good Time and LocalDateTime. All you need play of the System.TimeSpan object is thing about GetType is that it is near- to do, you think, is subtract the Last- shown here: ly always available. All you need to do BootUptime from the LocalDateTime, is call it. And here’s the source of the Days : 0 and then all is right with the world Hours : 0 problem – the LocalDateTime value is Minutes : 40 and you can even go catch a quick nine Seconds : 55 being reported as a string, not as a Sys- holes before dinner. Milliseconds : 914 tem.DateTime value: Ticks : 24559148010 So you fire up Windows PowerShell TotalDays : 0.0284249398263889 PS C:\> $wmi.LocalDateTime.gettype() TotalHours : 0.682198555833333 to query the Win32_OperatingSystem TotalMinutes : 40.93191335 WMI class and select the properties, as IsPublic IsSerial Name BaseType TotalSeconds : 2455.914801 ------TotalMilliseconds : 2455914.801 shown here: True True String System.Object The problem with this method is PS C:\> $wmi = Get-WmiObject -Class Win32_ that it only tells you how long the OperatingSystem If you need to subtract one time from server has been up since the last re- PS C:\> $wmi.LocalDateTime - $wmi.LastBootUpTime another time, make sure you’re dealing . It does not calculate downtime. with time values and not . This But when you run these commands, This is where up is down – to calculate is easy to do using the ConvertToDate- you are greeted not with the friend- uptime, first you need to know the Time method, which Windows Power- ly uptime of your server, but by the downtime. adds to all WMI classes: rather mournful error message you see So how do you figure out how long PS C:\> $wmi = Get-WmiObject -Class Win32_ in Figure 1. OperatingSystem the server has been down? To do this, The error message is perhaps a bit you need to know when the server PS C:\> $wmi.ConvertToDateTime($wmi.LocalDateTime) – misleading: “Bad numeric constant.” $wmi.ConvertToDateTime($wmi.LastBootUpTime) starts and when it shuts down. You can Huh? You know what a number is and you know what a constant is, but what does this have to do with time? When confronted with strange er- Figure 1 An error is ror messages, it’s best to look directly returned when trying to the data the script is trying to parse. subtract WMI UTC time Moreover, with Windows PowerShell, values

TechNet Magazine February 2009 71 Hey, Scripting Guy!

get this information from the System event log. One of the first processes that starts on your server or worksta- tion is the event log, and one of the last things that stop when a server is shut down is the event log. Each of these start/stop events generates an eventID – 6005 when the event log Figure 2 The event log service starts and 6006 when the event log starts shortly after stops. Figure 2 shows an example of startup an event log starting. By gathering the 6005 and 6006 events from the System Log, - Figure 3 CalculateSystemUpTimeFromEventLog 3 ing them, and subtracting the starts

#------from the stops, you can determine the # CalculateSystemUpTimeFromEventLog.ps1 amount of time the server was down # wilson, msft, 9/6/2008 # between restarts. If you then subtract # Creates a system.TimeSpan object to subtract date values # Uses a . Framework class, system.collections.sortedlist to sort the events from eventlog. that amount from the number of min- # utes during the period of time in ques- #------#Requires -version 2.0 tion, you can calculate the percentage Param($NumberOfDays = 30, [switch]$) of server uptime. This is the approach

if($debug) { $DebugPreference = " continue" } taken in the CalculateSystemUpTi- meFromEventLog.ps1 script, which is [timespan]$uptime = New-TimeSpan -start 0 -end 0 $currentTime = get-Date shown in Figure 3. $startUpID = 6005 The script begins by using the - $shutDownID = 6006 $minutesInPeriod = (24*60)*$NumberOfDays am statement to define a couple of $startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays) command-line parameters whose val-

Write-debug "'$uptime $uptime" ; start- -s 1 ues you can change when you run the -debug "'$currentTime $currentTime" ; start-sleep -s 1 script from the command line. The write-debug "'$startingDate $startingDate" ; start-sleep -s 1 first, $NumberOfDays, lets you specify $events = Get-EventLog - system | a different number of days to use in the Where-Object { $_.eventID -eq $startUpID -OR $_.eventID -eq $shutDownID ` -and $_.TimeGenerated -ge $startingDate } uptime report. (Note that I have sup- plied a default value of 30 days in the write-debug "'$events $($events)" ; start-sleep -s 1 script so you can run the script with- $sortedList = New-object system.collections.sortedlist out having to supply a value for the parameter. Of course, you can change ForEach($event in $events) { this if necessary.) $sortedList.Add( $event.timeGenerated, $event.eventID ) } #end foreach event The second, [switch]$debug, is a $uptime = $currentTime - $sortedList.keys[$($sortedList.Keys.Count-1)] switched parameter that lets you ob- Write-Debug "Current uptime $uptime" tain certain debugging information For($item = $sortedList.Count-2 ; $item -ge 0 ; $item -- ) from the script if you include it on { Write-Debug "$item `t `t $($sortedList.GetByIndex($item)) `t ` the command line when running the $($sortedList.Keys[$item])" script. This information can you if($sortedList.GetByIndex($item) -eq $startUpID) { feel confident in the results you $uptime += ($sortedList.Keys[$item+1] - $sortedList.Keys[$item]) Write-Debug "adding uptime. `t uptime is now: $uptime" get from the script. There could be } #end if times when the 6006 event log serv- } #end for item ice-stopping message is not present, "Total up time on $:computername since $startingDate is " + "{0:n2}" -f ` perhaps as a result of a catastrophic $uptime.TotalMinutes + " minutes." $UpTimeMinutes = $Uptime.TotalMinutes failure of the server that rendered it $percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100) unable to write to the event log, caus- $percentUpTime = 100 - $percentDowntime ing the script to subtract an uptime "$percentDowntime% downtime and $percentUpTime% uptime." value from another uptime value and skew the results.

72 Visit TechNet Magazine online at: http://technet.microsoft.com/en-gb/magazine/default.aspx After the $debug variable is supplied erals. The get eventIDs are equal either from the command line, it is present to $startUpID or to $shutDownID. You on the Variable: drive. In that case, the should also make sure the timeGener- value of the $debugPreference varia- ated property of the event log entry is ble is set to continue, which means the greater than or equal to the $starting- script will continue to run and any val- Date, like so: ue supplied to Write-Debug will be vis- $events = Get-EventLog -LogName system | ible. Note that, by default, the value of Where-Object { $_.eventID -eq $startUpID -OR $_.eventID -eq $shutDownID -and $debugPreference is silentlycontinue, $_.TimeGenerated -ge $startingDate } so if you don’t set the value of $debug- Keep in mind that this command Preference to continue, the script will will only run locally. In Windows run, but any value supplied to Write- PowerShell 2.0, you could use the – Debug will be silent (that is, it will not computerName parameter to make be visible). the command work remotely. When the script runs, the resulting Figure 4 Debug mode displays a The next step is to create a sorted list output lists each occurrence of the tracing of each time value that is added to the uptime calculation object. Why? Because when you walk 6005 and 6006 event log entries (as you through the collection of events, you can see in Figure 4) and shows the up- are not guaranteed in which order the time calculation. Using this informa- out the number of minutes during the event log entries are reported. Even if tion, you can confirm the accuracy of period of time in question: you pipeline the objects to the Sort- the results. $minutesInPeriod = (24*60)*$NumberOfDays Object cmdlet and store the results The next step is to create an instance back into a variable, when you iterate of the System.TimeSpan object. You Finally, you need to create the through the objects and store the re- could use the New-Object cmdlet to $startingDate variable, which will hold sults into a table, you cannot be create a default timespan object you a System.DateTime object that repre- certain that the list will maintain the would use to perform date-difference sents the starting time of the reporting results of the sort procedure. calculations: period. The date will be midnight of the beginning date for the period: In order to sidestep these frus- PS C:\> [timespan]$ts = New-Object system. trating and difficult-to-debug prob- timespan $startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays) lems, you create an instance of the But Windows PowerShell actually System.Collections.SortedList object, After the variables are created, you has a New-TimeSpan cmdlet for creat- using the default constructor for the retrieve the events from the event ing a timespan object, so it makes sense object. The default constructor tells log and store the results of the que- to use it. Using this cmdlet makes the the sorted list to sort dates chronologi- ry in the $events variable. Use the script easier to read, and the object that cally. Store the empty sorted list object Get-EventLog cmdlet to query the is created is equivalent to the timespan in the $sortedList variable: object created with New-Object. event log, specifying “system” as the log name. In Windows PowerShell $sortedList = New-object system.collections. Now, you can initialise a few var- sortedlist iables, starting with $currentTime, 2.0, you could use a –source parame- which is used to hold the current time ter to reduce the amount of informa- After you create the sorted list ob- and date value. You get this informa- tion that needs to be sorted out in the ject, you need to populate it. To do this, tion from the Get-Date cmdlet: Where-Object cmdlet. But in Win- use the ForEach statement and walk dows PowerShell 1.0, you don’t have through the collection of event log en- $currentTime = get-Date that and must therefore sort tries stored in the $entries variable. As Next, initialise the two variables through all the unfiltered events re- you walk through the collection, the that will hold the startup and shut- turned by the query. So pipeline the $event variable keeps track of your po- down eventID numbers. You don’t re- events to the Where-Object cmdlet to sition in the collection. You can use ally need to do this, but the code will filter out the appropriate event log en- the add method to add two properties be easier to read and easier to trouble- tries. As you examine the Where-Ob- to the System.Collections.SortedList shoot if you avoid embedding the two ject filter, you’ll see why the Scripting object. The sorted list allows you to as string literal values. Guys had you create the variables to add a key and a value property (simi- The next step is to create a variable hold the parameters. lar to a Dictionary object except that it called $minutesInPeriod to hold the The command reads much better also lets you index into the collection result of the calculation used to figure than it would if you used the string lit- just like an array does). Add the time-

TechNet Magazine February 2009 73 Hey, Scripting Guy!

generated property as the key and the $uptime = $currentTime - You can’t about $sortedList.keys[$($sortedList.Keys.Count-1)] eventID as the value property: Write-Debug "Current uptime $uptime"

ForEach($event in $events) uptime without { It is now time to walk through the $sortedList.Add( $event.timeGenerated, sorted list object and calculate the up- $event.eventID ) taking downtime } #end foreach event time for the server. Because you are us- ing the System.Collections.Sorted list Next, you calculate the current up- into consideration object, you will take advantage of the time of the server. To do this, you use fact that you can index into the list. To the most recent event log entry in the the sorted list. To get the index value, do this, use the for statement, begin- sorted list. Note that this will always be use the count property and subtract ning at the count -2 because we used a 6005 instance because if the most re- one from it. Then subtract the time count -1 earlier to figure the current cent entry were 6006, the server would the 6005 event was generated from amount of uptime. still be down. Since the index is zero the date time value stored in the $cur- We are going to count backward to based, the most recent entry will be renttime variable you populated earli- get the uptime, so the condition speci- the count –1. er. You can print out the results of this fied in the second position of the for To retrieve the time-generated value, calculation only if the script is run in statement is when the item is great- you need to look at the key property of debug mode. This code is shown here: er or equal to 0. In the third position

Version issues

While testing the CalculateSystem­Up­ of the System­.Collections.SortedList script, we m­odify the code to use tim­eFrom­EventLog.ps1 script on his class and adds som­e inform­ation to it. GetKey instead of iterating through laptop, Contributing Editor Michael At this point, I used the keys property the keys collection. In the 2.0 version Murgolo ran across a m­ost annoying to print out the listing of the keys. of the script, we add a tag that requires error. I gave the script to m­y friend Jit, Here is that code: version 2.0 of Windows PowerShell. If

and he ran into the sam­e error. What $aryList = 1,2,3,4,5 you try to run that script on a Win­ was that error? Well, here it is: $sl = New-Object Collections.SortedList dows PowerShell 1.0 m­achine, the ForEach($i in $aryList) { PS C:\> C:\fso\ script will sim­ply and you will not $sl.add($i,$i) CalculateSystemUpTimeFromEventLog.ps1 } get the error. Cannot index into a null array. At C:\fso\CalculateSystemUpTimeFromEventLog. Michael also pointed out som­ething $sl.keys ps1:36 char:43 + $uptime = $currentTime that’s not a bug but is related to a de­ - $sortedList.keys[$ <<<< ($sortedList.Keys. Count-1)] On m­y com­puter, this code works sign consideration. He noted that the Total up time on LISBON since 09/02/2008 00:00:00 is 0.00 minutes. fine. On Jit’s com­puter, it failed. script will not properly detect uptim­e 100.00% downtime and 0% uptime. Bum­m­er. But at least it pointed m­e in if you hibernate or sleep the com­puter. the right direction. The problem­, as it This is true, as we do not detect or The error, “Cannot index into a null turns out, is that there is a bug with look for these events. array,” is an indication that the array the System­.Collections.SortedList in In reality, however, I am­ not con­ was not created properly. So I dug into Windows PowerShell 1.0. And I happen cerned with uptim­e on m­y laptop or the code that creates the array: to be running the m­ost recent build of desktop com­puter. I am­ only interested ForEach($event in $events) the yet to be released Windows Pow­ in uptim­e on a server, and I have yet to { $sortedList.Add( $event.timeGenerated, erShell 2.0 where that bug was fixed, m­eet the server that sleeps or hiber­ $event.eventID ) nates. It could happen, of course, and } #end foreach event and thus the code runs fine. So where does that leave our script? it m­ight be an interesting way to con­ In the end, that code was fine. What As it turns out, the SortedList class serve electricity in the data center, but was causing the error? has a m­ethod called GetKey, and that it’s not som­ething I’ve encountered Next, I decided to look at the m­ethod works on both Windows yet. Let m­e know if you hibernate your SortedList object. To do this, I wrote a PowerShell 1.0 and Windows Power­ servers. You can reach m­e at Scripter@ sim­ple script that creates an instance Shell 2.0. So for the 1.0 version of the Microsoft.com­.

74 Visit TechNet Magazine online at: http://technet.microsoft.com/en-gb/magazine/default.aspx of the for statement you use --, which will decrement the value of $item by one. You use the Write-Debug cmdlet Dr Scripto’s scripting perplexer to print out the value of the index The m­onthly challenge that tests not only your puzzle-solving ability but also number if the script is run with the – your scripting skills. debug switch. You also tab over by us- ing the `t character and print out the December 2008: PowerShell commands timegenerated time value. This section The list below contains 21 Windows PowerShell com­m­ands. The square of code is shown here: contains the sam­e com­m­ands, but they are hidden. Your job is to find the com­­ m­ands, which m­ay be hidden horizontally, vertically, or diagonally (forward or For($item = $sortedList.Count-2 ; $item -ge 0 ; $item--) backward). { Write-Debug "$item `t `t $($sortedList. EXPORT-CSV -LIST FORMAT-TABLE GetByIndex($item)) `t ` GET-ACL GET- GET-CHILDITEM $($sortedList.Keys[$item])" GET-LOCATION INVOKE-ITEM MEASURE-OBJECT If the eventID value is equal to 6005, NEW-ITEMPROPERTY OUT- OUT-NULL which is the startup eventID value, REMOVE-PSSNAPIN SET-ACL SET-TRACESOURCE you calculate the amount of uptime by - START-SLEEP STOP-SERVICE subtracting the start time from the pre- SUSPEND-SERVICE WRITE-DEBUG WRITE-WARNING vious downtime value. Store this value S R E M O V E P S S N A P I N A in the $uptime variable. If you are in U T M E T I D L I H C T E G E T debug mode, you can use the Write- S G O F O R M A T T A B L E C Debug cmdlet to print these values to P R E P I S A I L A T E G X I E the screen: E X P T S O H T U O U N U P T J N W H I L E A N D B I L B O E B if($sortedList.GetByIndex($item) -eq $startUpID) { D R A T M O R M A N V C E R M O $uptime += ($sortedList.Keys[$item+1] S O L H A R C V R E D A D T P E - $sortedList.Keys[$item]) Write-Debug "adding uptime. `t uptime is E G E T A C L A I R I T E C R R now: $uptime" R E D A H K W E T C F E T S O U } #end if } #end for item V A N P I E R A L I E S I V P S I S E T T R A C E S O U R C E A Last, you need to generate the re- C U T I O U T N U L L N W I R E port. Pick up the computer name from E A R L M E T I E K O V N I T M the system com- S W A P F O R M A T L I S T Y A puter. You use the current time stored R E C S N O P E E L S T R A T S in the $startingdate value, and you dis- You can find the answer to this puzzle at technetmagazine.com/puzzle. play the total minutes of uptime for the period. You use the format speci- fier {0:n2} to print the number to two digits. Next, calculate the percentage Let’s return to the original ques- out the other “Hey, Scripting Guy” col- of downtime by dividing the number tion: When is up down? You now see umns at http://technet.microsoft.com/ of uptime minutes by the number of that you can’t talk about uptime with- magazine/cc135880 or go to the Script minutes in the time period covered by out taking downtime into considera- Center at www.microsoft.com/technet/ the report. Use the same format speci- tion. If you thought this was fun, check scriptcenter. ■ fier to print the value to two decimal places. Just for fun, you can also - Ed Wilson is a senior consultant at Microsoft and a well-known scripting expert. culate the percent of uptime and then He is a Microsoft Certified Trainer delivers a popular Windows PowerShell print out both values, like so: workshop to Microsoft Premier customers worldwide. He has written eight books "Total up time on $env:computername since including several on Windows scripting, and has contributed to almost a dozen other $startingDate is " + "{0:n2}" -f ` $uptime.TotalMinutes + " minutes." books. Ed holds more than 20 industry certifications. $UpTimeMinutes = $Uptime.TotalMinutes Craig Liebendorfer is a wordsmith and longtime Microsoft web editor. Craig $percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100) still can’t believe there’s a job that pays him to work with words every day. One of his $percentUpTime = 100 - $percentDowntime favourite things is irreverent humour, so he should fit right in here. He considers his "$percentDowntime% downtime and $percentUpTime% uptime." magnificent daughter to be his greatest accomplishment in life.

TechNet Magazine February 2009 75