Public/Find-LMLogAnomalyAlerts.ps1

<#
.SYNOPSIS
Generate list of alerts that have an associated log anomaly.

.DESCRIPTION
Generate list of alerts that have an associated log anomaly.

.EXAMPLE
Find-LMLogAnomalyAlerts

.NOTES
Must be connected to the portal you want to pull alerts from ahead of time.

.INPUTS
None. Does not accept pipeline input

.LINK
Module repo: https://github.com/stevevillardi/Logic.Monitor.SE

.LINK
PSGallery: https://www.powershellgallery.com/packages/Logic.Monitor.SE
#>


Function Find-LMLogAnomalyAlerts {
    [CmdletBinding()]
    Param(
        [DateTime]$StartDate = (Get-Date).AddDays(-14),
        [DateTime]$EndDate = (Get-Date)
    )
    
    Write-Information "Starting log anomaly alert matching process..."
    Write-Information "Date Range: $StartDate to $EndDate"

    If($(Get-LMAccountStatus).Valid){
        Write-Information "Account validation successful. Retrieving alerts and anomalies..."
        
        $Alerts = Get-LMAlert -StartDate $StartDate -EndDate $EndDate -ClearedAlerts $true
        Write-Information "Retrieved $($Alerts.Count) alerts"
        
        $Anomalies = Get-LMLogMessage -Query '_anomaly.type="never_before_seen"' -StartDate $(Get-Date).AddDays(-3) -EndDate $(Get-Date) -Async
        Write-Information "Retrieved $($Anomalies.Count) anomalies"

        If($Anomalies -and $Alerts){
            Write-Information "Starting matching process..."
            $matchedEvents = @()
            $processedCount = 0
            
            foreach ($log in $Anomalies) {
                $processedCount++
                if ($processedCount % 100 -eq 0) {
                    Write-Information "Processed $processedCount of $($Anomalies.Count) logs..."
                }

                # Convert timestamp to DateTime for comparison
                $logTime = [DateTimeOffset]::FromUnixTimeMilliseconds($log.timestamp).DateTime
                $logResourceId = $log._resource.id
                
                Write-Debug "Processing log ID: $($log.id) for resource $logResourceId at time $logTime"
                
                # Find matching alerts
                $matchingAlerts = $Alerts | Where-Object {
                    $alertResourceId = $_.monitorObjectId
                    $alertStartTime = [DateTimeOffset]::FromUnixTimeSeconds($_.startEpoch).DateTime
                    
                    # Check if IDs match and time is within ±30 minutes
                    ($logResourceId -eq $alertResourceId) -and 
                    ([Math]::Abs(($logTime - $alertStartTime).TotalMinutes) -le 30)
                }
                
                if ($matchingAlerts) {
                    Write-Information "Found $($matchingAlerts.Count) matching alert(s) for log ID: $($log.id)"
                    foreach ($alert in $matchingAlerts) {
                        $timeDiff = [Math]::Abs(($logTime - [DateTimeOffset]::FromUnixTimeSeconds($alert.startEpoch).DateTime).TotalMinutes)
                        Write-Debug "Matched with alert ID: $($alert.id), time difference: $timeDiff minutes"
                        
                        $matchedEvents += [PSCustomObject]@{
                            LogId = $log.id
                            AlertId = $alert.id
                            ResourceId = $logResourceId
                            LogTimestamp = $logTime
                            AlertTimestamp = [DateTimeOffset]::FromUnixTimeSeconds($alert.startEpoch).DateTime
                            TimeDifferenceMinutes = $timeDiff
                            LogMessage = $log.message
                            AlertType = $alert.type
                            Severity = $alert.severity
                        }
                    }
                }
                else {
                    Write-Debug "No matching alerts found for log ID: $($log.id)"
                }
            }
            
            # Return the matched events
            Write-Information "Matching process complete. Found $($matchedEvents.Count) total matches."
            if ($matchedEvents.Count -gt 0) {
                Write-Information "Returning matched events..."
                return $matchedEvents
            } else {
                Write-Information "No matching events found within the specified time window."
                return $null
            }
        }
        Else {
            Write-Information "No anomalies or alerts found for the given date range."
            Return
        }
    }
    Else {
        Write-Error "Please ensure you are logged in before running any commands, use Connect-LMAccount to login and try again."
    }
}