我一直tsdiscon
很愉快地使用该命令断开远程桌面连接。我用此行创建了一个“bat 文件”,并为此功能分配了一个快捷方式。现在,我在使用命令时遇到了麻烦Windows 10機器。
旧用法
有了tsdiscon
,我可以在两种情况下愉快地退出 RDP 连接:
- 当我处于 RDP 会话中时,我将退出 RDP 会话
- 当我在本地计算机时,RDP 会话也将终止。然而,本地计算机不会发生任何事情
当前问题
最近,可能是由于 Windows 10 更新,在远程桌面会话中发出此命令不仅会从 RDP 会话中注销,还会从本地计算机中注销。这有点烦人。相应地,当我tsdiscon
在两种情况下发出命令时:
- 如果我处于 RDP 会话中,我不仅会从该远程会话中退出,还会从本地计算机中退出
- 如果我在本地机器上,我也将在两台机器上签字。
解决方案?
我可以传入想要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
命令确实需要一系列参数,包括SessionName
和SessionId
。query 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+ CapsLock(Ctrl首先,然后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 会话处于活动状态时的窗口标题相匹配。我通常使用以下步骤查找它:
- 在窗口中打开 RDP 会话,即不让它跨越所有活动监视器
- 打开“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连接,双击快捷方式!