Src/Private/Get-AbrExoAlerting.ps1
|
function Get-AbrExoAlerting { <# .SYNOPSIS Documents Exchange Online and Defender for Office 365 alert policies, notification recipients, and operational monitoring configuration. .NOTES Version: 0.1.0 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Exchange Online Alerting configuration for $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Alerting' } process { Section -Style Heading2 'Alert Policies' { Paragraph "The following section documents alert policies configured in Microsoft 365 Defender and Purview compliance for tenant $TenantId." BlankLine #region Protection Alert Policies try { Write-Host " - Retrieving protection alert policies..." $AlertPolicies = Get-ProtectionAlert -ErrorAction Stop | Sort-Object Severity, Name if ($AlertPolicies -and @($AlertPolicies).Count -gt 0) { # Split into enabled vs disabled $EnabledAlerts = @($AlertPolicies | Where-Object { $_.Disabled -ne $true }) $DisabledAlerts = @($AlertPolicies | Where-Object { $_.Disabled -eq $true }) # IsSystemPolicy is unreliable in REST API (always null/false). # Identify system/built-in policies by IsDefault flag or known Microsoft policy name patterns. $SystemAlerts = @($AlertPolicies | Where-Object { $_.IsDefault -eq $true -or $_.IsSystemPolicy -eq $true -or $_.Name -match '^(Malware|Phish|Suspicious|Messages have been|Reply-all|User restricted|Potential Nation|A potentially|A user clicked|Form blocked|Form flagged|DLP-|Failed exact|Noisy Alert)' }) $CustomAlerts = @($AlertPolicies | Where-Object { $_.IsDefault -ne $true -and $_.IsSystemPolicy -ne $true -and $_.Name -notmatch '^(Malware|Phish|Suspicious|Messages have been|Reply-all|User restricted|Potential Nation|A potentially|A user clicked|Form blocked|Form flagged|DLP-|Failed exact|Noisy Alert)' }) Section -Style Heading3 'Alert Policy Summary' { Paragraph "$(@($AlertPolicies).Count) alert policy/policies configured. $($EnabledAlerts.Count) enabled, $($DisabledAlerts.Count) disabled. $($SystemAlerts.Count) are built-in Microsoft policies and $($CustomAlerts.Count) are custom." BlankLine $SumObj = [System.Collections.ArrayList]::new() $sumInObj = [ordered] @{ 'Total Alert Policies' = @($AlertPolicies).Count 'Enabled Policies' = $EnabledAlerts.Count 'Disabled Policies' = $DisabledAlerts.Count 'System (Built-in) Policies'= $SystemAlerts.Count 'Custom Policies' = $CustomAlerts.Count } $SumObj.Add([pscustomobject]$sumInObj) | Out-Null $SumTableParams = @{ Name = "Alert Policy Summary - $TenantId"; List = $true; ColumnWidths = 50, 50 } if ($Report.ShowTableCaptions) { $SumTableParams['Caption'] = "- $($SumTableParams.Name)" } $SumObj | Table @SumTableParams } # High + Medium severity policies $HighMedPolicies = $AlertPolicies | Where-Object { $_.Severity -in @('High', 'Medium') -and $_.Disabled -ne $true } | Sort-Object Severity, Name if (@($HighMedPolicies).Count -gt 0) { Section -Style Heading3 'High & Medium Severity Alerts (Enabled)' { Paragraph "The following $(@($HighMedPolicies).Count) high and medium severity alert policies are currently enabled." BlankLine $HmObj = [System.Collections.ArrayList]::new() foreach ($Policy in $HighMedPolicies) { $NotifyCount = if ($Policy.NotifyUser) { @($Policy.NotifyUser).Count } else { 0 } $hmInObj = [ordered] @{ 'Alert Name' = $Policy.Name 'Severity' = $Policy.Severity 'Category' = $Policy.Category 'Notify Recipients' = $NotifyCount 'System Policy' = $Policy.IsSystemPolicy 'Threshold (count)' = if ($Policy.Threshold) { $Policy.Threshold } else { 'N/A' } } $HmObj.Add([pscustomobject](ConvertTo-HashToYN $hmInObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.AuditLogging) { $null = ($HmObj | Where-Object { [int]"$($_.'Notify Recipients')" -eq 0 } | Set-Style -Style Warning | Out-Null) } }) $HmTableParams = @{ Name = "High-Med Severity Alerts - $TenantId"; List = $false; ColumnWidths = 28, 10, 16, 12, 12, 12, 10 } if ($Report.ShowTableCaptions) { $HmTableParams['Caption'] = "- $($HmTableParams.Name)" } if ($HmObj.Count -gt 0) { $HmObj | Table @HmTableParams } $script:ExcelSheets['High-Med Alerts'] = $HmObj } } # Disabled high-severity alerts (risk) $DisabledHighAlerts = $AlertPolicies | Where-Object { $_.Severity -eq 'High' -and $_.Disabled -eq $true } if (@($DisabledHighAlerts).Count -gt 0) { Section -Style Heading3 'Disabled High-Severity Alerts' { Paragraph "WARNING: The following $(@($DisabledHighAlerts).Count) high-severity alert policy/policies are currently disabled. Re-enable these to ensure critical security events are detected." BlankLine $DhObj = [System.Collections.ArrayList]::new() foreach ($Policy in $DisabledHighAlerts) { $dhInObj = [ordered] @{ 'Alert Name' = $Policy.Name 'Category' = $Policy.Category 'System Policy' = $Policy.IsSystemPolicy } $DhObj.Add([pscustomobject](ConvertTo-HashToYN $dhInObj)) | Out-Null } $null = (& { if ($HealthCheck.ExchangeOnline.AuditLogging) { $null = ($DhObj | Set-Style -Style Critical | Out-Null) } }) $DhTableParams = @{ Name = "Disabled High-Severity Alerts - $TenantId"; List = $false; ColumnWidths = 55, 25, 20 } if ($Report.ShowTableCaptions) { $DhTableParams['Caption'] = "- $($DhTableParams.Name)" } if ($DhObj.Count -gt 0) { $DhObj | Table @DhTableParams } $script:ExcelSheets['Disabled High Alerts'] = $DhObj } } # All alerts at InfoLevel 2 if ($InfoLevel.Alerting -ge 2) { Section -Style Heading3 'All Alert Policies' { Paragraph "Complete inventory of all $(@($AlertPolicies).Count) alert policy/policies." BlankLine $AllAlObj = [System.Collections.ArrayList]::new() foreach ($Policy in $AlertPolicies) { $NotifyCount = if ($Policy.NotifyUser) { @($Policy.NotifyUser).Count } else { 0 } $allAlInObj = [ordered] @{ 'Alert Name' = $Policy.Name 'Severity' = $Policy.Severity 'Category' = $Policy.Category 'Enabled' = -not $Policy.Disabled 'Notify Recipients' = $NotifyCount 'System Policy' = $Policy.IsSystemPolicy } $AllAlObj.Add([pscustomobject](ConvertTo-HashToYN $allAlInObj)) | Out-Null } $AllAlTableParams = @{ Name = "All Alert Policies - $TenantId"; List = $false; ColumnWidths = 34, 10, 16, 10, 12, 10, 8 } if ($Report.ShowTableCaptions) { $AllAlTableParams['Caption'] = "- $($AllAlTableParams.Name)" } if ($AllAlObj.Count -gt 0) { $AllAlObj | Table @AllAlTableParams } $script:ExcelSheets['All Alert Policies'] = $AllAlObj } } } else { Paragraph "No protection alert policies found in tenant $TenantId. Verify that the account has Security Reader or higher permissions." } } catch { Write-ExoError 'Alerting' "Unable to retrieve alert policies: $($_.Exception.Message)" Paragraph "Unable to retrieve alert policies. Verify the Security and Compliance PowerShell session was established successfully during connection." } #endregion #region Mail Flow Alert Policies (best effort via Get-MailDetailTransportRuleReport) Section -Style Heading3 'Monitoring Recommendations' { Paragraph "The following monitoring capabilities should be configured for Exchange Online in tenant ${TenantId}:" BlankLine $MonObj = [System.Collections.ArrayList]::new() $monRows = @( [pscustomobject]@{ 'Monitoring Area'='Malware detected in messages'; 'Recommended Alert'='Malware campaign detected after delivery'; 'Priority'='High'; 'Source'='Microsoft Defender for Office 365' } [pscustomobject]@{ 'Monitoring Area'='Phishing clicks by users'; 'Recommended Alert'='User clicked a potentially malicious URL'; 'Priority'='High'; 'Source'='Microsoft Defender for Office 365' } [pscustomobject]@{ 'Monitoring Area'='Account compromise / suspicious forwarding'; 'Recommended Alert'='Email forwarding rules created'; 'Priority'='High'; 'Source'='Microsoft 365 Defender' } [pscustomobject]@{ 'Monitoring Area'='Admin activity'; 'Recommended Alert'='Exchange transport rule created'; 'Priority'='Medium'; 'Source'='Microsoft 365 Compliance' } [pscustomobject]@{ 'Monitoring Area'='Mail flow degradation'; 'Recommended Alert'='Messages have been delayed'; 'Priority'='Medium'; 'Source'='Exchange Online (built-in)' } [pscustomobject]@{ 'Monitoring Area'='Outbound spam'; 'Recommended Alert'='User restricted from sending email'; 'Priority'='High'; 'Source'='Exchange Online Protection' } ) foreach ($row in $monRows) { $MonObj.Add($row) | Out-Null } $MonTableParams = @{ Name = "Monitoring Recommendations - $TenantId"; List = $false; ColumnWidths = 25, 32, 10, 33 } if ($Report.ShowTableCaptions) { $MonTableParams['Caption'] = "- $($MonTableParams.Name)" } if ($MonObj.Count -gt 0) { $MonObj | Table @MonTableParams } } #endregion } } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Alerting' } } |