The Admin’s First Steps: Scan Multiple Event Logs

The Admin’s First Steps: Scan Multiple Event Logs

  • Comments 5
  • Likes

Summary: Richard Siddaway talks about using Windows PowerShell to automate scanning event logs across many remote machines.

Hey, Scripting Guy! Question Hey, Scripting Guy! I’ve just starting using Windows PowerShell to administer my systems, and I’ve been asked to test multiple remote machines for a particular event. How can I do that?

—CV

Hey, Scripting Guy! Answer Hello CV,

Honorary Scripting Guy, Richard Siddaway, here today. I’m filling in for my good friend, The Scripting Guy. You’re in luck because today I’ve got just the answer you are looking for as part of my series of posts about how an administrator can start making productive use of Windows PowerShell.

Event logs have been a part of Windows for more years than I care to remember. Books and courses regularly recommend that you check the event logs on a regular basis, but how many administrators have the time to perform this task? Not many, I suspect. The problem is that you have a lot of machines to look after, and it takes time to check the logs on each machine. Time you don’t have. This post will explain an easy way to monitor as many logs as you need.

Event logs come in two flavors. The standard event logs include:

  • System
  • Application
  • Security

They are joined by feature-specific logs, such as DNS and Active Directory.

Windows Vista introduced the Windows Event Log technology, and then came a whole bunch of extra logs—243 of them on a Windows Surface RT alone! Each log is dedicated to a specific part of the operating system, such as:

  • Microsoft-Windows-Audio/PlaybackManager
  • Microsoft-Windows-PowerShell/Operational
  • Microsoft-Windows-Windows Firewall With Advanced Security/Firewall
  • Microsoft-Windows-WinRM/Operational

There is a wealth of data in the new logs, but I’m going to concentrate on the classic logs for the rest of this post.

Windows PowerShell supplies two cmdlets for reading event logs. Get-EventLog has been with us since Windows PowerShell 1.0. It reads the classic event logs. Get-WinEvent reads both the classic and the new event logs.

As an example, I’m going to look at the events that are recorded when the event log service starts. This can be taken as an indicator of a machine start up. So counting the number of events of this type in a given time period gives you an indication of how many times the machine has restarted in that period. Let’s use a week for the sake of argument:

Get-EventLog -LogName System -InstanceId 2147489653 -After (Get-Date).Adddays(-7)

The log name is specified as is the InstanceId, which identifies the events you want. The –After parameter is supplied a date—in this case, one week in the past. The big question is, “How did I know that I wanted an InstanceId of 2147489653?”. Simple. I cheated. I dumped the last day’s events and scrolled through them until I found what I needed:

Get-EventLog -LogName System -After (Get-Date).Adddays(-1)

One thing to beware of is that in Windows 8 and Windows 8.1 systems, when you select Shut down from the power button, the machine doesn’t actually shut down. It’s in a suspended mode, which is why computers running Windows 8 start much faster than those running Windows 7. You will only get an entry in the log when the machine actually restarts.

As a quick aside, and I can never resist dipping into WMI. You can test the last boot up time of machine quite simply:

Get-CimInstance -ClassName Win32_OperatingSystem |

select -ExpandProperty LastBootUpTime

I always use the CIM cmdlets if I need to access date information because they convert the date into a readable date for you.

Now you know how to get the events. How do you count them? I’d like to introduce one of the most underused cmdlets: Measure-Object.

Get-EventLog -LogName System -InstanceId 2147489653 -After (Get-Date).Adddays(-7) |

 Measure-Object

 

Count    : 6

Average  :

Sum      :

Maximum  :

Minimum  :

Property :

You only want the count of the objects, so change the code to:

Get-EventLog -LogName System -InstanceId 2147489653 -After (Get-Date).Adddays(-7) |

Measure-Object | select -ExpandProperty Count

Or if you prefer:

(Get-EventLog -LogName System -InstanceId 2147489653 -After (Get-Date).Adddays(-7) |

Measure-Object).Count

Which one should you use? It’s your decision, based on your preferred coding style. I use the first method because it’s more obvious to me what I’m doing when I come back to read the code in a few months.

As an alternative, you can use Get-WinEvent:

$filter = @{

 LogName = 'System'

 Id = 6005

 StartTime = (Get-Date).AddDays(-7)

}

 

Get-WinEvent -FilterHashtable $filter

