Training
Get a free hour of SANS training

Experience SANS training through course previews.

Learn More
Learning Paths
Can't find what you are looking for?

Let us help.

Contact us
Resources
Join the SANS Community

Become a member for instant access to our free resources.

Sign Up
For Organizations
Interested in developing a training plan to fit your organization’s needs?

We're here to help.

Contact Us
Talk with an expert

Month of PowerShell - Working with the Event Log, Part 1

In this article we'll start looking at working with the Windows event log using PowerShell.

Authored byJoshua Wright
Joshua Wright

#monthofpowershell

Windows event logs are a valuable source of information for threat hunting, incident response, digital forensics, and a slew of other fields. PowerShell has powerful support for working with event log data, if not always intuitive or consistent.

Animated GIF of the PowerShell command Get-WinEvent -LogName Security and the output of many similar-looking log entries.

Get-WinEvent vs. Get-EventLog

Microsoft has two commands for interrogating Windows event logs: [code]Get-WinEvent[/code] and [code]Get-EventLog[/code].

The [code]Get-EventLog[/code] cmdlet uses a Win32 API that has been deprecated, so Microsoft recommends using [code]Get-WinEvent[/code]. However, Reddit wisdom indicates that [code]Get-WinEvent[/code] can sometimes be slower than [code]Get-EventLog[/code].

In practice, [code]Get-WinEvent[/code] is the preferred way to access event log information, since it is designed to support the modern Windows Event Log technology features. If you are working only with the Application, System, and Security logs, then [code]Get-EventLog[/code] may still work for you, but as a deprecated API there's no guarantee Microsoft will continue to make [code]Get-EventLog[/code] available in the future.

Listing Event Log Sources

Microsoft has many sources of event log data, both those included with Windows and from third-party applications. To list the available event log sources, run [code]Get-WinEvent -ListLog *[/code]:

PS C:\Windows\system32> Get-WinEvent -ListLog *

LogMode   MaximumSizeInBytes RecordCount LogName
-------   ------------------ ----------- -------
Circular            15728640         195 Windows PowerShell
Circular             1052672           0 ThinPrint Diagnostics
Circular            20971520         268 System
Circular            20971520        9986 Security
Circular            20971520           0 Key Management Service
Circular             1052672           0 Internet Explorer
Circular            20971520           0 HardwareEvents
Circular            20971520         840 Application
Circular             1052672             Windows Networking Vpn Plugin Platform/OperationalVerbose
Circular             1052672             Windows Networking Vpn Plugin Platform/Operational
Circular             1052672           0 SMSApi
Circular             1052672           0 Setup
Circular            15728640           0 PowerShellCore/Operational
Circular             1052672           0 OpenSSH/Operational
Circular             1052672           0 OpenSSH/Admin
Circular             1052672             Network Isolation Operational
Circular             1052672           0 Microsoft-WindowsPhone-Connectivity-WiFiConnSvc-Channel
Circular             1052672           0 Microsoft-Windows-WWAN-SVC-Events/Operational
Circular             1052672           0 Microsoft-Windows-WPD-MTPClassDriver/Operational
Circular             1052672           0 Microsoft-Windows-WPD-CompositeClassDriver/Operational
Circular             1052672           0 Microsoft-Windows-WPD-ClassInstaller/Operational
Circular             1052672           0 Microsoft-Windows-Workplace Join/Admin
Circular             1052672             Microsoft-Windows-Wordpad/Admin
Circular             1052672         221 Microsoft-Windows-WMI-Activity/Operational
...

A lot of the event log sources on my test system are empty. We can use the pipeline and [code]Where-Object[/code] to filter the results to show log sources where logging events are available:

PS C:\Windows\system32> Get-WinEvent -ListLog * | Where-Object -Property RecordCount -GT 0 | Select-Object -Property LogName, RecordCount

LogName                                                            RecordCount
-------                                                            -----------
Windows PowerShell                                                         195
System                                                                     268
Security                                                                  9988
Application                                                                841
Microsoft-Windows-WMI-Activity/Operational                                 221
Microsoft-Windows-WinRM/Operational                                          8
Microsoft-Windows-Winlogon/Operational                                      86
Microsoft-Windows-WindowsSystemAssessmentTool/Operational                   38
Microsoft-Windows-Windows Firewall With Advanced Security/Firewall          22
Microsoft-Windows-Windows Defender/Operational                             177
...
Microsoft-Windows-AppModel-Runtime/Admin                                    18
Microsoft-Windows-AppLocker/EXE and DLL                                     16
Microsoft-Windows-Application-Experience/Program-Telemetry                   2
Microsoft-Windows-AppID/Operational                                         10
Microsoft-Client-Licensing-Platform/Admin                                  337

Let's break down this command piece-by-piece:

  • [code]Get-WinEvent -ListLog * | [/code]: List all the event log sources using the wildcard [code]*[/code], start the pipeline with [code]|[/code]
  • [code]Where-Object -Property RecordCount -GT 0 | [/code]: Filter the results to return only event log sources where the [code]RecordCount[/code] property is greater than ([code]-GT[/code]) zero, continue the pipeline with [code]|[/code]
  • [code]Select-Object -Property LogName, RecordCount[/code]: Select the [code]LogName[/code] and [code]RecordCount[/code] properties for display

In this example we retrieved all of the event log sources using [code]-Listlog [/code], but we can also supply a more specific keyword for filtering. Personally, I can never remember the exact event log name for AppLocker, so I use the [code]applocker*[/code] string to match that name:

PS C:\windows\system32> Get-WinEvent -ListLog *applocker* | Where-Object -Property RecordCount -GT 0 | Select-Object -Property LogName, RecordCount

LogName                                 RecordCount
-------                                 -----------
Microsoft-Windows-AppLocker/EXE and DLL          17

Retrieving Log Data

To get log data, specify the event log name with [code]Get-WinEvent -LogName[/code]. Let's take a look for the Security event log:

PS C:\Windows\system32> Get-WinEvent -LogName Security


   ProviderName: Microsoft-Windows-Security-Auditing

TimeCreated                      Id LevelDisplayName Message
-----------                      -- ---------------- -------
7/12/2022 11:51:43 AM          4616 Information      The system time was changed....
7/12/2022 11:48:32 AM          4672 Information      Special privileges assigned to new logon....
7/12/2022 11:48:32 AM          4624 Information      An account was successfully logged on....
7/12/2022 11:47:41 AM          4672 Information      Special privileges assigned to new logon....
7/12/2022 11:47:41 AM          4624 Information      An account was successfully logged on....
7/12/2022 11:46:48 AM          5379 Information      Credential Manager credentials were read....
7/12/2022 11:46:48 AM          5379 Information      Credential Manager credentials were read....
7/12/2022 11:46:48 AM          5379 Information      Credential Manager credentials were read....
...

By default, [code]Get-WinEvent[/code] will display the [code]TimeCreated[/code], [code]Id[/code], [code]LevelDisplayName[/code], and [code]Message[/code] fields. This output will be truncated unless you have a very small font or a very wide PowerShell window. I will often use [code]Format-List[/code] to see the results with each property is listed on a new line:

PS C:\Windows\system32> Get-WinEvent -LogName Security | Format-List


TimeCreated  : 7/12/2022 12:59:24 PM
ProviderName : Microsoft-Windows-Security-Auditing
Id           : 5379
Message      : Credential Manager credentials were read.

               Subject:
                Security ID:            S-1-5-18
                Account Name:           SEC504STUDENT$
                Account Domain:         SEC504
                Logon ID:               0x3E7
                Read Operation:         Enumerate Credentials

               This event occurs when a user performs a read operation on stored
               credentials in Credential Manager.

TimeCreated  : 7/12/2022 12:59:24 PM
ProviderName : Microsoft-Windows-Security-Auditing
Id           : 5379
...

We can look at the available properties for the [code]Get-WinEvent[/code] output using [code]Get-Member[/code]:

PS C:\Windows\system32> Get-WinEvent -LogName Security -MaxEvents 1 | Get-Member -MemberType Property


   TypeName: System.Diagnostics.Eventing.Reader.EventLogRecord

Name                 MemberType Definition
----                 ---------- ----------
ActivityId           Property   System.Nullable[guid] ActivityId {get;}
Bookmark             Property   System.Diagnostics.Eventing.Reader.EventBookmark Bookmark {get;}
ContainerLog         Property   string ContainerLog {get;}
Id                   Property   int Id {get;}
Keywords             Property   System.Nullable[long] Keywords {get;}
KeywordsDisplayNames Property   System.Collections.Generic.IEnumerable[string] KeywordsDisplayNames {get;}
Level                Property   System.Nullable[byte] Level {get;}
LevelDisplayName     Property   string LevelDisplayName {get;}
LogName              Property   string LogName {get;}
MachineName          Property   string MachineName {get;}
MatchedQueryIds      Property   System.Collections.Generic.IEnumerable[int] MatchedQueryIds {get;}
Opcode               Property   System.Nullable[int16] Opcode {get;}
OpcodeDisplayName    Property   string OpcodeDisplayName {get;}
ProcessId            Property   System.Nullable[int] ProcessId {get;}
Properties           Property   System.Collections.Generic.IList[System.Diagnostics.Eventing.Reader.EventProperty] Properties {get;}
ProviderId           Property   System.Nullable[guid] ProviderId {get;}
ProviderName         Property   string ProviderName {get;}
Qualifiers           Property   System.Nullable[int] Qualifiers {get;}
RecordId             Property   System.Nullable[long] RecordId {get;}
RelatedActivityId    Property   System.Nullable[guid] RelatedActivityId {get;}
Task                 Property   System.Nullable[int] Task {get;}
TaskDisplayName      Property   string TaskDisplayName {get;}
ThreadId             Property   System.Nullable[int] ThreadId {get;}
TimeCreated          Property   System.Nullable[datetime] TimeCreated {get;}
UserId               Property   System.Security.Principal.SecurityIdentifier UserId {get;}
Version              Property   System.Nullable[byte] Version {get;}

In this example I added [code]-MaxEvents 1[/code] to the [code]Get-WinEvent[/code] command. Without this, [code]Get-WinEvent[/code] will retrieve all events from the specified Security event log source before sending the output to the pipeline and [code]Get-Member[/code] (e.g., it will be slow, and probably require a lot of RAM). Instructing [code]Get-WinEvent[/code] to stop collecting events after the first event gets the property information we want without waiting to collect all event log data.

[code]Get-WinEvent[/code] shows a lot of available property information for us to work with. I will often get property information by looking at the actual values with [code]Select-Object[/code] as well:

PS C:\Windows\system32> Get-WinEvent -LogName Security -MaxEvents 1 | Select-Object -Property *


Message              : Credential Manager credentials were read.

                        Security ID:            S-1-5-18
                        Account Name:           SEC504STUDENT$
                        Account Domain:         SEC504
                        Logon ID:               0x3E7
                        Read Operation:         Enumerate Credentials

                       This event occurs when a user performs a read operation on stored credentials in Credential Manager.
Id                   : 5379
Version              : 0
Qualifiers           :
Level                : 0
Task                 : 13824
Opcode               : 0
Keywords             : -9214364837600034816
RecordId             : 31056
ProviderName         : Microsoft-Windows-Security-Auditing
ProviderId           : 54849625-5478-4994-a5ba-3e3b0328c30d
LogName              : Security
ProcessId            : 672
ThreadId             : 4652
MachineName          : Sec504Student
UserId               :
TimeCreated          : 7/12/2022 12:02:31 PM
ActivityId           : 4352e2d2-8bc2-0001-59e3-5243c28bd801
RelatedActivityId    :
ContainerLog         : Security
MatchedQueryIds      : {}
Bookmark             : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName     : Information
OpcodeDisplayName    : Info
TaskDisplayName      : User Account Management
KeywordsDisplayNames : {Audit Success}
Properties           : {System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventProperty,
                       System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventProperty...}

Filtering Event Logs with the Pipeline

Seeing the properties that are available, we can query the event log for specific properties using [code]Where-Object[/code]. For example, we can look for any event information for event ID 1102 (The audit log was cleared):

PS C:\Windows\system32> Get-WinEvent -LogName Security | Where-Object -Property Id -EQ 1102 | Format-List -Property TimeCreated,Message


TimeCreated : 6/26/2022 10:34:08 AM
Message     : The audit log was cleared.
              Subject:
                Security ID:    S-1-5-21-2977773840-2930198165-1551093962-1000
                Account Name:   Sec504
                Domain Name:    SEC504STUDENT
                Logon ID:       0x1BD38

