Summary: Microsoft MVP, Tome Tanasovski, provides expert commentary for 2012 Scripting Games Advanced Event 7.
Microsoft Scripting Guy, Ed Wilson, is here. Tome Tanasovski is the expert commentator for Advanced Event 7.
Tome is a Windows engineer for a market-leading global financial services firm in New York City. He is the founder and leader of the New York City PowerShell User group, a blogger, and a regular contributor to the Official Scripting Guys Forum. In 2011, he became a cofounder of the NYC Techstravaganza, coauthored the Windows PowerShell Bible, and received the title of Honorary Scripting Guy from the Hey, Scripting Guy! Blog. Tome has also received the MVP award in Windows PowerShell from Microsoft for the last two years.
A real scripting language can solve a problem in one line of code. That happens to be the case for this problem. Even after you’ve tackled each of the design points, you can still run the script as one line. So let’s start with the simple line that satisfies the problem and a couple of the design points. It uses Get-WinEvent to ensure that you are reading all of the logs that are available in Windows 7:
Get-WinEvent -ListLog * |?{$_.IsEnabled} |%{$_|Get-WinEvent -MaxEvents 1 -ErrorAction SilentlyContinue}
I should note that the question mark (?) and the percent sign (%) are aliases for Where-Object and Foreach-Object respectively. They are two of the most useful aliases when working in Windows PowerShell. However, in a contest like this, it’s best to avoid these aliases. Unlike Select, Where, ForEach, and Sort, the question mark and percent sign are frowned upon by some judges. My final solution does not use these shortcuts, but for the purpose of a one-liner they are always acceptable.
The first thing you may notice when running the previous script is that it may still give you errors—even though you set the ErrorAction parameter to SilentlyContinue. Get-WinEvent will choke on certain logs, if you are not running Windows PowerShell with elevated privileges through UAC (run as Adminstrator). Although it’s not a design requirement, I added the following to my script to ensure that an operator is running my script with enough privilege to ensure that they can hit every log:
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object System.Security.Principal.WindowsPrincipal($id)
if ($principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
# Script
} else {
Write-Error "PowerShell is not running elevated. This script requires elevated Administrator privileges in order to run"
}
One of the design points for this event is, “Your code should query hidden logs if they are enabled and they contain at least one entry.”
If you’re not familiar with the hidden analytic and debug logs that exist in Windows 7, you can find them in Event Viewer by clicking View, and then selecting Show Analytic and Debug Logs.
When these logs are visible, they can be enabled by right-clicking a log and viewing the properties of the log.
To view the logs that are available on a system through Windows PowerShell, regardless of whether they are hidden, you must use the Force parameter when using Get-WinEvent with the ListLog parameter.
Get-WinEvent -ListLog * -Force
If you only want to view enabled logs, you need to filter the logs that have the IsEnabled property set to True.
Get-WinEvent -ListLog * -Force |where {$_.IsEnabled}
To check that the log has at least one record, you can inspect the RecordCount property. The problem with RecordCount is that it won’t display the number of records for the hidden logs that are analytic or debug logs. Due to the nature of how these logs are written, you do not have the same type of insight into the log as you do with the others.
For the same reason, you must use the Oldest parameter switch with Get-WinEvent to view these logs. This switch returns the logs in reverse order. The side effect is that if you use the MaxEvents parameter of Get-WinEvent, it will return the first event instead of the last one. This leads to the following line of code that must be used with analytic and debug logs to get the most recent event:
'LogName' |Get-WinEvent -Oldest -ErrorVariable e -ErrorAction SilentlyContinue |select -First 1
You’ll note that I’m using SilentlyContinue with ErrorVariable here. Unfortunately, Get-WinEvent does not throw an exception, so you cannot use a Try/Catch block to capture an error. Setting the error to a variable and then checking that variable is the best way to ensure that you are handling that error when a Try/Catch will not work. The reason that I chose to handle an error at this point in the script is because I know that I may receive errors because an enabled analytic or debug log with no events in it will return an error.
Finally, I will leave you with the one-line version of my solution. Mind you, it does not check for Administrator because that was not an original requirement. It also does not send any verbose messages about logs that exist with no events. Again, this was not the requirement. The following line of code rigidly matches all of the design points. It’s not something I would have submitted. It’s sure to make some of the other judges cringe due to its use of aliases. However, if I read this line of code, I would have graded it as perfect:
Get-WinEvent -ListLog * -Force |? {$_.IsEnabled } |% {if ($_.LogType -match '^(Analytical|Debug)$') { $_ |Get-WinEvent -Oldest -ea SilentlyContinue|select -First 1 } elseif ($_.RecordCount) {$_ |Get-WinEvent -MaxEvents 1 }} |sort TimeCreated -Des |Format-List TimeCreated, LogName, Id, Message
~Tome
2012 Scripting Games Guest Commentator Week Part 2 will continue tomorrow when we will present the scenario for Event 8.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy