Public/Get-NSPM365AuditSummary.ps1

function Get-NSPM365AuditSummary {
    <#
    .SYNOPSIS
        Summarizes a Unified Audit Log export by operation, client IP, user or workload: count, first and last time, and who/where/what.
    .DESCRIPTION
        Reads Purview audit CSV exports (-Path) or records already parsed by NSP.LogParse's
        ConvertFrom-NSPM365AuditCsv (pipeline). One row per group, most frequent first:
 
          Name, Count, FirstUtc, LastUtc, UserCount, IpCount, Users, ClientIPs, Operations, Workloads
 
        The client IP is read from whichever property the workload uses (ClientIP,
        ClientIPAddress, ActorIpAddress, UserIp), with Exchange's port suffix removed.
 
        -ExcludeIP leaves out records from known-good addresses (office egress, the user's home
        ISP): exact addresses, CIDR ranges, or prefixes ending in '*'.
    .PARAMETER Path
        One or more Purview audit CSV exports (Audit > Export results).
    .PARAMETER InputObject
        Parsed audit records (ConvertFrom-NSPM365AuditCsv).
    .PARAMETER GroupBy
        Operation (default), ClientIP, UserId or Workload.
    .PARAMETER ExcludeIP
        Addresses, CIDR ranges or '*'-suffixed prefixes to leave out.
    .EXAMPLE
        Get-NSPM365AuditSummary -Path .\audit.csv | Format-Table
    .EXAMPLE
        Get-NSPM365AuditSummary -Path .\audit.csv -GroupBy ClientIP -ExcludeIP 198.51.100.0/24 | Resolve-NSPIpInfo
    #>

    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path')]
        [string[]]$Path,
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Record')]
        [psobject[]]$InputObject,
        [ValidateSet('Operation', 'ClientIP', 'UserId', 'Workload')]
        [string]$GroupBy = 'Operation',
        [string[]]$ExcludeIP = @()
    )

    begin { $records = New-Object System.Collections.Generic.List[object] }
    process { foreach ($record in $InputObject) { if ($null -ne $record) { $records.Add($record) } } }
    end {
        $source = if ($PSCmdlet.ParameterSetName -eq 'Path') { Read-NSPAuditCsv -Path $Path } else { $records.ToArray() }
        $views = @(foreach ($record in $source) {
                $view = ConvertTo-NSPAuditView $record
                if (-not (Test-NSPIpInRange -Ip $view.ClientIP -Range $ExcludeIP)) { $view }
            })
        $distinct = { param($rows, $property) @($rows | ForEach-Object $property | Where-Object { $_ } | Sort-Object -Unique) }
        foreach ($group in ($views | Group-Object -Property $GroupBy | Sort-Object Count -Descending)) {
            $times = @($group.Group | ForEach-Object TimeUtc | Where-Object { $_ } | Sort-Object)
            $users = & $distinct $group.Group 'UserId'
            $ips = & $distinct $group.Group 'ClientIP'
            [pscustomobject]@{
                Name       = $group.Name
                Count      = $group.Count
                FirstUtc   = if ($times.Count) { $times[0] } else { '' }
                LastUtc    = if ($times.Count) { $times[-1] } else { '' }
                UserCount  = $users.Count
                IpCount    = $ips.Count
                Users      = $users -join '; '
                ClientIPs  = $ips -join '; '
                Operations = (& $distinct $group.Group 'Operation') -join '; '
                Workloads  = (& $distinct $group.Group 'Workload') -join '; '
            }
        }
    }
}