Because I don't work in a corporate environment, I don't always see opportunities where PowerShell can make your life better as an IT professional. I have a friend -- let's call her Gladys Kravitz. Gladys and I were chatting and she mentioned how tricky it is to pull information out of Windows event logs. If I recall, she was looking at 4625 events in the Security log which represents failed logon attempts. Here's an example:
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Reading the message it is clear that user Aprils in the Company domain tried to logon to Win10. But how would you get that information from event logs on 100 of machines? You could resort to some regular expression voodoo to extract the information. The values are actually part of the event log record. You may have seen ReplacementStrings when using Get-Eventlog. When using Get-WinEvent these values are stored as properties.
Assuming these values were consistent, you could write a function to create a custom object from these values. But I'm always thinking of the bigger picture. What Gladys really wanted is a way to turn the event log record into a structured object she could use in PowerShell. This issue with replacement strings goes beyond this particular situation.
XML to the Rescue
You probably don't think of XML as a solution, but in this case, it XML saves the day. The event log record object you get from Get-WinEvent includes a method to create an XML version.
$r = get-winevent -FilterHashtable @{Logname="Security";ID=4625} -MaxEvents 1 -ComputerName Win10 [xml]$evt = $r.ToXml()
This document has properties that expose the data used to construct the event log record.
The Data node looks promising.
That almost looks like an object! I'll make one.
$evt.Event.EventData.Data | foreach-object -Begin {$h = @{}} -Process {
$h.add($_.name,$_.'#text')
} -end { $obj = New-Object -TypeName PSObject -Property $h }
I can build a tool around this to convert event log records into more meaningful objects. And that's exactly what I did for Gladys.
Convert-EventLogRecord
I wrote a function called Convert-EventLogRecord that is part of the latest release of my PSScriptTools module. The function is designed to convert event log entries into custom objects. The function creates a custom object with the data properties I showed, but other information as well such as event id, log name and computername.
Now Gladys has a PowerShell tool to get the information she wants in the form she wants.
This isn't a perfect tool. Some even log entries don't have extra data. Or the data they have is just a list of strings. In those cases, the data is stored as RawProperties.
But once I know that I can write a control script or other tooling.
As you explore different events in different logs you figure out what things look like. Sometimes the event log data isn't helpful. But with a little PowerShell effort on your part, you can create meaningful results. You should be able to run this code on your desktop.
Get-WinEvent -FilterHashtable @{Logname ='system';ID =7040} -MaxEvent 50 |
Convert-EventlogRecord |
Sort-Object -Property Param4,TimeCreated -Descending |
Format-Table -GroupBy @{Name="Service";Expression={$_.param4}} -Property TimeCreated,
@{Name="OriginalState";Expression = {$_.param2}},
@{Name="NewState";Expression={$_.param3}},Computername
The results show you when services changed state.
I've been showing results as formatted tables. But you can do whatever you want because the Convert-EventLogRecord function is writing an object to the pipeline. Run whatever Get-Winevent query or command you want, convert the results, and then do what you need to with the results.
I gave the code to Gladys to try out but now you can get it as well in the PSScriptTools module, beginning with version 2.13. I hope you'll give it a try and let me know what you think.
I have a very similar function to this. The one thing I’m left wanting is a way to replace all the placeholders with their insertion strings. Here’s a summary of the issue that I had written up before. >>>>Values like “%%2307” (or with only a single leading “%”) are insertion string placeholders. Messages are formed from message text files, which typically are compiled as .DLLs but can also be included in .EXEs (and maybe other) resources. The location of these message text files is stored in the registry under subkeys of HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog, that corresponds with the specific logname and source. So essentially you have have something like HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\\. Once you locate the correct key, the location data is stored in a value named EventMessageFile, which points to the path of the .DLL (or other type of file). There can also be a value for CategoryMessageFile, and ParameterMessageFile (these could all point to the same file, or different ones). As I understand it, the ParameterMessageFile is where the insertion strings are defined for the placeholders which begin with a double percent sign (%%xxxx).
So far I haven’t found any way to parse a message text file for insertion strings which correspond to their numbers.
The only bright side is that the message property of an event has already gone through the process of formatting (probably through the use of the FormatMessage function – https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-formatmessage), substituting all the placeholders with their insertion strings corresponding with the proper language.<<<<<<<
Care to take up the challenge?
Hi Nolan,
Have a look at this: https://github.com/wightsci/MessageTableReader – it’s a .Net class I put together to read the substitution strings from a .dll . You can use it within a PowerShell module or script, or compile it into a .dll .
Stuart
Stuart,
Thank you so much! I just remembered to check again whether I had received any replies (never got an email notification) and found your comment. I spent a few minutes experimenting with your code and I like what I see. I’m going to have spend some time to integrate that .Net class into my PS function.
I might try modifying it so that GetMessageList() (or another method) returns a hashtable. I have a semi-idea of using that as a cached lookup table, though I’ve yet to do any performance testing with your class as-is.
Again, many thanks for your contribution. I hope I can find the time to check out your eBook as well.
Nolan