2

I have been using the command tsdiscon happily for disconnecting from a remote desktop connection. I have made a "bat-file" with this line, and have assigned a shortcut to this function. Now, I have trouble using the command on Windows 10 machines.

Old usage

With tsdiscon, I can happily sign off from RDP connection in two cases:

  1. When I am in the RDP session, I will exit the RDP session
  2. When I am at the local machine, the RDP session will also get terminated. Yet, nothing will happen to the local machine

Current problem

Lately, maybe due to Windows 10 updates, issuing this command in the remote desktop session will sign off not only from the RDP session, but also the local machine. This is a bit annoying. Correspondingly, when I issue the command tsdiscon in both cases:

  1. If I am in the RDP session, I will get not only signed off from the that remote session, but also the local machine
  2. If I am at the local machine, I will get signed off on both machines as well.

Solution?

Can I pass in the specific session name that I would like tsdiscon to terminate? Or, should there be a certain parameter that stipulates at which scope this command shall take effect?

So far, same command (tsdiscon) is working in the same old way on Windows 7 machines. It become buggy when I start to use a Windows 10 machine to start remote desktop session.

3 Answers 3

0
/*
    This script is run in the server computer from the remote computer
    to disconnect the session without locking the server computer
    and do not require UAC after the first use
*/

; self elevate 
TaskName := RunAsTask()

; get the conexion number
Conn := ActiveSession()

; close the connection
Run, %COMSPEC% /c TSCON %Conn% /dest:console


; functions

