Hey, Scripting Guy! How Can I Read from Windows Event Logs with Windows PowerShell 2.0?

Hey, Scripting Guy! How Can I Read from Windows Event Logs with Windows PowerShell 2.0?

  • Comments 1
  • Likes

 

Bookmark and Share

 

Hey, Scripting Guy! Question

 

Hey, Scripting Guy! I need to be able to use Windows PowerShell 2.0 to read from the Windows event logs. On our network, we have a mixture of Windows 7 and Windows XP desktop clients. Our server farm has everything from Windows Server 2003 up to Windows 2008 R2 Core Edition. Is there one script I can use to allow me to read these event logs?

-- KG


Hey, Scripting Guy! AnswerHello KG,

Microsoft Scripting Guy Ed Wilson here. One of the things that Windows PowerShell does best is to provide an easy way to access the Windows event logs. There are actually two different cmdlets that grant access to event logs. The first cmdlet, Get-EventLog, was introduced in Windows PowerShell 1.0 and was improved in Windows PowerShell 2.0 with filtering capability and the ability to connect to remote event logs. Windows PowerShell 2.0 also introduced a new cmdlet that gives you the ability to query the new style of event logs that were introduced with Windows Vista. This cmdlet is called Get-WinEvent. Get-WinEvent also allows you to access the traditional event logs that Get-EventLog accesses; therefore, you can get by with only using the Get-WinEvent cmdlet and avoid the confusion of when to use Get-EventLog.

Beginner Event 2 of the 2010 Scripting Games requires you to query the event log for event ID 6005. Event ID 6005 is the event that is generated when the event log service starts. Because the event log service starts early in the boot process, it provides a reasonably accurate indicator of how long a server has been running. On Windows Vista and later, there are specific events that can be used for this purpose. The advantage of event id 6005 is that you can use it on earlier versions of Windows; thus, one script will work for multiple operating systems.

An example of using the Get-EventLog cmdlet is seen in the BEG2_Salamander_MN.ps1 script. The script begins by using the Get-EventLog cmdlet to retrieve all the entries from the system log. The resulting events are then piped to the Where-Object cmdlet to filter out the event IDs that are equal to 6005. A set of parentheses is used to work with the entire collection of events that match event ID 6005. The [0] is used to retrieve the first event from the array of event log entries. The TimeGenerated property contains the time that the event occurred. The complete BEG2_Salamander_MN.ps1 script is shown just below as a single line of code.

For an excellent example of a VBScript version of this script, see the script written by Stephanie Peters in our expert commentator article for Event 2 of the 2010 Scripting Games.


BEG2_Salamander_MN.ps1

(get-eventlog System | where-object {$_.EventID -eq "6005"})[0].TimeGenerated


When Beg2_Salamander_MN.ps1 runs, the output appears that is shown in the following image.

Image of output of Beg2_Salamander_MN.ps1 script

Windows PowerShell 2.0 introduces filter parameters that can be used to improve the speed and the efficiency of a query such as the one just seen. When run on my laptop, retrieving all of the events from the system event log, piping the results to the Where-Object to filter event 6005, and then indexing into the array to retrieve the time the event was generated takes more than 14 seconds. This is shown here, where I use the Measure-Command cmdlet to get a baseline:

PS C:\> Measure-Command -Expression {(get-eventlog System | where-object {$_.EventID -eq "6005"})[0]
.TimeGenerated}
Days : 0
Hours : 0
Minutes : 0
Seconds : 14
Milliseconds : 783
Ticks : 147837791
TotalDays : 0.000171108554398148
TotalHours : 0.00410660530555556
TotalMinutes : 0.246396318333333
TotalSeconds : 14.7837791
TotalMilliseconds : 14783.7791
PS C:\>

When I use the new filter parameters, I specify the source as EventLog, the EntryType as information, and the complete text of the message. The revised command is shown here just below.

