EventMonitor/EventProcessors/LogonEvents.ps1

# ── Logon Events Processor ────────────────────────────────────────────────────
# Monitors user authentication and session creation events.
# Event IDs: 4624 (success), 4625 (failure), 4648 (explicit creds), 4800 (lock), 4801 (unlock)
#
# Reference: https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/

<#
.SYNOPSIS
    Collects all logon-related security events for a user within the time window.
.DESCRIPTION
    Orchestrates logon event collection: successful logons (4624), failed logons (4625),
    explicit credential use (4648), workstation lock (4800), and unlock (4801).
.PARAMETER sessionId
    Correlation identifier for this monitoring session.
.PARAMETER StartTime
    Only process events created at or after this timestamp.
.PARAMETER User
    The Windows username to filter events for.
#>

function Get-LogonEvents {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    $commonParams = @{
        sessionId             = $sessionId
        StartTime             = $StartTime
        User                  = $User
    }

    Get-Event4624_LogonSuccess         @commonParams
    Get-Event4625_LogonFailed          @commonParams
    Get-Event4648_ExplicitCredential   @commonParams
    Get-Event4800_WorkstationLocked    @commonParams
    Get-Event4801_WorkstationUnlocked  @commonParams
}

# ── Event 4624: Successful Logon ──────────────────────────────────────────────
# Properties: [0]SubjectSID [1]SubjectUserName [2]SubjectDomain [3]SubjectLogonId
# [4]TargetSID [5]TargetUserName [6]TargetDomain [7]TargetLogonId
# [8]LogonType [9]LogonProcessName [10]AuthenticationPackage
# [11]WorkstationName [12]LogonGuid [13]TransmittedServices
# [14]LmPackageName [15]KeyLength [16]ProcessId [17]ProcessName
# [18]IpAddress [19]IpPort [20]ImpersonationLevel [21]RestrictedAdminMode
# [22]TargetOutboundUserName [23]TargetOutboundDomainName
# [24]VirtualAccount [25]TargetLinkedLogonId [26]ElevatedToken

function Get-Event4624_LogonSuccess {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEvents -EventId 4624 -LogName 'Security' -StartTime $StartTime

        # LogonTypes that represent real user activity:
        # 2=Interactive(console), 7=Unlock, 9=NewCredentials(runas), 10=RDP, 11=CachedInteractive
        # Skip: 0=System, 3=Network(too noisy - every SMB/share), 4=Batch, 5=Service, 8=NetworkCleartext
        $interactiveLogonTypes = @('2', '7', '9', '10', '11')

        foreach ($evt in $events) {
            $targetUser = "$($evt.Properties[5].Value)"
            if ($targetUser -ne $User -and $targetUser -notlike 'ssh_*') { continue }

            # Skip system/service account logons — they are noise
            $logonType = "$($evt.Properties[8].Value)"
            if ($logonType -notin $interactiveLogonTypes) { continue }

            $process = "$($evt.Properties[17].Value)"

            $props = New-EventProperties -SessionId $sessionId -EventType 'Connect' -Severity 'Info'
            $props['UserName']      = $targetUser
            $props['AccountDomain'] = "$($evt.Properties[6].Value)"
            $props['LogonSID']      = "$($evt.Properties[4].Value)"
            $props['LogonType']     = "$($evt.Properties[8].Value)"
            $props['LogonProcess']  = "$($evt.Properties[9].Value)"
            $props['Process']       = $process
            $props['SourceIP']      = "$($evt.Properties[18].Value)"
            $props['SourcePort']    = "$($evt.Properties[19].Value)"

            Send-LogAnalyticsConnectEvents `
                -eventName '4624 Logon Success' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-Event4624_LogonSuccess: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-Event4624_LogonSuccess' -User $User)
    }
}

# ── Event 4625: Failed Logon Attempt ──────────────────────────────────────────
# Critical for brute-force detection, unauthorized access attempts.
# Properties: [0]SubjectSID [1]SubjectUserName [2]SubjectDomain [3]SubjectLogonId
# [4]TargetSID [5]TargetUserName [6]TargetDomain [7]Status
# [8]FailureReason [9]SubStatus [10]LogonType [11]LogonProcessName
# [12]AuthenticationPackage [13]WorkstationName [14]TransmittedServices
# [15]LmPackageName [16]KeyLength [17]ProcessId [18]ProcessName
# [19]IpAddress [20]IpPort

function Get-Event4625_LogonFailed {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEvents -EventId 4625 -LogName 'Security' -StartTime $StartTime

        # System accounts to ignore for failed logons
        $systemAccounts = @('SYSTEM', 'LOCAL SERVICE', 'NETWORK SERVICE', 'DWM-1', 'DWM-2', 'DWM-3', 'UMFD-0', 'UMFD-1', '')

        foreach ($evt in $events) {
            $targetUser = "$($evt.Properties[5].Value)"

            # Skip failures for system/service accounts
            if ($targetUser -in $systemAccounts -or $targetUser -like 'DWM-*' -or $targetUser -like 'UMFD-*') { continue }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Alert' -Severity 'High'
            $props['TargetUserName']  = $targetUser
            $props['TargetDomain']    = "$($evt.Properties[6].Value)"
            $props['Status']          = "$($evt.Properties[7].Value)"
            $props['FailureReason']   = "$($evt.Properties[8].Value)"
            $props['SubStatus']       = "$($evt.Properties[9].Value)"
            $props['LogonType']       = "$($evt.Properties[10].Value)"
            $props['LogonProcess']    = "$($evt.Properties[11].Value)"
            $props['AuthPackage']     = "$($evt.Properties[12].Value)"
            $props['WorkstationName'] = "$($evt.Properties[13].Value)"
            $props['ProcessName']     = "$($evt.Properties[18].Value)"
            $props['SourceIP']        = "$($evt.Properties[19].Value)"
            $props['SourcePort']      = "$($evt.Properties[20].Value)"

            Send-LogAnalyticsConnectEvents `
                -eventName '4625 Logon Failed' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-Event4625_LogonFailed: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-Event4625_LogonFailed' -User $User)
    }
}

