mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-08-23 08:02:07 +00:00
Improve registry backup safety and add optional backup skipping (#710)
This commit is contained in:
@@ -201,7 +201,8 @@ function Get-RegistryKeySnapshot {
|
||||
|
||||
.DESCRIPTION
|
||||
Captures all values or selected value names, records missing selected values,
|
||||
and recursively captures subkeys when requested.
|
||||
and recursively captures subkeys when requested. Throws if a requested subkey
|
||||
cannot be read.
|
||||
#>
|
||||
function Convert-RegistryKeyToSnapshot {
|
||||
param(
|
||||
@@ -241,7 +242,9 @@ function Convert-RegistryKeyToSnapshot {
|
||||
if ($IncludeSubKeys) {
|
||||
foreach ($subKeyName in @($RegistryKey.GetSubKeyNames())) {
|
||||
$childKey = $RegistryKey.OpenSubKey($subKeyName, $false)
|
||||
if ($null -eq $childKey) { continue }
|
||||
if ($null -eq $childKey) {
|
||||
throw "Unable to read registry subkey '$($RegistryKey.Name)\$subKeyName' while creating a backup snapshot. The backup was not created."
|
||||
}
|
||||
|
||||
try {
|
||||
$childPath = if ([string]::IsNullOrWhiteSpace($FullPath)) { $subKeyName } else { "$FullPath\$subKeyName" }
|
||||
|
||||
@@ -121,7 +121,7 @@ function Invoke-FeatureApply {
|
||||
'DisableStoreSearchSuggestions' {
|
||||
if ($script:Params.ContainsKey("Sysprep")) {
|
||||
Write-Host "> Disabling Microsoft Store search suggestions in the start menu for all users..."
|
||||
DisableStoreSearchSuggestionsForAllUsers
|
||||
Set-StoreSearchSuggestionsDisabledForAllUsers
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
@@ -159,7 +159,7 @@ function Invoke-FeatureUndo {
|
||||
'DisableStoreSearchSuggestions' {
|
||||
if ($script:Params.ContainsKey('Sysprep')) {
|
||||
Write-Host "> Re-enabling Microsoft Store search suggestions in the start menu for all users..."
|
||||
EnableStoreSearchSuggestionsForAllUsers
|
||||
Set-StoreSearchSuggestionsEnabledForAllUsers
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
@@ -302,8 +302,8 @@ function Invoke-UndoFeatures {
|
||||
|
||||
.DESCRIPTION
|
||||
Sequenced in four phases:
|
||||
1. Registry backup
|
||||
2. System restore point
|
||||
1. Registry backup (skipped when SkipRegistryBackup is present)
|
||||
2. System restore point (skipped when CreateRestorePoint is absent)
|
||||
3. Apply phase - applies all selected features via Invoke-ApplyFeatures
|
||||
4. Undo phase - undoes selected features via Invoke-UndoFeatures
|
||||
|
||||
@@ -349,14 +349,14 @@ function Invoke-AllChanges {
|
||||
|
||||
# ---- Calculate total progress steps ----
|
||||
$totalSteps = $applyIds.Count + $undoIds.Count
|
||||
if ($needsBackup) { $totalSteps++ }
|
||||
if ($needsBackup -and -not $script:Params.ContainsKey('SkipRegistryBackup')) { $totalSteps++ }
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||
$step = 0
|
||||
|
||||
# ================================================================
|
||||
# Phase 1: Registry backup
|
||||
# ================================================================
|
||||
if ($needsBackup) {
|
||||
if ($needsBackup -and -not $script:Params.ContainsKey('SkipRegistryBackup')) {
|
||||
if ($script:CancelRequested) { return }
|
||||
$step++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
|
||||
@@ -65,17 +65,103 @@ function Restore-RegistryKeySnapshot {
|
||||
throw "Unsupported root-level registry path in backup: $($Snapshot.Path)"
|
||||
}
|
||||
|
||||
Test-RegistryKeySnapshotCanBeRestored -Snapshot $Snapshot
|
||||
Restore-RegistryKeySnapshotAtPath -Snapshot $Snapshot -RootKey $rootKey -SubKeyPath $subKeyPath
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validates registry values and subkey paths in a snapshot before live registry state is changed.
|
||||
|
||||
.PARAMETER Snapshot
|
||||
The registry key snapshot to validate before it is restored.
|
||||
#>
|
||||
function Test-RegistryKeySnapshotCanBeRestored {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
$Snapshot
|
||||
)
|
||||
|
||||
if (-not [bool]$Snapshot.Exists) { return }
|
||||
|
||||
$childNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($valueSnapshot in @($Snapshot.Values)) {
|
||||
if ([bool]$valueSnapshot.Exists) {
|
||||
$valueKind = Convert-RegistryValueKindFromBackup -KindName $valueSnapshot.Kind
|
||||
$null = Convert-RegistryValueDataFromBackup -Kind $valueKind -Data $valueSnapshot.Data
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($subKeySnapshot in @($Snapshot.SubKeys)) {
|
||||
$childName = Get-DirectRegistrySnapshotChildName -ParentPath $Snapshot.Path -ChildPath $subKeySnapshot.Path
|
||||
if ([string]::IsNullOrWhiteSpace($childName) -or -not $childNames.Add($childName)) {
|
||||
throw "Backup contains duplicate or unsupported registry child path: $($subKeySnapshot.Path)"
|
||||
}
|
||||
Test-RegistryKeySnapshotCanBeRestored -Snapshot $subKeySnapshot
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Returns a snapshot child's name only when it is directly below its parent.
|
||||
|
||||
.PARAMETER ParentPath
|
||||
The registry path of the expected parent snapshot.
|
||||
|
||||
.PARAMETER ChildPath
|
||||
The registry path of the child snapshot to validate.
|
||||
#>
|
||||
function Get-DirectRegistrySnapshotChildName {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ParentPath,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ChildPath
|
||||
)
|
||||
|
||||
$parentParts = Split-RegistryPath -path $ParentPath
|
||||
$childParts = Split-RegistryPath -path $ChildPath
|
||||
if (-not $parentParts -or -not $childParts -or
|
||||
-not $parentParts.Hive.Equals($childParts.Hive, [System.StringComparison]::OrdinalIgnoreCase) -or
|
||||
[string]::IsNullOrWhiteSpace($parentParts.SubKey) -or
|
||||
[string]::IsNullOrWhiteSpace($childParts.SubKey)) {
|
||||
throw "Unsupported registry child path in backup: $ChildPath"
|
||||
}
|
||||
|
||||
$childName = Split-Path -Path $childParts.SubKey -Leaf
|
||||
$expectedSubKey = "$($parentParts.SubKey)\$childName"
|
||||
if ([string]::IsNullOrWhiteSpace($childName) -or
|
||||
-not $childParts.SubKey.Equals($expectedSubKey, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Registry child path '$ChildPath' is not directly below parent '$ParentPath'."
|
||||
}
|
||||
|
||||
return $childName
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores a snapshot to a specific path below an already resolved registry root.
|
||||
|
||||
.DESCRIPTION
|
||||
Writes only values and descendants represented by the backup. Existing keys are
|
||||
retained so their security descriptors and unrelated data are not destroyed.
|
||||
#>
|
||||
function Restore-RegistryKeySnapshotAtPath {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
$Snapshot,
|
||||
[Parameter(Mandatory)]
|
||||
$RootKey,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SubKeyPath
|
||||
)
|
||||
|
||||
if (-not $Snapshot.Exists) {
|
||||
Remove-RegistrySubKeyTreeIfExists -RootKey $rootKey -SubKeyPath $subKeyPath
|
||||
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)
|
||||
$key = $RootKey.CreateSubKey($SubKeyPath)
|
||||
if ($null -eq $key) {
|
||||
throw "Unable to create or open registry key '$($Snapshot.Path)'"
|
||||
}
|
||||
@@ -90,8 +176,11 @@ function Restore-RegistryKeySnapshot {
|
||||
}
|
||||
|
||||
foreach ($subKeySnapshot in @($Snapshot.SubKeys)) {
|
||||
Restore-RegistryKeySnapshot -Snapshot $subKeySnapshot
|
||||
$childName = Get-DirectRegistrySnapshotChildName -ParentPath $Snapshot.Path -ChildPath $subKeySnapshot.Path
|
||||
|
||||
Restore-RegistryKeySnapshotAtPath -Snapshot $subKeySnapshot -RootKey $RootKey -SubKeyPath "$SubKeyPath\$childName"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<#
|
||||
|
||||
@@ -219,6 +219,11 @@ function Get-DeploymentSettings {
|
||||
$deploySettings += @{ Name = 'CreateRestorePoint'; Value = [bool]$restorePointCheckBox.IsChecked }
|
||||
}
|
||||
|
||||
$registryBackupCheckBox = $Owner.FindName('RegistryBackupCheckBox')
|
||||
if ($registryBackupCheckBox) {
|
||||
$deploySettings += @{ Name = 'SkipRegistryBackup'; Value = -not [bool]$registryBackupCheckBox.IsChecked }
|
||||
}
|
||||
|
||||
$restartExplorerCheckBox = $Owner.FindName('RestartExplorerCheckBox')
|
||||
if ($restartExplorerCheckBox) {
|
||||
$deploySettings += @{ Name = 'RestartExplorer'; Value = [bool]$restartExplorerCheckBox.IsChecked }
|
||||
@@ -272,6 +277,7 @@ function Get-DeploymentCategoryDetailString {
|
||||
|
||||
$options = @()
|
||||
if ($lookup.ContainsKey('CreateRestorePoint') -and [bool]$lookup['CreateRestorePoint']) { $options += 'Restore Point' }
|
||||
if (-not ($lookup.ContainsKey('SkipRegistryBackup') -and [bool]$lookup['SkipRegistryBackup'])) { $options += 'Registry Backup' }
|
||||
if ($lookup.ContainsKey('RestartExplorer') -and [bool]$lookup['RestartExplorer']) { $options += 'Restart Explorer' }
|
||||
|
||||
$lines = @()
|
||||
@@ -378,6 +384,13 @@ function Set-ImportedDeploymentSettings {
|
||||
$restorePointCheckBox.IsChecked = [bool]$lookup['CreateRestorePoint']
|
||||
}
|
||||
|
||||
$registryBackupCheckBox = $Owner.FindName('RegistryBackupCheckBox')
|
||||
if ($registryBackupCheckBox) {
|
||||
if ($lookup.ContainsKey('SkipRegistryBackup')) {
|
||||
$registryBackupCheckBox.IsChecked = -not [bool]$lookup['SkipRegistryBackup']
|
||||
}
|
||||
}
|
||||
|
||||
$restartExplorerCheckBox = $Owner.FindName('RestartExplorerCheckBox')
|
||||
if ($lookup.ContainsKey('RestartExplorer') -and $restartExplorerCheckBox) {
|
||||
$restartExplorerCheckBox.IsChecked = [bool]$lookup['RestartExplorer']
|
||||
|
||||
@@ -702,6 +702,11 @@ function Show-MainWindow {
|
||||
Add-Parameter 'CreateRestorePoint'
|
||||
}
|
||||
|
||||
$registryBackupCheckBox = $window.FindName('RegistryBackupCheckBox')
|
||||
if ($registryBackupCheckBox -and -not $registryBackupCheckBox.IsChecked) {
|
||||
Add-Parameter 'SkipRegistryBackup'
|
||||
}
|
||||
|
||||
switch ($userSelectionCombo.SelectedIndex) {
|
||||
0 { Write-Host "Selected user mode: current user ($(Get-UserName))" }
|
||||
1 {
|
||||
@@ -782,6 +787,12 @@ function Show-MainWindow {
|
||||
$restartExplorerCheckBox.IsEnabled = $false
|
||||
}
|
||||
|
||||
$registryBackupCheckBox = $window.FindName('RegistryBackupCheckBox')
|
||||
if ($registryBackupCheckBox -and $script:Params.ContainsKey('SkipRegistryBackup')) {
|
||||
$registryBackupCheckBox.IsChecked = $false
|
||||
$registryBackupCheckBox.IsEnabled = $false
|
||||
}
|
||||
|
||||
if ($script:Params.ContainsKey("Sysprep")) {
|
||||
$userSelectionCombo.SelectedIndex = 2
|
||||
$userSelectionCombo.IsEnabled = $false
|
||||
|
||||
@@ -10,6 +10,7 @@ param (
|
||||
[Alias('NoRestartExplorer')]
|
||||
[switch]$SkipExplorerRestart,
|
||||
[switch]$CreateRestorePoint,
|
||||
[switch]$SkipRegistryBackup,
|
||||
[switch]$RunDefaults,
|
||||
[switch]$RunDefaultsLite,
|
||||
[switch]$RunSavedSettings,
|
||||
|
||||
@@ -81,6 +81,11 @@ function Import-ConfigToParams {
|
||||
$importedItems++
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('SkipRegistryBackup') -and [bool]$deploymentLookup['SkipRegistryBackup']) {
|
||||
Add-Parameter 'SkipRegistryBackup'
|
||||
$importedItems++
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('RestartExplorer') -and -not [bool]$deploymentLookup['RestartExplorer']) {
|
||||
Add-Parameter 'SkipExplorerRestart'
|
||||
$importedItems++
|
||||
|
||||
Reference in New Issue
Block a user