Windows – How to properly quit from a Remote-Desktop Session? tsdiscon now logs the local user with priority

command lineremote desktopwindowswindows 10windows 7

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.

Best Answer

/*
    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
}

Related Question