mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-05-18 11:46:18 +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.
257 lines
7.9 KiB
PowerShell
257 lines
7.9 KiB
PowerShell
function Get-RegistryBackupCapturePlans {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[object[]]$SelectedRegistryFeatures
|
|
)
|
|
|
|
$planMap = @{}
|
|
foreach ($feature in $SelectedRegistryFeatures) {
|
|
$regFilePath = Get-RegistryFilePathForFeature -Feature $feature
|
|
if (-not (Test-Path $regFilePath)) {
|
|
throw "Unable to find registry file for backup: $($feature.RegistryKey) ($regFilePath)"
|
|
}
|
|
|
|
foreach ($operation in @(Get-RegFileOperations -regFilePath $regFilePath)) {
|
|
if (-not $operation.KeyPath) { continue }
|
|
|
|
$mapKey = $operation.KeyPath.ToLowerInvariant()
|
|
if (-not $planMap.ContainsKey($mapKey)) {
|
|
$planMap[$mapKey] = [PSCustomObject]@{
|
|
Path = $operation.KeyPath
|
|
IncludeSubKeys = $false
|
|
CaptureAllValues = $false
|
|
ValueNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
|
}
|
|
}
|
|
|
|
$plan = $planMap[$mapKey]
|
|
switch ($operation.OperationType) {
|
|
'DeleteKey' {
|
|
$plan.IncludeSubKeys = $true
|
|
$plan.CaptureAllValues = $true
|
|
}
|
|
'SetValue' {
|
|
if (-not $plan.CaptureAllValues) {
|
|
$null = $plan.ValueNames.Add([string]$operation.ValueName)
|
|
}
|
|
}
|
|
'DeleteValue' {
|
|
if (-not $plan.CaptureAllValues) {
|
|
$null = $plan.ValueNames.Add([string]$operation.ValueName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return @(
|
|
foreach ($entry in $planMap.Values) {
|
|
[PSCustomObject]@{
|
|
Path = $entry.Path
|
|
IncludeSubKeys = [bool]$entry.IncludeSubKeys
|
|
CaptureAllValues = [bool]$entry.CaptureAllValues
|
|
ValueNames = @($entry.ValueNames)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
function Get-RegistrySnapshotsForBackup {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[object[]]$CapturePlans
|
|
)
|
|
|
|
if ($CapturePlans.Count -eq 0) {
|
|
return @()
|
|
}
|
|
|
|
$snapshotScript = {
|
|
param($plans)
|
|
|
|
$snapshots = @()
|
|
foreach ($plan in $plans) {
|
|
$snapshots += Get-RegistryKeySnapshot -KeyPath $plan.Path -CaptureAllValues:$plan.CaptureAllValues -ValueNames @($plan.ValueNames) -IncludeSubKeys:$plan.IncludeSubKeys
|
|
}
|
|
|
|
return @($snapshots)
|
|
}
|
|
|
|
if ($script:Params.ContainsKey('Sysprep') -or $script:Params.ContainsKey('User')) {
|
|
return Invoke-WithLoadedBackupHive -ScriptBlock $snapshotScript -ArgumentObject @($CapturePlans)
|
|
}
|
|
|
|
return & $snapshotScript $CapturePlans
|
|
}
|
|
|
|
function Invoke-WithLoadedBackupHive {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[scriptblock]$ScriptBlock,
|
|
$ArgumentObject = $null
|
|
)
|
|
|
|
$hiveDatPath = if ($script:Params.ContainsKey('Sysprep')) {
|
|
GetUserDirectory -userName 'Default' -fileName 'NTUSER.DAT'
|
|
}
|
|
else {
|
|
GetUserDirectory -userName $script:Params.Item('User') -fileName 'NTUSER.DAT'
|
|
}
|
|
|
|
$global:LASTEXITCODE = 0
|
|
reg load 'HKU\Default' "$hiveDatPath" | Out-Null
|
|
$loadExitCode = $LASTEXITCODE
|
|
if ($loadExitCode -ne 0) {
|
|
throw "Failed to load user hive for registry backup at '$hiveDatPath' (exit code: $loadExitCode)"
|
|
}
|
|
|
|
try {
|
|
return & $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 Get-RegistryKeySnapshot {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$KeyPath,
|
|
[bool]$CaptureAllValues = $false,
|
|
[string[]]$ValueNames = @(),
|
|
[bool]$IncludeSubKeys = $false
|
|
)
|
|
|
|
$registryParts = Split-RegistryPath -path $KeyPath
|
|
if (-not $registryParts) {
|
|
throw "Unsupported registry path in backup: $KeyPath"
|
|
}
|
|
|
|
$rootKey = Get-RegistryRootKey -hiveName $registryParts.Hive
|
|
if (-not $rootKey) {
|
|
throw "Unsupported registry hive in backup: $($registryParts.Hive)"
|
|
}
|
|
|
|
$subKeyPath = $registryParts.SubKey
|
|
$key = $rootKey.OpenSubKey($subKeyPath, $false)
|
|
if ($null -eq $key) {
|
|
return @{
|
|
Path = $KeyPath
|
|
Exists = $false
|
|
Values = @()
|
|
SubKeys = @()
|
|
}
|
|
}
|
|
|
|
try {
|
|
return (Convert-RegistryKeyToSnapshot -RegistryKey $key -FullPath $KeyPath -CaptureAllValues:$CaptureAllValues -ValueNames $ValueNames -IncludeSubKeys:$IncludeSubKeys)
|
|
}
|
|
finally {
|
|
$key.Close()
|
|
}
|
|
}
|
|
|
|
function Convert-RegistryKeyToSnapshot {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
|
[Parameter(Mandatory)]
|
|
[string]$FullPath,
|
|
[bool]$CaptureAllValues = $false,
|
|
[string[]]$ValueNames = @(),
|
|
[bool]$IncludeSubKeys = $false
|
|
)
|
|
|
|
$values = @()
|
|
if ($CaptureAllValues) {
|
|
foreach ($valueName in @($RegistryKey.GetValueNames())) {
|
|
$values += @(Convert-RegistryValueToSnapshot -RegistryKey $RegistryKey -ValueName $valueName)
|
|
}
|
|
}
|
|
else {
|
|
foreach ($valueName in @($ValueNames | Sort-Object -Unique)) {
|
|
$exists = ($RegistryKey.GetValueNames() -contains $valueName)
|
|
if ($exists) {
|
|
$values += @(Convert-RegistryValueToSnapshot -RegistryKey $RegistryKey -ValueName $valueName)
|
|
}
|
|
else {
|
|
$values += @{
|
|
Name = $valueName
|
|
Exists = $false
|
|
Kind = $null
|
|
Data = $null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$subKeys = @()
|
|
if ($IncludeSubKeys) {
|
|
foreach ($subKeyName in @($RegistryKey.GetSubKeyNames())) {
|
|
$childKey = $RegistryKey.OpenSubKey($subKeyName, $false)
|
|
if ($null -eq $childKey) { continue }
|
|
|
|
try {
|
|
$childPath = if ([string]::IsNullOrWhiteSpace($FullPath)) { $subKeyName } else { "$FullPath\$subKeyName" }
|
|
$subKeys += @(Convert-RegistryKeyToSnapshot -RegistryKey $childKey -FullPath $childPath -CaptureAllValues:$true -IncludeSubKeys:$true)
|
|
}
|
|
finally {
|
|
$childKey.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
return @{
|
|
Path = $FullPath
|
|
Exists = $true
|
|
Values = $values
|
|
SubKeys = $subKeys
|
|
}
|
|
}
|
|
|
|
function Convert-RegistryValueToSnapshot {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
|
[Parameter(Mandatory)]
|
|
[AllowEmptyString()]
|
|
[string]$ValueName
|
|
)
|
|
|
|
$valueKind = $RegistryKey.GetValueKind($ValueName)
|
|
$value = $RegistryKey.GetValue($ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
|
$normalizedValue = switch ($valueKind) {
|
|
([Microsoft.Win32.RegistryValueKind]::Binary) { @($value | ForEach-Object { [int]$_ }) }
|
|
([Microsoft.Win32.RegistryValueKind]::MultiString) { @($value) }
|
|
([Microsoft.Win32.RegistryValueKind]::DWord) { [uint32]$value }
|
|
([Microsoft.Win32.RegistryValueKind]::QWord) { [uint64]$value }
|
|
default { if ($null -ne $value) { [string]$value } else { $null } }
|
|
}
|
|
|
|
return @{
|
|
Name = $ValueName
|
|
Exists = $true
|
|
Kind = $valueKind.ToString()
|
|
Data = $normalizedValue
|
|
}
|
|
}
|
|
|
|
function Get-RegistryBackupTargetDescription {
|
|
if ($script:Params.ContainsKey('Sysprep')) {
|
|
return 'DefaultUserProfile'
|
|
}
|
|
|
|
$resolvedUserName = [string](GetUserName)
|
|
|
|
if ($script:Params.ContainsKey('User')) {
|
|
return "User:$resolvedUserName"
|
|
}
|
|
|
|
return "CurrentUser:$resolvedUserName"
|
|
}
|