Query Event Logs Efficiently with PowerShell Get-EventLog

Published:14 June 2019 - 7 min. read

Today’s sponsor is n8n, the AI-native workflow automation tool built for ITOps and DevSecOps. With 100+ templates to get you started quickly and a powerful visual editor, you can automate complex workflows without giving up control. Check it out here.

 

 

 

 

 

Every Windows system administrator is probably familiar with the Windows Event Log. Using this cmdlet in PowerShell allows sysadmins to parse lots of events at once across many computers at once. It frees sysadmins up from clicking around in the Event Viewer trying to figure out just the right filter to use and to determine where precisely that critical event is stored. However, Get-EventLog does have its downfalls which you’ll see.

Not a reader? Watch this related video tutorial!
Not seeing the video? Make sure your ad blocker is disabled.

Listing Event Logs with Get-EventLog

The Get-EventLog cmdlet is available on all modern versions of Windows PowerShell. At it’s most straightforward use, this cmdlet needs an event log to query which it will then display all events in that event log.

But what if you don’t know the event log name in the first place? In that case, we need to figure out all of the event logs that are available on our local computer. We do that by using the command Get-EventLog -List.

Get-EventLog -List
Get-EventLog -List

You can see I’ve got a few event logs on my local system now, but you might be wondering where are the others? There are dozens of other event logs showing up under Applications and Services logs in the Event Viewer. Why aren’t they here?

If you need those events, unfortunately, Get-EventLog isn’t going to work. Instead, you’ll need to check out Get-WinEvent. The Get-EventLog cmdlet could be considered a legacy cmdlet at this point, but it’s one I still use frequently simply because it’s just so easy to use.

Querying Events with Get-EventLog

Now that we know all of the events logs available, we can now read events within that event log. Maybe I want to see all events in the Application event log. To get those events, I need to specify the LogName parameter with Get-EventLog and the cmdlet will oblige by returning all events in that event log.

Get-EventLog -LogName Application
Get-EventLog -LogName Application

By default, you’ll only see six properties in the output:

  • Index
  • Time
  • EntryType
  • Source
  • InstanceId
  • Message

In actuality, Get-EventLog returns 16 of them. The reason you only see six is due to PowerShell formatting rules which define the output. Below is an example of the actual output found by piping Get-EventLog to Select-Object and selecting all of the properties.

Get-EventLog -LogName Application | Select-Object -Property * EventID
Get-EventLog -LogName Application | Select-Object -Property * EventID

Filtering with Get-EventLog

Chances are when looking for events, we don’t need all events. Instead, we only need a few. In that case, we need to filter for particular events. Get-EventLog has a few different ways to do this. The Get-EventLog cmdlet can filter based on timestamp, entry type, event ID, message, source, and username. This takes care of the majority of ways to find events.

To demonstrate filtering, perhaps I’m querying for events every so often, and I want to find the ten newest events. In that case, I can use the Newest parameter and specify how many events I’d like to see. Get-EventLog -LogName Application -Newest 10 will return only the latest ten events.

Perhaps I want to find all events after a particular point in time. For that, we have the After parameter. The After parameter takes a date/time, so if I’d like to find only the events within the Application log that happened after 1/26/19 10:17 AM, I could do this Get-EventLog -LogName Application -After '1/26/19 10:17'. We could also perform the same process but select events that happened before a certain date with, you might have guessed it, the Before parameter.

The Get-EventLog has a lot of different ways to filter not including based on a timestamp. We can also filter events based on other attributes like event ID (Instance ID) and message which tend to be common attributes to search on. Maybe I know I’m looking for an event with an ID of 916; we’d pass 916 to the InstanceId parameter.

PS> Get-EventLog -LogName Application -InstanceId 916

We can combine filters too. Maybe I get a lot of events returned with an ID of 916, but I want those events with the string svchost in the message. In that case, we can add the Message parameter to Get-EventLog and specify a wildcard like svchost.

PS> Get-EventLog -LogName Application -InstanceId 916 -Message '*svchost*'

Bonus Script!

Do you need a great example of using Get-EventLog in a real-world script? If so, you’re in luck! Below is an advanced use case of Get-EventLog you can download and use today!

<#
.SYNOPSIS
    This script searches a Windows computer for all event log or text log entries
    between a specified start and end time.
.DESCRIPTION
    This script enumerates all event logs within a specified timeframe and all text logs 
 that have a last write time in the timeframe or contains a line that has a date/time
 reference within the timeframe.  It records this information to a set of files and copies
 any interesting log file off for further analysis.
