随便聊聊:为什么打补丁要重启

引子

我有一个朋友,因为最近微软出了很多漏洞修复补丁,导致需要频繁的打补丁。在打补丁重启之后,有些机器出现了蓝屏等等一起奇怪的问题。所以问题来了,为什么给Windows打补丁要重启,不重启行不行。

结论

先说结论,不重启大部分情况下是没问题的。但是​​不建议​

需要重启的理由

动态链接库

在分析原因之前,需要先聊一下,为什么需要重启。在聊重启之前,又要聊一下Windows的​​动态链接库 (DLL) ​​。

动态链接库相当于组成乐高的不同砖块。有些是铺在底下的砖头,如果要更换这些砖头,就需要把这块砖上关联的东西都铲了(停止),才能更换。比如要拆除下图左下角的砖,换一个别的颜色。除了把整个风车拆除,别无她法。

 随便聊聊:为什么打补丁要重启_安全更新

对于Windows,操作系统的很多功能都由 DLL 提供。 此外,当您在这些操作系统中的一个Windows程序时,该程序的很多功能可能由 DLL 提供。 例如,某些程序可能包含许多不同的模块,并且程序的每个模块包含在 DLL 中并分发。

使用 DLL 有助于促进代码的模块化、代码重用、高效的内存使用并减少磁盘空间。 因此,操作系统和程序加载速度更快,运行速度更快,并且占用了更少的磁盘空间。

为什么会重启

知道了最重要的原理,回到为什么会重启的问题:安装安全更新后,如果满足下列条件之一,系统可能会提示您重新启动计算机:

  • 安全更新将更新一个 DLL,该 DLL 加载在由 Windows 所需的一个或多个进程中
  • 安全更新将更新.exe文件,该文件当前作为应用程序所需的进程Windows
  • 安全更新将更新当前使用的设备驱动程序,以及当前Windows。 使用此设备驱动程序时无法完成更新,但是,你无法卸载此设备驱动程序,除非你关闭Windows。
  • 安全更新对注册表进行更改。 这些更改要求您重新启动计算机。
  • 安全更新将更改在您启动计算机时只读的注册表项。

说了那么多点,在于更新的过程,可能(很大概率)会修改Windows的基石。如果细心观察,有的小伙伴可能会遇到系统提示​​当前无法安装补丁,会在下一次重启时安装​​。

重启的几种状态

在我们双击补丁程序之后,可能会发生以下几种情况

  • 没有任何提示,系统不要求重启
  • 要求重启
  • 提示在下一次重启时进行安装。

打补丁为什么会打出新问题

微软的补丁有一个特色,就是可能会解决一些问题的同时,带来一些新问题。

事情的根因大约可以归于动态链接库机制。不同的应用、服务、角色之间有着千丝万缕的关联。Windows上可以安装各种各样的软件、功能、角色,在补丁发版之前很难说做到万全的测试。如果很不幸的撞到了没有测试的项目上,就算中招了。

再比如,安装完某个补丁后没有重启,然后开始安装新的补丁。这种场景测试人员也很难复现。

更为严重的是,如果打补丁出现问题,也不见得次次都能还原回去。

重启能解决什么问题

回到最初的问题,虽然问题是打补丁为什么要重启(打完补丁之后重启),但在打补丁之前,我们提前重启一次,可以避免很多问题。

  • 当前系统安装完补丁还未重启
  • 当前系统安装完某个软件后未重启
  • 当前系统的某个功能或角色,配置完成后还未重启

如果在安装补丁之前,提前重启一次,以上问题带来的潜在风险都可以避免。

检查是否需要重启

在打补丁之前重启,是为了避免存在一些已经要重启了但是没重启的情况,但是这种状态可以提前查询么?答案是肯定的。当重新启动挂起时,Windows 会添加一些注册表值来显示这一点。所以要做的就是检查这些各种各样的注册表。

下面的这个脚本来自​​Adam Bertram​​​,作者是​​微软的在任MVP​​。直接照抄就可以了。

 随便聊聊:为什么打补丁要重启_重启_02

展示效果具体是这样的

PS51> Test-PendingReboot.ps1 -ComputerName localhost

ComputerName IsPendingReboot
------------ ---------------
localhost False

以下是测试代码。

function TestPendingreboot($ComputerName)
{
#功能来源:https://adamtheautomator.com/pending-reboot-registry-windows/
if ($ComputerName -match ";")
{ $ComputerName = $ComputerName.split(";") }

$scriptBlock = {
function Test-RegistryKey
{
[OutputType('bool')]
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Key
)

$ErrorActionPreference = 'Stop'

if (Get-Item -Path $Key -ErrorAction Ignore)
{
$true
}
}

function Test-RegistryValue
{
[OutputType('bool')]
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Key,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Value
)

$ErrorActionPreference = 'Stop'

if (Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore)
{
$true
}
}

function Test-RegistryValueNotNull
{
[OutputType('bool')]
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Key,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Value
)

$ErrorActionPreference = 'Stop'

if (($regVal = Get-ItemProperty -Path $Key -Name $Value -ErrorAction Ignore) -and $regVal.($Value))
{
$true
}
}

# Added "test-path" to each test that did not leverage a custom function from above since
# an exception is thrown when Get-ItemProperty or Get-ChildItem are passed a nonexistant key path
$tests = @(
{ Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending' }
{ Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress' }
{ Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired' }
{ Test-RegistryKey -Key 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending' }
{ Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting' }
{ Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations' }
{ Test-RegistryValueNotNull -Key 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager' -Value 'PendingFileRenameOperations2' }
{
# Added test to check first if key exists, using "ErrorAction ignore" will incorrectly return $true
'HKLM:\SOFTWARE\Microsoft\Updates' | ?{ test-path $_ -PathType Container } | %{
(Get-ItemProperty -Path $_ -Name 'UpdateExeVolatile' | Select-Object -ExpandProperty UpdateExeVolatile) -ne 0
}
}
{ Test-RegistryValue -Key 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Value 'DVDRebootSignal' }
{ Test-RegistryKey -Key 'HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttemps' }
{ Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'JoinDomain' }
{ Test-RegistryValue -Key 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Value 'AvoidSpnSet' }
{
# Added test to check first if keys exists, if not each group will return $Null
# May need to eval(89, 89, 89); margin: 0px; padding: 0px; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0);">('HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName' | ?{ test-path $_ } | %{ (Get-ItemProperty -Path $_).ComputerName }) -ne
('HKLM:\SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName' | ?{ test-path $_ } | %{ (Get-ItemProperty -Path $_).ComputerName })
}
{
# Added test to check first if key exists
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending' | Where-Object {
(Test-Path $_) -and (Get-ChildItem -Path $_)
} | ForEach-Object { $true }
}
)

foreach ($test in $tests)
{
if (& $test)
{
$true
break
}
}
}

foreach ($computer in $ComputerName)
{

$connParams = @{
'ComputerName' = $computer
}


$output = @{
ComputerName = $computer
IsPendingReboot = $false
}

$psRemotingSession = New-PSSession @connParams

if (-not ($output.IsPendingReboot = Invoke-Command -Session $psRemotingSession -ScriptBlock $scriptBlock))
{
$output.IsPendingReboot = $false
}
[pscustomobject]$output


}

}

总结

想一想,因为疫情的紧张,我们已经知道出门要戴口罩。

先说结论,不​​带口罩​​​大部分情况下是没问题的。但是​​不建议​

同理,打补丁重启其实没什么可讨论的,当把它当做一个预防和保护措施的时候,能够节省很多麻烦。当然最重要的是,打补丁之前要做足够多的验证。