To force a logoff on a remote computer, admins have a few options at their disposal. In this article, we'll go over three ways to make this happen with PowerShell.

There are a number of reasons to do this, including employee termination, as a troubleshooting measure or simply to free up some sessions on a remote desktop server. Knowing how to do this from the command line will prevent either having to yell at users over the phone to log off, or having to access their remote consoles via VNC. Using the command line also gives an admin the opportunity to perform this task across many machines or users at once, if necessary.

The first way is by using the logoff command. This command has been around a long time. Many admins are not aware of this command, let alone that it has the ability to perform remote logoffs!

The logoff utility can log off users remotely but requires an extra step of finding a session ID. So first, we must find this ID. We can do this by using the quser utility and the server argument as you can see below.

Finding the session ID

Finding the session ID

Notice the value of 2 under the ID field. That is the session ID we need. However, unfortunately, since quser is not a PowerShell command that would return a structured object, we'll have to parse this string to pull out that value. To pull out this value by itself, we can use the Where-Object command and do a little regular expression matching to make it happen.

$userName = 'administrator'
$sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ' +')[2]
$sessionId

Above, I am searching for any user with the name of administrator and only returning the session ID. Once I have the session ID, I can now use this ID with the logoff command to log off the administrator account remotely on the DC computer.

logoff $sessionId /server:DC

Another method is to use WMI/CIM and the Win32Shutdown() method. This method doesn't force you to find a user session first but also doesn't give you the ability to pick a user either. This method will just log off all users interactively logged on to the remote computer. You can do this on a single line in PowerShell.

Below, you can see that I'm invoking a CIM method on the DC computer again and specifying 4 as a flag. This forcefully logs off the user. You can use 0 here too to perform a "graceful" logoff.

Invoke-CimMethod -ClassName Win32_Operatingsystem -ComputerName DC -MethodName Win32Shutdown -Arguments @{ Flags = 4 }

There's one last method if the client system from which you're invoking the command is Windows 10. In this case, you should have the Invoke-RDUserLogoff command available to you. Using this method is similar to using the logoff utility. You must first obtain the session ID using quser or the qwinsta utility if the remote server is not running Remote Desktop Services.

$userName = 'administrator'
$sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ' +')[2]

After acquiring the session ID, you can pass it to Invoke-RDUserLogoff with the UnifiedSessionId parameter and use the HostServer parameter to pass along the remote computer name.

Subscribe to 4sysops newsletter!

Invoke-RDUserLogoff -HostServer DC -UnifiedSessionId $sessionId

By now, I hope you've seen enough ways to log off a user from a remote computer. Each method performs the task in a different way but in the end gets the same job done.

