2026-06-22 21:43:53 +02:00
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Applies a single feature/debloat operation.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Handles two categories of features:
|
|
|
|
|
- Registry-backed: imports the .reg file via ImportRegistryFile, then runs
|
|
|
|
|
any post-import side effects (e.g., removing companion app packages).
|
|
|
|
|
- Custom logic: app removal, Windows optional features, start menu
|
|
|
|
|
replacement, and other special-case features.
|
|
|
|
|
#>
|
|
|
|
|
function Invoke-FeatureApply {
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string]$FeatureId
|
2026-03-15 22:58:06 +01:00
|
|
|
)
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# Resolve feature metadata from Features.json
|
2026-03-15 22:58:06 +01:00
|
|
|
$feature = $null
|
2026-06-22 21:43:53 +02:00
|
|
|
if ($script:Features.ContainsKey($FeatureId)) {
|
|
|
|
|
$feature = $script:Features[$FeatureId]
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-06-22 22:13:01 +02:00
|
|
|
|
|
|
|
|
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
2026-06-22 22:13:01 +02:00
|
|
|
if ($feature -and $feature.RegistryKey) {
|
|
|
|
|
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# Post-import side effects for specific features
|
|
|
|
|
switch ($FeatureId) {
|
2026-03-15 22:58:06 +01:00
|
|
|
'DisableBing' {
|
|
|
|
|
# Also remove the app package for Bing search
|
2026-06-10 17:40:31 +02:00
|
|
|
RemoveApps @('Microsoft.BingSearch')
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
|
|
|
|
'DisableCopilot' {
|
2026-06-22 22:13:01 +02:00
|
|
|
# Also remove the app packages for Copilot
|
|
|
|
|
RemoveApps @('Microsoft.Copilot', 'XP9CXNGPPJ97XX')
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-06-21 00:04:46 +07:00
|
|
|
'DisableTelemetry' {
|
|
|
|
|
# Also disable telemetry scheduled tasks
|
|
|
|
|
Disable-TelemetryScheduledTasks
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# ---- Custom features (no registry backing, or special handling required) ----
|
|
|
|
|
switch ($FeatureId) {
|
2026-03-15 22:58:06 +01:00
|
|
|
'RemoveApps' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText for $(GetFriendlyTargetUserName)..."
|
2026-03-15 22:58:06 +01:00
|
|
|
$appsList = GenerateAppsList
|
|
|
|
|
|
|
|
|
|
if ($appsList.Count -eq 0) {
|
|
|
|
|
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "$($appsList.Count) apps selected for removal"
|
|
|
|
|
RemoveApps $appsList
|
2026-06-22 21:43:53 +02:00
|
|
|
return
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
|
|
|
|
'RemoveGamingApps' {
|
2026-06-10 17:40:31 +02:00
|
|
|
$appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText..."
|
2026-03-15 22:58:06 +01:00
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'RemoveHPApps' {
|
2026-06-10 17:40:31 +02:00
|
|
|
$appsList = @('AD2F1837.HPAIExperienceCenter', 'AD2F1837.HPJumpStarts', 'AD2F1837.HPPCHardwareDiagnosticsWindows', 'AD2F1837.HPPowerManager', 'AD2F1837.HPPrivacySettings', 'AD2F1837.HPSupportAssistant', 'AD2F1837.HPSureShieldAI', 'AD2F1837.HPSystemInformation', 'AD2F1837.HPQuickDrop', 'AD2F1837.HPWorkWell', 'AD2F1837.myHP', 'AD2F1837.HPDesktopSupportUtilities', 'AD2F1837.HPQuickTouch', 'AD2F1837.HPEasyClean', 'AD2F1837.HPConnectedMusic', 'AD2F1837.HPFileViewer', 'AD2F1837.HPRegistration', 'AD2F1837.HPWelcome', 'AD2F1837.HPConnectedPhotopoweredbySnapfish', 'AD2F1837.HPPrinterControl')
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText..."
|
2026-03-15 22:58:06 +01:00
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-20 16:29:06 +02:00
|
|
|
'DisableWidgets' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText..."
|
2026-05-20 16:29:06 +02:00
|
|
|
# Stop widgets related processes before removing the app packages to prevent potential issues
|
2026-06-22 02:30:31 +07:00
|
|
|
if (-not $script:Params.ContainsKey("WhatIf")) {
|
|
|
|
|
Get-Process *Widget* -ErrorAction SilentlyContinue | Stop-Process
|
|
|
|
|
}
|
2026-06-10 17:40:31 +02:00
|
|
|
|
|
|
|
|
RemoveApps @('Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime')
|
2026-06-22 21:43:53 +02:00
|
|
|
return
|
2026-05-20 16:29:06 +02:00
|
|
|
}
|
2026-06-10 17:40:31 +02:00
|
|
|
'EnableWindowsSandbox' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText..."
|
2026-03-15 22:58:06 +01:00
|
|
|
EnableWindowsFeature "Containers-DisposableClientVM"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-06-10 17:40:31 +02:00
|
|
|
'EnableWindowsSubsystemForLinux' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText..."
|
2026-03-15 22:58:06 +01:00
|
|
|
EnableWindowsFeature "VirtualMachinePlatform"
|
|
|
|
|
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ClearStart' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText for user $(GetUserName)..."
|
2026-06-18 23:02:25 +02:00
|
|
|
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
2026-06-22 21:43:53 +02:00
|
|
|
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
|
|
|
|
ReplaceStartMenu -startMenuBinFile $startMenuBinFile
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ReplaceStart' {
|
2026-06-22 21:43:53 +02:00
|
|
|
Write-Host "> $applyText for user $(GetUserName)..."
|
2026-06-18 23:02:25 +02:00
|
|
|
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
2026-06-22 21:43:53 +02:00
|
|
|
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
|
|
|
|
ReplaceStartMenu -startMenuBinFile $startMenuBinFile -startMenuTemplate $script:Params.Item("ReplaceStart")
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ClearStartAllUsers' {
|
|
|
|
|
ReplaceStartMenuForAllUsers
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ReplaceStartAllUsers' {
|
2026-06-18 23:02:25 +02:00
|
|
|
ReplaceStartMenuForAllUsers -startMenuTemplate $script:Params.Item("ReplaceStartAllUsers")
|
2026-03-15 22:58:06 +01:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'DisableStoreSearchSuggestions' {
|
|
|
|
|
if ($script:Params.ContainsKey("Sysprep")) {
|
|
|
|
|
Write-Host "> Disabling Microsoft Store search suggestions in the start menu for all users..."
|
|
|
|
|
DisableStoreSearchSuggestionsForAllUsers
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "> Disabling Microsoft Store search suggestions for user $(GetUserName)..."
|
2026-06-21 01:56:28 +02:00
|
|
|
$storeDb = GetStoreAppsDatabasePathForUser -UserName (GetUserName)
|
|
|
|
|
if ($storeDb) {
|
|
|
|
|
DisableStoreSearchSuggestions -StoreAppsDatabase $storeDb
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Undoes a single feature that has no RegistryUndoKey.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Handles undo for features that require custom logic rather than a simple
|
|
|
|
|
.reg file import. Features with a RegistryUndoKey are handled directly
|
|
|
|
|
via ImportRegistryFile in Invoke-UndoFeatures.
|
|
|
|
|
#>
|
|
|
|
|
function Invoke-FeatureUndo {
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string]$FeatureId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$feature = if ($script:Features.ContainsKey($FeatureId)) { $script:Features[$FeatureId] } else { $null }
|
|
|
|
|
|
|
|
|
|
switch ($FeatureId) {
|
|
|
|
|
'DisableStoreSearchSuggestions' {
|
|
|
|
|
if ($script:Params.ContainsKey('Sysprep')) {
|
|
|
|
|
Write-Host "> Re-enabling Microsoft Store search suggestions in the start menu for all users..."
|
|
|
|
|
EnableStoreSearchSuggestionsForAllUsers
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "> Re-enabling Microsoft Store search suggestions for user $(GetUserName)..."
|
|
|
|
|
$storeDb = GetStoreAppsDatabasePathForUser -UserName (GetUserName)
|
|
|
|
|
if ($storeDb) {
|
|
|
|
|
EnableStoreSearchSuggestions -StoreAppsDatabase $storeDb
|
|
|
|
|
}
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'EnableWindowsSandbox' {
|
|
|
|
|
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Sandbox' }
|
|
|
|
|
Write-Host "> $undoText..."
|
|
|
|
|
DisableWindowsFeature 'Containers-DisposableClientVM'
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'EnableWindowsSubsystemForLinux' {
|
|
|
|
|
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Subsystem for Linux' }
|
|
|
|
|
Write-Host "> $undoText..."
|
|
|
|
|
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
|
|
|
|
DisableWindowsFeature 'VirtualMachinePlatform'
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'DisableTelemetry' {
|
|
|
|
|
# Also re-enable telemetry scheduled tasks
|
|
|
|
|
Enable-TelemetryScheduledTasks
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Resolves the path of an undo .reg file relative to $script:RegfilesPath.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Checks the Undo/ subfolder first, then falls back to the root Regfiles/
|
|
|
|
|
folder. This allows undo files to be organized separately from apply files.
|
|
|
|
|
#>
|
|
|
|
|
function Resolve-UndoRegFilePath {
|
|
|
|
|
param([string]$FileName)
|
|
|
|
|
|
|
|
|
|
$undoSubPath = Join-Path 'Undo' $FileName
|
|
|
|
|
if (Test-Path (Join-Path $script:RegfilesPath $undoSubPath)) {
|
|
|
|
|
return $undoSubPath
|
|
|
|
|
}
|
|
|
|
|
return $FileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Applies a list of features, reporting progress for each.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Iterates through the provided feature IDs and calls Invoke-FeatureApply
|
|
|
|
|
for each. Handles progress callbacks (GUI mode) and cancellation checks.
|
|
|
|
|
This is called by Invoke-AllChanges during the apply phase.
|
|
|
|
|
#>
|
|
|
|
|
function Invoke-ApplyFeatures {
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string[]]$FeatureIds,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[int]$StartStep,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[int]$TotalSteps
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ($FeatureIds.Count -eq 0) { return }
|
|
|
|
|
|
|
|
|
|
$step = $StartStep
|
|
|
|
|
foreach ($featureId in $FeatureIds) {
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
|
|
|
|
& $script:ApplyProgressCallback $step $TotalSteps $displayName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invoke-FeatureApply -FeatureId $featureId
|
|
|
|
|
$step++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Undoes a list of features, reporting progress for each.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Iterates through the provided feature IDs. Features with a RegistryUndoKey
|
|
|
|
|
are handled by importing the undo .reg file; all others delegate to
|
|
|
|
|
Invoke-FeatureUndo for custom undo logic.
|
|
|
|
|
This is called by Invoke-AllChanges during the undo phase.
|
|
|
|
|
#>
|
|
|
|
|
function Invoke-UndoFeatures {
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string[]]$FeatureIds,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[int]$StartStep,
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[int]$TotalSteps
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if ($FeatureIds.Count -eq 0) { return }
|
|
|
|
|
|
|
|
|
|
$step = $StartStep
|
|
|
|
|
foreach ($featureId in $FeatureIds) {
|
|
|
|
|
if ($script:CancelRequested) { return }
|
|
|
|
|
|
|
|
|
|
$f = if ($script:Features.ContainsKey($featureId)) { $script:Features[$featureId] } else { $null }
|
|
|
|
|
$undoLabel = if ($f -and $f.UndoLabel) { $f.UndoLabel } else { $featureId }
|
|
|
|
|
$undoText = if ($f -and $f.ApplyUndoText) { $f.ApplyUndoText } else { $undoLabel }
|
|
|
|
|
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
|
|
|
|
& $script:ApplyProgressCallback $step $TotalSteps $undoText
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($f -and $f.RegistryUndoKey) {
|
|
|
|
|
ImportRegistryFile "> $undoText" (Resolve-UndoRegFilePath $f.RegistryUndoKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Invoke-FeatureUndo -FeatureId $featureId
|
|
|
|
|
$step++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Main orchestrator: applies and undoes all selected features.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Sequenced in four phases:
|
|
|
|
|
1. Registry backup
|
|
|
|
|
2. System restore point
|
|
|
|
|
3. Apply phase - applies all selected features via Invoke-ApplyFeatures
|
|
|
|
|
4. Undo phase - undoes selected features via Invoke-UndoFeatures
|
|
|
|
|
|
|
|
|
|
Progress is reported through $script:ApplyProgressCallback when set
|
|
|
|
|
(used by the GUI modal). Cancellation is checked between each step.
|
|
|
|
|
#>
|
|
|
|
|
function Invoke-AllChanges {
|
|
|
|
|
# Guard: prevent running as SYSTEM account without explicit target user
|
2026-06-07 22:51:01 +02:00
|
|
|
$isSystem = ([Security.Principal.WindowsIdentity]::GetCurrent().User.Value -eq 'S-1-5-18')
|
|
|
|
|
if ($isSystem -and -not $script:Params.ContainsKey("User") -and -not $script:Params.ContainsKey("Sysprep")) {
|
|
|
|
|
throw "Win11Debloat is running as the SYSTEM account. Use the '-User' or '-Sysprep' parameter to target a specific user."
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 16:29:06 +02:00
|
|
|
$script:RegistryImportFailures = 0
|
|
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ---- Gather work items ----
|
|
|
|
|
$applyIds = @()
|
|
|
|
|
foreach ($key in $script:Params.Keys) {
|
|
|
|
|
if ($script:ControlParams -contains $key) { continue }
|
|
|
|
|
if ($key -eq 'Apps') { continue }
|
|
|
|
|
if ($key -eq 'CreateRestorePoint') { continue }
|
|
|
|
|
$applyIds += $key
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-06-22 21:43:53 +02:00
|
|
|
$undoIds = @($script:UndoParams.Keys)
|
2026-05-08 21:19:52 +02:00
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ---- 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
|
2026-05-08 21:19:52 +02:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-22 21:43:53 +02:00
|
|
|
if (-not $needsBackup) {
|
|
|
|
|
foreach ($id in $undoIds) {
|
|
|
|
|
$f = if ($script:Features.ContainsKey($id)) { $script:Features[$id] } else { $null }
|
|
|
|
|
if ($f -and $f.RegistryUndoKey) { $needsBackup = $true; break }
|
2026-06-10 17:40:31 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# ---- Calculate total progress steps ----
|
|
|
|
|
$totalSteps = $applyIds.Count + $undoIds.Count
|
|
|
|
|
if ($needsBackup) { $totalSteps++ }
|
2026-03-15 22:58:06 +01:00
|
|
|
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
2026-06-22 21:43:53 +02:00
|
|
|
$step = 0
|
2026-05-08 21:19:52 +02:00
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# Phase 1: Registry backup
|
|
|
|
|
# ================================================================
|
|
|
|
|
if ($needsBackup) {
|
|
|
|
|
$step++
|
2026-05-08 21:19:52 +02:00
|
|
|
if ($script:ApplyProgressCallback) {
|
2026-06-22 21:43:53 +02:00
|
|
|
& $script:ApplyProgressCallback $step $totalSteps "Creating registry backup..."
|
2026-05-08 21:19:52 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-22 02:30:31 +07:00
|
|
|
if ($script:Params.ContainsKey("WhatIf")) {
|
|
|
|
|
Write-Host "[WhatIf] Create registry backup" -ForegroundColor Cyan
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Write-Host "> Creating registry backup..."
|
|
|
|
|
try {
|
2026-06-22 21:43:53 +02:00
|
|
|
$undoSyntheticFeatures = @($undoIds | ForEach-Object {
|
2026-06-22 02:30:31 +07:00
|
|
|
$f = if ($script:Features.ContainsKey($_)) { $script:Features[$_] } else { $null }
|
|
|
|
|
if ($f -and $f.RegistryUndoKey) {
|
|
|
|
|
[PSCustomObject]@{ FeatureId = $_; RegistryKey = (Resolve-UndoRegFilePath $f.RegistryUndoKey) }
|
|
|
|
|
}
|
|
|
|
|
} | Where-Object { $_ })
|
2026-06-22 21:43:53 +02:00
|
|
|
New-RegistrySettingsBackup -ActionableKeys $applyIds -ExtraFeatures $undoSyntheticFeatures | Out-Null
|
2026-06-22 02:30:31 +07:00
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
throw "Registry backup failed before applying changes. $($_.Exception.Message)"
|
|
|
|
|
}
|
2026-05-11 19:14:08 +02:00
|
|
|
}
|
2026-05-08 21:19:52 +02:00
|
|
|
}
|
2026-06-22 21:43:53 +02:00
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# Phase 2: System restore point
|
|
|
|
|
# ================================================================
|
2026-03-15 22:58:06 +01:00
|
|
|
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
2026-06-22 21:43:53 +02:00
|
|
|
$step++
|
2026-03-15 22:58:06 +01:00
|
|
|
if ($script:ApplyProgressCallback) {
|
2026-06-22 21:43:53 +02:00
|
|
|
& $script:ApplyProgressCallback $step $totalSteps "Creating system restore point, this may take a moment..."
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-06-22 02:30:31 +07:00
|
|
|
if ($script:Params.ContainsKey("WhatIf")) {
|
|
|
|
|
Write-Host "[WhatIf] Create system restore point" -ForegroundColor Cyan
|
|
|
|
|
Write-Host ""
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Write-Host "> Creating a system restore point..."
|
|
|
|
|
CreateSystemRestorePoint
|
|
|
|
|
Write-Host ""
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# Phase 3: Apply features
|
|
|
|
|
# ================================================================
|
|
|
|
|
if ($applyIds.Count -gt 0) {
|
|
|
|
|
Invoke-ApplyFeatures -FeatureIds $applyIds -StartStep ($step + 1) -TotalSteps $totalSteps
|
|
|
|
|
$step += $applyIds.Count
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-05-20 16:29:06 +02:00
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# Phase 4: Undo features
|
|
|
|
|
# ================================================================
|
|
|
|
|
if ($undoIds.Count -gt 0) {
|
|
|
|
|
Invoke-UndoFeatures -FeatureIds $undoIds -StartStep ($step + 1) -TotalSteps $totalSteps
|
|
|
|
|
$step += $undoIds.Count
|
2026-06-10 17:40:31 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-22 21:43:53 +02:00
|
|
|
# ================================================================
|
|
|
|
|
# Final: Report registry import failures
|
|
|
|
|
# ================================================================
|
2026-05-20 16:29:06 +02:00
|
|
|
if ($script:RegistryImportFailures -gt 0) {
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow
|
|
|
|
|
}
|
2026-06-10 17:40:31 +02:00
|
|
|
}
|