More Eventlog Eventing
In this issue:
Last time we started looking at creating event subscribers for new event log entries. This is a handy technique when you want to monitor specific events but there isn't a direct event that you can subscribe to. If the thing you are interested in writes an event log entry, you can create an event subscriber for that.
The first step is to identify the event log you need to monitor This is because the type of event log will dictate how you subscribe to it. In the previous article, we looked at the classic event logs like System and Application. These are the types of event logs that have been around since the days of Windows NT. However, they also include newer logs like Windows PowerShell.
Windows Vista introduced a new event log format and many new logs that we refer to as operational or admin logs.
Operational and Admin Logs
Before you can create an event subscription, you need to identify the event log you want to monitor.The best way is to use Get-WinEvent. You can run this command to list all event logs.
Get-WinEvent -ListLog *
Note that this will also include classic event logs. But they are easy to filter out.
PS C:\> Get-WinEvent -ListLog * | Where {-Not $_.IsClassicLog -AND $_.RecordCount -gt 0 }
LogMode MaximumSizeInBytes RecordCount LogName
------- ------------------ ----------- -------
Circular 1052672 2428 Setup
Circular 15728640 1116 PowerShellCore/Operational
Circular 1052672 27 OpenSSH/Operational
Circular 1052672 1115 Microsoft-Windows-WMI-Activity/Operational
Circular 1052672 1423 Microsoft-Windows-WLAN-AutoConfig/Operational
...
EventLogWatcher
To monitor these type of event logs, you will need to use a special .NET class, the System.Diagnostics.Eventing.Reader.EventLogWatcher. I can discover a bit more by using the Get-TypeMember command from the PSScriptTools module.
PS C:\> Get-TypeMember System.Diagnostics.Eventing.Reader.EventLogWatcher
Type: System.Diagnostics.Eventing.Reader.EventLogWatcher
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
EventRecordWritten Event
GetType Method Type
Enabled Property Boolean
I can also check the constructor, or how to create a new instance.
PS C:\> Get-TypeConstructor System.Diagnostics.Eventing.Reader.EventLogWatcher
[System.Diagnostics.Eventing.Reader.EventLogWatcher]::new([System.String]$Path))
[System.Diagnostics.Eventing.Reader.EventLogWatcher]::new([System.Diagnostics.Eventing.Reader.EventLogQuery]$eventQuery))
[System.Diagnostics.Eventing.Reader.EventLogWatcher]::new([System.Diagnostics.Eventing.Reader.EventLogQuery]$eventQuery),[System.Diagnostics.Eventing.Reader.EventBookmark]$Bookmark))
[System.Diagnostics.Eventing.Reader.EventLogWatcher]::new([System.Diagnostics.Eventing.Reader.EventLogQuery]$eventQuery),[System.Diagnostics.Eventing.Reader.EventBookmark]$Bookmark),[System.Boolean]$readExistingEvents))
> Get-TypeConstructor is also part of PSScriptTools.
Creating a Watcher
Let's create a simple watcher for the Windows Remote Management log.
PS C:\> $elw = [System.Diagnostics.Eventing.Reader.EventLogWatcher]::new("Microsoft-Windows-WinRM/Operational")
PS C:\> $elw |
Enabled
-------
False
The event log watcher has a single property. Before I can use it, I need to enable it.
$elw.Enabled = $true
Registering an Event Subscriber
To use the watcher, you will need to use the Register-ObjectEvent cmdlet. In order to use the watcher on an object, in this case $elw, you need to subscribe to a specific event. If you look back at the output of Get-TypeMember, you can see that the event is called EventRecordWritten.
The other properties are similar to other event subscribers you have created in the past.
Register-ObjectEvent -InputObject $elw -EventName "EventRecordWritten" -MessageData "Something happened!" -SourceIdentifier "WatchWSMan"
There isn't any polling like we had with WMI/CIM event subscribers. I also am watching for any event in the log. I'll cover filtering later.
Processing an Event
Eventually, I'll get an event.
PS C:\> Get-Event -id 1
ComputerName :
RunspaceId : e3b1a2ab-a387-4c51-91f9-2efff8b71516
EventIdentifier : 1
Sender : System.Diagnostics.Eventing.Reader.EventLogWatcher
SourceEventArgs : System.Diagnostics.Eventing.Reader.EventRecordWrittenEventArgs
SourceArgs : {System.Diagnostics.Eventing.Reader.EventLogWatcher, System.Diagnostics.Eventing.Reader.EventRecordWrittenEventArgs}
SourceIdentifier : WatchWSMan
TimeGenerated : 12/8/2025 4:11:47 PM
MessageData : Something happened!
As with previous event subscribers, I can use the SourceEventArgs property to get more information about the event.
PS C:\> (Get-Event -id 1).SourceEventArgs
EventRecord EventException
----------- --------------
System.Diagnostics.Eventing.Reader.EventLogRecord
PS C:\> (Get-Event -id 1).SourceEventArgs.EventRecord
ProviderName: Microsoft-Windows-WinRM
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
12/8/2025 4:11:47 PM 91 Information
Never assume this is all there is.
PS C:\> (Get-Event -id 1).SourceEventArgs.EventRecord | Select-Object *
Id : 91
Version : 0
Qualifiers :
Level : 4
Task : 9
Opcode : 0
Keywords : 4611686018427387908
RecordId : 321427
ProviderName : Microsoft-Windows-WinRM
ProviderId : a7975c8f-ac13-49f1-87da-5a984a4ab417
LogName : Microsoft-Windows-WinRM/Operational
ProcessId : 17256
ThreadId : 35408
MachineName : Prospero
UserId : S-1-5-21-3465062479-264850141-705915528-1001
TimeCreated : 12/8/2025 4:11:47 PM
ActivityId : 0f0b7f5d-67b9-0006-9edc-110fb967dc01
RelatedActivityId :
ContainerLog : Microsoft-Windows-WinRM/Operational
MatchedQueryIds : {}
Bookmark : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName : Information
OpcodeDisplayName : Info
TaskDisplayName : Request handling
KeywordsDisplayNames : {Server}
Properties : {System.Diagnostics.Eventing.Reader.EventProperty}
Retrieving Event Data
Depending on the log, you might see different information. In this case, what I don't see is the message text. One way around that is to use the event recored's FormatDescription() method.
PS C:\> $r = (Get-Event -id 1).SourceEventArgs.EventRecord
PS C:\> $r.FormatDescription()
Creating WSMan shell on server with ResourceUri: http://schemas.microsoft.com/powershell/Microsoft.PowerShell (PROSPERO\jeff clientIP: fe80::eef9:5d0d:89a8:d794%20)
With this information, I can process all the events.
PS C:\> Get-Event | Sort TimeGenerated | Select TimeGenerated,EventIdentifier,
@{Name="EventID";Expression = {$_.SourceEventArgs.EventRecord.ID}},
@{Name="Operation";Expression = {$_.SourceEventArgs.EventRecord.OpcodeDisplayName}},
@{Name="Task";Expression = {$_.SourceEventArgs.EventRecord.TaskDisplayName}},
@{Name="Message";Expression = {$_.SourceEventArgs.EventRecord.FormatDescription()}},
@{Name="Computername"; Expression = {$_.SourceEventArgs.EventRecord.MachineName.ToUpper()} }
TimeGenerated : 12/8/2025 4:11:47 PM
EventIdentifier : 1
EventID : 91
Operation : Info
Task : Request handling
Message : Creating WSMan shell on server with ResourceUri: http://schemas.microsoft.com/
powershell/Microsoft.PowerShell (PROSPERO\jeff clientIP: fe80::eef9:5d0d:89a8:d794%20)
Computername : PROSPERO
TimeGenerated : 12/8/2025 4:41:27 PM
EventIdentifier : 2
EventID : 145
Operation : Info
Task : Request handling
Message : WSMan operation Enumeration started with resourceUri http://schemas.microsoft.com/
wbem/wsman/1/config/listener
Computername : PROSPERO
...