Note: The backtick character (`) is used to indicate line continuation. This is done because we have width limitations when displaying code in our blog. If you were typing the code inside the Windows PowerShell console, you would not need to include the line continuation character because commands automatically wrap to the line. Therefore, this command is a single logical line.

 

Get-EventLog -LogName system -Newest 1 -Source eventlog -EntryType information `
-Message "The Event log Service was started."

Using the Measure-Command cmdlet, I see the new command takes 55 milliseconds. This is shown here:

PS C:\> Measure-Command -expression {Get-EventLog -LogName system -Newest 1 -Source eventlog `
-EntryType information -Message "The Event log Service was started."}
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 55
Ticks : 551201
TotalDays : 6.3796412037037E-07
TotalHours : 1.53111388888889E-05
TotalMinutes : 0.000918668333333333
TotalSeconds : 0.0551201
TotalMilliseconds : 55.1201
PS C:\>

The new filter parameters of the Get-EventLog cmdlet result in both performance improvements and easier to read code.

The ADV2_JGerse.ps1 script uses the Win32_NTLogEvent WMI class. A number of entries in this year’s Scripting Games used this class. The notes and comments that accompanied many of the entries stated they did so because of performance reasons. When tested with the Measure-Command cmdlet, the script takes 40 seconds to run. This is shown here:

PS C:\> Measure-Command -Expression { C:\a\ADV2_JGerse.ps1 }
Days : 0
Hours : 0
Minutes : 0
Seconds : 40
Milliseconds : 375
Ticks : 403759221
TotalDays : 0.000467313913194444
TotalHours : 0.0112155339166667
TotalMinutes : 0.672932035
TotalSeconds : 40.3759221
TotalMilliseconds : 40375.9221

It is obvious the writer of the script spent a lot of time and effort writing the script. Much of the effort was spent in manually creating help. When using Windows PowerShell 2.0, you should not do that; instead, use the help tags. It is much less work, it makes the script easier to read, and it allows for consistent behavior between scripts and cmdlets. I talked about this in yesterday’s Hey, Scripting Guy! Blog post.

The script uses the Win32_PingStatus WMI class to determine if the remote host is up and running. Here is the code that is used to perform that check:

Function Pinghost ([string] $Hostname )
{
$ping = get-wmiobject -class win32_pingstatus -filter("Address='$Hostname'")`
-ErrorAction Silentlycontinue
if ($ping.statuscode -ne 0) #nincs válasz
{
return $false
}
else
{
return $true
}
}

There is no need to write your own function in Windows PowerShell 2.0 to ping a remote computer. That is what the Test-Connection cmdlet does:

Test-Connection –ComputerName $Hostname

The script has a function, called CheckandSetAuthmethod, to determine if credentials are used with the WMI command. This function could have been simplified by using a switched parameter to indicate that credentials needed to be supplied for the remote connection. In addition, by using the –credential parameter of the Get-WmiObject cmdlet, there is no need to use the PromptForCredential method from $host.ui. This function is shown here:

Function CheckandSetAuthmethod
{
if ($Authmethod -eq "Default")
{
$cred = $null
}
elseif ($Authmethod -eq "Password") #get credentials
{
$cred = $host.ui.PromptForCredential("Need credentials ",
"Please enter your user name and password.", "", "NetBiosUserName")
}
else #invalid Authmethod value encountered
{
Write-Host "Invalid -Authmethod $authmethod" -Foregroundcolor Red
break
}
}

One problem with the performance of the script is the WMI query itself. The Win32_NTLogEvent WMI class has horrible performance unless a very specific query is used. The eventid property is not used in the WMI query, and the default date range ensures that every record from the system event log is returned by the script. Essentially, the WMI command that is being generated by the script boils down to the command shown here. Note that when the command runs, it returns a number that represents the value of the TimeGenerated property:

PS C:\> (gwmi win32_NTLogEvent -filter "Logfile = 'System'" | where-object {$_.EventCode -eq "6005"}
)[0].TimeGenerated
20100516140218.000000-000

When I test the performance of that command, by using Measure-Command, I see that it takes just over 26 seconds to complete.

I talked about using the Measure-Command cmdlet in Hey, Scripting Guy! How Can I Speed Up the Performance of My Windows PowerShell Scripts? If you are modifying two scripts, and wish to check the efficiency of your changes, you may wish to review Hey Scripting Guy! How Can I Test the Efficacy of My Script Modifications? where I talk about a script I wrote that will automatically test the performance of two scripts.

This is more than half the time required for the entire script to complete and nearly double the amount of time that the Beg2_Salamander_MN.ps1 script took to complete. You will notice that I adapted the syntax from the Beg2_Salamander_MN.ps1 script for testing the WMI code. This is shown here:

PS C:\> Measure-Command -exp {(gwmi win32_NTLogEvent -filter "Logfile = 'System'" |
where-object {$_.EventCode -eq "6005"})[0].TimeGenerated}
Days : 0
Hours : 0
Minutes : 0
Seconds : 26
Milliseconds : 96
Ticks : 260962530
TotalDays : 0.000302039965277778
TotalHours : 0.00724895916666667
TotalMinutes : 0.43493755
TotalSeconds : 26.096253
TotalMilliseconds : 26096.253
PS C:\>

One thing about the output from the script is that it returns a raw UTC date. Windows PowerShell includes the ability to convert this type of date to a more readable form. The ConvertToDateTime method has been added to all the WMI classes. To use this method, store the WMI object in a variable, and then call it directly while supplying the UTC format date. This is illustrated here:

PS C:\> $wmi = (gwmi win32_NTLogEvent -filter "Logfile = 'System' AND EventCode = '6005'")[0]
PS C:\> $wmi.ConvertToDateTime($wmi.TimeGenerated)
Sunday, May 16, 2010 10:02:18 AM

The complete ADV2_JGerse.ps1 script is shown here.

ADV2_JGerse.ps1

param (
[string] $Authmethod = "Default",
[string] $Computer = $env:ComputerName,
[string] $eventlog = "system",
[string] $fromdate = "19000101",
[string] $todate = "20991231",
[string] $eventid = "",
[string] $pattern = ""
)
#******************************************************************************
#help
function HowToUse
{
$usage = "NAME$crlf Game02Advanced.ps1$crlf$crlf"
$usage += "SYNOPSIS$crlf Get last start time of the local or remote computer$crlf"
$usage += " Alternatively lists eventlog records$crlf$crlf"
$usage += "SYNTAX$crlf [Path]\Game02Advanced.ps1 [-Computer <computername>] [-Authmethod <methodname>]$crlf"
$usage += " [-Eventlog <eventlognam>] [-Eventid <eventid>] [-Pattern <pattern>]$crlf"
$usage += " [-Fromdate <date>] [-Todate <date>]$crlf"
$usage += " $crlf"
$usage += "DESCRIPTION$crlf"
$usage += " If You omit computername the script connects to local computer.$crlf"
$usage += " The methodname is Default or Password.$crlf"
$usage += " Default means the credentials of the current user (This is default value)$crlf"
$usage += " In the case Password You will be prompted for valid username/password.$crlf"
$usage += " You should have enough rights to connect to WMI provider on the destination computer.$crlf"
$usage += " The eventlog default system.$crlf"
$usage += " If you give eventid the result will be filtered.$crlf"
$usage += " If you give pattern the result will contain those records where Message contains the pattern.$crlf"
$usage += " The date format yyyymmdd$crlf"
$usage += " Default fromdate 19000101 and default todate 20991231.$crlf"
$usage += "$crlf"
Write-Host $usage -foregroundcolor green
}
Function Pinghost ([string] $Hostname )
{
$ping = get-wmiobject -class win32_pingstatus -filter("Address='$Hostname'")`
-ErrorAction Silentlycontinue
if ($ping.statuscode -ne 0) #nincs válasz
{
return $false
}
else
{
return $true
}
}
#check for -Authmethod parameter and get credentials if Password method
Function CheckandSetAuthmethod
{
if ($Authmethod -eq "Default")
{
$cred = $null
}
elseif ($Authmethod -eq "Password") #get credentials
{
$cred = $host.ui.PromptForCredential("Need credentials ",
"Please enter your user name and password.", "", "NetBiosUserName")
}
else #invalid authmethod value encountered
{
Write-Host "Invalid -Authmethod $authmethod" -Foregroundcolor Red
break
}
}
#******************************************************************************
#main
$crlf = "`r`n"
#[bool] $HostExists = $false
if ($args[0] -eq "-?")
{
HowToUse
return
}
#Authmethod parameter value check and get credentials if Password
#.dotted call needed!
. CheckandSetAuthmethod
if(!(pinghost($computer)))
{
Write-Host "Computer $computer unreachable" -Foregroundcolor red
break
}
$a = ""
Try
{
if ($Authmethod -eq "Default") #runs with default credentials
{
$a = Get-WmiObject -Namespace "root\cimv2" -Class Win32_ntlogevent -filter `
"Logfile='$eventlog' and Timegenerated >= '$fromdate' and Timegenerated
<= '$todate'" -ComputerName $computer
}
else #runs with another user credentials
{
$a = Get-WmiObject -Namespace "root\cimv2" -Class Win32_ntlogevent -filter `
"Logfile='$eventlog' and Timegenerated > '$fromdate' and Timegenerated
<= '$todate'" -ComputerName $computer `
-Impersonation 3 -Credential $cred
}
}
catch
{
Write-Host "Access denied to $computer" -foregroundcolor red
Break
}
$startdatesearch = $false
if ($eventid.trim() -eq "" -and $pattern.trim() -eq "" -and $eventlog -eq "system")
{
$startdatesearch = $true
$eventid = 6005
}
#filter result for eventid
if ($eventid -ne "")
{
$a = $a | where-object{$_.eventcode -eq $eventid}
}
if($startdatesearch)
{
"$computer last started"
$a | select-object timegenerated | sort-object timegenerated -desc |
select-object -first 1
}
else
{
#filter result for pattern
if ($pattern -ne "")
{
$a = $a | where-object{$_.message -match $pattern}
}
$a | select-object timegenerated,eventcode,message |
sort-object timegenerated -desc | ft
}

When the ADV2_JGerse.ps1 script runs, the output appears that is shown in the following image.

Image of script output

KG, that is all there is to Event 2. The 2010 Scripting Games Wrap-Up Week will continue tomorrow.

 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send email to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson and Craig Liebendorfer, Scripting Guys


Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • <p>Hey guys i want to set remainders using script could anyone help in this.</p> <p>With Regards</p> <p>Srinivas</p>