Windows 10 自动更新重启的观察

Windows Update
Windows Update

我之前在知乎问题“为什么很多人要禁止 Windows 10 自动更新”下多个回答的评论区宣传 ShutdownBlockReasonCreate 这个 API,尝试“教育”开发者正确处(阻)理(止)Windows 10 自动更新重启带来的关键任务被终止的问题,还因为这个 被人挂了。一开始我抱着怀疑的态度提出的方法,之后不知什么原因就假设这个 API 确实有效了——我当时已经知道该 API 不能其效果的情况,但是我没料到“大更新”的时候 Windows 真的会用那么暴力的方法重启电脑。本文记录了一些我的观察,并从使用者的角度给出一些关于 Windows 10 自动更新处理上的 建议

其实“聪明反被聪明误”换成“自作聪明”更好,但我不想标签太碎片化。好了,说正题。首先,Windows 10 自动更新大体可以分成两类:

  • 大更新:这类更新安装完之后会改变 Windows 10 的版本号(在 winver.exe 中显示的 Version xxxx 中的 xxxx 会改变)。
  • 小更新:其他更新。

大更新的其他特点包括:

  • 安装完成后会出现 Windows.old 文件夹,你可以回(滚)滚(回)到先前的版本。
  • 安装完成后会有新的 OOBE 过程。
  • 在简体中文界面中,安装这类更新的提示框标题是“幸福倒计时”。
  • 它的自动安装非常强硬。

实验说明

我的实验方法很简单,就是放开所有阻碍更新重启的设置,然后等待一个需要重启的更新到来,然后静置电脑(我要用的时候还是会用,然而不用的连续若干小时都是插上交流电静置的),看看会有什么反应。此外,我还用 Raymond Chen 的 scratch program 写了一个处理 WM_QUERYENDSESSIONWM_ENDSESSION 的程序,在第一次收到 WM_QUERYENDSESSION 时调用 ShutdownBlockReasonCreate 并返回 FALSE(表示“现在不关掉我”),此外,每次处理这两个消息的时候,都会在日志文件里面记录本次消息的参数。

小更新的实验

如果等待重启的是小更新,除非你已经计划了一个固定的时间,否则 Windows 不会重启你的电脑。我从 6 月 21 日就放开所有阻碍更新重启的设置,并且设置我的工作时间为某 1 个小时,然而直到 7 月 30 日,Windows 都没有主动重启我的电脑。

期间我有一次主动点击了“重启”,可以发现这个重启是 MusNotificationUx.exe 发起的,并且是以当前用户的身份发起的。该重启被成功拦截,欢迎屏幕上显示如下的对话框:

Closing 3 apps and restarting

To go back and save your work, click Cancel and finish what you need to.

Icon for Scratch
Scratch
You don't want to shut Windows down.
Icon for Maps
Example Maps
 
Icon for Mail
Non-example Mail
 
Restart anyway
Cancel

如果你有未保存的文件,你也会看到该对话框。

7 月 30 日当天,我计划了早 6:30 重启,结果终于被重启了。我当时所在的操作系统是 Windows 10 版本 1709。

此外,我在期间多次登录注销,重设 shutdown block reason(关机阻却原因)的存在性。在最后重启之前,shutdown block reason 是存在的。我的实验结论是:在 Windows 10 版本 1709 上若只有小更新需要安装且当前有一个 outstanding block reason,且没有计划重启的时间,则 Windows 不会自动重启。

大更新的实验

我最近处理了一个 Windows 10 版本 1511 的电脑,让它“自生自灭”地安装更新。结果发现凌晨 3:56 系统进行了重启。是 svchost.exeSYSTEM 的身份,以 0x80020010(计划好的操作系统安装 Serivce Pack)的原因重启。

同样,我也尝试了 Windows 10 版本 1709 更新到 1803,期间遇到了如下对话框(在中文系统里这个对话框的标题是“幸福倒计时”)。

Important updates are pending