The difference is that you present the filtering information as a hash table. You can find the valid keys in the filter hash table in the TechNet Library: Get-WinEvent. You can also get to them like this:

get-help get-winevent -online

Unfortunately, they don’t come through with the Help file. For completeness, they are:

  • LogName=<String[]>
  • ProviderName=<String[]>
  • Path=<String[]>
  • Keywords=<Long[]>
  • ID=<Int32[]>
  • Level=<Int32[]>
  • StartTime=<DateTime>
  • EndTime=<DataTime>
  • UserID=<SID>
  • Data=<String[]>
  • *=<String[]>

In this case, the StartTime property matches the –After parameter in Get-EventLog, and you use Id instead of InstanceId.

Hang on! You’re shouting that the numbers are different. Yes, they are, because there are two different sets of identification numbers in the event log data that Get-EventLog returns:

  • InstanceId – usually a big number
  • EventId – usually small number

Get-WinEvent only returns the EventId, but as the Id property. Get-EventLog doesn’t let you filter the EventId property directly. You could do this:

Get-EventLog -LogName System -After (Get-Date).Adddays(-7) |

where EventId -eq 6005

However, it’s not as efficient because when you run this against remote machines, you are filtering the client rather than the server.

That leads us to working with remote machines, which is where we get the real value. If Windows PowerShell is about anything, it’s about the ability for admins to administer tens, hundreds, or thousands of machines remotely.

Get-WinEvent and Get-EventLog have a ComputerName parameter. Get-WinEvent doesn’t get as much air time as its older sibling, so let’s use that. You might come up with something like this:

$computers = 'server02', 'server03', 'win12r2'

$filter = @{

 LogName = 'System'

 Id = 6005

 StartTime = (Get-Date).AddDays(-7)

}

 

foreach ($computer in $computers){

New-Object -TypeName PSObject -Property @{

 Computer = $computer

 Count = Get-WinEvent -FilterHashtable $filter -ComputerName $computer | Measure-Object | select -ExpandProperty Count

}

}

Create a list of computers. The filter is the same as before. You use Foreach to iterate the list of computers. An object is created with two properties: the name of the computer and the count of the events that were discovered. You will get output something like this:

Count Computer                              

   -----  --------                              

      6 server02                              

      1 server03                              

      4 win12r2  

Well, hopefully not just like this output because it indicates that my machines have restarted up to 6 times in the last week. These results are from my test lab, which I turn off most nights. In a production environment, you would expect 0 or possibly 1 if a restart was necessary for patching. Anything higher than that is a cause for investigation.

If you are running Windows Firewall (or any other firewall software) on your servers, you need to ensure that Remote Event Log Management is enabled. As another aside, on computers running Windows Server 2012, this is the way to do it:

Get-NetFirewallRule | where DisplayName -like  '* Event Log*' | Enable-NetFirewallRule

The three rules that get enabled are:

  • Remote Event Log Management (RPC)
  • Remote Event Log Management (NP-In)
  • Remote Event Log Management (RPC-EPMAP)

The script is a useful tool, but its main drawback is that it tests each individual machine in sequence.  Wouldn’t it be nice if you could run the script against multiple machines in parallel? Most of the work is done on the remote machine, so you won’t be overloading your admin workstation.

Windows PowerShell 3.0 brought us the ability to work in parallel by using workflows. This task is ideal as a workflow. It hits multiple machines in parallel, and it returns minimal information.

workflow scaneventlogs {

param (

[string[]]$computers

)

$filter = @{

 LogName = 'System'

 Id = 6005

 StartTime = (Get-Date).AddDays(-7)

}

 

foreach -parallel  ($computer in $computers){

$data = New-Object -TypeName PSObject -Property @{

 Computer = $computer

 Count = Get-WinEvent -FilterHashtable $filter -ComputerName $computer |

  Measure-Object | select -ExpandProperty Count

}

$data

}

 

}

 

scaneventlogs -computers 'server02', 'server03', 'win12r2'

Normally, you would expect to use –PSComputerName in a workflow, but you need –ComputerName in this instance because it is in the property hash table for New-Object…simply one of the quirks of workflows that keep us entertained.

You will get back something like this for each machine:

Count                 : 4

Computer              : win12r2

PSComputerName        : localhost

PSSourceJobInstanceId : 9c655254-3e8d-420d-9d98-47ad6b34d24c

