################################################################################################ # # 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