PowerShell Get-WinEvent XML Madness: Getting details from event logs


Announcements

Before we jump into today’s script here are some current events:

  • This blog post celebrates three years of PowerShell blogging on TechNet as GoateePFE.  It has been a great ride, and I am far from done.  See the most popular posts here.  Thank you for making this blog successful.
  • The PowerShell Deep Dives book is out now.  I contributed a chapter on Active Directory token bloat taken from my SID history blog series.  This book has a ton of great chapters by a ton of great people. All the proceeds go to Save The Children.  Buy your copy today.
  • If you haven’t had a chance to watch the Microsoft Virtual Academy recordings Getting Started with PowerShell 3.0 Jump Start and Advanced Tools & Scripting with PowerShell 3.0 Jump Start then you need to put them on your list.  Jeffrey Snover and Jason Helmick do a fantastic job of covering everything you need to know to get started with PowerShell.  Make time for this over several lunches or knock it out in a couple training days.  These videos will seriously boost your career.  You could even gather the family around with a bowl of popcorn.
  • PowerShell Saturday 005 is coming up October 26th in Atlanta, Georgia.  My session is titled It’s Time To Part With Blankie: Moving from command line tools to PowerShell for Active Directory.  If you’re in the area stop by for a good time with several PowerShell celebrities.  I’m looking forward to Ed Wilson’s session PowerShell Workflows for Mere Mortals.

Now for today’s topic…

XML vs. IT Pro

Maybe I haven’t looked hard enough, but I’ve just not found any clear documentation aimed at IT Pros for what I am posting today.  As an IT Pro type guy (not a .NET type guy) I have avoided XML for years.  CSV and HTML are so much easier.  XML seems to be a labyrinth of complexity in my mind, and it still is, at least from a PowerShell perspective.  The object model is convenient, but trying to navigate it loses me.  Yeah, I know XML makes the world a happy place, but I’m just not there yet.

Despite this disparaging disclaimer I believe I have drafted a script that will help many of us IT Pros as we weed through event logs (or ETL trace files or EVTX files).

The Backstory

A couple years ago a distinguished peer of mine, Matthew Reynolds, invited me to contribute to a project parsing ETL trace data with PowerShell.  You can hear him talk about the fruits of this labor in his recent TechEd talk, How Many Coffees (New 2013 Edition) Can You Drink While Your PC Starts?  Through this project I became very familiar with parsing XML event log data.  Most of this learning happened for me while I was parsing GPO processing events.

Recently I had a request from a customer who wanted to parse logon audit events and filter deep into the event message body.  I had to dust off my code from the former project and dive a little deeper.  I figured if I’ve had this much trouble with the topic, then I should blog it so that I have something to refer back to when I forget all this again in a few months.

Events:  The good, the bad, and the ugly

The good:  PowerShell works with event logs out of the box.  You have two cmdlets:  Get-EventLog and Get-WinEvent.  Get-WinEvent is the one we’re all supposed to use now.

The bad:  All of a sudden reading event logs gets complicated.  The filtering in particular requires some crazy syntax.  We are far removed from the simplicity of DUMPEL.  PowerShell team blog posts from 2009 here and here attempt to make this look routine.  Um… yeah.

The ugly:  All of the juicy nuggets of event data in the message body are stored in XML.  And nearly every combination of event ID and provider has a unique event schema for storing the data we want.  Neo’s MSDN blog post gets us most of the way there.  AskDS and Hey Scripting Guy show how we can use the GUI to help write the XML filter syntax.  Now my head is spinning.  This is the farthest point from intuitive.  Don’t even get me started on XPATH.

Note:  In all fairness to the product this data structure is necessary.  All events have a few common properties like provider, ID number, date/time, source, etc.  But in order to capture the unique details of each event we needed a way to store a variable number of properties.  So the design is good, just a bit complicated to script.

In the life of every scripter you will come to challenges like this.  You just have to cowboy up and dive in.  I recommend that you cruise through these other articles linked above for some good details on filtering XML event log data.

The thing I’ve not seen in these blog posts is how to dump out the event message data in a CSV file where I can easily report and manipulate the data I need.  For example, if I’m collecting logon failure event 4625, then I want the guts of the message body in separate columns where I can easily summarize and report on the user and computer accounts involved.  While I can harvest event logs from multiple servers in the GUI, it is just not friendly for mass reporting, sorting and visualization like Excel.  This is the problem I am trying to solve. 

image

The individual nuggets of interest are represented as XML in the EventData portion of the message:

image

 

Finding Event Message Schemas

