Applies ToWindows 10 Windows 11

Introducción

Microsoft ha desarrollado un script de PowerShell de ejemplo que puede ayudarte a automatizar la actualización del Entorno de recuperación de Windows (WinRE) en dispositivos implementados para solucionar las vulnerabilidades de seguridad de CVE-2022-41099.

Script de PowerShell de ejemplo

El script de PowerShell de ejemplo lo desarrolló el equipo de producto de Microsoft para ayudar a automatizar la actualización de imágenes de WinRE en dispositivos Windows 10 y Windows 11. Ejecute el script con credenciales de administrador en PowerShell en los dispositivos afectados. Hay dos scripts disponibles: el script que debes usar depende de la versión de Windows que estés ejecutando. Use la versión adecuada para su entorno.

PatchWinREScript_2004plus.ps1 (recomendado)

Este script es para Windows 10, versión 2004 y versiones posteriores, incluyendo Windows 11. Le recomendamos que use esta versión del script, ya que es más sólida, pero usa características disponibles solo en Windows 10, versión 2004 y versiones posteriores.

################################################################################################

#

# Copyright (c) Microsoft Corporation.

# Licensed under the MIT License.

#

# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

# SOFTWARE.

#

################################################################################################

Param (

[Parameter(HelpMessage="Work Directory for patch WinRE")][string]$workDir="",

[Parameter(Mandatory=$true,HelpMessage="Path of target package")][string]$packagePath

)

# ------------------------------------

# Help functions

# ------------------------------------

# Log message

function LogMessage([string]$message)

{

$message = "$([DateTime]::Now) - $message"

Write-Host $message

}

function IsTPMBasedProtector

{

$DriveLetter = $env:SystemDrive

LogMessage("Checking BitLocker status")

$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"

if(-not $BitLocker)

{

LogMessage("No BitLocker object")

return $False

}

$protectionEnabled = $False

switch ($BitLocker.GetProtectionStatus().protectionStatus){

("0"){

LogMessage("Unprotected")

break

}

("1"){

LogMessage("Protected")

$protectionEnabled = $True

break

}

("2"){

LogMessage("Uknown")

break

}

default{

LogMessage("NoReturn")

break

}

}

if (!$protectionEnabled)

{

LogMessage("Bitlocker isn’t enabled on the OS")

return $False

}

$ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID

$return = $False

foreach ($ProtectorID in $ProtectorIds){

$KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType

switch($KeyProtectorType){

"1"{

LogMessage("Trusted Platform Module (TPM)")

$return = $True

break

}

"4"{

LogMessage("TPM And PIN")

$return = $True

break

}

"5"{

LogMessage("TPM And Startup Key")

$return = $True

break

}

"6"{

LogMessage("TPM And PIN And Startup Key")

$return = $True

break

}

default {break}

}#endSwitch

}#EndForeach

if ($return)

{

LogMessage("Has TPM-based protector")

}

else

{

LogMessage("Doesn't have TPM-based protector")

}

return $return

}

function SetRegistrykeyForSuccess

{

reg add HKLM\SOFTWARE\Microsoft\PushButtonReset /v WinREPathScriptSucceed /d 1 /f

}

function TargetfileVersionExam([string]$mountDir)