You can live without the last two properties because they show the machine on which the workflow is running and the Windows PowerShell job instance. You can filter them out by changing the last line to this:

scaneventlogs -computers 'server02', 'server03', 'win12r2' | select Computer, Count

You could also save the data to a .csv file or a database if required.

For more information about workflows, read my series on the Hey, Scripting Guy! Blog:

PowerShell Workflows

You could develop this idea in multiple ways:

  • Bring back the date of the event
  • Get the list of computers from Active Directory or a text file
  • Test of Active Directory log ons that occur out of hours
  • Test of the last boot up time (use Get-CimInstance in the workflow)
  • Test for Active Directory replication issues

If it’s in the event logs, you can access it this way.

CV, that is all there is to using Windows PowerShell to scan event logs on remote machines. Next time I’ll have another idea for you to try as you bring automation into your environment.

Bye for now.

~Richard

Richard has been working with Microsoft technologies for 25 years and has spent time in most  IT roles, including analyst-programmer, server administrator, support, DBA, and architect. He has been interested in automation techniques (including automating job creation and submission on main frames many years ago). He originally used VBScript and WMI since it became available on Windows NT 4.0. Windows PowerShell caught his interest during the early beta versions in 2005. 

Richard blogs extensively about Windows PowerShell and founded the UK Windows PowerShell User Group in 2007. A Windows PowerShell MVP for the last six years, Richard has given numerous talks on Windows PowerShell at various events in the UK, Europe, and the USA. He is frequent speaker for Windows PowerShell User Groups worldwide.

He has published a number of posts about Windows PowerShell, including expert commentaries on the Microsoft Scripting Games, for which he has been a judge for the last four years. He has written two Windows PowerShell books: Windows PowerShell in Practice (Manning 2010) and Windows PowerShell and WMI (Manning 2012). He then collaborated with Don Jones and Jeff Hicks to write Windows PowerShell in Depth, which was published in 2013. Richard is currently writing an introductory book for Active Directory administrators that features Windows PowerShell.

Contact information: Richard Siddaway's Blog

Thanks, Richard.

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Excellent walk-through.  One of the best I have seen.  It is a big subject and this only scratches the surface but it is a pretty deep scratch.

    XML, XPath and HashTable filters are extremely Powerful.  This alone is a huge topic.

    Thanks for the bootstrap.

  • “How did I know that I wanted an InstanceId of 2147489653?”

    InstanceID is an odd duck.  It's a 32-bit Event ID (6005, in this case), but the two highest bits are flags.  When both flags are zero, InstanceID is equal to EventID, but when either of them are set to 1, you get big, useless looking decimal numbers.  It seems like it would have been cleaner for the Get-EventLog cmdlet to allow you to filter based on EventID instead of InstanceID, but it is what it is. (and Get-WinEvent doesn't have the same problem)

    Here's a function for translating an Event ID (such as 6005) into the 4 possible matching InstanceIDs, which you can then pass to Get-EventLog, for example:

    Get-EventLog -LogName System -InstanceId (Get-InstanceID 6005) -After (Get-Date).AddDays(-7)

    I'm not sure how well it will format in a blog comment; you may have to clean up the indentation / etc.

    function Get-InstanceID

    {

       [CmdletBinding()]

       param (

           [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]

           [System.Int64[]]

           $EventID

       )

       process

       {

           foreach ($id in $EventID)

           {

               $maskedId = $id -band 0xFFFFFF

               Write-Output $maskedId

               Write-Output ($maskedId -bor 0x40000000L)

               Write-Output ($maskedId -bor 0x80000000L)

               Write-Output ($maskedId -bor 0xC0000000L)

           }

       }

    }

  • Whoops, just noticed a slip in that code.

    $maskedId = $id -band 0xFFFFFF

    should be

    $maskedId = $id -band 0x3FFFFFFF

    I was originally masking off the top two hex digits (a full byte) instead of just the top two bits.  I don't know if there are any EventIDs large enough to cause a problem there, but technically, it was incorrect.

  • "This task is ideal as a workflow. It hits multiple machines in parallel, and it returns minimal information."

    Why do you think a workflow is an ideal solution for this task? Invoke-Command supports throttling which bring "parallelism" as well. Add -AsJob parameter to it, and you are all set.

  • @Aleksandar Nikolić

    Workflow should be more efficient and perform better.  It uses the Workflow API and subsystem and not the job system.