735 lines
9.4 KiB
Plaintext
735 lines
9.4 KiB
Plaintext
################################################################################################
|
|
|
|
#
|
|
|
|
# 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 |