Public/Send-SignalEventLog.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-EventLogSourceExists {
    <#
    .SYNOPSIS
        Wrapper for [System.Diagnostics.EventLog]::SourceExists() to enable mocking in tests.
    #>

    [CmdletBinding()]
    param([string]$Source)
    [System.Diagnostics.EventLog]::SourceExists($Source)
}

function Register-EventLogSource {
    <#
    .SYNOPSIS
        Wrapper for [System.Diagnostics.EventLog]::CreateEventSource() to enable mocking in tests.
    #>

    [CmdletBinding()]
    param([string]$Source, [string]$LogName)
    [System.Diagnostics.EventLog]::CreateEventSource($Source, $LogName)
}

function Send-SignalEventLog {
    <#
    .SYNOPSIS
        Writes threat alerts to the Windows Event Log.
    .DESCRIPTION
        Creates PSGuerrilla events in the Windows Application event log. Requires the event
        source to be registered (needs admin elevation for first-time setup). Gracefully
        skips if not elevated and source doesn't exist.
    .PARAMETER Threats
        Array of threat objects to write as events.
    .PARAMETER Subject
        Alert subject line used in the event message.
    .PARAMETER Source
        Event log source name. Default: 'PSGuerrilla'.
    .PARAMETER LogName
        Event log name. Default: 'Application'.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject[]]$Threats,

        [string]$Subject = '[PSGuerrilla] Threat Detection',

        [string]$Source = 'PSGuerrilla',
        [string]$LogName = 'Application'
    )

    # Event IDs: 1000=CRITICAL, 1001=HIGH, 1002=MEDIUM, 1003=LOW, 1100=SCAN_COMPLETE
    $eventIdMap = @{
        'CRITICAL' = 1000
        'HIGH'     = 1001
        'MEDIUM'   = 1002
        'LOW'      = 1003
    }

    $entryTypeMap = @{
        'CRITICAL' = 'Error'
        'HIGH'     = 'Error'
        'MEDIUM'   = 'Warning'
        'LOW'      = 'Information'
    }

    # Ensure event source exists
    $sourceExists = $false
    try {
        $sourceExists = Test-EventLogSourceExists -Source $Source
    } catch {
        # SourceExists can throw if not elevated
        Write-Verbose "Cannot check event source (may need elevation): $_"
    }

    if (-not $sourceExists) {
        try {
            Register-EventLogSource -Source $Source -LogName $LogName
            $sourceExists = $true
            Write-Verbose "Created event log source '$Source' in '$LogName'"
        } catch {
            Write-Warning "Cannot create event log source '$Source'. Run as Administrator once to register, or use a different alert provider."
            return [PSCustomObject]@{
                Provider = 'EventLog'
                Success  = $false
                Message  = 'Event log source not registered (requires elevation)'
                Error    = $_.Exception.Message
            }
        }
    }

    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    foreach ($threat in $Threats) {
        $level = $threat.ThreatLevel ?? 'LOW'
        $eventId = $eventIdMap[$level] ?? 1003
        $entryType = $entryTypeMap[$level] ?? 'Information'

        $email = $threat.Email ?? $threat.UserPrincipalName ?? 'unknown'
        $indicators = ($threat.Indicators -join "`r`n - ")
        $score = [int]($threat.ThreatScore ?? 0)

        $message = @"
$Subject
 
Identity: $email
Threat Level: $level
Threat Score: $score
Indicators:
  - $indicators
 
Source: PSGuerrilla v2.1.0
Timestamp: $([datetime]::UtcNow.ToString('o'))
"@


        try {
            Write-EventLog -LogName $LogName -Source $Source -EventId $eventId `
                -EntryType $entryType -Message $message -ErrorAction Stop

            $results.Add([PSCustomObject]@{
                Provider = 'EventLog'
                Success  = $true
                Message  = "Event $eventId written for $email ($level)"
                Error    = $null
            })
        } catch {
            $results.Add([PSCustomObject]@{
                Provider = 'EventLog'
                Success  = $false
                Message  = "Failed to write event for $email"
                Error    = $_.Exception.Message
            })
        }
    }

    $anySuccess = @($results | Where-Object Success).Count -gt 0
    return [PSCustomObject]@{
        Provider = 'EventLog'
        Success  = $anySuccess
        Message  = "EventLog: $(@($results | Where-Object Success).Count)/$($results.Count) events written"
        Error    = if (-not $anySuccess) { ($results | Where-Object { -not $_.Success } | Select-Object -First 1).Error } else { $null }
        Details  = @($results)
    }
}