quser, qwinsta and rwinsta answer the question every server admin asks under pressure: who is on this machine, and how do I get them off it. They are the short names for query user, query session and reset session, and they ship with every Pro and Server edition of Windows. No RSAT, no modules, no GUI.
This matters more than it looks. A Windows Server allows two administrative RDP sessions; a Remote Desktop Services host has a licence-bound limit. Sessions that users close with the X button do not end, they go to Disc – and a disconnected session keeps its RAM, its file handles and its loaded profile. The third person trying to connect gets a refusal that has nothing to do with the network.
Microsoft documents each of these commands on its own page. What is missing there is the workflow that joins them, the difference in what quser and qwinsta actually show you, and the one output quirk that silently breaks almost every script written around quser.
Applies to: Windows 10 / 11 Pro and Enterprise, Windows Server 2016 / 2019 / 2022
Quick answer
List the sessions, warn the user, then release the session. Three commands, run from an elevated prompt on your own workstation – you do not need to be logged on to the target server.
rem 1. who is on the box, and what is each session ID
quser /server:SRV-PROD-01
rem 2. warn before you disrupt anyone - message stays on screen for 30 seconds
msg 3 /server:SRV-PROD-01 /time:30 "Your disconnected session will be closed in 1 minute."
rem 3. end session ID 3 gracefully - profile is saved, processes are closed properly
logoff 3 /server:SRV-PROD-01
quser immediately before logoff – never reuse an ID you read five minutes ago.
What these commands do
All of them talk to the Remote Desktop Services stack (the TermService service) through RPC. That is worth remembering: the traffic does not use port 3389. A server that accepts RDP connections can still refuse quser /server: if RPC is filtered, and the reverse is also true.
| Command | Long form | What it answers |
|---|---|---|
quser | query user | Which user accounts have a session, and how long they have been idle |
qwinsta | query session | Every session slot on the host, including empty listeners and Services |
rwinsta | reset session | Force-terminates a session that will not close |
logoff | – | Ends a session gracefully, saving the profile |
tscon | – | Attaches an existing session to the console or to your own session |
msg | – | Sends a pop-up to an active session before you disrupt it |
Syntax
quser [username | sessionname | sessionID] [/server:servername]
qwinsta [sessionname | username | sessionID] [/server:servername] [/mode] [/flow] [/connect] [/counter]
rwinsta {sessionname | sessionID} [/server:servername] [/v]
logoff [sessionname | sessionID] [/server:servername] [/v]
| Parameter | Applies to | Meaning |
|---|---|---|
/server: | all | Target host. Omit it and the command runs against the machine you are on. |
/mode | qwinsta | Current line settings for the session. |
/counter | qwinsta | Total sessions created, disconnected and reconnected since boot. |
/v | rwinsta, logoff | Verbose – prints what is being done. Useful inside scripts. |
The STATE column is where the useful information lives. Active means a live connection. Disc means the user’s programs are still running but nobody is attached. Listen rows in qwinsta are the protocol listeners waiting for someone to connect – they are not people.
Practical examples
1. See who is on a server right now
The problem: A change window opens in ten minutes and you need to know whether anyone is still working on SRV-PROD-01.
The solution: quser lists only real user sessions, with idle time and logon time, so you can tell an active worker from a forgotten window at a glance.
rem /server: works from your own workstation - no need to RDP in first
quser /server:SRV-PROD-01
Expected output. The > in the left margin marks your own session, so it only appears when you run the command locally:
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
>administrator console 1 Active none 9/2/2026 8:12 AM
jdoe rdp-tcp#2 2 Active 5 9/2/2026 9:40 AM
msmith 3 Disc 1:22 9/1/2026 4:05 PM
svc_backup rdp-tcp#7 4 Active 1+02:14 9/1/2026 11:03 PM
Read the IDLE TIME column carefully – it uses three different formats. none means active in the last minute, a bare number is minutes, 1:22 is hours and minutes, and 1+02:14 is one day, two hours, fourteen minutes. That last format is your signal that a session has been abandoned rather than paused.
2. Find out why nobody else can connect
The problem: A colleague reports “the server is full” when connecting to SRV-PROD-01, but quser shows only two users and one of them is you.
The solution: Switch to qwinsta, which shows every session slot on the host rather than only the ones with a named user attached.
rem qwinsta shows listeners and the Services session that quser hides
qwinsta /server:SRV-PROD-01
SESSIONNAME USERNAME ID STATE TYPE DEVICE
services 0 Disc
>console administrator 1 Active
rdp-tcp#2 jdoe 2 Active rdpwd
msmith 3 Disc
rdp-tcp 65536 Listen
Session 0 (services) and the rdp-tcp listener at 65536 are infrastructure, not people. The real finding is msmith at ID 3: a disconnected session with no session name, occupying one of the two administrative slots.
qwinsta /server:SRV-RDS-01 /counter shows how many sessions have been created and reconnected since boot, which is a faster health check than counting rows.
3. Warn the user, then release the session
The problem: You have identified msmith at ID 3 as the blocker, but that session may hold unsaved work.
The solution: Send a message first, wait, then use logoff, which closes processes properly and writes the user profile back to disk.
rem /time:30 keeps the pop-up on screen for 30 seconds instead of the 60-second default
msg 3 /server:SRV-PROD-01 /time:30 "Save your work - this session closes in 2 minutes."
rem timeout gives the user a real chance to react; /t is seconds, /nobreak ignores keypresses
timeout /t 120 /nobreak
rem /v prints what logoff is doing, which is what you want in a change log
logoff 3 /server:SRV-PROD-01 /v
msg only reaches sessions in Active state. Sending it to a Disc session succeeds silently and nobody ever sees it. If the session is disconnected, the user is not at the screen – contact them another way, or accept the risk.
4. The session that refuses to log off
The problem: You ran logoff 3, it returned without an error, and quser still shows ID 3 two minutes later. A hung application is refusing to exit and the graceful shutdown is stuck waiting for it.
The solution: rwinsta tears the session down at the Terminal Services layer without asking processes to co-operate.
rem confirm the ID is still what you think it is - IDs get recycled
quser /server:SRV-PROD-01
rem reset by ID; /v confirms the action rather than failing silently
rwinsta 3 /server:SRV-PROD-01 /v
rwinsta is not a stronger logoff – it is the equivalent of pulling the plug on that session. Unsaved data is lost and the roaming profile may not be written back. Use logoff first, every time, and keep rwinsta for sessions that have already ignored it.
5. Sweep a list of servers in one pass
The problem: Before a monthly patch run you need to know who is logged on across a handful of hosts, not just one.
The solution: A short batch file that builds the host list, then loops quser over it. Save the following as C:\bat\session-sweep.bat and run it from an elevated prompt.
@echo off
if not exist C:\bat mkdir C:\bat
rem build the host list once - the parentheses group the echoes into a single redirect,
rem and there is no space before each & so no trailing spaces end up in the file
(echo SRV-PROD-01&echo SRV-PROD-02&echo SRV-RDS-01)>C:\bat\servers.txt
rem %%s is the batch-file form of the loop variable - at an interactive prompt use %s instead
for /f %%s in (C:\bat\servers.txt) do (
echo === %%s
rem quser writes "No User exists for *" to the error stream, so 2>nul keeps output clean
quser /server:%%s 2>nul
)
=== SRV-PROD-01
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
jdoe rdp-tcp#2 2 Active 5 9/2/2026 9:40 AM
=== SRV-PROD-02
=== SRV-RDS-01
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
msmith 3 Disc 1:22 9/1/2026 4:05 PM
SRV-PROD-02 printed nothing, which means nobody is logged on. A host that is unreachable produces the same empty result once 2>nul hides the error, so drop the redirect while you are troubleshooting connectivity.
Hidden gems
The blank column that breaks scripts
This is the single most common bug in quser automation. A disconnected session has an empty SESSIONNAME field, so splitting the line on whitespace returns five fields instead of six. Every value after the username shifts one place to the left, and the script happily records a session ID of Disc without complaining.
Look again at the output from example 1: msmith has nothing between the username and the ID. Any parser that assumes a fixed field count will mis-read that row, and it will be exactly the row you care about, because disconnected sessions are the ones you are hunting. The working PowerShell parser is in the next section.
Disc does not mean logged off
A session in Disc state is still fully alive. Its processes run, its memory is committed, its file handles are held and its network drives stay mapped. Users assume closing the RDP window ends the session; it does not. This is why a server can be short of RAM with nobody visibly logged on, and why a file stays locked long after its owner went home.
Reattaching a session instead of ending it
tscon moves an existing session to a different one rather than closing it. The practical use is rescuing your own session after a network drop: connect, find your old disconnected ID, and pull it to the session you are sitting in – your applications are exactly where you left them.
rem 2 is the old disconnected session; /dest is the session you are in right now
tscon 2 /dest:rdp-tcp#5
tscon only works without a password when the target session belongs to you. Connecting to another user’s session requires their password via /password:, and you cannot connect to the console session at all.
Query one user instead of the whole host
Both commands accept a filter as the first positional argument, which is far cheaper than listing everything and grepping. On a busy RDS host with a hundred sessions the difference is immediately noticeable.
rem returns only msmith's session, or an error if that user has none
quser msmith /server:SRV-RDS-01
PowerShell equivalents
There is no built-in cmdlet that replaces quser on a plain Windows install – Get-RDUserSession exists only on a Remote Desktop deployment with the RDS module. The practical approach is to keep using quser and turn its text output into objects. The function below handles the blank-column case described above.
function Get-RdpSession {
param([string]$ComputerName = $env:COMPUTERNAME)
# quser is a native exe - 2>$null swallows "No User exists for *" on an idle host
$raw = if ($ComputerName -eq $env:COMPUTERNAME) { quser 2>$null }
else { quser /server:$ComputerName 2>$null }
if (-not $raw) { return }
$raw | Select-Object -Skip 1 | ForEach-Object {
# strip the '>' marking your own session, then split on runs of 2+ spaces
$f = ($_ -replace '^>', ' ').Trim() -split '\s{2,}'
# a disconnected session has an EMPTY SESSIONNAME -> only 5 fields, not 6
if ($f.Count -eq 6) { $sess = $f[1]; $rest = $f[2..5] }
else { $sess = ''; $rest = $f[1..4] }
[pscustomobject]@{
Server = $ComputerName
UserName = $f[0]
SessionName = $sess
Id = [int]$rest[0]
State = $rest[1]
IdleTime = $rest[2]
LogonTime = $rest[3]
}
}
}
With sessions as objects you can filter the way you would with any other cmdlet. This finds sessions that have been disconnected for more than a day – the + only appears in the idle-time format once it passes 24 hours, which makes it a reliable marker for abandonment.
Get-RdpSession -ComputerName SRV-RDS-01 |
Where-Object { $_.State -eq 'Disc' -and $_.IdleTime -match '\+' } |
Format-Table Server, UserName, Id, IdleTime, LogonTime -AutoSize
And this closes them. Wrap $_.Id in $( ) so PowerShell evaluates the property before handing the value to logoff as an argument.
Get-RdpSession -ComputerName SRV-RDS-01 |
Where-Object { $_.State -eq 'Disc' -and $_.IdleTime -match '\+' } |
ForEach-Object {
Write-Host "Logging off $($_.UserName) (session $($_.Id))"
logoff $($_.Id) /server:SRV-RDS-01
}
For many hosts at once, run quser locally on each one through PowerShell remoting rather than making a remote RPC call per server. One round trip per host is faster and it works where RPC is filtered but WinRM is not.
$servers = 'SRV-PROD-01','SRV-PROD-02','SRV-RDS-01'
# the ScriptBlock runs on the far end, so quser reports on its own local sessions
Invoke-Command -ComputerName $servers -ScriptBlock { quser 2>$null } |
ForEach-Object { '{0,-14} {1}' -f $_.PSComputerName, $_ }
WMI offers a second, independent view. Win32_LogonSession with LogonType 10 returns RemoteInteractive logons, which is useful for correlating against event logs.
# LogonType 10 = RemoteInteractive (RDP), 2 = Interactive (console)
$logons = Get-CimInstance Win32_LoggedOnUser
Get-CimInstance Win32_LogonSession -Filter 'LogonType = 10' | ForEach-Object {
$s = $_
$u = $logons | Where-Object { $_.Dependent.LogonId -eq $s.LogonId } | Select-Object -First 1
[pscustomobject]@{
Account = '{0}\{1}' -f $u.Antecedent.Domain, $u.Antecedent.Name
LogonId = $s.LogonId
StartTime = $s.StartTime
}
}
Win32_LogonSession lists logon sessions, not Terminal Services sessions. There is no session ID in that output that you can pass to logoff, and a single user can hold several logon sessions at once. Use it for auditing and correlation – keep quser for anything you intend to act on.
Where this matters
- Before a reboot. One
quser /server:tells you whether the maintenance window is actually clear, without opening a single RDP connection. - When the two-session limit bites. A disconnected admin session from last week is the usual reason a server refuses your connection at the worst possible moment.
- Chasing a locked file. If a document or database file will not release, the owning session is often disconnected rather than gone –
quserfinds it in seconds. - Unexplained memory pressure. A handful of forgotten
Discsessions on an RDS host can hold gigabytes that Task Manager attributes to nothing obvious. - Security review. Cross-checking live sessions against expected working hours exposes shared accounts and stale service logons faster than reading event logs.
- Recovering your own work. After a VPN drop,
tsconreattaches the session you lost instead of forcing you to start over.
Tips and limitations
- You can always query and log off your own session. Acting on another user’s session requires Full Control permission on that session – in practice, local administrator on the target.
/server:uses RPC, not port 3389. If it fails with an access or RPC error while RDP itself works, look at the firewall and at RPC rather than at Remote Desktop settings.- Windows Home editions do not ship
quserorqwinsta. On Pro, Enterprise and Server they are in%SystemRoot%\System32and need no installation. - You cannot log off the console session, and you cannot
tsconinto it. - Session IDs are recycled. Always re-query immediately before you act, especially inside scripts that loop over several hosts.
msgreaches active sessions only, and delivery is not guaranteed – treat it as a courtesy, not as consent.- Column widths in
quseroutput shift with long usernames. Split on runs of two or more spaces rather than on fixed character positions.
Official documentation
- query user (quser) – Windows Commands | Microsoft Learn
- query session (qwinsta) – Windows Commands | Microsoft Learn
- reset session (rwinsta) – Windows Commands | Microsoft Learn
- logoff – Windows Commands | Microsoft Learn
Related tools
- Windows Event Log Analyzer – paste the logon and session events you find while auditing sessions and get the event ID explained in context.
- Port Checker – confirm whether 3389 is actually reachable before blaming session limits for a failed connection.
Related guides
- RDP security warning after the April 2026 Windows update – the client-side dialog you will meet on the way into the sessions this article manages.
- How to check open ports on Windows – rule out a blocked 3389 before you start hunting for stale sessions.
- How to read Windows Event Logs with PowerShell – pull the matching logon and logoff events once you know which session you are investigating.
- Find what is causing high CPU on a Windows server – the next step when a forgotten session turns out to be doing real work.