目录

默认情况下,当用户没有在 Windows 上执行任何输入(没有鼠标键盘等的输入)并保持一定时间后,Windows 会自动切换到锁屏模式(或屏保模式),甚至待机。

一般情况下,这样不会有任何问题,而且也是推荐的设置(出于安全和节能的角度)。但是,如果这台电脑被用于进行一些自动化的测试,尤其是涉及到 UI 的交互操作时(比如,测试过程中用脚本操控鼠标来模拟点击一个按钮),这将会是个很大的问题:鼠标键盘失效了!UI 自动化做不下去了!

解决这个办法的方案很简单:设置 Windows 的电源模式,让 Windows 不要自动锁屏和待机,同时去掉屏保。

问题进阶

UI 自动化测试期间,执行测试的电脑几乎不能继续被他人使用(因为鼠标、键盘等输入设备此时需要响应自动化测试脚本)。为了不影响电脑使用,一般会搭一个虚拟机,然后远程连接到虚拟机里,在虚拟机里执行 UI 自动化测试。

但是,万一你不小心把某个远程桌面“最小化”了...Oops,你的自动化测试就挂了。既然连最小化都会失败,相信你已经猜到直接关闭远程桌面的后果了。

为什么最小化或关闭远程连接的桌面会导致自动化测试失败呢?也就是说为什么最小化或者关闭远程桌面连接后,程序不允许了?

概念介绍

要解释清楚这个原因,首先必须要了解3个在Windows 操作系统中的经常用到,却很陌生的对象:Session、Windows Station 和 Desktop。

Session

用户会话,每个登录操作系统的用户都会分配一个唯一的登录会话,用于标识该用户。操作系统(Vista 及以上)保留0号会话给一些系统服务及用户态的驱动使用,第1个登录系统的用户使用的 Session ID 为1,该用户执行的所有应用程序都在 Session 1 下执行。

我们可以打开任务管理器,切换到进程列表,然后在菜单->视图->选择列中,勾选 Session ID 列。

关闭远程桌面后如何使UI自动化程序仍处于可交互状态

如果有其它的用户登录到系统,就会看到 Session ID 大于1的情况,比如远程桌面。

Windows Station

Station 可以理解为工作站,它被认为是桌面和进程的安全边界。因此,每一个 session 都会包含多个 station,而每一个 Station 又包含1个或多个 Desktop。

但是,多个 Station 中,只有名字叫 Winsta0 的 Station 才是交互式的 station,也就是说只有它才能有 UI 并接受用户输入。所以每个 Session 都有一个叫 Winsta0 的用户进行交互。

Desktop

这里的 Desktop 并不是我们进入系统后所看到的那个蓝底的桌面(,我们看到的这个桌面,实际上只是一个窗口)。而是逻辑上的一个显示对象,它包含可显示的 GUI 对象(比如窗口、菜单、钩子等)。一般情况下,WinSta0 包含至少三个Desktop:登录(WinLogon)、屏保(ScreenSaver)和默认(Default,能看到所有应用程序的地方)。

同一时刻只能有一个Desktop处于激活状态(而能激活的 Desktop 只能属于 Winsta0)。用户还没登录的时候,登录桌面处于激活状态。登录之后,默认的Desktop处于激活状态。当达到系统电源设置的某个点的时候,系统切换到屏保,此时屏保Desktop处于激活状态。当用户按下 Ctrl + Alt + Delete 时,切换到登录Desktop,此时登录Desktop处于激活状态。

激活状态的Desktop才能接收用户输入,钩子才能获取其中的某个窗口消息。

如果你已经体验过 Win10,那应该就会知道 Win10 提供了很方便的创建多个Desktop的方式。

关闭远程桌面后如何使UI自动化程序仍处于可交互状态

原 因

了解了上述三个对象后,就可以比较容易的解释出现问题的原因了。

最小化

最小化会让远程桌面的会话切换到无图形界面的模式,这自然就无法继续接收鼠标、键盘的指令了。

关闭远程桌面

关闭远程桌面会让系统切换到登录Desktop的界面,而在该Desktop上并没有我们打开的其它窗口,因此会导致 UI 自动化测试失败。

解决办法

最小化

通过设置注册表的值可以阻止切换到无图形界面。

32位系统

找到 HKEY_LOCAL_MACHINE\Software\Microsoft\Terminal Server Client,创建一个 DWORD 类型的值,名字叫做RemoteDesktop_SuppressWhenMinimized,然后设置值为 2。

64位系统

找到 HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Terminal Server Client,然后和 32 位一样创建一个DWORD 类型的值,名字叫做 RemoteDesktop_SuppressWhenMinimized,并设置值为 2。

上面的改动会应用于整个机器,如果只想应用于当前的用户,把 LOCAL 替换成 USER。

关闭远程桌面

方案一

在远程桌面(被连接到的电脑)中先执行 query session 来查看当前登录到的 session,(远程桌面的 sessionName 都以 rdp-tcp 开头)。

关闭远程桌面后如何使UI自动化程序仍处于可交互状态

然后用管理员用户打开命令行工具,并执行 "tscon rdp-tcp#0 /dest:console",其中 rdp-tcp#0 为该该命令会关闭远程桌面的连接,然后把连接返回给远程的那台电脑(绕开登录过程)。这里的 console 只是一个 session 的名字,而这个名字的意思并非是 C# 中 “控制台” 的意思,而是指带有输入输出设备的机器,一般直接登录电脑的会话就是 console。

假设电脑A执行 mstsc 连接到电脑B(,连接成功后,电脑B黑屏),此时在电脑B上执行上述命令后(替换对应的session名字),电脑A中的远程连接窗口会被关闭,并提示远程连接会话已经终止。电脑B(假设运行在另一台物理机上)会恢复到已经登录的状态,如果需要重新让电脑B恢复锁屏状态,可以在电脑B上执行如下命令:

rundll32.exe user32.dll,LockWorkStation

方案二

假设用于跑 UI 自动化的机器是虚拟机A,现在再使用另一台虚拟机B作为中转。本机先连接到虚拟机B,然后再从虚拟机B连接到虚拟机A,最后断开虚拟机B。

关闭远程桌面后如何使UI自动化程序仍处于可交互状态

补充知识

有些时候,我们需要通过命令行的方式去连接并登录远程电脑,这个时候可以使用 mstsc 这个命令。
mstsc /v:目标机器的ip 或 机器名,比如 mstsc /v:192.168.1.188

但是,上述命令会跳出远程登录的窗口来输入用户名密码,如果不希望输入,则在执行 mstsc 之前先执行如下命令:
cmdkey /generic:termsrv/192.168.1.188 /user:xuchen /pass:Password1

另外,可以通过如下命令来查看当前系统中保存的账户信息:
cmdkey /list 查看所有账户信息
cmdkey /delete:targetname 来删除某一个账户 e.g. cmdkey /delete:termsrv/192.168.1.188

重复添加同一个 target 的多个账户信息,只保留最后一条信息。

参考链接