{

# Exam target binary

$targetBinary=$mountDir + "\Windows\System32\bootmenuux.dll"

LogMessage("TargetFile: " + $targetBinary)

$realNTVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($targetBinary).ProductVersion

$versionString = "$($realNTVersion.Split('.')[0]).$($realNTVersion.Split('.')[1])"

$fileVersion = $($realNTVersion.Split('.')[2])

$fileRevision = $($realNTVersion.Split('.')[3])

LogMessage("Target file version: " + $realNTVersion)

if (!($versionString -eq "10.0"))

{

LogMessage("Not Windows 10 or later")

return $False

}

$hasUpdated = $False

#Windows 10, version 1507 10240.19567

#Windows 10, version 1607 14393.5499

#Windows 10, version 1809 17763.3646

#Windows 10, version 2004 1904X.2247

#Windows 11, version 21H2 22000.1215

#Windows 11, version 22H2 22621.815

switch ($fileVersion) {

"10240" {

LogMessage("Windows 10, version 1507")

if ($fileRevision -ge 19567)

{

LogMessage("Windows 10, version 1507 with revision " + $fileRevision + " >= 19567, updates have been applied")

$hasUpdated = $True

}

break

}

"14393" {

LogMessage("Windows 10, version 1607")

if ($fileRevision -ge 5499)

{

LogMessage("Windows 10, version 1607 with revision " + $fileRevision + " >= 5499, updates have been applied")

$hasUpdated = $True

}

break

}

"17763" {

LogMessage("Windows 10, version 1809")

if ($fileRevision -ge 3646)

{

LogMessage("Windows 10, version 1809 with revision " + $fileRevision + " >= 3646, updates have been applied")

$hasUpdated = $True

}

break

}

"19041" {

LogMessage("Windows 10, version 2004")

if ($fileRevision -ge 2247)

{

LogMessage("Windows 10, version 2004 with revision " + $fileRevision + " >= 2247, updates have been applied")

$hasUpdated = $True

}

break

}

"22000" {

LogMessage("Windows 11, version 21H2")

if ($fileRevision -ge 1215)

{

LogMessage("Windows 11, version 21H2 with revision " + $fileRevision + " >= 1215, updates have been applied")

$hasUpdated = $True

}

break

}

"22621" {

LogMessage("Windows 11, version 22H2")

if ($fileRevision -ge 815)

{

LogMessage("Windows 11, version 22H2 with revision " + $fileRevision + " >= 815, updates have been applied")

$hasUpdated = $True

}

break

}

default {

LogMessage("Warning: unsupported OS version")

}

}

return $hasUpdated

}

function PatchPackage([string]$mountDir, [string]$packagePath)

{

# Exam target binary

$hasUpdated =TargetfileVersionExam($mountDir)

if ($hasUpdated)

{

LogMessage("The update has already been added to WinRE")

SetRegistrykeyForSuccess

return $False

}

# Add package

LogMessage("Apply package:" + $packagePath)

Dism /Add-Package /Image:$mountDir /PackagePath:$packagePath

if ($LASTEXITCODE -eq 0)

{

LogMessage("Successfully applied the package")

}

else

{

LogMessage("Applying the package failed with exit code: " + $LASTEXITCODE)

return $False

}

# Cleanup recovery image

LogMessage("Cleanup image")

Dism /image:$mountDir /cleanup-image /StartComponentCleanup /ResetBase

if ($LASTEXITCODE -eq 0)

{

LogMessage("Cleanup image succeed")

}

else

{

LogMessage("Cleanup image failed: " + $LASTEXITCODE)

return $False

}

return $True

}

# ------------------------------------

# Execution starts

# ------------------------------------

# Check breadcrumb

if (Test-Path HKLM:\Software\Microsoft\PushButtonReset)

{

$values = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset

if (!(-not $values))

{

if (Get-Member -InputObject $values -Name WinREPathScriptSucceed)

{

$value = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset -Name WinREPathScriptSucceed

if ($value.WinREPathScriptSucceed -eq 1)

{

LogMessage("This script was previously run successfully")

exit 1

}

}

}

}

if ([string]::IsNullorEmpty($workDir))

{

LogMessage("No input for mount directory")

LogMessage("Use default path from temporary directory")

$workDir = [System.IO.Path]::GetTempPath()

}

LogMessage("Working Dir: " + $workDir)

$name = "CA551926-299B-27A55276EC22_Mount"

$mountDir = Join-Path $workDir $name

LogMessage("MountDir: " + $mountdir)

# Delete existing mount directory

if (Test-Path $mountDir)

{

LogMessage("Mount directory: " + $mountDir + " already exists")

LogMessage("Try to unmount it")

Dism /unmount-image /mountDir:$mountDir /discard

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Warning: unmount failed: " + $LASTEXITCODE)

}

LogMessage("Delete existing mount direcotry " + $mountDir)

Remove-Item $mountDir -Recurse

}

# Create mount directory

LogMessage("Create mount directory " + $mountDir)

New-Item -Path $mountDir -ItemType Directory

# Set ACL for mount directory

LogMessage("Set ACL for mount directory")

icacls $mountDir /inheritance:r

icacls $mountDir /grant:r SYSTEM:"(OI)(CI)(F)"

icacls $mountDir /grant:r *S-1-5-32-544:"(OI)(CI)(F)"

# Mount WinRE

LogMessage("Mount WinRE:")

reagentc /mountre /path $mountdir

if ($LASTEXITCODE -eq 0)

{

# Patch WinRE

if (PatchPackage -mountDir $mountDir -packagePath $packagePath)

{

$hasUpdated = TargetfileVersionExam($mountDir)

if ($hasUpdated)

{

LogMessage("After patch, find expected version for target file")

}

else

{

LogMessage("Warning: After applying the patch, unexpected version found for the target file")

}

LogMessage("Patch succeed, unmount to commit change")

Dism /unmount-image /mountDir:$mountDir /commit

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Unmount failed: " + $LASTEXITCODE)

exit 1

}

else

{

if ($hasUpdated)

{

if (IsTPMBasedProtector)

{

# Disable WinRE and re-enable it to let new WinRE be trusted by BitLocker

LogMessage("Disable WinRE")

reagentc /disable

LogMessage("Re-enable WinRE")

reagentc /enable

reagentc /info

}

# Leave a breadcrumb indicates the script has succeed

SetRegistrykeyForSuccess

}

}

}

else

{

LogMessage("Patch failed or is not applicable, discard unmount")

Dism /unmount-image /mountDir:$mountDir /discard

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Unmount failed: " + $LASTEXITCODE)

exit 1

}

}

}

else

{

LogMessage("Mount failed: " + $LASTEXITCODE)

}

# Cleanup Mount directory in the end

LogMessage("Delete mount direcotry")

Remove-Item $mountDir -Recurse

PatchWinREScript_General.ps1

Este script es para Windows 10, versión 1909 y versiones anteriores, pero se ejecuta en todas las versiones de Windows 10 y Windows 11.

################################################################################################

#

# Copyright (c) Microsoft Corporation.

# Licensed under the MIT License.

#

# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

# SOFTWARE.

#

################################################################################################

Param (

[Parameter(HelpMessage="Work Directory for patch WinRE")][string]$workDir="",

[Parameter(Mandatory=$true,HelpMessage="Path of target package")][string]$packagePath

)

# ------------------------------------

# Help functions

# ------------------------------------

# Log message

function LogMessage([string]$message)

{

$message = "$([DateTime]::Now) - $message"

Write-Host $message

}

function IsTPMBasedProtector

{

$DriveLetter = $env:SystemDrive

LogMessage("Checking BitLocker status")

$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"

if(-not $BitLocker)

{

LogMessage("No BitLocker object")

return $False

}

$protectionEnabled = $False

switch ($BitLocker.GetProtectionStatus().protectionStatus){

("0"){

LogMessage("Unprotected")

break

}

("1"){

LogMessage("Protected")

$protectionEnabled = $True

break

}

("2"){

LogMessage("Uknown")

break

}

default{

LogMessage("NoReturn")

break

}

}

if (!$protectionEnabled)

{

LogMessage("Bitlocker isn’t enabled on the OS")

return $False

}

$ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID

$return = $False

foreach ($ProtectorID in $ProtectorIds){

$KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType

switch($KeyProtectorType){

"1"{

LogMessage("Trusted Platform Module (TPM)")

$return = $True

break

}

"4"{

LogMessage("TPM And PIN")

$return = $True

break

}

"5"{

LogMessage("TPM And Startup Key")

$return = $True

break

}

"6"{

LogMessage("TPM And PIN And Startup Key")

$return = $True

break

}

default {break}

}#endSwitch

}#EndForeach

if ($return)

{

LogMessage("Has TPM-based protector")

}

else

{

LogMessage("Doesn't have TPM-based protector")

}

return $return

}

function SetRegistrykeyForSuccess

{

reg add HKLM\SOFTWARE\Microsoft\PushButtonReset /v WinREPathScriptSucceed /d 1 /f

}

function TargetfileVersionExam([string]$mountDir)

