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" } } } |