The newest Windows feature update is ready to install. We need you to kick it off. With new features and apps, this one could take a little longer than other updates.

Ready? Restart now. Not ready? Pick a time that works for you.

Pick a time
Remind me later
Restart now

注意默认焦点在 Remind me later 上,但是似乎一段时间不理它就会导致重启。我点了 Remind me later,接下来每天提示一次,过了几天就没有 Remind me later 的选项了,Windows 回自动选一个时间。如果我进入 Settings > Update & Security > Windows Update > Restart options 取消自动设置的时间,它会提示“Windows will automatically find a good time to restart your PC.”如果你在 Windows 计划重启期间休眠了,从休眠回复后你会看到一个对话框,让你等待一小时(默认选项)或者现在重启。我又放置了一会儿电脑,Windows 重启了。重启方式同 1511 到 1803 的。

Windows 重启的方式

如果是用户主动点“现在重启”,则是这样的:

WM_QUERYENDSESSION
    reason = CLOSEAPP
    blockReasonCreated = TRUE

此时会看到 关机阻碍对话框,如果你选择“Cancel”,则会有如下消息传入 Scratch 程序:

WM_ENDSESSION
    isEnding = FALSE
    reason = CLOSEAPP
    blockReasonCreated = TRUE

如果是 Windows 触发的重启,无论是用户设置了计划重启的时间,还是 Windows 等不及的时候趁用户不注意重启,都是:

WM_QUERYENDSESSION
    reason = CLOSEAPP | CRITICAL
    blockReasonCreated = TRUE
WM_ENDSESSION
    isEnding = TRUE
    reason = CLOSEAPP | CRITICAL
    blockReasonCreated = TRUE

注意 CRTICAL 标志,这表示重启是强制的,相当于用户在关机阻碍对话框中点了“Restart anyway”的效果,后面的 WM_ENDSESSION 中“是否应该结束”的标志也印证了这一点。

注意:CLOSEAPP 标志的意思是“如果你的程序模型符合,你应该在用户下次登录时自动启动并尝试恢复状态”,具体来说这样的 app 应该消费 Restart Manager API

建议

就目前的实验,我认为 Windows 的小更新是可控的,大更新是不可控的(只要联网,就有一个最晚安装期限,除非用一些特别的方法绕过)。我个人还没有被自动更新的重启打扰过,但很有必要预防这些问题。除了换用 Windows Server 或加入域的 Windows 10、换用早期 Windows 或其他操作系统、使用 undocumented 方式绕过 Windows Update 之外,还有很多可以做的。

一方面,应该在 Feedback app 中发声要求微软允许用户更好地控制 Windows Update(有效率的沟通很有必要且有用,我敦促过并正在敦促微软解决一些我遇到的 bug);另一方面,在进行长时间计算之前安装全部更新、允许延迟功能更新 365 天、延迟安全更新 30 天、暂停检查更新 35 天、进入 Semi-Annual Channel (Targeted)。终极杀招是断网,或者用 hosts 干掉 Windows Update 发现更新的可能性(记得关掉 Delivery optimization)。

如果实验结果准确,安全更新永远都是“小更新”,那么按照允许延迟功能更新 365 天的方法,应该可以做到一年不重启,我想这对于几乎所有没有入域的 Windows 10 用户来说应该是足够了。

不可行的方法

使用 ShutdownBlockReasonCreate 并响应 WM_QUERYENDSESSION 是无效的。Windows 自动重启的时候,你最多有 10 秒的时间收拾干净。实验也表明 Windows 不会选择看看有没有 outstanding shut-down block reason。

使用 Windows service 的 pre-shutdown notification 无法阻止关机。HKLM\SYSTEM\CurrentControlSet\Control 中的 WaitToKillServiceTimeout 默认是 5000,这表示一个服务有最多 5 秒来收拾干净。(注意:但对于一个 BitTorrent 下载器,做成服务仍然是有效的,在重启后可以自动继续下载。)

请启用 JavaScript 来查看由 Disqus 驱动的评论。