# ── Event 4648: Explicit Credential Logon ─────────────────────────────────────
# Properties: [0]SubjectSID [1]SubjectUserName [2]SubjectDomain [3]SubjectLogonId
# [4]LogonGuid [5]TargetUserName [6]TargetDomain [7]TargetLogonGuid
# [8]TargetServerName [9]TargetServerInfo [10]ProcessId [11]ProcessName
# [12]NetworkAddress [13]NetworkPort

function Get-Event4648_ExplicitCredential {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEvents -EventId 4648 -LogName 'Security' -StartTime $StartTime

        foreach ($evt in $events) {
            $targetUser = "$($evt.Properties[5].Value)"
            if ($targetUser -ne $User -and $targetUser -notlike 'ssh_*') { continue }

            $process = "$($evt.Properties[11].Value)"

            # Skip sshd.exe — SSH connections are tracked by the dedicated SSH processor
            if ($process -like '*sshd.exe') { continue }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Connect' -Severity 'Medium'
            $props['UserName']      = $targetUser
            $props['AccountDomain'] = "$($evt.Properties[2].Value)"
            $props['TargetServer']  = "$($evt.Properties[8].Value)"
            $props['Process']       = $process
            $props['SourceIP']      = "$($evt.Properties[12].Value)"
            $props['SourcePort']    = "$($evt.Properties[13].Value)"

            Send-LogAnalyticsConnectEvents `
                -eventName '4648 Explicit Credential Logon' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-Event4648_ExplicitCredential: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-Event4648_ExplicitCredential' -User $User)
    }
}

# ── Event 4800: Workstation Locked ────────────────────────────────────────────
# Properties: [0]TargetSID [1]TargetUserName [2]TargetDomainName [3]TargetLogonId [4]SessionId

function Get-Event4800_WorkstationLocked {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEvents -EventId 4800 -LogName 'Security' -StartTime $StartTime

        foreach ($evt in $events) {
            if ("$($evt.Properties[1].Value)" -ne $User) { continue }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Info' -Severity 'Info'
            $props['UserName']      = "$($evt.Properties[1].Value)"
            $props['LogonSID']      = "$($evt.Properties[0].Value)"
            $props['AccountDomain'] = "$($evt.Properties[2].Value)"

            Send-LogAnalyticsConnectEvents `
                -eventName '4800 Workstation Locked' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-Event4800_WorkstationLocked: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-Event4800_WorkstationLocked' -User $User)
    }
}

# ── Event 4801: Workstation Unlocked ──────────────────────────────────────────
# Properties: [0]TargetSID [1]TargetUserName [2]TargetDomainName [3]TargetLogonId [4]SessionId

function Get-Event4801_WorkstationUnlocked {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string]$sessionId,
        [Parameter(Mandatory)] [DateTime]$StartTime,
        [Parameter(Mandatory)] [string]$User
    )

    try {
        $events = Read-WindowsEvents -EventId 4801 -LogName 'Security' -StartTime $StartTime

        foreach ($evt in $events) {
            if ("$($evt.Properties[1].Value)" -ne $User) { continue }

            $props = New-EventProperties -SessionId $sessionId -EventType 'Connect' -Severity 'Info'
            $props['UserName']      = "$($evt.Properties[1].Value)"
            $props['LogonSID']      = "$($evt.Properties[0].Value)"
            $props['AccountDomain'] = "$($evt.Properties[2].Value)"

            Send-LogAnalyticsConnectEvents `
                -eventName '4801 Workstation Unlocked' -Properties $props -sendEvent $evt
        }
    }
    catch {
        Write-EMLog -Message "Get-Event4801_WorkstationUnlocked: $($_.Exception.Message)" -Level Error
        TrackException -ErrorRecord $_ `
            -Properties (New-ErrorProperties -SessionId $sessionId -FunctionName 'Get-Event4801_WorkstationUnlocked' -User $User)
    }
}