如何正确退出远程桌面会话?tsdiscon 现在优先记录本地用户

如何正确退出远程桌面会话?tsdiscon 现在优先记录本地用户

我一直tsdiscon很愉快地使用该命令断开远程桌面连接。我用此行创建了一个“bat 文件”,并为此功能分配了一个快捷方式。现在,我在使用命令时遇到了麻烦Windows 10機器。

旧用法

有了tsdiscon,我可以在两种情况下愉快地退出 RDP 连接:

  1. 当我处于 RDP 会话中时,我将退出 RDP 会话
  2. 当我在本地计算机时,RDP 会话也将终止。然而,本地计算机不会发生任何事情

当前问题

最近,可能是由于 Windows 10 更新,在远程桌面会话中发出此命令不仅会从 RDP 会话中注销,还会从本地计算机中注销。这有点烦人。相应地,当我tsdiscon在两种情况下发出命令时:

  1. 如果我处于 RDP 会话中,我不仅会从该远程会话中退出,还会从本地计算机中退出
  2. 如果我在本地机器上,我也将在两台机器上签字。

解决方案?

我可以传入想要tsdiscon终止的特定会话名称吗?或者,是否应该有一个特定的参数来规定此命令在哪个范围内生效?

到目前为止,相同的命令(tsdiscon)仍然以相同的方式运行Windows 7的机器。当我开始使用Windows 10机器启动远程桌面会话。

答案1

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

答案2

这是为了回答我近 2 年前提出的问题。我仍然每天使用 RDP,并且花了更多时间阅读有关tsdiscon命令

简短答案

首先,让我回答一下最初的问题。根据其文档,该tsdiscon命令确实需要一系列参数,包括SessionNameSessionIdquery session通过命令提示符发出命令将显示这两个字段。

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

在打出这个答案之前的一个小时里,我一直对应该在哪里发出tsdiscon命令感到困惑:问题中最初的困惑表明了一种特殊的误解 ==>tsdiscon当本地计算机是个人计算机时,该命令不应该从本地计算机发出。当我是这台本地个人计算机的唯一用户时,情况更是如此。我敢打赌,它的预期用途tsdiscon是服务器管理员将人们从他们的服务器上踢出去 :)


不过,我认为值得花时间讨论如何正确地从远程 RDP 会话中返回。目前,我采用 AutoHotKey 方法,该方法分为两个部分:1. 从 RDP 会话中返回;然后 2. 从本地计算机终止 RDP 的本地会话。

从远程 RDP 会话暂时返回

目前,我设计了以下快捷方式来从 RDP 会话中返回。在保持本地计算机和远程 RDP 连接的计算机上运行相同的脚本的同时,按Ctrl+ CapsLockCtrl首先,然后Capslock)将“隐藏”RDP 会话,并且几乎总是将键盘焦点+鼠标焦点恢复到本地机器。

; 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

“终止” RDP 会话的简单解决方案

命令提示符/Powershell 方法:终止远程计算机上的活动 RDP 会话

作为尼科波瓦提到过这个帖子tscon.exe 1 /dest:console应终止远程计算机上的活动 RDP 会话并使远程机器保持解锁状态

一种扩展是创建 WSL 的别名,如:alias rdp_stop='tscon.exe 1 /dest:console'。然后,调用rdp_stop远程计算机上运行的 WSL 的控制台将关闭该特定的 RDP 会话。

警告:tscon.exe 1 /dest:console不仅会关闭与远程计算机的 RDP 连接,还会“解锁”远程计算机。

解决方案:tsdiscon.exe在 RDP 连接的远程计算机上单独调用可执行文件,而是终止 RDP 会话并使远程计算机保持解锁状态。然后,别名可以是:

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

AHK 方法:终止(所有)本地 RDP 会话

由于Ctrl+CapsLock快捷键 99% 的时间都应该有效,因此我将任务简化如下:终止现有的 RDP 会话。再次强调,AutoHotKey 非常方便,因为我可能在不同的机器上运行多个 RDP 会话,而我只需要终止其中一个。

#+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

需要小心地替换<Session 1...>AHK 脚本的一部分。它需要与 RDP 会话处于活动状态时的窗口标题相匹配。我通常使用以下步骤查找它:

  1. 在窗口中打开 RDP 会话,即不让它跨越所有活动监视器
  2. 打开“Windows Spy”,这是一个 AHK 实用程序,可显示“窗口”的所有标识符:完整的标识符集包括 Window-Title、process_name 和 win_class_name。

PS:在我每周家居(-代码-)改进会话,我再次出发解决这个tsdiscon问题。查询条件非常相似,我很高兴重新发现这个老问题。仔细阅读文档后,我意识到我不应该指望一个命令来处理我的所有用法。因此,我得到了这个冗长的答案。希望它能帮助经常使用 RDP 的人。

答案3

在阅读了一些关于此问题的帖子后,我找到了一个适用于 Windows 10.2004 x64 Professional 的简单解决方案

我需要一个干净的退出(在 RDP 断开连接后解锁并恢复远程会话),没有屏幕控制台窗口,也没有第三方程序,这里是:

在远程计算机(您想要控制的计算机)上创建一个 exit_rdp.bat 文件,并将以下行粘贴到其中:

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

然后 :

  • 右键单击桌面
  • 新建 > 快捷方式
  • 浏览到bat文件
  • 下一步,完成

最后 :

  • 右键单击新创建的快捷方式
  • 点击“属性”
  • 点击“快捷方式”选项卡中的“高级”
  • 勾选“以管理员身份运行”
  • 好的,再好一次

通过RDP连接,双击快捷方式!

相关内容