TopNet247Independent notes for Windows admins

How-to · RDS & Session Management

See who is logged on to your RDS hosts and reset stuck sessions

List sessions across RD Session Hosts with quser and PowerShell, warn users, then safely log off or reset stuck sessions, with the rights each step needs.

It’s 8:40 on a Monday. The help desk has three tickets saying “can’t log in, it just spins”. Nine times out of ten the cause is the same: a profile or app still holds on in a disconnected session from last Friday, and the new logon hangs until that session goes away. Before you can fix it, you need to know which host the session is on, what state it’s in, and whose it is. This guide covers the built-in commands, a small PowerShell loop for a farm of several hosts, and the point where a GUI console starts to pay for itself.

Use only on systems you administer and with your organisation’s authorization. Logging off or resetting a session ends that user’s work, so warn them first.

What you need before you start

Step 1 — List sessions on one host

Start small. From an admin workstation or a management server:

quser /server:RDSH01
# same output, older name:
query session /server:RDSH01

quser shows the user name, session name (for example rdp-tcp#7), session ID, state (Active or Disc), idle time and logon time. Write down the ID, because every later command uses it. Session IDs only mean something on the host that issued them. Session 4 on RDSH01 has nothing to do with session 4 on RDSH02.

Step 2 — Sweep the whole farm with PowerShell

quser prints text for people to read, not objects, and disconnected sessions leave the session-name column empty. The regex below allows for that gap:

$hosts = Get-Content C:\Admin\rdsh-hosts.txt    # one host name per line
$pattern = '^\s*>?(?<User>\S+)\s+(?:(?<Session>\S+)\s+)?(?<Id>\d+)\s+(?<State>\S+)\s+(?<Idle>\S+)\s+(?<Logon>.+?)\s*$'

$sessions = foreach ($h in $hosts) {
    $lines = quser /server:$h 2>$null
    if (-not $lines) { Write-Warning "$h returned nothing (no sessions or unreachable)"; continue }
    foreach ($line in ($lines | Select-Object -Skip 1)) {
        if ($line -match $pattern) {
            [pscustomobject]@{
                Host    = $h
                User    = $Matches.User
                Id      = [int]$Matches.Id
                State   = $Matches.State
                Idle    = $Matches.Idle
                LogonAt = $Matches.Logon
            }
        }
    }
}

$sessions | Where-Object User -eq 'jdoe' | Format-Table -AutoSize
$sessions | Where-Object State -ne 'Active' | Sort-Object Host | Format-Table -AutoSize

Two caveats. First, the column text is localized: on a German or French server, “Disc” and “Active” appear in that language, so filter on what your hosts actually print. Second, the logon time is left as a string on purpose, because the date format follows each host’s regional settings.

If you run a full RDS deployment with a Connection Broker, the RemoteDesktop module gives you real objects and skips the parsing:

Import-Module RemoteDesktop
Get-RDUserSession -ConnectionBroker 'rdcb01.corp.example.com' |
    Select-Object HostServer, UserName, UnifiedSessionId, SessionState, CreateTime |
    Sort-Object HostServer

Step 3 — Warn the user

Microsoft’s own guidance for logoff is to message the user before you end the session. It takes five seconds and saves you an argument about lost spreadsheets:

msg 3 /server:RDSH01 /time:120 "IT: your session will be signed out in 2 minutes. Please save your work."

For a disconnected session, nobody will read the message. Check with the user or their manager instead, especially if they may have unsaved documents open in that session.

Step 4 — Log off or reset

Pick the gentlest option that works:

  1. Log off closes the user’s processes in order and deletes the session. Try this first.
    logoff 3 /server:RDSH01 /v
  2. Reset tears the session down without an orderly logoff. The docs reserve it for sessions that “malfunction or appear to have stopped responding”. Expect unsaved data to be lost.
    reset session 3 /server:RDSH01 /v
  3. In a broker deployment, use the cmdlet so the broker’s view stays consistent:
    Invoke-RDUserLogoff -HostServer 'rdsh01.corp.example.com' -UnifiedSessionID 3 -Force

You can’t log off the console session with logoff. If the stuck session is the console, you’ll need a different approach, often a restart during a maintenance window.

Step 5 — Stop it from coming back

Stale disconnected sessions are a policy problem. In Group Policy, go to Computer Configuration → Administrative Templates → Windows Components → Remote Desktop Services → Remote Desktop Session Host → Session Time Limits. Set a limit for disconnected sessions (a few hours is a common choice), and an idle limit that fits how your users work. Run gpupdate on the hosts, then check the result with the sweep from step 2 a day later.

Common mistakes

When a console beats the commands

The loop above is enough for two or three hosts. Once you’re triaging sessions across a larger farm every day, a dedicated console saves real time. LizardSystems Terminal Services Manager shows sessions and processes across several hosts in one window. The vendor also lists messaging, disconnect, log off, reset and shadowing, and connects over SMB (TCP 445) and RPC, plus TCP 3389 for shadowing. Admin rights on each host are still required. If you’d rather stay with Microsoft’s free tooling, Windows Admin Center covers per-server tasks but has no farm-wide session list. We weigh the two in Terminal Services Manager vs Windows Admin Center.

For the history of who connected and when, as opposed to who is on right now, see pulling RDP logon history from event logs. More session tools are listed under RDS & Session Management, and where to get explains how to obtain any of them from the vendor and check the file before you run it.

Tool used in this how-to