2026-05-27 21:36:07 +02:00
|
|
|
# Resolves the path of an undo reg file relative to $script:RegfilesPath.
|
|
|
|
|
# Checks the Undo/ subfolder first, then falls back to the root Regfiles/ folder.
|
|
|
|
|
function Resolve-UndoRegFilePath {
|
|
|
|
|
param ([string]$FileName)
|
|
|
|
|
$undoSubPath = Join-Path 'Undo' $FileName
|
|
|
|
|
if (Test-Path (Join-Path $script:RegfilesPath $undoSubPath)) {
|
|
|
|
|
return $undoSubPath
|
|
|
|
|
}
|
|
|
|
|
return $FileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Invoke-UndoFeatureAction {
|
|
|
|
|
param(
|
|
|
|
|
[Parameter(Mandatory)]
|
|
|
|
|
[string]$FeatureId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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)..."
|
|
|
|
|
EnableStoreSearchSuggestions
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'EnableWindowsSandbox' {
|
|
|
|
|
Write-Host "> Disabling Windows Sandbox..."
|
|
|
|
|
DisableWindowsFeature 'Containers-DisposableClientVM'
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'EnableWindowsSubsystemForLinux' {
|
|
|
|
|
Write-Host "> Disabling Windows Subsystem for Linux..."
|
|
|
|
|
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
|
|
|
|
DisableWindowsFeature 'VirtualMachinePlatform'
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-28 23:26:40 +02:00
|
|
|
default {
|
|
|
|
|
Write-Host "> No undo action defined for $FeatureId, skipping..." -ForegroundColor Yellow
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-27 21:36:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-15 22:58:06 +01:00
|
|
|
# Executes a single parameter/feature based on its key
|
|
|
|
|
# Parameters:
|
|
|
|
|
# $paramKey - The parameter name to execute
|
|
|
|
|
function ExecuteParameter {
|
|
|
|
|
param (
|
|
|
|
|
[string]$paramKey
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Check if this feature has metadata in Features.json
|
|
|
|
|
$feature = $null
|
|
|
|
|
if ($script:Features.ContainsKey($paramKey)) {
|
|
|
|
|
$feature = $script:Features[$paramKey]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# If feature has RegistryKey and ApplyText, use dynamic ImportRegistryFile
|
|
|
|
|
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
|
|
|
|
ImportRegistryFile "> $($feature.ApplyText)" $feature.RegistryKey
|
|
|
|
|
|
|
|
|
|
# Handle special cases that have additional logic after ImportRegistryFile
|
|
|
|
|
switch ($paramKey) {
|
|
|
|
|
'DisableBing' {
|
|
|
|
|
# Also remove the app package for Bing search
|
|
|
|
|
RemoveApps 'Microsoft.BingSearch'
|
|
|
|
|
}
|
|
|
|
|
'DisableCopilot' {
|
|
|
|
|
# Also remove the app package for Copilot
|
|
|
|
|
RemoveApps 'Microsoft.Copilot'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Handle features without RegistryKey or with special logic
|
|
|
|
|
switch ($paramKey) {
|
|
|
|
|
'RemoveApps' {
|
|
|
|
|
Write-Host "> Removing selected apps for $(GetFriendlyTargetUserName)..."
|
|
|
|
|
$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
|
|
|
|
|
}
|
|
|
|
|
'RemoveAppsCustom' {
|
|
|
|
|
Write-Host "> Removing selected apps..."
|
|
|
|
|
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
'RemoveCommApps' {
|
|
|
|
|
$appsList = 'Microsoft.windowscommunicationsapps', 'Microsoft.People'
|
|
|
|
|
Write-Host "> Removing Mail, Calendar and People apps..."
|
|
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'RemoveW11Outlook' {
|
|
|
|
|
$appsList = 'Microsoft.OutlookForWindows'
|
|
|
|
|
Write-Host "> Removing new Outlook for Windows app..."
|
|
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'RemoveGamingApps' {
|
|
|
|
|
$appsList = 'Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay'
|
|
|
|
|
Write-Host "> Removing gaming related apps..."
|
|
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'RemoveHPApps' {
|
|
|
|
|
$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'
|
|
|
|
|
Write-Host "> Removing HP apps..."
|
|
|
|
|
RemoveApps $appsList
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-20 16:29:06 +02:00
|
|
|
'DisableWidgets' {
|
|
|
|
|
Write-Host "> Disabling widgets on the taskbar & lock screen..."
|
|
|
|
|
# Stop widgets related processes before removing the app packages to prevent potential issues
|
|
|
|
|
Get-Process *Widget* | Stop-Process
|
|
|
|
|
|
|
|
|
|
RemoveApps 'Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime'
|
|
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
"EnableWindowsSandbox" {
|
|
|
|
|
Write-Host "> Enabling Windows Sandbox..."
|
|
|
|
|
EnableWindowsFeature "Containers-DisposableClientVM"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
"EnableWindowsSubsystemForLinux" {
|
|
|
|
|
Write-Host "> Enabling Windows Subsystem for Linux..."
|
|
|
|
|
EnableWindowsFeature "VirtualMachinePlatform"
|
|
|
|
|
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ClearStart' {
|
|
|
|
|
Write-Host "> Removing all pinned apps from the start menu for user $(GetUserName)..."
|
|
|
|
|
ReplaceStartMenu
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ReplaceStart' {
|
|
|
|
|
Write-Host "> Replacing the start menu for user $(GetUserName)..."
|
|
|
|
|
ReplaceStartMenu $script:Params.Item("ReplaceStart")
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ClearStartAllUsers' {
|
|
|
|
|
ReplaceStartMenuForAllUsers
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
'ReplaceStartAllUsers' {
|
|
|
|
|
ReplaceStartMenuForAllUsers $script:Params.Item("ReplaceStartAllUsers")
|
|
|
|
|
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)..."
|
|
|
|
|
DisableStoreSearchSuggestions
|
|
|
|
|
Write-Host ""
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Executes all selected parameters/features
|
|
|
|
|
function ExecuteAllChanges {
|
2026-05-20 16:29:06 +02:00
|
|
|
$script:RegistryImportFailures = 0
|
|
|
|
|
|
2026-03-15 22:58:06 +01:00
|
|
|
# Build list of actionable parameters (skip control params and data-only params)
|
|
|
|
|
$actionableKeys = @()
|
|
|
|
|
foreach ($paramKey in $script:Params.Keys) {
|
|
|
|
|
if ($script:ControlParams -contains $paramKey) { continue }
|
|
|
|
|
if ($paramKey -eq 'Apps') { continue }
|
|
|
|
|
if ($paramKey -eq 'CreateRestorePoint') { continue }
|
|
|
|
|
$actionableKeys += $paramKey
|
|
|
|
|
}
|
2026-05-08 21:19:52 +02:00
|
|
|
|
|
|
|
|
$hasRegistryBackedFeature = $false
|
|
|
|
|
foreach ($paramKey in $actionableKeys) {
|
|
|
|
|
if (-not $script:Features.ContainsKey($paramKey)) { continue }
|
|
|
|
|
|
|
|
|
|
$feature = $script:Features[$paramKey]
|
|
|
|
|
if ($feature -and -not [string]::IsNullOrWhiteSpace([string]$feature.RegistryKey)) {
|
|
|
|
|
$hasRegistryBackedFeature = $true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-27 21:36:07 +02:00
|
|
|
# Undo operations that write registry values also require a backup
|
2026-05-28 22:55:38 +02:00
|
|
|
if (-not $hasRegistryBackedFeature) {
|
|
|
|
|
foreach ($featureId in $script:UndoParams.Keys) {
|
|
|
|
|
$f = if ($script:Features.ContainsKey($featureId)) { $script:Features[$featureId] } else { $null }
|
|
|
|
|
if ($f -and $f.RegistryUndoKey) { $hasRegistryBackedFeature = $true; break }
|
|
|
|
|
}
|
2026-05-27 21:36:07 +02:00
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
|
2026-05-28 22:55:38 +02:00
|
|
|
$totalSteps = $actionableKeys.Count + $script:UndoParams.Count
|
2026-05-08 21:19:52 +02:00
|
|
|
if ($hasRegistryBackedFeature) { $totalSteps++ }
|
2026-03-15 22:58:06 +01:00
|
|
|
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
|
|
|
|
$currentStep = 0
|
2026-05-08 21:19:52 +02:00
|
|
|
|
|
|
|
|
if ($hasRegistryBackedFeature) {
|
|
|
|
|
$currentStep++
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
2026-05-17 18:50:36 +03:00
|
|
|
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating registry backup..."
|
2026-05-08 21:19:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "> Creating registry backup..."
|
2026-05-11 19:14:08 +02:00
|
|
|
try {
|
2026-05-28 22:55:38 +02:00
|
|
|
$undoSyntheticFeatures = @($script:UndoParams.Keys | ForEach-Object {
|
|
|
|
|
$f = if ($script:Features.ContainsKey($_)) { $script:Features[$_] } else { $null }
|
|
|
|
|
if ($f -and $f.RegistryUndoKey) {
|
|
|
|
|
[PSCustomObject]@{ FeatureId = $_; RegistryKey = (Resolve-UndoRegFilePath $f.RegistryUndoKey) }
|
|
|
|
|
}
|
|
|
|
|
} | Where-Object { $_ })
|
2026-05-27 21:36:07 +02:00
|
|
|
New-RegistrySettingsBackup -ActionableKeys $actionableKeys -ExtraFeatures $undoSyntheticFeatures | Out-Null
|
2026-05-11 19:14:08 +02:00
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
throw "Registry backup failed before applying changes. $($_.Exception.Message)"
|
|
|
|
|
}
|
2026-05-08 21:19:52 +02:00
|
|
|
}
|
2026-03-15 22:58:06 +01:00
|
|
|
|
|
|
|
|
# Create restore point if requested (CLI only - GUI handles this separately)
|
|
|
|
|
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
|
|
|
|
$currentStep++
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
2026-05-17 18:50:36 +03:00
|
|
|
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point, this may take a moment..."
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
2026-05-17 18:50:36 +03:00
|
|
|
Write-Host "> Creating a system restore point..."
|
2026-03-15 22:58:06 +01:00
|
|
|
CreateSystemRestorePoint
|
|
|
|
|
Write-Host ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Execute all parameters
|
|
|
|
|
foreach ($paramKey in $actionableKeys) {
|
2026-06-03 21:16:10 +02:00
|
|
|
if ($script:CancelRequested) { return }
|
2026-03-15 22:58:06 +01:00
|
|
|
|
|
|
|
|
$currentStep++
|
|
|
|
|
|
|
|
|
|
# Get friendly name for the step
|
|
|
|
|
$stepName = $paramKey
|
|
|
|
|
if ($script:Features.ContainsKey($paramKey)) {
|
|
|
|
|
$feature = $script:Features[$paramKey]
|
|
|
|
|
if ($feature.ApplyText) {
|
|
|
|
|
# Prefer explicit ApplyText when provided
|
|
|
|
|
$stepName = $feature.ApplyText
|
|
|
|
|
} elseif ($feature.Label) {
|
2026-05-08 21:19:52 +02:00
|
|
|
# Fallback: use label from Features.json
|
|
|
|
|
$stepName = $feature.Label
|
2026-03-15 22:58:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
|
|
|
|
& $script:ApplyProgressCallback $currentStep $totalSteps $stepName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExecuteParameter -paramKey $paramKey
|
|
|
|
|
}
|
2026-05-20 16:29:06 +02:00
|
|
|
|
2026-05-27 21:36:07 +02:00
|
|
|
# Execute all undo operations
|
2026-05-28 22:55:38 +02:00
|
|
|
foreach ($featureId in $script:UndoParams.Keys) {
|
2026-05-27 21:36:07 +02:00
|
|
|
if ($script:CancelRequested) { return }
|
|
|
|
|
|
2026-06-03 21:16:10 +02:00
|
|
|
$f = if ($script:Features.ContainsKey($featureId)) { $script:Features[$featureId] } else { $null }
|
|
|
|
|
$undoLabel = if ($f -and $f.UndoLabel) { $f.UndoLabel } else { $featureId }
|
|
|
|
|
$applyUndoText = if ($f -and $f.ApplyUndoText) { $f.ApplyUndoText } else { $undoLabel }
|
2026-05-27 21:36:07 +02:00
|
|
|
|
|
|
|
|
$currentStep++
|
|
|
|
|
if ($script:ApplyProgressCallback) {
|
2026-06-03 21:16:10 +02:00
|
|
|
& $script:ApplyProgressCallback $currentStep $totalSteps $applyUndoText
|
2026-05-27 21:36:07 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 22:55:38 +02:00
|
|
|
if ($f -and $f.RegistryUndoKey) {
|
2026-06-03 21:16:10 +02:00
|
|
|
ImportRegistryFile "> $applyUndoText" (Resolve-UndoRegFilePath $f.RegistryUndoKey)
|
2026-05-28 22:55:38 +02:00
|
|
|
} else {
|
|
|
|
|
Invoke-UndoFeatureAction -FeatureId $featureId
|
2026-05-27 21:36:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-03-15 22:58:06 +01:00
|
|
|
}
|