Windows 开启 SSH 与远程桌面完整指南:OpenSSH、WSL、家庭版 RDPWrap 与 Remmina 调优
十月 09, 2026 更新: 十月 10, 2026 [网络与安全] #Windows 与 WSL #SSH #RDP #安全 #网络摘要:本文整理 Windows 开启 SSH Server 的两种方式(原生 OpenSSH Server 与 WSL),覆盖 ICMP 放行、账户密码与 PIN、公钥认证、默认 Shell、提权与密码修改;后半部分是家庭版开启远程桌面的 RDPWrap 实战——Smart App Control 拦截排查、从安装器中雕刻提取 rdpwrap.dll 手工安装、版本匹配与长期维护,以及 Linux 下 Remmina 客户端黑背景/清晰度问题的完整调优。
1. 方案一:Windows 原生 OpenSSH Server
从 Windows 10 1809 和 Windows 11 开始,系统内置了 OpenSSH 服务器组件,无需额外下载安装包。
1.1 检查是否已安装
以管理员身份打开 PowerShell:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*'
如果 OpenSSH.Server 的 State 显示为 Installed,说明已安装;否则需要手动安装。
1.2 安装 OpenSSH 服务器
图形界面方式:
- 打开「设置」→「应用」→「可选功能」。
- 点击「添加可选功能」。
- 搜索并安装 OpenSSH 服务器。
PowerShell 方式:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
1.3 启动服务并设置开机自启
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
验证服务状态:
Get-Service sshd
Status 显示 Running 即表示 SSH 服务已正常运行。
1.4 防火墙规则:重复创建报错怎么办
安装 OpenSSH Server 时,系统通常会自动创建名为 OpenSSH-Server-In-TCP 的防火墙规则,允许 TCP 22 端口入站。
如果你手动执行:
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
可能会遇到:
New-NetFirewallRule : 当文件已存在时,无法创建该文件。
Windows System Error 183
这说明规则已经存在,不需要重复创建。正确做法是先查看:
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' | Select-Object Name, DisplayName, Enabled, Direction, Action
如果 Enabled 为 True,说明规则已生效。
如果规则存在但未启用,使用:
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True
如果确实需要重建:
Remove-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'
New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
1.5 放行 ICMP,让外部可以 ping 通
开启 SSH 与允许 ICMP ping 是两个独立操作,可以同时进行。
Windows 防火墙默认会阻止 ICMP 回显请求。以管理员身份运行:
netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow
也可以使用 PowerShell:
New-NetFirewallRule -DisplayName "Allow ICMPv4-In" -Protocol ICMPv4 -IcmpType 8 -Direction Inbound -Action Allow
安全提示:允许 ICMP 会增加主机在网络上被发现的概率。公共网络环境下建议限定来源 IP,或谨慎开启。
2. 方案二:WSL 中开启 SSH Server
WSL 本质上是运行在 Windows 中的 Linux 子系统,可以在其中安装 Linux 版 OpenSSH Server。适合需要 Linux 开发环境的场景,但需要处理端口转发和 WSL IP 变动问题。
2.1 在 WSL 中安装 OpenSSH Server
进入 WSL 终端,例如 Ubuntu:
sudo apt update
sudo apt install openssh-server -y
2.2 修改 SSH 配置
编辑配置文件:
sudo nano /etc/ssh/sshd_config
建议修改:
Port 2222
ListenAddress 0.0.0.0
PasswordAuthentication yes
PermitRootLogin no
说明:
Port 2222:避免与 Windows 原生 SSH 的 22 端口冲突。ListenAddress 0.0.0.0:允许所有网络接口连接。PasswordAuthentication yes:启用密码登录。PermitRootLogin no:禁用 root 直接登录。
2.3 启用 systemd 并启动 SSH
在 WSL 中创建或编辑 /etc/wsl.conf:
[boot]
systemd=true
重启 WSL 后执行:
sudo systemctl enable ssh
sudo systemctl start ssh
如果 systemctl 不可用,可以临时使用:
sudo service ssh start
2.4 Windows 主机端口转发
WSL2 使用虚拟网络,外部设备无法直接访问 WSL 内部 IP,需要将 Windows 主机端口转发到 WSL。
先获取 WSL IP:
hostname -I
然后在管理员 PowerShell 中执行:
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=2222 connectaddress=172.23.22.69 connectport=2222
将 172.23.22.69 替换为实际 WSL IP。
添加防火墙入站规则:
netsh advfirewall firewall add rule name="WSL2 SSH" dir=in action=allow protocol=TCP localport=2222
局域网设备可通过以下方式连接:
ssh 用户名@Windows主机IP -p 2222
注意:portproxy 指向的是 WSL IP,而 WSL 每次重启后 IP 可能变化,转发会失效。需要重新执行 netsh interface portproxy delete v4tov4 listenport=2222 再添加,或改用下面的镜像网络模式。
2.5 WSL 镜像网络模式
如果希望 WSL 与 Windows 共享同一 IP,可以启用镜像网络模式。
编辑或创建 C:\Users\你的用户名\.wslconfig:
[wsl2]
networkingMode=mirrored
保存后执行:
wsl --shutdown
重启 WSL 后生效。启用镜像模式后,再结合 Windows 防火墙规则,外部访问 WSL 会更直接,也省去了 portproxy 的 IP 变动问题。
3. 登录用户名和密码是什么
Windows OpenSSH Server 本身没有独立的用户名和密码,它直接使用 Windows 系统账户凭据。
3.1 用户名
用户名就是当前 Windows 账户名,可通过以下命令查看:
whoami
输出通常形如:
desktop-abc123\peter
SSH 登录时可以使用:
ssh peter@Windows主机IP
3.2 密码
密码取决于账户类型:
- 本地账户:密码就是你设置的 Windows 登录密码。
- Microsoft 账户:必须使用 Microsoft 账户的在线密码,而不是 PIN 码。
如果你在电脑上只设置了 PIN,需要回想或重置 Microsoft 账户的完整密码。
3.3 只知道用户名和 PIN 怎么办
SSH 不支持 PIN 码。可以尝试以下方案。
方案一:关闭"仅允许 Windows Hello 登录"
- 打开「设置」→「账户」→「登录选项」。
- 找到「为了提高安全性,仅允许对此设备上的 Microsoft 账户使用 Windows Hello 登录」。
- 将其关闭。
- 锁定电脑再重新解锁。
- 重启 sshd 服务:
Restart-Service sshd
然后设置或使用 Microsoft 账户在线密码登录 SSH。
方案二:配置 SSH 公钥认证
使用密钥登录,不再依赖账户密码。见下一节。
方案三:创建专用本地用户
net user peter YourPassword123 /add
该用户默认属于 Users 标准用户组,不具备管理员权限。
如果只是 SSH 登录并执行普通命令,标准用户足够。若需要管理员权限,可加入 Administrators:
net localgroup Administrators peter /add
注意:Remote Management Users 组主要面向 WinRM,不是管理员组,也不是 SSH 必需。
4. SSH 公钥认证
长期使用密码登录不够安全,更推荐 SSH 公钥认证。
4.1 在客户端生成密钥
在客户端机器执行:
ssh-keygen -t ed25519
默认会生成:
~/.ssh/id_ed25519
~/.ssh/id_ed25519.pub
4.2 部署公钥到 Windows
普通用户:
C:\Users\用户名\.ssh\authorized_keys
管理员用户:
C:\ProgramData\ssh\administrators_authorized_keys
如果用户属于 Administrators 组,公钥必须放到 administrators_authorized_keys,否则可能无法登录。这是 sshd_config 默认末尾的 Match Group Administrators 段决定的行为。
4.3 命令行添加管理员公钥
在管理员 PowerShell 中执行:
$path = "C:\ProgramData\ssh\administrators_authorized_keys"
$key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@client"
# 确保目录存在
$dir = Split-Path $path
if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
# 如果文件不存在则创建
if (!(Test-Path $path)) { New-Item -Path $path -ItemType File -Force | Out-Null }
# 检查公钥是否已存在,不存在则追加
if (-not (Select-String -Path $path -Pattern ([regex]::Escape($key)) -Quiet)) {
Add-Content -Path $path -Value $key -Encoding Ascii
Write-Host "公钥已添加。"
} else {
Write-Host "公钥已存在,未重复添加。"
}
# 设置严格权限:移除继承,只允许 SYSTEM 和 Administrators 完全控制
icacls $path /inheritance:r
icacls $path /grant '*S-1-5-18:F'
icacls $path /grant '*S-1-5-32-544:F'
# 显示最终内容和权限
Write-Host "`n--- 文件内容 ---"
Get-Content $path
Write-Host "`n--- 文件权限 ---"
icacls $path
说明:*S-1-5-18 是 SYSTEM,*S-1-5-32-544 是 Administrators,用 SID 授权可避免中文系统上组名不一致的问题。权限过宽(如继承了 Users 组读取)会导致 sshd 拒绝使用该公钥文件。
4.4 验证
Get-Content C:\ProgramData\ssh\administrators_authorized_keys
icacls C:\ProgramData\ssh\administrators_authorized_keys
确认内容正确,且权限列表中只有 SYSTEM 和 Administrators。
客户端登录:
ssh -i 私钥路径 用户名@Windows主机IP
无需重启 sshd,OpenSSH 每次认证都会重新读取该文件。
5. 默认进入 cmd 还是 PowerShell
Windows OpenSSH Server 默认进入的是 cmd.exe,不是 PowerShell。
5.1 如何查看当前 Shell
看提示符:
- cmd:
C:\Users\peter> - PowerShell:
PS C:\Users\peter>
在 cmd 中验证:
echo %COMSPEC%
输出 C:\Windows\System32\cmd.exe 说明当前是 cmd。
在 PowerShell 中验证:
$PSVersionTable
能输出版本表格就是 PowerShell。
5.2 修改默认 Shell 为 PowerShell
以管理员身份运行:
if (!(Test-Path "HKLM:\SOFTWARE\OpenSSH")) {
New-Item -Path "HKLM:\SOFTWARE\OpenSSH" -Force
}
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
重启 sshd:
Restart-Service sshd
重新 SSH 登录后,提示符会变成:
PS C:\Users\peter>
如果安装了 PowerShell 7,也可以改为:
C:\Program Files\PowerShell\7\pwsh.exe
临时切换:在 cmd 中输入 powershell 回车即可进入 PowerShell,输入 exit 退回 cmd。
5.3 实战坑:reg add 写了却不生效
实测一台 Windows 11 25H2(OpenSSH 9.5p2)上,用 reg add 写入 DefaultShell 后 net stop sshd && net start sshd,新会话仍然进 cmd;换成 PowerShell 原生方式一次性生效:
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell `
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" `
-PropertyType String -Force
Restart-Service sshd -Force
排查思路备忘:
- 确认新连接命中的是哪个监听进程:
netstat -ano | findstr :22,再对照Get-CimInstance Win32_Process -Filter "Name='sshd.exe'"的CreationDate,确认服务真的重启过; - 验证 shell 是否切换成功,用一条只有 PowerShell 认识的命令最干净:
ssh 用户名@主机 'Write-Output PS-OK'——报「不是内部或外部命令」说明还是 cmd; - SSH 会话是否拿到管理员完整令牌:
whoami /groups | findstr S-1-16-,S-1-16-12288(High)才是提权令牌,net session能跑通同样说明有完整管理员权限。
另一个高频痛点:默认 shell 是 cmd 时,从 Linux 远程执行复杂命令会陷入引号转义地狱(%、"、嵌套引号层层剥皮,curl -w "%{http_code}" 这类必然阵亡)。最稳的办法是根本不让 cmd 解析——本地把 PowerShell 脚本编码后用 -EncodedCommand 传输,全程无引号:
# 本地任意复杂的 PowerShell,base64(UTF-16LE) 后传给远端
ENC=$(printf '%s' 'Get-Service sshd | Select-Object Status,Name | Out-String' \
| iconv -t UTF-16LE | base64 -w0)
ssh 用户名@Windows主机IP "powershell -EncodedCommand $ENC"
顺带的编码坑:中文系统 cmd 远程输出是 GBK,curl -w 的 %% 转义在不同层级表现不一致,timeout /t 在 SSH 无 stdin 时直接报错不可用于延时(改用 Start-Sleep)。
6. 提权:sudo、runas 与专用管理员账户
6.1 Windows 11 内置 sudo
Windows 11 24H2 及以上版本支持原生 sudo。
打开「设置」→「系统」→「开发者选项」,启用 sudo。
之后可以:
sudo netstat -ab
注意:该功能依赖 UAC 弹窗,在纯 SSH 会话中可能无法正常工作。
6.2 使用 runas
runas 是 Windows 传统提权命令,兼容所有版本。
runas /user:Administrator "powershell.exe"
执行后会提示输入 Administrator 密码。
注意事项:
runas会启动一个新进程,当前 SSH 会话仍然是原用户。- 新启动的窗口在 SSH 中可能不可见,输出不一定回显到当前终端。
- 每次都需要交互输入密码,不适合自动化。
- 新进程的工作目录可能变成
C:\Windows\System32。
如果内置 Administrator 账户未启用,需要先启用并设置密码:
net user administrator /active:yes
net user administrator YourStrongPassword
用完建议禁用:
net user administrator /active:no
6.3 更推荐:专用管理员 SSH 账户
创建一个专用本地管理员账户:
net user SSHAdmin YourStrongPassword /add
net localgroup Administrators SSHAdmin /add
然后为 SSHAdmin 配置 SSH 公钥,公钥放到:
C:\ProgramData\ssh\administrators_authorized_keys
之后直接登录:
ssh SSHAdmin@Windows主机IP
这种方式登录后的会话通常具备管理员权限,不需要再 runas 或 sudo,更适合长期 SSH 管理。
7. 本地账户与 Microsoft 在线账户
7.1 两者的区别
| 类型 | 凭据存储位置 | 验证方式 |
|---|---|---|
| Microsoft 账户 | 微软在线服务器 | 需要在线密码 |
| 本地账户 | 本机 SAM 数据库 | 本地密码验证 |
Windows OpenSSH Server 验证的是本地账户数据库。使用 Microsoft 账户登录 Windows 时,系统会在后台创建一个对应的本地用户,但密码验证会转发到微软服务器。因此 SSH 登录 Microsoft 账户时,需要的是在线密码,而不是 PIN。
7.2 需求对照
| 需求 | 是否需要 Microsoft 在线账户 |
|---|---|
| SSH 远程管理,使用本地管理员 | 不需要 |
| 桌面日常登录,使用 PIN / Windows Hello | 需要,保持现状即可 |
| 用原 Microsoft 账户 SSH 登录 | 需要在线密码,或改用公钥认证 |
结论:
- 有了本地管理员账户后,SSH 管理完全不需要 Microsoft 在线账户。
- Microsoft 账户可以继续用于桌面登录和同步。
- 两者互不冲突。
- 如果长期 SSH 管理,建议创建专用本地管理员账户,而不是长期启用内置 Administrator。
7.3 在线账户丢失是否影响本地用户
不影响。 即使 Microsoft 在线账户丢失、无法登录、密码遗忘或被注销,本地用户仍然可以正常使用,包括:
- 在 Windows 登录界面本地登录。
- 通过 SSH 远程登录。
- 运行程序、访问文件。
- 如果属于 Administrators 组,拥有管理员权限。
原因:本地账户的用户名和密码哈希存储在本地 SAM 数据库中,验证完全在本地完成,不依赖微软服务器。
7.4 需要注意的例外情况
虽然本地账户本身不受影响,但以下事项可能和 Microsoft 账户有关:
-
BitLocker 恢复密钥 如果系统盘启用了 BitLocker,恢复密钥可能备份在 Microsoft 账户中。在线账户丢失可能导致无法获取恢复密钥。但这影响的是磁盘解锁,不是本地账户本身。
查看本机是否开启了 BitLocker(需管理员,家庭版对应的是「设备加密」,机制相同):
Get-BitLockerVolume | Select-Object MountPoint,VolumeStatus,ProtectionStatus,EncryptionPercentage,EncryptionMethod实测一台 Windows 11 家庭中文版的输出(两块盘全部加密、TPM 就绪):
MountPoint VolumeStatus ProtectionStatus EncryptionPercentage EncryptionMethod ---------- -------------- ----------------- -------------------- --------------- C: FullyEncrypted On 100 XtsAes128 D: FullyEncrypted On 100 XtsAes128VolumeStatus = FullyEncrypted且ProtectionStatus = On即已启用。顺手确认 TPM 状态:Get-Tpm,TpmReady为True才能无感解锁。查看恢复密钥保护器(只看 ID,不在终端展开密钥本身):
(Get-BitLockerVolume -MountPoint C:).KeyProtector | Where-Object KeyProtectorType -eq RecoveryPassword | Select-Object KeyProtectorId有
RecoveryPassword条目说明恢复密钥存在。务必确认它至少有一份离线备份:登录aka.ms/myrecoverykey查看 Microsoft 账户中的托管记录,同时建议额外保存一份(打印或密码管理器)——恢复密钥是 48 位数字,主板/TPM 变更、系统重装后是唯一解锁手段,且不要把密钥明文贴到任何终端输出或笔记里。读出恢复密钥本身(管理员权限,SSH / RDP / 本地终端均可):
# 方式一:PowerShell 原生 (Get-BitLockerVolume -MountPoint C:).KeyProtector | Where-Object KeyProtectorType -eq RecoveryPassword | Select-Object KeyProtectorId, RecoveryPassword # 方式二:老牌工具,ID + 密钥分栏显示,更直观 manage-bde -protectors -get C:注意事项:
- 每个卷有独立的恢复密钥,多块盘要逐卷执行(把
C:换成D:等)。 - 本机读出的与
aka.ms/myrecoverykey里托管的是同一把——两边核对:微软账户里查不到,则本机这份就是唯一副本,立即存入密码管理器。 - 密钥属于敏感凭据:在自己的终端里查看、存好后清掉终端回滚缓冲;不要让 AI 助手 / 聊天工具代查(密钥会进入会话记录,与密码不进任何中间上下文是同一条原则)。
若怀疑密钥已泄露,可以轮换(生成全新随机恢复密钥,旧的可后续用
Remove-BitLockerKeyProtector删除):Add-BitLockerKeyProtector -MountPoint C: -RecoveryPasswordProtector - 每个卷有独立的恢复密钥,多块盘要逐卷执行(把
-
原 Microsoft 账户的用户配置文件 原在线账户对应的
C:\Users\原用户名文件夹仍然存在,但本地新用户默认无权访问。如需里面的文件,需要手动修改权限或取得所有权。 -
OneDrive / Microsoft 服务 原账户关联的 OneDrive、Office 订阅、Edge 同步等会失效,但这些与本地登录和 SSH 无关。
-
密码重置盘 / PIN 本地用户默认没有 PIN,只能用密码登录。请务必记住密码。
-
组策略限制 极少数情况下,域策略或本地安全策略可能限制某些用户本地登录。默认情况下,Users 和 Administrators 组都有"允许本地登录"权限。
7.5 建议
- 给本地管理员账户设置强密码并妥善保存。
- 不要删除或禁用这个本地账户。
- 如果担心忘记密码,可以提前创建密码重置盘。
- 本地账户和 Microsoft 账户可以共存,互不干扰。
8. 修改用户密码
8.1 使用 net user
net user 用户名 新密码
示例:
net user peter NewP@ssw0rd2026!
交互式输入,避免密码出现在命令历史中:
net user peter *
系统会提示输入两次密码。
8.2 使用 PowerShell Set-LocalUser
Set-LocalUser -Name "peter" -Password (ConvertTo-SecureString "NewP@ssw0rd2026!" -AsPlainText -Force)
8.3 图形界面
如果能通过远程桌面连接:
- 按
Win + R,输入control。 - 进入「用户账户」→「管理其他账户」。
- 选择目标账户,点击「更改密码」。
注意:
- 执行修改密码命令的 SSH 会话必须具备管理员权限。
- 修改后立即生效,无需重启系统或 sshd。
- 新密码需要符合系统密码策略要求。
9. 常见问题排查
9.1 New-NetFirewallRule 报错"文件已存在"
说明规则已经存在。使用:
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'
Set-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -Enabled True
9.2 SSH 登录提示 Permission denied
可能原因:
- 使用的是 PIN,而不是账户密码。
- Microsoft 账户需要在线密码。
- 公钥未放到正确位置。
- 管理员公钥未放到
administrators_authorized_keys。 - 文件权限不正确。
查看日志:
C:\ProgramData\ssh\logs\sshd.log
9.3 只知道 PIN,不知道密码
- 关闭"仅允许 Windows Hello 登录"。
- 设置或重置 Microsoft 账户在线密码。
- 或改用 SSH 公钥认证。
- 或创建专用本地用户。
9.4 sudo 被禁用
Windows 默认禁用 sudo。可在 Windows 11 开发者设置中启用,但 SSH 中可能受 UAC 限制。更推荐使用专用管理员 SSH 账户。
9.5 登录后默认是 cmd
这是正常默认行为。可通过注册表 DefaultShell 改为 PowerShell。
9.6 错误代码 1326
常见于 Microsoft 账户认证失败。可尝试关闭 Windows Hello 限制,或改用本地账户 / 公钥认证。
9.7 本地用户能否正常本地登录
可以。使用 net user 创建的本地用户默认属于 Users 组,拥有本地登录权限。在登录界面选择"其他用户",输入 电脑名\用户名 和密码即可登录。
9.8 Microsoft 账户丢失后本地用户还能用吗
可以。本地账户的验证完全在本地进行,不依赖 Microsoft 在线账户。在线账户丢失不影响本地用户的本地登录、SSH 登录和日常使用。
10. 安全建议
- 优先使用 SSH 公钥认证,而不是密码登录。
- 不要长期启用内置 Administrator 账户。
- 创建专用本地管理员账户用于 SSH 管理。
- 公钥文件权限必须严格限制。
- ICMP 放行尽量限定来源 IP。
- 定期查看 SSH 日志。
- 公网环境不要直接暴露 22 端口,建议修改端口或使用跳板机、VPN。
- 定期更新 Windows 和 OpenSSH。
- 为本地管理员账户设置强密码,并妥善保存。
- 如果使用 BitLocker,确保恢复密钥有额外备份,不要只依赖 Microsoft 账户。
11. 进阶:家庭版开启远程桌面(RDPWrap 实战)
需求:Windows 11 家庭中文版(EditionID = CoreCountrySpecific)通过 SSH 开启原生 RDP 服务。家庭版没有 RDP 服务端,这是功能阉割不是配置问题——TermService 服务存在、fDenyTSConnections 注册表键也在,但 termsrv 有版本许可检查,改键也拒绝连接。免升级的专业版解锁方案是 RDPWrap。
11.1 版本判定:看 termsrv.dll 而不是 OS build
RDPWrap 按 termsrv.dll 的文件版本精确匹配 ini 配置段,这里有两个极易踩的坑:
- OS build ≠ dll build。Windows 11 25H2 是 enablement 升级包,OS 显示 26200.x,但很多系统 DLL 仍在 26100 分支。实测一台 25H2(OS 26200.9168)的
termsrv.dll是 10.0.26100.8972——按 OS build 去搜 ini 会搜错方向。 - 字符串属性会骗人。
(Get-Item C:\Windows\System32\termsrv.dll).VersionInfo.FileVersion返回的字符串显示 26100.8875,但FileVersionRaw才是 RDPWrap 实际读取的字段:
(Get-Item C:\Windows\System32\termsrv.dll).VersionInfo.FileVersionRaw
# Major Minor Build Revision
# 10 0 26100 8972
ini 用社区持续维护的版本(官方 stascorp 仓库 2017 年就停更了):
https://github.com/sebaxakerhtc/rdpwrap.ini
下载后 Select-String -Pattern '^\[10\.0\.26100\.8972\]' 确认有对应段再继续。
11.2 Smart App Control:Win11 24H2+ 的头号拦路虎
新版 Windows 出厂默认开启 SAC 评估模式(VerifiedAndReputablePolicyState=1),基于云信誉拦截"无签名无信誉"的程序。RDPWInst.exe(2017 年的老 exe)必被拦,症状很有迷惑性:
- cmd 执行报「系统无法执行指定的程序」,无任何其他输出;
[System.Reflection.Assembly]::LoadFile()也失败,HRESULT0x800711CD(应用程序控制策略阻止)——不只是拦进程执行;- 连 DLL 加载都拦:把
TermService的ServiceDll换成未签名的 rdpwrap.dll 后,服务 7023 报「应用程序控制策略阻止了文件」。
实锤方法看 CodeIntegrity 事件日志:
Get-WinEvent -LogName 'Microsoft-Windows-CodeIntegrity/Operational' -MaxEvents 10
# 事件 3077/3033: ... did not meet the Enterprise signing level requirements
# 事件 3118: Smart App Control Block
排除法过程记录(都不是原因,但值得知道):MOTW 标记(Get-Item -Stream Zone.Identifier,curl.exe 下载的文件本来就不带)、Defender 拦截(Get-MpThreat 为空)、WOW64(32 位 exe,SysWOW64\cmd.exe 能跑)、.NET 运行库(4.8 在、3.5 也装了)。
关闭 SAC(不可逆,重装系统才能再开,个人桌面跑社区工具的标准前提):
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy' `
-Name VerifiedAndReputablePolicyState -Value 0 -Type DWord
Restart-Computer -Force # 必须重启才生效,重启服务无效
11.3 关 SAC 前的安装路径:从安装器里"雕刻"出 rdpwrap.dll
若不想关 SAC,还有一条硬核路:SAC 拦的是执行,纯文件读写不受影响。而官方安装包 RDPWInst.exe 里以资源形式内嵌了所有需要的 DLL(rdpwrap.dll x86/x64、rdpclip、RfxVmt.dll),可以用 PowerShell 按 PE 结构手工提取(官方 GitHub Release 是 RDPWrap-v1.6.2.zip,注意老的 RDPWInst.zip 路径已 404):
$b = [IO.File]::ReadAllBytes('C:\Users\你的用户名\rdpwrap\RDPWInst.exe')
# 扫描所有内嵌 PE:MZ 头 + e_lfanew 指向合法 PE 签名
for ($i = 0; $i -lt $b.Length - 0x40; $i++) {
if ($b[$i] -eq 0x4D -and $b[$i+1] -eq 0x5A) {
$pe = [BitConverter]::ToInt32($b, $i + 0x3C)
if ($pe -gt 0 -and $pe -lt 0x400 -and
$b[$i+$pe] -eq 0x50 -and $b[$i+$pe+1] -eq 0x45) {
$mach = [BitConverter]::ToUInt16($b, $i + $pe + 4)
Write-Output ("MZ at {0} machine 0x{1:X4}" -f $i, $mach)
}
}
}
得到一串内嵌 PE 后按特征识别:machine=0x8664 的是 x64,其中内含 ServiceMain 字样、无 .pdb 调试路径的 116736 字节那块就是 rdpwrap.dll(有 rdpclip.pdb/RfxVmt.pdb 字样的是另两个)。按节表计算精确长度后 carve 出来:
$dll = New-Object byte[] 116736
[Array]::Copy($b, 1271752, $dll, 0, 116736) # 偏移/长度按实际扫描结果
[IO.File]::WriteAllBytes('C:\Users\你的用户名\rdpwrap\rdpwrap.dll', $dll)
家庭版通常还缺 RfxVmt.dll(远程会话虚拟通道组件,同样内嵌在安装器里),一并提取备用。
11.4 手工安装:注册表接线五步
关掉 SAC 并重启后(或直接用官方安装器 RDPWInst.exe -i,把新 ini 放同目录),手工安装的完整步骤:
# 1. 就位文件(ini 与 dll 同目录)
New-Item -Path 'C:\Program Files\RDP Wrapper' -ItemType Directory -Force
Copy-Item C:\Users\你的用户名\rdpwrap\rdpwrap.dll 'C:\Program Files\RDP Wrapper\'
Copy-Item C:\Users\你的用户名\rdpwrap\rdpwrap.ini 'C:\Program Files\RDP Wrapper\'
Copy-Item C:\Users\你的用户名\rdpwrap\RfxVmt.dll C:\Windows\System32\ # 家庭版缺失时
# 2. 核心接线:让 svchost 加载 rdpwrap.dll(它再加载真 termsrv.dll 并内存补丁)
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\TermService\Parameters' `
-Name ServiceDll -Value 'C:\Program Files\RDP Wrapper\rdpwrap.dll'
# 3. 放开远程连接总开关
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' `
-Name fDenyTSConnections -Value 0
# 4. 防火墙放行 3389(TCP+UDP)
netsh advfirewall firewall add rule name="RDPWrap-TCP-3389" dir=in action=allow protocol=TCP localport=3389
netsh advfirewall firewall add rule name="RDPWrap-UDP-3389" dir=in action=allow protocol=UDP localport=3389
# 5. 重启服务并验证
Restart-Service TermService -Force
netstat -ano | Select-String ':3389' # 应看到 LISTENING
端到端验证技巧:不需要真实登录,用假凭据做一次 NLA 探测——错误码是 LOGON_FAILURE(凭据被真实校验后拒绝)就证明 TLS + CredSSP + 补丁后的 termsrv 全链路正常;若是协议层错误才是真有问题:
xfreerdp /v:Windows主机IP /u:probe /p:wrong /cert:ignore /auth-only /sec:nla
# ERRCONNECT_LOGON_FAILURE = 栈正常,只是密码错 —— 预期结果
11.5 长期维护与卸载
- Windows 大更新后 RDP 突然连不上:多半是累积更新把
termsrv.dll升到了 ini 没有的修订号。重新下载 sebaxakerhtc 的最新 ini 覆盖C:\Program Files\RDP Wrapper\rdpwrap.ini,Restart-Service TermService即可。先查FileVersionRaw再去 ini 里找段,思路同 11.1。 - 卸载:把
ServiceDll改回C:\Windows\System32\termsrv.dll,删除C:\Program Files\RDP Wrapper\,重启 TermService。 - 会话机制备忘:RDP 登录创建的是独立会话,远程接入时物理屏幕会切到锁屏(控制台用户的程序仍在后台,断开远程后解锁即回)。想「远程操作、本地围观」的同屏演示场景,RDP 做不到(shadow 模式 Linux 客户端基本无支持),需要 RustDesk 这类共享同一会话的远程协助工具。
12. Linux 客户端(Remmina)黑背景与清晰度调优
RDP 通了只是第一步,Remmina 默认配置连上去是「小窗 + 黑背景 + 文字发虚」三连,对应 profile(~/.local/share/remmina/xxx.remmina)里三个键:
| 症状 | 原因 | 修复 |
|---|---|---|
| 窗口很小 | resolution_mode=2 + 宽高为 0,回落到默认小分辨率 | resolution_mode=1(跟随本地屏幕)或设固定宽高 |
| 背景纯黑 | quality=0(Poor 档)为省带宽主动剥离壁纸/主题 | quality=9(Best 档) |
| 文字发虚 | colordepth=99 是 GFX AVC444(H.264 有损编码) | colordepth=32(无损位图)+ viewmode=4(全屏 1:1) |
改完必须断开重连(RDP 分辨率与编码在会话建立时协商)。全屏模式下鼠标移到屏幕顶部中央可呼出 Remmina 工具条退出全屏。
12.1 如何客观验证「到底糊不糊」
感知不可靠时,两个可复现的诊断手段:
一、从客户端往 RDP 会话内注入探针——SSH 在 Windows 上跑的进程不在 RDP 会话里,直接查分辨率查到的是控制台会话;用交互式计划任务把探针打进会话内部:
# 探针脚本: 输出会话 ID、屏幕尺寸、DPI、ClearType 状态
schtasks /create /tn RDPProbe /it /ru 用户名 /sc once /st 23:59 `
/tr 'powershell -ExecutionPolicy Bypass -File C:\Users\用户名\probe.ps1' /f
schtasks /run /tn RDPProbe
Get-Content C:\Users\用户名\probe.txt
# probe.ps1 内容
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
"session: $([Diagnostics.Process]::GetCurrentProcess().SessionId)"
[System.Windows.Forms.Screen]::AllScreens | % { "screen: $($_.Bounds)" }
$g = [Drawing.Graphics]::FromHwnd([IntPtr]::Zero); "dpi: $($g.DpiX)"
"fontSmoothing: $((Get-ItemProperty 'HKCU:\Control Panel\Desktop').FontSmoothing)"
预期健康值:屏幕尺寸 = 本地显示器原生分辨率、dpi 96(100%)、fontSmoothing 2(ClearType 开)。
二、截图做像素级测量——GNOME Wayland 下 CLI 截屏走 xdg-desktop-portal(org.freedesktop.portal.Desktop 的 Screenshot 方法,会弹一次用户确认框),拿到截屏后用拉普拉斯高频能量对比、并检查 1px UI 线(任务栏边缘)是否仍是单像素:若 80% 强边缘保持 1px 锐利,说明客户端无缩放重采样、像素严格对齐,模糊感另有原因。
12.2 调优上限的诚实结论
无损 32bpp + 分辨率 1:1 + 全屏 + ClearType 开启,就是 RDP 的画质上限。剩余与「坐在实体机前」的观感差来自远端会话光栅化时的次像素排列/伽马假设与物理面板出厂校准不一致,属于协议固有特性(微软官方 mstsc 客户端同样存在),无法靠配置消除。缓解手段:在远程会话里 Win+R 运行 cttune 重新走一遍 ClearType 调谐向导。
13. 总结
Windows 开启 SSH Server 有两条主要路线:
- Windows 原生 OpenSSH Server:安装简单,适合远程管理 Windows 本身。
- WSL 中开启 SSH Server:适合 Linux 开发环境,但需要处理端口转发和网络模式。
远程桌面方面:
- 专业版直接开系统设置里的远程桌面即可;家庭版没有 RDP 服务端,RDPWrap 可以免升级解锁,但 Win11 24H2+ 需先关闭 Smart App Control(不可逆),且 Windows 大更新后可能需要更新 ini。
- Linux 客户端 Remmina 的推荐配置:
resolution_mode=1(跟随本地屏幕)、quality=9(恢复壁纸)、colordepth=32(无损)、viewmode=4(全屏 1:1)。无损 + 1:1 已是 RDP 画质上限,剩余观感差是协议固有特性。 - 需要「远程操作、本地围观」的同屏演示场景,RDP 会话隔离机制做不了,用 RustDesk 类工具。
登录时,SSH 使用的是 Windows 账户凭据:
- 本地账户:使用本地密码。
- Microsoft 账户:使用在线密码,不是 PIN。
- 管理员账户:公钥要放到
administrators_authorized_keys。
如果长期使用 SSH 管理,推荐:
- 创建专用本地管理员账户。
- 配置 SSH 公钥认证。
- 修改默认 Shell 为 PowerShell。
- 按需放行 ICMP。
- 禁用内置 Administrator,降低风险。
本地账户与 Microsoft 在线账户相互独立。即使在线账户丢失,本地用户仍然可以正常本地登录和 SSH 登录。建议保留一个可用的本地管理员账户作为备用,并妥善保管密码。