Although you can use the pipeline to filter the results of [code]Get-WinEvent[/code], it's not ideal. The PowerShell pipeline works in serial, where each command in the pipeline has to complete execution before sending the data to the next command. For event logs with lots of logging data, this can take a long time and uses up more RAM (and disk I/O) than absolutely necessary.

Filtering Event Logs with FilterHashTable

[code]Get-WinEvent[/code] can filter using a filter hash table. A hash table (a.k.a. associative array or dictionary) is a mechanism to specify properties and values. When used with the [code]-FilterHashTable[/code] option, we can specify attributes to filter the events returned in an optimal manner without relying on the PowerShell pipeline.

PS C:\Windows\system32> Get-WinEvent -FilterHashtable @{LogName='Security'; ID=1102 } | Format-List -Property TimeCreated,Message


TimeCreated : 6/26/2022 10:34:08 AM
Message     : The audit log was cleared.
              Subject:
                Security ID:    S-1-5-21-2977773840-2930198165-1551093962-1000
                Account Name:   Sec504
                Domain Name:    SEC504STUDENT
                Logon ID:       0x1BD38

Let's break down this command step-by-step:

  • [code]Get-WinEvent -FilterHashtable[/code]: Run [code]Get-WinEvent[/code], specifying that a filter hash table will follow as the next argument
  • [code]@{[/code]: Specify the beginning of a hash table with [code]@{[/code]
  • [code]LogName='Security';[/code]: Indicate the log name for filtering, then end the hash table element with a semicolon
  • [code]ID=1102[/code]: Indicate the event ID type for filtering; no trailing semicolon is needed in the last filter hash table element
  • [code]}[/code]: End the filter hash table
  • [code]| Format-List -Property TimeCreated,Message[/code]: Continue the pipeline to display the time created and message fields

Here's why you should use [code]-FilterHashTable[/code]:

  • Speed: In this example, using a filter hash table for a specific event ID is about 250x faster than using [code]Where-Object[/code] in the pipeline
  • Flexibility: When building a filter hash table you have a lot more flexibility in how you are filtering events (more on this later)
  • Clarity: Once you recognize the syntax for a hash table [code]@{}[/code], it becomes more clear what the filter is trying to accomplish vs. using a long, awkward pipeline

Next, let's look at an example of how you might apply using the filter hash table in practice during an incident.

Event Log Interrogation for Incident Response

[code]Get-WinEvent[/code] accepts a filter hash table with multiple values (adapted from the Microsoft documentation):

Key nameData typeWildcard Support
LogName<String[]>Yes
ProviderName<String[]>Yes
Path<String[]>No
Keywords<Long[]>No
ID<Int32[]>No
Level<Int32[]>No
StartTime<DateTime>No
EndTime<DateTime>No
UserID<SID>No
Data<String[]>No
<named-data><String[]>No

In an incident, you may want to interrogate logging events between a specific start and end date range. First, specify the start and end dates using [code]Get-Date[/code]:

PS C:\Windows\System32> $startDate = Get-Date 7/1/2022
PS C:\Windows\System32> $endDate = Get-Date 7/12/2022
PS C:\Windows\System32>

Next, Run [code]Get-WinEvent[/code] with a hash table, specifying the [code]LogName[/code], [code]StartTime[/code], and [code]EndTime[/code] elements:

PS C:\Windows\System32> Get-WinEvent -FilterHashtable @{LogName='Security'; StartTime=$startDate; EndTime=$endDate}


   ProviderName: Microsoft-Windows-Security-Auditing

TimeCreated                      Id LevelDisplayName Message
-----------                      -- ---------------- -------
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
7/11/2022 9:58:15 PM           5379 Information      Credential Manager credentials were read....
...
7/1/2022 8:10:44 PM            4624 Information      An account was successfully logged on....
7/1/2022 8:03:46 PM            4672 Information      Special privileges assigned to new logon....
7/1/2022 8:03:46 PM            4624 Information      An account was successfully logged on....
7/1/2022 7:58:10 PM            4672 Information      Special privileges assigned to new logon....
7/1/2022 7:58:10 PM            4624 Information      An account was successfully logged on....

In the next article on Working with the PowerShell Event Log, we'll dig into more practical ways to apply these PowerShell skills for threat hunting. Stay tuned!

-Joshua Wright

Return to Getting Started With PowerShell


Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.

Month of PowerShell - Working with the Event Log, Part 1 | SANS Institute