avataravataravatar
20 Comments
  1. commotion 6 years ago

    You actually need to have

    $sessionId = ((quser /server:DC | Where-Object { $_ -match $userName }) -split ‘ +’)[3]

    instead of the 2 on the end otherwise your output will be consolename not id

     

    • German Pulido 4 years ago

      Actually, sometimes you need the second column, sometimes you need the third column. Using “quser” in scripts is simply not doable, since sometimes (as in the screenshot shown in this blog) SESSIONNAME is blank, but other times it has a value (either the number of the RDP session, or “console”).

  2. Alfred Kuhnert 6 years ago

    Here is the final revised functional script that is being used at my company. I found a PC today with 16 users currently logged on!!! The script contains a loop so you can logoff more than one person on the targeted PC:

    cls
    Write-Host '******************************'
    Write-Host '* Log Off User Remotely *'
    Write-Host '******************************'
    Write-Host
    
    $global:adminCreds = $host.ui.PromptForCredential("Need credentials", "Please enter your user name and password.", "", "")
    $global:ComputerName = Read-Host 'Computer Name?'
    Function getSessions {
    Write-host
    Write-host "Getting user sessions..."
    Write-Host
    Write-Host '***************************************************************************'
    Invoke-Command -ComputerName $global:ComputerName -scriptBlock {query session} -credential $global:adminCreds
    }
    
    Function logUserOff {
    Write-Host
    $SessionNum = Read-Host 'Session ID number to log off?'
    $title = "Log Off"
    $message = "Are you sure you want to log them off?"
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
    "Logs selected user off."
    $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
    "Exits."
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
    $result = $host.ui.PromptForChoice($title, $message, $options, 1)
    
    switch ($result){
    0 {
    Write-Host
    Write-Host 'OK. Logging them off...'
    Invoke-Command -ComputerName $global:ComputerName -scriptBlock {logoff $args[0]} -ArgumentList $SessionNum -credential $global:adminCreds
    Write-Host
    Write-Host 'Success!' -ForegroundColor green
    break
    }
    1 {break}
    }
    }
    
    Do {
    getSessions
    logUserOff
    
    Write-Host
    #Write-Host "Press any key to continue ..."
    # $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
    
    #Configure yes choice
    $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes","Remove another profile."
    
    #Configure no choice
    $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No","Quit profile removal"
    
    #Determine Values for Choice
    $choice = [System.Management.Automation.Host.ChoiceDescription[]] @($yes,$no)
    
    #Determine Default Selection
    [int]$default = 0
    
    #Present choice option to user
    $userchoice = $host.ui.PromptforChoice("","Logoff Another Profile?",$choice,$default)
    }
    #If user selects No, then quit the script
    Until ($userchoice -eq 1)

  3. Dan 5 years ago

    The script Alfred posted is my new favorite thing.  Thank you!

  4. Greg Shelton 5 years ago

    I love this script.. but what could I add as an option to say log off “all users”?

    we have a process that allows 50 people to connect, and I don’t want to list each session ID one at a time..

    An option to remove all users by typing “All” instead of a number would make this flawless!

    thanks so much..

  5. A few notes for everyone:

    1. QUser and QWinsta do *not* require admin rights to get session information from *any* Windows system with Windows 2003/XP or newer.
    2. Logoff, Reset, and QProcess *do* require admin rights on the machine you will be talking to (unless you are logging off or querying processes from your own session).
    3. All of these commands support a /v:<servername> option to interact with a remote machine

    It’s easy to process the output with powershell since the output is just strings.

    One other utility that can be incredibly helpful is the tslogoff.exe command from C-A-D consulting (http://www.ctrl-alt-del.com.au/CAD_TSUtils.htm), it has multiple options such as tslogoff * /server:<servername> to logoff everyone, or tslogoff /disc /server:<servername> to logoff disconnected sessions, etc.

    David F.

  6. Koen 5 years ago

    I wrote this:  It will scan all servers you select for users that are logged on and lets you select users to log off.

    Function YesNo
    {
    Param($provide)
    $a = new-object -comobject wscript.shell
    $popup = “Do you want to Select the servers to scan ?”
    $intAnswer = $a.popup($popup, `
    0,$provide,4)
    If ($intAnswer -eq 6) {
    $answer = “yes”
    } else {
    $answer = $null
    }
    return $Answer
    }
    Function DisplayInfo
    {
    Param($Servers)
    cls
    write-host
    $count = $servers.count
    write-host $count -ForegroundColor Yellow -NoNewline
    write-host ” servers selected to investigate.” -ForegroundColor Green
    write-host “The total of servers in the AD is :” -ForegroundColor Green -NoNewline
    Write-Host (get-adcomputer -filter {Operatingsystem -like “Windows Server*”} | select name).count -ForegroundColor Red
    Write-Host
    }
    Function Selection
    {
    #$servers = (get-adcomputer -filter {Operatingsystem -like “Windows Server*”} | sort name | select name).name
    $servers = (get-adcomputer -LDAPFilter “(&(objectCategory=computer)(operatingSystem=Windows Server*)(!serviceprincipalname=*MSClusterVirtualServer*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))” -Property name | sort-object Name).name
    if (yesNo -eq “Yes”)
    {
    $servers = $servers | out-gridview -passthru
    }
    return $servers
    }

    Function DoIt
    {
    Param($Servers)
    $completelist = @()
    foreach ($server in $servers)
    {
    $serverdisp = $server.PadRight(($servers | Measure-Object -Maximum -Property Length).Maximum)
    $Skip = 0
    Write-host “Checking server ” -ForegroundColor Green -NoNewline; write-host $Server -ForegroundColor Yellow -NoNewline
    if (Test-Connection $server -count 1 -Quiet)
    {
    Write-Host ” —— (online)” -ForegroundColor Green
    $sessionId = $null
    try
    {
    #$ErrorActionPreference = “SilentlyContinue”
    $SessionIds = quser /server:$server 2>&1
    #$ErrorActionPreference = “Continue”
    }
    catch
    {
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host “$serverdisp seems inaccessible.” -ForegroundColor Red -NoNewline
    Write-Host ” No data available.” -ForegroundColor Yellow
    write-host ” ———————- ” -ForegroundColor green
    $skip = 1
    }
    if ($Skip -eq 0)
    {
    $nr = $sessionIds.count-2
    $c=0
    do
    {
    $c++
    $user = (($sessionIds[$c]) -split ‘ +’)[1]
    $session = (($sessionIds[$c]) -split ‘ +’)[2]
    $State = (($sessionIds[$c]) -split ‘ +’)[3]
    $IdleT = (($sessionIds[$c]) -split ‘ +’)[4]
    $LogonT = (($sessionIds[$c]) -split ‘ +’)[5] + ” ” + (($sessionIds[$c]) -split ‘ +’)[6]
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host $serverdisp -ForegroundColor Yellow -NoNewline
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host $user -ForegroundColor Magenta -NoNewline
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host $session -ForegroundColor Cyan -NoNewline
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host $State -ForegroundColor Cyan -NoNewline
    Write-Host ” – ” -ForegroundColor Green -NoNewline
    write-host $LogonT -ForegroundColor Cyan -NoNewline
    Write-Host ” => ” -ForegroundColor Green -NoNewline
    write-host $IdleT -ForegroundColor Cyan
    # $Ser+=@($server); $use+=@($user);$Ses+=@($session);$sta+=@($state);$Idl+=@($IdleT);$Log+=@($LogonT)
    $completelist += @{server=$server;user=$user;session=$session;State=$state;IdleTime=$idleT;LogonTime=$logonT}
    }
    while ($c -le $nr)
    write-host ” ———————- ” -ForegroundColor green
    }
    }
    else
    {
    Write-Host ” —— (offline)” -ForegroundColor Red
    Write-Host ” – No data available.” -ForegroundColor Yellow
    write-host ” ———————- ” -ForegroundColor green
    }
    }
    <# $completeList = New-Object PSObject
    $completeList | Add-Member NoteProperty Server $Ser
    $completeList | Add-Member NoteProperty User $use
    $completeList | Add-Member NoteProperty Session $ses
    $completeList | Add-Member NoteProperty State $sta
    $completeList | Add-Member NoteProperty IdleTime $Idl
    $completeList | Add-Member NoteProperty Logon $Log
    $completelist = @(server=$ser;user=$use;session=$ses;State=$sta;IdleTime=$idl;LogonTime=$log) #>
    return $completeList
    }
    Function YesNo2
    {
    Param($Completelist)
    $List = $completelist |% {New-Object psobject -Property $_}
    $Users = ($completelist |% {New-Object psobject -Property $_}).user | sort -Unique
    $a = new-object -comobject wscript.shell
    $intAnswer = $a.popup(“Do you want to Log Off (A) certain User(s)?”, `
    0,”Logoff Users”,4)
    If ($intAnswer -eq 6)
    {
    $answer2 = $Users | Out-GridView -PassThru
    $step = 0
    do
    {
    $logs = $list | where {$_.user -eq $answer2}
    foreach ($log in $logs)
    {
    write-host $log.user -ForegroundColor green -NoNewline
    write-host ” logged on to device ” -ForegroundColor Yellow -NoNewline
    write-host $log.server -ForegroundColor Green -NoNewline
    Write-Host ” since ” -ForegroundColor Yellow -NoNewline
    write-host $log.Logontime -ForegroundColor Cyan
    write-host “Attempt log off:” -ForegroundColor Yellow -NoNewline
    $connect = $log.server
    try{Invoke-RDUserLogoff -HostServer $connect -UnifiedSessionId $Log.session -Force ;$logof=”OK”}
    catch{$logof=”NOK”}
    if($Logof -eq “OK”){ write-host ” was logged off!” -ForegroundColor Green}
    else { write-host ” wasn’t logged off!” -ForegroundColor red; write-host “Please check manually.” -ForegroundColor red}

    }
    $step++
    }
    while ($step -lt $answer2.count)
    }
    }

    # SO now let’s play!
    $Servers = Selection
    Displayinfo $servers
    $completelist = DoIt $servers
    yesno2 $completelist

  7. Koen 5 years ago
    Function YesNo
    {
        Param($provide)
        $a = new-object -comobject wscript.shell 
        $popup = "Do you want to Select the servers to scan ?"
        $intAnswer = $a.popup($popup, ` 
        0,$provide,4) 
            If ($intAnswer -eq 6) { 
                $answer = "yes" 
            } else { 
                $answer = $null 
            } 
        return $Answer
    }
    Function DisplayInfo
    {
        Param($Servers)
        cls
        write-host
        $count = $servers.count
        write-host $count -ForegroundColor Yellow -NoNewline
        write-host " servers selected to investigate." -ForegroundColor Green
        write-host "The total of servers in the AD is :" -ForegroundColor Green -NoNewline
        Write-Host (get-adcomputer -filter {Operatingsystem -like "Windows Server*"} | select name).count -ForegroundColor Red
        Write-Host 
    }
    Function Selection
    {
        #$servers = (get-adcomputer -filter {Operatingsystem -like "Windows Server*"} | sort name | select name).name
        $servers = (get-adcomputer -LDAPFilter "(&(objectCategory=computer)(operatingSystem=Windows Server*)(!serviceprincipalname=*MSClusterVirtualServer*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" -Property name | sort-object Name).name
        if (yesNo -eq "Yes")
        {
            $servers = $servers | out-gridview -passthru
        }
        return $servers
    }
    
    Function DoIt
    {
        Param($Servers)
        $completelist = @()
        foreach ($server in $servers) 
        {
            $serverdisp = $server.PadRight(($servers | Measure-Object -Maximum -Property Length).Maximum)
            $Skip = 0
            Write-host "Checking server " -ForegroundColor Green -NoNewline; write-host $Server -ForegroundColor Yellow -NoNewline
            if (Test-Connection $server -count 1 -Quiet)
            {
                Write-Host "         ------ (online)" -ForegroundColor Green
                $sessionId = $null
                try 
                {
                    #$ErrorActionPreference = "SilentlyContinue"
                    $SessionIds = quser /server:$server 2>&1
                    #$ErrorActionPreference = "Continue"
                }
                catch
                { 
                    Write-Host "              - " -ForegroundColor Green -NoNewline
                    write-host "$serverdisp seems inaccessible." -ForegroundColor Red -NoNewline
                    Write-Host " No data available." -ForegroundColor Yellow
                    write-host " ---------------------- " -ForegroundColor green
                    $skip = 1
                }
                if ($Skip -eq 0)
                {
                    $nr = $sessionIds.count-2
                    $c=0
                    do
                    {
                        $c++
                        $user = (($sessionIds[$c]) -split ' +')[1]
                        $session = (($sessionIds[$c]) -split ' +')[2]
                        $State = (($sessionIds[$c]) -split ' +')[3]
                        $IdleT = (($sessionIds[$c]) -split ' +')[4]
                        $LogonT = (($sessionIds[$c]) -split ' +')[5] + " " + (($sessionIds[$c]) -split ' +')[6]
                        Write-Host "              - " -ForegroundColor Green -NoNewline
                        write-host $serverdisp -ForegroundColor Yellow -NoNewline
                        Write-Host " - " -ForegroundColor Green -NoNewline
                        write-host $user -ForegroundColor Magenta -NoNewline
                        Write-Host " - " -ForegroundColor Green -NoNewline
                        write-host $session -ForegroundColor Cyan -NoNewline
                        Write-Host " - " -ForegroundColor Green -NoNewline
                        write-host $State -ForegroundColor Cyan -NoNewline
                        Write-Host " - " -ForegroundColor Green -NoNewline
                        write-host $LogonT -ForegroundColor Cyan -NoNewline
                        Write-Host " => " -ForegroundColor Green -NoNewline
                        write-host $IdleT -ForegroundColor Cyan
                        # $Ser+=@($server); $use+=@($user);$Ses+=@($session);$sta+=@($state);$Idl+=@($IdleT);$Log+=@($LogonT)
                        $completelist += @{server=$server;user=$user;session=$session;State=$state;IdleTime=$idleT;LogonTime=$logonT}
                    }
                    while ($c -le $nr)
                    write-host " ---------------------- " -ForegroundColor green
                }
            }
            else
            {
                    Write-Host "         ------ (offline)" -ForegroundColor Red
                    Write-Host "              - No data available." -ForegroundColor Yellow
                    write-host " ---------------------- " -ForegroundColor green
            }
        }
        <# $completeList = New-Object PSObject
        $completeList | Add-Member NoteProperty Server   $Ser
        $completeList | Add-Member NoteProperty User     $use
        $completeList | Add-Member NoteProperty Session  $ses
        $completeList | Add-Member NoteProperty State    $sta
        $completeList | Add-Member NoteProperty IdleTime $Idl
        $completeList | Add-Member NoteProperty Logon    $Log
        $completelist = @(server=$ser;user=$use;session=$ses;State=$sta;IdleTime=$idl;LogonTime=$log) #>
        return $completeList
    }
    Function YesNo2
    {
        Param($Completelist)
        $more = @()
        $List = $completelist |% {New-Object psobject -Property $_}
        $Users = ($completelist |% {New-Object psobject -Property $_}).user | sort -Unique
        $a = new-object -comobject wscript.shell 
        $intAnswer = $a.popup("Do you want to Log Off (A) certain User(s)?", ` 
        0,"Logoff Users",4) 
        If ($intAnswer -eq 6) 
        { 
            $answer2 = $Users | Out-GridView -PassThru
            foreach ($sub in $answer2)
            {
                $more += $list | where {$_.user -eq $sub}
            }
            $answer2 = $more | select server, user, State, logontime, idletime, session | Out-GridView -PassThru
            $step = 0
    
            foreach ($log in $answer2)
                {
                    write-host $log.user -ForegroundColor green -NoNewline
                    write-host " logged on to device " -ForegroundColor Yellow -NoNewline
                    write-host $log.server -ForegroundColor Green -NoNewline
                    Write-Host " since " -ForegroundColor Yellow -NoNewline
                    write-host $log.Logontime -ForegroundColor Cyan
                    write-host "   °Attempt log off:" -ForegroundColor Yellow -NoNewline
                    $connect = $log.server
                    try{Invoke-RDUserLogoff -HostServer $connect -UnifiedSessionId $Log.session -Force ;$logof="OK"}
                    catch{$logof="NOK"}
                    if($Logof -eq "OK"){ write-host " log off succesful !" -ForegroundColor Green}
                    else { write-host " log off failed! Please check manually." -ForegroundColor red}
    
                }
        }
    }
    
    
    
    # SO now let's play!
    $Servers = Selection
    Displayinfo $servers
    $completelist = DoIt $servers
    yesno2 $completelist
    

  8. Peter Velinov 5 years ago

    Koen, thank you! This script is really … really very useful!

  9. Danny 4 years ago

    Koen,

    How would I edit this to search through all computers on an AD Domain?

    Many Thanks

  10. Koen 4 years ago

    Danny,

    if you really want all AD-devices, then alter line 30 to:

    $servers = (get-adcomputer -Filter * -Property name | sort-object Name).name

    grts

    Koen

    avatar
  11. Eric O 4 years ago

    Thank you @Adam B for this post! Very Helpful!

  12. Derli Dias Campos Junior 4 years ago
    $scriptBlock = {
         $ErrorActionPreference = 'Stop'
     
         try {
             ## Find all sessions matching the specified username
             $sessions = quser | Where-Object {$_ -match 'teste'}
             ## Parse the session IDs from the output
             $sessionIds = ($sessions -split ' +')[3]
             Write-Host "Found $(@($sessionIds).Count) user login(s) on computer."
             ## Loop through each session ID and pass each to the logoff command
             $sessionIds | ForEach-Object {
                 Write-Host "Logging off session id [$($_)]..."
                 logoff $_
             }
         } catch {
             if ($_.Exception.Message -match 'No user exists') {
                 Write-Host "The user is not logged in."
             } else {
                 throw $_.Exception.Message
             }
         }
     }
    
    

    This piece of code is exactly what I need, it just won’t work if the user is not connected, the catch part isn’t working.

    • @Derli Dias Campos Junior

      Try this in the catch section:

      $error[0].exception.message -match 'No user exists'

  13. Bob 4 years ago

    For Alfred and Keon's scripts I get the below error:

    [%servername%] Connecting to remote server l%servername% failed with the following error
    message : The client cannot connect to the destination specified in the request. Verify that the service on the
    destination is running and is accepting requests. Consult the logs and documentation for the WS-Management service
    running on the destination, most commonly IIS or WinRM. If the destination is the WinRM service, run the following
    command on the destination to analyze and configure the WinRM service: "winrm quickconfig". For more information, see
    the about_Remote_Troubleshooting Help topic.
        + CategoryInfo          : OpenError: (%servername%:String) [], PSRemotingTransportException
        + FullyQualifiedErrorId : CannotConnect,PSSessionStateBroken

    • @Bob

      Please try to see if port 5985 is reachable on the remote computer.

      If your local workstation runs on Windows 8 or above:

      Test-NetConnection -ComputerName RemoteComputer -Port 5985

      If your local workstation runs Windows 7

      Install the Telnet Client feature and run this:

      telnet  RemoteComputerName  5985

       

  14. Mohamed 3 years ago

    @ Koen  It really very useful script,Thank you very much for it

    I tried to use it for all AD computers but it shows "[5]:Access – is – denied." when Checking server,However it is working fine with servers 

    my user is domain admins,it there any thing to do from my side to fix this issue.Last thank you again for this fantastic script

  15. Jeremy 3 years ago

    I commonly need to remove all other folks on a machine, so this is what I created for it. I'm posting it here since I haven't seen something that deals with quser's nullable session name. I've found that only that column is nullable, so you can count the number of populated fields to determine where the session ID is:

    function Remove-OtherUsers {
    	[CmdletBinding()]
    	param()
    
    	quser |
    		? {$_ -notmatch 'USERNAME' -and $_ -notmatch '^>'} | %{
    			$fields = $_.Trim() -split '\s+'
    			$sessionId = $fields[$fields.Count - 6]
    			Write-Verbose "Logging off user '$($fields[0])'"
    			& logoff $sessionId
    		}
    }
    

  16. Logan 3 years ago

    The post below seems to be a replication of this work, Adam. It doesn't appear to give you credit for it. I am uncertain if this is authorized or not, but thought I would mention it.

    https://www.heelpbook.net/2018/force-a-user-logoff-remotely-with-powershell/

  17. Jasper 3 years ago

    If you are running an RDS farm environment, the simple thing to do is combine the Get-RDUserSession cmdlet (which returns the UnifiedSessionId) with Invoke-RDUserLogoff.

     

    If there is no farm, you can also take the quser output and pipe it to ConvertFrom-String to have a simple object to work with. You can avoid regex this way.

Leave a reply

Your email address will not be published.

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account