Two weeks ago I was helping out at my old company (it is part of my old contract with them). When one of my friends there told me they just can’t look at the Event Viewer anymore. Why? Well it seems another friend got a little overboard in logging stuff and the Event Viewer turned from logging Errors and important events to “real time logging” (their words). So being the helpful guy that I am I told them about PowerShell. I even pointed them to some blogs in the web.
Today I decided to follow my own advice and check out PowerShell (and maybe write something they could use). So I went through some basic tutorials (1, 2, 3 reading briefly, 4, 5, 6 (can be skipped), 7, 8, 9, there are more tutorials but they are not needed for this simple task). When following tutorials I sometimes like to use my own example, that way I can be sure I am understanding things like a human and not like a monkey (“Monkey see, monkey do”).
On the second tutorial I already knew I had to use ‘get-eventlog’ or the newer ‘get-winevent’ command (by using ‘get-command -Verb Get’ and looking at the options). By looking at the help for these commands (by using ‘get-help get-eventlog’) I found out that get-winevent needs newer operating systems (minimum Windows Vista) which are not available to my friends…
So what do I want my script to do?
1. Display only the errors from Event Viewer’s Application log (and filter by the application name).
- Looking at the examples was enough for this:
- get-eventlog -logname System -EntryType Error
- Wanting something more advanced (I learned that you shouldn’t use TimeWritten in the where since it is a string object, see sorting below):
- Get-Eventlog Application -After ([datetime]::Parse("2012-02-08 17:00")) | where {($_.entryType -eq "Error")}
- This way you can “add” warnings not just errors:
- Get-Eventlog Application -After ([datetime]::Parse("2012-02-08 17:00")) | where {(($_.entryType -eq "Error") -or ($_.entryType -eq "Warning"))}
- Adding sorting to view the errors from old to new, added difficulty because TimeWritten is a string with a problematic format (M/d/yyyy h:m:s AM/PM) that doesn’t work with string comparison:
- Get-Eventlog Application -After ([datetime]::Parse("2012-02-08 17:00")) | where {($_.entryType -eq "Error")} | select EntryType,InstanceId,Message,Category,Source, @{Name="TimeWritten";Expression={ ([datetime]::Parse($_.TimeWritten)).ToString("yyyy-MM-dd HH:mm:ss") }} | sort -Property TimeWritten
- Wanting to actually see the message (since PowerShell by default limits the column size):
- Get-Eventlog Application -After ([datetime]::Parse("2012-02-08 17:00")) | where {($_.entryType -eq "Error")} | select EntryType,InstanceId,Message,Category,Source, @{Name="TimeWritten";Expression={ ([datetime]::Parse($_.TimeWritten)).ToString("yyyy-MM-dd HH:mm:ss") }} | Format-list -Property EntryType, Source, Message, TimeWritten
2. Export the errors from 1 to a csv file (so that the errors will become less real time and more all time).
As seen in the bottom of tutorial 5:
- Get-Eventlog Application -After ([datetime]::Parse("2012-02-08 17:00")) | where {($_.entryType -eq "Error")} | select EntryType,InstanceId,Message,Category,Source, @{Name="TimeWritten";Expression={ ([datetime]::Parse($_.TimeWritten)).ToString("yyyy-MM-dd HH:mm:ss") }} | Export-CSV "EventErrors.csv"
3. Do an infinite loop that does 2 on new errors, so that errors in the night shift will be stored.
That was a bit more complex simply because I wanted to append rows to the CSV.
- $currentTime = get-date
- $currentDate = get-date -format "yyyy_MM_dd"
- if(test-path "EventErrors_$currentDate.csv")
- {
- $record = Import-CSV "EventErrors_$currentDate.csv" | sort -Property TimeWritten | Select-Object -Last 1 | select TimeWritten
- $currentTime = [datetime]::Parse($record.TimeWritten)
- }
- while($true)
- {
- $currentDate = get-date -format "yyyy_MM_dd"
- $data = Get-Eventlog Application -After $currentTime | where {($_.entryType -eq "Error")} | select EntryType,InstanceId,Message,Category,Source, @{Name="TimeWritten";Expression={ ([datetime]::Parse($_.TimeWritten)).ToString("yyyy-MM-dd HH:mm:ss") }}
- $currentTime = get-date
- if($data -ne $NULL)
- {
- if(test-path "EventErrors_$currentDate.csv")
- {
- "File EventErrors_$currentDate.csv exists, importing data."
- $data = (Import-CSV "EventErrors_$currentDate.csv") + $data | sort -Property TimeWritten
- }
- $data | Export-CSV "EventErrors_$currentDate.csv"
- }
- Start-Sleep -s 20
- "Ping $currentTime"
- }
Notes:
- The script won’t work if the file is opened in Excel (but the csv can be copied or opened with notepad).
- You can adjust the sleep time, I choose 20 seconds.
- Since it’s an infinite loop it can only be exited by Ctrl+C
Output in Excel:
By the way an advanced script to this could be outputting the errors to the DB… But that can be considered a version in some companies.
Resources:
PowerShell Tutorial 1: Configuring the PowerShell Console
PowerShell Tutorial 2: PowerShell Commands – Cmdlet
PowerShell Tutorial 3: PowerShell Aliases
PowerShell Tutorial 4: Using Cmdlet Options
PowerShell Tutorial 5: Windows PowerShell Providers
PowerShell Tutorial 7: Accumulate, Recall, and Modify Data
PowerShell Tutorial 8: Conditional Logic (if, elseif, else, and switch)