.EXAMPLE
    PS> .\Get-EventsFromTimeframe.ps1 -StartTimestamp '01-29-2014 13:25:00' -EndTimeStamp '01-29-2014 13:28:00' -ComputerName COMPUTERNAME
 
    This example finds all event log entries between StartTimestamp and EndTimestamp and any file
    with an extension inside the $FileExtension param that either was last written within the timeframe
    or contains a line with a date/time reference within the timeframe.
.PARAMETER StartTimestamp
 The earliest date/time you'd like to begin searching for events.
.PARAMETER EndTimestamp
 The latest date/time you'd like to begin searching for events.
.PARAMETER Computername
 The name of the remote (or local) computer you'd like to search on.
.PARAMETER OutputFolderPath
 The path of the folder that will contain the text files that will contain the events.
.PARAMETER LogAuditFilPath
 The path to the text file that will document the log file, line number and match type
 to the logs that were matched.
.PARAMETER EventLogsOnly
 Use this switch parameter if you only want to search in the event logs.
.PARAMETER LogFilesOnly
 Use this parameter if you only want to search for logs on the file system.
.PARAMETER ExcludeDirectory
 If searching on the file system, specify any folder paths you'd like to skip.
.PARAMETER FileExtension
 Specify one or more comma-delimited set of file extensions you'd like to search for on the file system.
 This defaults to 'log,txt,wer' extensions.
