Public/Invoke-EventActivityAccumulation.ps1

function Invoke-EventActivityAccumulation {
    <#
    .SYNOPSIS
    Accumulates events by session and activity ID using modern PowerShell 7.4 features.
     
    .DESCRIPTION
    This cmdlet groups events by session_id and attach_activity_id. When an event arrives with a different
    activity ID for a session than what was previously seen, the cmdlet outputs all accumulated events for the
    previous activity as a PSCustomObject wrapping an SQLActivityGroup class with dynamic properties.
     
    .PARAMETER EventItem
    The event object to process. Must have session_id and attach_activity_id properties.
     
    .EXAMPLE
    $activities = $events | Invoke-EventActivityAccumulation
     
    # Using method access for properties
    $activities[0].GetEventsByType('rpc_completed') # All RPC completed events
    $activities[0].GetLastTimestamp() # Timestamp of the last event
    $activities[0].GetUsername() # Username from context
     
    # Using dynamic properties
    $activities[0].rpc_completed # Direct access via dynamic property
     
    .NOTES
    Requires PowerShell 7.4 or newer for class enhancements and syntax features.
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [object]$EventItem
    )
    
    begin {
        # Track activities by session
        $sessionActivities = [Dictionary[int, Dictionary[string, System.Collections.ArrayList]]]::new()
        # Track current activity ID for each session
        $currentActivityIds = [Dictionary[int, string]]::new()
        # Counters for diagnostic purposes
        $processedCount = 0
        $emittedCount = 0
        
        Write-Verbose "Begin processing event stream"
    }
    
    process {
        $processedCount++
        
        # Extract session_id (with null check using null coalescing)
        $sessionId = $EventItem.session_id ?? 0
        
        # Extract activity ID from the attach_activity_id using string handling
        $activityIdGuid = $null
        
        if ($null -ne $EventItem.attach_activity_id) {
            $activityIdParts = $EventItem.attach_activity_id.ToString().Split(':')
            $activityIdGuid = $activityIdParts[0]
        }
        
        # Debug logging with modern string interpolation
        if ($processedCount -eq 1 -or ($processedCount % 100 -eq 0)) {
            Write-Verbose "Event $processedCount properties: $($EventItem | Format-List | Out-String)"
            Write-Verbose "SessionId: $sessionId, ActivityIdGuid: $activityIdGuid"
        }
        
        # Skip events without session or activity ID
        if ($sessionId -eq 0 -or [string]::IsNullOrEmpty($activityIdGuid)) {
            Write-Verbose "Skipping event $processedCount without session ID or activity ID"
            return
        }
        
        # Initialize tracking for new sessions
        if (-not $sessionActivities.ContainsKey($sessionId)) {
            $sessionActivities[$sessionId] = [Dictionary[string, System.Collections.ArrayList]]::new()
            $currentActivityIds[$sessionId] = $null
        }
        
        # Check if activity changed for this session
        if ($currentActivityIds[$sessionId] -and 
            $currentActivityIds[$sessionId] -ne $activityIdGuid) {
            
            # Get previous activity
            $previousActivityId = $currentActivityIds[$sessionId]
            $activityEvents = $sessionActivities[$sessionId][$previousActivityId]
            
            Write-Verbose "Activity changed for session $($sessionId) from $($previousActivityId) to $($activityIdGuid)"
            Write-Verbose "Emitting activity with $($activityEvents.Count) events"
            
            # Create our class instance
            $activityGroup = [SQLActivityGroup]::new(
                $sessionId,
                $previousActivityId,
                $activityEvents
            )
            
            # Wrap in a PSCustomObject with dynamic properties for event types
            $outputObj = [PSCustomObject]@{
                PSTypeName = 'SQLActivityGroup'
                # Expose the underlying class instance
                ActivityGroup = $activityGroup
            }
            
            # Add type-specific script properties
            $eventTypes = $activityGroup.GetEventTypes()
            foreach ($eventType in $eventTypes) {
                # Add a dynamic property for each event type
                $outputObj | Add-Member -MemberType ScriptProperty -Name $eventType -Value ([ScriptBlock]::Create("
                    `$this.ActivityGroup.GetEventsByType('$eventType')
                "
))
            }
            
            # Add methods and properties from the class as script properties
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'SessionId' -Value { $this.ActivityGroup.SessionId }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'ActivityId' -Value { $this.ActivityGroup.ActivityId }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'FirstTimestamp' -Value { $this.ActivityGroup.FirstTimestamp }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'EventCount' -Value { $this.ActivityGroup.EventCount }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'LastTimestamp' -Value { $this.ActivityGroup.GetLastTimestamp() }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'Duration' -Value { $this.ActivityGroup.GetDuration() }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'Username' -Value { $this.ActivityGroup.GetUsername() }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'TotalCPUTime' -Value { $this.ActivityGroup.GetTotalCPUTime() }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'TotalDuration' -Value { $this.ActivityGroup.GetTotalDuration() }
            $outputObj | Add-Member -MemberType ScriptProperty -Name 'Events' -Value { $this.ActivityGroup.GetAllEvents() }
            
            # Output the accumulated activity
            $emittedCount++
            $outputObj
            
            # Remove processed activity from tracking to free memory
            $sessionActivities[$sessionId].Remove($previousActivityId)
        }
        
        # Update current activity for this session
        $currentActivityIds[$sessionId] = $activityIdGuid
        
        # Initialize tracking for new activity
        if (-not $sessionActivities[$sessionId].ContainsKey($activityIdGuid)) {
            $sessionActivities[$sessionId][$activityIdGuid] = [System.Collections.ArrayList]::new()
        }
        
        # Add event to current activity (use .Add() method instead of [void] casting)
        $sessionActivities[$sessionId][$activityIdGuid].Add($EventItem) | Out-Null
    }
    
    end {
        Write-Verbose "End processing. Processed $processedCount events, emitted $emittedCount activities."
        Write-Verbose "Emitting remaining activities"
        
        # Output any remaining activities
        foreach ($sessionId in $sessionActivities.Keys) {
            foreach ($activityId in $sessionActivities[$sessionId].Keys) {
                $activityEvents = $sessionActivities[$sessionId][$activityId]
                
                Write-Verbose "Emitting remaining activity $($activityId) for session $($sessionId) with $($activityEvents.Count) events"
                
                # Create our class instance
                $activityGroup = [SQLActivityGroup]::new(
                    $sessionId,
                    $activityId,
                    $activityEvents
                )
                
                # Wrap in a PSCustomObject with dynamic properties for event types
                $outputObj = [PSCustomObject]@{
                    PSTypeName = 'SQLActivityGroup'
                    # Expose the underlying class instance
                    ActivityGroup = $activityGroup
                }
                
                # Add type-specific script properties
                $eventTypes = $activityGroup.GetEventTypes()
                foreach ($eventType in $eventTypes) {
                    # Add a dynamic property for each event type
                    $outputObj | Add-Member -MemberType ScriptProperty -Name $eventType -Value ([ScriptBlock]::Create("
                        `$this.ActivityGroup.GetEventsByType('$eventType')
                    "
))
                }
                
                # Add methods and properties from the class as script properties
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'SessionId' -Value { $this.ActivityGroup.SessionId }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'ActivityId' -Value { $this.ActivityGroup.ActivityId }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'FirstTimestamp' -Value { $this.ActivityGroup.FirstTimestamp }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'EventCount' -Value { $this.ActivityGroup.EventCount }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'LastTimestamp' -Value { $this.ActivityGroup.GetLastTimestamp() }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'Duration' -Value { $this.ActivityGroup.GetDuration() }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'Username' -Value { $this.ActivityGroup.GetUsername() }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'TotalCPUTime' -Value { $this.ActivityGroup.GetTotalCPUTime() }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'TotalDuration' -Value { $this.ActivityGroup.GetTotalDuration() }
                $outputObj | Add-Member -MemberType ScriptProperty -Name 'Events' -Value { $this.ActivityGroup.GetAllEvents() }
                
                # Output the object
                $outputObj
            }
        }
        
        Write-Verbose "Session count: $($sessionActivities.Count)"
        foreach ($sessionId in $sessionActivities.Keys) {
            Write-Verbose " Session $($sessionId) has $($sessionActivities[$sessionId].Count) activities"
        }
    }
}