PowerShell 7.6.6 中文环境启动崩溃:一行中文文案引发的 FailFast
TL;DR:这不是 Windows 更新,也不是配置损坏。是 PowerShell 自带的”有新版本”提醒在简体中文(zh-Hans)下的格式化 bug:代码假设提醒文案第一行最长,中文文案第三行反而最长,算出
PadRight(-17),.NET 直接抛异常,进程在启动阶段FailFast。7.6.7 发布满 7 天后提醒到期,所以某天早上突然开始崩。临时修复:设置用户环境变量
POWERSHELL_UPDATECHECK=Off,关闭更新提醒。升级到 7.6.7 不能解决。
一、现象
环境:Windows 11 + PowerShell 7.6.6(系统 UI 语言为简体中文)。
某天开始,打开 PowerShell 7 直接进程终止,窗口只剩一段报错:
1 | PowerShell 7.6.6 |
特征:
- 每次启动都崩,根本进不了交互界面
-NoProfile无效- 换个窗口宽度、最大化、改字体都无效
- 用脚本方式调用(输出重定向、非交互)反而不崩
二、先排除 Windows 更新
一开始最容易怀疑的是 Windows Update 动了 PowerShell,但可以很快排除:
| 检查项 | 结果 |
|---|---|
C:\Program Files\PowerShell\7\ 文件日期 |
9 月 2 日(构建时间),未变 |
注册表 HKLM\SOFTWARE\Microsoft\PowerShellCore\InstalledVersions |
7.6.6 |
| Windows Update 是否分发 PowerShell 7 | 否,PowerShell 7 是独立 MSI 安装 |
也就是说,安装本身没被动过。问题出在”运行时的某个条件”上。
三、定位:崩溃发生在更新提醒的格式化代码里
调用栈已经点名了函数:
1 | Microsoft.PowerShell.UpdatesNotification.ShowUpdateNotification |
对应源码(v7.6.6):
src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs
关键片段:
1 | // The first line is longest, if the message changes, this needs to be updated |
注释写得很清楚:**”第一行是最长的”**。这段代码靠这个假设来计算后两行要补多少空格,让三行对齐。
问题是,这个假设在简体中文资源里不成立。
zh-Hans 的稳定版提醒文案:
1 | {1} 有新的 PowerShell 稳定版本可用: v{0} {2} |
三行长度:
| 行 | 内容 | 长度 |
|---|---|---|
| 第 1 行 | {1} 有新的 PowerShell 稳定版本可用: v{0} {2} |
37 |
| 第 2 行 | {1} 立即升级,或访问发布页:{3}{2} |
24 |
| 第 3 行 | {1} https://aka.ms/PowerShell-Release?tag=v{0} {4}{2} |
57 |
代进代码:
1 | line1Length - line3Length + 3 |
于是执行了等价于 "".PadRight(-17) 的操作。String.PadRight 不接受负数,抛出 ArgumentOutOfRangeException。由于这发生在控制台宿主启动阶段、没有上层可以接住,进程直接 FailFast 退出。
预览版和 LTS 文案同样有问题:
| 提醒类型 | 第 1 行长度 | 第 3 行长度 | 计算结果 |
|---|---|---|---|
| 稳定版 | 37 | 57 | -17 |
| 预览版 | 37 | 57 | -17 |
| LTS | 39 | 57 | -15 |
所以这不是某个版本独有的,而是中文翻译文案与代码假设共同导致的。
四、为什么是”今天”突然开始崩
更新提醒不是发现新版本立刻弹,而是延迟 7 天。源码里写死了:
1 | private const int NotificationDelayDays = 7; |
延迟的理由也写在注释里:新版本刚发布时镜像可能还在滚动,等一周再提醒,降低”看到了却装不上”的概率。
时间线完全对得上:
| 时间 | 事件 |
|---|---|
| 2026-09-08 | PowerShell 7.6.6 发布(安装文件构建于 9 月 2 日) |
| 2026-10-13 | PowerShell 7.6.7 发布 |
| 2026-10-14 22:52 | 本机更新检查发现 7.6.7,写入缓存文件 |
| 2026-10-20 | 距 10-13 满 7 天,提醒开始显示 |
| 2026-10-20 早上 | 打开 PowerShell,首次触发提醒,启动即崩 |
本机缓存文件可以直接看到:
1 | %LOCALAPPDATA%\Microsoft\PowerShell\7.6.6\update1_v7.6.7_2026-10-13 |
文件名里的日期就是新版本的发布日期,ShowUpdateNotification 正是拿它和当前时间比较来决定是否显示。
这也解释了两个现象:
- 为什么之前一直好好的:提醒还没到期。
- 为什么脚本里复现不了:崩溃只在
hostUI.SupportsVirtualTerminal为真时才走补空格那段代码。真实交互式终端支持 VT,重定向/非交互环境通常不支持,于是绕过了崩溃点。
五、修复
5.1 关闭更新检查(推荐,立竿见影)
设置用户级环境变量:
1 | [Environment]::SetEnvironmentVariable('POWERSHELL_UPDATECHECK', 'Off', 'User') |
或者用传统方式:
1 | setx POWERSHELL_UPDATECHECK Off |
这个变量会让 UpdatesNotification.CanNotifyUpdates 变成 false。而 ConsoleHost.Start 只在它为真时才调用显示逻辑:
1 | if (UpdatesNotification.CanNotifyUpdates) |
所以提醒路径根本不会执行,也就不会崩。
5.2 验证是否生效
不设变量时(默认):
1 | $t = [AppDomain]::CurrentDomain.GetAssemblies() | |
设置 POWERSHELL_UPDATECHECK=Off 后:
1 | # False |
本机实测:不设变量为 True,设为 Off 后为 False。
5.3 让变量生效
环境变量只对新启动的进程生效,而已打开的终端会缓存旧环境:
- 关闭所有 PowerShell 窗口
- 如果用的是 Windows Terminal,退出整个应用(不是只关标签页)
- 重新打开 PowerShell 7
如果还不行,注销再登录,或重启资源管理器;最稳妥的是重启系统。
想在当前会话里立刻验证,可以:
1 | $env:POWERSHELL_UPDATECHECK = 'Off' |
子进程会继承这个变量。
5.4 代价
- 以后不会再收到 PowerShell 更新提醒,需要自己偶尔看一眼 Releases。
- 这是临时规避,不是根治。等上游修复并发布新版本后,可以升级,再把变量改回
Default或删除。
恢复提醒:
1 | [Environment]::SetEnvironmentVariable('POWERSHELL_UPDATECHECK', $null, 'User') |
5.5 为什么升级到 7.6.7 没用
我核对了 v7.6.7 的源码和 zh-Hans 资源文件:
UpdatesNotification.cs里PadRight(line1Length - line3Length + 3)的逻辑原样保留ManagedEntranceStrings.zh-Hans.resx里三行文案的长度仍然是 37 / 24 / 57
也就是说,升级只是把”下一次崩溃”推迟到 7.6.8 发布后的第 7 天。必须等官方修复这个格式化逻辑。
5.6 其他方案为什么不行
| 方案 | 结果 |
|---|---|
| 把窗口拉宽 / 最大化 | 无效。-17 来自字符串长度,和窗口宽度无关 |
pwsh -NoProfile |
无效。崩溃发生在加载 profile 之前 |
| 切到 LTS 更新通道 | 无效。LTS 的 zh-Hans 文案同样算出负数(-15) |
删除 update1_* 缓存文件 |
无效。下次启动会重新检查并写回,提醒条件依然成立 |
| 把系统语言改成英文 | 能绕过,但代价过大,不推荐 |
| 修改安装目录里的资源 DLL | 不推荐。MSI 修复/升级会覆盖,且破坏签名完整性 |
六、上游状态
- Issue:PowerShell/PowerShell#28131
- 标题:PowerShell can crash on startup when displaying the update notification under zh-Hans due to negative PadRight width
- 状态:open,
Needs-Triage - 报告者在 7.7.0-preview.4 上首次遇到,并确认 7.7.0-preview.5 仍有同样代码和文案
- 2026-10-20 仍有用户回复 “same”,说明影响面在扩大
修复思路本身不复杂:取三行中的最大长度,或者对负值做兜底(Math.Max(0, ...))。但在官方发版前,本地只能先关掉提醒。
七、可复用的排查经验
- 先看调用栈,别猜。
ShowUpdateNotification已经把范围缩到一个函数,比盲目重装快得多。 - **区分”安装被改了”和”运行时条件触发了”**。文件日期 + 注册表版本就能排除 Windows Update。
- 注意时间相关的功能。更新提醒有”延迟 N 天”的设计,这类 bug 往往在发布后第 N 天才出现,看起来像是”今天突然坏了”。
- 负宽度、负数索引这类异常,优先怀疑本地化文案。代码按英文文案的长度关系写死假设,翻译一变就翻车。
- 交互式才崩、脚本不崩,说明和终端能力有关。本例是
SupportsVirtualTerminal分支。