环境:Windows 11 + PowerShell 7.6.5 + Windows Terminal 1.24 症状:每次打开 PowerShell 7,要等十几秒才出现输入行 结论:国内网络下 ctldl.windowsupdate.com 不可达,导致证书信任列表(CTL)下载超时 15 秒。关闭根证书自动更新后彻底解决。


一、排查过程

1. 第一嫌疑人:PowerToys CommandNotFound 模块

打开 profile 检查(路径:C:\Users\<用户名>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1),发现里面有:

Import-Module -Name Microsoft.WinGet.CommandNotFound

实测数据:

场景耗时
pwsh -NoProfile -Command "exit"~0.3 秒
pwsh -Command "exit"(带 profile)~17 秒
冷启动单独导入该模块~58 秒

这是 PowerToys 的"命令未找到时建议 winget 安装"功能,启动时要读 winget 索引,是已知慢点。

处理:卸载 PowerToys、清空 profile、删除模块残留目录:

C:\Users\<用户名>\Documents\PowerShell\Modules\Microsoft.WinGet.CommandNotFound

注意:仅删 profile 里的 Import-Module 行即可解决启动慢,模块文件残留不影响速度。另外 PowerToys 卸载后注册表里可能仍有残留条目,属正常现象。

2. 第二嫌疑人:PSReadLine

清完之后依然要十几秒。进一步测试:

# 交互模式(会加载 PSReadLine)
"exit" | pwsh -NoLogo          # 15.4 秒

# 非交互模式
pwsh -NoProfile -Command "..."  # 0.3 秒

# 换其他模块对照
pwsh -NoProfile -Command "Import-Module CimCmdlets"  # 0.4 秒
pwsh -NoProfile -Command "Import-Module PSReadLine"  # 15.4 秒

规律非常明显:

  • 精确 15 秒,每次都是

  • 卡住期间 CPU 几乎无占用

  • 只有 PSReadLine 这类签名模块慢,普通模块不慢

  • Get-PSDrive 在新进程首次运行也同样卡 15 秒

这是典型的网络 I/O 超时特征,而不是计算慢。

3. 官方文档的线索

PowerShell 官方文档《Troubleshoot PowerShell startup issues》明确记载:

如果 pwsh -noninteractive 启动很快,那么问题很可能出在证书吊销列表(CRL)检查上。.NET 运行时在验证数字签名时会检查 CRL 以确保证书仍然有效。在断网环境下无法访问互联网上的 CRL,默认超时是 15 秒。

先按文档尝试了降低超时的注册表方案:

New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CertDllCreateCertificateChainEngine\Config" -Name "ChainUrlRetrievalTimeoutMilliseconds" -Value 1000 -PropertyType DWord -Force

没有效果——说明卡的不是 CRL 本身,而是别的东西。

4. 抓现场:卡住时进程在连谁

启动一个卡住的进程,第 6 秒时抓它的 TCP 连接:

$p = Start-Process pwsh -ArgumentList '-NoProfile','-Command','Import-Module PSReadLine' -PassThru -WindowStyle Hidden
Start-Sleep -Seconds 6
Get-NetTCPConnection -OwningProcess $p.Id

结果:

RemoteAddress   RemotePort  State
223.106.78.79   80          SynSent

进程在等一个永远握手不成功的 HTTP 连接。223.106.78.79 不是微软的 IP(DNS 污染返回的假地址)。

5. 定位域名

对比系统 DNS 和公共 DNS 的解析结果:

ctldl.windowsupdate.com
  系统DNS: 223.106.78.78, 223.87.195.118, 223.106.78.79   ← 假 IP
  阿里DNS: 117.68.54.123 ...                              ← 也连不上

真凶就是 ctldl.windowsupdate.com——Windows 下载"证书信任列表(Certificate Trust List)“的服务器。PowerShell 新进程做证书链验证时,Windows 会先去下载最新的 CTL,在你的网络环境下这个域名根本连不上,于是每次都卡满 15 秒超时。

换 DNS 也没用:两个渠道返回的 IP 实测均不可达。


二、最终修复

在管理员终端运行,关闭根证书自动更新:

New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot" -Force | Out-Null
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot" -Name "DisableRootAutoUpdate" -Value 1 -PropertyType DWord -Force

效果:启动从 15+ 秒恢复到 0.3 秒左右。

安全性说明:

  • 系统已内置的根证书全部继续有效,正常上网不受影响

  • 根证书也会随 Windows 大版本更新而刷新

  • 损失的只是"自动增量下载新根证书”,而这一步在你的网络下本来就从没成功过(一直在超时),等于零损失

附:之前改的 CRL 超时可以还原

Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CertDllCreateCertificateChainEngine\Config" -Name "ChainUrlRetrievalTimeoutMilliseconds"

三、以后想更新根证书怎么办

方法1:一键拉取全部根证书(推荐)

# 从 Windows Update 下载全部受信任根证书,打包成 roots.sst
certutil -generateSSTFromWU "$env:USERPROFILE\Desktop\roots.sst"

# 导入到本机根证书存储
Import-Certificate -FilePath "$env:USERPROFILE\Desktop\roots.sst" -CertStoreLocation Cert:\LocalMachine\Root

不受 DisableRootAutoUpdate 策略影响,是独立下载通道。

方法2:按需导入单个根证书

实际中只有访问某网站报证书错误时才需要。去对应 CA 官网下载根证书(.cer),然后:

Import-Certificate -FilePath "证书.cer" -CertStoreLocation Cert:\LocalMachine\Root

方法3:临时恢复自动更新

Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot" -Name DisableRootAutoUpdate -Value 0
certutil -pulse   # 立即触发一次
Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot" -Name DisableRootAutoUpdate -Value 1

提醒:以上方法本质都从 ctldl.windowsupdate.com / Windows Update 下载,在网络不畅时依然会超时。可行姿势:哪天挂了代理或换了网络环境,跑一次方法1一步到位。


四、排查方法论总结

  1. 先测基线:pwsh -NoProfile vs 带_profile,隔离 profile 因素
  2. 对照实验:换模块、换命令,找出"只有 X 慢"的规律
  3. 看数字特征:精确 15 秒 + 零 CPU = 网络/IO 超时,不是代码慢
  4. 抓现场:卡住瞬间用 Get-NetTCPConnection -OwningProcess 看它在连谁
  5. 查 DNS:系统 DNS vs 公共 DNS 对比,识别污染
  6. 善用官方文档:PowerShell 文档对启动慢有专门章节,“非交互快 = CRL 检查"这条经验直接命中方向

常用排查命令速查

# 对比有无 profile 的启动耗时
"无Profile: " + (Measure-Command { pwsh -NoProfile -Command "exit" }).TotalMilliseconds + " ms"
"有Profile: " + (Measure-Command { pwsh -Command "exit" }).TotalMilliseconds + " ms"

# 测模块加载耗时
Measure-Command { pwsh -NoProfile -Command "Import-Module PSReadLine" }

# 抓卡住进程的网络连接
Get-NetTCPConnection -OwningProcess <PID>

# 对比 DNS 解析
Resolve-DnsName ctldl.windowsupdate.com
Resolve-DnsName ctldl.windowsupdate.com -Server 223.5.5.5