#>
[CmdletBinding(DefaultParameterSetName = 'Neither')]
param (
    [Parameter(Mandatory)]
    [datetime]$StartTimestamp,
    [Parameter(Mandatory)]
    [datetime]$EndTimestamp,
    [Parameter(ValueFromPipeline,
        ValueFromPipelineByPropertyName)]
    [string]$ComputerName = 'localhost',
    [Parameter()]
 [string]$OutputFolderPath = ".\$Computername",
 [Parameter(ParameterSetName = 'LogFiles')]
 [string]$LogAuditFilePath = "$OutputFolderPath\LogActivity.csv",
 [Parameter(ParameterSetName = 'EventLogs')]
 [switch]$EventLogsOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
    [switch]$LogFilesOnly,
    [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$ExcludeDirectory,
 [Parameter(ParameterSetName = 'LogFiles')]
 [string[]]$FileExtension = @('log', 'txt', 'wer')
)
 
begin {
 if (!$EventLogsOnly.IsPresent) {
 ## Create the local directory where to store any log files that matched the criteria
 $LogsFolderPath = "$OutputFolderPath\logs"
 if (!(Test-Path $LogsFolderPath)) {
 mkdir $LogsFolderPath | Out-Null
 }
 }
 
 function Add-ToLog($FilePath,$LineText,$LineNumber,$MatchType) {
 $Audit = @{
 'FilePath' = $FilePath;
 'LineText' = $LineText
 'LineNumber' = $LineNumber
 'MatchType' = $MatchType
 }
 [pscustomobject]$Audit | Export-Csv -Path $LogAuditFilePath -Append -NoTypeInformation
 }
}
 
process {
 
    ## Run only if the user wants to find event log entries
    if (!$LogFilesOnly.IsPresent) {
 ## Find all of the event log names that contain at least 1 event
 $Logs = (Get-WinEvent -ListLog * -ComputerName $ComputerName | where { $_.RecordCount }).LogName
 $FilterTable = @{
 'StartTime' = $StartTimestamp
 'EndTime' = $EndTimestamp
 'LogName' = $Logs
 }
 
 ## Find all of the events in all of the event logs that are between the start and ending timestamps
 $Events = Get-WinEvent -ComputerName $ComputerName -FilterHashtable $FilterTable -ea 'SilentlyContinue'
 Write-Verbose "Found $($Events.Count) total events"
 
 ## Convert the properties to something friendlier and append each event into the event log text file
 $LogProps = @{ }
 [System.Collections.ArrayList]$MyEvents = @()
 foreach ($Event in $Events) {
 $LogProps.Time = $Event.TimeCreated
 $LogProps.Source = $Event.ProviderName
 $LogProps.EventId = $Event.Id
 if ($Event.Message) {
 $LogProps.Message = $Event.Message.Replace("<code>n", '|').Replace("</code>r", '|')
 }
 $LogProps.EventLog = $Event.LogName
 $MyEvents.Add([pscustomobject]$LogProps) | Out-Null
 }
 $MyEvents | sort Time | Export-Csv -Path "$OutputFolderPath\eventlogs.txt" -Append -NoTypeInformation
 }
 
 ## Run only if the user wants to find log files
 if (!$EventLogsOnly.IsPresent) {
        ## Enumerate all remote admin shares on the remote computer.  I do this instead of enumerating all phyiscal drives because
        ## the drive may be there and the share may not which means I can't get to the drive anyway.
 $Shares = Get-WmiObject -ComputerName $ComputerName -Class Win32_Share | where { $_.Path -match '^\w{1}:\\$' }
 [System.Collections.ArrayList]$AccessibleShares = @()
 ## Ensure I can get to all of the remote admin shares
 foreach ($Share in $Shares) {
 $Share = "\\$ComputerName\$($Share.Name)"
 if (!(Test-Path $Share)) {
 Write-Warning "Unable to access the '$Share' share on '$Computername'"
 } else {
 $AccessibleShares.Add($Share) | Out-Null 
 }
 }
 
 $AllFilesQueryParams = @{
 Path = $AccessibleShares
 Recurse = $true
 Force = $true
 ErrorAction = 'SilentlyContinue'
 File = $true
 }
 ## Add any directories specified in $ExcludeDirectory param to not search for log files in
 if ($ExcludeDirectory) {
 $AllFilesQueryParams.ExcludeDirectory = $ExcludeDirectory 
 }
 ## Create the crazy regex string that I use to search for a number of different date/time formats.
 ## This is used in an attempt to search for date/time strings in each text file found
 ##TODO: Add capability to match on Jan,Feb,Mar,etc
 $DateTimeRegex = "($($StartTimestamp.Month)[\\.\-/]?$($StartTimestamp.Day)[\\.\-/]?[\\.\-/]$($StartTimestamp.Year))|($($StartTimestamp.Year)[\\.\-/]?$($StartTimestamp.Month)[\\.\-/]?[\\.\-/]?$($StartTimestamp.Day))"
 ## Enumerate all files matching the query params that have content
 Get-ChildItem @AllFilesQueryParams | where { $_.Length -ne 0 } | foreach {
 try {
 Write-Verbose "Processing file '$($_.Name)'"
 ## Record the file if the last write time is within the timeframe.  This finds log files that may not record a 
 ## date/time timestamp but may still be involved in whatever event the user is trying to find.
 if (($_.LastWriteTime -ge $StartTimestamp) -and ($_.LastWriteTime -le $EndTimestamp)) {
 Write-Verbose "Last write time within timeframe for file '$($_.Name)'"
 Add-ToLog -FilePath $_.FullName -MatchType 'LastWriteTime'
 }
 ## If the file found matches the set of extensions I'm trying to find and it's actually a plain text file.
 ## I use the Get-Content to just double-check it's plain text before parsing through it.
 if ($FileExtension -contains $_.Extension.Replace('.','') -and !((Get-Content $_.FullName -Encoding Byte -TotalCount 1024) -contains 0)) {
 ## Check the contents of text file to references to dates in the timeframe
 Write-Verbose "Checking log file '$($_.Name)' for date/time match in contents"
 $LineMatches = Select-String -Path $_.FullName -Pattern $DateTimeRegex
 if ($LineMatches) {
 Write-Verbose "Date/time match found in file '$($_.FullName)'"
 ## Record all of the matching lines to the audit file.
 foreach ($Match in $LineMatches) {
 Add-ToLog -FilePath $_.FullName -LineNumber $Match.LineNumber -LineText $Match.Line -MatchType 'Contents'
 }
 ## Create the same path to the log file on the remote computer inside the output log directory and 
 ## copy the log file with an event inside the timeframe to that path.
 ## This will not work if an file path above 255 characters is met.
 $Trim = $_.FullName.Replace("\\$Computername\", '')
 $Destination = "$OutputFolderPath\$Trim"
 if (!(Test-Path $Destination)) {
 ##TODO: Remove the error action when long path support is implemented
 mkdir $Destination -ErrorAction SilentlyContinue | Out-Null
 }
 Copy-Item -Path $_.FullName -Destination $Destination -ErrorAction SilentlyContinue -Recurse
 }
 }
 } catch {
 Write-Warning $_.Exception.Message 
 }
 }
 }
}

Summary

The Get-EventLog cmdlet is a great command to use if you ever find yourself needing to query one of the common event logs quickly. It’s easy to use and provides some basic filtering ability. However, if you need to do any in-depth event log sleuthing, the Get-WinEvent command will probably work better, but it’s a little harder to use and sometimes requiring knowing syntax like XPath.

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!