GUI to log off Remote Desktop users by non-admins

I’ve blogged about the issues with the well-intentioned but ill-though-out Remote Desktop Management Server concept in Windows Server 2012 (inc R2) before, trying to come up with workarounds to all the things you used to be able to do easily with tsadmin in previous version, that you now just cannot do.

Like delegate non-admin users (e.g. helpdesk, expert users) the ability to log off other users.

So here’s a PowerShell script that falls back on the (very) old but thankfully still perfectly functional quser and logoff commands. My suggestion is to create a group, put the helpdesk users who need this functionality into the group, then grant the group permission via the following command:

wmic /namespace:\\root\CIMV2\TerminalServices PATH Win32_TSPermissionsSetting WHERE (TerminalName ="RDP-Tcp") CALL AddAccount "domain\group",2

You need to run that on all your RDS servers.

Once the helpdesk staff are in the group they’ll need to log off the RDS server and back on again. Now you can give them the script to run.

The script uses quser to get the current user sessions on the server where it’s being run, parses it and displays it in a GridView (with multi-select). Selected users are then logged off via the (also old) logoff Command.
Get your helpdesk user to right-click the script, select “Run with PowerShell”, then just select one or more users to log off and click “OK”.

# Log off RDS user sessions
# For regular users to be able to do this you need to grant them permission:
# wmic /namespace:\\root\CIMV2\TerminalServices PATH Win32_TSPermissionsSetting WHERE (TerminalName ="RDP-Tcp") CALL AddAccount "domain\group",2

# Get the list of users using good old quser (query user)
$QUser = &quser.exe
# Get rid of the Sessionname column because it is inconsistent (contains no data for disconnected sessions)
$QUser = $QUser -replace("rdp-tcp#\d+","")
# Remove header row
$QUser = $QUser -replace("username.+time","")
# Tidy up the spaces to leave one space separator only
$QUser = $QUser -replace("\s+"," ")
# Remove the current user line prefix
$QUser = $QUser -replace(">"," ")
# Split into an array, data starts at position 3, 7 items per line
$QUserArray = $Quser -split " "
# Make an array of objects
$CurrentUsers = New-Object System.Collections.ArrayList
for ($i = 3; $i -lt $QUserArray.Count; $i+=7){
    $ThisUser = New-Object -TypeName System.Object
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name "UserName" -Value $QUserArray[$i]
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name "ID" -Value $QUserArray[$i+1]
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name "State" -Value $QUserArray[$i+2]
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name "IdleTime" -Value $QUserArray[$i+3]
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name "LogonTime" -Value ($QUserArray[$i+4]+" "+$QUserArray[$i+5])
    $CurrentUsers.Add($ThisUser) | Out-Null
}
# Display the array in a gridview
$SelectedUsers = $CurrentUsers | Out-GridView -Title "Select users(s) to log off" -OutputMode Multiple
# Log off selected sessions
foreach($User in $SelectedUsers){
    Write-Host ("Logging off "+$User.UserName+" (session ID "+$User.ID+")... ") -NoNewline
    $x = &logoff.exe $User.ID
    Write-Host "Done"
}
Start-Sleep -Seconds 1
This entry was posted in PowerShell, Remote Desktop, Windows and tagged , , , , , , , , , , , . Bookmark the permalink.

10 Responses to GUI to log off Remote Desktop users by non-admins

  1. Bill Leuze says:

    Works great, thanks, finally after quite a search getting something to work for me. 3 things to note:

    -1) In addition to the given command to give the group permissions, I also had to do step 4 in https://social.technet.microsoft.com/Forums/windowsserver/en-US/20fbaf7a-d454-4dc0-80fd-eed374b717cf/delegate-terminal-services-rights?forum=winserverTS Before I did this, I would get “Error [5]:Access is denied.” with every user I tried to log off. Doing this additional step gave me the added feature that I can restrict permitted users to only log off users in their own branches – if they attempt to log off a user in a different branch (security group) they would get “Error [5]:Access is denied.”

    -2) This does not work for terminal servers that default users to the standard US time format (12 hour clock + AM or PM). In order to fix this:
    change line 19 to: for ($i = 3; $i -lt $QUserArray.Count; $i+=8){
    Change line 25 to: Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name “LogonTime” -Value ($QUserArray[$i+4]+” “+$QUserArray[$i+5]+” “+$QUserArray[$i+6])
    I do not know how to make it work for both 12h and 24h regional settings. My way works for 12h, the authors way works for 24h

    -3) to give the user a grid presorted by username:
    Change Line 29 to: $SelectedUsers = $CurrentUsers | Sort-Object -Property UserName | Out-GridView -Title “Select user(s) to log off and click [OK]” -OutputMode Multiple

    Like

  2. Christian says:

    Hi,
    I’m planning on using your awesome script. Made some test over a 2016 TS running in French Canada and i’m having weird issues. The information is not correctly placed under the correct columns(ex: Username column is empty adn the username apears in the id coloumn) so i opted for Like Bill Leuze modifications. Now every column are aligned correctly but i lost the “username” instead it shows me the ID but it not really user friendly to know which is who.

    I tried playing with the script but cannot find the sweet spot. Do you have a clue base on my informations?

    Thanks
    Christian

    Like

    • rcmtech says:

      In a word, no clue, sorry!
      I wonder if you’re running a different localisation to me (English UK) and are maybe getting different command output?

      Like

  3. Bill Leuze says:

    Hi Christian. I’ll have a try at this after I get caught up with stuff (which might not be until tomorrow).

    Like

  4. Bill Leuze says:

    On a test system I changed language to french and have same result as you. I have a solution but want to test on live system tomorrow before I give it to you. My test system had only the one logged on user so I can’t know for sure if the following rows are still correct until I test with more users signed in. A demain.

    Like

  5. Bill Leuze says:

    Following solution explains how to adjust for any language.

    You need to manually run quser.exe and look at the results. The important part is the header row. You need to compare the first word and the last word of the header row to the English. For example the English looks like:
    USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
    French is:
    UTILISATEUR SESSION ID ÉTAT TEMPS INACT TEMPS SESSION
    so now the key is to change line 8 of the authour’s original script. Line 8 removes everything between the first word and the last word of the header row. So for the example of the french output of quser.exe line 8 should be:
    $QUser = $QUser -replace(“utilisateur.+session”,””)

    Just make that one change and your script will work

    If you want to, you can also translate the displayed column headers into your own language in lines 21-25 of the original script. But note that if you change line 21, you must also change line 32 and replace “UserName” with whatever you used in line 21. And of course if you use my method to sort by UserName (in my first comment of this blog) then you also have to replace “UserName” in line 29.

    Note that each remote desktop user can be configured to use a different language. If all your users are configured with the same language, then they can all use the same script. But anyone who uses a different language needs an individually customized script. I even found that when I changed one user from English US to French (Canadian), by default the time format changed to 24 hour clock so I also had to change that part of the script for just that one user. I am guessing this is why Christian is getting different results on his win10 computer than with server 2016

    Like

    • rcmtech says:

      Thanks Bill :-)

      Like

    • Christian says:

      Last night i did run quser.exe manually and i found out the difference between the headers row like you said but i wasnt able to figure out the way to make it work.
      I’m in a hurry right now but i’ll do some tests with your infos.

      Thanks :)

      Like

      • Christian says:

        I confirm that it working fine with your modifications! My only issue right now are the user rights. I’ having issue with Win32_TSPermissionsSetting but its not related to the script so i’ll sort this out with some searches.
        Thanks again for your precious help :)

        Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.