EventMonitor/LogoffIndicators.psm1

<#PSScriptInfo
.AUTHOR Microsoft
.COMPANYNAME Microsoft Corporation
.COPYRIGHT (c) Microsoft Corporation
#>


$EventLogType = @{
    SecurityLogs = "Security";
    SystemLogs = "System";
    ApplicationLogs = "Application";
    SetupLogs = "Setup";
    OpenSSHOperationalLogs = "OpenSSH/Operational";
};

<#
.SYNOPSIS
    ##############################################################################################################################
    Get the user Logoff related events and return the latest logoff event with its details.
    ##############################################################################################################################
.DESCRIPTION
    Gets the details of the latest windows logoff related events and returns the latest event among those events. The list of events
    considered to be logoff related are
    1) 4647: This event is generated when a logoff is initiated. No further user-initiated activity can occur for related logon ref.
    2) 4779: This event is generated when a user disconnects from an existing Terminal Services session, or when a user switches away from an existing desktop using Fast User Switching.
    3) OpenSSHApplication: OpenSSH/Operational This OpenSSH application event is generated when SSH disconnect is requested
    4) 4689: This event is generated when process is terminated. (Disabled for calculations as netstat provides if any ssh connection is active.
#>

function Get-UserIdleEvents {
    param(
        [Parameter(Mandatory = $true)]
        [string] $logAnalyticsConString,
        [Parameter(Mandatory = $true)]
        [string] $sessionId,
        [Parameter(Mandatory = $true)]
        [DateTime] $TimeBefore,
        [Parameter(Mandatory = $true)]
        [string] $User
    )
    # 4647
    Get-Event_4647_LogoffInitiated -sessionId $sessionId -User $User -TimeBefore $TimeBefore -logAnalyticsConString $logAnalyticsConString;
    # 4779
    Get-Event_4779_DisconnectsOrSwitch -sessionId $sessionId -User $User -TimeBefore $TimeBefore -logAnalyticsConString $logAnalyticsConString;
    # OpenSSH/Operational log
    Get-OpenSSHApplication_Event_Disconnect -sessionId $sessionId -User $User -TimeBefore $TimeBefore -logAnalyticsConString $logAnalyticsConString;
}


<#
.SYNOPSIS
    ##############################################################################################################################
    # OpenSSH/Operational This OpenSSH application event is generated when SSH disconnect is requested.
    # Less reliable as event does not provide the user info
    ##############################################################################################################################
#>

function Get-OpenSSHApplication_Event_Disconnect {
    param(
        [Parameter(Mandatory = $true)]
        [string] $logAnalyticsConString,
        [Parameter(Mandatory = $true)]
        [string] $sessionId,
        [Parameter(Mandatory = $true)]
        [string] $User,
        [Parameter(Mandatory = $true)]
        [DateTime] $TimeBefore
    )
    Import-Module -Name "$PSScriptRoot\EMCommon.psm1"
    Import-Module -Name "$PSScriptRoot\Telemetry\AITelemetry.psm1"
    $LogFilePath = "$PSScriptRoot\Telemetry\Logs.txt";

    try {
        $events = Get-WinEvent -FilterHashtable @{logname=$EventLogType.OpenSSHOperationalLogs;} -ErrorAction Stop `
            | Where-Object { (($_.properties[1].Value -like 'Disconnected*') -and ($_.TimeCreated -ge $TimeBefore ) )}
        $events | ForEach-Object {
            $sendEvent = $_
            $message = $sendEvent.Message
            $UserSID = $($($sendEvent).UserId).Value

            $EvProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $EvProps.Add("SessionId", "$sessionId")
            $EvProps.Add("EventType", "Disconnect")
            # Add metadata info about event to be useful
            $EvProps.Add("UserName", "$User")
            $EvProps.Add("Process", "$($message.split(" ")[0])")
            $EvProps.Add("IP", "$($message.split(" ")[3])")
            $EvProps.Add("Port", "$($message.split(" ")[5])")
            $EvProps.Add("UserSID","$UserSID")
            
            Send-LogAnalyticsConnectEvents `
                -eventName "OpenSSHApplication Disconnect Event" `
                -Properties $EvProps `
                -sendEvent $sendEvent `
                -logAnalyticsConString $logAnalyticsConString;
        }
    }
    catch {
        if ($_ -notlike "*No events were found*") { 
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: Exception Message: `n$_.Exception.Message"
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: ScriptStackTrace: `n$_.ScriptStackTrace"
            $LASTEXITCODE = 1
            $ErrorProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $ErrorProps.Add("SessionId", "$sessionId")
            $ErrorProps.Add("Function", "Get-OpenSSHApplication_Event_Disconnect")
            $ErrorProps.Add("User", "$User")
            $ErrorProps.Add("Query events since", "$TimeBefore")
            TrackException -ErrorRecord $_ -Properties $ErrorProps -logAnalyticsConString $logAnalyticsConString;
        }
    }
    finally { }
}


<#
.SYNOPSIS
    ##############################################################################################################################
    # 4647 This event is generated when a logoff is initiated. No further user-initiated activity can occur for related logon ref.
    ##############################################################################################################################
#>

function Get-Event_4647_LogoffInitiated {
    param(
        [Parameter(Mandatory = $true)]
        [string] $logAnalyticsConString,
        [Parameter(Mandatory = $true)]
        [string] $sessionId,
        [Parameter(Mandatory = $true)]
        [string] $User,
        [Parameter(Mandatory = $true)]
        [DateTime] $TimeBefore
    )
    Import-Module -Name "$PSScriptRoot\EMCommon.psm1"
    Import-Module -Name "$PSScriptRoot\Telemetry\AITelemetry.psm1"
    $LogFilePath = "$PSScriptRoot\Telemetry\Logs.txt";

    try {
        $events = Get-WinEvent -FilterHashtable @{logname=$EventLogType.SecurityLogs; id=4647} -ErrorAction Stop `
            | Where-Object { ( ($_.properties[1].Value -eq $User -or $_.properties[1].Value -like 'ssh_*') -and ($_.TimeCreated -ge $TimeBefore ) )}
        $events | ForEach-Object {
            $sendEvent = $_
            $EvProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $EvProps.Add("SessionId", "$sessionId")
            $EvProps.Add("EventType", "Disconnect")
            # Add metadata info about event to be useful
            $EvProps.Add("UserName", "$User")
            $EvProps.Add("LogOffSecurityID", "$($sendEvent.properties[0].Value)")
            $EvProps.Add("AccountDomain","$($sendEvent.properties[2].Value)")
            $EvProps.Add("LogonID", "$($sendEvent.properties[3].Value)")

            Send-LogAnalyticsConnectEvents `
                -eventName "$($sendEvent.Id) Disconnect Event" `
                -Properties $EvProps `
                -sendEvent $sendEvent `
                -logAnalyticsConString $logAnalyticsConString;
        }
    }
    catch {
        if ($_ -notlike "*No events were found*") { 
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: Exception Message: `n$_.Exception.Message"
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: ScriptStackTrace: `n$_.ScriptStackTrace"
            $LASTEXITCODE = 1
            $ErrorProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $ErrorProps.Add("SessionId", "$sessionId")
            $ErrorProps.Add("User", "$User")
            $ErrorProps.Add("Query events since", "$TimeBefore")
            $ErrorProps.Add("Function", "Get-Event_4647_LogoffInitiated")
            TrackException -ErrorRecord $_ -Properties $ErrorProps -logAnalyticsConString $logAnalyticsConString;
        }
    }
    finally { }
}

<#
.SYNOPSIS
    ##############################################################################################################################
    # This function logs the given event details locally and send its details to azure app-insights.
    # 4779 This event is generated when a user disconnects from an existing Terminal Services session,
    # or when a user switches away from an existing desktop using Fast User Switching.
    ##############################################################################################################################
#>

function Get-Event_4779_DisconnectsOrSwitch {
    param(
        [Parameter(Mandatory = $true)]
        [string] $logAnalyticsConString,
        [Parameter(Mandatory = $true)]
        [string] $sessionId,
        [Parameter(Mandatory = $true)]
        [string] $User,
        [Parameter(Mandatory = $true)]
        [DateTime] $TimeBefore
    )
    Import-Module -Name "$PSScriptRoot\EMCommon.psm1"
    Import-Module -Name "$PSScriptRoot\Telemetry\AITelemetry.psm1"
    $LogFilePath = "$PSScriptRoot\Telemetry\Logs.txt";

    try {
        $events = Get-WinEvent -FilterHashtable @{logname=$EventLogType.SecurityLogs; id=4779} -ErrorAction Stop `
            | Where-Object { ( ($_.properties[1].Value -eq $User -or $_.properties[1].Value -like 'ssh_*') -and ($_.TimeCreated -ge $TimeBefore ) )}
        $events | ForEach-Object {
            $sendEvent = $_
            $EvProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $EvProps.Add("SessionId", "$sessionId")
            $EvProps.Add("EventType", "Disconnect")
            # Add metadata info about event to be useful
            $EvProps.Add("UserName", "$User")
            $EvProps.Add("AccountDomain", "$($sendEvent.properties[1].Value)")
            $EvProps.Add("SessionName","$($sendEvent.properties[3].Value)")
            $EvProps.Add("ClientName", "$($sendEvent.properties[4].Value)")
            $EvProps.Add("ClientAddress", "$($sendEvent.properties[5].Value)")
            
            Send-LogAnalyticsConnectEvents `
                -eventName "$($sendEvent.Id) Disconnect Event" `
                -Properties $EvProps `
                -sendEvent $sendEvent `
                -logAnalyticsConString $logAnalyticsConString;
        }
    }
    catch {
        if ($_ -notlike "*No events were found*") { 
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: Exception Message: `n$_.Exception.Message"
            Add-Content -Path $LogFilePath -Value "$(get-date -UFormat %c) :: ScriptStackTrace: `n$_.ScriptStackTrace"
            $LASTEXITCODE = 1
            $ErrorProps = New-Object 'system.collections.generic.dictionary[string, string]'
            $ErrorProps.Add("SessionId", "$sessionId")
            $ErrorProps.Add("User", "$User")
            $ErrorProps.Add("Query events since", "$TimeBefore")
            $ErrorProps.Add("Function", "Get-Event_4779_DisconnectsOrSwitch")
            TrackException -ErrorRecord $_ -Properties $ErrorProps -logAnalyticsConString $logAnalyticsConString;
        }
    }
    finally { }
}