windows-remote-desktop-connection-doctor
Diagnose Windows App (Microsoft Remote Desktop / Azure Virtual Desktop / W365 / direct PC) connection issues on macOS. Analyze transport protocol selection (UDP Shortpath vs WebSocket), detect VPN/proxy interference, parse Windows App logs for Shortpath failures, and resolve stuck "Configuring remote PC..." dialogs caused by expired Microsoft accounts, server reboots, or client-side auth poisoning. Use when VDI connections are slow or stuck, when direct PC connections fail to connect, when transport shows WebSocket instead of UDP, when RDP Shortpath fails, or when Windows App is frozen at a progress dialog.
1How It Works & Mechanism
It first branches based on 'Connection Type' before gathering evidence: AVD/WVD/W365 uses the Connection Info panel + gateway health checks + Shortpath logs; direct PC connections lack this panel and require running an RDP protocol probe first to verify the server is alive, followed by inspecting client-side identity/logs. The branching dictates which evidence to collect next.
| Scenario | Key Characteristic | Primary Evidence Source | |---|---|---| | **AVD/WVD/W365** | User connects to a cloud desktop through a workspace/gateway | Connection Info panel, gateway health checks, UDP Shortpath logs | | **Direct PC** | User connects to a named PC by hostname or IP (e.g., a home workstation) | RDP protocol probe, Windows App log auth chain, Windows-side reboot events |
The core diagnostic method is parsing Windows App client logs—because transport negotiation details 'cannot be revealed by any network-layer test,' making this an irreplaceable source of information for this skill.
This is the most critical step. Windows App logs contain transport negotiation details that no network-level test can reveal.
The built-in probe script verifies server-side RDP stack health in a 'credential-free' manner: it sends an X.224 Connection Request + RDP_NEG_REQ, parses the RDP_NEG_RSP to determine the negotiated encryption protocol, and then upgrades the same socket to TLS.
RDP_NEG_REQ_PACKET = bytes.fromhex("030000130ee000000000000100080003000000")
The probe intentionally disables TLS certificate verification, explicitly explaining the rationale and limitations in documentation—the purpose is to verify that 'the server accepts TLS' rather than authenticating the server.
> **Note on TLS verification:** The probe intentionally disables certificate verification so it can confirm the RDP stack accepts TLS even with self-signed or domain-issued certificates. It does not authenticate the server. For a full certificate audit, use a separate TLS inspection tool.
Diagnostic discipline mandates determining 'client vs server first'—if the probe succeeds, the issue lies on the client side (authentication, application state, proxy, identity); if it fails, the issue is on the server or network. This turns binary attribution into an actionable pass/fail criterion.
A successful probe reports `RDP server <host>:<port> is reachable and healthy (RDP + TLS).` and means the problem is **client-side** (auth, app state, proxy, or identity). A failed probe means the problem is **server-side or network** (PC off, firewall, port unreachable, TLS interception).
It organizes six root causes into a verifiable evidence signature table (VPN/proxy interference, ISP UDP throttling, client health check failure, server Shortpath disabled, client identity/account poisoning, server reboot), providing a three-part 'Evidence → Fix → Verification' workflow for each.
#### Category E: Client Identity / Expired Microsoft Account Poisoning (Direct PC and AVD)
The verification phase requires behavioral evidence from the client rather than merely checking 'whether it can connect': input events must appear in the logs, and UserCancelled(8) must no longer occur.
2. The Windows App log for the new session should show mouse/keyboard input events (`IHAddMouseEventToPDU`, `IHAddKeyboardEventToPDU`) and no `UserCancelled(8)` errors.
2Core Capabilities
3External Dependencies
| Kind | Dependency / Endpoint |
|---|---|
| cli | python3(运行自带 RDP 探针与文档内联的 STUN 检测脚本) |
| cli | macOS 网络工具(ifconfig / netstat / scutil / lsof / ps / env / tailscale) |
| cli | grep(日志模式提取,文档给成套 grep -E 模板) |
| cli | ssh + PowerShell -EncodedCommand(经 WSL/Tailscale 到 Windows 主机取证) |
| cli | curl(读 ShadowRocket 配置 API 以核对代理状态) |
| api | Azure Virtual Desktop 网关/工作区端点(日志中出现,用于 DIRECT 规则配置示例) |
| network | STUN 公共服务器(测试 UDP 是否被 ISP 封锁) |
| network | 目标 RDP 主机(用户指定的 host:3389 或自定义端口,探针与日志分析的对象) |
| network | Windows 侧 RDP 主机(经 SSH/Tailscale 执行 PowerShell 取证) |
| network | Tailscale(netcheck/status,用于 NAT 类型与 UDP 能力评估) |
4Recommended Governance & Evidence
- scripts/: Executes local scripts or commands: python3 scripts/probe_rdp_server.py <host> [port] (bundled in repo; initiates TCP connection + RDP probe to target host)
- 网络端点: Declares external network access: TCP connection to user-specified RDP host:3389 (or custom port), sends RDP negotiation packets, and upgrades to TLS
5Second-Pass Verification
- [ok] 探针「无凭据」声明是否有反例(是否真的不读认证材料) — 反例检索失败:probe_rdp_server.py 的 import 为 socket/ssl/struct/sys/typing,无 os.environ/getenv、无 open()、无 keychain 调用;全流程是 create_connection → sendall(19 字节固定握手包) → recv → wrap_socket。凭据类 token 在全目录 grep(password/token/secret/api_key/credential)除文档描述「认证失败」的语义词外零命中。声明成立。
- [ok] TLS 校验关闭是否为「未交代的松绑」 — 已充分交代:代码内注释 'We intentionally disable verification because the diagnostic goal is to confirm the RDP stack accepts TLS, not to validate the certificate chain.';reference 内有专门引用块说明 'The probe intentionally disables certificate verification... It does not authenticate the server. For a full certificate audit, use a separate TLS inspection tool.'。属有意识、有边界说明的设计,不是隐藏降级。已记入 security.injection_surface 与 grade.reason。
- [ok] 是否漏记外发目标(域名/IP 全量回查) — 提取结果:stun.l.google.com:19302、stun1.l.google.com:19302、rdweb.wvd.microsoft.com、<prefix>-rdgateway-r1.wvd.microsoft.com、*.wvd.microsoft.com(DIRECT 规则示例)、13.104.0.0/14(IP-CIDR 示例)、<local-ip>:8080(ShadowRocket 配置 API)、<host>:3389(用户指定目标)。均已归入 external_deps 的 12 个条目;无未记载的隐蔽外发。
- [ok] frontmatter 的 allowed-tools 声明与文档行为是否一致 — 声明为 `allowed-tools: Read, Grep, Bash`。文档要求的动作是:读日志(Read/grep)、跑 ifconfig/netstat/scutil/lsof/tailscale/ps(Bash)、跑探针与内联 python(Bash)——全部落在声明范围内;且文档不要求写文件(无 chmod/编辑配置/生成文件的步骤,AVD 的 DIRECT 规则是以「给用户的修法」形式给出,由用户在代理工具里配置,而非本 skill 直接改配置)。声明与行为一致,且比同仓库的 tunnel-doctor(含 Edit)更保守。
- [ok] 是否有未实现的宣称(能力 vs 文档) — 逐项抽查成立:承诺「分析传输协议选择」→ 有 Connection Info 字段表与日志 FetchClientOptions/WebRTC 条目;承诺「解析日志找 Shortpath 失败」→ 有日志模式→含义对照表与 GUID 聚合方法;承诺「解决卡在 Configuring remote PC」→ 有 Category E 完整签名集与修复步骤;承诺「检测 VPN/代理干扰」→ 有 198.18.0.x、STUN/TURN routed through VPN 的判据与 DIRECT 规则修法。均有对应实现/流程支撑,未见夸大。
- [ok] 文档内部是否存在自相矛盾(如是否同时建议放宽校验又不建议) — 抽查与 tunnel-doctor 类似的张力点:本 skill 未出现 ssh -o StrictHostKeyChecking=no;唯一放宽是探针的 CERT_NONE,且被解释为「验证服务端接受 TLS」的必需手法并有明确局限说明。未发现未消解的内部矛盾。
- [ok] pin commit 与 skill.path 是否与任务书表格一致 — git rev-parse HEAD = d5c4678cb5d4fd6acc9c922690df035dbd33d247,与任务书表格『库内 HEAD』逐字一致;目录位于仓库根,相对路径 windows-remote-desktop-connection-doctor;本目录最后一次提交 878f947(2026-09-07)。GitHub API 复核 MIT / stars 1392 / pushed 2026-09-15T09:30:04Z。
- [ok] evidence 逐字性是否经磁盘级复核 — 【evidence 逐字性校正】首稿该条 quote 把源码第 152 行的 grep 模板截断且过度转义(把 `\|` 写成 `\\|`),导致未与源文逐字匹配;已改为源码中的真实前缀(截到 `"$LATEST_LOG"` 为止,避开后续含反斜杠的 `grep -v` 段)。该 grep 模板确实存在,结论未变,属引用精度问题。
6Verdict & Scenarios
c5c2e8a79a426da5…