To remove the UWF filter uwfvol, the only way outside of Windows is to edit the registry by loading the system hive and removing it from
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class{71a27cdd-812a-11d0-bec7-08002be2092f} – LowerFilters
Below is a script that will create a winpe iso that modifies the value, note: your value may have different filters to keep. It also injects a CSV for bitlocker codes in the format ComputerName, Password.
If machines are stuck in a loop this will get them out.
# Build-UWF-WinPE.ps1
# Creates C:\UWF_WinPE.iso
# Copies D:\Temp\bitlocker.csv into WinPE
# On boot: auto-unlocks C: from CSV, prints Bitlocker Unlocked or Computer Not In Sheet, then runs UWF LowerFilters fix using ControlSet001
#requires -RunAsAdministrator
$ErrorActionPreference = "Stop"
$Arch = "amd64"
$Work = "C:\UWF_WinPE"
$Iso = "C:\UWF_WinPE.iso"
$Mount = "$Work\mount"
$BitLockerCsvSource = "D:\Temp\bitlocker.csv"
$BitLockerCsvName = "bitlocker.csv"
$AdkRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\Assessment and Deployment Kit"
$Deploy = Join-Path $AdkRoot "Deployment Tools"
$WinPeRoot = Join-Path $AdkRoot "Windows Preinstallation Environment"
$OcRoot = Join-Path $WinPeRoot "$Arch\WinPE_OCs"
$DandI = Join-Path $Deploy "DandISetEnv.bat"
if (-not (Test-Path $BitLockerCsvSource)) {
throw "BitLocker CSV not found: $BitLockerCsvSource"
}
if (-not (Test-Path $AdkRoot)) {
throw "ADK root not found: $AdkRoot"
}
if (-not (Test-Path $DandI)) {
throw "DandISetEnv.bat not found. Install ADK with Deployment Tools."
}
if (-not (Test-Path $WinPeRoot)) {
throw "WinPE folder not found. Install the Windows PE Add-on for the ADK."
}
if (-not (Test-Path $OcRoot)) {
throw "WinPE optional components not found: $OcRoot"
}
$Copype = Get-ChildItem $AdkRoot -Recurse -Filter "copype.cmd" -ErrorAction SilentlyContinue | Select-Object -First 1
$MakePE = Get-ChildItem $AdkRoot -Recurse -Filter "MakeWinPEMedia.cmd" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $Copype) {
throw "copype.cmd not found. This usually means the WinPE Add-on is missing or broken."
}
if (-not $MakePE) {
throw "MakeWinPEMedia.cmd not found. This usually means the WinPE Add-on is missing or broken."
}
$Dism = Join-Path $Deploy "$Arch\DISM\dism.exe"
if (-not (Test-Path $Dism)) {
$Dism = "dism.exe"
}
function Invoke-CmdChecked {
param(
[Parameter(Mandatory)]
[string]$Command,
[Parameter(Mandatory)]
[string]$FailMessage
)
Write-Host ""
Write-Host "Running: $Command"
$OutFile = Join-Path $env:TEMP "uwf_winpe_stdout.txt"
$ErrFile = Join-Path $env:TEMP "uwf_winpe_stderr.txt"
Remove-Item $OutFile, $ErrFile -Force -ErrorAction SilentlyContinue
$p = Start-Process `
-FilePath "cmd.exe" `
-ArgumentList "/d", "/c", $Command `
-Wait `
-PassThru `
-NoNewWindow `
-RedirectStandardOutput $OutFile `
-RedirectStandardError $ErrFile
if (Test-Path $OutFile) {
Get-Content $OutFile | ForEach-Object { Write-Host $_ }
}
if (Test-Path $ErrFile) {
Get-Content $ErrFile | ForEach-Object { Write-Host $_ }
}
if ($p.ExitCode -ne 0) {
throw "$FailMessage Exit code: $($p.ExitCode)"
}
}
function Invoke-DismChecked {
param(
[Parameter(Mandatory)]
[string[]]$Args,
[Parameter(Mandatory)]
[string]$FailMessage
)
Write-Host ""
Write-Host "Running: $Dism $($Args -join ' ')"
& $Dism @Args
if ($LASTEXITCODE -ne 0) {
throw "$FailMessage Exit code: $LASTEXITCODE"
}
}
function Add-WinPePackageChecked {
param(
[Parameter(Mandatory)]
[string]$PackageRelativePath
)
$PkgPath = Join-Path $OcRoot $PackageRelativePath
if (-not (Test-Path $PkgPath)) {
throw "Required WinPE package missing: $PkgPath"
}
Invoke-DismChecked `
-Args @("/Image:$Mount", "/Add-Package", "/PackagePath:$PkgPath") `
-FailMessage "Failed adding package: $PkgPath"
}
if (Test-Path $Mount) {
Write-Host "Cleaning up any previous mount..."
& $Dism /Unmount-Image /MountDir:"$Mount" /Discard 2>$null | Out-Null
}
if (Test-Path $Work) {
Write-Host "Removing old working folder: $Work"
Remove-Item $Work -Recurse -Force
}
if (Test-Path $Iso) {
Write-Host "Removing old ISO: $Iso"
Remove-Item $Iso -Force
}
Write-Host ""
Write-Host "Creating WinPE working files..."
Invoke-CmdChecked `
-Command "call `"$DandI`" && call `"$($Copype.FullName)`" $Arch `"$Work`"" `
-FailMessage "copype failed."
if (-not (Test-Path "$Work\media\sources\boot.wim")) {
throw "boot.wim was not created at $Work\media\sources\boot.wim"
}
Write-Host ""
Write-Host "Mounting boot.wim..."
Invoke-DismChecked `
-Args @("/Mount-Image", "/ImageFile:$Work\media\sources\boot.wim", "/Index:1", "/MountDir:$Mount") `
-FailMessage "DISM mount failed."
try {
Write-Host ""
Write-Host "Adding WinPE packages..."
$Packages = @(
"WinPE-WMI.cab",
"en-us\WinPE-WMI_en-us.cab",
"WinPE-NetFX.cab",
"en-us\WinPE-NetFX_en-us.cab",
"WinPE-Scripting.cab",
"en-us\WinPE-Scripting_en-us.cab",
"WinPE-PowerShell.cab",
"en-us\WinPE-PowerShell_en-us.cab",
"WinPE-StorageWMI.cab",
"en-us\WinPE-StorageWMI_en-us.cab",
"WinPE-SecureStartup.cab",
"en-us\WinPE-SecureStartup_en-us.cab"
)
foreach ($Pkg in $Packages) {
Add-WinPePackageChecked -PackageRelativePath $Pkg
}
Write-Host ""
Write-Host "Copying BitLocker CSV into WinPE..."
Copy-Item -Path $BitLockerCsvSource -Destination "$Mount\Windows\System32\$BitLockerCsvName" -Force
Copy-Item -Path $BitLockerCsvSource -Destination "$Mount\$BitLockerCsvName" -Force
Write-Host ""
Write-Host "Writing UWF automatic startup script..."
$StartNet = @'
wpeinit
powershell.exe -NoProfile -ExecutionPolicy Bypass -File %SystemRoot%\System32\UWFAuto.ps1
cmd.exe
'@
Set-Content -Path "$Mount\Windows\System32\startnet.cmd" -Value $StartNet -Encoding ASCII -Force
$UwfAuto = @'
$ErrorActionPreference = "Continue"
$Drive = "C:"
$Hive = "C:\Windows\System32\Config\SYSTEM"
$MountName = "HKLM\OFFSYS"
$ClassKey = "HKLM\OFFSYS\ControlSet001\Control\Class\{71a27cdd-812a-11d0-bec7-08002be2092f}"
$LogPath = "X:\UWF-Auto.log"
function Log {
param([string]$Message)
$line = "{0} {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message
Write-Host $Message
try { Add-Content -Path $LogPath -Value $line -Encoding ASCII -ErrorAction SilentlyContinue } catch {}
}
function Get-CsvPath {
$paths = @(
"X:\bitlocker.csv",
"X:\Windows\System32\bitlocker.csv",
(Join-Path $env:WINDIR "System32\bitlocker.csv")
) | Select-Object -Unique
foreach ($p in $paths) {
if (Test-Path $p) { return $p }
}
return $null
}
function Test-OfflineWindowsAvailable {
return (Test-Path $Hive)
}
function Test-BitLockerUnlocked {
if (Test-OfflineWindowsAvailable) { return $true }
$status = & manage-bde.exe -status $Drive 2>$null | Out-String
if ($status -match "Lock Status:\s+Unlocked") { return $true }
return $false
}
function Normalize-Name {
param([string]$Name)
if ($null -eq $Name) { return "" }
return (($Name -replace '[^a-zA-Z0-9]', '').ToLowerInvariant())
}
function Get-ColumnName {
param(
[string[]]$Headers,
[string[]]$Wanted
)
foreach ($h in $Headers) {
$n = Normalize-Name $h
foreach ($w in $Wanted) {
if ($n -eq $w -or $n -like "*$w*") { return $h }
}
}
return $null
}
function Get-RecoveryPasswordsFromText {
param([string]$Text)
if ([string]::IsNullOrWhiteSpace($Text)) { return @() }
$matches = [regex]::Matches($Text, '(?<!\d)(?:\d{6}-){7}\d{6}(?!\d)')
return @($matches | ForEach-Object { $_.Value } | Select-Object -Unique)
}
function Try-UnlockWithPassword {
param([string]$RecoveryPassword)
if ([string]::IsNullOrWhiteSpace($RecoveryPassword)) { return $false }
$RecoveryPassword = $RecoveryPassword.Trim()
if ($RecoveryPassword -notmatch '^(?:\d{6}-){7}\d{6}$') { return $false }
& manage-bde.exe -unlock $Drive -RecoveryPassword $RecoveryPassword | Out-Null
Start-Sleep -Milliseconds 300
return (Test-BitLockerUnlocked)
}
function Unlock-BitLockerFromCsv {
if (Test-BitLockerUnlocked) {
Log "Bitlocker Unlocked"
return $true
}
$csvPath = Get-CsvPath
if (-not $csvPath) {
Log "Computer Not In Sheet"
Log "CSV not found. Expected X:\bitlocker.csv or X:\Windows\System32\bitlocker.csv"
return $false
}
Log "Using CSV: $csvPath"
Log "WinPE computer name: $env:COMPUTERNAME"
$raw = ""
try { $raw = Get-Content -Path $csvPath -Raw -ErrorAction Stop } catch {}
$rows = @()
try { $rows = @(Import-Csv -Path $csvPath -ErrorAction Stop) } catch {}
$passwordsToTry = New-Object System.Collections.Generic.List[string]
if ($rows.Count -gt 0) {
$first = $rows | Select-Object -First 1
$headers = @($first.PSObject.Properties.Name)
$computerColumn = Get-ColumnName -Headers $headers -Wanted @("computer", "computername", "devicename", "hostname", "name")
$passwordColumn = Get-ColumnName -Headers $headers -Wanted @("password", "recoverypassword", "bitlockerrecoverypassword", "recoverykey", "key")
if ($passwordColumn) {
if ($computerColumn) {
$matchedRows = @($rows | Where-Object {
$value = [string]($_.$computerColumn)
$value.Trim() -ieq $env:COMPUTERNAME
})
foreach ($row in $matchedRows) {
$pw = [string]($row.$passwordColumn)
if ($pw) { [void]$passwordsToTry.Add($pw.Trim()) }
}
}
foreach ($row in $rows) {
$pw = [string]($row.$passwordColumn)
if ($pw) { [void]$passwordsToTry.Add($pw.Trim()) }
}
}
}
foreach ($pw in (Get-RecoveryPasswordsFromText -Text $raw)) {
[void]$passwordsToTry.Add($pw)
}
$passwords = @($passwordsToTry | Where-Object { $_ -match '^(?:\d{6}-){7}\d{6}$' } | Select-Object -Unique)
if ($passwords.Count -eq 0) {
Log "Computer Not In Sheet"
Log "No valid 48-digit recovery passwords found in CSV. Check the CSV format."
return $false
}
Log "Trying $($passwords.Count) recovery password candidate(s)."
foreach ($pw in $passwords) {
if (Try-UnlockWithPassword -RecoveryPassword $pw) {
Log "Bitlocker Unlocked"
return $true
}
}
Log "Computer Not In Sheet"
Log "No CSV recovery password unlocked $Drive."
return $false
}
function Run-UwfFix {
Log ""
Log "Running UWF LowerFilters fix..."
if (-not (Test-Path $Hive)) {
Log "ERROR: $Hive not found. C: is still locked, or Windows is not C:."
return $false
}
try {
Copy-Item -Path $Hive -Destination "$Hive.bak" -Force
Log "Backed up SYSTEM hive to $Hive.bak"
}
catch {
Log "ERROR: Backup failed: $($_.Exception.Message)"
return $false
}
& reg.exe unload $MountName 1>$null 2>$null
& reg.exe load $MountName $Hive 1>$null
if ($LASTEXITCODE -ne 0) {
Log "ERROR: Failed to load offline SYSTEM hive."
return $false
}
& reg.exe add $ClassKey /v LowerFilters /t REG_MULTI_SZ /d "fvevol\0iorate\0rdyboost" /f 1>$null
if ($LASTEXITCODE -ne 0) {
Log "ERROR: Failed to set LowerFilters."
& reg.exe unload $MountName | Out-Null
return $false
}
& reg.exe query $ClassKey /v LowerFilters
& reg.exe unload $MountName 1>$null
if ($LASTEXITCODE -ne 0) {
Log "WARNING: Failed to unload hive. Run manually: reg unload $MountName"
return $false
}
Log "UWF Fix Complete"
return $true
}
Clear-Host
Log "========================================"
Log " UWF WinPE Auto Repair"
Log "========================================"
Log ""
Unlock-BitLockerFromCsv | Out-Null
Run-UwfFix | Out-Null
Log ""
Log "Finished. Log: $LogPath"
Log "Command prompt will remain open."
'@
Set-Content -Path "$Mount\Windows\System32\UWFAuto.ps1" -Value $UwfAuto -Encoding ASCII -Force
Write-Host ""
Write-Host "Committing boot.wim..."
Invoke-DismChecked `
-Args @("/Unmount-Image", "/MountDir:$Mount", "/Commit") `
-FailMessage "DISM commit failed."
}
catch {
Write-Warning "Build failed. Discarding mounted image..."
& $Dism /Unmount-Image /MountDir:"$Mount" /Discard 2>$null | Out-Null
throw
}
Write-Host ""
Write-Host "Creating ISO..."
$DandI = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\DandISetEnv.bat'
$Make = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\MakeWinPEMedia.cmd'
$Out = "$env:TEMP\UWF_WinPE_out.txt"
$Err = "$env:TEMP\UWF_WinPE_err.txt"
Remove-Item $Iso -Force -ErrorAction SilentlyContinue
Remove-Item $Out,$Err -Force -ErrorAction SilentlyContinue
$p = Start-Process `
-FilePath "cmd.exe" `
-ArgumentList "/d", "/c", "call `"$DandI`" && call `"$Make`" /ISO /F `"C:\UWF_WinPE`" `"C:\UWF_WinPE.iso`"" `
-Wait `
-PassThru `
-NoNewWindow `
-RedirectStandardOutput $Out `
-RedirectStandardError $Err
Get-Content $Out -ErrorAction SilentlyContinue
Get-Content $Err -ErrorAction SilentlyContinue
if ($p.ExitCode -ne 0) {
throw "MakeWinPEMedia ISO creation failed. Exit code: $($p.ExitCode)"
}
if (-not (Test-Path $Iso)) {
throw "ISO was not created: $Iso"
}
Write-Host ""
Write-Host "DONE"
Write-Host "ISO: $Iso"
Write-Host ""
Write-Host "To write to USB later:"
Write-Host "MakeWinPEMedia /UFD /F $Work E:"
Write-Host ""
Write-Host "Replace E: with the USB drive letter. It will wipe the USB."
Leave a Reply