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.
BEHOLD: The Windows event log.
Get-WinEvent vs. Get-EventLog
Microsoft has two commands for interrogating Windows event logs: Get-WinEvent and Get-EventLog.
The Get-EventLog cmdlet uses a Win32 API that has been deprecated, so Microsoft recommends using Get-WinEvent. However, Reddit wisdom indicates that Get-WinEvent can sometimes be slower than Get-EventLog.
In practice, Get-WinEvent 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 Get-EventLog may still work for you, but as a deprecated API there's no guarantee Microsoft will continue to make Get-EventLog available in the future.
In this article we'll use Get-WinEvent to get the best support for access event log data from PowerShell.
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 Get-WinEvent -ListLog *:
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 ...
Most of the Get-WinEvent commands will require an administrative PowerShell session. Open PowerShell as an administrator to follow along with these commands!
A lot of the event log sources on my test system are empty. We can use the pipeline and Where-Object 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:
- Get-WinEvent -ListLog * | : List all the event log sources using the wildcard *, start the pipeline with |
- Where-Object -Property RecordCount -GT 0 | : Filter the results to return only event log sources where the RecordCount property is greater than (-GT) zero, continue the pipeline with |
- Select-Object -Property LogName, RecordCount: Select the LogName and RecordCount properties for display
In this example we retrieved all of the event log sources using -Listlog , 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 applocker* 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 Get-WinEvent -LogName. 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, Get-WinEvent will display the TimeCreated, Id, LevelDisplayName, and Message fields. This output will be truncated unless you have a very small font or a very wide PowerShell window. I will often use Format-List 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 Get-WinEvent output using Get-Member:
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 -MaxEvents 1 to the Get-WinEvent command. Without this, Get-WinEvent will retrieve all events from the specified Security event log source before sending the output to the pipeline and Get-Member (e.g., it will be slow, and probably require a lot of RAM). Instructing Get-WinEvent to stop collecting events after the first event gets the property information we want without waiting to collect all event log data.
Get-WinEvent 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 Select-Object 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 Where-Object. For example, we can look for any event information for event ID 1102 (The audit log was cleared):
Be prepared to wait. This command will take a while.
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 Get-WinEvent, 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
Get-WinEvent 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 -FilterHashTable 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:
- Get-WinEvent -FilterHashtable: Run Get-WinEvent, specifying that a filter hash table will follow as the next argument
- @{: Specify the beginning of a hash table with @{
- LogName='Security';: Indicate the log name for filtering, then end the hash table element with a semicolon
- ID=1102: Indicate the event ID type for filtering; no trailing semicolon is needed in the last filter hash table element
- }: End the filter hash table
- | Format-List -Property TimeCreated,Message: Continue the pipeline to display the time created and message fields
NOTE: When using -FilterHashTable, you must specify a LogName in the hash table, not using the -LogName cmdlet argument.
Here's why you should use -FilterHashTable:
- Speed: In this example, using a filter hash table for a specific event ID is about 250x faster than using Where-Object 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 @{}, 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
Get-WinEvent accepts a filter hash table with multiple values (adapted from the Microsoft documentation):
Key name | Data type | Wildcard 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 Get-Date:
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 Get-WinEvent with a hash table, specifying the LogName, StartTime, and EndTime 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.