Add registry backup & restore (#566)

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.
This commit is contained in:
Jeffrey
2026-05-08 21:19:52 +02:00
committed by GitHub
parent 11a324365d
commit 2c360961e3
37 changed files with 3193 additions and 719 deletions

View File

@@ -0,0 +1,174 @@
function Get-RegFileOperations {
param(
[Parameter(Mandatory)]
[string]$regFilePath
)
$content = Get-Content -Path $regFilePath -Raw -ErrorAction Stop
$rawLines = $content -split "`r?`n"
# Join continuation lines (lines ending with \)
$lines = @()
$i = 0
while ($i -lt $rawLines.Count) {
$line = $rawLines[$i]
# Join lines that end with backslash to the next line(s)
while ($line.EndsWith("\") -and $i + 1 -lt $rawLines.Count) {
$line = $line.Substring(0, $line.Length - 1) + $rawLines[$i + 1]
$i++
}
$lines += $line
$i++
}
$operations = @()
$currentKeyPath = $null
$isDeletedKey = $false
foreach ($rawLine in $lines) {
$line = $rawLine.Trim()
if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith(';')) {
continue
}
if ($line -match '^Windows Registry Editor Version') {
continue
}
if ($line -match '^\[(?<deleted>-)?(?<keyPath>[^\]]+)\]$') {
$currentKeyPath = $matches.keyPath.Trim()
$isDeletedKey = $matches.deleted -eq '-'
if ($isDeletedKey) {
$operations += [PSCustomObject]@{
OperationType = 'DeleteKey'
KeyPath = $currentKeyPath
}
}
continue
}
if (-not $currentKeyPath -or $isDeletedKey) {
continue
}
if ($line -notmatch '^(?<valueName>@|"[^"]+")=(?<valueData>.*)$') {
continue
}
$valueNameToken = $matches.valueName
$valueName = if ($valueNameToken -eq '@') {
''
}
else {
$valueNameToken.Trim('"')
}
$parsedValue = Convert-RegValueData -valueData $matches.valueData.Trim()
if (-not $parsedValue) { continue }
$operations += [PSCustomObject]@{
OperationType = $parsedValue.OperationType
KeyPath = $currentKeyPath
ValueName = $valueName
ValueType = $parsedValue.ValueType
ValueData = $parsedValue.ValueData
}
}
return $operations
}
function Convert-RegValueData {
param(
[Parameter(Mandatory)]
[string]$valueData
)
if ($valueData -eq '-') {
return [PSCustomObject]@{
OperationType = 'DeleteValue'
ValueType = $null
ValueData = $null
}
}
if ($valueData -match '^dword:(?<value>[0-9a-fA-F]{1,8})$') {
return [PSCustomObject]@{
OperationType = 'SetValue'
ValueType = 'DWord'
ValueData = [uint32]::Parse($matches.value, [System.Globalization.NumberStyles]::HexNumber)
}
}
if ($valueData -match '^qword:(?<value>[0-9a-fA-F]{1,16})$') {
return [PSCustomObject]@{
OperationType = 'SetValue'
ValueType = 'QWord'
ValueData = [uint64]::Parse($matches.value, [System.Globalization.NumberStyles]::HexNumber)
}
}
if ($valueData -match '^hex(?:\((?<kind>[0-9a-fA-F]+)\))?:(?<bytes>[0-9a-fA-F,\s]+)$') {
$bytes = Convert-HexStringToByteArray -hexValue $matches.bytes
$valueType = if ($matches.kind) { "Hex$($matches.kind)" } else { 'Binary' }
$value = switch ($matches.kind) {
'2' { Convert-RegistryByteArrayToString -byteData $bytes }
'7' { Convert-RegistryByteArrayToMultiString -byteData $bytes }
default { $bytes }
}
return [PSCustomObject]@{
OperationType = 'SetValue'
ValueType = $valueType
ValueData = $value
}
}
if ($valueData -match '^"(?<value>.*)"$') {
return [PSCustomObject]@{
OperationType = 'SetValue'
ValueType = 'String'
ValueData = $matches.value
}
}
return $null
}
function Convert-HexStringToByteArray {
param(
[Parameter(Mandatory)]
[string]$hexValue
)
$parts = $hexValue.Split(',') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
$bytes = New-Object byte[] $parts.Count
for ($i = 0; $i -lt $parts.Count; $i++) {
$bytes[$i] = [byte]::Parse($parts[$i], [System.Globalization.NumberStyles]::HexNumber)
}
return $bytes
}
function Convert-RegistryByteArrayToString {
param(
[Parameter(Mandatory)]
[byte[]]$byteData
)
return ([System.Text.Encoding]::Unicode.GetString($byteData)).TrimEnd([char]0)
}
function Convert-RegistryByteArrayToMultiString {
param(
[Parameter(Mandatory)]
[byte[]]$byteData
)
return @(([System.Text.Encoding]::Unicode.GetString($byteData)).TrimEnd([char]0) -split "`0" | Where-Object { $_ -ne '' })
}

View File

@@ -0,0 +1,43 @@
function GetFriendlyRegistryBackupTarget {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Target
)
if ([string]::IsNullOrWhiteSpace($Target)) {
return 'Unknown'
}
if ($Target -eq 'DefaultUserProfile') {
return 'Default user profile'
}
if ($Target -eq 'CurrentUser') {
return 'Current user'
}
if ($Target -eq 'AllUsers') {
return 'All users'
}
if ($Target -like 'CurrentUser:*') {
$userName = $Target.Substring(12)
if ([string]::IsNullOrWhiteSpace($userName)) {
return 'Current user'
}
return "Current user ($userName)"
}
if ($Target -like 'User:*') {
$userName = $Target.Substring(5)
if ([string]::IsNullOrWhiteSpace($userName)) {
return 'User'
}
return "User ($userName)"
}
return $Target
}

