Expert Solutions: Beginner Event 2 of the 2010 Scripting Games
(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.0
param(
[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 Engineer
Microsoft 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 “
0 comments