I had this issue with locked Remote Desktop Application users. I wrote this Powershell script to run on a scheduled task to log off users that showed as disconnected for more than 2 minutes. The only edit that is required is the SERVERNAME which I set to exclude the Remote Desktop Broker Server, however you can exclude any server you like, or none at all.
My script was written for Windows Server 2012 R2, by the way...
The script does this :
- Gets a list of all Remote Desktop User Sessions.
- Ignores any sessions that do not say "STATE_DISCONNECTED".
- Ignores the Broker Server (or any other server)
- Ignores any sessions with no Unified Session Id
- Ignores any sessions that do not have a disconnect time
- For those sessions that have a disconnect time, it checks the current
time and if the time difference between now and the disconnect time is more than X minutes (in this case
2), kills the winlogon process.
- It also attempts to issue a log off command (This will most likely
fail after the winlogon process is killed).
It works for me! I hope it helps someone else! :)
CLS
$RD = Get-RDUserSession | select ServerName, UserName, SessionState, DisconnectTime, UnifiedSessionId, SessionId #Get details about the sessions
foreach ($item in $RD) {
$UsessionID = $item.UnifiedSessionId -as [int]
$sessionID = $item.SessionId -as [int]
if ($item.SessionState -eq "STATE_DISCONNECTED" -and $item.ServerName -ne "SERVERNAME" -and $item.DisconnectTime -ne $null -and $item.UnifiedSessionId -ne $null){
$TimeDiff = New-TimeSpan -start $item.DisconnectTime -end (Get-Date) #check time difference between disconnect time and now. If time is greater than 2 minutes....
if ($TimeDiff.Minutes -gt 2) {
#Kill winlogon session for the user
Get-WmiObject -ComputerName $item.Servername -query "select * from win32_process where name='winlogon.exe'" | Where-Object {$_.SessionId -eq $SessionId} | %{$_.terminate()}
#Log off user if session still exists (will fail if user kicked)
Invoke-RDUserLogoff -HostServer $item.ServerName -UnifiedSessionID $UsessionID -Force -erroraction 'silentlycontinue'
}
}
}
Or if you prefer a version that you can see what is happening on the screen :
CLS
$RD = Get-RDUserSession | select ServerName, UserName, SessionState, DisconnectTime, UnifiedSessionId, SessionId
foreach ($item in $RD) {
$UsessionID = $item.UnifiedSessionId -as [int]
$sessionID = $item.SessionId -as [int]
if ($item.SessionState -eq "STATE_DISCONNECTED" -and $item.ServerName -ne "SERVERNAME" -and $item.DisconnectTime -ne $null -and $item.UnifiedSessionId -ne $null){
#On Screen Output
write-host " Name : " $Item.UserName -ForegroundColor "yellow" -NoNewline
write-host " Unified Session Id : " $UsessionID -ForegroundColor "darkcyan" -NoNewline
write-host " User Session Id : " $sessionID -ForegroundColor "darkyellow" -NoNewline
write-host " Session State : " $item.SessionState -ForegroundColor "magenta" -NoNewline
write-host " Server : " $item.ServerName -ForegroundColor "cyan" -NoNewline
write-host " Disconnect Time : " $item.DisconnectTime -ForegroundColor "gray"
#End On Screen Output
$TimeDiff = New-TimeSpan -start $item.DisconnectTime -end (Get-Date)
if ($TimeDiff.Minutes -lt 2) {
write-host " Disconnected for less than 2 minutes" -ForegroundColor "Green"}
else {
write-host " Disconnected for more than 2 minutes" -ForegroundColor "Red" -BackgroundColor "darkyellow"
write-host " Killing session : " $item.ServerName " ID : " $UsessionID $item.UserName -ForegroundColor "Red"
#Kill Process "Winlogon.exe" for the user (this should kill the session)
Get-WmiObject -ComputerName $item.Servername -query "select * from win32_process where name='winlogon.exe'" | Where-Object {$_.SessionId -eq $SessionId} | %{$_.terminate()}
#Logout User (if session still exists)
Invoke-RDUserLogoff -HostServer $item.ServerName -UnifiedSessionID $UsessionID -Force -erroraction 'silentlycontinue'
Write-host " Done! " -ForegroundColor "Green" -BackgroundColor "blue"
}
}
}