param( [Parameter(Mandatory = $true)] [string]$Path ) $ErrorActionPreference = 'Stop' $resolved = Resolve-Path -LiteralPath $Path $item = Get-Item -LiteralPath $resolved $file = if ($item.PSIsContainer) { Get-ChildItem -LiteralPath $item.FullName -Filter '*.jsonl' -File | Sort-Object LastWriteTime -Descending | Select-Object -First 1 } else { $item } if (-not $file) { throw "No JSONL file found: $Path" } $records = New-Object System.Collections.Generic.List[object] $lineNumber = 0 Get-Content -LiteralPath $file.FullName | ForEach-Object { $lineNumber++ try { $records.Add(($_ | ConvertFrom-Json)) } catch { throw "Invalid JSONL at line $lineNumber in $($file.FullName): $($_.Exception.Message)" } } $sequenceStrict = $true for ($index = 1; $index -lt $records.Count; $index++) { if ([uint64]$records[$index].sequence -le [uint64]$records[$index - 1].sequence) { $sequenceStrict = $false break } } $immQueries = @($records | Where-Object recordType -eq 'imm_query') $successfulResults = @($immQueries | Where-Object immQueryStatus -eq 'success') $echoSummaries = @($records | Where-Object recordType -eq 'echo_correlation') $textLeaks = @($records | Where-Object { $_.messageCategory -eq 'text' -and $_.wParamHex -ne $null }) $lost = @($records | Where-Object recordType -eq 'records_lost') $attach = $records | Where-Object recordType -eq 'window_attach' | Select-Object -First 1 $correlationCounts = [ordered]@{} foreach ($entry in $echoSummaries) { $key = "$($entry.echoChannel):$($entry.correlationStatus)" if (-not $correlationCounts.Contains($key)) { $correlationCounts[$key] = 0 } $correlationCounts[$key]++ } [pscustomobject]@{ file = $file.FullName recordCount = $records.Count sequenceStrict = $sequenceStrict recordsLost = $lost.Count textRawWParamLeaks = $textLeaks.Count isWindowUnicode = $attach.isWindowUnicode ansiCodePage = $attach.ansiCodePage wndProcThreadIds = @($records | Where-Object sourceBackend -eq 'Win32WndProc' | Select-Object -ExpandProperty threadId -Unique) hostThreadIds = @($records | Where-Object sourceBackend -eq 'HostEventLoop' | Select-Object -ExpandProperty threadId -Unique) imeStartCount = @($records | Where-Object messageName -eq 'WM_IME_STARTCOMPOSITION').Count imeCompositionCount = @($records | Where-Object messageName -eq 'WM_IME_COMPOSITION').Count imeEndCount = @($records | Where-Object messageName -eq 'WM_IME_ENDCOMPOSITION').Count inputLanguageChangeRequestCount = @($records | Where-Object messageName -eq 'WM_INPUTLANGCHANGEREQUEST').Count inputLanguageChangeCount = @($records | Where-Object messageName -eq 'WM_INPUTLANGCHANGE').Count immQueryCount = $immQueries.Count successfulResultCount = $successfulResults.Count successfulUtf16Lengths = @($successfulResults | Select-Object -ExpandProperty utf16Length) successfulScalarCounts = @($successfulResults | Select-Object -ExpandProperty scalarCount) digestPresentCount = @($successfulResults | Where-Object sessionDigest -ne $null).Count correlationCounts = [pscustomobject]$correlationCounts closeObserved = @($records | Where-Object messageName -eq 'WM_CLOSE').Count -gt 0 sessionEndObserved = @($records | Where-Object recordType -eq 'session_end').Count -gt 0 }