From 307a57a5bf231996c98dcd59bdcf0cc71418cd05 Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Wed, 15 May 2024 17:21:30 +0200 Subject: [PATCH] Adds feature to check Eventlog for problem and acknowleding events --- doc/100-General/10-Changelog.md | 2 + .../Icinga_IcingaExceptionEnums.psm1 | 1 + .../Get-IcingaProviderDataValuesEventlog.psm1 | 1 + .../New-IcingaProviderFilterDataEventlog.psm1 | 175 +++++++++++++----- 4 files changed, 135 insertions(+), 44 deletions(-) diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index c605382..e170c46 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -15,10 +15,12 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic * [#729](https://github.com/Icinga/icinga-powershell-framework/issues/729) Fixes `Update-Icinga` to print an error in case a component is not installed, instead of silently continue * [#734](https://github.com/Icinga/icinga-powershell-framework/issues/734) Fixes a scenario on which a JEA service could become orphaned while manually stopping the Icinga for Windows service, without gracefully shutting down JEA +* [#735](https://github.com/Icinga/icinga-powershell-framework/pull/735) Fixes an issue with filter for EventLog events, which did not properly handle multiple event id includes, causing empty results ### Enhancements * [#732](https://github.com/Icinga/icinga-powershell-framework/pull/732) Adds support for TLS 1.3 and improves startup response +* [#735](https://github.com/Icinga/icinga-powershell-framework/pull/735) Adds support to provide occuring problem event id's for the Eventlog and corresponding acknowledgement id's, providing an indicator if certain issues are resolved or still present ## 1.12.3 (2024-04-24) diff --git a/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 b/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 index bef79bc..393c6f8 100644 --- a/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 +++ b/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 @@ -37,6 +37,7 @@ [hashtable]$Configuration = @{ PluginArgumentConflict = 'Your plugin argument configuration is causing a conflict. Mostly this error is caused by mismatching configurations by enabling multiple switch arguments which are resulting in a conflicting configuration for the plugin.'; PluginArgumentMissing = 'Your plugin argument configuration is missing mandatory arguments. This error is caused when mandatory or required arguments are missing from a plugin call and the operation is unable to process without them.'; + PluginArgumentAsymmetry = 'Your plugin argument configuration is causing an asymmetry. This error is caused by an uneven amount of arguments in your plugin call. Please ensure that your plugin call is properly configured and all arguments are set correctly.'; PluginNotInstalled = 'The plugin assigned to this service check seems not to be installed on this machine. Please review your service check configuration for spelling errors and check if the plugin is installed and executable on this machine by PowerShell. You can ensure modules are available by manually importing them by their name with the following commands: Import-Module -Name "module name" -Force; Import-Module -Name "module name" -Global -Force;'; PluginNotAssigned = 'Your check for this service could not be processed because it seems like no valid Cmdlet was assigned to the check command. Please review your check command to ensure that a valid Cmdlet is assigned and executed by a PowerShell call.'; EventLogNotInstalled = 'Your Icinga PowerShell Framework has been executed by an unprivileged user before it was properly installed. The Windows EventLog application could not be registered because the current user has insufficient permissions. Please log into the machine and run "Use-Icinga" once from an administrative shell to complete the setup process. Once done this error should vanish.'; diff --git a/lib/provider/logging/Get-IcingaProviderDataValuesEventlog.psm1 b/lib/provider/logging/Get-IcingaProviderDataValuesEventlog.psm1 index 50289c9..80cdd40 100644 --- a/lib/provider/logging/Get-IcingaProviderDataValuesEventlog.psm1 +++ b/lib/provider/logging/Get-IcingaProviderDataValuesEventlog.psm1 @@ -12,6 +12,7 @@ function Get-IcingaProviderDataValuesEventlog() $EventLogData.Metrics | Add-Member -MemberType NoteProperty -Name 'List' -Value $FilterObject.EventLog.Query.List; $EventLogData.Metrics | Add-Member -MemberType NoteProperty -Name 'Events' -Value $FilterObject.EventLog.Query.Events; + $EventLogData.Metrics | Add-Member -MemberType NoteProperty -Name 'Problems' -Value $FilterObject.EventLog.Query.Problems; $EventLogData.Metrics | Add-Member -MemberType NoteProperty -Name 'HasEvents' -Value $FilterObject.EventLog.Query.HasEvents; $FilterObject = $null; diff --git a/lib/provider/logging/New-IcingaProviderFilterDataEventlog.psm1 b/lib/provider/logging/New-IcingaProviderFilterDataEventlog.psm1 index f4b5d60..ab855fa 100644 --- a/lib/provider/logging/New-IcingaProviderFilterDataEventlog.psm1 +++ b/lib/provider/logging/New-IcingaProviderFilterDataEventlog.psm1 @@ -6,25 +6,32 @@ function New-IcingaProviderFilterDataEventlog() { param( - [string]$LogName = '', - [array]$IncludeEventId = @(), - [array]$ExcludeEventId = @(), - [array]$IncludeUsername = @(), - [array]$ExcludeUsername = @(), - [array]$IncludeEntryType = @(), - [array]$ExcludeEntryType = @(), - [array]$IncludeMessage = @(), - [array]$ExcludeMessage = @(), - [array]$IncludeSource = @(), - [array]$ExcludeSource = @(), - [string]$EventsAfter = $null, - [string]$EventsBefore = $null, - [int]$MaxEntries = 40000, - [switch]$DisableTimeCache = $FALSE + [string]$LogName = '', + [array]$IncludeEventId = @(), + [array]$ExcludeEventId = @(), + [array]$IncludeUsername = @(), + [array]$ExcludeUsername = @(), + [array]$IncludeEntryType = @(), + [array]$ExcludeEntryType = @(), + [array]$IncludeMessage = @(), + [array]$ExcludeMessage = @(), + [array]$IncludeSource = @(), + [array]$ExcludeSource = @(), + [array]$ProblemId = @(), + [array]$AcknowledgeId = @(), + [string]$EventsAfter = $null, + [string]$EventsBefore = $null, + [int]$MaxEntries = 40000, + [switch]$DisableTimeCache = $FALSE ); + if ($ProblemId.Count -ne $AcknowledgeId.Count) { + Exit-IcingaThrowException -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Configuration.PluginArgumentAsymmetry -ExceptionList $IcingaPluginExceptions -CustomMessage ([string]::Format('ProblemId count: {0}, AcknowledgeId count: {1}', $ProblemId.Count, $AcknowledgeId.Count)) -Force; + } + [string]$EventLogFilter = ''; $EventIdFilter = New-Object -TypeName 'System.Text.StringBuilder'; + $EventIdInternalFilter = New-Object -TypeName 'System.Text.StringBuilder'; $EntryTypeFilter = New-Object -TypeName 'System.Text.StringBuilder'; $SourceFilter = New-Object -TypeName 'System.Text.StringBuilder'; $UserFilter = New-Object -TypeName 'System.Text.StringBuilder'; @@ -33,7 +40,31 @@ function New-IcingaProviderFilterDataEventlog() $EventBeforeFilter = $null; $EventsAfter = (Convert-IcingaPluginThresholds -Threshold $EventsAfter).Value; $EventsBefore = (Convert-IcingaPluginThresholds -Threshold $EventsBefore).Value; - [string]$CheckHash = (Get-StringSha1 ($LogName + $IncludeEventId + $ExcludeEventId + $IncludeUsername + $ExcludeUsername + $IncludeEntryType + $ExcludeEntryType + $IncludeMessage + $ExcludeMessage)) + '.lastcheck'; + [string]$CheckHash = (Get-StringSha1 ($LogName + $IncludeEventId + $ExcludeEventId + $IncludeUsername + $ExcludeUsername + $IncludeEntryType + $ExcludeEntryType + $IncludeMessage + $ExcludeMessage + $ProblemId + $AcknowledgeId)) + '.lastcheck'; + [hashtable]$ProblemList = @{ }; + [hashtable]$ResolveList = @{ }; + [int]$IndexOfEntries = 0; + + foreach ($entry in $ProblemId) { + Add-IcingaHashtableItem -Hashtable $ProblemList -Key ([string]$entry) -Value @{ + 'NewestEntry' = ''; + 'Message' = ''; + 'Count' = 0; + 'ResolvedId' = $AcknowledgeId[$IndexOfEntries]; + 'IsProblem' = $TRUE; + } | Out-Null; + } + + # Reset the Id's + [int]$IndexOfEntries = 0; + + foreach ($entry in $AcknowledgeId) { + Add-IcingaHashtableItem -Hashtable $ResolveList -Key ([string]$entry) -Value @{ + 'NewestEntry' = ''; + 'Count' = 0; + 'ProblemId' = $ProblemId[$IndexOfEntries]; + } | Out-Null; + } if ([string]::IsNullOrEmpty($EventsAfter) -and $DisableTimeCache -eq $FALSE) { $time = Get-IcingaCacheData -Space 'provider' -CacheStore 'eventlog' -KeyName $CheckHash; @@ -67,28 +98,42 @@ function New-IcingaProviderFilterDataEventlog() [string]$EventBeforeFilter = ([datetime]::FromFileTime(((Get-Date).ToFileTime()))).ToString("yyyy-MM-dd HH:mm:ss"); } - foreach ($entry in $IncludeEventId) { - if ($EventIdFilter.Length -ne 0) { - $EventIdFilter.Append( - ([string]::Format(' and EventID={0}', $entry)) - ) | Out-Null; - } else { - $EventIdFilter.Append( - ([string]::Format('EventID={0}', $entry)) - ) | Out-Null; - } - } + # The filter string can be used to query Event Log entries based on the specified Event IDs, to filter correctly + # between included and excluded Event IDs to ensure that includes are separated with OR while excludes + # are added with AND to ensure that the filter string is correctly constructed. + if ($IncludeEventId.Count -ne 0 -Or $ExcludeEventId.Count -ne 0) { + $EventIdInternalFilter.Append('(') | Out-Null; - foreach ($entry in $ExcludeEventId) { - if ($EventIdFilter.Length -ne 0) { - $EventIdFilter.Append( - ([string]::Format(' and EventID!={0}', $entry)) - ) | Out-Null; - } else { - $EventIdFilter.Append( - ([string]::Format('EventID!={0}', $entry)) - ) | Out-Null; + foreach ($entry in $IncludeEventId) { + if ($EventIdFilter.Length -ne 0) { + $EventIdFilter.Append( + ([string]::Format(' or EventID={0}', $entry)) + ) | Out-Null; + } else { + $EventIdFilter.Append( + ([string]::Format('( EventID={0}', $entry)) + ) | Out-Null; + } } + + if ($EventIdFilter.Length -ne 0) { + $EventIdFilter.Append(' )'); + } + + foreach ($entry in $ExcludeEventId) { + if ($EventIdFilter.Length -ne 0) { + $EventIdFilter.Append( + ([string]::Format(' and EventID!={0}', $entry)) + ) | Out-Null; + } else { + $EventIdFilter.Append( + ([string]::Format('EventID!={0}', $entry)) + ) | Out-Null; + } + } + + $EventIdInternalFilter.Append($EventIdFilter.ToString()) | Out-Null; + $EventIdInternalFilter.Append(')') | Out-Null; } foreach ($entry in $IncludeEntryType) { @@ -175,7 +220,7 @@ function New-IcingaProviderFilterDataEventlog() ([string]::Format(' and TimeCreated[@SystemTime<="{0}"]', (Get-Date $EventBeforeFilter).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ssZ"))) ) | Out-Null; - [string]$EventLogFilter = Add-IcingaProviderEventlogFilterData -EventFilter $EventLogFilter -StringBuilderObject $EventIdFilter; + [string]$EventLogFilter = Add-IcingaProviderEventlogFilterData -EventFilter $EventLogFilter -StringBuilderObject $EventIdInternalFilter; [string]$EventLogFilter = Add-IcingaProviderEventlogFilterData -EventFilter $EventLogFilter -StringBuilderObject $EntryTypeFilter; [string]$EventLogFilter = Add-IcingaProviderEventlogFilterData -EventFilter $EventLogFilter -StringBuilderObject $SourceFilter; [string]$EventLogFilter = Add-IcingaProviderEventlogFilterData -EventFilter $EventLogFilter -StringBuilderObject $UserFilter; @@ -198,6 +243,7 @@ function New-IcingaProviderFilterDataEventlog() $EventLogQueryData = New-Object PSCustomObject; $EventLogQueryData | Add-Member -MemberType NoteProperty -Name 'List' -Value (New-Object PSCustomObject); $EventLogQueryData | Add-Member -MemberType NoteProperty -Name 'Events' -Value (New-Object PSCustomObject); + $EventLogQueryData | Add-Member -MemberType NoteProperty -Name 'Problems' -Value (New-Object PSCustomObject); $EventLogQueryData | Add-Member -MemberType NoteProperty -Name 'HasEvents' -Value $FALSE; foreach ($event in $EventLogEntries) { @@ -206,6 +252,19 @@ function New-IcingaProviderFilterDataEventlog() continue; } + if ($ProblemList.ContainsKey([string]$event.Id)) { + if ([string]::IsNullOrEmpty($ProblemList[([string]$event.Id)].NewestEntry)) { + $ProblemList[([string]$event.Id)].NewestEntry = ([string]($event.TimeCreated)); + $ProblemList[([string]$event.Id)].Message = [string]($event.Message); + } + $ProblemList[([string]$event.Id)].Count += 1; + } + if ($ResolveList.ContainsKey([string]$event.Id)) { + if ([string]::IsNullOrEmpty($ResolveList[([string]$event.Id)].NewestEntry)) { + $ResolveList[([string]$event.Id)].NewestEntry = ([string]($event.TimeCreated)); + } + } + $EventLogQueryData.HasEvents = $TRUE; [string]$EventIdentifier = [string]::Format('{0}-{1}', @@ -242,17 +301,45 @@ function New-IcingaProviderFilterDataEventlog() } } + if ($ProblemId.Count -ne 0) { + foreach ($problem in $ProblemList.Keys) { + [string]$ressolvedId = $ProblemList[$problem].ResolvedId; + $LastProblem = $null; + $LastResolved = $null; + + if ([string]::IsNullOrEmpty($ProblemList[$problem].NewestEntry) -eq $FALSE) { + $LastProblem = [DateTime]$ProblemList[$problem].NewestEntry; + } + if ([string]::IsNullOrEmpty($ResolveList[$ressolvedId].NewestEntry) -eq $FALSE) { + $LastResolved = [DateTime]$ResolveList[$ressolvedId].NewestEntry; + } + + if ($ResolveList.ContainsKey($ressolvedId)) { + if ($null -ne $LastProblem -And $null -ne $LastResolved -And $LastProblem -le $LastResolved) { + $ProblemList[$problem].$IsProblem = $FALSE; + } else { + $EventLogQueryData.Problems | Add-Member -MemberType NoteProperty -Name $problem -Value (New-Object PSCustomObject); + $EventLogQueryData.Problems.$problem | Add-Member -MemberType NoteProperty -Name 'EventId' -Value $problem; + $EventLogQueryData.Problems.$problem | Add-Member -MemberType NoteProperty -Name 'Count' -Value $ProblemList[$problem].Count; + $EventLogQueryData.Problems.$problem | Add-Member -MemberType NoteProperty -Name 'Message' -Value $ProblemList[$problem].Message; + $EventLogQueryData.Problems.$problem | Add-Member -MemberType NoteProperty -Name 'NewestEntry' -Value $ProblemList[$problem].NewestEntry; + } + } + } + } + if ($null -ne $EventLogEntries) { $EventLogEntries.Dispose(); } - $EventLogEntries = $null; - $EventLogFilter = $null; - $EventIdFilter = $null; - $EntryTypeFilter = $null; - $SourceFilter = $null; - $UserFilter = $null; - $TimeFilter = $null; + $EventLogEntries = $null; + $EventLogFilter = $null; + $EventIdFilter = $null; + $EventIdInternalFilter = $null; + $EntryTypeFilter = $null; + $SourceFilter = $null; + $UserFilter = $null; + $TimeFilter = $null; return $EventLogQueryData; }