[powershell] Powershell Log Off Remote Session

I am trying to formulate a Powershell command to remotely log off a user. We have a terminal server with a very unstable program that sometimes locks sessions. We have to remotely log off a user but I'm trying to write a Powershell statement that will log off the person who ran the script. I Googled around, and found this command:

Invoke-Command -ComputerName MyServer -Command {shutdown -l}

However, the command returns "incorrect function." I can run other commands successfully in the brackets, such as Get-Process.

The idea is for me to put that into a script that users can run to log themselves off of the server (since when it locks, they cannot access the start menu or ALT+CTRL+END to do it through the GUI).

The flow would be this: Bob logs into MyServer via RDP but his session freezes. On his local desktop, he can run MyScript (containing a command similar to above) which will log off his session on MyServer.

This question is related to powershell rdp

The answer is


Perhaps surprisingly you can logoff users with the logoff command.

C:\> logoff /?
Terminates a session.

LOGOFF [sessionname | sessionid] [/SERVER:servername] [/V] [/VM]

  sessionname         The name of the session.
  sessionid           The ID of the session.
  /SERVER:servername  Specifies the Remote Desktop server containing the user
                      session to log off (default is current).
  /V                  Displays information about the actions performed.
  /VM                 Logs off a session on server or within virtual machine.
                      The unique ID of the session needs to be specified.

The session ID can be determined with the qwinsta (query session) or quser (query user) commands (see here):

$server   = 'MyServer'
$username = $env:USERNAME

$session = ((quser /server:$server | ? { $_ -match $username }) -split ' +')[2]

logoff $session /server:$server

I've modified Casey's answer to only logoff disconnected sessions by doing the following:

   foreach($Server in $Servers) {
    try {
        query user /server:$Server 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5] -eq 'Disc'} | % {logoff ($_ -split "\s+")[-6] /server:$Server /V}
    }
    catch {}
    }

here is what i came up with. combining multiple answers

#computer list below
$computers = (
'computer1.domain.local',
'computer2.domain.local'

)
 
foreach ($Computer in $computers) {

 Invoke-Command -ComputerName $Computer -ScriptBlock { 
  Write-Host '______  '$Env:Computername
  $usertocheck = 'SomeUserName'
$sessionID = ((quser | Where-Object { $_ -match $usertocheck }) -split ' +')[2]
 

If([string]::IsNullOrEmpty($sessionID)){            
    Write-Host -ForegroundColor Yellow  "User Not Found."           
} else {            
    write-host -ForegroundColor Green  'Logging off ' $usertocheck 'Session ID' $sessionID
    logoff $sessionID    
}

 }
 }

Here's a great scripted solution for logging people out remotely or locally. I'm using qwinsta to get session information and building an array out of the given output. This makes it really easy to iterate through each entry and log out only the actual users, and not the system or RDP listener itself which usually just throws an access denied error anyway.

$serverName = "Name of server here OR localhost"
$sessions = qwinsta /server $serverName| ?{ $_ -notmatch '^ SESSIONNAME' } | %{
$item = "" | Select "Active", "SessionName", "Username", "Id", "State", "Type", "Device"
$item.Active = $_.Substring(0,1) -match '>'
$item.SessionName = $_.Substring(1,18).Trim()
$item.Username = $_.Substring(19,20).Trim()
$item.Id = $_.Substring(39,9).Trim()
$item.State = $_.Substring(48,8).Trim()
$item.Type = $_.Substring(56,12).Trim()
$item.Device = $_.Substring(68).Trim()
$item
} 

foreach ($session in $sessions){
    if ($session.Username -ne "" -or $session.Username.Length -gt 1){
        logoff /server $serverName $session.Id
    }
}

In the first line of this script give $serverName the appropriate value or localhost if running locally. I use this script to kick users before an automated process attempts to move some folders around. Prevents "file in use" errors for me. Another note, this script will have to be ran as an administrator user otherwise you can get accessed denied trying to log someone out. Hope this helps!


Adding plain DOS commands, if someone is so inclined. Yes, this still works for Win 8 and Server 2008 + Server 2012.

Query session /server:Server100

Will return:

SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
rdp-tcp#0         Bob                       3  Active  rdpwd
rdp-tcp#5         Jim                       9  Active  rdpwd
rdp-tcp                                 65536  Listen

And to log off a session, use:

Reset session 3 /server:Server100

Log off all users from a machine:

try {
   query user /server:$SERVER 2>&1 | select -skip 1 | foreach {
     logoff ($_ -split "\s+")[-6] /server:$SERVER
   }
}
catch {}

Details:

  • the try/catch is used when there are no users are on the server, and the query returns an error. however, you could drop the 2>&1 part, and remove the try/catch if you don't mind seeing the error string
  • select -skip 1 removes the header line
  • the inner foreach logs off each user
  • ($_ -split "\s+") splits the string to an array with just text items
  • [-6] index gets session ID and is the 6th string counting from the reverse of the array, you need to do this because the query output will have either 8 or 9 elements depending if the users connected or disconnected from the terminal session

