Private/Core/Test-ConcurrentSessions.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Test-ConcurrentSessions {
    [CmdletBinding()]
    param(
        [hashtable[]]$LoginEvents = @(),

        [int]$WindowMinutes = 5
    )

    $results = [System.Collections.Generic.List[PSCustomObject]]::new()
    if ($LoginEvents.Count -lt 2) { return @($results) }

    # Parse and sort by timestamp
    $parsed = [System.Collections.Generic.List[hashtable]]::new()
    foreach ($event in $LoginEvents) {
        $ip = $event.IpAddress
        if (-not $ip) { continue }
        $ts = if ($event.Timestamp -is [datetime]) { $event.Timestamp } else {
            try { [datetime]::Parse($event.Timestamp) } catch { continue }
        }
        $parsed.Add(@{
            Timestamp = $ts
            IpAddress = $ip
            EventName = $event.EventName
            Event     = $event
        })
    }

    if ($parsed.Count -lt 2) { return @($results) }

    $sorted = @($parsed | Sort-Object { $_.Timestamp })
    $window = [TimeSpan]::FromMinutes($WindowMinutes)
    $reported = [System.Collections.Generic.HashSet[string]]::new()

    for ($i = 0; $i -lt $sorted.Count; $i++) {
        $current = $sorted[$i]
        $concurrentIps = [System.Collections.Generic.HashSet[string]]::new()
        [void]$concurrentIps.Add($current.IpAddress)
        $concurrentEvents = [System.Collections.Generic.List[hashtable]]::new()
        $concurrentEvents.Add($current)

        for ($j = $i + 1; $j -lt $sorted.Count; $j++) {
            $other = $sorted[$j]
            if (($other.Timestamp - $current.Timestamp) -gt $window) { break }
            if ($other.IpAddress -ne $current.IpAddress) {
                [void]$concurrentIps.Add($other.IpAddress)
                $concurrentEvents.Add($other)
            }
        }

        if ($concurrentIps.Count -ge 2) {
            $key = ($concurrentIps | Sort-Object) -join '|'
            $timeKey = $current.Timestamp.ToString('yyyyMMddHHmm')
            $reportKey = "${key}:${timeKey}"
            if (-not $reported.Contains($reportKey)) {
                [void]$reported.Add($reportKey)
                $results.Add([PSCustomObject]@{
                    WindowStart   = $current.Timestamp
                    WindowEnd     = ($concurrentEvents | Sort-Object { $_.Timestamp } | Select-Object -Last 1).Timestamp
                    DistinctIps   = @($concurrentIps | Sort-Object)
                    IpCount       = $concurrentIps.Count
                    EventCount    = $concurrentEvents.Count
                })
            }
        }
    }

    return @($results)
}