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
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
LikeLike
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
LikeLike
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?
LikeLike
I made further tests with our 2016 server and my Windows 10 computer.
Both have the location “Canada French” but the issue occurs only on the 2016 server(!?)
I made two capture for your comprehension:
https://drive.google.com/open?id=1WNVaZkndN_NtqgNaBZBZLj12gR83cJcv
Changing $i+=7){ for $i+=8){ at the line 19 give me the difference between the printscreens.
I’ll make some other tests.
LikeLike
Hi Christian. I’ll have a try at this after I get caught up with stuff (which might not be until tomorrow).
LikeLike
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.
LikeLike
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
LikeLike
Thanks Bill :-)
LikeLike
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 :)
LikeLike
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 :)
LikeLike