Public/Get-NSPM365InboxRuleAudit.ps1

function Get-NSPM365InboxRuleAudit {
    <#
    .SYNOPSIS
        Pulls inbox rule and mailbox forwarding changes out of Unified Audit Log records, flagging the ones that look like attacker tradecraft.
    .DESCRIPTION
        Covers New-/Set-/Enable-/Disable-/Remove-InboxRule (Exchange cmdlets and Outlook on the
        web), UpdateInboxRules (Outlook desktop), and Set-Mailbox when it changes forwarding.
        Rule parameters (Name, MoveToFolder, ForwardTo, DeleteMessage, ...) come out as columns
        so you don't have to dig through the Parameters array.
 
        Flags (the same as Get-NSPM365InboxRuleReport):
          Forwards forwards or redirects outside -InternalDomain (anywhere, without it)
          ForwardsInternal forwards only to internal recipients (informational)
          Deletes, MarksRead
          MovesTo:<folder> RSS Feeds, Conversation History, Archive, Junk Email, Deleted Items, ...
          OddName a name with no three letters or digits in a row ('.', '..', 'aa')
          TargetsKeywords conditions on invoice / payment / wire / hacked / phish / password ...
 
        Suspicious is true when any flag other than ForwardsInternal is set.
    .PARAMETER Path
        One or more Purview audit CSV exports.
    .PARAMETER InputObject
        Parsed audit records (ConvertFrom-NSPM365AuditCsv).
    .PARAMETER InternalDomain
        The tenant's own domains, so internal forwarding isn't flagged.
    .EXAMPLE
        Get-NSPM365InboxRuleAudit -Path .\audit.csv | Where-Object Suspicious | Format-List
    .EXAMPLE
        Get-NSPM365InboxRuleAudit -Path .\audit.csv -InternalDomain contoso.com, contoso.onmicrosoft.com
    #>

    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')]
        [string[]]$Path,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [psobject[]]$InputObject,
        [string[]]$InternalDomain = @()
    )

    begin {
        $domains = $InternalDomain
        $convert = {
            param($Record)
            $operation = [string]$Record.Operation
            if ($script:NSPRuleOperations -notcontains $operation -or $null -eq $Record.AuditData) { return }
            $audit = $Record.AuditData
            $parameters = if ($audit.PSObject.Properties['Parameters']) { $audit.Parameters } else { @() }
            $get = { param($n) $v = Get-NSPAuditNameValue $parameters $n; if ($null -eq $v) { '' } else { [string]$v } }

            if ($operation -eq 'UpdateInboxRules') {
                $properties = if ($audit.PSObject.Properties['OperationProperties']) { $audit.OperationProperties } else { @() }
                $actions = [string](Get-NSPAuditNameValue $properties 'RuleActions')
                $condition = [string](Get-NSPAuditNameValue $properties 'RuleCondition')
                $ruleName = [string](Get-NSPAuditNameValue $properties 'RuleName')
                $moveTo = ''
                $targets = @(if ($actions -match '(?i)forward|redirect') { 'unknown (see RuleActions)' })
                $delete = $actions -match '(?i)delete'
                $markRead = $actions -match '(?i)markasread|mark as read'
                $conditions = $condition
                $detail = "RuleOperation=$(Get-NSPAuditNameValue $properties 'RuleOperation'); RuleActions=$actions; RuleCondition=$condition"
            } else {
                if ($operation -eq 'Set-Mailbox' -and -not (@('ForwardingSmtpAddress', 'ForwardingAddress', 'DeliverToMailboxAndForward') | Where-Object { $null -ne (Get-NSPAuditNameValue $parameters $_) })) { return }
                $ruleName = & $get 'Name'
                if (-not $ruleName -and $operation -ne 'Set-Mailbox') { $ruleName = ((& $get 'Identity') -split '\\')[-1] }
                $moveTo = & $get 'MoveToFolder'
                $targets = @(ConvertTo-NSPRuleTarget @((& $get 'ForwardTo'), (& $get 'RedirectTo'), (& $get 'ForwardAsAttachmentTo'), (& $get 'ForwardingSmtpAddress'), (& $get 'ForwardingAddress')))
                $delete = (& $get 'DeleteMessage') -eq 'True' -or (& $get 'SoftDeleteMessage') -eq 'True'
                $markRead = (& $get 'MarkAsRead') -eq 'True'
                $conditions = @(foreach ($n in 'From', 'FromAddressContainsWords', 'SubjectContainsWords', 'SubjectOrBodyContainsWords', 'BodyContainsWords', 'SentTo', 'HeaderContainsWords') {
                        $v = & $get $n
                        if ($v) { "$n=$v" }
                    }) -join '; '
                $detail = @(foreach ($p in @($parameters)) { if ($p) { "$($p.Name)=$($p.Value)" } }) -join '; '
            }

            $flagArgs = @{ MoveToFolder = $moveTo; ForwardTarget = $targets; Delete = [bool]$delete; MarkAsRead = [bool]$markRead; Conditions = $conditions; InternalDomain = $domains }
            if ($operation -ne 'Set-Mailbox' -and $operation -ne 'Remove-InboxRule') { $flagArgs.Name = $ruleName }
            $flags = if ($operation -eq 'Remove-InboxRule' -or $operation -eq 'Disable-InboxRule') { @() } else { @(Get-NSPInboxRuleFlag @flagArgs) }
            [pscustomobject]@{
                TimeUtc      = Get-NSPAuditTime $Record
                Operation    = $operation
                UserId       = [string]$Record.UserId
                Mailbox      = if ($audit.PSObject.Properties['ObjectId']) { [string]$audit.ObjectId } else { '' }
                ClientIP     = Get-NSPAuditClientIp $audit
                RuleName     = $ruleName
                MoveToFolder = $moveTo
                ForwardTo    = $targets -join '; '
                DeleteMessage = [bool]$delete
                MarkAsRead   = [bool]$markRead
                Conditions   = $conditions
                Flags        = $flags -join '; '
                Suspicious   = [bool](@($flags | Where-Object { $_ -ne 'ForwardsInternal' }).Count)
                Detail       = $detail
                RecordId     = [string]$Record.RecordId
            }
        }
    }
    process { foreach ($record in $InputObject) { if ($null -ne $record) { & $convert $record } } }
    end {
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            foreach ($record in (Read-NSPAuditCsv -Path $Path)) { & $convert $record }
        }
    }
}