Scripting secrets for reviewing Windows event log data

Using custom views in Microsoft Event Viewer help you filter event logs, but they lack ideal flexibility. This script refines your search so you can quickly access the data you need.

The good part about operating system event logs is that they capture a lot of data that tells you the condition of the OS. Unfortunately, those logs mean that you are left with a ton of data to look through.

To ease your pain, you can create custom views from the Event Viewer GUI to filter by source. To do this, open Event Viewer, click to create a custom view, and pick the type of error you want to filter for. Then choose the date range and list all the computers by name that you want to include in the custom view.

Before you begin creating custom views, however, you should know that they are far from perfect. For starters, making a custom view involves a lot of clicking. It also requires knowing the names of every computer you want to monitor for different event IDs.

In addition, custom views are not very flexible. Let's say you want to collect licensing errors for every terminal server in a farm, and all members of that farm are in an organizational unit (OU). If the membership of the OU changes, then you'll need to manually update the custom view to make sure it reflects the change. This is one reason why it would be unwieldy to create custom views for large OUs.

Finally, ask yourself this: Do you really want to create a custom view every time you need to know how many unexpected shutdowns happened since last Tuesday? With no being the obvious answer to this question, you'll instead want to create a script that will make it easy for you to get only the data you want from the servers you want and to put it in a form that's easy to digest. We're going to do this with a script that will:

  • Accept input for the event ID to filter for, the number of records you want to return and the date from which you want to start looking.

  • Provide guidance for how to use the script.

  • Create a file named for the event ID it pertains to.

  • Only return error conditions.

  • Create a log file for each event ID and record the status for each server in the target OU.

It's actually a pretty simple script. Let's take a look.

Organizing input

The first stage of this script, where you lay out the data you need, is critical. In this script, we're assuming that some pieces of information are unlikely to change much, but others may change quite a bit. For example, you don't want to create one script for each event ID you want to monitor. Also, if you allow people to edit the script "willy-nilly" to feed in new event IDs or dates, sooner or later it's likely the script won't work because someone edited the wrong piece. It is far better to enable the script to take arguments for mutable data while hardcoding the pieces that will rarely, if ever, change.

The following example assumes that the event ID and date from which the script should start collecting data will change whenever you run the script, but other data -- such as the domain name and path where the log files are stored -- will not. Notice that we've used an example domain in this script; you can edit it as appropriate for your environment.

###### Configuration Area #########

$LogPath = "c:\Logs\"

$EventID = $args[0]

$LogFileName = "eventID$EventID.csv"

$OU = "ASH_TerminalServers"

$Domain = "alpineskihouse"

$DC = "com"

$LogName = "System"

$LogFile = "$LogPath$LogFileName"

$NumberEntries = "50"

$StartDate = $args[1]

##### End Configuration Area #######

Notice that the OU is hardcoded, but it doesn't have to be. You can make this script more or less flexible by adding support for additional arguments. Simply update the value of the declarations from, say, $OU= "ASH_TerminalServers" to $OU = $args[2]. The first argument will always be indexed as 0 and numbered up from there.

Just one caution: If you require more arguments, then you'll l need to check the array index to be sure that the right argument is supplying data to the proper variable. You'll also have to update the usage information.

In our current script, the usage information looks like this:

if ($args.Count -ne 2)

{

write-host "Type the Event ID you want to view and the first date for which you want to return records, like this: 1111 02/07/2009."

exit}

else