Use the lines below to discover the XML schemas behind the events you want to parse.  Run each line and substitute the event IDs and providers you want to investigate.  These lines serve as a bit of an event log deep dive when you run them on your own machine.  Note that some of these lines may need to run in an elevated shell (Run As Administrator).

            
# List all event providers            
Get-WinEvent -ListProvider * | Format-Table            
            
# List all policy-related event providers.            
Get-WinEvent -ListProvider *policy* | Format-Table            
            
# List the logs on the machine where the name is like 'policy'            
Get-WinEvent -ListLog *policy*            
            
# List all possible event IDs and descriptions for the provider            
(Get-WinEvent -ListProvider Microsoft-Windows-GroupPolicy).Events |            
   Format-Table id, description -AutoSize            
            
# List all of the event log entries for the provider            
Get-WinEvent -LogName Microsoft-Windows-GroupPolicy/Operational            
            
# Each event in each provider has its own message data schema.            
# Use this line to find the schema of each event ID.            
# For a specific event            
(Get-WinEvent -ListProvider Microsoft-Windows-GroupPolicy).Events |
   Where-Object {$_.Id -eq 5314}
            
# For a keyword in the event data            
(Get-WinEvent -ListProvider Microsoft-Windows-GroupPolicy).Events |            
   Where-Object {$_.Template -like "*reason*"}            
            
# Find an event ID across all ETW providers:            
Get-WinEvent -ListProvider * |            
   ForEach-Object { $_.Events | Where-Object {$_.ID -eq 4168} }            

Notice that the Template property holds the XML definition of the event message body.  This is where our event log goodness hides.  The trick is parsing these individual XML values for our reporting.  I’ve highlighted the data entries and their corresponding placeholders in the GPO event schema example below. 

PS C:\> (Get-WinEvent -ListProvider Microsoft-Windows-GroupPolicy).Events |
 Where-Object {$_.Id -eq 5314}


Id          : 5314
Version     : 0
LogLink     : System.Diagnostics.Eventing.Reader.EventLogLink
Level       : System.Diagnostics.Eventing.Reader.EventLevel
Opcode      : System.Diagnostics.Eventing.Reader.EventOpcode
Task        : System.Diagnostics.Eventing.Reader.EventTask
Keywords    : {}
Template    : <template
              xmlns="http://schemas.microsoft.com/win/2004/08/events">
              <data name="BandwidthInkbps" inType="win:UInt32"
                  outType="xs:unsignedInt"/>
              <data name="IsSlowLink" inType="win:Boolean"
                  outType="xs:boolean"/>
              <data name="ThresholdInkbps" inType="win:UInt32"
                  outType="xs:unsignedInt"/>
              <data name="PolicyApplicationMode" inType="win:UInt32"
                  outType="xs:unsignedInt"/>
              <data name="ErrorCode" inType="win:UInt32"
                  outType="win:HexInt32"/>
              <data name="LinkDescription" inType="win:UnicodeString"
                  outType="xs:string"/>
              </template>

Description : A %6 link was detected. The Estimated bandwidth is %1 kbps. The
              slow link threshold is %3 kbps.

 

Cracking the XML Nut

So how do I pull those values out of the event message?  This time we’re going to look at a log from one of our domain controllers (DCs).  Notice the filter syntax.  DO NOT pipe the entire event log to a Where-Object if you want results returned in this century.  Let’s grab one event entry to examine:

# Prompts for creds            
$cred = Get-Credential Contoso\Administrator            

# Grab the events from a DC            
# Target DC needs firewall rule enabled:            
# Remote Event Log Management (RPC)            
$Event = Get-WinEvent -ComputerName 2012DC -Credential $cred ` 
 -FilterHashtable @{Logname='Security';Id=4625} ` 
 -MaxEvents 1

# View the event properties.
$Event | Format-List *

# View the array of message body values.
# But the property names are missing.
$Event.Properties

# Convert the event to XML
$eventXML = [xml]$Event.ToXml()

# Drill down through the XML to the message goodness
# Ah ha! This is what we want.
$eventXML.Event.EventData.Data

# You have to index each data element to access it.
$eventXML.Event.EventData.Data[0].name
$eventXML.Event.EventData.Data[0].'#text'

 

PS C:\> $eventXML.Event.EventData.Data

Name                      #text                          
----                      -----                          
SubjectUserSid            S-1-5-18                       
SubjectUserName           2012DC$                        
SubjectDomainName         CONTOSO                        
SubjectLogonId            0x3e7                          
TargetUserSid             S-1-0-0                        
TargetUserName            DanPark                        
TargetDomainName          CONTOSO                        
Status                    0xc000015b                     
FailureReason             %%2308                         
SubStatus                 0x0                            
LogonType                 4                              
LogonProcessName          Advapi                         
AuthenticationPackageName Negotiate                      
WorkstationName           2012DC                         
TransmittedServices       -                              
LmPackageName             -                              
KeyLength                 0                              
ProcessId                 0x390                          
ProcessName               C:\Windows\System32\svchost.exe
IpAddress                 -                              
IpPort                    -

  

My Inefficient Magic XML to CSV Event Reporting Machine

The code in this example pulls event 4625 from the Security log on a domain controller, and then it copies each of the XML message body properties into their own event object property for easier reporting.  Note that the DC must have the firewall rule enabled to allow Remote Event Log Management (RPC).

# Prompt for creds            
$cred = Get-Credential Contoso\Administrator            
            
# Grab the events from a DC            
$Events = Get-WinEvent -ComputerName 2012DC -Credential $cred ` 
    -FilterHashtable @{Logname='Security';Id=4625}            
            
# Parse out the event message data            
ForEach ($Event in $Events) {            
    # Convert the event to XML            
    $eventXML = [xml]$Event.ToXml()            
    # Iterate through each one of the XML message properties            
    For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) {            
        # Append these as object properties            
        Add-Member -InputObject $Event -MemberType NoteProperty -Force ` 
            -Name  $eventXML.Event.EventData.Data[$i].name ` 
            -Value $eventXML.Event.EventData.Data[$i].'#text'            
    }            
}            
            
# View the results with your favorite output method            
$Events | Export-Csv .\events.csv            
$Events | Select-Object * | Out-GridView            

I call this inefficient, because it must go back through the event log data a second time to process the XML message body properties.  This is OK for smaller data sets, but your performance will be slower with larger data sets.

Note that this approach is only appropriate for events that share the same schema.  Some events within a provider will have the same schema, even though the event IDs are different.  In those cases it would be OK to include multiple event IDs in your query.

image

This is my solution to the problem called out at the beginning of the article.  Now that I have all of the XML data in native event object properties I can easily group, filter, sort, and report.  Yes, there is fancy XML syntax that will filter deep into the message body, but that does not give me visibility to all of the values.  Now I have the data exposed in a CSV spreadsheet where I can quickly visualize patterns and trends.  Add some pivot tables and charts to make your reporting management-friendly.

The Big Finish

You could really amp this up by running it as a workflow in parallel against all of your target computers.  Or you could use Invoke-Command -AsJob for multi-threading.  Unfortunately remote sessions and workflows return their results as deserialized objects that lack the .ToXML() method.  I’m still working on a way to get this working over these remoting technologies.  In the meantime you can scale this out across multiple servers by using the cmdlet Start-Job to spin up a thread for each server you query.

UPDATE: See the new-improved version of this XML event log filtering in a newer post here.

Am I Wrong?

One of the things I like about working in IT is constantly learning new things.  I’ve been doing PowerShell for three years, but I still have much to learn.  If I’m missing something here please let me know in the comments below.  Have you found an easier way to work with XML event message bodies from Get-WinEvent?  I’d love to hear about it.

Comments (42)

  1. Ashley McGlone says:

    Hello Roger,
    This information is readily available with a few searches online. Do any of these links help you?

    http://technet.microsoft.com/en-us/library/ms189046(v=sql.110).aspx
    http://technet.microsoft.com/en-us/library/cc748890.aspx
    http://blogs.technet.com/b/askds/archive/2011/08/29/the-security-log-haystack-event-forwarding-and-you.aspx

    Regards,
    Ashley
    @GoateePFE

  2. connerrs says:

    Excellent post! I was able to combine some of your code along with the information found here(http://blogs.technet.com/b/otto/archive/2011/08/24/trigger-a-powershell-script-from-a-windows-event.aspx)
    to setup a scheduled task linked to every time an event ID occurs to keep an ongoing log of that event ID. Since our DC log files fill up rather quickly, exporting a bulk set of events would not really work well because we would likely miss a lot of data.
    To make this work you have to export a scheduled task and add eventRecordID and eventChannel to the xml then re-import it. This allows the scheduled task to pass those values to the powershell script when the event occurs. Another great thing about this is
    that it can be used with any eventID without having to modify the code, etc. I’ve posted the code below:

    # Script Name: ExportEventsThroughSchedTask.ps1
    # Usage Example (use a valid ID found via Event Viewer XML view of an event): powershell .TriggerScript.ps1 -eventRecordID 1 -eventChannel Application
    #
    http://blogs.technet.com/b/ashleymcglone/archive/2013/08/28/powershell-get-winevent-xml-madness-getting-details-from-event-logs.aspx
    #
    http://blogs.technet.com/b/otto/archive/2011/08/24/trigger-a-powershell-script-from-a-windows-event.aspx

    # Collects all named paramters (all others end up in $Args)
    param($eventRecordID,$eventChannel)

    # Grab the events with the EventRecordID and EventChannel arguments
    $Events = Get-WinEvent -LogName $eventChannel -FilterXPath ""

    # Parse out the event message data
    ForEach ($Event in $Events) {
    # Convert the event to XML
    $eventXML = [xml]$Event.ToXml()
    # Iterate through each one of the XML message properties
    For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) {
    # Append these as object properties
    Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name $eventXML.Event.EventData.Data[$i].name -Value $eventXML.Event.EventData.Data[$i].’#text’

    }
    }

    # View the results with your favorite output method
    # Replace the save location in the following line
    $logpath = "FileSystem::\servershare" + $env:computername + "eventlog.csv"
    $Event | Export-Csv -Path $logpath -NoTypeInformation -Append -Force

  3. Ashley McGlone says:

    Hello Anonymous,

    I had the same result on my local machine. First, make sure you launch the PowerShell console as Administrator if you want to query the Security event log. Second, it is not likely that event 4625 is present on your local machine. Try event 4624.

    Let me know if those two changes help.
    Ashley (GoateePFE)

  4. Ashley McGlone says:

    Thanks for the feedback. Good to hear.

    @sgrinker, I've not found multiple data nodes in any of the providers or events I've explored. Your mileage may vary.

    @jason, glad you're comfortable with XML. The only problem with deserialization is that you always lose the methods. Comes with the territory.

    Keep scripting,

    Ashley

    @GoateePFE

  5. Anonymous says:

    Awesome post!  …and yes I have to agree working with that XML data from a scripting perspective is headache inducing.  You had asked if anyone found an "easier" way to work with it.  Unfortunately I can't offer an easier way, but a fairly similar methodology.  Check out the "Advanced Examples" as a part of this older (and no longer updated) project:  pseventlogwatcher.codeplex.com/wikipage

    More specifically Example 1-B and Example 2.  Although they are relate the some of the cmdlets in the module, you can see the basic logic used on the XML there.  As you already mentioned this only works if you keep to one event ID type for each CSV generated.

    I am curious on the FOR loop to index $eventXML.Event.EventData.Data.Count in your example.  Did you find Events with multiple Data nodes in your work?  For the most part I was working with a subset of audit events when building the project, so I only scratched the surface of event schemas in my travels.

    Definitely agree though that it would be nice to find a more effecient way to break apart the details in the message details of the event.

  6. Keith Langmead says:

    Superb post! I think that pretty much covers every "how the hell am I going to do x" question relating to querying events that I’ve had, or at least goes into enough detail of the how and why that I can use it to find what I need. Of course, time will
    tell if that’s just wishful thinking!

    Completely agree with you on the XML v IT Pro statement, feels at times like most XML articles assume the reader is a developer.

  7. Ashley McGlone says:

    Hello zafman,

    I think the error is in your first line:
    $Event = Get-WinEvent -FilterHashtable @{Logname=’ForwardedEvents’}

    Instead make the variable plural:
    $Events = Get-WinEvent -FilterHashtable @{Logname=’ForwardedEvents’}

    Let me know if that fixes it.
    Ashley
    @GoateePFE

  8. Ashley McGlone says:

    Hi Joel182,

    Ooops. I must have overlooked your comment. I apologize for the delay.

    Try setting $Events with one of these lines instead:

    Get-WinEvent -ProviderName Microsoft-Windows-Diagnostics-Performance/Operational -FilterXPath ‘*[System[(EventID=100)]]’

    Get-WinEvent -FilterHashtable @{Logname=’Microsoft-Windows-Diagnostics-Performance/Operational’;Id=100}

    The syntax you were using to get the events is actually looking at the event schema rather than the events themselves. Also, you want to avoid piping Get-WinEvent to Where-Object as much as possible. Make sure you do your filtering in the event cmdlet parameters.
    This will make your scripts much faster.

    Hope this helps,
    Ashley @GoateePFE

  9. GermánM says:

    Great job Mr. McGlone

  10. GordonDelgado says:

    ELOGDMP | grep -i whatever >datafile.csv

    Life is so much easier when you stop trying to make it complicated…

  11. Ashley McGlone says:

    Thanks for sharing, Sushena. You could probably simplify all that OR logic using either the -Contains or -In operator. See if that can shorten your code.

  12. Anonymous says:

    I use export-clixml and convertto-xml cmdlets for turning deserialized objects into XML.  I use PSRemoting in place of RPC everywhere in my environment so working with deserialized objects is in my bailiwick.

  13. Ashley McGlone says:

    Hmmm. Get-WinEvent is not compatible with Windows Server 2003, although it can read the classic log format. I would have to repro this in my lab to completely analyze the scenario. For now…
    Set a debug break point in the PowerShell ISE, then use F11 to step one line at a time. Use the command pane to inspect the value of these two items:
    $eventXML.Event.EventData.Data[$i].name
    $eventXML.Event.EventData.Data[$i].’#text’
    You could even substitute numbers for the variables like this:
    $eventXML.Event.EventData.Data[0].name
    $eventXML.Event.EventData.Data[0].’#text’
    Then change the 0 to 1,2,3 respectively to check each one of your expected name/value pairs.
    That should help you identify the null name.
    Hope this helps,
    Ashley
    @GoateePFE

  14. Ashley McGlone says:

    Great work, Connerrs. Thanks for sharing!

  15. Mike says:

    Running

    Get-WinEvent -FilterHashtable @{Logname='Security';Id=4625} on a local computer returns an error to say that parameter is incorrect at char13, which is -FilterHashtable

    It's in the examples if you do a get-help get-Winevent, so am not sure what is going wrong now.

    even copy-pasting the examples on FilterHashtabel don't work

  16. zafman says:

    hi Ashley,

    I been trying to run your script on my local server but keep getting errors. Its look like failing to index each data element to access it? its server 2012 and running PS as admin!
    here is my code:

    $Event = Get-WinEvent -FilterHashtable @{Logname=’ForwardedEvents’}

    # Parse out the event message data
    ForEach ($Event in $Events) {
    # Convert the event to XML
    $eventXML = [xml]$Event.ToXml()
    # Iterate through each one of the XML message properties
    For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) {
    # Append these as object properties
    Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name $eventXML.Event.EventData.Data[$i].name -Value $eventXML.Event.EventData.Data[$i].’#text’
    }
    }
    # View the results with your favorite output method
    $Events | Export-Csv .events.csv
    $Events | Select-Object * | Out-GridView

    and this is the error Iam getting:
    Add-Member : Cannot bind argument to parameter ‘Name’ because it is null.
    At line:10 char:79
    + Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name $e …
    + ~~

  17. zafman says:

    I am still getting the same error!
    Add-Member : Cannot bind argument to parameter ‘Name’ because it is null.
    At line:10 char:70
    + Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name $eventXML.E …
    + ~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Add-Member], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.AddMemberCommand

    ForwardedEvents are coming from server 2003 (event ID=680)

    my results are little bit different then from yours for this events (680)

    XML in the EventData portion of the message:

    http://schemas.microsoft.com/win/2004/08/events/event"&gt;

    680
    0
    9
    0xa0000000000000

    243224405
    Security
    AD01


    MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
    John.Doe
    PC07
    0x0

    PS C:Windowssystem32> $Event.Properties

    Value
    —–
    MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
    John.Doe
    PC07
    0x0

    PS C:Windowssystem32> $eventXML.Event.EventData.Data
    MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
    John.Doe
    PC07
    0x0

    any idea how can I pull these type event logs?

  18. sholland says:

    I used the Event Log Collector to pull all of my logs to a single server. They ALL wind up in a very busy ‘Forwarded Events’ folder, but from there I can manipulate them.

    There are filtering options when setting up the Subscriptions. You can even apply an XML filter that you have written or copied from another source.

    Does that address the problem with de-serialized return results? The events that I am receiving seem complete and contain the source Computer name.

  19. Joe182 says:

    Hi,

    I’m trying to use your code to take an event and output it to a CSV but am returning errors when trying the xml stuff and what I do get to work doesnt report the values, get boolean in the place of the numbers.

    My xml error is Method invocation failed because [system.diagnostics.eventing.reader.eventmetadata] does’nt contain a method named ‘toxml’.
    $events = (Get-WinEvent -ListProvider Microsoft-Windows-Diagnostics-Performance).Events | Where-Object {$_.Id -eq 100}
    $event | Format-List *
    $event.Properties
    $eventxml = [xml]$event.ToXml()
    $eventxml.Event.EventData.Data
    $eventxml | Export-CSV c:boottimerslog.csv

    Any ideas?

  20. Joe182 says:

    Correction in code I didn’t publish the latest
    $events = (Get-WinEvent -ListProvider Microsoft-Windows-Diagnostics-Performance).Events | Where-Object {$_.Id -eq 100}
    $event = $events[0]
    $event | Format-List *
    $event.Properties
    $eventxml = [xml]$event.ToXml()
    $eventxml.Event.EventData.Data
    $eventxml > c:boottimerlog.csv

  21. Roger says:

    Hi all
    First of all thank you very much Mr Ashley McGlore for the hard work and this post.
    Unfortunately my boss tells me if I could not save all forwarded events to a SQL server until end of this week I will lost my job.
    @all could somebody help me please?. If you can let me know and if the script work I have a surprise for you.

  22. matthew g says:

    nice.

  23. Anonymous says:

    Welcome! Today’s post includes demo scripts and links from the Microsoft Virtual Academy event: Using PowerShell for Active Directory . We had a great time creating this for you, and I hope you will share it with anyone needing to ramp up their

  24. Sushena says:

    Script will extract only required parameter from event log for one event id.

    Write-Host "Message `t On Whom `t Who did `t Domain `t Timecreated `t Record ID `t ObjectType"
    Write-Host "——————————————————————————————–"
    $obj = "user"

    Get-WinEvent -FilterHashtable @{Logname=’Security’}|? { ($_.id -eq 4720) -or ($_.id -eq 4722) -or ($_.id -eq 4723) -or ($_.id -eq 4724) -or ($_.id -eq 4725) -or ($_.id -eq 4726) -or ($_.id -eq 4738) -or ($_.id -eq 4740) -or ($_.id -eq 4765) -or ($_.id -eq 4766)
    -or ($_.id -eq 4767) -or ($_.id -eq 4780) -or ($_.id -eq 4781) -or ($_.id -eq 4794) -or ($_.id -eq 5376) -or ($_.id -eq 5377)} | foreach-object {
    $Event = $_
    $splt = $Event.Message.Split("`n")
    $msg = $splt | Select -First 1
    $eventXML = [xml]$Event.ToXml()
    foreach ($attr in $eventXML.Event.EventData.Data)
    {
    if($attr.name -eq "TargetUserName")
    {
    $user = $attr.’#text’
    }
    if($attr.name -eq "TargetDomainName")
    {
    $domain = $attr.’#text’
    }
    if($attr.name -eq "SubjectUserName")
    {
    $auser = $attr.’#text’
    }
    }
    $Eid = $Event.Id
    $timec = $Event.TimeCreated
    if($user)
    {
    "{0,-45} {1,-10} {2,-10} {3,-20} {4,-20} {5,-8} {6,-10}" -f $msg, $user, $auser, $domain, $timec, $Eid, $obj

    }
    }

    __________________________________________

    Start and end date can also be specified in below format. Get-WInEvent and switch -FilterHashTable suites best for win 2008R2

    Get-WinEvent -FilterHashtable @{Logname=’Security’;StartTime="5/14/2014";EndTime="5/20/2014"}|? { ($_.id -eq 4720) -or ($_.id -eq 4722) -or ($_.id -eq 4723) } | ForEachObject

  25. Sushena says:

    Script will extract only required parameter from event log for one event id.

    Write-Host "Message `t On Whom `t Who did `t Domain `t Timecreated `t Record ID `t ObjectType"
    Write-Host "——————————————————————————————–"
    $obj = "user"

    Get-WinEvent -FilterHashtable @{Logname=’Security’}|? { ($_.id -eq 4720) -or ($_.id -eq 4722) -or ($_.id -eq 4723) -or ($_.id -eq 4724) -or ($_.id -eq 4725) -or ($_.id -eq 4726) -or ($_.id -eq 4738) -or ($_.id -eq 4740) -or ($_.id -eq 4765) -or ($_.id -eq 4766)
    -or ($_.id -eq 4767) -or ($_.id -eq 4780) -or ($_.id -eq 4781) -or ($_.id -eq 4794) -or ($_.id -eq 5376) -or ($_.id -eq 5377)} | foreach-object {
    $Event = $_
    $splt = $Event.Message.Split("`n")
    $msg = $splt | Select -First 1
    $eventXML = [xml]$Event.ToXml()
    foreach ($attr in $eventXML.Event.EventData.Data)
    {
    if($attr.name -eq "TargetUserName")
    {
    $user = $attr.’#text’
    }
    if($attr.name -eq "TargetDomainName")
    {
    $domain = $attr.’#text’
    }
    if($attr.name -eq "SubjectUserName")
    {
    $auser = $attr.’#text’
    }
    }
    $Eid = $Event.Id
    $timec = $Event.TimeCreated
    if($user)
    {
    "{0,-45} {1,-10} {2,-10} {3,-20} {4,-20} {5,-8} {6,-10}" -f $msg, $user, $auser, $domain, $timec, $Eid, $obj

    }
    }

    __________________________________________

    Start and end date can also be specified in below format. Get-WInEvent and switch -FilterHashTable suites best for win 2008R2

    Get-WinEvent -FilterHashtable @{Logname=’Security’;StartTime="5/14/2014";EndTime="5/20/2014"}|? { ($_.id -eq 4720) -or ($_.id -eq 4722) -or ($_.id -eq 4723) } | ForEachObject

  26. Mike Rickard says:

    As you say this does seem to be a painful way of dealing with event log data extraction.
    The schema for any particular event tells you the name and type of each data element. so, they will always be the same for that event. In effect, you are looking up this information for every event, although you’ll always get the same answer.
    If you take the $event.properties.value array, that will always be the same length, and will always contain the data items in the same order.

    so, as you process the events you are doing something like this:

    foreach ($event in $events) {$eventXML = [xml]$event.toxml(); "$($eventxml.Event.eventdata.data[2].’#text’), $($eventxml.Event.eventdata.data[0].’#text’)"}

    when you could be doing:

    foreach ($event in $events) {"$($event.timecreated.datetime), $($event.properties.value[2]), $($event.properties.value[0])"}

    (I think that I did work out how to do the time from the XML, but it wasn’t pretty)

    So, generating your own csv data will probably be the simplest way of dealing with this. Interestingly, I’ve seen a number of references to extracting the event log data that use this round tripping with the $eventXML route which suggests that one suggestion
    that works will propagate rather well.

  27. John Sympson says:

    This is a very helpful article, but I’m having trouble digging into NETLOGON events. When I run
    (Get-WinEvent -ListProvider Netlogon).Events | Format-Table id, description -AutoSize

    I get nothing back. I checked some of the other NETLOGON members (keywords, displayname) and very few of them return any data. Some that I noted do return data are messagefilepath, and id. I verified running the same PS line as above but with Microsoft-Windows-GroupPolicy,
    and confirmed it does return results for Events.
    Also, FYI, I did see a potential problem and fix that relates to changing your Region to EN-US, but that seems to only apply to those not already in EN-US.

    What am I missing here?

  28. David Smith says:

    How to parse out the event message data so it only contains the following columns in the csv exported output?

    Message:
    SubjectUserName:
    SubjectDomainName:
    ObjectType:
    IpAddress:
    ShareName:
    ShareLocalPath:
    RelativeTargetName:
    ProviderName:
    LogName:
    TimeCreated:

    #–below is the whole powershell statement—

    $query=@"

    <>Select Path="Security">*[EventData[Data[@Name=’ShareName’] and (Data=’\*FolderShare’)] and *[Data[@Name=’AccessMask’] and (Data=’0x100080′)]]

    "@
    $Events = Get-WinEvent -FilterXml $query
    # Parse out the event message data
    ForEach ($Event in $Events) {
    # Convert the event to XML
    $eventXML = [xml]$Event.ToXml()
    # Iterate through each one of the XML message properties
    For ($i=0; $i -lt $eventXML.Event.EventData.Data.Count; $i++) {
    # Append these as object properties
    Add-Member -InputObject $Event -MemberType NoteProperty -Force -Name $eventXML.Event.EventData.Data[$i].name -Value $eventXML.Event.EventData.Data[$i].’#text’

    }
    }

    # View the results
    $Events | Export-Csv access-events.csv

  29. David Smith says:

    Can someone improve the query to get specific dates of the log like daily? I just want to get a daily report of the above security audit logs.

  30. Anonymous says:

    Windows PowerShell is very well suited for monitoring services because tasks can be repeated and they

  31. Anonymous says:

    Windows PowerShell is very well suited for monitoring services because tasks can be repeated and they

  32. Wes Evernden says:

    I am working with a 4 GB evtx file and need to pull out SubjectUserName from the data of event 4656. The PS script is taking forever but have found I can load the file in MS Message Analyzer 1.3.1. It loads quite quickly, I can filter to the event ID and
    add columns for the data I want. Then export to csv. Works great.

  33. Ayman says:

    Hello Ashley
    great post
    I hope you still checking the comments section :)

    I hit a wall early ,

    [PS] C:Windowssystem32>$query = @"
    >>
    >>
    >>
    >>
    >>
    >> "@
    >> $events = Get-WinEvent -FilterXml $query

    >$eventXML = [xml]$events.ToXml()

    Method invocation failed because [System.Object[]] doesn’t contain a method named ‘ToXml’.
    At line:1 char:14
    + $events.ToXml <<<< ()
    + CategoryInfo : InvalidOperation: (ToXml:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

    ————-

    Also didn’t work with you example above !

    please can you advice ?

  34. Hello Ayman,
    Nice to meet you. Yes, I do try to keep up with comments on blog posts, but sometimes it just takes me a while to get to them between customer engagements.
    Unfortunately it appears that all of the angle brackets formatted in your pasted comment do not render well in the browser. Please use the Email Blog Author link (http://blogs.technet.com/b/ashleymcglone/contact.aspx)
    to send me your code for review.
    My other thought is that perhaps the query returned no results. If an empty result set, then there may not be an object for the ToXML() method.
    If neither my example nor your example are working, perhaps something else is going on. What OS and PowerShell version are you using?
    Thanks,
    Ashley
    @GoateePFE

  35. Anonymous says:

    Today we learn how to efficiently filter event log queries, going beyond simple event ID filtering into the specific values of the XML message data. Then we will run this filter against multiple servers in parallel for faster data collection.

  36. James Pearman says:

    Hi,
    Excellent blog but I’m a little stuck trying to export print service events to a .csv. I have this so far:

    $date = (Get-Date).AddDays(-1)
    $Event = Get-WinEvent -FilterHashTable @{Logname="Microsoft-Windows-PrintService/Operational"; ID=307} -MaxEvents 1
    $Event | Format-List *
    $Event.Properties
    $eventXML = [xml]$Event.ToXml()
    $eventXML.Event.EventData.Data
    $eventXML.Event.UserData.Data[0].name
    $eventXML.Event.UserData.Data[0].’#text’

    But I am getting the following errors:

    Cannot index into a null array.
    At line:6 char:1
    + $eventXML.Event.UserData.Data[0].name
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Cannot index into a null array.
    At line:7 char:1
    + $eventXML.Event.UserData.Data[0].’#text’
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Can you please tell me what i’m doing wrong?

    Thanks for your help.

  37. Hello James,
    What do you see when you dump out $eventXML.Event.EventData.Data? What do you find when following the guidance above in the section "Finding Event Message Schemas" for this provider and event schema?
    Thanks,
    GoateePFE

  38. Justin Grote says:

    Hey great article. I created a function called Expand-WinEvent to do this easier while making an AD Changes Monitor script.
    https://gist.github.com/JustinGrote/33e3765dfcd40a0b2673

  39. Port St. Joe says:

    on my machine (2012 R2 ), the only way I got the Add-member syntax to work was use -NotePropertyName and -NotePropertyValue and NOT use -MemberType, nor -Name, nor -Value. Maybe new syntax on 2012 R2?

    Also – every once in a while during debugging, I get in a state where the $Event variable inside the ForEach I am using to convert to XML, etc., suddenly is NULL inside the loop ***but ONLY if a method of $Event is used… if i take everything out except output
    some of the current $Event properties, then it is NOT null, and I get data. VERY WEIRD. I am guessing it is some kind of bug getting triggered, because as soon as I close the Powershell ISE session, and then start fresh, it starts working as expected. Just
    throwing that out there in case someone else is banging their head on a wall over that.***

  40. Peter says:

    What about translating the Failure Reason into something meaningful

  41. Mohammad Afzal says:

    Excellent Post …I am getting the following Error when trying to finding Event Message Schemas

    [PS] C:\temp>Get-WinEvent -ListProvider * | ForEach-Object { $_.Events | Where-Object {$_.ID -eq 10027} }

    Id : 10027
    Version : 0
    LogLink : System.Diagnostics.Eventing.Reader.EventLogLink
    Level : System.Diagnostics.Eventing.Reader.EventLevel
    Opcode : System.Diagnostics.Eventing.Reader.EventOpcode
    Task : System.Diagnostics.Eventing.Reader.EventTask
    Keywords : {, Init}
    Template :

    Description : Failed to indicate filter arrival

  42. Thank you for this Ashley. It is an amazing video, and I have enjoyed it every single second. The whole course is awesome, I have learned a lot. I will definitely implement bunch of the stuff you have showed us here.