简介RDPWrap-master.zip 是面向需要在 Windows 10 20H219042.630/660环境下启用远程桌面多用户并发连接的运维与测试人员的工具包主要用于缓解系统默认单会话限制并支持在系统版本变化后通过替换配置文件继续适配。压缩包共 71 个文件约 6.76MB包含 exe 可执行工具、ps1 脚本、bat 批处理、ini 配置、txt 说明及 dll 组件等覆盖安装、卸载、更新、检测与辅助配置等用途目录按功能分层便于按需取用。已有 679 人学习下载说明其在远程桌面多用户场景中具有一定参考价值与讨论基础。读者可获得较完整的工具集合、版本适配配置文件、批量脚本与检测程序并了解不同组件在部署和排错中的分工对需要做兼容性验证、环境复现和问题定位的读者也提供了可拆解研究的样本适合作为实验环境中的参考资料。1. 一台机器两个人用远程桌面先卡在哪白天要跑自动化脚本晚上要有人上去改配置。你在一台 Windows 测试机上开好远程桌面账号密码发过去结果第二个人一连上第一个人的桌面“啪”地断开。这就是 Windows 远程桌面默认只允许单会话带来的限制它不报错、不提示直接把前一个会话顶掉。很多人以为是网络断线其实是被会话仲裁机制踢下来的。RDPWrap-master.zip 解决的就是这一层。它不是远程控制软件也不是换个客户端而是在系统里挂一个补丁服务改掉 termsrv.dll 里的会话仲裁逻辑让远程桌面能同时挂多个会话同一个账号也能重复登录。适合手里有多台 Windows 测试机、办公机需要并发桌面会话的开发和运维不适合想把家庭版改成远程桌面主机的人那是另一条路装了也白装。2. 先看清它改了什么termsrv.dll、会话策略与三个开关2.1 单会话限制到底卡在哪一层Windows 的远程桌面从来不是“开”或“关”一个开关。它至少有三条链路同时在管注册表里的许可与策略、TermService 服务加上 termsrv.dll 里的会话仲裁、以及每个会话自己的窗口站和桌面对象。默认装完的系统HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server下面fSingleSessionPerUser是 1意思是同一个账号只允许存在一个会话MaxInstanceCount在部分版本里被压到 1连不同账号同时连都进不来。RDPWrap 的补丁正好分两块对应这两层。策略那一块它把“同用户重复登录就注销旧会话”的判定改掉更底层的那一块它往 termsrv.dll 的几个关键函数入口写跳转让多实例检查直接放行。前者用组策略和注册表也能蹭到一部分效果后者必须改进程内存里的代码这就是为什么它需要一个常驻服务、一个 DLL 和一个 ini 配置文件三件套。所以先用一句话给它定性它补的是会话仲裁逻辑不是网络层也不是账号权限。端口没放通、防火墙拦了 3389、账号没进 Remote Desktop Users 组这些问题它一个都解决不了。很多人装完发现“没效果”八成是方向从一开始就错了。还有一个必须提前说清的边界Windows 家庭版根本没有完整的远程桌面主机组件termsrv.dll 在但服务端能力被裁掉了。这类系统上装完配置工具会直接给红字不要在这上面耗时间。精简版、Ghost 版系统同理缺组件的情况比版本不匹配更难查。2.2 包里的文件各管什么解压出来是一堆 bat 加几个 exe、dll第一次看容易懵。按作用分一下动手时就不会乱文件作用什么时候动它install.bat注册服务、把 dll 和 ini 拷到安装目录首次部署uninstall.bat停服务、删文件、清残留升级系统前、彻底卸载时update.bat用当前目录的 ini 覆盖安装目录里的旧 ini换了新 ini 之后RDPWInst.exe上面三个脚本实际调用的命令行工具脚本里带参数调用rdpwrap.dll注入到 termsrv 进程里的补丁本体不单独动rdpwrap.ini每个系统版本对应的偏移量表版本不匹配时唯一要改的RDPConf.exe图形化状态面板每次部署完必看RDPCheck.exe本机回环连接测试排查连不上时用默认安装目录是C:\Program Files\RDP Wrapper服务名常见为RDPWrapper。这个目录和 ini 路径记牢后面 90% 的排错都是在这两个地方打转。安装脚本做的事其实很朴素把 dll 和 ini 拷过去注册一个开机自启的服务服务启动时把 dll 注入到 TermService 的进程空间里。卸载脚本反向做一遍但清理不彻底是常见问题后面单独讲。2.3 动手前先对齐版本这一步别省rdpwrap.ini是按 termsrv.dll 的版本号分节的版本对不上服务能起来、配置工具会告诉你 Not supported然后什么都没有。所以装之前先花三十秒查两件事系统内部版本号和当前 termsrv.dll 的文件版本。# 1) 系统版本ProductName 用来判断是不是家庭版CurrentBuild UBR 对应 ini 的节名 Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion | Select-Object ProductName, DisplayVersion, CurrentBuild, UBR # 2) termsrv.dll 的实际文件版本ini 里的节名就是它空格前那一段 (Get-Item $env:SystemRoot\System32\termsrv.dll).VersionInfo | Select-Object FileVersion, ProductVersion # 3) 看包里自带的 ini 覆盖到哪个版本只列 10.0 开头的节取最后几条 Select-String -Path .\rdpwrap.ini -Pattern ^\[10\. | Select-Object -Last 10FileVersion的格式通常是10.0.19041.1348 (WinBuild.xxxxxxx)这样节名只取空格前的10.0.19041.1348。第 3 条命令列出来的最后一个节就是这个 ini 支持的最新版本。如果你机器的版本比它新那就别急着装先去补 ini 节或者换一份更新的 ini否则装完只能看着红字发呆。这里有个血泪经验Windows 的功能更新和月度补丁都会动 termsrv.dll版本号一变原来能用的 ini 立刻失效。包里自带的 ini 往往是打包那一刻的状态遇到比较新的系统基本都要补。判断依据就一句话——先查版本再决定装不装、补不补。3. 装上去并跑通从 install.bat 到第一条并发会话3.1 安装与第一次状态自检解压到一个不带中文、不带空格的路径比如C:\Tools\RDPWrap然后以管理员身份开命令行。带空格的路径在 bat 里最容易翻车要么加引号要么干脆别用。:: 进入解压目录/d 保证跨盘符也能切过去 cd /d C:\Tools\RDPWrap :: 执行安装脚本内部会调用 RDPWInst.exe -i install.bat :: 确认服务注册成功并在运行服务名常见为 RDPWrapper sc query RDPWrapper :: 顺手确认远程桌面主服务是自启状态 sc qc TermServiceinstall.bat正常跑完不会输出太多东西别指望它告诉你成功。真正的验收标准是打开RDPConf.exe看面板上那三行状态Wrapper state、Service state、Listener state。三行全绿才算装对任何一行红字都对应一个明确的排查方向——第一行红是 ini 没匹配上你的版本第二行红是服务没起来或被拦截第三行红是监听没建立通常是注册表策略或端口的问题。如果安装时报“拒绝访问”八成是没提权。右键“以管理员身份运行”不是可选项这个工具全程要写C:\Program Files和注册表UAC 拦下来是必然的。3.2 把策略和端口这两道门打开服务跑起来只是补丁生效了远程桌面本身还得准许连入。这一段用 PowerShell 一次做完比在组策略界面里点来点去快也方便你在多台机器上重复。# 远程桌面总开关0 表示允许连入 $ts HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server Set-ItemProperty -Path $ts -Name fDenyTSConnections -Value 0 # 关键允许同一用户存在多个会话这是并发的前提 Set-ItemProperty -Path $ts -Name fSingleSessionPerUser -Value 0 # 实例数上限0xFFFFFFFF 等于不额外限制实际还受系统版本约束 New-ItemProperty -Path $ts -Name MaxInstanceCount -Value 0xFFFFFFFF -PropertyType DWord -Force # 主服务设为自启并重启让策略立即生效 Set-Service -Name TermService -StartupType Automatic Restart-Service -Name TermService -ForcefSingleSessionPerUser这一条最容易被忽略。RDPWrap 的补丁解决的是 dll 层的拦截但策略层如果还写着“每用户单会话”系统照样会在第二个连接进来时把第一个踢掉表现就是“补丁好像没生效”。两个层面都得改缺一不可。端口这边防火墙默认规则有时候是关的尤其在服务器版上被安全基线改过之后。加一条显式入站规则最稳妥# 放通 3389/TCP 入站-Profile 限定作用域按需改成 Domain,Private New-NetFirewallRule -DisplayName RDP-3389-In -Direction Inbound -Protocol TCP -LocalPort 3389 -Action Allow -Profile Domain,Private # 确认监听确实在 3389 上 Get-NetTCPConnection -LocalPort 3389 -State Listen | Select-Object LocalAddress, LocalPort, OwningProcessOwningProcess那列应该指向 svchost 里的 TermService。如果这条命令查不到任何监听先别怀疑补丁回去看 Listener state 和fDenyTSConnections。端口没监听的情况下任何客户端都连不上这跟 RDPWrap 一点关系都没有。3.3 验证并发是不是真的成立装完不验证等于没装。最直接的验证是两台机器同时连同一台目标机、同一个账号。但在跑过去试之前本机上就能看出很多信息# 列出当前所有会话包括监听态和已连接态 qwinsta # 只看已登录用户第二个人连上后这里应该出现两条同账号记录 quser # 查看会话管理器日志连接、断开、重连都有事件 ID Get-WinEvent -LogName Microsoft-Windows-TerminalServices-LocalSessionManager/Operational -MaxEvents 30 | Select-Object TimeCreated, Id, Messageqwinsta输出里console是本机物理会话rdp-tcp#0、rdp-tcp#1是远程会话。第二个人连进来之后如果出现两条rdp-tcp#记录且第一条还是 Active说明并发成立如果第一条变成了 Disc 或者直接消失说明补丁没完全生效回去检查第 2 章里说的策略和 ini 匹配。事件日志那边登录成功一般对应事件 ID 21断开是 24重连是 25。排错时按时间点对一下比瞎猜快得多。我的习惯是装完先自己连一次、断一次、再连一次把这三条事件确认一遍再去叫第二个人来测——不然两个人在那儿互相踢谁也说不清是配置问题还是操作问题。4. rdpwrap.ini 才是命门版本节结构、对齐与维护4.1 一个版本节里到底写了什么ini 的结构很直白一个方括号包住的版本号下面挂一组开关和偏移量。以 x64 系统为例一个完整的节大概长这样; 节名必须和 termsrv.dll 的 FileVersion 前三到四段完全一致 [10.0.19041.1348] ; 是否打“仅本地连接”补丁1 为启用 LocalOnlyPatch.x641 LocalOnlyOffset.x640x9D2A1 LocalOnlyCode.x64jmpsc ; 单选会话补丁让同一用户能重复登录 SingleUserPatch.x641 SingleUserOffset.x640x3494C SingleUserCode.x64Zero ; 策略补丁改写会话策略查询的返回值 DefPolicyPatch.x641 DefPolicyOffset.x640x4C5A1 DefPolicyCode.x64CDefPolicy_Query_eax_rcx ; 会话初始化钩子负责在进程启动早期接管 SLInitHook.x641 SLInitHookOffset.x640x2A5F0上面这些偏移量只是演示字段结构不同版本的值完全不同照着抄会直接把服务搞崩。真正要理解的是字段语义*.x641是开关*.Offset.x64是函数在 dll 里的地址偏移*.Code.x64是打进去的指令形态jmpsc是跳转Zero是清零。每个版本编译出来的 dll 布局都不一样所以必须一节一版本不能复用。注意手写偏移量是这整套东西里风险最高的操作。偏移错了轻则服务启动失败重则 termsrv.dll 加载异常导致远程桌面彻底不可用。社区维护的 ini 之所以存在就是因为这件事靠人肉试错成本太高。4.2 系统更新后版本对不上怎么快速定位Windows 每次打补丁都可能换 termsrv.dll 版本表现出来就是昨天还好好的今天变成 Not supported。与其一个个去翻 ini不如写个几行的自检脚本把“当前版本”和“ini 是否覆盖”直接对出来# 取当前 termsrv.dll 版本去掉括号里的构建信息 $fileVer (Get-Item $env:SystemRoot\System32\termsrv.dll).VersionInfo.FileVersion $key ($fileVer -split )[0] # ini 的实际路径装完默认在这里 $ini C:\Program Files\RDP Wrapper\rdpwrap.ini # 精确匹配节名避免 19041.1 匹配到 19041.1348 这种前缀误判 if (Select-String -Path $ini -Pattern ^\[$([regex]::Escape($key))\] -Quiet) { OK: ini 已包含 $key问题不在版本匹配 } else { MISS: ini 缺少 $key需要补节或替换 ini 后执行 update.bat } # 顺手确认服务加载的是哪份 ini避免改错文件 Get-CimInstance Win32_Service -Filter NameRDPWrapper | Select-Object Name, State, StartMode, PathName脚本里[regex]::Escape不能省。版本号里的点如果不转义在正则里会变成“任意字符”10.0.19041.1能匹配上10x0x19041x1这种根本不存在的东西给你一个假的 OK。这种细节不注意排错方向就跑偏了。PathName那一条也是给自己留的后路。有些机器上装过旧版服务指向的还是老目录你在新目录里改半天 ini 一点用没有。确认服务实际加载路径比什么都实在。4.3 换 ini 的正确姿势先备份再覆盖最后重启拿到新版 ini 之后别直接拖进安装目录覆盖。正确顺序是备份、覆盖、重启服务、看状态四步一步都不能少$dir C:\Program Files\RDP Wrapper $stamp Get-Date -Format yyyyMMdd-HHmmss # 1) 备份现有 ini带时间戳回滚时直接拿回来 Copy-Item $dir\rdpwrap.ini $dir\rdpwrap.ini.$stamp.bak -Force # 2) 覆盖为目录里的新版 ini Copy-Item C:\Tools\RDPWrap\rdpwrap.ini $dir\rdpwrap.ini -Force # 3) 重启服务让新表生效-Force 会连带停掉依赖项 Restart-Service -Name RDPWrapper -Force # 4) 确认服务状态随后打开 RDPConf.exe 看三行是否全绿 Get-Service -Name RDPWrapper | Select-Object Name, Status, StartType备份这一步别嫌烦。ini 覆盖错了回滚只要把.bak改回来再重启一次不备份的话遇到服务起不来又找不到旧文件就只能重装。带时间戳的命名还有个好处连着调几版之后你能一眼看出哪一版是最后能用的。如果重启服务直接报错退出先看 ini 有没有语法问题——方括号、等号、大小写手工编辑最容易在这三处出岔子。可以把 ini 里你新增的节整段注释掉再启动一次确认是不是新节的问题这个二分法比逐行读快得多。5. 排查清单五种“服务在跑但就是不对”的真实情况5.1 状态面板红字Not supported 与 服务起不来现象RDPConf.exe第一行 Wrapper state 显示Not supportedService state 倒是 Running。原因ini 里没有当前 termsrv.dll 版本对应的节。常见于系统刚打完月度补丁版本号往上跳了一小段。补丁服务的代码本身没问题它只是找不到该往哪儿打。解决跑 4.2 那段脚本确认版本号和节名缺失就补节或换 ini然后update.bat加重启服务。补完再看面板第一行应该变成Installed。现象Service state 是 Stopped手动sc start RDPWrapper报错 1053 或 1067。原因多半是 ini 里某个偏移量写坏了服务一启动就往非法地址写进程直接被打死。少数情况是杀毒软件把rdpwrap.dll隔离了服务找不到文件。解决先看 ini 最近改过什么把新增节注释掉重启验证再确认安装目录里 dll 还在。如果 dll 被隔离把安装目录加进白名单后重新执行install.bat。5.2 Listener 不绿与并发仍然踢人现象前两行都绿Listener state 显示Not listening。原因fDenyTSConnections被组策略或安全基线改回 1或者 3389 被别的进程占用、监听绑到了别的地址上。这个状态跟补丁无关是远程桌面本身没开。解决按 3.2 重新写一遍注册表并重启 TermService然后用Get-NetTCPConnection -LocalPort 3389 -State Listen确认监听。查不到监听就去看 TermService 的启动日志别在 ini 上继续折腾。现象第二个人连上来第一个人还是被踢下线面板却全绿。原因fSingleSessionPerUser没改成 0或者组策略里的会话限制覆盖了注册表。策略层的“每用户一个会话”优先级比 dll 补丁高补丁放行了也没用。解决改注册表后必须重启 TermService 而不是只重启 RDPWrapper两者加载时机不一样。改完用quser看是否出现两条同账号记录来确认。5.3 系统更新与残留最容易被忽略的两件事现象某次 Windows 更新之后远程桌面直接连不上重装 RDPWrap 也报错。原因更新程序要替换 termsrv.dll但旧的rdpwrap.dll还挂在 TermService 进程里文件被占用更新装了一半失败系统处于半残状态。解决做大版本更新前先uninstall.bat更新完再装回来。已经踩坑的进安全模式执行卸载脚本确认C:\Program Files\RDP Wrapper目录删干净再让系统把更新补完。现象卸载后重装配置工具显示的还是旧的版本信息。原因卸载脚本删了服务但没删干净 ini 和 dll重装时安装程序检测到目录已存在跳过了文件拷贝。解决手动确认安装目录被清空注册表里HKLM\SYSTEM\CurrentControlSet\Services\RDPWrapper也已移除然后再执行install.bat。残留问题在反复装卸的机器上特别常见。6. 把它当生产组件看一轮压测和一个回滚习惯装通只是起点。真要在团队里用我一般会做一轮最简单的压测找三到四台机器同一账号、同一时间连同一台目标机连上去之后各自跑一点占用 CPU 的操作观察 15 分钟。看两个指标——qwinsta里的会话数是否稳定不掉以及目标机的内存和句柄增长是否收敛。桌面会话本身不轻每个会话都有自己的桌面合成和一堆后台进程四五个并发在低配机器上会明显卡顿这个上限跟补丁无关是硬件的事。下面这段可以当成日常巡检脚本扔到计划任务里每天跑一次出问题比等人报障早$ini C:\Program Files\RDP Wrapper\rdpwrap.ini $key ((Get-Item $env:SystemRoot\System32\termsrv.dll).VersionInfo.FileVersion -split )[0] $svc Get-Service -Name RDPWrapper -ErrorAction SilentlyContinue $lst Get-NetTCPConnection -LocalPort 3389 -State Listen -ErrorAction SilentlyContinue [pscustomobject]{ DllVersion $key IniMatched [bool](Select-String -Path $ini -Pattern ^\[$([regex]::Escape($key))\] -Quiet) SvcState if ($svc) { $svc.Status } else { Missing } Listening [bool]$lst Sessions (quser 2$null | Measure-Object -Line).Lines - 1 } | Format-List输出里IniMatched为 False 就该提前准备新 ini 了不用等到下一次重启才发现Sessions那一项减 1 是去掉表头行数字突然归零说明会话被清空值得翻一下事件日志。还有个习惯每台机器上线前把当前能用的 ini 和 dll 打包存一份到配置库命名带上系统版本号。听起来很土但系统更新把补丁打废的时候你能在五分钟内回滚到已知可用状态比在论坛里翻半天快得多。我自己吃过一次亏——手上五台机器更新后全挂只有一台因为之前备份过 ini十分钟恢复剩下四台耗了一整个下午。从那以后我每次改 ini 之前都强制走一遍备份加时间戳一次没落下。包里的文件按第 3 章的顺序走一遍先在测试机上验证quser能出两条同账号记录再推到其他机器上顺序别反。希望帮到你。本文还有配套的精品资源点击获取