Learn about Windows PowerShell
(Note: These solutions were written for Beginner Event 2 of the 2010 Scripting Games.)
Beginner Event 2 (Windows PowerShell)
Christian Bellée—Microsoft Premier Field Engineer, Sydney, Australia. I teach Windows PowerShell and VBScript courses for Microsoft Premier, and I am an Active Directory support engineer. I have also recently developed a Windows PowerShell 2.0 for the IT Administrator Workshop PLUS course for Microsoft Premier. Get-RecentStartupEvent.ps1 Script:#--------------------------------------------------------------------------------------------#requires -version 2.0param([array]$arrComputer)$arrEvent = @()$arrComputer | ForEach-Object ` { if (Test-Connection -ComputerName $_ -count 1 -Quiet) { "`n Collecting event information from $_" $event = Get-EventLog -LogName system -ComputerName $_ | Where-Object {$_.eventID -eq 6005} | Select-Object -First 1 $arrEvent += $event } else {"`n $_ is Unreachable"} } $arrEvent | Sort-Object -property TimeGenerated -desc | Format-Table -Property MachineName,TimeGenerated,EventID,Message –autosize#-------------------------------------------------------------------------------------------- Description Collecting event log entries from remote machines is a very common administrative task, and Windows PowerShell 1.0 provided this functionality for local machines only, with the Get-Eventlog cmdlet. Although the underlying .NET Framework type, system.diagnostics.eventlog, used within the cmdlet has a constructor that allows us to specify the name of a remote machine, this wasn’t exposed in Windows PowerShell 1.0. Windows PowerShell 2.0 enhanced the Get-Eventlog cmdlet to include the ability to specify a remote machine, making this script challenge a whole lot easier! Essentially, the solution depends on the following simple Windows PowerShell pipeline command: Get-EventLog -LogName system -ComputerName <computer name> | Where-Object {$_.eventID -eq 6005} By employing the –ComputerName parameter of the Get-Eventlog cmdlet, we can target a remote machine. The resulting objects are then piped to a Where-Object cmdlet, which extracts all events of EventID 6005. Although, my script solution goes a bit further than this, allowing the user to pass an array of machine names into the script and sorting the entries from all the machines in descending date order. The param() keyword allows a named argument to be specified on the command line and also casts the comma-separated string into an array, using the [array] type accelerator. Note the default machine name of “localhost” is assigned to the variable as a default. param([array]$arrComputer=”localhost”) Next, we create an empty array to store our events for format processing later on: $arrEvent = @() The $arrComputer variable containing the list of remote computer names is then piped to a ForEach-Object cmdlet, where an if() statement containing the Test-Connection cmdlet determines whether the computer can be contacted. If the test fails, script execution moves to the else {} part of the statement and displays that the machine is unreachable. Determining whether remote machines are available before running further operations against them is vital when writing scripts for remote machines. if (Test-Connection -ComputerName $_ -count 1 -Quiet) {<eventlog query code removed>}else {"$_ is Unreachable"} If the remote machine is responding, we can then query it for event log entries matching Event ID 6005 and select the most recent entry by using the Select-Object cmdlet and specifying the –First parameter as 1: "Collecting event information from $_"$event = Get-EventLog -LogName system -ComputerName $_ | Where-Object {$_.eventID -eq 6005} | Select-Object -First 1 Next, the resulting event log entry is saved in the $event variable and added to the $arrEvents array we created at the beginning of the script: $arrEvent += $event Finally, we can sort the $arrEvents array of objects by their TimeGenerated property and format the result as an autosized table containing the MachineName, TimeGenerated, EventID, and Message properties of each object: $arrEvent | Sort-Object -property TimeGenerated -desc | Format-Table -Property MachineName,TimeGenerated,EventID,Message –autosize To run the script, just pass a comma-separated list of machine names after the script name on the command line. PC C:\> ./Get-RecentStartupEvent.ps1 machine1,machine2,machine3 The following image shows the results of the script. Beginner Event 2 (VBScript) Stephanie Peters, Senior Premier Field EngineerMicrosoft Solutions Support This Is How I Roll Before going any further, I should explain that I am known to wander outside the lines a bit. Being given a task like this one, which seems fairly simple, typically sends my mind into a maelstrom of “So how about if I…” or “I should make sure to add…” and almost always “But wouldn’t it be better if I…?” In the spirit of the games, I will make sure to meet the requirements technically, but I hope you’ll indulge my selfish habits a bit as I embellish and insert my own specifications into the mix. Specify a Computer Before we can get any information, we have to specify a target host. There are a number of ways to allow the user to specify a computer name. My favorite is the command-line argument. Personally I like to use named arguments, so I started off by accepting a named argument: If Wscript.Arguments.Named.Count > 0 Then On Error Resume Next strComputer = WScript.Arguments.Named("Computer") On Error Goto 0 End If Notice the On Error statements. I don’t typically like to run large sections of code under On Error Resume Next. I usually will identify a line where an error is likely to occur and only ignore errors for those specific lines. In this case we don’t need to check to see if an error occurred because our logic will test the value of strComputer later. Since I’ve been using Windows PowerShell a lot the last few years, I’ve grown used to being able to provide an argument either as a named or a positional argument. I thought it might be nice to allow a positional argument (at the first position) if a named argument wasn’t specified: If Not strComputer > "" Then If WScript.Arguments.Count > 0 Then strComputer = WScript.Arguments(0) End If End if Finally, if no arguments were supplied to the script, we will fall back to a default value of “.”, which represents the localhost: If Not strComputer > "" Then strComputer = "." End If Collecting Data Because I’m going to potentially be doing time calculations at different points in my script, I decided to add a variable to hold the date time at a fixed point toward the beginning of the script, before any WMI values are returned: dtmNow = Now The next thing to do is to open a connection to the WMI Service on the target host. This is another time when an error could occur. The user could have specified an invalid computer name, there could be a permissions issue, or there could be a problem communicating with that computer over RPC. In this case, we need to handle any error by displaying a message to the user and aborting the script: On Error Resume Next Set objWMIService = GetObject("winmgmts://" & strComputer _ & "/root/cimv2") If Err.Number Then WScript.Echo "ERROR: There was a problem connecting to " _ & the WMI service on """ & strComputer _ & """. The script is exiting." WScript.Quit End If On Error Goto 0 When working with event logs via WMI, it’s best to scope your query so that it will return as little data as possible. Trust me on this one—I’ve had to restart too many WMI services over the years to steer you wrong on this. In this case, we’re looking for only events in the System Log whose source is “Eventlog” and whose Event ID is 6005. Further, we’re not returning all properties for these events because all we need to know is the TimeGenerated value. strEventQuery = "SELECT TimeGenerated FROM Win32_NTLogEvent" _ & " WHERE LogFile='System' AND SourceName = 'Eventlog' AND " _ & " EventCode = '6005'" Now we execute the query, which can also fail for a number of reasons (bogus query, WMI corruption on the target computer, more permissions issues, etc.). The thing to know about this is that most of the time it won’t fail on ExecQuery. It will usually fail when you try to loop through the collection it returns: On Error Resume Next Set colEvents = objWMIService.ExecQuery(strEventQuery) For Each objEvent in colEvents If Err.Number Then WScript.Echo "ERROR: There was a problem executing " _ & "the query """ & strEventQuery _ & """ against host """ & strComputer _ & """. The script is exiting." WScript.Quit End If On Error Goto 0 strUTCEventTime = objEvent.TimeGenerated Exit For Next On Error Goto 0 Notice above that we stored the TimeGenerated property value of the first object in the collection as the variable strUTCEventTime, and then we exited the For Each loop. The WMI Event Log provider always returns the most recent events first, which is lucky for us. This means we can just read the first event and forget the rest. Ensuring a Good Result Now we potentially have a date time value representing the last time the computer started. I say “potentially” because it’s likely that there was no such event in the System Log. The log might have been cleared or overwritten since the last time the computer started. In that case, our query would have succeeded, but we would have had no events in our collection to loop through. Therefore, strUTCEventTime would be empty. First, let’s deal with the situation where we do have a date time value. WMI always returns dates and times in a UTC string format that is not so easy to read and really isn’t easy to leverage in a calculation. To overcome those challenges, we need to convert the UTC string to a DateTime value, for which we use the SWbemDateTime helper object: If strUTCEventTime > "" Then Set objWMIDTHelper = CreateObject("WbemScripting.SWbemDateTime") objWMIDTHelper.Value = objEvent.TimeGenerated dtmEventTime = objWMIDTHelper.GetVarDate While we’re at it, we can just go ahead and get the current uptime duration in seconds by comparing the date time we just got with the date time (dtmNow) we captured earlier in the script: intUptimeSeconds = DateDiff("s", dtmEventTime, dtmNow) Now, here’s where I go a bit rogue. If I wasn’t able to get a date time from an event, I know that I can still get the uptime information from a more reliable source. There is a performance counter (System\System Up Time) that is for this express purpose. In a perfect world with no fixed scenarios I would have just used this strategy and forgotten about event logs entirely: Else Set objPerfOS = _ objWMIService.Get("Win32_PerfFormattedData_PerfOS_System=@") intUptimeSeconds = objPerfOS.SystemUptime After we have the uptime in seconds, we can easily calculate the date time of the last start. You’ll generally find that this will be at least several seconds before the Eventlog 6005 event: dtmEventTime = DateAdd("s",-(intUpTimeSeconds),dtmNow) End If Making It Pretty Rather than display the uptime duration in seconds, I decided to create a more readable string that shows the number of days, hours, minutes, and seconds that the computer has been up. At first I tried using a series of DateDiffs to break this out, but I found a nasty little bug that resulted in an extra minute and negative seconds. That’s when I decided to use Mod and the division operator to make this work. To understand how I did this, you have to go back to all those long division problems you did in elementary school. Given a value of 64088 seconds, I need to first figure out how many of those seconds are not accounted for in minutes. 64088 sec/60 sec = 1068 minutes with a remainder of 8 seconds 1068 min/60 min = 17 hours with a remainder of 48 minutes 17 hours/24 hours = 0 days with a remainder of 17 hours What we would want to see is: 0 days, 17 hours, 48 minutes, and 8 seconds. We can achieve this by using the Mod operator, which gives usethe remainder of a division operation, subtracting the remainder from the original value and dividing the result by the specific interval to get to the next calculation. When we have all of the interval values, we can concatenate them into a readable string: intTempValue = intUpTimeSeconds intSeconds = intTempValue Mod 60 intTempValue = (intTempValue - intSeconds)/60 intMinutes = intTempValue Mod 60 intTempValue = (intTempValue - intMinutes)/60 intHours = intTempValue Mod 24 intTempValue = (intTempValue - intHours)/24 intDays = intTempValue strUptime = intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds" All that’s left to do now is to echo the results to the user. I decided to go ahead and display both the date time of startup as well as the uptime duration. WScript.Echo "Host """ & strComputer & """ has been running since " _ & dtmEventTime wscript.Echo "Uptime is " & strUptime Let’s See It Work! Note from Microsoft Scripting Guy Ed Wilson: Hey, did you know you can use Windows PowerShell to run a VBScript? Way cool trick, Stephanie! I love it! The complete script is shown here. GetComputerUptime.vbs ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' GetComputerUptime.vbs '' Written by Stephanie Peters '' '' This script returns the current uptime as determined by the last time the '' event log service was started. '' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''Option ExplicitDim colEventsDim dtmEventTimeDim dtmNowDim intDaysDim intHoursDim intMinutesDim intSecondsDim intTempValueDim intUptimeSecondsDim objEventDim objPerfOSDim objWMIDTHelperDim objWMIServiceDim strComputerDim strEventQueryDim strUptimeDim strUTCEventTime' Provide an input mechanism for the computer name with the following order' of precendence: Named argument, positional argument, default value of ' localhost (.)If Wscript.Arguments.Named.Count > 0 Then On Error Resume Next strComputer = WScript.Arguments.Named("Computer") On Error Goto 0End IfIf Not strComputer > "" Then If WScript.Arguments.Count > 0 Then strComputer = WScript.Arguments(0) End IfEnd ifIf Not strComputer > "" Then strComputer = "."End If' Echo an empty line for looksWScript.Echo' Get the current time to use as a reference point for calculating uptimedtmNow = Now' Get a reference to the WMI service on the target computerOn Error Resume Next Set objWMIService = GetObject("winmgmts://" & strComputer & "/root/cimv2") If Err.Number Then WScript.Echo "ERROR: There was a problem connecting to the WMI service on """ _ & strComputer & """. The script is exiting." WScript.Quit End IfOn Error Goto 0' Construct the query stringstrEventQuery = "SELECT TimeGenerated FROM Win32_NTLogEvent" & _ " WHERE LogFile='System' AND SourceName = 'Eventlog' AND EventCode = '6005'"' Execute query and return resultsOn Error Resume Next Set colEvents = objWMIService.ExecQuery(strEventQuery) For Each objEvent in colEvents If Err.Number Then WScript.Echo "ERROR: There was a problem executing the query """ _ & strEventQuery & """ against host """ & strComputer _ & """. The script is exiting." WScript.Quit End If On Error Goto 0 ' We only need to store the TimeGenerated property of the most recent event. strUTCEventTime = objEvent.TimeGenerated ' The WMI Event Log Provider always returns the most recent events first. ' After we've read one item, we can skip the rest. Exit For NextOn Error Goto 0If strUTCEventTime > "" Then ' Since WMI returns a UTC date time string, we have to convert it to a DateTime subtype Set objWMIDTHelper = CreateObject("WbemScripting.SWbemDateTime") objWMIDTHelper.Value = objEvent.TimeGenerated dtmEventTime = objWMIDTHelper.GetVarDate ' Given the DateTime of the last start up, we can calculate the number of seconds ' of uptime. intUptimeSeconds = DateDiff("s", dtmEventTime, dtmNow)Else ' If there were no matching events, the variable strUTCEventTime would be empty. ' This is a common scenario because event logs can be cleared or overwritten after a ' system was started. ' In this case, we'll get the uptime seconds from the performance counter designed ' for this purpose. Set objPerfOS = objWMIService.Get("Win32_PerfFormattedData_PerfOS_System=@") intUptimeSeconds = objPerfOS.SystemUptime ' Given the number of seconds of uptime, we can calculate the DateTime of the last ' startup. dtmEventTime = DateAdd("s",-(intUpTimeSeconds),dtmNow)End IfintTempValue = intUpTimeSecondsintSeconds = intTempValue Mod 60intTempValue = (intTempValue - intSeconds)/60intMinutes = intTempValue Mod 60intTempValue = (intTempValue - intMinutes)/60intHours = intTempValue Mod 24intTempValue = (intTempValue - intHours)/24intDays = intTempValuestrUptime = intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds"WScript.Echo "Host """ & strComputer & """ has been running since " & dtmEventTimewscript.Echo "Uptime is " & strUptimeWScript.Echo If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace. Ed Wilson and Craig Liebendorfer, Scripting Guys
Christian Bellée—Microsoft Premier Field Engineer, Sydney, Australia.
I teach Windows PowerShell and VBScript courses for Microsoft Premier, and I am an Active Directory support engineer. I have also recently developed a Windows PowerShell 2.0 for the IT Administrator Workshop PLUS course for Microsoft Premier.
Get-RecentStartupEvent.ps1
Script:#--------------------------------------------------------------------------------------------#requires -version 2.0param([array]$arrComputer)$arrEvent = @()$arrComputer | ForEach-Object ` { if (Test-Connection -ComputerName $_ -count 1 -Quiet) { "`n Collecting event information from $_" $event = Get-EventLog -LogName system -ComputerName $_ | Where-Object {$_.eventID -eq 6005} | Select-Object -First 1 $arrEvent += $event } else {"`n $_ is Unreachable"} } $arrEvent | Sort-Object -property TimeGenerated -desc | Format-Table -Property MachineName,TimeGenerated,EventID,Message –autosize#--------------------------------------------------------------------------------------------
Description
Collecting event log entries from remote machines is a very common administrative task, and Windows PowerShell 1.0 provided this functionality for local machines only, with the Get-Eventlog cmdlet. Although the underlying .NET Framework type, system.diagnostics.eventlog, used within the cmdlet has a constructor that allows us to specify the name of a remote machine, this wasn’t exposed in Windows PowerShell 1.0.
Windows PowerShell 2.0 enhanced the Get-Eventlog cmdlet to include the ability to specify a remote machine, making this script challenge a whole lot easier!
Essentially, the solution depends on the following simple Windows PowerShell pipeline command:
Get-EventLog -LogName system -ComputerName <computer name> | Where-Object {$_.eventID -eq 6005}
By employing the –ComputerName parameter of the Get-Eventlog cmdlet, we can target a remote machine. The resulting objects are then piped to a Where-Object cmdlet, which extracts all events of EventID 6005. Although, my script solution goes a bit further than this, allowing the user to pass an array of machine names into the script and sorting the entries from all the machines in descending date order.
The param() keyword allows a named argument to be specified on the command line and also casts the comma-separated string into an array, using the [array] type accelerator. Note the default machine name of “localhost” is assigned to the variable as a default.
param([array]$arrComputer=”localhost”)
Next, we create an empty array to store our events for format processing later on:
$arrEvent = @()
The $arrComputer variable containing the list of remote computer names is then piped to a ForEach-Object cmdlet, where an if() statement containing the Test-Connection cmdlet determines whether the computer can be contacted. If the test fails, script execution moves to the else {} part of the statement and displays that the machine is unreachable. Determining whether remote machines are available before running further operations against them is vital when writing scripts for remote machines.
if (Test-Connection -ComputerName $_ -count 1 -Quiet) {<eventlog query code removed>}else {"$_ is Unreachable"}
If the remote machine is responding, we can then query it for event log entries matching Event ID 6005 and select the most recent entry by using the Select-Object cmdlet and specifying the –First parameter as 1:
"Collecting event information from $_"$event = Get-EventLog -LogName system -ComputerName $_ | Where-Object {$_.eventID -eq 6005} | Select-Object -First 1
Next, the resulting event log entry is saved in the $event variable and added to the $arrEvents array we created at the beginning of the script:
$arrEvent += $event
Finally, we can sort the $arrEvents array of objects by their TimeGenerated property and format the result as an autosized table containing the MachineName, TimeGenerated, EventID, and Message properties of each object:
$arrEvent | Sort-Object -property TimeGenerated -desc | Format-Table -Property MachineName,TimeGenerated,EventID,Message –autosize
To run the script, just pass a comma-separated list of machine names after the script name on the command line.
PC C:\> ./Get-RecentStartupEvent.ps1 machine1,machine2,machine3
The following image shows the results of the script.
Beginner Event 2 (VBScript)
Stephanie Peters, Senior Premier Field EngineerMicrosoft Solutions Support
This Is How I Roll
Before going any further, I should explain that I am known to wander outside the lines a bit. Being given a task like this one, which seems fairly simple, typically sends my mind into a maelstrom of “So how about if I…” or “I should make sure to add…” and almost always “But wouldn’t it be better if I…?” In the spirit of the games, I will make sure to meet the requirements technically, but I hope you’ll indulge my selfish habits a bit as I embellish and insert my own specifications into the mix.
Specify a Computer
Before we can get any information, we have to specify a target host. There are a number of ways to allow the user to specify a computer name. My favorite is the command-line argument. Personally I like to use named arguments, so I started off by accepting a named argument:
If Wscript.Arguments.Named.Count > 0 Then
On Error Resume Next
strComputer = WScript.Arguments.Named("Computer")
On Error Goto 0
End If
Notice the On Error statements. I don’t typically like to run large sections of code under On Error Resume Next. I usually will identify a line where an error is likely to occur and only ignore errors for those specific lines. In this case we don’t need to check to see if an error occurred because our logic will test the value of strComputer later.
Since I’ve been using Windows PowerShell a lot the last few years, I’ve grown used to being able to provide an argument either as a named or a positional argument. I thought it might be nice to allow a positional argument (at the first position) if a named argument wasn’t specified:
If Not strComputer > "" Then
If WScript.Arguments.Count > 0 Then
strComputer = WScript.Arguments(0)
End if
Finally, if no arguments were supplied to the script, we will fall back to a default value of “.”, which represents the localhost:
strComputer = "."
Collecting Data
Because I’m going to potentially be doing time calculations at different points in my script, I decided to add a variable to hold the date time at a fixed point toward the beginning of the script, before any WMI values are returned:
dtmNow = Now
The next thing to do is to open a connection to the WMI Service on the target host. This is another time when an error could occur. The user could have specified an invalid computer name, there could be a permissions issue, or there could be a problem communicating with that computer over RPC. In this case, we need to handle any error by displaying a message to the user and aborting the script:
Set objWMIService = GetObject("winmgmts://" & strComputer _
& "/root/cimv2")
If Err.Number Then
WScript.Echo "ERROR: There was a problem connecting to " _
& the WMI service on """ & strComputer _
& """. The script is exiting."
WScript.Quit
When working with event logs via WMI, it’s best to scope your query so that it will return as little data as possible. Trust me on this one—I’ve had to restart too many WMI services over the years to steer you wrong on this. In this case, we’re looking for only events in the System Log whose source is “Eventlog” and whose Event ID is 6005. Further, we’re not returning all properties for these events because all we need to know is the TimeGenerated value.
strEventQuery = "SELECT TimeGenerated FROM Win32_NTLogEvent" _
& " WHERE LogFile='System' AND SourceName = 'Eventlog' AND " _
& " EventCode = '6005'"
Now we execute the query, which can also fail for a number of reasons (bogus query, WMI corruption on the target computer, more permissions issues, etc.). The thing to know about this is that most of the time it won’t fail on ExecQuery. It will usually fail when you try to loop through the collection it returns:
Set colEvents = objWMIService.ExecQuery(strEventQuery)
For Each objEvent in colEvents
WScript.Echo "ERROR: There was a problem executing " _
& "the query """ & strEventQuery _
& """ against host """ & strComputer _
strUTCEventTime = objEvent.TimeGenerated
Exit For
Next
Notice above that we stored the TimeGenerated property value of the first object in the collection as the variable strUTCEventTime, and then we exited the For Each loop. The WMI Event Log provider always returns the most recent events first, which is lucky for us. This means we can just read the first event and forget the rest.
Ensuring a Good Result
Now we potentially have a date time value representing the last time the computer started. I say “potentially” because it’s likely that there was no such event in the System Log. The log might have been cleared or overwritten since the last time the computer started. In that case, our query would have succeeded, but we would have had no events in our collection to loop through. Therefore, strUTCEventTime would be empty.
First, let’s deal with the situation where we do have a date time value. WMI always returns dates and times in a UTC string format that is not so easy to read and really isn’t easy to leverage in a calculation. To overcome those challenges, we need to convert the UTC string to a DateTime value, for which we use the SWbemDateTime helper object:
If strUTCEventTime > "" Then
Set objWMIDTHelper = CreateObject("WbemScripting.SWbemDateTime")
objWMIDTHelper.Value = objEvent.TimeGenerated
dtmEventTime = objWMIDTHelper.GetVarDate
While we’re at it, we can just go ahead and get the current uptime duration in seconds by comparing the date time we just got with the date time (dtmNow) we captured earlier in the script:
intUptimeSeconds = DateDiff("s", dtmEventTime, dtmNow)
Now, here’s where I go a bit rogue. If I wasn’t able to get a date time from an event, I know that I can still get the uptime information from a more reliable source. There is a performance counter (System\System Up Time) that is for this express purpose. In a perfect world with no fixed scenarios I would have just used this strategy and forgotten about event logs entirely:
Else
Set objPerfOS = _
objWMIService.Get("Win32_PerfFormattedData_PerfOS_System=@")
intUptimeSeconds = objPerfOS.SystemUptime
After we have the uptime in seconds, we can easily calculate the date time of the last start. You’ll generally find that this will be at least several seconds before the Eventlog 6005 event:
dtmEventTime = DateAdd("s",-(intUpTimeSeconds),dtmNow)
Making It Pretty
Rather than display the uptime duration in seconds, I decided to create a more readable string that shows the number of days, hours, minutes, and seconds that the computer has been up. At first I tried using a series of DateDiffs to break this out, but I found a nasty little bug that resulted in an extra minute and negative seconds. That’s when I decided to use Mod and the division operator to make this work.
To understand how I did this, you have to go back to all those long division problems you did in elementary school. Given a value of 64088 seconds, I need to first figure out how many of those seconds are not accounted for in minutes.
64088 sec/60 sec = 1068 minutes with a remainder of 8 seconds
1068 min/60 min = 17 hours with a remainder of 48 minutes
17 hours/24 hours = 0 days with a remainder of 17 hours
What we would want to see is: 0 days, 17 hours, 48 minutes, and 8 seconds. We can achieve this by using the Mod operator, which gives usethe remainder of a division operation, subtracting the remainder from the original value and dividing the result by the specific interval to get to the next calculation. When we have all of the interval values, we can concatenate them into a readable string:
intTempValue = intUpTimeSeconds
intSeconds = intTempValue Mod 60
intTempValue = (intTempValue - intSeconds)/60
intMinutes = intTempValue Mod 60
intTempValue = (intTempValue - intMinutes)/60
intHours = intTempValue Mod 24
intTempValue = (intTempValue - intHours)/24
intDays = intTempValue
strUptime = intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds"
All that’s left to do now is to echo the results to the user. I decided to go ahead and display both the date time of startup as well as the uptime duration.
WScript.Echo "Host """ & strComputer & """ has been running since " _
& dtmEventTime
wscript.Echo "Uptime is " & strUptime
Let’s See It Work!
Note from Microsoft Scripting Guy Ed Wilson: Hey, did you know you can use Windows PowerShell to run a VBScript? Way cool trick, Stephanie! I love it!
The complete script is shown here.
GetComputerUptime.vbs
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' GetComputerUptime.vbs '' Written by Stephanie Peters '' '' This script returns the current uptime as determined by the last time the '' event log service was started. '' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''Option ExplicitDim colEventsDim dtmEventTimeDim dtmNowDim intDaysDim intHoursDim intMinutesDim intSecondsDim intTempValueDim intUptimeSecondsDim objEventDim objPerfOSDim objWMIDTHelperDim objWMIServiceDim strComputerDim strEventQueryDim strUptimeDim strUTCEventTime' Provide an input mechanism for the computer name with the following order' of precendence: Named argument, positional argument, default value of ' localhost (.)If Wscript.Arguments.Named.Count > 0 Then On Error Resume Next strComputer = WScript.Arguments.Named("Computer") On Error Goto 0End IfIf Not strComputer > "" Then If WScript.Arguments.Count > 0 Then strComputer = WScript.Arguments(0) End IfEnd ifIf Not strComputer > "" Then strComputer = "."End If' Echo an empty line for looksWScript.Echo' Get the current time to use as a reference point for calculating uptimedtmNow = Now' Get a reference to the WMI service on the target computerOn Error Resume Next Set objWMIService = GetObject("winmgmts://" & strComputer & "/root/cimv2") If Err.Number Then WScript.Echo "ERROR: There was a problem connecting to the WMI service on """ _ & strComputer & """. The script is exiting." WScript.Quit End IfOn Error Goto 0' Construct the query stringstrEventQuery = "SELECT TimeGenerated FROM Win32_NTLogEvent" & _ " WHERE LogFile='System' AND SourceName = 'Eventlog' AND EventCode = '6005'"' Execute query and return resultsOn Error Resume Next Set colEvents = objWMIService.ExecQuery(strEventQuery) For Each objEvent in colEvents If Err.Number Then WScript.Echo "ERROR: There was a problem executing the query """ _ & strEventQuery & """ against host """ & strComputer _ & """. The script is exiting." WScript.Quit End If On Error Goto 0 ' We only need to store the TimeGenerated property of the most recent event. strUTCEventTime = objEvent.TimeGenerated ' The WMI Event Log Provider always returns the most recent events first. ' After we've read one item, we can skip the rest. Exit For NextOn Error Goto 0If strUTCEventTime > "" Then ' Since WMI returns a UTC date time string, we have to convert it to a DateTime subtype Set objWMIDTHelper = CreateObject("WbemScripting.SWbemDateTime") objWMIDTHelper.Value = objEvent.TimeGenerated dtmEventTime = objWMIDTHelper.GetVarDate ' Given the DateTime of the last start up, we can calculate the number of seconds ' of uptime. intUptimeSeconds = DateDiff("s", dtmEventTime, dtmNow)Else ' If there were no matching events, the variable strUTCEventTime would be empty. ' This is a common scenario because event logs can be cleared or overwritten after a ' system was started. ' In this case, we'll get the uptime seconds from the performance counter designed ' for this purpose. Set objPerfOS = objWMIService.Get("Win32_PerfFormattedData_PerfOS_System=@") intUptimeSeconds = objPerfOS.SystemUptime ' Given the number of seconds of uptime, we can calculate the DateTime of the last ' startup. dtmEventTime = DateAdd("s",-(intUpTimeSeconds),dtmNow)End IfintTempValue = intUpTimeSecondsintSeconds = intTempValue Mod 60intTempValue = (intTempValue - intSeconds)/60intMinutes = intTempValue Mod 60intTempValue = (intTempValue - intMinutes)/60intHours = intTempValue Mod 24intTempValue = (intTempValue - intHours)/24intDays = intTempValuestrUptime = intDays & " days, " & intHours & " hours, " & intMinutes & " minutes, " & intSeconds & " seconds"WScript.Echo "Host """ & strComputer & """ has been running since " & dtmEventTimewscript.Echo "Uptime is " & strUptimeWScript.Echo
If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson and Craig Liebendorfer, Scripting Guys