EventMonitor/LogoffIndicators.ps1
|
# ── Logoff Indicators ───────────────────────────────────────────────────────── # Functions that read Windows events indicating a user disconnection or idle state. # Each function queries a specific Event ID, extracts relevant properties, # and forwards the enriched data to Application Insights. <# .SYNOPSIS Orchestrates all logoff-indicator event readers for a given user and time window. .DESCRIPTION Calls each logoff event reader in sequence: 4647, 4779, OpenSSH disconnect. #> function Get-UserIdleEvents { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$sessionId, [Parameter(Mandatory)] [DateTime]$TimeBefore, [Parameter(Mandatory)] [string]$User ) $commonParams = @{ sessionId = $sessionId User = $User TimeBefore = $TimeBefore } Get-Event_4647_LogoffInitiated @commonParams Get-Event_4779_DisconnectsOrSwitch @commonParams Get-OpenSSHApplication_Event_Disconnect @commonParams } # ── Event 4647: Logoff Initiated ────────────────────────────────────────────── <# .SYNOPSIS Reads Security event 4647 — user-initiated logoff. .DESCRIPTION No further user-initiated activity can occur for the related logon session after this event is generated. #> function Get-Event_4647_LogoffInitiated { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$sessionId, [Parameter(Mandatory)] [string]$User, [Parameter(Mandatory)] [DateTime]$TimeBefore ) try { $events = Get-WinEvent -FilterHashtable @{ logname = $script:EventLogType.Security; id = 4647 } -ErrorAction Stop | Where-Object { ($_.properties[1].Value -eq $User -or $_.properties[1].Value -like 'ssh_*') -and $_.TimeCreated -ge $TimeBefore } foreach ($evt in $events) { $evProps = [System.Collections.Generic.Dictionary[string, string]]::new() $evProps['SessionId'] = $sessionId $evProps['EventType'] = 'Disconnect' $evProps['UserName'] = $User $evProps['LogOffSecurityID'] = "$($evt.properties[0].Value)" $evProps['AccountDomain'] = "$($evt.properties[2].Value)" $evProps['LogonID'] = "$($evt.properties[3].Value)" Send-LogAnalyticsConnectEvents -eventName "$($evt.Id) Disconnect Event" ` -Properties $evProps -sendEvent $evt } } catch { if ($_.Exception.Message -notlike '*No events were found*') { Write-EMLog -Message "Get-Event_4647 failed: $($_.Exception.Message)" -Level Error $errorProps = [System.Collections.Generic.Dictionary[string, string]]::new() $errorProps['SessionId'] = $sessionId $errorProps['User'] = $User $errorProps['Function'] = 'Get-Event_4647_LogoffInitiated' TrackException -ErrorRecord $_ -Properties $errorProps } } } # ── Event 4779: Terminal Services Disconnect / Fast User Switch ─────────────── <# .SYNOPSIS Reads Security event 4779 — user disconnects from Terminal Services session or switches away via Fast User Switching. #> function Get-Event_4779_DisconnectsOrSwitch { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$sessionId, [Parameter(Mandatory)] [string]$User, [Parameter(Mandatory)] [DateTime]$TimeBefore ) try { $events = Get-WinEvent -FilterHashtable @{ logname = $script:EventLogType.Security; id = 4779 } -ErrorAction Stop | Where-Object { ($_.properties[1].Value -eq $User -or $_.properties[1].Value -like 'ssh_*') -and $_.TimeCreated -ge $TimeBefore } foreach ($evt in $events) { $evProps = [System.Collections.Generic.Dictionary[string, string]]::new() $evProps['SessionId'] = $sessionId $evProps['EventType'] = 'Disconnect' $evProps['UserName'] = $User $evProps['AccountDomain'] = "$($evt.properties[1].Value)" $evProps['SessionName'] = "$($evt.properties[3].Value)" $evProps['ClientName'] = "$($evt.properties[4].Value)" $evProps['ClientAddress'] = "$($evt.properties[5].Value)" Send-LogAnalyticsConnectEvents -eventName "$($evt.Id) Disconnect Event" ` -Properties $evProps -sendEvent $evt } } catch { if ($_.Exception.Message -notlike '*No events were found*') { Write-EMLog -Message "Get-Event_4779 failed: $($_.Exception.Message)" -Level Error $errorProps = [System.Collections.Generic.Dictionary[string, string]]::new() $errorProps['SessionId'] = $sessionId $errorProps['User'] = $User $errorProps['Function'] = 'Get-Event_4779_DisconnectsOrSwitch' TrackException -ErrorRecord $_ -Properties $errorProps } } } # ── OpenSSH Disconnect ──────────────────────────────────────────────────────── <# .SYNOPSIS Reads OpenSSH/Operational events for SSH disconnect requests. .NOTES Less reliable than Security events because the OpenSSH disconnect event does not always include the specific user who disconnected. #> function Get-OpenSSHApplication_Event_Disconnect { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$sessionId, [Parameter(Mandatory)] [string]$User, [Parameter(Mandatory)] [DateTime]$TimeBefore ) try { $events = Get-WinEvent -FilterHashtable @{ logname = $script:EventLogType.OpenSSHOperational } -ErrorAction Stop | Where-Object { $_.properties[1].Value -like 'Disconnected*' -and $_.TimeCreated -ge $TimeBefore } foreach ($evt in $events) { $message = $evt.Message $parts = $message -split '\s+' $evProps = [System.Collections.Generic.Dictionary[string, string]]::new() $evProps['SessionId'] = $sessionId $evProps['EventType'] = 'Disconnect' $evProps['UserName'] = $User $evProps['Process'] = if ($parts.Count -ge 1) { $parts[0] } else { '' } $evProps['IP'] = if ($parts.Count -ge 4) { $parts[3] } else { '' } $evProps['Port'] = if ($parts.Count -ge 6) { $parts[5] } else { '' } $evProps['UserSID'] = "$($evt.UserId)" Send-LogAnalyticsConnectEvents -eventName 'OpenSSHApplication Disconnect Event' ` -Properties $evProps -sendEvent $evt } } catch { if ($_.Exception.Message -notlike '*No events were found*') { Write-EMLog -Message "Get-OpenSSH_Disconnect failed: $($_.Exception.Message)" -Level Error $errorProps = [System.Collections.Generic.Dictionary[string, string]]::new() $errorProps['SessionId'] = $sessionId $errorProps['User'] = $User $errorProps['Function'] = 'Get-OpenSSHApplication_Event_Disconnect' TrackException -ErrorRecord $_ -Properties $errorProps } } } |