Expert Solutions: Advanced Event 2 of the 2010 Scripting Games

Bookmark and Share

 

(Note: These solutions were written for Advanced Event 2 of the 2010 Scripting Games.)

 

Advanced Event 2 (Windows PowerShell and VBScript)

Image of Richard Siddaway

 

Richard Siddaway, Windows PowerShell MVP


Richard Siddaway is technical architect for Serco in the United Kingdom who works on transformation projects in the local government and commercial arena. With more than 20 years’ experience in various aspects of information technology, Richard specializes in the Microsoft environment at an architectural level, especially around Active Directory (AD), Exchange, SQL Server, and infrastructure optimization.


Richard is always looking for the opportunity to automate a process, preferably with Windows PowerShell. Richard founded and currently leads the UK Windows PowerShell User Group. Microsoft has recognized his technical expertise and community activities by presenting him with a Microsoft Most Valued Professional Award. Richard has presented to the Directory Experts Conference, at various events at Microsoft in the UK and Europe, and for other UK User Groups. Richard has a number of articles and technical publications to his credit, and is currently finalizing his first print book. The subject? Windows PowerShell of course.


Blog
: http://msmvps.com/blogs/RichardSiddaway/Default.aspx

Book details: http://www.manning.com/siddaway


————-


I will be providing a Windows PowerShell version and a VBScript version of this solution. I will also compare and contrast the two versions.

 


The Problem


We need to read the system event log of a machine to find when it started.  This is approximated by the time the event log service started. From the information supplied this is contained in Event ID 6005.


After that we need to extend our script to:

  • Accept alternative credentials when running against a remote machine.

  • Query any of the traditional event logs.

  • Query for any event ID.

  • Permit the user to specify a specific date or date range from which the events are to be returned.

  • Query for events based upon a wild card character pattern match in the event description.

  • Include command-line help

  • Include error handling


Okay, that should keep us busy. We’ll start with the Windows PowerShell version.


Note:
I am using Windows PowerShell 2.0 for this explanation. All development will be performed in the Windows PowerShell ISE.

 

Windows PowerShell


In Windows PowerShell 1.0, we had Get-EventLog. This is still available in Windows PowerShell 2.0 with enhancements. We query the System event log on the given computer and then filter using Where-Object on Event ID 6005. Get-EventLog can accept an Instance ID but not an Event ID. The IDs are related but not identical. Because we only have the Event ID, we have to use Where-Object. This is shown here:


#Requires -version 2.0
$evt = Get-EventLog -LogName System -ComputerName rslaptop01 |
Where {$_.EventId -eq 6005} |
Select -First 1

Write-Host “Time Started: $($evt.TimeGenerated.DateTime)”


I then use the Select-Object cmdlet to get the first event, which will contain the time of last startup. The Write-Host cmdlet is used to write out the startup time.


We also have the option of using WMI, as shown here:

 

$evt = Get-WmiObject -Class Win32_NTLogEvent -Filter “LogFile = ‘System’ AND EventCode=6005 ” -ComputerName rslaptop01 |  
select -First 1

Write-Host “Time Started: $($evt.ConvertToDatetime($evt.TimeGenerated))”


The only issue with this is converting the date to a readable format, but we have the ConvertToDatetime() method as a standard way of achieving this.


Windows PowerShell 2.0 introduced the Get-WinEvent cmdlet that can read traditional and newer-style logs introduced in Windows Vista and later:


#Requires -version 2.0
$evt = Get-WinEvent -FilterHashtable @{LogName=’System’; ID=6005} `
 -MaxEvents 1 -ComputerName rslaptop01

Write-Host “Time Started: $($evt.TimeCreated.DateTime)”


In this version we supply the event log name and the event ID in a hash table. We can filter directly and restrict ourselves to the first event found. Write-Host is then used to display the answer. This runs much faster than using Get-EventLog because the filtering all happens in the cmdlet rather than on the pipeline. A best practice is to always restrict the amount of data you are working with as soon as possible.


In both the scripts, we display the DateTime property. This is to produce a display that removes any ambiguity about the date being in dd/mm/yyyyy format or mm/dd/yyyy format.


The filter hash table can work with a number of parameters including:

·         LogName=<String[]>

·         ProviderName=<String[]>

·         Path=<String[]>

·         Keywords=<Long[]>

·         ID=<Int32[]>

·         Level=<Int32[]>

·         StartTime=<DateTime>

·         EndTime=<DataTime>

·         UserID=<SID>

·         Data=<String[]>

·         *=<String[]>



For the rest of the example, I will work with Get-WinEvent. The following script is what I came up with.


Event2a.ps1


#Requires -version 2.0
function Get-Log {
<#
.SYNOPSIS
  Retrieves events from the event logs on local or remote machines.
.DESCRIPTION
  The function will retrieve events from classic and new style logs
  on local or remote machines. Alternate credentials can be used
  where the current user doesn’t have permissions on the remote
  machine.
.PARAMETER log
  Name of the event log. Defaults to system log. 
.PARAMETER id
  Specific event to retrieve.
.PARAMETER computer
  Computer name. Defaults to localhost.
.PARAMETER date
  Date for which events are to be retrieved.
.PARAMETER firstdate
  Starting date to retrieve events.
.PARAMETER lastdate
  Date to end retrieving events
.EXAMPLE
 Get-Log -date “22 March 2010”
 Gets events from the system log on the local machine
 that occurred on a given day.
.EXAMPLE
 Get-Log -firstdate “20 March 2010”
 Gets events from the system log on the local machine
 that occurred after a given day.
.EXAMPLE 
 Get-Log -lastdate “22 March 2010”
 Gets events from the system log on the local machine
 that occurred before a given day.
.EXAMPLE 
 Get-Log -firstdate “20 March 2010” -lastdate “22 March 2010”
 Gets events between the 20th and 22nd of March
.EXAMPLE 
 Get-Log -log Application -firstdate “20 March 2010” -lastdate “22 March 2010”
#>
param (
    [CmdletBinding()]

    [string]$log = “System”,
    [int]$id,
    [string]$computer = ‘localhost’,
    [datetime]$date,
    [datetime]$firstdate,
    [datetime]$lastdate,
    [switch]$cred,
    [regex]$pattern
)
    if ($date -and ($firstdate -or $lastdate)){Throw “Cannot use date together with firsdate or lastdate”}
   
    $fht = @{LogName = $log}

    if ($id){$fht += @{ID = $id}}

    if ($date){
       $fht += @{StartTime = $date;
       EndTime = $date.AddHours(23).AddMinutes(59).AddSeconds(59)}
    }

    if ($firstdate -and !$lastdate){
       $fht += @{StartTime = $firstdate}
    }

    if (!$firstdate -and $lastdate){
       $fht += @{EndTime = $lastdate}
    }

    if ($firstdate -and $lastdate){
       if ($lastdate -lt $firstdate){Throw “Last date before first date”}
       else{ $fht += @{StartTime = $firstdate; EndTime = $lastdate} }
    }

    if ($cred){
        $crd = Get-Credential
        $s = {Get-WinEvent -FilterHashtable $fht -ComputerName $computer -Credential $crd}
    }
    else {
        $s = {Get-WinEvent -FilterHashtable $fht -ComputerName $computer}
    }
    if ($pattern){
        Invoke-Command $s | Where {$_.Message -match $pattern}
    }
    else {
        Invoke-Command $s
    }
}   


I created the answer as a function because I decided I would eventually create a module of all of the answers. I can then load all of them in one command. Alternatively, you could put the function in a script and dot source it.


. ./event2a.ps1


This loads the function and we can use it as Get-Log, which will return the contents of the system log on the local machine. The script starts with:


#Requires -version 2.0


This checks to see if we are running on Windows PowerShell 2.0 and stops the script if not. The next set of lines between <# and #> supplies the comment-based help. This is a new feature for functions in Windows PowerShell 2.0.


Typing


Get-Help Get-Log


provides exactly the same sort of help that we get with the cmdlets. We have the same options for getting more detailed help. For example


Get-Help Get-Log –full


displays all of the help information.


The parameters are defined using a param statement. I have supplied a data type for all of the parameters and provided default values where applicable. This catches a number of errors; for example, an attempt to supply an Event ID of “swamp” will be rejected and cause an error.


A test is performed to see if the date parameter has been combined with firstdate and/or lastdate. If so, an error is thrown. This could also be achieved using parameter sets if required.


The hash table $fht is built up starting with the log name. One addition to the script would be to test to see if that log existed on the target machine. At the moment, we assume it does. If an Event ID is supplied, we add that to the hash table. The default is that records for all event codes are returned.


The next task is to set the start and end dates for the period we want to examine the entries. There are a number of possibilities for this so we have a number of tests.


A final job is to test if we need alternative credentials. If so, we use the Get-Credential cmdlet to get them.


A script block is created with the required syntax for Get-WinEvent including the computer name. We do one last check to see if we are filtering on a regular expression (man, I still hate them), and we use Invoke-Command to run our script block. If we have a regular expression filter, we use that in the Where-Object cmdlet as shown.


Some examples of using the script:


To display all entries in the system log on the local machine for a given date:


Get-Log  -date “22 March 2010”


To display all entries in the system log on the local machine that occurred after the given date:


Get-Log  -firstdate “22 March 2010”


To display all entries in the system log on the local machine that occurred before the given date:


Get-Log   -lastdate “”22 March 2010”


To display all entries in the system log on the local machine that occurred March 20–22:


Get-Log   -firstdate “20 March 2010” -lastdate “22 March 2010”


If the dates are the wrong way around, an error is thrown:


Get-Log   -firstdate “22 March 2010” -lastdate “20 March 2010”


Last date before first date:


At C:scriptsScripting Games 2010event2a.ps1:26 char:39
+    if ($lastdate -lt $firstdate){Throw <<<<  “Last date before first date”}
    + CategoryInfo          : OperationStopped: (Last date before first date:String) [], RuntimeException
    + FullyQualifiedErrorId : Last date before first date

 


To display all entries in the application log on the local machine that occurred March 20–22:


Get-Log -log Application -firstdate “20 March 2010” -lastdate “22 March 2010”


The really nice thing about doing this as a function is that I can use it on the pipeline, as the following example shows.

Image of using function on the pipeline

 

I was also asked to have a look at the VBScript option. I used to use VBScript a lot until I discovered Windows PowerShell. I thought it would be an interesting challenge to go back and try the same task in VBScript.


We are more limited in our options with VBScript in that we have to use WMI to access the logs. The WMI class we need is Win32_NTLogEvent as shown here:



strComputer = “.”
Set objWMIService = GetObject(“winmgmts:” _
      & “{impersonationLevel=impersonate}!\” _
      & strComputer & “rootcimv2”)
     
Set colEvents = objWMIService.ExecQuery _
      (“SELECT * FROM Win32_NTLogEvent WHERE Logfile = ‘System’ AND “  _
      &  “EventCode = ‘6005’”)

For Each objEvent in colEvents
      Wscript.Echo “Computer ” & objEvent.ComputerName _
      & ” Started at ” &  WmiDate(objEvent.TimeWritten)
      WScript.Quit
Next
Function WmiDate(startdate)
      WmiDate = Mid(startdate, 7, 2) & “/”  _
         & Mid(startdate, 5, 2) & “/” &  Left (startdate, 4) & ” “   _
         & Mid(startdate, 9, 2) & “:” _
         & Mid(startdate, 11, 2)  & “:” _
         & Mid(startdate, 13, 2)
     
End Function


This is the direct equivalent of the Windows PowerShell script, which is shown again here:


 
$evt = Get-WmiObject -Class Win32_NTLogEvent -Filter “LogFile = ‘System’ AND EventCode=6005 ” -ComputerName rslaptop01 |  
select -First 1

Write-Host “Time Started: $($evt.ConvertToDatetime($evt.TimeGenerated))”


The computer name is set to “.” to represent the local machine. We then create an object for WMI. We have to create a full WQL query, which we run to create a collection of objec