fix(restore-gui): surface registry-backup load failures to the user (#685)

Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
This commit is contained in:
HetCreep
2026-07-03 02:16:47 +07:00
committed by GitHub
parent 9a700afaed
commit 3b4a5f16ad
2 changed files with 91 additions and 5 deletions

View File

@@ -1,3 +1,19 @@
<#
.SYNOPSIS
Loads a registry backup from a JSON file and normalizes its contents.
.DESCRIPTION
Loads a registry backup from disk and returns a normalized representation
of its contents suitable for use by the restore workflow. Throws if the
file is missing, unreadable, or not valid JSON.
.PARAMETER FilePath
The absolute path to the registry backup JSON file to load.
.OUTPUTS
PSCustomObject
A normalized registry backup object produced by Normalize-RegistryBackup.
#>
function Load-RegistryBackupFromFile { function Load-RegistryBackupFromFile {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -18,6 +34,24 @@ function Load-RegistryBackupFromFile {
return Normalize-RegistryBackup -Backup $rawBackup return Normalize-RegistryBackup -Backup $rawBackup
} }
<#
.SYNOPSIS
Validates and normalizes a raw registry backup object.
.DESCRIPTION
Validates the structure and content of the supplied backup and converts
it into a normalized representation that can be safely consumed by the
restore workflow. Throws if validation fails.
.PARAMETER Backup
The raw backup object (typically parsed from JSON) to normalize.
.OUTPUTS
PSCustomObject
A normalized backup with Version, BackupType, CreatedAt, CreatedBy,
ComputerName, Target, SelectedFeatures, SelectedUndoFeatures, and
RegistryKeys properties.
#>
function Normalize-RegistryBackup { function Normalize-RegistryBackup {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -97,10 +131,17 @@ function Normalize-RegistryBackup {
if ($allSelectedFeatures.Count -eq 0) { if ($allSelectedFeatures.Count -eq 0) {
$errors.Add('Backup must contain at least one feature ID in SelectedFeatures or SelectedUndoFeatures.') $errors.Add('Backup must contain at least one feature ID in SelectedFeatures or SelectedUndoFeatures.')
} }
else {
try {
$allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -SelectedUndoFeatureIds @($selectedUndoFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys)) $allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -SelectedUndoFeatureIds @($selectedUndoFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys))
foreach ($allowListValidationError in $allowListValidationErrors) { foreach ($allowListValidationError in $allowListValidationErrors) {
$errors.Add([string]$allowListValidationError) $errors.Add([string]$allowListValidationError)
} }
}
catch {
$errors.Add("Failed to validate backup: $($_.Exception.Message)")
}
}
if ($errors.Count -gt 0) { if ($errors.Count -gt 0) {
Write-Error "Backup validation failed: $($errors -join ' ')" Write-Error "Backup validation failed: $($errors -join ' ')"
@@ -125,6 +166,23 @@ function Normalize-RegistryBackup {
} }
} }
<#
.SYNOPSIS
Restores registry state from a normalized backup object.
.DESCRIPTION
Applies the registry state described by the supplied backup back to the
registry, loading the appropriate user hive when required.
.PARAMETER Backup
A normalized backup object (as produced by Normalize-RegistryBackup) whose
RegistryKeys snapshots should be restored.
.OUTPUTS
PSCustomObject
Returns an object with a Result property set to $true when the restore
completes successfully.
#>
function Restore-RegistryBackupState { function Restore-RegistryBackupState {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]

View File

@@ -1,3 +1,24 @@
<#
.SYNOPSIS
Displays the Restore Backup wizard dialog.
.DESCRIPTION
Presents a modal wizard that lets the user choose and restore either a
registry backup or a Start Menu pinned-apps backup. Returns the user's
selection via $window.Tag.
.PARAMETER Owner
Optional parent WPF Window used to host this modal dialog. Defaults to the
shared $script:GuiWindow when not supplied.
.OUTPUTS
Hashtable
Returns a Hashtable describing the user's choice. Possible shapes:
RestoreRegistry - @{ Result='RestoreRegistry'; Backup=<normalizedBackup> }
RestoreStartMenu - @{ Result='RestoreStartMenu'; StartMenuScope=<scope>;
UseManualBackupFile=<bool>; BackupFilePath=<path|string> }
Cancelled - @{ Result='Cancelled' } (from New-RestoreDialogState)
#>
function Show-RestoreBackupDialog { function Show-RestoreBackupDialog {
param( param(
[System.Windows.Window]$Owner = $null [System.Windows.Window]$Owner = $null
@@ -295,11 +316,18 @@ function Show-RestoreBackupDialog {
} }
Write-Host "Backup file selected: $($openDialog.FileName)" Write-Host "Backup file selected: $($openDialog.FileName)"
try {
$selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName $selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName
if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) { if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) {
return return
} }
}
catch {
Show-MessageBox -Owner $window -Title 'Invalid Backup File' -Message "The selected file could not be loaded:`n$($_.Exception.Message)" -Button 'OK' -Icon 'Error' | Out-Null
return
}
$state.SelectedRegistryBackup = $selectedBackup $state.SelectedRegistryBackup = $selectedBackup
$primaryActionBtn.Content = 'Restore from backup' $primaryActionBtn.Content = 'Restore from backup'