Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

PowerShell Event Log Mining

Posted on May 7, 2021May 7, 2021

The other day someone who is learning PowerShell reached out to me with a problem. He couldn't understand why the relatively simple PowerShell expression to pull information from the System event log wasn't working. He wasn't seeing errors, but he also wasn't seeing the events he was expecting. Searching event logs with PowerShell is a common task. But as you'll see, you may need to update your approach to mining event logs with PowerShell. Things change in the PowerShell world, and sometimes in subtle ways that you may not notice. Although to be fair, some of these changes my arise from new versions of the .NET Framework and/or Windows 10. Here's what we encountered.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Get-EventLog

From the very beginning we've used Get-EventLog to search classic event logs like System and Application. And that's what my student was doing as well in Windows PowerShell. He was searching the System event log for event id 1074 which indicates a computer restart. He was using code like this:

Get-EventLog -log system -newest 1000 |
Where-Object {$_.eventid -eq '1074'}  |
Format-Table machinename, username, timegenerated -autosize

There's technically nothing wrong with this. I ran it and got 3 results. Then I double-checked the help to make sure I wasn't forgetting anything. That's when I saw the note indicating that Get-EventLog uses a deprecated Win32API. The note goes on to say that results may not be accurate. And since I know I've restarted my computer more than 3 times, this warning was right on. I occasionally still fire up a Get-EventLog command because the muscle memory is so strong. But now I know I really need to break this habit.

I knew that Get-EventLog isn't in PowerShell 7 and that you have to use Get-WinEvent. So I suggested going down that route.

Get-WinEvent

I'll be the first to admit that Get-WinEvent is a bit more complicated to learn, but it is also much more efficient. Here's an equivalent approach:

Get-WinEvent -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Format-Table Machinename,UserID,TimeCreated

When I run this I get 97 events which is considerably more accurate. The output from Get-WinEvent is different than Get-EventLog so you need to adjust property names. But filtering is much faster and easier. Now I can filter for the event ID early and not rely on Where-Object.

One critical difference for this particular task, is that we want to display the username. But Get-WinEvent reports a SID.

Fortunately, that property includes a method to translate the SID. Here's my revised code.

Get-WinEvent -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Select-Object @{Name="Computername";Expression = {$_.machinename}},
@{Name="UserName";Expression = {$_.UserId.translate([System.Security.Principal.NTAccount]).value}}, TimeCreated

The Translate() method may not always resolve the SID based. For example, I have credentials to query another laptop from my desktop, but I can't translate the SID other than the generic SYSTEM account. Fortunately, the replacement strings used in the event log record are stored under a "Properties" property.

The last item in this array is the user account. There's also some other useful information such as the type of restart event. With this in mind, I'll revise my code so that I can query remote machines.

Get-WinEvent -computer thinkp1 -filterhash @{Logname = 'system';ID=1074} -MaxEvents 1000 |
Select-Object @{Name="Computername";Expression = {$_.machinename}},
@{Name="UserName";Expression = { ($_.properties[-1]).value}}, TimeCreated,
@{Name="Category";Expression = {$_.properties[4].value}}

Much better.

Function Time

More than likely, the original code was something that would be run periodically. So instead of always having to type the code, creating a PowerShell function around it is the smart move. I already have Get-Winevent expression that works so that will be the center of my function. I always stress the importance of getting your core code to work at a console prompt first. Then build the function around it.

Because you want functions to be flexible, I thought a bit about what parameters I might need. Even though the original code was searching the local computer's event log, it isn't a stretch to imagine wanting to search a remote computer and Get-WinEvent supports that. As well as alternate credentials. I decided to keep the MaxEvents parameter. But I also imagined situations where I wanted to find restart events after a certain date.

Param(
    [Parameter(Position = 0, ValueFromPipeline)]
    [ValidateNotNullOrEmpty()]
    [Alias("CN")]
    [string]$Computername = $env:COMPUTERNAME,
    [Parameter(HelpMessage = "Find restart events since this date and time.")]
    [ValidateNotNullOrEmpty()]
    [Alias("Since")]
    [datetime]$After,
    [int64]$MaxEvents,
    [PSCredential]$Credential
)

You'll notice I kept the same parameter names. There's no reason to reinvent the wheel. Although I added a few parameter aliases. My Get-WinEvent command is going to use a filtering hashtable, so I'll build that on the fly.

$filter = @{
    Logname = "System"
    ID      = 1074
}
if ($After) {
    Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Getting restart events after $After"
    $filter.Add("StartTime", $After)
}

The next step is to define a hashtable of Get-WinEvent parameter that I can splat. Splatting isn't always required or necessary but in this case it keeps my code simple.

$entries = Get-WinEvent @splat

Objects, Objects, Objects

You always want your functions to write objects to the pipeline. I could have used the native output from Get-WinEvent, but the original command only wanted a few properties so I'll do the same. I like creating custom objects like this:

foreach ($entry in $entries) {
    #resolve the user SID
    Try {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Translating $($entry.UserId)"
        $user = $entry.UserId.translate([System.Security.Principal.NTAccount]).value
    }
    Catch {
        $user = $entry.properties[-1].value
        #$entry.userid
    }

    [pscustomobject]@{
        PSTypeName   = "RestartEvent"
        Computername = $entry.machinename.ToUpper()
        Datetime     = $entry.TimeCreated
        Username     = $user
        Category     = $entry.properties[4].value
        Process      = $entry.properties[0].value.split()[0].trim()
    }
} #foreach item

My function is using both techniques to resolve the user SID for the sake of education.

Polishing PowerShell

What I have to this point is useful. I've changed to using Get-WinEvent to search event logs and I've built a simple, reusable tool around it that I can use at a PowerShell prompt. But how about putting a high polish on this function? For example, even though the default output shows as a list, I know a table view would be easier to read. And how about a way to make different categories jump out?

You'll notice that my custom hashtable defines a typename. This is so that I can create a custom format ps1xml file using New-PSFormatXML. In the .ps1 file that defines the function I'll also load the format file.

Update-FormatData $PSScriptRoot\restartevent.format.ps1xml

In the format file, I'm going to group the output by computername. I'm also going to add some color coding using ANSI escape sequences.

You can grab the complete function and format file from Github. And even if you don't need the function, begin making the transition to using Get-WinEvent. It will be a little tricky at first, but it will be worth your time.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

6 thoughts on “PowerShell Event Log Mining”

  1. Pingback: Event Log Mining with Powershell – Curated SQL
  2. yang says:
    May 20, 2021 at 4:15 am

    Hi Jeff,

    Do you have any good idea how we can validate a user name and password combi is valid in powershell?

    One big frustration when using powershell to set Windows Service or IIS pool credential is that you only know the error when the account is locked(unattended deployment). Because the error only emerges in eventviewer later on.

    Thanks in advance.

    1. Jeffery Hicks says:
      May 20, 2021 at 9:07 am

      The only way you can tell a credential is good is to use it. I suppose you could verify the username by comparing the credential name with a source. But you won’t know if the password is good until you use it.

      1. Curtis Dove says:
        July 16, 2021 at 3:23 pm

        Jeff, can we get restart-computer cmdlet a -reason parameter 😉 ? to put as the Reason for the Event Log Parsing…

      2. Jeffery Hicks says:
        July 19, 2021 at 12:13 pm

        You should post this as an issue at https://github.com/PowerShell/PowerShell/issues. Don’t expect to see this added to Windows PowerShell unless you write your own version of Restart-Computer to add this functionality.

  3. yang says:
    May 25, 2021 at 4:10 pm

    Ok, thanks lot

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d