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:
@@ -1,17 +1,10 @@
|
||||
function Get-FeatureId {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
$Feature
|
||||
)
|
||||
|
||||
$featureId = [string]$Feature.FeatureId
|
||||
if ([string]::IsNullOrWhiteSpace($featureId)) {
|
||||
throw 'Selected feature is missing required FeatureId.'
|
||||
}
|
||||
|
||||
return $featureId
|
||||
}
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Filters a list of features to those that have a non-empty RegistryKey.
|
||||
|
||||
.PARAMETER Features
|
||||
An array of feature objects to filter.
|
||||
#>
|
||||
function Get-RegistryBackedFeatures {
|
||||
param(
|
||||
[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 {
|
||||
param(
|
||||
[string[]]$ActionableKeys,
|
||||
@@ -32,6 +47,13 @@ function New-RegistrySettingsBackup {
|
||||
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 {
|
||||
param(
|
||||
[string[]]$ActionableKeys
|
||||
@@ -46,8 +68,7 @@ function Get-SelectedFeatures {
|
||||
$feature = $script:Features[$paramKey]
|
||||
if (-not $feature) { continue }
|
||||
|
||||
$featureId = Get-FeatureId -Feature $feature
|
||||
|
||||
$featureId = [string]$feature.FeatureId
|
||||
if ($selectedFeatureIds.Add($featureId)) {
|
||||
$selectedFeatures.Add($feature)
|
||||
}
|
||||
@@ -56,6 +77,23 @@ function Get-SelectedFeatures {
|
||||
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 {
|
||||
param(
|
||||
[object[]]$SelectedFeatures = @(),
|
||||
@@ -67,8 +105,7 @@ function Get-RegistryBackupPayload {
|
||||
$selectedFeatureIds = New-Object System.Collections.Generic.List[string]
|
||||
$seenSelectedFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($feature in $SelectedFeatures) {
|
||||
$featureId = Get-FeatureId -Feature $feature
|
||||
|
||||
$featureId = [string]$feature.FeatureId
|
||||
if ($seenSelectedFeatureIds.Add($featureId)) {
|
||||
$selectedFeatureIds.Add($featureId)
|
||||
}
|
||||
@@ -77,8 +114,7 @@ function Get-RegistryBackupPayload {
|
||||
$selectedUndoFeatureIds = New-Object System.Collections.Generic.List[string]
|
||||
$seenUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
foreach ($feature in $UndoFeatures) {
|
||||
$featureId = Get-FeatureId -Feature $feature
|
||||
|
||||
$featureId = [string]$feature.FeatureId
|
||||
if ($seenUndoFeatureIds.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.
|
||||
# Returns $false if the feature has no RegistryKey, the file is missing, or any operation mismatches.
|
||||
<#
|
||||
.SYNOPSIS
|
||||
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 {
|
||||
param(
|
||||
[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 {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FeatureId
|
||||
)
|
||||
|
||||
if (-not $script:Features.ContainsKey($FeatureId)) { return $false }
|
||||
$feature = $script:Features[$FeatureId]
|
||||
|
||||
switch ($FeatureId) {
|
||||
@@ -147,8 +163,18 @@ function Test-FeatureApplied {
|
||||
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 {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
|
||||
@@ -16,15 +16,11 @@ function Invoke-FeatureApply {
|
||||
)
|
||||
|
||||
# Resolve feature metadata from Features.json
|
||||
$feature = $null
|
||||
if ($script:Features.ContainsKey($FeatureId)) {
|
||||
$feature = $script:Features[$FeatureId]
|
||||
}
|
||||
|
||||
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
||||
$feature = $script:Features[$FeatureId]
|
||||
$applyText = $feature.ApplyText
|
||||
|
||||
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
||||
if ($feature -and $feature.RegistryKey) {
|
||||
if ($feature.RegistryKey) {
|
||||
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
||||
|
||||
# Post-import side effects for specific features
|
||||
@@ -177,15 +173,13 @@ function Invoke-FeatureUndo {
|
||||
return
|
||||
}
|
||||
'EnableWindowsSandbox' {
|
||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Sandbox' }
|
||||
Write-Host "> $undoText..."
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
DisableWindowsFeature 'Containers-DisposableClientVM'
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSubsystemForLinux' {
|
||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Subsystem for Linux' }
|
||||
Write-Host "> $undoText..."
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
||||
DisableWindowsFeature 'VirtualMachinePlatform'
|
||||
Write-Host ""
|
||||
@@ -245,15 +239,8 @@ function Invoke-ApplyFeatures {
|
||||
if ($script:CancelRequested) { return }
|
||||
|
||||
# Resolve display name for the progress indicator
|
||||
$displayName = $featureId
|
||||
if ($script:Features.ContainsKey($featureId)) {
|
||||
$f = $script:Features[$featureId]
|
||||
if ($f.ApplyText) {
|
||||
$displayName = $f.ApplyText
|
||||
} elseif ($f.Label) {
|
||||
$displayName = $f.Label
|
||||
}
|
||||
}
|
||||
$f = $script:Features[$featureId]
|
||||
$displayName = $f.ApplyText
|
||||
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $step $TotalSteps $displayName
|
||||
@@ -345,7 +332,6 @@ function Invoke-AllChanges {
|
||||
# ---- Determine if registry backup is needed ----
|
||||
$needsBackup = $false
|
||||
foreach ($id in $applyIds) {
|
||||
if (-not $script:Features.ContainsKey($id)) { continue }
|
||||
$f = $script:Features[$id]
|
||||
if ($f -and -not [string]::IsNullOrWhiteSpace([string]$f.RegistryKey)) {
|
||||
$needsBackup = $true
|
||||
|
||||
Reference in New Issue
Block a user