mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-05-18 19:56:25 +00:00
Starting from this commit, Win11Debloat will automatically create a registry backup every time the script is run. This registry backup can be used to revert any registry changes made by the script.
236 lines
6.3 KiB
PowerShell
236 lines
6.3 KiB
PowerShell
function Invoke-WithLoadedRestoreHive {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$Target,
|
|
[Parameter(Mandatory)]
|
|
[scriptblock]$ScriptBlock,
|
|
$ArgumentObject = $null
|
|
)
|
|
|
|
$hiveDatPath = if ($Target -eq 'DefaultUserProfile') {
|
|
GetUserDirectory -userName 'Default' -fileName 'NTUSER.DAT'
|
|
}
|
|
elseif ($Target -like 'User:*') {
|
|
$userName = $Target.Substring(5)
|
|
if ([string]::IsNullOrWhiteSpace($userName)) {
|
|
throw 'Invalid backup target format for user restore.'
|
|
}
|
|
GetUserDirectory -userName $userName -fileName 'NTUSER.DAT'
|
|
}
|
|
else {
|
|
throw "Unsupported backup target '$Target'."
|
|
}
|
|
|
|
$global:LASTEXITCODE = 0
|
|
reg load 'HKU\Default' "$hiveDatPath" | Out-Null
|
|
$loadExitCode = $LASTEXITCODE
|
|
if ($loadExitCode -ne 0) {
|
|
throw "Failed to load target user hive '$hiveDatPath' (exit code: $loadExitCode)."
|
|
}
|
|
|
|
try {
|
|
& $ScriptBlock $ArgumentObject
|
|
}
|
|
finally {
|
|
$global:LASTEXITCODE = 0
|
|
reg unload 'HKU\Default' | Out-Null
|
|
$unloadExitCode = $LASTEXITCODE
|
|
if ($unloadExitCode -ne 0) {
|
|
throw "Failed to unload registry hive 'HKU\Default' (exit code: $unloadExitCode)"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Restore-RegistryKeySnapshot {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
$Snapshot
|
|
)
|
|
|
|
$registryParts = Split-RegistryPath -path $Snapshot.Path
|
|
if (-not $registryParts) {
|
|
throw "Unsupported registry path in backup: $($Snapshot.Path)"
|
|
}
|
|
|
|
$rootKey = Get-RegistryRootKey -hiveName $registryParts.Hive
|
|
if (-not $rootKey) {
|
|
throw "Unsupported registry hive in backup: $($registryParts.Hive)"
|
|
}
|
|
|
|
$subKeyPath = $registryParts.SubKey
|
|
if ([string]::IsNullOrWhiteSpace($subKeyPath)) {
|
|
throw "Unsupported root-level registry path in backup: $($Snapshot.Path)"
|
|
}
|
|
|
|
if (-not $Snapshot.Exists) {
|
|
Remove-RegistrySubKeyTreeIfExists -RootKey $rootKey -SubKeyPath $subKeyPath
|
|
return
|
|
}
|
|
|
|
$forceFullTree = @($Snapshot.SubKeys).Count -gt 0
|
|
if ($forceFullTree) {
|
|
Remove-RegistrySubKeyTreeIfExists -RootKey $rootKey -SubKeyPath $subKeyPath
|
|
}
|
|
|
|
$key = $rootKey.CreateSubKey($subKeyPath)
|
|
if ($null -eq $key) {
|
|
throw "Unable to create or open registry key '$($Snapshot.Path)'"
|
|
}
|
|
|
|
try {
|
|
foreach ($valueSnapshot in @($Snapshot.Values)) {
|
|
Restore-RegistryValueSnapshot -RegistryKey $key -Snapshot $valueSnapshot
|
|
}
|
|
}
|
|
finally {
|
|
$key.Close()
|
|
}
|
|
|
|
foreach ($subKeySnapshot in @($Snapshot.SubKeys)) {
|
|
Restore-RegistryKeySnapshot -Snapshot $subKeySnapshot
|
|
}
|
|
}
|
|
|
|
function Restore-RegistryValueSnapshot {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
|
[Parameter(Mandatory)]
|
|
$Snapshot
|
|
)
|
|
|
|
$valueName = if ($null -ne $Snapshot.Name) { [string]$Snapshot.Name } else { '' }
|
|
|
|
if (-not [bool]$Snapshot.Exists) {
|
|
try {
|
|
$RegistryKey.DeleteValue($valueName, $false)
|
|
}
|
|
catch {
|
|
throw "Failed deleting registry value '$valueName' in '$($RegistryKey.Name)': $($_.Exception.Message)"
|
|
}
|
|
return
|
|
}
|
|
|
|
$valueKind = Convert-RegistryValueKindFromBackup -KindName $Snapshot.Kind
|
|
$normalizedData = Convert-RegistryValueDataFromBackup -Kind $valueKind -Data $Snapshot.Data
|
|
|
|
try {
|
|
$RegistryKey.SetValue($valueName, $normalizedData, $valueKind)
|
|
}
|
|
catch {
|
|
$retryBytes = Convert-BackupDataToByteArray -Data $Snapshot.Data
|
|
if ($null -ne $retryBytes) {
|
|
try {
|
|
$RegistryKey.SetValue($valueName, $retryBytes, [Microsoft.Win32.RegistryValueKind]::Binary)
|
|
return
|
|
}
|
|
catch {
|
|
# Fall through to original error message for context.
|
|
}
|
|
}
|
|
|
|
throw "Failed setting registry value '$valueName' in '$($RegistryKey.Name)': $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Convert-RegistryValueKindFromBackup {
|
|
param(
|
|
[string]$KindName
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($KindName)) {
|
|
return [Microsoft.Win32.RegistryValueKind]::String
|
|
}
|
|
|
|
try {
|
|
return [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $KindName, $true)
|
|
}
|
|
catch {
|
|
throw "Unsupported registry value kind in backup: $KindName"
|
|
}
|
|
}
|
|
|
|
function Convert-RegistryValueDataFromBackup {
|
|
param(
|
|
[Microsoft.Win32.RegistryValueKind]$Kind,
|
|
$Data
|
|
)
|
|
|
|
switch ($Kind) {
|
|
([Microsoft.Win32.RegistryValueKind]::DWord) { return [uint32]$Data }
|
|
([Microsoft.Win32.RegistryValueKind]::QWord) { return [uint64]$Data }
|
|
([Microsoft.Win32.RegistryValueKind]::MultiString) { return @($Data | ForEach-Object { [string]$_ }) }
|
|
([Microsoft.Win32.RegistryValueKind]::Binary) {
|
|
$bytes = Convert-BackupDataToByteArray -Data $Data
|
|
if ($null -eq $bytes) {
|
|
return (New-Object byte[] 0)
|
|
}
|
|
return $bytes
|
|
}
|
|
([Microsoft.Win32.RegistryValueKind]::None) { return $null }
|
|
default {
|
|
if ($null -ne $Data) {
|
|
return [string]$Data
|
|
}
|
|
|
|
return ''
|
|
}
|
|
}
|
|
}
|
|
|
|
function Convert-BackupDataToByteArray {
|
|
param(
|
|
$Data
|
|
)
|
|
|
|
if ($null -eq $Data) {
|
|
return $null
|
|
}
|
|
|
|
if ($Data -is [byte[]]) {
|
|
return ,$Data
|
|
}
|
|
|
|
$items = @($Data)
|
|
if ($items.Count -eq 0) {
|
|
return ,(New-Object byte[] 0)
|
|
}
|
|
|
|
foreach ($item in $items) {
|
|
if ($item -isnot [ValueType] -and $item -isnot [string]) {
|
|
return $null
|
|
}
|
|
|
|
$parsed = 0
|
|
if (-not [int]::TryParse([string]$item, [ref]$parsed)) {
|
|
return $null
|
|
}
|
|
|
|
if ($parsed -lt 0 -or $parsed -gt 255) {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
$bytes = New-Object byte[] $items.Count
|
|
for ($i = 0; $i -lt $items.Count; $i++) {
|
|
$bytes[$i] = [byte][int]$items[$i]
|
|
}
|
|
|
|
return ,$bytes
|
|
}
|
|
|
|
function Remove-RegistrySubKeyTreeIfExists {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[Microsoft.Win32.RegistryKey]$RootKey,
|
|
[Parameter(Mandatory)]
|
|
[string]$SubKeyPath
|
|
)
|
|
|
|
$existing = $RootKey.OpenSubKey($SubKeyPath, $false)
|
|
if ($existing) {
|
|
$existing.Close()
|
|
$RootKey.DeleteSubKeyTree($SubKeyPath, $false)
|
|
}
|
|
}
|