启动器是一个 Tkinter 窗体程序。隐藏器是一个独立的 PowerShell 进程。启动器负责发现影刀环境、登录账号、提交任务和退出清理。隐藏器负责按固定条件隐藏影刀窗口,并在启动器退出后恢复窗口。一、启动客户端时,启动器设置两个显示开关开发者调用 subprocess.Popen 启动 ShadowBot.exe 时,启动器把 creationflags 设为

扫码分享
二维码

详细介绍

启动器是一个 Tkinter 窗体程序。隐藏器是一个独立的 PowerShell 进程。启动器负责发现影刀环境、登录账号、提交任务和退出清理。隐藏器负责按固定条件隐藏影刀窗口,并在启动器退出后恢复窗口。

一、启动客户端时,启动器设置两个显示开关

开发者调用 subprocess.Popen 启动 ShadowBot.exe 时,启动器把 creationflags 设为 CREATE_NO_WINDOW,把 startupinfo.wShowWindow 设为 SW_HIDE。

第一个开关阻止 Windows 创建控制台窗口。第二个开关告诉 Windows 用隐藏状态显示程序主窗口。启动器在 hidden_startupinfo() 里构造这个启动参数,在 ensure_client() 里使用它:

D:\J盘-杂料\业务资料\RPA项目资料\网站RPA应用市场\成品系统\影刀应用解码+打包+脱机运行\yingdao_run_hider\yingdao_launcher.py:261

ScreenShot_2026-10-20_095809_082.png

二、启动器用进程快照判断影刀是否在运行

开发者调用 process_running() 时,启动器用 CreateToolhelp32Snapshot 取得系统进程快照,再逐个比较 szExeFile。

启动器比较的目标名称是 ShadowBot.Shell.exe 和 ShadowBot.exe。启动器先把两个名称转成小写,再做完全匹配。代码没有解析 tasklist 的文本输出。

实现位置:yingdao_launcher.py:332

三、启动器先确认 CLI 能响应,再决定是否启动客户端

client_api_ready() 执行 shadowbot.shell-cli auth current。CLI 返回可解析的 JSON 后,启动器把本地接口视为可调用。

CLI 返回“没有启动”或 not running 时,启动器把接口视为不可调用。wait_for_api() 每 2 秒查询一次,最多等待 120 秒。

实现位置:yingdao_launcher.py:379

四、隐藏器用命名互斥量限制实例数量

隐藏器启动时创建命名互斥量 Local\ShadowBotWindowHiderV2,再调用 WaitOne(0) 抢锁。

第一个隐藏器拿到锁,开始隐藏窗口。第二个隐藏器拿不到锁,直接退出。Local\ 前缀把互斥量限制在当前登录会话内。

实现位置:D:\J盘-杂料\业务资料\RPA项目资料\网站RPA应用市场\成品系统\影刀应用解码+打包+脱机运行\yingdao_run_hider\hide_shadowbot_windows.ps1:2

ScreenShot_2026-10-20_093734_786.png

五、隐藏器监听启动器进程

启动器把自身 PID 写入环境变量 SHADOWBOT_HIDER_PARENT_PID。隐藏器读取这个 PID,每秒执行一次 Process.GetProcessById。

父进程消失后,隐藏器跳出循环,执行窗口恢复,再释放互斥量。启动器被强制结束,隐藏器也会自行退出。

实现位置:hide_shadowbot_windows.ps1:94

六、隐藏器直接调用 Win32 窗口 API

隐藏器用 Add-Type 编译一段 C# 代码,声明 user32.dll 的 EnumWindows、GetWindowText、ShowWindow、IsWindow 等函数。

PowerShell 负责循环,C# 负责枚举顶层窗口和调用 Win32 API。

每轮循环里,隐藏器先跳过不可见窗口,再根据窗口句柄取得 PID,再根据 PID 取得进程名。进程名不在 ShadowBot、ShadowBot.Shell、ShadowBot.Bridge、ShadowBot.Explorer 里时,隐藏器不动这个窗口。

进程名匹配后,隐藏器读取窗口标题,只接受 影刀、影刀登录、RobotRunnerView 三个标题。隐藏器调用 ShowWindow(h, 0) 隐藏窗口,把句柄和标题写进 HiddenWindows 字典,并向日志追加一行记录。

隐藏器用这份白名单排除了应用弹窗。我用带 影刀对话框 的测试应用跑了一遍,日志没有记录这个标题,弹窗一直可见。

实现位置:hide_shadowbot_windows.ps1:24

ScreenShot_2026-10-20_101017_041.png

七、退出时先停止隐藏器,再恢复窗口

启动器退出时,向 %TEMP%\shadowbot_window_hider.stop 写入内容。隐藏器每 50 毫秒检查一次这个文件,看到文件就退出循环。

隐藏器遍历 HiddenWindows。它先用 IsWindow 检查句柄,再核对窗口标题,最后调用 ShowWindow(h, 4) 恢复窗口。状态码 4 是 SW_SHOWNOACTIVATE,窗口显示出来,但不抢当前焦点。

启动器还调用 restore_visible_shadowbot_windows(),枚举标题为 影刀 或 影刀登录 的隐藏窗口,再调用 ShowWindow(h, 4)。这一步覆盖旧版隐藏器没有恢复逻辑的情况。

启动器把 app-run.auto-hide-robot-window 写成 false,启动时也复位一次。启动器不把自动隐藏状态留给下一次运行。

实现位置:yingdao_launcher.py:291、yingdao_launcher.py:1140、yingdao_launcher.py:1162

八、切换账号时,启动器重启影刀进程

ensure_login() 比较当前账号和目标账号。两个账号不同且用户填了密码时,启动器先清空应用列表,再调用 taskkill /F /T /IM ShadowBot.Shell.exe 和 taskkill /F /T /IM ShadowBot.exe。

启动器每 0.5 秒检查一次进程,最多等 20 秒。旧进程退出后,启动器隐藏启动新客户端,登录目标账号,再用 auth current 查一次实际登录用户名。

启动器用重启客户端的方式换会话。taskkill /F /T 会结束进程树,正在运行的任务会一起结束。

实现位置:yingdao_launcher.py:843

九、这个模块容易踩的坑

  • 窗口标题是硬编码字符串。影刀新版本修改标题后,开发者要同步修改 ClientWindowTitles。

  • 启动器用普通权限运行,影刀用管理员权限运行,Windows 会拒绝 taskkill。开发者要让两个进程使用相同权限级别。

  • 隐藏器每隔 1 秒才检查一次父进程。父进程退出后,隐藏器最多再等约 1 秒才退出。

  • 隐藏器只修改窗口显示状态,不结束影刀进程。启动器退出后,影刀后台仍在运行。

  • 隐藏器在恢复前核对窗口标题。开发者在恢复前修改窗口标题,隐藏器会跳过这个句柄。

  • 影刀客户端升级后,开发者要重新确认 ShadowBot.Shell.exe 的窗口标题和进程名。

十、我实际试过的部分

我运行了一个带弹窗的应用,观察 14 秒。隐藏器日志记录了 RobotRunnerView 和 影刀,没有记录 影刀对话框。

我启动一个只活 2 秒的临时父进程,再启动隔离的隐藏器实例。父进程退出 2 秒后,隐藏器进程消失。

我执行退出调用链的测试,调用顺序是:停止隐藏器、销毁窗体、把自动隐藏写成 false、等待隐藏器退出、恢复窗口。

我还没在每台机器、每个影刀版本上做肉眼恢复验证。开发者改完这个模块后,先拿一个带弹窗的应用跑一遍,再关闭启动器看主窗口是否回来。