EventMonitor/Core/EventHistory.ps1

# ── Event History ─────────────────────────────────────────────────────────────
# Reads captured event journal (JSONL files) and presents them in a clean,
# searchable format. Provides both table output and file path for deep analysis.

<#
.SYNOPSIS
    Shows history of tracked security events from the event journal.
.DESCRIPTION
    Reads the JSONL event journal files and presents a formatted table of
    recent events. Each event includes timestamp, name, severity, user, and IP.
 
    For full event details, use -Detailed or open the JSONL file directly.
 
    Requires the event journal to be enabled (Set-EventJournal -Enabled $true).
.PARAMETER Days
    How many days of history to show. Default: 7. Max: 365.
.PARAMETER Severity
    Filter by minimum severity: Critical, High, Medium, Low, Info. Default: all.
.PARAMETER EventName
    Filter by event name (supports wildcards). Example: '*SSH*', '*4625*'
.PARAMETER Detailed
    Show all properties for each event instead of the summary table.
.PARAMETER Last
    Show only the last N events. Default: 50.
.EXAMPLE
    Get-EventHistory
.EXAMPLE
    Get-EventHistory -Days 1 -Severity High
.EXAMPLE
    Get-EventHistory -EventName '*SSH*' -Last 20
.EXAMPLE
    Get-EventHistory -Days 3 -Detailed
#>

function Get-EventHistory {
    [CmdletBinding()]
    param(
        [ValidateRange(1, 365)]
        [int]$Days = 7,

        [ValidateSet('Critical', 'High', 'Medium', 'Low', 'Info')]
        [string]$Severity,

        [string]$EventName,

        [switch]$Detailed,

        [ValidateRange(1, 10000)]
        [int]$Last = 50
    )

    $journalDir = $script:JournalDir
    # Also check if called from EventMonitor root
    if (-not (Test-Path $journalDir)) {
        $journalDir = $script:JournalDir
    }

    if (-not (Test-Path $journalDir)) {
        Write-Warning "Event journal directory not found at '$journalDir'."
        Write-Warning "Enable the journal first: Set-EventJournal -Enabled `$true"
        Write-Warning "Then run Invoke-EventMonitor to capture events."
        return
    }

    # Find journal files within the date range
    $cutoffDate = (Get-Date).AddDays(-$Days)
    $journalFiles = Get-ChildItem -Path $journalDir -Filter 'EventJournal-*.jsonl' |
        Where-Object { $_.LastWriteTime -ge $cutoffDate } |
        Sort-Object Name

    if ($journalFiles.Count -eq 0) {
        Write-Warning "No journal files found for the last $Days day(s)."
        Write-Warning "Run: Invoke-EventMonitor -LookBackMinutes 60"
        return
    }

    # Severity ordering for filtering
    $severityOrder = @{ 'Critical' = 5; 'High' = 4; 'Medium' = 3; 'Low' = 2; 'Info' = 1 }
    $minSeverityLevel = if ($Severity) { $severityOrder[$Severity] } else { 0 }

    # Read and parse all matching events
    $allEvents = [System.Collections.Generic.List[PSCustomObject]]::new()

    foreach ($file in $journalFiles) {
        $lines = Get-Content -Path $file.FullName -ErrorAction SilentlyContinue
        foreach ($line in $lines) {
            if ([string]::IsNullOrWhiteSpace($line)) { continue }
            try {
                $evt = $line | ConvertFrom-Json

                # Severity filter
                $evtSev = if ($evt.severity) { $evt.severity } else { 'Info' }
                if ($severityOrder[$evtSev] -lt $minSeverityLevel) { continue }

                # Event name filter
                if ($EventName -and $evt.event -notlike $EventName) { continue }

                $allEvents.Add([PSCustomObject]@{
                    Time        = $evt.t
                    Event       = $evt.event
                    Severity    = $evtSev
                    Type        = if ($evt.type) { $evt.type } else { '' }
                    User        = if ($evt.UserName) { $evt.UserName }
                                  elseif ($evt.TargetUserName) { $evt.TargetUserName }
                                  elseif ($evt.SubjectUserName) { $evt.SubjectUserName }
                                  else { '' }
                    SourceIP    = if ($evt.SourceIP) { $evt.SourceIP }
                                  elseif ($evt.SourceAddress) { $evt.SourceAddress }
                                  elseif ($evt.IPAddress) { $evt.IPAddress }
                                  elseif ($evt.ClientAddress) { $evt.ClientAddress }
                                  else { '' }
                    Details     = if ($evt.EventDescription) { $evt.EventDescription } else { '' }
                    _Raw        = $evt
                })
            }
            catch {
                continue # Skip malformed JSON lines
            }
        }
    }

    if ($allEvents.Count -eq 0) {
        Write-Warning "No events match the specified filters."
        return
    }

    # Sort by time descending, take last N
    $results = $allEvents | Sort-Object Time -Descending | Select-Object -First $Last

    # Display summary
    $totalFiles = $journalFiles.Count
    $dateRange = "$($journalFiles[0].Name -replace 'EventJournal-|\.jsonl','') to $($journalFiles[-1].Name -replace 'EventJournal-|\.jsonl','')"

    Write-Host ""
    Write-Host " Event History: $($results.Count) events (of $($allEvents.Count) total)" -ForegroundColor Cyan
    Write-Host " Date range: $dateRange ($totalFiles file(s))" -ForegroundColor DarkGray
    Write-Host " Journal path: $journalDir" -ForegroundColor DarkGray
    Write-Host ""

    if ($Detailed) {
        $results | ForEach-Object {
            Write-Host "[$($_.Severity.PadRight(8))] $($_.Time) — $($_.Event)" -ForegroundColor $(
                switch ($_.Severity) {
                    'Critical' { 'Red' }
                    'High'     { 'Yellow' }
                    'Medium'   { 'DarkYellow' }
                    default    { 'Gray' }
                }
            )
            $_._Raw | ConvertTo-Json -Depth 2 | Write-Host -ForegroundColor DarkGray
            Write-Host ""
        }
    }
    else {
        $results | Select-Object Time, Severity, Event, User, SourceIP, Details |
            Format-Table -AutoSize -Wrap
    }

    # Show file paths for deep analysis
    Write-Host " Journal files:" -ForegroundColor DarkGray
    foreach ($f in $journalFiles) {
        Write-Host " $($f.FullName)" -ForegroundColor DarkGray
    }
    Write-Host ""
    Write-Host " Tip: Get-Content '$($journalFiles[-1].FullName)' | ConvertFrom-Json | Where-Object severity -eq 'Critical'" -ForegroundColor DarkGray
    Write-Host ""
}