View File

@@ -0,0 +1,44 @@
function Split-RegistryPath {
param(
[Parameter(Mandatory)]
[string]$path
)
if ($path -notmatch '^(?<hive>HKEY_[^\\]+)(?:\\(?<subKey>.*))?$') {
return $null
}
return [PSCustomObject]@{
Hive = $matches.hive
SubKey = $matches.subKey
}
}
function Get-RegistryRootKey {
param(
[Parameter(Mandatory)]
[string]$hiveName
)
switch ($hiveName.ToUpperInvariant()) {
'HKEY_CURRENT_USER' { return [Microsoft.Win32.Registry]::CurrentUser }
'HKEY_LOCAL_MACHINE' { return [Microsoft.Win32.Registry]::LocalMachine }
'HKEY_CLASSES_ROOT' { return [Microsoft.Win32.Registry]::ClassesRoot }
'HKEY_USERS' { return [Microsoft.Win32.Registry]::Users }
'HKEY_CURRENT_CONFIG' { return [Microsoft.Win32.Registry]::CurrentConfig }
default { return $null }
}
}
function Get-RegistryFilePathForFeature {
param(
[Parameter(Mandatory)]
$Feature
)
if ($script:Params.ContainsKey('Sysprep') -or $script:Params.ContainsKey('User')) {
return Join-Path (Join-Path $script:RegfilesPath 'Sysprep') $Feature.RegistryKey
}
return Join-Path $script:RegfilesPath $Feature.RegistryKey
}

View File

@@ -0,0 +1,47 @@
function Test-TargetUserName {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$UserName
)
$normalizedUserName = if ($null -ne $UserName) { $UserName.Trim() } else { '' }
if ([string]::IsNullOrWhiteSpace($normalizedUserName)) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
Message = 'Please enter a username'
}
}
if ($normalizedUserName -eq $env:USERNAME) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
Message = "Cannot enter your own username, use 'Current User' option instead"
}
}
if (-not (CheckIfUserExists -userName $normalizedUserName)) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
Message = 'User not found, please enter a valid username'
}
}
if (TestIfUserIsLoggedIn -Username $normalizedUserName) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
Message = "User '$normalizedUserName' is currently logged in. Please sign out that user first."
}
}
return [PSCustomObject]@{
IsValid = $true
UserName = $normalizedUserName
Message = "User found: $normalizedUserName"
}
}