Public/Expand-NSPM365AuditRecord.ps1

function Expand-NSPM365AuditRecord {
    <#
    .SYNOPSIS
        Emits one row per nested item in a parsed Microsoft 365 audit record.
    .DESCRIPTION
        Expands MailItemsAccessed Folders/FolderItems, AttachmentAccess
        Messages/MessageItems, or AffectedItems from mail operations. The
        original record, container, and item remain nested
        to avoid property name collisions or loss of source detail.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)][psobject]$InputObject,
        [ValidateSet('Auto', 'FolderItems', 'MessageItems', 'AffectedItems')][string]$ItemSource = 'Auto',
        [switch]$IncludeEmpty
    )

    process {
        if (-not $InputObject.PSObject.Properties['AuditData'] -or
            -not $InputObject.PSObject.Properties['Operation']) {
            throw 'Each record must have AuditData and Operation properties.'
        }

        $source = $ItemSource
        if ($source -eq 'Auto') {
            switch ([string]$InputObject.Operation) {
                'MailItemsAccessed' { $source = 'FolderItems'; break }
                'AttachmentAccess' { $source = 'MessageItems'; break }
                { $_ -in 'HardDelete', 'SoftDelete', 'MoveToDeletedItems' } { $source = 'AffectedItems'; break }
                default { throw "No default item source for operation '$($InputObject.Operation)'. Specify -ItemSource." }
            }
        }

        $audit = $InputObject.AuditData
        if ($null -eq $audit) { throw 'AuditData is null.' }

        $containers = @()
        if ($source -in 'FolderItems', 'MessageItems') {
            $containerName = if ($source -eq 'FolderItems') { 'Folders' } else { 'Messages' }
            if ($audit.PSObject.Properties[$containerName] -and $null -ne $audit.$containerName) {
                $containers = @($audit.$containerName)
            }
        } else {
            if ($audit.PSObject.Properties['AffectedItems'] -and $null -ne $audit.AffectedItems) {
                $containers = @($audit.AffectedItems)
            }
        }

        $folderIndex = 0
        foreach ($container in $containers) {
            if ($source -in 'FolderItems', 'MessageItems') {
                if ($null -eq $container) { throw "AuditData.$containerName contains a null entry." }
                $folder = if ($source -eq 'FolderItems') { $container } else { $null }
                $message = if ($source -eq 'MessageItems') { $container } else { $null }
                $items = @()
                $itemName = if ($source -eq 'FolderItems') { 'FolderItems' } else { 'MessageItems' }
                if ($container.PSObject.Properties[$itemName] -and $null -ne $container.$itemName) {
                    $items = @($container.$itemName)
                }
            } else {
                $folder = $null
                $message = $null
                $items = @($container)
            }

            if ($items.Count -eq 0 -or ($items.Count -eq 1 -and $null -eq $items[0])) {
                if ($IncludeEmpty) {
                    [pscustomobject]@{
                        RecordId = $InputObject.RecordId
                        Operation = $InputObject.Operation
                        CreationDate = $InputObject.CreationDate
                        UserId = $InputObject.UserId
                        FolderIndex = if ($source -eq 'FolderItems') { $folderIndex } else { $null }
                        MessageIndex = if ($source -eq 'MessageItems') { $folderIndex } else { $null }
                        ItemIndex = $null
                        Folder = $folder
                        Message = $message
                        Item = $null
                        _Record = $InputObject
                    }
                }
            } else {
                for ($itemIndex = 0; $itemIndex -lt $items.Count; $itemIndex++) {
                    [pscustomobject]@{
                        RecordId = $InputObject.RecordId
                        Operation = $InputObject.Operation
                        CreationDate = $InputObject.CreationDate
                        UserId = $InputObject.UserId
                        FolderIndex = if ($source -eq 'FolderItems') { $folderIndex } else { $null }
                        MessageIndex = if ($source -eq 'MessageItems') { $folderIndex } else { $null }
                        ItemIndex = $itemIndex
                        Folder = $folder
                        Message = $message
                        Item = $items[$itemIndex]
                        _Record = $InputObject
                    }
                }
            }
            $folderIndex++
        }
        if ($containers.Count -eq 0 -and $IncludeEmpty) {
            [pscustomobject]@{
                RecordId = $InputObject.RecordId
                Operation = $InputObject.Operation
                CreationDate = $InputObject.CreationDate
                UserId = $InputObject.UserId
                FolderIndex = $null
                MessageIndex = $null
                ItemIndex = $null
                Folder = $null
                Message = $null
                Item = $null
                _Record = $InputObject
            }
        }
    }
}