{

# Exam target binary

$targetBinary=$mountDir + "\Windows\System32\bootmenuux.dll"

LogMessage("TargetFile: " + $targetBinary)

$realNTVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($targetBinary).ProductVersion

$versionString = "$($realNTVersion.Split('.')[0]).$($realNTVersion.Split('.')[1])"

$fileVersion = $($realNTVersion.Split('.')[2])

$fileRevision = $($realNTVersion.Split('.')[3])

LogMessage("Target file version: " + $realNTVersion)

if (!($versionString -eq "10.0"))

{

LogMessage("Not Windows 10 or later")

return $False

}

$hasUpdated = $False

#Windows 10, version 1507 10240.19567

#Windows 10, version 1607 14393.5499

#Windows 10, version 1809 17763.3646

#Windows 10, version 2004 1904X.2247

#Windows 11, version 21H2 22000.1215

#Windows 11, version 22H2 22621.815

switch ($fileVersion) {

"10240" {

LogMessage("Windows 10, version 1507")

if ($fileRevision -ge 19567)

{

LogMessage("Windows 10, version 1507 with revision " + $fileRevision + " >= 19567, updates have been applied")

$hasUpdated = $True

}

break

}

"14393" {

LogMessage("Windows 10, version 1607")

if ($fileRevision -ge 5499)

{

LogMessage("Windows 10, version 1607 with revision " + $fileRevision + " >= 5499, updates have been applied")

$hasUpdated = $True

}

break

}

"17763" {

LogMessage("Windows 10, version 1809")

if ($fileRevision -ge 3646)

{

LogMessage("Windows 10, version 1809 with revision " + $fileRevision + " >= 3646, updates have been applied")

$hasUpdated = $True

}

break

}

"19041" {

LogMessage("Windows 10, version 2004")

if ($fileRevision -ge 2247)

{

LogMessage("Windows 10, version 2004 with revision " + $fileRevision + " >= 2247, updates have been applied")

$hasUpdated = $True

}

break

}

"22000" {

LogMessage("Windows 11, version 21H2")

if ($fileRevision -ge 1215)

{

LogMessage("Windows 11, version 21H2 with revision " + $fileRevision + " >= 1215, updates have been applied")

$hasUpdated = $True

}

break

}

"22621" {

LogMessage("Windows 11, version 22H2")

if ($fileRevision -ge 815)

{

LogMessage("Windows 11, version 22H2 with revision " + $fileRevision + " >= 815, updates have been applied")

$hasUpdated = $True

}

break

}

default {

LogMessage("Warning: unsupported OS version")

}

}

return $hasUpdated

}

function PatchPackage([string]$mountDir, [string]$packagePath)

{

# Exam target binary

$hasUpdated = TargetfileVersionExam($mountDir)

if ($hasUpdated)

{

LogMessage("The update has already been added to WinRE")

SetRegistrykeyForSuccess

return $False

}

# Add package

LogMessage("Apply package:" + $packagePath)

Dism /Add-Package /Image:$mountDir /PackagePath:$packagePath

if ($LASTEXITCODE -eq 0)

{

LogMessage("Successfully applied the package")

}

else

{

LogMessage("Applying the package failed with exit code: " + $LASTEXITCODE)

return $False

}

# Cleanup recovery image

LogMessage("Cleanup image")

Dism /image:$mountDir /cleanup-image /StartComponentCleanup /ResetBase

if ($LASTEXITCODE -eq 0)

{

LogMessage("Cleanup image succeed")

}

else

{

LogMessage("Cleanup image failed: " + $LASTEXITCODE)

return $False

}

return $True

}

# ------------------------------------

# Execution starts

# ------------------------------------

# Check breadcrumb

if (Test-Path HKLM:\Software\Microsoft\PushButtonReset)

{

$values = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset

if (!(-not $values))

{

if (Get-Member -InputObject $values -Name WinREPathScriptSucceed)

{

$value = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset -Name WinREPathScriptSucceed

if ($value.WinREPathScriptSucceed -eq 1)

{

LogMessage("This script was previously run successfully")

exit 1

}

}

}

}

# Get WinRE info

$WinREInfo = Reagentc /info

$findLocation = $False

foreach ($line in $WinREInfo)

{

$params = $line.Split(':')

if ($params.count -le 1)

{

continue

}

if ($params[1].Lenght -eq 0)

{

continue

}

$content = $params[1].Trim()

if ($content.Lenght -eq 0)

{

continue

}

$index = $content.IndexOf("\\?\")

if ($index -ge 0)

{

LogMessage("Find \\?\ at " + $index + " for [" + $content + "]")

$WinRELocation = $content

$findLocation = $True

}

}

if (!$findLocation)

{

LogMessage("WinRE Disabled")

exit 1

}

LogMessage("WinRE Enabled. WinRE location:" + $WinRELocation)

$WinREFile = $WinRELocation + "\winre.wim"

if ([string]::IsNullorEmpty($workDir))

{

LogMessage("No input for mount directory")

LogMessage("Use default path from temporary directory")

$workDir = [System.IO.Path]::GetTempPath()

}

LogMessage("Working Dir: " + $workDir)

$name = "CA551926-299B-27A55276EC22_Mount"

$mountDir = Join-Path $workDir $name

LogMessage("MountDir: " + $mountdir)

# Delete existing mount directory

if (Test-Path $mountDir)

{

LogMessage("Mount directory: " + $mountDir + " already exists")

LogMessage("Try to unmount it")

Dism /unmount-image /mountDir:$mountDir /discard

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Warning: unmount failed: " + $LASTEXITCODE)

}

LogMessage("Delete existing mount direcotry " + $mountDir)

Remove-Item $mountDir -Recurse

}