I am sure my code will be easier and works faster.

    $logon_sessions = get-process -includeusername | Select-Object -Unique -Property UserName, si | ? { $_ -match "server" -and $_ -notmatch "admin" }

    foreach ($id in $logon_sessions.si) {
        logoff $id
    }

You can use Invoke-RDUserLogoff

An example logging off Active Directory users of a specific Organizational Unit:

$users = Get-ADUser -filter * -SearchBase "ou=YOUR_OU_NAME,dc=contoso,dc=com"

Get-RDUserSession | where { $users.sAMAccountName -contains $_.UserName } | % { $_ | Invoke-RDUserLogoff -Force }

At the end of the pipe, if you try to use only foreach (%), it will log off only one user. But using this combination of foreach and pipe:

| % { $_ | command }

will work as expected.

Ps. Run as Adm.


This is oldschool and predates PowerShell, but I have used the qwinsta / rwinsta combo for YEARS to remotely log off stale RDP sessions. It's built in on at least Windows XP and forward (possibly earlier)

Determine the session ID:

qwinsta /SERVER:<NAME>

Remove the session in question:

rwinsta <SESSION_ID> /SERVER:<NAME>

Below script will work well for both active and disconnected sessions as long as user has access to run logoff command remotely. All you have to do is change the servername from "YourServerName" on 4th line.

param (
    $queryResults = $null,
    [string]$UserName = $env:USERNAME,
    [string]$ServerName = "YourServerName"
)


if (Test-Connection $ServerName -Count 1 -Quiet) {  
    Write-Host "`n`n`n$ServerName is online!" -BackgroundColor Green -ForegroundColor Black


    Write-Host ("`nQuerying Server: `"$ServerName`" for disconnected sessions under UserName: `"" + $UserName.ToUpper() + "`"...") -BackgroundColor Gray -ForegroundColor Black

        query user $UserName /server:$ServerName 2>&1 | foreach {  

            if ($_ -match "Active") {
                Write-Host "Active Sessions"
                $queryResults = ("`n$ServerName," + (($_.trim() -replace ' {2,}', ','))) | ConvertFrom-Csv -Delimiter "," -Header "ServerName","UserName","SessionName","SessionID","CurrentState","IdealTime","LogonTime"


                $queryResults | ft
                Write-Host "Starting logoff procedure..." -BackgroundColor Gray -ForegroundColor Black

                $queryResults | foreach {
                    $Sessionl = $_.SessionID
                    $Serverl = $_.ServerName
                    Write-Host "Logging off"$_.username"from $serverl..." -ForegroundColor black -BackgroundColor Gray
                    sleep 2
                    logoff $Sessionl /server:$Serverl /v

                }


            }                
            elseif ($_ -match "Disc") {
                Write-Host "Disconnected Sessions"
                $queryResults = ("`n$ServerName," + (($_.trim() -replace ' {2,}', ','))) |  ConvertFrom-Csv -Delimiter "," -Header "ServerName","UserName","SessionID","CurrentState","IdealTime","LogonTime"

                $queryResults | ft
                Write-Host "Starting logoff procedure..." -BackgroundColor Gray -ForegroundColor Black

                $queryResults | foreach {
                    $Sessionl = $_.SessionID
                    $Serverl = $_.ServerName
                    Write-Host "Logging off"$_.username"from $serverl..."
                    sleep 2
                    logoff $Sessionl /server:$Serverl /v

                }
            }
            elseif ($_ -match "The RPC server is unavailable") {

                Write-Host "Unable to query the $ServerName, check for firewall settings on $ServerName!" -ForegroundColor White -BackgroundColor Red
            }
            elseif ($_ -match "No User exists for") {Write-Host "No user session exists"}

    }
}
else {

    Write-Host "`n`n`n$ServerName is Offline!" -BackgroundColor red -ForegroundColor white
    Write-Host "Error: Unable to connect to $ServerName!" -BackgroundColor red -ForegroundColor white
    Write-Host "Either the $ServerName is down or check for firewall settings on server $ServerName!" -BackgroundColor Yellow -ForegroundColor black
}





Read-Host "`n`nScript execution finished, press enter to exit!"

Some sample outputs. For active session: enter image description here

For disconnected sessions: enter image description here

if no sessions found: enter image description here Check out this solution as well to query all AD servers for your username and logoff only disconnected sessions. The script will also tell you if there were error connecting or querying the server.

Powershell to find out disconnected RDP session and log off at the same time


Try the Terminal Services PowerShell Module:

Get-TSSession -ComputerName comp1 -UserName user1 | Stop-TSSession -Force