{

The first line is counting the number of items in the argument's array. If it's not equal to 2 (that's what –ne 2 means), then it spits back the usage information for the script and exits. If it is equal to 2, then the script proceeds as expected.

Finding the path

The second section fills in the path for the members of the OU for which you want data, gets the current date, and then uses this information to start querying servers. We've used variables below so that it's easier to change the OU or domain in the script, even if you hardcode them when declaring the variables.

$OU = [ADSI] "LDAP://OU=$OU, DC=$Domain, DC=$DC"

$CurrentDate = get-date

Collecting log data

Now the script is ready to walk through the OU and collect the event log data. First, it prepares to address every server in the OU specified in $OU. A child object to an OU is a user or computer.

foreach ($child in $ou.psbase.children)

{

Next, it writes the name of each server and the date to the log file at the location specified in the first section.

#$Servername = $child.name | out-File $LogFile -append

$Date = $CurrentDate | out-File $LogFile –append

Now we are getting the event log data. Notice that we're only getting the 50 most recent logs within the specified time window, as set with the variable $NumberEntries, which we declared earlier. You can change this number to fit your needs. We're also collecting only the logs generated after the start time and before the current time, and only from the System log. Both of these constraints keep your script from becoming bogged down, as the more records you collect the longer the script will take to run.

You can edit any of this, draw from another log or show another type of event. Additionally, you could control these settings from arguments to make the script more flexible. Just be sure to keep track of where, in the array of arguments, you're storing all of this data. That way, you can provide meaningful directions and you won't end up with errors from disorganized data.

$ServerName = $child.name

$ServerName

$logs=[System.Diagnostics.EventLog]::GetEventlogs($ServerName)

$SysLog = $logs |? {$_.log -eq $LogName}

$SysLogLimit = $SysLog.Entries[($SysLog.Entries.count -1)..($SysLog.Entries.count -$NumberEntries)]

$Filter=$SysLogLimit | where-object { ($_.TimeGenerated -gt $StartDate) -and ($_.TimeGenerated -lt $CurrentDate) -and ($_.EventID -eq $EventID) }| Format-Table -wrap -Autosize

$Filter | out-File $LogFile -append

}

}

The end result allows you to input the event ID for which you want data, how many log entries you want to search through and the date from which you want to start searching the event log; and either creates a new log file (if one didn't already exist) or appends to the one previously created for that event ID.

Here's the complete script:

###### Configuration Area #########

$LogPath = "c:\Logs\"

$EventID = $args[0]

$LogFileName = "eventID$EventID.csv"

$OU = "ASH_TerminalServers"

$Domain = "alpineskihouse"

$DC = "com"

$LogName = "System"

$LogFile = "$LogPath$LogFileName"

$NumberEntries = "50"

$StartDate = $args[1]

##### End Configuration Area #######

if ($args.Count -ne 2)

{

write-host "Type the Event ID you want to view and the first date for which you want to return records, like this: 1111 02/07/2009."

exit}

else

{

$OU = [ADSI] "LDAP://OU=$OU, DC=$Domain, DC=$DC"

$CurrentDate = get-date

foreach ($child in $ou.psbase.children)

{

$Servername = $child.name | out-File $LogFile -append

$Date = $CurrentDate | out-File $LogFile -append

$ServerName = $child.name

$ServerName

$logs=[System.Diagnostics.EventLog]::GetEventlogs($ServerName)

$SysLog = $logs |? {$_.log -eq $LogName}

$SysLogLimit = $SysLog.Entries[($SysLog.Entries.count -1)..($SysLog.Entries.count -$NumberEntries)]

$Filter=$SysLogLimit | where-object { ($_.TimeGenerated -gt $StartDate) -and ($_.TimeGenerated -lt $CurrentDate) -and ($_.EventID -eq $EventID) }| Format-Table -wrap -Autosize

$Filter | out-File $LogFile -append

}

}

Miss a column? Check out the Scripting School archive.

ABOUT THE AUTHORS
Christa Anderson, a former Terminal Services MVP, is a program manager on the Terminal Services team at Microsoft and author of the forthcoming Windows Terminal Services Resource Kit from Microsoft Press. She is an internationally known authority on scripting, the author of Windows Terminal Services, The Definitive Guide to MetaFrame XP, and co-author of the book Mastering Windows 2003 Server.

Kristin Griffin is an independent consultant and author with more than 10 years of experience in creating and maintaining Microsoft-centric networks using scripting to automate tasks and reduce errors from manually performed system administration. She recently co-authored Windows Server 2008 Terminal Services Resource Kit (Microsoft Press, 2008) and is a contributor to Mark Minasi's Mastering Windows Server 2008: Enterprise Technologies.

This was first published in March 2009

Dig deeper on Microsoft Windows Data Backup and Protection

Pro+

Features

Enjoy the benefits of Pro+ membership, learn more and join.

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

SearchServerVirtualization

SearchCloudComputing

SearchExchange

SearchSQLServer

SearchWinIT

SearchEnterpriseDesktop

SearchVirtualDesktop

Close