RunAsTask() {                         ;  By SKAN,  http://ahkscript.org/boards/viewtopic.php?t=4334

  Local CmdLine, TaskName, TaskExists, XML, TaskSchd, TaskRoot, RunAsTask
  Local TASK_CREATE := 0x2,  TASK_LOGON_INTERACTIVE_TOKEN := 3 

  Try TaskSchd  := ComObjCreate( "Schedule.Service" ),    TaskSchd.Connect()
    , TaskRoot  := TaskSchd.GetFolder( "\" )
  Catch
      Return "", ErrorLevel := 1    

  CmdLine       := ( A_IsCompiled ? "" : """"  A_AhkPath """" )  A_Space  ( """" A_ScriptFullpath """"  )
  TaskName      := "[RunAsTask] " A_ScriptName " @" SubStr( "000000000"  DllCall( "NTDLL\RtlComputeCrc32"
                   , "Int",0, "WStr",CmdLine, "UInt",StrLen( CmdLine ) * 2, "UInt" ), -9 )

  Try RunAsTask := TaskRoot.GetTask( TaskName )
  TaskExists    := ! A_LastError 

  If ( not A_IsAdmin and TaskExists )      { 

    RunAsTask.Run( "" )
    ExitApp

  }

  If ( not A_IsAdmin and not TaskExists )  { 

    Run *RunAs %CmdLine%, %A_ScriptDir%, UseErrorLevel
    ExitApp

  }

  If ( A_IsAdmin and not TaskExists )      {  

    XML := "
    ( LTrim Join
      <?xml version=""1.0"" ?><Task xmlns=""http://schemas.microsoft.com/windows/2004/02/mit/task""><Regi
      strationInfo /><Triggers /><Principals><Principal id=""Author""><LogonType>InteractiveToken</LogonT
      ype><RunLevel>HighestAvailable</RunLevel></Principal></Principals><Settings><MultipleInstancesPolic
      y>Parallel</MultipleInstancesPolicy><DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries><
      StopIfGoingOnBatteries>false</StopIfGoingOnBatteries><AllowHardTerminate>false</AllowHardTerminate>
      <StartWhenAvailable>false</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAva
      ilable><IdleSettings><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleS
      ettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><
      RunOnlyIfIdle>false</RunOnlyIfIdle><DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteApp
      Session><UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine><WakeToRun>false</WakeToRun><
      ExecutionTimeLimit>PT0S</ExecutionTimeLimit></Settings><Actions Context=""Author""><Exec>
      <Command>"   (  A_IsCompiled ? A_ScriptFullpath : A_AhkPath )       "</Command>
      <Arguments>" ( !A_IsCompiled ? """" A_ScriptFullpath  """" : "" )   "</Arguments>
      <WorkingDirectory>" A_ScriptDir "</WorkingDirectory></Exec></Actions></Task>
    )"    

    TaskRoot.RegisterTask( TaskName, XML, TASK_CREATE, "", "", TASK_LOGON_INTERACTIVE_TOKEN )
  }         
Return TaskName, ErrorLevel := 0
}

ActiveSession() {
    if ((wtsapi32 := DllCall("LoadLibrary", "Str", "wtsapi32.dll", "Ptr")))
    {
        if (DllCall("wtsapi32\WTSEnumerateSessionsEx", "Ptr", WTS_CURRENT_SERVER_HANDLE := 0, "UInt*", 1, "UInt", 0, "Ptr*", pSessionInfo, "UInt*", wtsSessionCount)) 
        {
            WTS_CONNECTSTATE_CLASS := {0: "WTSActive", 1: "WTSConnected", 2: "WTSConnectQuery", 3: "WTSShadow", 4: "WTSDisconnected", 5: "WTSIdle", 6: "WTSListen", 7: "WTSReset", 8: "WTSDown", 9: "WTSInit"}
            cbWTS_SESSION_INFO_1 := A_PtrSize == 8 ? 56 : 32
            Loop % wtsSessionCount {
                currSessOffset := cbWTS_SESSION_INFO_1 * (A_Index - 1)
                ExecEnvId := NumGet(pSessionInfo+0, currSessOffset, "UInt")
                currSessOffset += 4
                State := NumGet(pSessionInfo+0, currSessOffset, "UInt")
                currSessOffset += 4
                SessionId := NumGet(pSessionInfo+0, currSessOffset, "UInt")
                currSessOffset += A_PtrSize
                SessionName := StrGet(NumGet(pSessionInfo+0, currSessOffset, "Ptr"),, A_IsUnicode ? "UTF-16" : "CP0")
                currSessOffset += A_PtrSize
                HostName := StrGet(NumGet(pSessionInfo+0, currSessOffset, "Ptr"),, A_IsUnicode ? "UTF-16" : "CP0")
                currSessOffset += A_PtrSize
                UserName := StrGet(NumGet(pSessionInfo+0, currSessOffset, "Ptr"),, A_IsUnicode ? "UTF-16" : "CP0")
                currSessOffset += A_PtrSize
                DomainName := StrGet(NumGet(pSessionInfo+0, currSessOffset, "Ptr"),, A_IsUnicode ? "UTF-16" : "CP0")
                currSessOffset += A_PtrSize
                FarmName := StrGet(NumGet(pSessionInfo+0, currSessOffset, "Ptr"),, A_IsUnicode ? "UTF-16" : "CP0")

                ; MsgBox % "Username: " . UserName . "`r`n" . "State: " . WTS_CONNECTSTATE_CLASS[State] . " (raw state: " . State . ")"

                If (UserName = A_UserName && State = 0)
                    Activa := SessionId
            }
            DllCall("wtsapi32\WTSFreeMemoryEx", "UInt", WTSTypeSessionInfoLevel1 := 2, "Ptr", pSessionInfo, "UInt", wtsSessionCount)
        }
        DllCall("FreeLibrary", "Ptr", wtsapi32)
    }
    Return Activa
}

5
  • Some text explaining this would make it a much better answer than it is now. Commented Oct 22, 2019 at 2:28
  • 1
    Because Microsoft sells Windows licenses for single user, it does not allow multiple sessions to run at the same time. When you connect to to a remote server, the server's running session ends and a new session starts, even if it is the same user. In this case at the end of the connection, you need a way to disconnect from the server without ending the server's running session.
    – gmoises
    Commented Oct 29, 2019 at 11:48
  • You misunderstood me. The answer itself needs to be improved with the additional information added to the answer itself through use of the EDIT button. Commented Oct 30, 2019 at 18:58
  • The code works very well, I use it everyday to close Remote Desktop connections, I do not feel the need to change the code, but you are encouraged to improve it to fit your specific requirements.
    – gmoises
    Commented Nov 6, 2019 at 20:11
  • Dude, you aren't reading what I'm writing. We don't need more code if your code works great. What we need is that the current "code lacking context" has some context attached to it. You click the little EDIT button, and then you add some EXPLANATION, not more code, not change what's there, just ADDING something to explain HOW you use this code in a solution. For instance, what the code is, how you implement it, how you run it to solve the problem presented. Commented Nov 7, 2019 at 4:39
0

This is an attempt to answer my own questions asked almost 2 years ago. I am still using RDP on a daily basis, and have spent more time reading about the tsdiscon command.

Shorter answer

First, let me answer the original question. According to its documentation, the tsdiscon command does take a range of parameters, including SessionName and SessionId. Issuing query session command through the command prompt shall reveal these two fields.

PS C:\WINDOWS\system32> query session
 SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
 services                                    0  Disc
>rdp-tcp#84        Your_Username             1  Active
 console                                     3  Conn
 rdp-tcp                                 65536  Listen

Up to an hour before typing up this answer, I have been confused by where should one issue the tsdiscon command: the original confusion in the question signifies a particular misunderstanding ==> the tsdiscon command is not supposed to be issued from a local machine when this local machine is a personal computer. This is more true when I am the single user of this local personal computer. I bet the intended usage of tsdiscon is for server admin to kick people off from their server :)


Still, I think it is worth the time to discuss how to properly get back from remote RDP sessions. For now, I am taking a AutoHotKey-approach that comes in two parts: 1. to get back from RDP session; and then 2. to kill the local session of RDP from the local machine.

Getting back temporarily from the remote RDP session

For now, I have devised the following shortcut to get me back from a RDP session. While keeping the identical script running both the local machine and the remote RDP-connected machine, pressing Ctrl + CapsLock (Ctrl first, then Capslock) shall "hide" the RDP session, and almost always restore keyboard focus + mouse focus back to the local machine.

; The following are AutoHotKey scripts.
#IfWinActive ahk_class TscShellContainerClass
    ^Capslock::
        Sleep 50
        WinMinimize
    return
#IfWinActive
; Make-shift script as suggested by: https://autohotkey.com/boards/viewtopic.php?t=25432
; May solve the awkward loss-of-focus when returning back from RDP
^Capslock::
    WinGetClass activeclass, A
    WinGetTitle activetitle, A
    MsgBox, 48, Warning, %activetitle% ahk_class %activeclass%, 0.666666
return

Simple solutions to "kill" RDP session(s)

Command Prompt/Powershell approach: terminate an active RDP session on the remote machine

As nicopowa has mentioned in this post, tscon.exe 1 /dest:console shall terminate an active RDP session on the remote machine and leave the remote machine unlocked.

One extension is to create an alias for WSL, as: alias rdp_stop='tscon.exe 1 /dest:console'. Then, calling rdp_stop on the console of WSL running on the remote computer shall shut down that specific RDP session.

Caveat: tscon.exe 1 /dest:console not only shuts down the RDP connection to the remote machine. It also "unlocks" the remote computer.

Solution: calling the tsdiscon.exe executable alone on the RDP-connected remote computer, instead, terminates the RDP session and leave the remote machine unlocked. Then, the alias can be:

alias rdp_stop_leave_remote_machine_unlocked='tscon.exe 1 /dest:console'
alias rdp_stop_keep_remote_machine_locked='tsdiscon.exe'

AHK approach: terminate (all) local RDP sessions

As the Ctrl + CapsLock shortcut should be working 99% of the times, I then simplify the task as: kill the existing RDP session. Again, AutoHotKey comes handy, as I may have multiple RDP sessions to different machines running, and I shall only need to kill one of them.

#+y:: 
    WinClose, <Session 1: name_of_the_saved_RDP_config_file> - Remote Desktop Connection
    WinClose, <Session 2: name_of_the_saved_RDP_config_file> - Remote Desktop Connection
return

One would need to carefully substitute the <Session 1...> portion of the AHK script. It needs to match the Window-Title of the RDP session when it is active. I usually look it up using the following procedures:

  1. Open an RDP session in a Window, i.e. without having it span all active monitors
  2. Open "Windows Spy", an AHK-utility that reveals all identifiers for a "window": full set of identifiers include Window-Title, process_name and win_class_name.

PS: during my weekly home(-code-)improvement session, I headed out to solve the tsdiscon issue again. With very similar query terms, I am happy to re-discover this old question. With a bit more careful reading of the documentation, it became apparent that I should not bet on one single command to handle all usages of mine. Thus come this pro-longed answer. Hope it can help people who play with RDP a lot.

0

After reading a few posts about this, I found a simple solution working on Windows 10.2004 x64 Professional

I needed a clean exit (unlock and restore remote session after RDP disconnect) with no on-screen console window, and no third-party programs, here it is :

create a exit_rdp.bat file on the remote machine (the one you want to control) and paste these lines inside :

@echo off
start /b "" %windir%\System32\tscon.exe 1 /dest:console
exit

Then :

  • right-click desktop
  • New > Shortcut
  • browse to bat file
  • next, finish

Finally :

  • right-click newly created shortcut
  • click "Properties"
  • click "Advanced" in "Shortcut" tab
  • check "Run as administrator"
  • Ok, Ok again

Connect via RDP, double-click the shortcut !

11
  • One clearification question: where is your bat file supposed to run? On the machine that you are remote-accessing, or on the machien that is used locally, for accessing the remote machine?
    – llinfeng
    Commented Jun 21, 2020 at 15:55
  • Also, I tried to run the bat file with admin privileges on my personal machine (Win 10, Version 2004). I ended up with a warning message that reads Windows cannot find 'C:\Windows\System32\tscon.exe'. Make sure you typed the name correctly, and then try again.. This message is identical regardless where the bat file is ran.
    – llinfeng
    Commented Jun 21, 2020 at 15:57
  • The bat file runs on the remote machine, i will edit my answer right away and check your problem with tscon.exe, are you running windows 10 pro ? i forgot to mention that.
    – nicopowa
    Commented Jun 22, 2020 at 16:04
  • also can you check from file explorer if tscon.exe exists in %SYSTEMROOT%\System32
    – nicopowa
    Commented Jun 22, 2020 at 16:19
  • I confirm that I do have tscon.exe in %SYSTEMROOT%\System32, and the remote machine that runs the bat-file is Win 10 Pro machine (thus available for RDP access). I also did create a shortcut for the bat file, and ran it as admin ==> same error message. I tried it on two remote Win 10 Pro machines, both failed after 2nd attempt. On one machine, the bat file ran per the first attempt (as admin). Not any longer.
    – llinfeng
    Commented Jun 22, 2020 at 17:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .