src/windows/win-crowdstrike-fix-bootloop.ps1 (240 lines of code) (raw):
#
# .SUMMARY
# Workaround for machines stuck in boot loop due to corrupt crowdstrike falcon sensor 2024-07-19 by removing corrupt crowdstrike files,
# loading/unloading the registry hives, and removing regtrans-ms and txr.blf files under config\TxR folder.
# Check for the corrupt CrowdStrike driver file(s) C-00000291*.sys. If found, delete it. Load the registry hive to check for corruption and if corruption is found, fixes it.
#
# .RESOLVES
# Helps recover VMs stuck in a non-boot state due to the faulty CrowdStrike update from July 19th, 2024.
#
# .PUBLIC DOCS
# https://techcommunity.microsoft.com/blog/azurecompute/recovery-options-for-azure-virtual-machines-vm-affected-by-crowdstrike-falcon-ag/4196798
#
. .\src\windows\common\setup\init.ps1
. .\src\windows\common\helpers\Get-Disk-Partitions-v2.ps1
# Check if corrupt CrowdStrike files exist in each drive letter
# Remove any corrupt CrowdStrike files
function RemoveCrowdStrikeFiles
{
param(
[Parameter(Mandatory = $true)]
[Object[]]$Partitionlist
)
Log-Info "Removing any corrupt crowdstrike files..."
$crowdStrikeFileRemoved = $false
forEach ( $partition in $partitionlist )
{
$driveLetter = $partition.DriveLetter
if ($driveLetter) { # Skip partitions without drive letter
$driveLetter = ($driveLetter + ":")
Log-Info "Check Drive letter: $driveLetter"
$corruptFiles = "$driveLetter\Windows\System32\drivers\CrowdStrike\C-00000291*.sys"
if (Test-Path -Path $corruptFiles) {
Log-Info "Found crowdstrike files to cleanup at $corruptFiles, removing..."
Remove-Item $corruptFiles
Log-Info "Corrupt crowdstrike files are removed."
$crowdStrikeFileRemoved = $true
}
}
}
if ($crowdStrikeFileRemoved) {
Log-Info "Successfully cleaned up crowdstrike files"
} else {
Log-Warning "No bad crowdstrike files found"
}
}
# Check if registry config files exist in each non system drive letter.
# if registry config files exist in the non system drive letter, load it to the registry hive and then unload it.
function LoadUnloadRegistryHives
{
param(
[Parameter(Mandatory = $true)]
[Object[]]$Partitionlist
)
Log-Info "Loading/unloading Registry Hives from registry config files..."
# System Drive (which is usually C:) should be skipped as it is from the OS disk rather than the Data disk
Log-Info "Getting system drive..."
$systemDrive = $Env:SYSTEMDRIVE
Log-Info "System drive is: $systemDrive"
$registryConfigFileFound = $false
forEach ( $partition in $partitionlist )
{
$driveLetter = $partition.DriveLetter
if ($driveLetter) { # Skip partitions without drive letter
$driveLetter = ($driveLetter + ":")
Log-Info "Check Drive letter: $driveLetter"
if ($driveLetter -ne $systemDrive) { # Skip OS disk
Log-Info "Found non system drive: $driveLetter"
Log-Info "Checking if registry config files exist from $driveLetter ..."
$configExistInDrive = $false
$guidSuffix = "f85afa50-13cc-48e0-8a29-90603a43cfe1" # get a guid online as the reg key suffix in case the reg key name already exist
$regKeyToFile = @{
"HKLM\temp_system_hive_$guidSuffix" = "$driveLetter\windows\system32\config\system"
"HKLM\temp_software_hive_$guidSuffix" = "$driveLetter\windows\system32\config\software"
}
foreach ($regKey in $regKeyToFile.Keys)
{
$regFile = $regKeyToFile[$regKey]
if (Test-Path -Path $regFile) {
Log-Info "Found registry config file at $regFile."
$configExistInDrive = $true
Log-Info "Loading registry hive $regKey from $regFile..."
$succeeded = $false
$result = reg load $regKey $regFile 2>&1
if ($LASTEXITCODE -ne 0) {
Log-Error "Load registry hive $regKey from $regFile failed with exit code $LASTEXITCODE. Error: $result"
if ($result -Match "corrupt") {
try {
FixRegistryCorruptions -RegFile $regFile
Log-Info "Corrupt registry config file $regFile fixed, loading registry hive one more time..."
$result = reg load $regKey $regFile 2>&1
if ($LASTEXITCODE -ne 0) {
Log-Error "Load registry hive $regKey from $regFile after ChkReg fix failed with exit code $LASTEXITCODE. Error: $result"
}
else {
Log-Info "Load registry hive $regKey from $regFile after ChkReg fix succeeded with message: $result"
$succeeded = $true
}
}
catch {
Log-Error "$_"
}
}
}
else {
Log-Info "Load registry hive $regKey from $regFile succeeded with message: $result"
$succeeded = $true
}
if ($succeeded) {
if ($regKey -eq "HKLM\temp_software_hive_$guidSuffix") {
# Delete regtrans-ms and txr.blf files under config\TxR for Windows Server 2016 or newer version
CleanUpRegtransmsAndTxrblfFiles -GuidSuffix $guidSuffix -DriveLetter $driveLetter
}
Log-Info "Unloading registry hive $regKey..."
$result = reg unload $regKey 2>&1
if ($LASTEXITCODE -ne 0) {
Log-Error "Unload registry hive $regKey failed with exit code $LASTEXITCODE. Error: $result"
} else {
Log-Info "Unload registry hive $regKey succeeded with message: $result"
}
}
$registryConfigFileFound = $true
}
}
if (!$configExistInDrive) {
Log-Info "Registry config files don't exist from $driveLetter"
}
}
else {
Log-Info "Skip system drive: $driveLetter"
}
}
}
if ($registryConfigFileFound) {
Log-Info "Registry Hives load/unload: done"
} else {
Log-Warning "No registry config files found"
}
}
# Delete regtrans-ms and txr.blf files under config\TxR for Windows Server 2016 or newer version
function CleanUpRegtransmsAndTxrblfFiles
{
param(
[Parameter(Mandatory = $true)]
[string]$GuidSuffix,
[Parameter(Mandatory = $true)]
[string]$DriveLetter
)
Log-Info "Deleting regtrans-ms and txr.blf files under config\TxR for Windows Server 2016 or newer version..."
Log-Info "Checking Windows Build number..."
$regKey = "HKLM:\temp_software_hive_$GuidSuffix\Microsoft\Windows NT\CurrentVersion"
$currentBuild = (Get-ItemProperty $regKey -Name CurrentBuild).CurrentBuild
Log-Info "CurrentBuild: $currentBuild"
if ($null -eq $currentBuild) {
Log-Error "Failed to retrieve the Build Number of the Windows System from the mounted data disk"
return
}
# On Server 2016 and newer, we know that the logs will never replay their changes successfully and so their contents aren't useful.
# We can safely remove these files in this case.
if ($currentBuild -ge 14393) # 14393 is the build number of Windows 2016.
{
Log-Info "Trying to Delete regtrans-ms and txr.blf files under config\TxR..."
$regtransmsFiles = "$DriveLetter\Windows\system32\config\TxR\*.TxR.*.regtrans-ms"
try
{
Remove-Item $regtransmsFiles -ErrorAction Stop -Force
Log-Info "regtrans-ms files under config\TxR removed"
}
catch
{
Log-Error "Remove regtrans-ms files under config\TxR failed: Error: $_"
}
$txrBlfFiles = "$DriveLetter\Windows\system32\config\TxR\*.TxR.blf"
try
{
Remove-Item $txrBlfFiles -ErrorAction Stop -Force
Log-Info "txr.blf files under config\TxR removed"
}
catch
{
Log-Error "Remove txr.blf files under config\TxR failed: Error: $_"
}
}
else
{
Log-Info "Skip deleting regtrans-ms and txr.blf files under config\TxR"
}
}
function FixRegistryCorruptions
{
param(
[Parameter(Mandatory = $true)]
[string]$RegFile
)
Log-Info "Attempting to backup the original registry config file and fix registry config file with chkreg.exe..."
Log-Info "Making a backup of the original registry config file..."
$timestamp = Get-Date -Format "yyyyMMddTHHmmssffffZ"
$copySucceeded = $false
try {
$backupFileName = "$RegFile.backup-$timestamp"
Copy-Item $RegFile -Destination $backupFileName -ErrorAction Stop
Log-Info "Original registry config file $RegFile is backed up at $backupFileName"
$regFileLog1 = "$RegFile.LOG1"
$regFileLog1Backup = "$regFileLog1.backup-$timestamp"
if (Test-Path $regFileLog1) {
Copy-Item $regFileLog1 -Destination $regFileLog1Backup -ErrorAction Stop
Log-Info "Original registry config file $regFileLog1 is backed up at $regFileLog1Backup"
}
$regFileLog2 = "$RegFile.LOG2"
$regFileLog2Backup = "$regFileLog2.backup-$timestamp"
if (Test-Path $regFileLog2) {
Copy-Item $regFileLog2 -Destination $regFileLog2Backup -ErrorAction Stop
Log-Info "Original registry config file $regFileLog2 is backed up at $regFileLog2Backup"
}
$copySucceeded = $true
}
catch {
Log-Error "Copy $RegFile and related files failed: Error: $_"
}
if ($copySucceeded) {
Log-Info "Start fixing registry config file with chkreg.exe as original config file backup succeeded..."
$result = cmd /c $PSScriptRoot\common\tools\chkreg.exe /F $RegFile /R `2>`&1
if ($LASTEXITCODE -ne 0) {
Log-Error "chkreg.exe execution on $RegFile failed with error code: $LASTEXITCODE. Error: $result"
throw "chkreg.exe execution on $RegFile failed with error code: $LASTEXITCODE. Error: $result"
}
else {
Log-Info "chkreg.exe execution on $RegFile succeeded with message: $result"
}
}
else {
Log-Error "Fixing registry config file with chkreg.exe is skipped as original config file backup failed"
throw "Fixing registry config file with chkreg.exe is skipped as original config file backup failed"
}
}
$partitionlist = Get-Disk-Partitions
$driveLetters = $partitionlist.DriveLetter
Log-Info "Found drive letters: $driveLetters"
RemoveCrowdStrikeFiles -Partitionlist $partitionlist
LoadUnloadRegistryHives -Partitionlist $partitionlist
return $STATUS_SUCCESS