Tuesday, March 9, 2021

Automatically Logoff Idle Users

Google around and you'll find various ways of automatically logging off idle users.  None of them suited my requirements so I created a task scheduler coupled to a powershell script to manage it.  The advantages of this approach are

  • Can't be intercepted by users
  • Only uses existing OS tools and scripting
  • Manages user logons individually
  • Works if Switch User is used to logon other accounts
  • Simple, quick, effective with no significant processing requirement
  • Any background management tasks such as OS updates are not affected.
The exact requirements were to logoff "idle" user logon sessions from Windows 10 computers.  The computers were open access type computers.  None of the work in the sessions would have any state to save and no background processing tasks in the session would exist.  Forcing a logoff for unused logons would not result in any lost work.  In reality this does not check for "Idle" sessions or an idle computer state.  It assumes sessions are unused and not needed and forces them to logoff.

This method also requires security event auditing to be turned on so that the events are stored in the security log.

The scheduled task is set to run as SYSTEM.  Check the tick box Run with highest privileges

The scheduled task is triggered by a custom event filter. The XML of which is thus:

<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*[System[(EventID=4800 or EventID=4802)]]</Select>
  </Query>
</QueryList>

The program called is the powershell.exe with the argument -file <PathToScript>

There are no conditions.

Settings - Select "Run a new instance in parallel"  This setting enables multiple user sessions to be tracked if neccessary.

The PowerShell script is as follows:

#This script has just been triggered by a user 4800(lock) or 4802(screensaver) security log event
#get user id and event time
$trigger = get-eventlog -logname security | where {$_.InstanceId -like "4800" -or $_.InstanceId -like "4802"} | select -first 1

#Get event log time
#We'll look forward from this point later.
$trigtime = get-date -date $trigger.TimeGenerated

#Get trigger SessionId
$trigMsg = $trigger.Message
$trigIDtemp = $trigMsg.split("Session ID: ") | select -last 1
$trigID = $trigIDtemp.trim()

# Every 15 minutes check to see if the user has returned
# Do this in total 11 times (during 3 hours) if the user is still not back
# log them out.

#first wait 15 mins  wait period 1
start-sleep -s 900
#wait test
#start-sleep -s 120

#count from 0 while less that 10 causes 10 checks
$count = 0
While ($count -le 10)
    {
    #Current Time
    if ($count -eq 0)
        {$TimeSpec = $trigtime}
    else
        {$TimeSpec = $CheckTime}
           
    #Has user returned in the last 15 mins?
    $CheckEvLog = $null
    $CheckEvLog = get-eventlog -logname security -After $TimeSpec | where {$_.InstanceID -eq 4801 -or $_.InstanceId -eq 4803}
    
    #Check any return events 4801(unlock) or 4803(screensaver dismissed) for user id
    if ($CheckEvLog -notlike $null) 
        {
        #were any of the events related to the trigger sessionid ?
        foreach ($logitem in $CheckEvLog)
            {
            $logitemMsg = $logitem.Message
            $entryIDtemp = $logitemMsg.split("Session ID: ") | select -last 1
            $entryID = $entryIDtemp.trim()
        
            #if sessionIDs match then user has returned so exit
            if ($entryID -eq $trigID){exit}
            }
        }
    
    #there are no return events or they are not for current session id.
    $CheckTime = Get-Date
    
    #wait 15 mins
    start-sleep -s 900
    #wait test
    #start-sleep -s 15
       
    $Count; $Count +=1
    }

#if you're here, that user/sessionID ain't back. Time periods configured above have expired.
#Time to log them off
& logoff $trigID