PowerShell: Get-WinEvent vs. Get-EventLog

Mark Berry April 1, 2011

I’ve been working to write a flexible PowerShell script to retrieve and email warning and error events from computers in a small network. The computers variously run XP, Windows 7, Server 2003, SBS 2008, and Server 2008 R2. I wanted to include the new Applications and Services logs for Vista/2008 and beyond, so for those OSs, I use the new Get-WinEvent cmdlet that is part of PowerShell 2.0.

Resources

I found three primary Microsoft articles on Get-WinEvent:

Differences from Get-EventLog

The good new is that Get-WinEvent does allow retrieving events from the Applications and Services logs. Unlike Get-EventLog, which returns System.Diagnostics.EventLogEntry, Get-WinEvent returns System.Diagnostics.Eventing.Reader.EventLogRecord. There are significant differences in the properties:

  • Source becomes ProviderName
  • EntryType becomes LevelDisplayName
  • Category becomes TaskDisplayName
  • ReplacementStrings are only visible if you save the event as XML and retrieve the Data:
    $eventxml = $event.ToXML()
    $eventxml.Event.EventData.Data

The Performance Promise

Besides accessing Application event logs, the big advantage of Get-WinEvent is supposed to be performance, especially when retrieving logs from remote computers. If you use one of the Filter parameters, the event engine does the filtering before passing the events (potentially across the network) back to PowerShell.

This is where things really got interesting.

FilterHashtable Fails on Server 2008

Arguably the simplest of the Filter parameters is FilterHashtable. The Scripting Guy article has extensive examples on how to use this. And it works fine on Windows 7 and Server 2008 R2. The problem is, it doesn’t work at all on Server 2008:  all you get is “The parameter is incorrect”. Reportedly it fails on Vista as well.

This thread led me to this PowerShell bug report, which confirms that this is a known bug in “Crimson” (the new event log technology). On April 10, 2009, the PowerShell team said they were following up with the “owners” of Crimson; the bug was later closed as External.

I only wish all the Microsoft articles had included a big highlighted box like this article, warning me not to waste my time on FilterHashtable:

Note  The Get-WinEvent -FilterHashtable parameter works only on Windows 7/2008 R2. On Windows Vista/2008, it generates an error: “The parameter is incorrect.”

Fortunately, there is a workaround:  FilterXML does work on Server 2008; it’s just more of a pain to build up the queries using examples from the Event Viewer’s XML tab.

Filtering by Keyword Slower than Get-EventLog

The main thing I want my script to do is report all the warning and error events in all logs from the past 24 hours. This works pretty well in most logs, and Get-WinEvent beats Get-EventLog nicely, especially over the LAN. For example, here are the elapsed times needed to retrieve 13 events from an SBS 2008 Application log, which is 20MB and contains about 53,000 events (time in h:mm:ss):

Application log:
Warnings and Errors
Time Elapsed – Local Time Elapsed – Remote
Get-EventLog 0:00:16 0:05:34
Get-WinEvent 0:00:05 0:00:27

The Security Log

You may have already noticed that in Vista/Server 2008 and beyond, the Security log considers security failures to be Information events. Actually they are recorded with Event Level 0, LogAlways, which means that “no filtering on the level is done during the event publishing.” (See the StandardEventLevel enumeration.)

This means that even in Event Viewer, if you want to find the security failures, you have to filter by Keyword, not Event Level:

Get-WinEvent 1

With the old PowerShell Get-EventLog cmdlet, to find Audit Failure events, even on Vista and beyond, you use

Where { (“FailureAudit” -eq $_.EntryType) }

With the new PowerShell Get-WinEvent cmdlet, to filter for Audit Failure events, you include this in your FilterXML string:

band(Keywords,4503599627370496)

They both work. The problem is that Get-WinEvent with the “Audit Failure” filter is much slower than Get-EventLog. For example, to retrieve about 24,000 events from an SBS 2008 Security log, which is 128MB and contains about 280,000 events (time in h:mm:ss):

Security log:
Audit Failures
Time Elapsed – Local Time Elapsed – Remote
Get-EventLog 0:01:57 0:30:44
Get-WinEvent 0:09:46 1:04:16

That’s right:  working remotely, Get-WinEvent takes over an hour to retrieve the Audit Failure events, twice as long as Get-EventLog. Running locally, Get-WinEvent takes almost five times as long as the same query run with Get-EventLog.

No doubt the large number of events in the Security log compounds the problem, but that’s only about 27 hours worth of data. By the way, I also ran the tests on a Server 2008 R2 machine. There, Get-EventLog is still faster, but the margin wasn’t as great. However the R2 machine only has about 16,000 events, so it’s not a totally fair comparison.

Conclusions

  1. If you’re writing a PowerShell script to handle events from Vista or Server 2008, avoid the Get-WinEvent –FilterHashtable parameter; use –FilterXML instead.
  2. Even on Vista and beyond, consider using Get-EventLog if you need to filter the Security log for Audit Failures.

The Test Script

If you want to try these tests yourself, here’s the script I used:

EventTest.zip

If you do run the test, leave a comment with your results.



11 Comments

  1. VasekB   |  March 15, 2012 at 3:34 am

    Perfect comaparison. What about “Get-WmiObject Win32_NTLogEvent” ?

  2. VasekB   |  March 15, 2012 at 4:17 am

    Measure-Command -Expression {Get-WmiObject Win32_NTLogEvent -Filter “Logfile=’System’ AND EventCode = ’7036′”}
    Measure-Command -Expression {Get-WmiObject Win32_NTLogEvent -Filter “Logfile=’System’”}
    TotalSeconds : 5,1125578
    TotalSeconds : 5,1057069
    TotalSecondsAVG : 5,10913235

    Measure-Command -Expression {Get-WinEvent -LogName System | where {$_.id -eq 7036}}
    Measure-Command -Expression {Get-WinEvent -LogName System}
    TotalSeconds : 5,2320771
    TotalSeconds : 4,1938785
    TotalSecondsAVG : 4,7129778

    Measure-Command -Expression {Get-EventLog -LogName System | where {$_.eventID -eq 7036}}
    Measure-Command -Expression {Get-EventLog -LogName System}
    TotalSeconds : 3,8932569
    TotalSeconds : 2,3241086
    TotalSecondsAVG : 3,10868275

    Measure-Command -Expression {Get-EventLog -LogName System -InstanceId 1073748860}
    Measure-Command -Expression {Get-EventLog -LogName System}
    TotalSeconds : 2,1006783
    TotalSeconds : 2,3756022
    TotalSecondsAVG : 2,23814025

  3. Mark Berry   |  March 15, 2012 at 10:26 am

    VasekB – thanks for adding that info!

  4. Robin   |  March 16, 2012 at 7:08 am

    Hi Mark,

    Are you aware that running the query with -FilterHashTable on a 2008 R2 / Windows 7 machine against a 2008 machine works? At least it does for me when querying remote application and system logs on Server 2008 x86 and x64. I haven’t tried querying the security log though.

    Cheers,

    Robin

  5. Mark Berry   |  March 16, 2012 at 9:55 am

    Robin – good to know, thanks. Kind of makes sense. I wanted my script to run on SBS 2008 (based on Windows Server 2008) so I had to use -FilterXML.

  6. VasekB   |  March 16, 2012 at 1:25 pm

    I wrote script for collecting EventLogs from servers to one DB – I had a 3 weeks of fun with them ;o)
    Every of thats command have some kind of problems ;o(

    * Get-WmiObject Win32_NTLogEvent
    = Properties: Category; CategoryString; EventCode; EventIdentifier; TypeEvent; InsertionStrings; LogFile; Message; RecordNumber; SourceName; TimeGenerated; TimeWritten; Type; UserName
    + complette Message
    – TimeGenerated – special WMI-DateTime format
    – not easy to take a List of all EventLogs
    – be aware of using RAM by WMI
    – the slowest

    * Get-WinEvent
    = Properties: TimeCreated; ProviderName; Id; Message
    – incomplette Message : The description for Event ID ” in Source ” cannot be found…..
    – no List of all EventLogs
    – very simple filters and HashFilter not working in Win2008

    * Get-EventLog
    = Properties: Index; EntryType; InstanceId; Message; Category; CategoryNumber; ReplacementStrings; Source; TimeGenerated; TimeWritten; UserName
    – incomplette Message : The description for Event ID ” in Source ” cannot be found…..
    + After & Before for DateTime filter
    + List of ALL EventLogs including “Applications adn Services logs” [Get-EventLog -list]

    … so.. script to collect only new Events not yet in DB:

    ForEach ($EventLog in $(Get-EventLog -list)) {
      ForEach ($Event in Get-EventLog -LogName $EventLogName -After $LastEventDateInDB -Before $ScriptStarted){
        if ($EventMessage -match 'The description for Event ID .* in Source .* cannot be found'){
          $WMIWin32NTLogEvent = Get-WmiObject Win32_NTLogEvent -Filter "Logfile='$EventLogName' AND RecordNumber = '$EventIndex'"
          $EventMessage = $WMIWin32NTLogEvent.Message
      }
    }

  7. Mark Berry   |  March 16, 2012 at 1:37 pm

    VasekB, when the description is not found, I pull the message from other event properties.

    For Get-WinEvent:

    # Custom handling of $_.Message
    if (    ($_.Message -eq $null) `
        -or ($_.Message -like "*Either the component that raises this event is not installed on your local computer or the installation is corrupted.*") ) {
      # ReplacementStrings are not exposed in this object, so convert to XML and get Data
      $EventXML = $_.ToXML()
      $Message = $EventXML.Event.EventData.Data
    }
    else {
      $Message = $_.Message
    }

    For Get-EventLog (maybe this one should check “-eq $null” as well):

    # Custom handling of $_.Message
    if ($_.Message -like "*The local computer may not have the necessary registry information or message DLL files*") {
      # A custom message is stored in the ReplacementStrings.  If there are multiple
      # ReplacementStings, PowerShell treats as them an array, so convert to a single string
      $Message = $_.ReplacementStrings | Out-String -Width 8192
    }
    else {
      $Message = $_.Message
    }

    Does that help?

  8. VasekB   |  March 16, 2012 at 2:43 pm

    Mark, nice idea, I’ll change in my script .. now it’s 2 times getting Message – it’s slow.
    I have 2 scripts and I’m starting them in one moment.
    1) getting Events from last time I ran script (-After $LastEventDateInDB -Before $ScriptStarted)
    script ends after putting all events to DB
    2) waitting for new Events using
    $Scope = New-Object System.Management.ManagementScope(“\\.\root\cimV2″)
    $EventQuery = “TargetInstance ISA ‘Win32_NTLogEvent’” + $EventLogsFilter
    $EventQueryWQL = New-Object System.Management.WQLEventQuery (“__InstanceCreationEvent”,$TimeSpan, $EventQuery )

  9. Sanchit   |  October 29, 2014 at 2:47 am

    I wanted to fetch all types of Windows Logs through PowerShell as follows-

    Application,
    Security,
    System,
    Setup,
    Forwarded Events

    I can get Application and System logs through Get-EventLog command but I am not able to get the Security, Setup, Forwarded Events through it.

    I tried using Get-WinEvent -LogName. It worked for Setup and Security but not for Forwarded events. Error which I get is-

    Get-WinEvent : No events were found that match the specified selection criteria.

    Question #1. Is it due to no log(s) entries in Forwarded Events? If yes, how can I create an entry in Forwarded Events & verify if that is the case.

    Also, response of Get-EventLog command includes following heads-

    Index
    Time
    EntryType
    Source
    InstanceID
    Message

    but, response of Get-WinEvent -LogName command does not includes full of them. It just includes-

    TimeCreated
    Id
    LevelDisplayName
    Message

    Question #2. Is there any way by which I can responses same as Get-EventLog command?

    Kindly help.

    Thanks

  10. Mark Berry   |  October 29, 2014 at 9:27 am

    Sanchit, I have never used ForwardedEvents; in fact I see on my system that it is Disabled. Is this the log that is used when receiving events forwarded from other systems? Maybe you could get something into the log that way.

    Get-WinEvent -LogName returns objects. Each object contains the properties specified in System.Diagnostics.Eventing.Reader.EventLogRecord (see link in main article above). Some names have changed, e.g. Source is now ProviderName. You can create a custom list of columns in a format-table command, for example:

    get-winevent -logname Application | format-table -property TimeCreated,ProviderName,Message

  11. Sanchit   |  October 29, 2014 at 6:12 pm

    Hi Mark,

    That helps.

    Thank you! :)

Leave a Reply





Notify me of followup comments via e-mail. You can also subscribe without commenting.