How can I monitor the number of event log messages over a few hours, get alerts if a threshold is reached, and include logs not listed in the WMI EventLog sensor in PRTG?
The following sensor will search multiple event logs from multiple providers. You can search for IDs and the message text. Make sure you read the synopsis of the sensor to get an idea of what the specific parameters do. Please use the following script with an EXE/Script sensor:
#___ ___ _____ ___ #| _ \ _ \_ _/ __| #| _/ / | || (_ | #|_| |_|_\ |_| \___| # NETWORK MONITOR #------------------- #(c) 2015 Stephan Linke, Paessler AG # # Name: PRTG-Get-WinEvents # Description: Reads the windows eventlog and filters for the specific events # # Version History # ---------------------------- # Version Date Description # 1.1 07/12/2016 [Fixed] If only one event existed, the sensor showed no events # 1.0 19/05/2014 Initial Release <# .SYNOPSIS Reads the windows eventlog and filters for the specified events. .DESCRIPTION This custom sensor for PRTG will read the given EventLog file and search it for the defined events. It also allows to error if the last event found has a certain ID or Message. .PARAMETER ComputerName The computer whose event log you want to check .PARAMETER Channel The log name that is used by the application .PARAMETER ProviderName The application that you want to watch .PARAMETER EventID The event IDs you want to filter. Seperate multiple IDs with comma .PARAMETER WarningEvents The event IDs you want to raise a warning when found. Those IDs also have to be included in the event ids .PARAMETER ErrorEvents The event IDs you want to raise a error when found. Those IDs also have to be included in the event ids .Parameter Levels The Loglevels you want to include in the search .PARAMETER MaxAge The age of the Logfile in hours .PARAMETER LimitEntries Maximum number of log entries to be checked (order is new -> old) .PARAMETER WarningStrings Put the sensor into a warning state when a certain string is found within the message .PARAMETER ErrorStrings Put the sensor into a error state when a certain string is found within the message .PARAMETER StateBasedOnLastID If this parameter is set, not the sheer number of events will decide if the sensor will go into error or warning state, but only the event id of the last entry found. This is useful for RAID controllers, etc. .PARAMETER StateBasedOnLastMessage If this parameter is set, not the sheer number of events will decide if the sensor will go into error or warning state, but only the message of the last entry found. This is useful if messages have the same event ID for errors and information events. .PARAMETER Username and Password The username and password that the script should use to create the credential object. Format -Username "domain\username" -Password 'yourpass' .OUTPUTS <number of entries found>:<entries> found in the event log. Last message: <last entry message> .EXAMPLE C:\PS> .\Get-Events.ps1 -ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -ProviderName "Microsoft-Windows-Immersive-Shell" -Channel "Microsoft-Windows-TWinUI/Operational" -LimitEntries 1 -MaxAge 1 -EventID 1719 -Level 4 .EXAMPLE C:\PS> .\Get-Events.ps1 -ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -ProviderName "Microsoft-Windows-Immersive-Shell" -Channel "Microsoft-Windows-TWinUI/Operational" -LimitEntries 1 -MaxAge 1 -EventID 1719 -Level 4 -StateBasedOnLastEntry #> param( [string]$ComputerName = "Percy", [string[]]$Channel = @("PRTG Network Monitor"), [string[]]$ProviderName = @("PRTG Network Monitor"), [int[]]$EventID = @(2), [int[]]$WarningEvents = @(), [int[]]$ErrorEvents = @(), [string[]]$ErrorStrings = @(), [string[]]$WarningStrings = @(), [int[]]$Levels = @(), [float]$MaxAge = 24, [int]$LimitEntries = 100, # empty credentials, in case we run at localhost. [string]$Username = '', [string]$Password = '', [switch]$AlwaysShowMessage = $true, [switch]$StateBasedOnLastMessage = $true, [switch]$StateBasedOnLastEventID = $true ) [System.Threading.Thread]::CurrentThread.CurrentCulture = New-Object "System.Globalization.CultureInfo" "en-US" [switch]$Verbose = $false # This will return an error and exit accordingly # If there's an error, only this will be outputted ####################################### function This-PrtgResult([int]$Value = 0, [string]$Message,[int]$ExitCode){ if(!($verbose)){ Write-Host ([string]::Format("{0}:{1}",$Value,$Message)); exit $ExitCode; } else { Write-Host ([string]::Format("{0}:{1}",$Value,$Message)); } } # This will create the credential # object that is used to get the events ####################################### function This-GenerateCredentials(){ # Generate Credentials if we're not checking localhost if((($env:COMPUTERNAME) -ne $ComputerName)){ # Generate Credentials Object first $SecPasswd = ConvertTo-SecureString $Password -AsPlainText -Force $Credentials= New-Object System.Management.Automation.PSCredential ($Username, $secpasswd) return $Credentials } # otherwise return false else{ return "false" } } # This will retrieve the event log entries # based on channel, provider and events ID. ####################################### function This-ReadEventLog(){ $Credentials = (This-GenerateCredentials); $EventFilter = @{}; # Filter the objects according to their timestamp $EventFilter.Add("StartTime",(get-date).AddHours(-$MaxAge)); # We need a provider, otherwise the script will error if none is given. # If it's set, it'll be added to the filter if($ProviderName.Count -eq 0) { This-PrtgResult -Message "No ProviderName given. Please enter a valid Provider." -ExitCode 1; } $EventFilter.Add('ProviderName',$ProviderName) # We need a Channel, otherwise the script will error if none is given. # If it's set, it'll be added to the filter if($Channel.Count -eq 0) { This-PrtgResult -Message "No Channel given. Please enter a valid Provider." -ExitCode 1; } $EventFilter.Add("LogName",$Channel) # If there are event IDs, add them to the filter if($EventID.Count -gt 0) { $EventFilter.Add("ID",$EventID); } # if there are levels, add them to the filter if($Levels.Count -gt 0) { $EventFilter.Add("Level",$Levels) } try{ if($Credentials -ne "false"){ $Events = (Get-WinEvent -ComputerName $ComputerName -FilterHashTable $EventFilter -MaxEvents $LimitEntries -Credential $Credentials) } else { $Events = (Get-WinEvent -ComputerName $ComputerName -FilterHashTable $EventFilter -MaxEvents $LimitEntries ) } return $Events; } catch [Exception] { This-PrtgResult -Message ([string]::Format("Can't find anything for {0} in your {1} eventlog. Please check Log name, Provider, Log ID, EventID, ComputerName and Credentials",$ProviderName -join " or ",$Channel -join " or ")) -ExitCode 1 } } # This will evaluate the results from the above # function and return the sensor value ####################################### function This-EvaluateLogResults(){ $Events = (This-ReadEventLog); $Counter = $Events.Count # We need the events to be in an array, even if we only have one value # This makes iterating easier. $Events = @($Events); if($Counter -gt 0){ $EventList = [System.Collections.ArrayList]$Events # Always show the last message when enabled if(($AlwaysShowMessage) -and ($Counter -ne 0)){ if(!([string]::IsNullOrEmpty($EventList[0].Message))){ $LastMessage = ($EventList[0].Message.Remove(50)+"..." -replace "`n|`r") $Message = "(Last entry: "+$LastMessage+")" } else { $Message = "(Latest event has no message)"; } } # Search for error and warning IDs if((($StateBasedOnLastEventID)) -and (-not($StateBasedOnLastMessage))){ switch ($EventList[0].Id){ {$ErrorEvents -contains $EventList[0].Id} { This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1 } {$WarningEvents -contains $EventList[0].Id}{ This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1 } } } # Search for messages that contain the error and warning strings elseif(($Counter -ne 0) -and ($StateBasedOnLastMessage)){ foreach($String in $ErrorStrings){ if($EventList[0].Message -match "($String)"){ This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1 } } foreach($String in $WarningStrings){ if($EventList[0].Message -match "($String)"){ This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1 } } } # Search for messages that contain the error and warning strings and the messages elseif(($Counter -ne 0) -and (($StateBasedOnLastMessage) -and ($StateBasedOnLastID))){ switch ($EventList[0].Id){ {($ErrorEvents -contains $EventList[0].Id) -and ($EventList[0].Message -match $ErrorStrings)} { This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1} {($WarningEvents -contains $EventList[0].Id) -and ($EventList[0].Message -match $WarningStrings)} { This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1} } } } switch ($Counter){ {$Counter -eq 0}{ This-PrtgResult -Message "No events found." -ExitCode 0 } {$Counter -ge 1}{ This-PrtgResult -Message "$($Counter) event(s) found $($Message)." -Value $Counter -ExitCode 0 } } } This-EvaluateLogResults;
In order to get detailed information about the event you want to monitor, please open the Windows Event Viewer application, search for the event and switch to the detail tab. Select the XML view to see all properties of the event.
Parameter example:
-ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -MaxAge 8 -Channel 'Application' -ProviderName 'ESENT' -EventID @(105,200,301) -WarningEvents @(200) -ErrorEvents @(301) -AlwaysShowLastMessage
The % variables will be replaced automatically by PRTG. If you want to look for multiple event IDs, strings, logs and providers, simply add them to the array:
IDs @(101,102,103,104)
Events, Strings @("String 1","String 2")
If you want to work with channel limits instead of the hardcoded limits (0 = green, 1 = warning, 2 and above = error), edit the lines 155 and 156, specifically the exit code.
Important The user you use needs to have administrative privileges on the target device; he can't read the events as a normal user.
Note: Newer PRTG versions require a different parameter setup. Please check the following script:
The script has been updated with the following modifications: the MaxAge parameter has been changed from hours to minutes, and the comments/help output have been updated to reflect the new parameter names. Unnecessary debug statements have been removed, and the script now properly strips `\n\r` characters from messages for compatibility with PowerShell 2.0. Additionally, the `-ErrorAction Stop` parameter has been added to the `Get-WinEvent` call to trap non-fatal "No matching events" exceptions, improving stability, especially with PowerShell 2.0.
#___ ___ _____ ___ #| _ \ _ \_ _/ __| #| _/ / | || (_ | #|_| |_|_\ |_| \___| # NETWORK MONITOR #------------------- #(c) 2015 Stephan Linke, Paessler AG # # Name: PRTG-Read-WinEventLog # Description: Reads the windows eventlog and filters for the specific events # # Version History # ---------------------------- # Version Date Who Description #--------------------------------------------------- # 1.3 11/14/2019 Todd Piket Updated the comments / help output to match the new parameter names. Forgot # to mention I changed the MaxAge parameter from hours to MINUTES. Removed a # bunch of commented out debug statements. Put back the stripping of the # \n\r characters because Powershell 2.0 doesn't like them. # Add the -ErrorAction Stop parameter to the Get-WinEvent call so I could trap # the non-fataln "No matching events" exception because it was making PRTG cranky. # Maybe this is only an issue with Powershell 2.0 (again), but it's probably a good # idea to trap this exception anyway. # 1.2 11/13/2019 Todd Piket Made ProviderName optional. Removed truncation of message to 50 characters # so I can see the full message. Made the switch parameters function properly # by removing default value of $true. Changed the AlwaysShowMessage switch to # more specific ShowLastMessage. Added the ShowAllMessages switch so you can # return all the messages that match the provided criteria if you want. Added # a TruncateMessage switch and TruncateMessageLenght parameter so the user can # specify the truncation and length of messages, if desired. Changed Channel # parameter to LogNames. Pluralized all parameter namess that can take in a # list of items. Change the item list for array parameters to be a comma # delimited string and then turn the string into a Powershell array later. # This works around the PRTG problem of using the @ sign when passing parameters # to Powershell. # 1.1 07/12/2016 Stephan Linke [Fixed] If only one event existed, the sensor showed no events # 1.0 19/05/2014 Stephan Linke Initial Release <# .SYNOPSIS Reads the windows eventlog and filters for the specified events. .DESCRIPTION This custom sensor for PRTG will read the given EventLog file and search it for the defined events. It also allows to error if the last event found has a certain ID or Message. .PARAMETER ComputerName The computer whose event log you want to check .PARAMETER LogNames The log name that is used by the application .PARAMETER ProviderNames The application(s) that you want to watch .PARAMETER EventIDs The event IDs you want to filter. Seperate multiple IDs with comma .PARAMETER WarningEvents The event IDs you want to raise a warning when found. Those IDs also have to be included in the event ids .PARAMETER ErrorEvents The event IDs you want to raise a error when found. Those IDs also have to be included in the event ids .Parameter Levels The Loglevels you want to include in the search. See MS for Log Level enumeration. .PARAMETER MaxAge How far back to look in the log in MINUTES .PARAMETER LimitEntries Maximum number of log entries to be checked (order is new -> old) .PARAMETER WarningStrings Put the sensor into a warning state when a certain string is found within the message. Separate multiple strings with comma .PARAMETER ErrorStrings Put the sensor into a error state when a certain string is found within the message. Separate multiple strings with comma .PARAMETER ShowLastMessage If this parameter is set, only the last (most recent) message from the event log will be returned. .PARAMETER ShowAllMessages If this parameter is set, all messages from the eventlog matching the specified parameters will be returned. .PARAMETER TruncateMessage If this parameter is set, all messages from the eventlog will be truncated to the length specified by the TruncateMessageLength parameter. If ShowAllMessages is specified, then EACH MESSAGE FOUND will be truncated to the length specified. .PARAMETER TruncateMessageLength If this parameter is set, all messages from the eventlog will be truncated to the specfied length. If ShowAllMessages is specified, then EACH MESSAGE FOUND will be truncated to the length specified. .PARAMETER StateBasedOnLastID If this parameter is set, not the sheer number of events will decide if the sensor will go into error or warning state, but only the event id of the last entry found. This is useful for RAID controllers, etc. .PARAMETER StateBasedOnLastMessage If this parameter is set, not the sheer number of events will decide if the sensor will go into error or warning state, but only the message of the last entry found. This is useful if messages have the same event ID for errors and information events. .PARAMETER Username and Password The username and password that the script should use to create the credential object. Format -Username "domain\username" -Password 'yourpass' .OUTPUTS <number of entries found>:<entries> found in the event log. Last message: <last entry message> .EXAMPLE C:\PS> .\PRTG-Read-WinEventLog.ps1 -ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -ProviderNames "Microsoft-Windows-Immersive-Shell" -LogNames "Microsoft-Windows-TWinUI/Operational" -LimitEntries 1 -MaxAge 1 -EventIDs 1719 -Levels 4 .EXAMPLE C:\PS> .\PRTG-Read-WinEventLog.ps1 -ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -ProviderNames "Microsoft-Windows-Immersive-Shell" -LogNames "Microsoft-Windows-TWinUI/Operational" -LimitEntries 1 -MaxAge 1 -EventIDs 1719 -Levels 4 -StateBasedOnLastEntry #> param( [string]$ComputerName = "", [string]$LogNames = "", [string]$ProviderNames = "", [string]$EventIDs = "", [string]$WarningEvents = "", [string]$ErrorEvents = "", [string]$ErrorStrings = "", [string]$WarningStrings = "", [string]$Levels = "", [float]$MaxAge = 60, [int]$LimitEntries = 100, # empty credentials, in case we run at localhost. [string]$Username = '', [string]$Password = '', [int]$TruncateMessageLength = 50, [switch]$ShowLastMessage, [switch]$ShowAllMessages, [switch]$TruncateMessage, [switch]$StateBasedOnLastMessage, [switch]$StateBasedOnLastEventID, [switch]$Verbose ) [System.Threading.Thread]::CurrentThread.CurrentCulture = New-Object "System.Globalization.CultureInfo" "en-US" # # Process passed in strings that were destined for array parameters by # now turning them into an appropriate array. # if (! [string]::IsNullOrEmpty($EventIDs)) { $arEventIDs = foreach ($number in $EventIDs.split(",")) {([int]::parse($number))} } if (! [string]::IsNullOrEmpty($LogNames)) { $arLogNames = $LogNames.split(",") } if (! [string]::IsNullOrEmpty($ProviderNames)) { $arProviders = $ProviderNames.split(",") } if (! [string]::IsNullOrEmpty($ErrorEvents)) { $arErrorIDs = foreach ($number in $ErrorEvents.split(",")) {([int]::parse($number))} } if (! [string]::IsNullOrEmpty($WarningEvents)) { $arWarnIDs = foreach ($number in $WarningEvents.split(",")) {([int]::parse($number))} } if (! [string]::IsNullOrEmpty($Levels)) { $arLevels = foreach ($number in $Levels.split(",")) {([int]::parse($number))} } if (! [string]::IsNullOrEmpty($ErrorStrings)) { $arErrorStrings = $ErrorStrings.split(",") } if (! [string]::IsNullOrEmpty($WarningStrings)) { $arWarnStrings = $WarningStrings.split(",") } # This will return an error and exit accordingly # If there's an error, only this will be outputted ####################################### function This-PrtgResult([int]$Value = 0, [string]$Message,[int]$ExitCode){ if(!($verbose)){ Write-Host ([string]::Format("{0}:{1}",$Value,$Message)); exit $ExitCode; } else { Write-Host ([string]::Format("{0}:{1}",$Value,$Message)); } } # This will create the credential # object that is used to get the events ####################################### function This-GenerateCredentials(){ # Generate Credentials if we're not checking localhost if((($env:COMPUTERNAME) -ne $ComputerName)){ # Generate Credentials Object first $SecPasswd = ConvertTo-SecureString $Password -AsPlainText -Force $Credentials= New-Object System.Management.Automation.PSCredential ($Username, $secpasswd) return $Credentials } # otherwise return false else{ return "false" } } # This will retrieve the event log entries # based on channel, provider and events ID. ####################################### function This-ReadEventLog(){ $Credentials = (This-GenerateCredentials) $EventFilter = @{}; # Filter the objects according to their timestamp $EventFilter.Add("StartTime",(get-date).AddMinutes(-$MaxAge)) # We need a provider, otherwise the script will error if none is given. # If it's set, it'll be added to the filter if($arProviders.Count -gt 0) { $EventFilter.Add('ProviderName',$arProviders) } # We need a Channel, otherwise the script will error if none is given. # If it's set, it'll be added to the filter if($arLogNames.Count -eq 0) { This-PrtgResult -Message "No Channel given. Please enter a valid Provider." -ExitCode 1; } $EventFilter.Add("LogName",$arLogNames) # If there are event IDs, add them to the filter if($arEventIDs.Count -gt 0) { $EventFilter.Add("ID",$arEventIDs) } # if there are levels, add them to the filter if($arLevels.Count -gt 0) { $EventFilter.Add("Level",$arLevels) } try{ if($Credentials -ne "false"){ $Events = (Get-WinEvent -ComputerName $ComputerName -FilterHashTable $EventFilter -MaxEvents $LimitEntries -Credential $Credentials -ErrorAction Stop) } else { $Events = (Get-WinEvent -ComputerName $ComputerName -FilterHashTable $EventFilter -MaxEvents $LimitEntries -ErrorAction Stop) } return $Events } catch [Exception] { if ($_.Exception -match "No events were found that match the specified selection criteria") { This-PrtgResult -Message ([string]::Format("No events were found that match the specified selection criteria")) -ExitCode 0 } else { This-PrtgResult -Message ([string]::Format("Can't find anything for {0} in your {1} eventlog. Please check Log name, Provider, Log ID, EventID, ComputerName and Credentials",$arProviders -join " or ",$arLogNames -join " or ")) -ExitCode 1 # Additional Debugging, replace above line with the one below. #This-PrtgResult -Message ([string]::Format("Exception: $($_.Exception.Message) - Can't find anything for {0} in your {1} eventlog. Please check Log name, Provider, Log ID, EventID, ComputerName and Credentials",$arProviders -join " or ",$arLogNames -join " or ")) -ExitCode 1 } } } # This will evaluate the results from the above # function and return the sensor value ####################################### function This-EvaluateLogResults(){ $Events = (This-ReadEventLog); $Counter = $Events.Count # We need the events to be in an array, even if we only have one value # This makes iterating easier. $Events = @($Events); if($Counter -gt 0){ $EventList = [System.Collections.ArrayList]$Events # Show the last message when enabled if(($ShowLastMessage) -and ($Counter -ne 0)) { if(!([string]::IsNullOrEmpty($EventList[0].Message))) { if ($TruncateMessage) { $LastMessage = ($EventList[0].Message.substring(0, [System.Math]::Min($TruncateMessageLength, $EventList[0].Message.Length)) + "..." -replace "`n|`r") } else { $LastMessage = ($EventList[0].Message) -replace "`n|`r" } $Message = "Last entry: "+$LastMessage } else { $Message = "Latest event has no message" } } # Show all messages matching criteria when enabled. if(($ShowAllMessages) -and ($Counter -ne 0)) { $i = 0 foreach($event in $EventList) { if(!([string]::IsNullOrEmpty($EventList[$i].Message))) { if ($TruncateMessage) { $Message = "$Message" + ($EventList[$i].Message.substring(0, [System.Math]::Min($TruncateMessageLength, $EventList[$i].Message.Length)) + "..." -replace "`n|`r") } else { $Message = "$Message" + ($EventList[$i].Message) -replace "`n|`r" } } $i++ } } # Search for error and warning IDs if((($StateBasedOnLastEventID)) -and (-not($StateBasedOnLastMessage))){ switch ($EventList[0].Id){ {$arErrorIDs -contains $EventList[0].Id} { This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1 } {$arWarnIDs -contains $EventList[0].Id}{ This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1 } } } # Search for messages that contain the error and warning strings elseif(($Counter -ne 0) -and ($StateBasedOnLastMessage)){ foreach($String in $arErrorStrings){ if($EventList[0].Message -match "($String)"){ This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1 } } foreach($String in $arWarnStrings){ if($EventList[0].Message -match "($String)"){ This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1 } } } # Search for messages that contain the error and warning strings and the messages elseif(($Counter -ne 0) -and (($StateBasedOnLastMessage) -and ($StateBasedOnLastID))){ switch ($EventList[0].Id){ {($arErrorIDs -contains $EventList[0].Id) -and ($EventList[0].Message -match $arErrorStrings)} { This-PrtgResult -Message "Critical Event found: $($Message)" -Value $Counter -ExitCode 1} {($arWarnIDs -contains $EventList[0].Id) -and ($EventList[0].Message -match $arWarnStrings)} { This-PrtgResult -Message "Warning Event found: $($Message)" -Value $Counter -ExitCode 1} } } } switch ($Counter){ {$Counter -eq 0}{ This-PrtgResult -Message "No events found" -ExitCode 0 } {$Counter -ge 1}{ This-PrtgResult -Message "$($Counter) event(s) found $($Message)" -Value $Counter -ExitCode 0 } } } This-EvaluateLogResults;
Disclaimer:
The information in the Paessler Knowledge Base comes without warranty of any kind. Use at your own risk. Before applying any instructions please exercise proper system administrator housekeeping. You must make sure that a proper backup of all your data is available.