mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-07-02 22:58:34 +00:00
Guard against loading, saving & executing undefined features (#665)
This commit is contained in:
@@ -368,7 +368,7 @@
|
|||||||
"Label": "Create a system restore point",
|
"Label": "Create a system restore point",
|
||||||
"Category": null,
|
"Category": null,
|
||||||
"RegistryKey": null,
|
"RegistryKey": null,
|
||||||
"ApplyText": null,
|
"ApplyText": "Creating system restore point",
|
||||||
"UndoLabel": null,
|
"UndoLabel": null,
|
||||||
"ApplyUndoText": null,
|
"ApplyUndoText": null,
|
||||||
"RegistryUndoKey": null,
|
"RegistryUndoKey": null,
|
||||||
|
|||||||
@@ -45,14 +45,8 @@ function PrintPendingChanges {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
default {
|
default {
|
||||||
if ($script:Features -and $script:Features.ContainsKey($parameterName)) {
|
|
||||||
$message = $script:Features[$parameterName].Label
|
$message = $script:Features[$parameterName].Label
|
||||||
Write-Output "- $message"
|
Write-Output "- $message"
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Fallback: show the parameter name if no feature description is available
|
|
||||||
Write-Output "- $parameterName"
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
function Get-FeatureId {
|
<#
|
||||||
param(
|
.SYNOPSIS
|
||||||
[Parameter(Mandatory)]
|
Filters a list of features to those that have a non-empty RegistryKey.
|
||||||
$Feature
|
|
||||||
)
|
|
||||||
|
|
||||||
$featureId = [string]$Feature.FeatureId
|
|
||||||
if ([string]::IsNullOrWhiteSpace($featureId)) {
|
|
||||||
throw 'Selected feature is missing required FeatureId.'
|
|
||||||
}
|
|
||||||
|
|
||||||
return $featureId
|
|
||||||
}
|
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
An array of feature objects to filter.
|
||||||
|
#>
|
||||||
function Get-RegistryBackedFeatures {
|
function Get-RegistryBackedFeatures {
|
||||||
param(
|
param(
|
||||||
[object[]]$Features = @()
|
[object[]]$Features = @()
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Creates a timestamped JSON backup of registry state for selected features.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Resolves selected and undo features from the provided keys, captures their
|
||||||
|
registry state, and saves the result as a JSON file in the Backups/ folder.
|
||||||
|
Returns the file path on success, $null if no registry-backed features exist.
|
||||||
|
|
||||||
|
.PARAMETER ActionableKeys
|
||||||
|
Param keys from $script:Params to resolve into apply features.
|
||||||
|
|
||||||
|
.PARAMETER ExtraFeatures
|
||||||
|
Additional synthetic feature objects (e.g. undo features) to include.
|
||||||
|
#>
|
||||||
function New-RegistrySettingsBackup {
|
function New-RegistrySettingsBackup {
|
||||||
param(
|
param(
|
||||||
[string[]]$ActionableKeys,
|
[string[]]$ActionableKeys,
|
||||||
@@ -32,6 +47,13 @@ function New-RegistrySettingsBackup {
|
|||||||
return $backupFilePath
|
return $backupFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Resolves param keys into deduplicated feature objects from the catalog.
|
||||||
|
|
||||||
|
.PARAMETER ActionableKeys
|
||||||
|
Param keys to look up in $script:Features.
|
||||||
|
#>
|
||||||
function Get-SelectedFeatures {
|
function Get-SelectedFeatures {
|
||||||
param(
|
param(
|
||||||
[string[]]$ActionableKeys
|
[string[]]$ActionableKeys
|
||||||
@@ -46,8 +68,7 @@ function Get-SelectedFeatures {
|
|||||||
$feature = $script:Features[$paramKey]
|
$feature = $script:Features[$paramKey]
|
||||||
if (-not $feature) { continue }
|
if (-not $feature) { continue }
|
||||||
|
|
||||||
$featureId = Get-FeatureId -Feature $feature
|
$featureId = [string]$feature.FeatureId
|
||||||
|
|
||||||
if ($selectedFeatureIds.Add($featureId)) {
|
if ($selectedFeatureIds.Add($featureId)) {
|
||||||
$selectedFeatures.Add($feature)
|
$selectedFeatures.Add($feature)
|
||||||
}
|
}
|
||||||
@@ -56,6 +77,23 @@ function Get-SelectedFeatures {
|
|||||||
return @($selectedFeatures.ToArray())
|
return @($selectedFeatures.ToArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Builds the full backup payload object from selected and undo features.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Deduplicates feature IDs, resolves registry capture plans, snapshots all
|
||||||
|
registry keys, and assembles the final backup hashtable with metadata.
|
||||||
|
|
||||||
|
.PARAMETER SelectedFeatures
|
||||||
|
Feature objects from the apply side.
|
||||||
|
|
||||||
|
.PARAMETER UndoFeatures
|
||||||
|
Synthetic feature objects from the undo side.
|
||||||
|
|
||||||
|
.PARAMETER CreatedAt
|
||||||
|
Timestamp recorded in the backup metadata.
|
||||||
|
#>
|
||||||
function Get-RegistryBackupPayload {
|
function Get-RegistryBackupPayload {
|
||||||
param(
|
param(
|
||||||
[object[]]$SelectedFeatures = @(),
|
[object[]]$SelectedFeatures = @(),
|
||||||
@@ -67,8 +105,7 @@ function Get-RegistryBackupPayload {
|
|||||||
$selectedFeatureIds = New-Object System.Collections.Generic.List[string]
|
$selectedFeatureIds = New-Object System.Collections.Generic.List[string]
|
||||||
$seenSelectedFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
$seenSelectedFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
foreach ($feature in $SelectedFeatures) {
|
foreach ($feature in $SelectedFeatures) {
|
||||||
$featureId = Get-FeatureId -Feature $feature
|
$featureId = [string]$feature.FeatureId
|
||||||
|
|
||||||
if ($seenSelectedFeatureIds.Add($featureId)) {
|
if ($seenSelectedFeatureIds.Add($featureId)) {
|
||||||
$selectedFeatureIds.Add($featureId)
|
$selectedFeatureIds.Add($featureId)
|
||||||
}
|
}
|
||||||
@@ -77,8 +114,7 @@ function Get-RegistryBackupPayload {
|
|||||||
$selectedUndoFeatureIds = New-Object System.Collections.Generic.List[string]
|
$selectedUndoFeatureIds = New-Object System.Collections.Generic.List[string]
|
||||||
$seenUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
$seenUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
foreach ($feature in $UndoFeatures) {
|
foreach ($feature in $UndoFeatures) {
|
||||||
$featureId = Get-FeatureId -Feature $feature
|
$featureId = [string]$feature.FeatureId
|
||||||
|
|
||||||
if ($seenUndoFeatureIds.Add($featureId)) {
|
if ($seenUndoFeatureIds.Add($featureId)) {
|
||||||
$selectedUndoFeatureIds.Add($featureId)
|
$selectedUndoFeatureIds.Add($featureId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# Tests whether the registry operations in a feature's .reg file currently match the live registry.
|
<#
|
||||||
# Returns $true if ALL operations in the apply reg file match current system state.
|
.SYNOPSIS
|
||||||
# Returns $false if the feature has no RegistryKey, the file is missing, or any operation mismatches.
|
Maps a .reg file value type string to its RegistryValueKind enum.
|
||||||
|
|
||||||
|
.PARAMETER Operation
|
||||||
|
A parsed .reg operation object containing a ValueType property.
|
||||||
|
#>
|
||||||
function Get-ExpectedRegistryValueKind {
|
function Get-ExpectedRegistryValueKind {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -18,13 +22,25 @@ function Get-ExpectedRegistryValueKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tests whether a feature's registry operations currently match the live registry.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns $true when ALL operations in the apply .reg file match current system
|
||||||
|
state. Returns $false if the feature has no RegistryKey, the reg file is
|
||||||
|
missing, or any operation mismatches. Special-cased features (Widgets, Store
|
||||||
|
suggestions, Windows Sandbox, WSL) bypass .reg checking entirely.
|
||||||
|
|
||||||
|
.PARAMETER FeatureId
|
||||||
|
The feature identifier to test.
|
||||||
|
#>
|
||||||
function Test-FeatureApplied {
|
function Test-FeatureApplied {
|
||||||
param (
|
param (
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$FeatureId
|
[string]$FeatureId
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not $script:Features.ContainsKey($FeatureId)) { return $false }
|
|
||||||
$feature = $script:Features[$FeatureId]
|
$feature = $script:Features[$FeatureId]
|
||||||
|
|
||||||
switch ($FeatureId) {
|
switch ($FeatureId) {
|
||||||
@@ -147,8 +163,18 @@ function Test-FeatureApplied {
|
|||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Returns the 1-based index of the UiGroup option whose features all match current system state,
|
<#
|
||||||
# or 0 if no option fully matches (meaning the current state is unknown / "No Change").
|
.SYNOPSIS
|
||||||
|
Returns the 1-based index of the UiGroup option whose features all match
|
||||||
|
current system state.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns 0 if no option fully matches, meaning the current state is unknown
|
||||||
|
or represents "No Change".
|
||||||
|
|
||||||
|
.PARAMETER Group
|
||||||
|
A UiGroup object whose Values array contains options with FeatureIds.
|
||||||
|
#>
|
||||||
function Get-CurrentGroupActiveIndex {
|
function Get-CurrentGroupActiveIndex {
|
||||||
param (
|
param (
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
@@ -16,15 +16,11 @@ function Invoke-FeatureApply {
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Resolve feature metadata from Features.json
|
# Resolve feature metadata from Features.json
|
||||||
$feature = $null
|
|
||||||
if ($script:Features.ContainsKey($FeatureId)) {
|
|
||||||
$feature = $script:Features[$FeatureId]
|
$feature = $script:Features[$FeatureId]
|
||||||
}
|
$applyText = $feature.ApplyText
|
||||||
|
|
||||||
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
|
||||||
|
|
||||||
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
||||||
if ($feature -and $feature.RegistryKey) {
|
if ($feature.RegistryKey) {
|
||||||
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
||||||
|
|
||||||
# Post-import side effects for specific features
|
# Post-import side effects for specific features
|
||||||
@@ -177,15 +173,13 @@ function Invoke-FeatureUndo {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
'EnableWindowsSandbox' {
|
'EnableWindowsSandbox' {
|
||||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Sandbox' }
|
Write-Host "> $($feature.ApplyUndoText)..."
|
||||||
Write-Host "> $undoText..."
|
|
||||||
DisableWindowsFeature 'Containers-DisposableClientVM'
|
DisableWindowsFeature 'Containers-DisposableClientVM'
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
'EnableWindowsSubsystemForLinux' {
|
'EnableWindowsSubsystemForLinux' {
|
||||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Subsystem for Linux' }
|
Write-Host "> $($feature.ApplyUndoText)..."
|
||||||
Write-Host "> $undoText..."
|
|
||||||
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
||||||
DisableWindowsFeature 'VirtualMachinePlatform'
|
DisableWindowsFeature 'VirtualMachinePlatform'
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
@@ -245,15 +239,8 @@ function Invoke-ApplyFeatures {
|
|||||||
if ($script:CancelRequested) { return }
|
if ($script:CancelRequested) { return }
|
||||||
|
|
||||||
# Resolve display name for the progress indicator
|
# Resolve display name for the progress indicator
|
||||||
$displayName = $featureId
|
|
||||||
if ($script:Features.ContainsKey($featureId)) {
|
|
||||||
$f = $script:Features[$featureId]
|
$f = $script:Features[$featureId]
|
||||||
if ($f.ApplyText) {
|
|
||||||
$displayName = $f.ApplyText
|
$displayName = $f.ApplyText
|
||||||
} elseif ($f.Label) {
|
|
||||||
$displayName = $f.Label
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($script:ApplyProgressCallback) {
|
if ($script:ApplyProgressCallback) {
|
||||||
& $script:ApplyProgressCallback $step $TotalSteps $displayName
|
& $script:ApplyProgressCallback $step $TotalSteps $displayName
|
||||||
@@ -345,7 +332,6 @@ function Invoke-AllChanges {
|
|||||||
# ---- Determine if registry backup is needed ----
|
# ---- Determine if registry backup is needed ----
|
||||||
$needsBackup = $false
|
$needsBackup = $false
|
||||||
foreach ($id in $applyIds) {
|
foreach ($id in $applyIds) {
|
||||||
if (-not $script:Features.ContainsKey($id)) { continue }
|
|
||||||
$f = $script:Features[$id]
|
$f = $script:Features[$id]
|
||||||
if ($f -and -not [string]::IsNullOrWhiteSpace([string]$f.RegistryKey)) {
|
if ($f -and -not [string]::IsNullOrWhiteSpace([string]$f.RegistryKey)) {
|
||||||
$needsBackup = $true
|
$needsBackup = $true
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ function LoadSettings {
|
|||||||
|
|
||||||
$feature = $script:Features[$setting.Name]
|
$feature = $script:Features[$setting.Name]
|
||||||
|
|
||||||
|
# Skip unknown settings that aren't defined in Features.json
|
||||||
|
if (-not $feature) { continue }
|
||||||
|
|
||||||
# Check version and feature compatibility using Features.json
|
# Check version and feature compatibility using Features.json
|
||||||
if (($feature.MinVersion -and $WinVersion -lt $feature.MinVersion) -or ($feature.MaxVersion -and $WinVersion -gt $feature.MaxVersion) -or ($feature.FeatureId -eq 'DisableModernStandbyNetworking' -and (-not $script:ModernStandbySupported))) {
|
if (($feature.MinVersion -and $WinVersion -lt $feature.MinVersion) -or ($feature.MaxVersion -and $WinVersion -gt $feature.MaxVersion) -or ($feature.FeatureId -eq 'DisableModernStandbyNetworking' -and (-not $script:ModernStandbySupported))) {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function SaveSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($param in $script:Params.Keys) {
|
foreach ($param in $script:Params.Keys) {
|
||||||
if ($script:ControlParams -notcontains $param) {
|
if ($script:ControlParams -notcontains $param -and $script:Features.ContainsKey($param)) {
|
||||||
$value = $script:Params[$param]
|
$value = $script:Params[$param]
|
||||||
|
|
||||||
$settings.Settings += @{
|
$settings.Settings += @{
|
||||||
|
|||||||
@@ -1,29 +1,8 @@
|
|||||||
# MainWindow-Deployment.ps1
|
# MainWindow-Deployment.ps1
|
||||||
# Overview generation, pending tweak actions, feature labels, tweak preset maps, apply logic, user mode state, user selection, and validation.
|
# Overview generation, pending tweak actions, feature labels, tweak preset maps, apply logic, user mode state, user selection, and validation.
|
||||||
|
|
||||||
function Get-FeatureLabel {
|
|
||||||
param(
|
|
||||||
[string]$FeatureId,
|
|
||||||
$FallbackLabel = $null
|
|
||||||
)
|
|
||||||
|
|
||||||
$label = $script:FeatureLabelLookup[$FeatureId]
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace([string]$label)) {
|
|
||||||
return [string]$label
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace([string]$FallbackLabel)) {
|
|
||||||
return [string]$FallbackLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
return [string]$FeatureId
|
|
||||||
}
|
|
||||||
|
|
||||||
function Get-UndoFeatureLabel {
|
function Get-UndoFeatureLabel {
|
||||||
param(
|
param([string]$FeatureId)
|
||||||
[string]$FeatureId,
|
|
||||||
$FallbackLabel = $null
|
|
||||||
)
|
|
||||||
|
|
||||||
$undoLabel = $script:UndoFeatureLabelLookup[$FeatureId]
|
$undoLabel = $script:UndoFeatureLabelLookup[$FeatureId]
|
||||||
if (-not [string]::IsNullOrWhiteSpace([string]$undoLabel)) {
|
if (-not [string]::IsNullOrWhiteSpace([string]$undoLabel)) {
|
||||||
@@ -31,8 +10,7 @@ function Get-UndoFeatureLabel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Fall back to the regular label (prefixed for undo context)
|
# Fall back to the regular label (prefixed for undo context)
|
||||||
$label = Get-FeatureLabel -FeatureId $FeatureId -FallbackLabel $FallbackLabel
|
return [string]$script:FeatureLabelLookup[$FeatureId]
|
||||||
return [string]$label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-PendingTweakActions {
|
function Get-PendingTweakActions {
|
||||||
@@ -69,14 +47,14 @@ function Get-PendingTweakActions {
|
|||||||
$actions.Add([PSCustomObject]@{
|
$actions.Add([PSCustomObject]@{
|
||||||
Action = 'Apply'
|
Action = 'Apply'
|
||||||
FeatureId = [string]$mapping.FeatureId
|
FeatureId = [string]$mapping.FeatureId
|
||||||
Label = (Get-FeatureLabel -FeatureId $mapping.FeatureId -FallbackLabel $mapping.Label)
|
Label = [string]$script:FeatureLabelLookup[$mapping.FeatureId]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
elseif ($wasApplied -and -not $isNowChecked) {
|
elseif ($wasApplied -and -not $isNowChecked) {
|
||||||
$actions.Add([PSCustomObject]@{
|
$actions.Add([PSCustomObject]@{
|
||||||
Action = 'Undo'
|
Action = 'Undo'
|
||||||
FeatureId = [string]$mapping.FeatureId
|
FeatureId = [string]$mapping.FeatureId
|
||||||
Label = (Get-FeatureLabel -FeatureId $mapping.FeatureId -FallbackLabel $mapping.Label)
|
Label = [string]$script:FeatureLabelLookup[$mapping.FeatureId]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +79,7 @@ function Get-PendingTweakActions {
|
|||||||
$actions.Add([PSCustomObject]@{
|
$actions.Add([PSCustomObject]@{
|
||||||
Action = 'Apply'
|
Action = 'Apply'
|
||||||
FeatureId = [string]$fid
|
FeatureId = [string]$fid
|
||||||
Label = (Get-FeatureLabel -FeatureId $fid)
|
Label = [string]$script:FeatureLabelLookup[$fid]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Creates a lightweight state object for the restore-backup dialog result.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Encapsulates the user's dialog choice (Result), the selected backup file
|
||||||
|
path, and the parsed backup payload so callers receive a single object.
|
||||||
|
#>
|
||||||
function New-RestoreDialogState {
|
function New-RestoreDialogState {
|
||||||
param(
|
param(
|
||||||
[string]$Result = 'Cancel',
|
[string]$Result = 'Cancel',
|
||||||
@@ -8,6 +16,16 @@ function New-RestoreDialogState {
|
|||||||
return @{ Result = $Result; SelectedFile = $SelectedFile; Backup = $Backup }
|
return @{ Result = $Result; SelectedFile = $SelectedFile; Backup = $Backup }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Looks up a feature definition by ID from the provided feature catalog.
|
||||||
|
|
||||||
|
.PARAMETER FeatureId
|
||||||
|
The identifier to search for (e.g. 'DisableTelemetry').
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
A hashtable loaded from Features.json (FeatureId -> feature object).
|
||||||
|
#>
|
||||||
function Get-RestoreDialogFeatureDefinition {
|
function Get-RestoreDialogFeatureDefinition {
|
||||||
param(
|
param(
|
||||||
[string]$FeatureId,
|
[string]$FeatureId,
|
||||||
@@ -25,6 +43,21 @@ function Get-RestoreDialogFeatureDefinition {
|
|||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Determines whether a feature can be automatically reverted via registry restore.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns $true when the feature has a non-empty RegistryKey, indicating
|
||||||
|
an apply .reg file exists that can be undone automatically. Features
|
||||||
|
with custom logic (no RegistryKey) must be manually reverted.
|
||||||
|
|
||||||
|
.PARAMETER FeatureId
|
||||||
|
The feature identifier to check.
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
A hashtable loaded from Features.json.
|
||||||
|
#>
|
||||||
function Test-RestoreDialogFeatureCanAutoRevert {
|
function Test-RestoreDialogFeatureCanAutoRevert {
|
||||||
param(
|
param(
|
||||||
[string]$FeatureId,
|
[string]$FeatureId,
|
||||||
@@ -43,6 +76,20 @@ function Test-RestoreDialogFeatureCanAutoRevert {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Resolves a human-readable label for a feature shown in the restore dialog.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Returns the feature's Label from Features.json when found, falling back
|
||||||
|
to the raw FeatureId string. For null/empty FeatureIds returns 'Unknown feature'.
|
||||||
|
|
||||||
|
.PARAMETER FeatureId
|
||||||
|
The feature identifier to resolve a label for.
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
A hashtable loaded from Features.json.
|
||||||
|
#>
|
||||||
function Get-RestoreDialogFeatureDisplayLabel {
|
function Get-RestoreDialogFeatureDisplayLabel {
|
||||||
param(
|
param(
|
||||||
[string]$FeatureId,
|
[string]$FeatureId,
|
||||||
@@ -54,13 +101,28 @@ function Get-RestoreDialogFeatureDisplayLabel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$featureDefinition = Get-RestoreDialogFeatureDefinition -FeatureId $FeatureId -Features $Features
|
$featureDefinition = Get-RestoreDialogFeatureDefinition -FeatureId $FeatureId -Features $Features
|
||||||
if ($featureDefinition -and -not [string]::IsNullOrWhiteSpace([string]$featureDefinition.Label)) {
|
if ($featureDefinition) {
|
||||||
return [string]$featureDefinition.Label
|
return [string]$featureDefinition.Label
|
||||||
}
|
}
|
||||||
|
|
||||||
return $FeatureId
|
return $FeatureId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Checks whether a feature should appear in the restore dialog's overview list.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
A feature is considered visible when it exists in the catalog and has
|
||||||
|
a non-empty Category (meaning it belongs to a UI grouping). Features
|
||||||
|
without a Category are hidden from the overview.
|
||||||
|
|
||||||
|
.PARAMETER FeatureId
|
||||||
|
The feature identifier to check.
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
A hashtable loaded from Features.json.
|
||||||
|
#>
|
||||||
function Test-RestoreDialogFeatureVisibleInOverview {
|
function Test-RestoreDialogFeatureVisibleInOverview {
|
||||||
param(
|
param(
|
||||||
[string]$FeatureId,
|
[string]$FeatureId,
|
||||||
@@ -79,6 +141,13 @@ function Test-RestoreDialogFeatureVisibleInOverview {
|
|||||||
return -not [string]::IsNullOrWhiteSpace([string]$featureDefinition.Category)
|
return -not [string]::IsNullOrWhiteSpace([string]$featureDefinition.Category)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Extracts deduplicated forward (apply) feature IDs from a backup payload.
|
||||||
|
|
||||||
|
.PARAMETER SelectedBackup
|
||||||
|
The parsed backup object containing a SelectedFeatures property.
|
||||||
|
#>
|
||||||
function Get-SelectedForwardFeatureIdsFromBackup {
|
function Get-SelectedForwardFeatureIdsFromBackup {
|
||||||
param($SelectedBackup)
|
param($SelectedBackup)
|
||||||
|
|
||||||
@@ -99,6 +168,13 @@ function Get-SelectedForwardFeatureIdsFromBackup {
|
|||||||
return @($selectedFeatureIds.ToArray())
|
return @($selectedFeatureIds.ToArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Extracts deduplicated undo feature IDs from a backup payload.
|
||||||
|
|
||||||
|
.PARAMETER SelectedBackup
|
||||||
|
The parsed backup object containing a SelectedUndoFeatures property.
|
||||||
|
#>
|
||||||
function Get-SelectedUndoFeatureIdsFromBackup {
|
function Get-SelectedUndoFeatureIdsFromBackup {
|
||||||
param($SelectedBackup)
|
param($SelectedBackup)
|
||||||
|
|
||||||
@@ -119,6 +195,13 @@ function Get-SelectedUndoFeatureIdsFromBackup {
|
|||||||
return @($selectedUndoFeatureIds.ToArray())
|
return @($selectedUndoFeatureIds.ToArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Merges forward and undo feature IDs from a backup into a single deduplicated list.
|
||||||
|
|
||||||
|
.PARAMETER SelectedBackup
|
||||||
|
The parsed backup object containing SelectedFeatures and SelectedUndoFeatures.
|
||||||
|
#>
|
||||||
function Get-CombinedSelectedFeatureIdsFromBackup {
|
function Get-CombinedSelectedFeatureIdsFromBackup {
|
||||||
param($SelectedBackup)
|
param($SelectedBackup)
|
||||||
|
|
||||||
@@ -139,12 +222,34 @@ function Get-CombinedSelectedFeatureIdsFromBackup {
|
|||||||
return @($featureIds.ToArray())
|
return @($featureIds.ToArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Convenience wrapper that returns all combined feature IDs from a backup.
|
||||||
|
|
||||||
|
.PARAMETER SelectedBackup
|
||||||
|
The parsed backup object.
|
||||||
|
#>
|
||||||
function Get-SelectedFeatureIdsFromBackup {
|
function Get-SelectedFeatureIdsFromBackup {
|
||||||
param($SelectedBackup)
|
param($SelectedBackup)
|
||||||
|
|
||||||
return @(Get-CombinedSelectedFeatureIdsFromBackup -SelectedBackup $SelectedBackup)
|
return @(Get-CombinedSelectedFeatureIdsFromBackup -SelectedBackup $SelectedBackup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Splits selected feature IDs into revertible and non-revertible lists for display.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Iterates the provided feature IDs, filters to those visible in the overview,
|
||||||
|
and separates them into auto-revertible (has a RegistryKey) and non-revertible
|
||||||
|
(requires manual undo) buckets. Each entry includes a display label.
|
||||||
|
|
||||||
|
.PARAMETER SelectedFeatureIds
|
||||||
|
The list of feature IDs to categorize.
|
||||||
|
|
||||||
|
.PARAMETER Features
|
||||||
|
A hashtable loaded from Features.json.
|
||||||
|
#>
|
||||||
function Get-RestoreBackupFeatureLists {
|
function Get-RestoreBackupFeatureLists {
|
||||||
param(
|
param(
|
||||||
[string[]]$SelectedFeatureIds,
|
[string[]]$SelectedFeatureIds,
|
||||||
|
|||||||
@@ -242,6 +242,10 @@ $script:Features = @{}
|
|||||||
try {
|
try {
|
||||||
$featuresData = Get-Content -Path $script:FeaturesFilePath -Raw | ConvertFrom-Json
|
$featuresData = Get-Content -Path $script:FeaturesFilePath -Raw | ConvertFrom-Json
|
||||||
foreach ($feature in $featuresData.Features) {
|
foreach ($feature in $featuresData.Features) {
|
||||||
|
if ([string]::IsNullOrWhiteSpace([string]$feature.FeatureId) -or [string]::IsNullOrWhiteSpace([string]$feature.Label) -or [string]::IsNullOrWhiteSpace([string]$feature.ApplyText)) {
|
||||||
|
Write-Warning "Feature '$($feature.FeatureId)' is missing a FeatureId, Label, or ApplyText in Features.json and will be skipped."
|
||||||
|
continue
|
||||||
|
}
|
||||||
$script:Features[$feature.FeatureId] = $feature
|
$script:Features[$feature.FeatureId] = $feature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user