# Create mount directory

LogMessage("Create mount directory " + $mountDir)

New-Item -Path $mountDir -ItemType Directory

# Set ACL for mount directory

LogMessage("Set ACL for mount directory")

icacls $mountDir /inheritance:r

icacls $mountDir /grant:r SYSTEM:"(OI)(CI)(F)"

icacls $mountDir /grant:r *S-1-5-32-544:"(OI)(CI)(F)"

# Mount WinRE

LogMessage("Mount WinRE:")

Dism /mount-image /imagefile:$WinREFile /index:1 /mountdir:$mountDir

if ($LASTEXITCODE -eq 0)

{

# Patch WinRE

if (PatchPackage -mountDir $mountDir -packagePath $packagePath)

{

$hasUpdated = TargetfileVersionExam($mountDir)

if ($hasUpdated)

{

LogMessage("After patch, find expected version for target file")

}

else

{

LogMessage("Warning: After applying the patch, unexpected version found for the target file")

}

LogMessage("Patch succeed, unmount to commit change")

Dism /unmount-image /mountDir:$mountDir /commit

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Unmount failed: " + $LASTEXITCODE)

exit 1

}

else

{

if ($hasUpdated)

{

if (IsTPMBasedProtector)

{

# Disable WinRE and re-enable it to let new WinRE be trusted by BitLocker

LogMessage("Disable WinRE")

reagentc /disable

LogMessage("Re-enable WinRE")

reagentc /enable

reagentc /info

}

# Leave a breadcrumb indicates the script has succeed

SetRegistrykeyForSuccess

}

}

}

else

{

LogMessage("Patch failed or is not applicable, discard unmount")

Dism /unmount-image /mountDir:$mountDir /discard

if (!($LASTEXITCODE -eq 0))

{

LogMessage("Unmount failed: " + $LASTEXITCODE)

exit 1

}

}

}

else

{

LogMessage("Mount failed: " + $LASTEXITCODE)

}

# Cleanup Mount directory in the end

LogMessage("Delete mount direcotry")

Remove-Item $mountDir -Recurse

Más información

Con el dispositivo iniciado en la versión en ejecución de Windows instalada en el dispositivo, el script realizará los siguientes pasos:

  1. Monta la imagen de WinRE existente (WINRE. WIM).

  2. Actualiza la imagen de WinRE con el paquete especificado de actualización dinámica del sistema operativo seguro (actualización de compatibilidad) disponible en el catálogo de Windows Update. Te recomendamos que uses la actualización dinámica del SO seguro más reciente disponible para la versión de Windows instalada en el dispositivo.

  3. Desmonta la imagen de WinRE.

  4. Si el protector de TPM de BitLocker está presente, vuelve a configurar WinRE para el servicio de BitLocker.

    Importante Este paso no está presente en la mayoría de scripts de terceros para aplicar actualizaciones a la imagen de WinRE.

Uso

Los siguientes parámetros se pueden pasar al script:

Parámetro

Descripción

workDir

<>opcional Especifica el espacio de desecho usado para parchear WinRE. Si no se especifica, el script usará la carpeta temporal predeterminada para el dispositivo.

packagePath

<>necesario Especifica la ruta de acceso y el nombre del paquete de actualización safe OS Dynamic específico de la arquitectura del sistema operativo y del procesador que se usará para actualizar la imagen de WinRE.Nota Puede ser una ruta de acceso local o una ruta UNC remota, pero la actualización dinámica del so seguro debe descargarse y estar disponible para que el script lo use.

Ejemplo: 

.\PatchWinREScript_2004plus.ps1 -packagePath "\\server\share\windows10.0-kb5021043-x64_efa19d2d431c5e782a59daaf2d.cab

Referencias

Cómo escribir y ejecutar scripts en la Windows PowerShell ISE 

¿Necesita más ayuda?

¿Quiere más opciones?

Explore las ventajas de las suscripciones, examine los cursos de aprendizaje, aprenda a proteger su dispositivo y mucho más.

Las comunidades le ayudan a formular y responder preguntas, enviar comentarios y leer a expertos con conocimientos extensos.