mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-07-02 22:58:34 +00:00
Refactor: ExecuteChanges to InvokeChanges, clean up for readability (#641)
This commit is contained in:
@@ -1,23 +1,32 @@
|
||||
# Executes a single parameter/feature based on its key
|
||||
# Parameters:
|
||||
# $paramKey - The parameter name to execute
|
||||
function ExecuteParameter {
|
||||
param (
|
||||
[string]$paramKey
|
||||
<#
|
||||
.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
|
||||
)
|
||||
|
||||
# Check if this feature has metadata in Features.json
|
||||
|
||||
# Resolve feature metadata from Features.json
|
||||
$feature = $null
|
||||
if ($script:Features.ContainsKey($paramKey)) {
|
||||
$feature = $script:Features[$paramKey]
|
||||
if ($script:Features.ContainsKey($FeatureId)) {
|
||||
$feature = $script:Features[$FeatureId]
|
||||
}
|
||||
|
||||
# If feature has RegistryKey and ApplyText, use dynamic ImportRegistryFile
|
||||
|
||||
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
||||
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
||||
ImportRegistryFile "> $($feature.ApplyText)..." $feature.RegistryKey
|
||||
|
||||
# Handle special cases that have additional logic after ImportRegistryFile
|
||||
switch ($paramKey) {
|
||||
|
||||
# Post-import side effects for specific features
|
||||
switch ($FeatureId) {
|
||||
'DisableBing' {
|
||||
# Also remove the app package for Bing search
|
||||
RemoveApps @('Microsoft.BingSearch')
|
||||
@@ -33,11 +42,13 @@ function ExecuteParameter {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Handle features without RegistryKey or with special logic
|
||||
switch ($paramKey) {
|
||||
|
||||
# ---- Custom features (no registry backing, or special handling required) ----
|
||||
# Resolve a safe apply-text fallback in case the feature is missing from Features.json
|
||||
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
||||
switch ($FeatureId) {
|
||||
'RemoveApps' {
|
||||
Write-Host "> $($feature.ApplyText) for $(GetFriendlyTargetUserName)..."
|
||||
Write-Host "> $applyText for $(GetFriendlyTargetUserName)..."
|
||||
$appsList = GenerateAppsList
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
@@ -48,9 +59,10 @@ function ExecuteParameter {
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveAppsCustom' {
|
||||
Write-Host "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
@@ -61,52 +73,58 @@ function ExecuteParameter {
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveGamingApps' {
|
||||
$appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
|
||||
Write-Host "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
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 "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'DisableWidgets' {
|
||||
Write-Host "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
# Stop widgets related processes before removing the app packages to prevent potential issues
|
||||
if (-not $script:Params.ContainsKey("WhatIf")) {
|
||||
Get-Process *Widget* -ErrorAction SilentlyContinue | Stop-Process
|
||||
}
|
||||
|
||||
RemoveApps @('Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime')
|
||||
return
|
||||
}
|
||||
'EnableWindowsSandbox' {
|
||||
Write-Host "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
EnableWindowsFeature "Containers-DisposableClientVM"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSubsystemForLinux' {
|
||||
Write-Host "> $($feature.ApplyText)..."
|
||||
Write-Host "> $applyText..."
|
||||
EnableWindowsFeature "VirtualMachinePlatform"
|
||||
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStart' {
|
||||
Write-Host "> $($feature.ApplyText) for user $(GetUserName)..."
|
||||
Write-Host "> $applyText for user $(GetUserName)..."
|
||||
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile
|
||||
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ReplaceStart' {
|
||||
Write-Host "> $($feature.ApplyText) for user $(GetUserName)..."
|
||||
Write-Host "> $applyText for user $(GetUserName)..."
|
||||
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile -startMenuTemplate $script:Params.Item("ReplaceStart")
|
||||
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile -startMenuTemplate $script:Params.Item("ReplaceStart")
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
@@ -138,156 +156,16 @@ function ExecuteParameter {
|
||||
}
|
||||
|
||||
|
||||
# Executes all selected parameters/features
|
||||
function ExecuteAllChanges {
|
||||
# When running as SYSTEM, require -User or -Sysprep to prevent applying
|
||||
# changes to the SYSTEM profile instead of a real user.
|
||||
$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."
|
||||
}
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Undoes a single feature that has no RegistryUndoKey.
|
||||
|
||||
$script:RegistryImportFailures = 0
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
$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
|
||||
}
|
||||
}
|
||||
# Undo operations that write registry values also require a backup
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
$totalSteps = $actionableKeys.Count + $script:UndoParams.Count
|
||||
if ($hasRegistryBackedFeature) { $totalSteps++ }
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||
$currentStep = 0
|
||||
|
||||
if ($hasRegistryBackedFeature) {
|
||||
$currentStep++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating registry backup..."
|
||||
}
|
||||
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Create registry backup" -ForegroundColor Cyan
|
||||
}
|
||||
else {
|
||||
Write-Host "> Creating registry backup..."
|
||||
try {
|
||||
$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 { $_ })
|
||||
New-RegistrySettingsBackup -ActionableKeys $actionableKeys -ExtraFeatures $undoSyntheticFeatures | Out-Null
|
||||
}
|
||||
catch {
|
||||
throw "Registry backup failed before applying changes. $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create restore point if requested (CLI only - GUI handles this separately)
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||
$currentStep++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point, this may take a moment..."
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
|
||||
# Execute all parameters
|
||||
foreach ($paramKey in $actionableKeys) {
|
||||
if ($script:CancelRequested) { return }
|
||||
|
||||
$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) {
|
||||
# Fallback: use label from Features.json
|
||||
$stepName = $feature.Label
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps $stepName
|
||||
}
|
||||
|
||||
ExecuteParameter -paramKey $paramKey
|
||||
}
|
||||
|
||||
# Execute all undo operations
|
||||
foreach ($featureId in $script:UndoParams.Keys) {
|
||||
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 }
|
||||
$applyUndoText = if ($f -and $f.ApplyUndoText) { $f.ApplyUndoText } else { $undoLabel }
|
||||
|
||||
$currentStep++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps $applyUndoText
|
||||
}
|
||||
|
||||
if ($f -and $f.RegistryUndoKey) {
|
||||
ImportRegistryFile "> $applyUndoText" (Resolve-UndoRegFilePath $f.RegistryUndoKey)
|
||||
}
|
||||
|
||||
Invoke-UndoFeatureAction -FeatureId $featureId
|
||||
}
|
||||
|
||||
if ($script:RegistryImportFailures -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# 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 {
|
||||
.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
|
||||
@@ -313,13 +191,15 @@ function Invoke-UndoFeatureAction {
|
||||
return
|
||||
}
|
||||
'EnableWindowsSandbox' {
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Sandbox' }
|
||||
Write-Host "> $undoText..."
|
||||
DisableWindowsFeature 'Containers-DisposableClientVM'
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSubsystemForLinux' {
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
$undoText = if ($feature) { $feature.ApplyUndoText } else { 'Disabling Windows Subsystem for Linux' }
|
||||
Write-Host "> $undoText..."
|
||||
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
||||
DisableWindowsFeature 'VirtualMachinePlatform'
|
||||
Write-Host ""
|
||||
@@ -332,3 +212,242 @@ function Invoke-UndoFeatureAction {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<#
|
||||
.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
|
||||
$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."
|
||||
}
|
||||
|
||||
$script:RegistryImportFailures = 0
|
||||
|
||||
# ---- 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
|
||||
}
|
||||
$undoIds = @($script:UndoParams.Keys)
|
||||
|
||||
# ---- 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
|
||||
break
|
||||
}
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
# ---- Calculate total progress steps ----
|
||||
$totalSteps = $applyIds.Count + $undoIds.Count
|
||||
if ($needsBackup) { $totalSteps++ }
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||
$step = 0
|
||||
|
||||
# ================================================================
|
||||
# Phase 1: Registry backup
|
||||
# ================================================================
|
||||
if ($needsBackup) {
|
||||
$step++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $step $totalSteps "Creating registry backup..."
|
||||
}
|
||||
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Create registry backup" -ForegroundColor Cyan
|
||||
}
|
||||
else {
|
||||
Write-Host "> Creating registry backup..."
|
||||
try {
|
||||
$undoSyntheticFeatures = @($undoIds | 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 { $_ })
|
||||
New-RegistrySettingsBackup -ActionableKeys $applyIds -ExtraFeatures $undoSyntheticFeatures | Out-Null
|
||||
}
|
||||
catch {
|
||||
throw "Registry backup failed before applying changes. $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ================================================================
|
||||
# Phase 2: System restore point
|
||||
# ================================================================
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||
$step++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $step $totalSteps "Creating system restore point, this may take a moment..."
|
||||
}
|
||||
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 ""
|
||||
}
|
||||
}
|
||||
|
||||
# ================================================================
|
||||
# Phase 3: Apply features
|
||||
# ================================================================
|
||||
if ($applyIds.Count -gt 0) {
|
||||
Invoke-ApplyFeatures -FeatureIds $applyIds -StartStep ($step + 1) -TotalSteps $totalSteps
|
||||
$step += $applyIds.Count
|
||||
}
|
||||
|
||||
# ================================================================
|
||||
# Phase 4: Undo features
|
||||
# ================================================================
|
||||
if ($undoIds.Count -gt 0) {
|
||||
Invoke-UndoFeatures -FeatureIds $undoIds -StartStep ($step + 1) -TotalSteps $totalSteps
|
||||
$step += $undoIds.Count
|
||||
}
|
||||
|
||||
# ================================================================
|
||||
# Final: Report registry import failures
|
||||
# ================================================================
|
||||
if ($script:RegistryImportFailures -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
# Replace the startmenu for all users, when using the default startmenuTemplate this clears all pinned apps
|
||||
# Credit: https://lazyadmin.nl/win-11/customize-windows-11-start-menu-layout/
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Replaces the start menu layout for all user profiles.
|
||||
|
||||
.DESCRIPTION
|
||||
Iterates over every existing user profile and the Default user profile,
|
||||
replacing each user's start2.bin file with the specified template. When
|
||||
using the default template, this clears all pinned apps from the start menu.
|
||||
|
||||
Credit: https://lazyadmin.nl/win-11/customize-windows-11-start-menu-layout/
|
||||
|
||||
.PARAMETER startMenuTemplate
|
||||
Path to the .bin template file to apply. Defaults to the blank template
|
||||
bundled with the script (Assets/Start/start2.bin).
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenuForAllUsers
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenuForAllUsers -startMenuTemplate "C:\CustomLayout.bin"
|
||||
#>
|
||||
function ReplaceStartMenuForAllUsers {
|
||||
param (
|
||||
[string]$startMenuTemplate = "$script:AssetsPath\Start\start2.bin"
|
||||
@@ -44,8 +63,31 @@ function ReplaceStartMenuForAllUsers {
|
||||
}
|
||||
|
||||
|
||||
# Replace the startmenu at the specified location, when using the default startmenuTemplate this clears all pinned apps
|
||||
# Credit: https://lazyadmin.nl/win-11/customize-windows-11-start-menu-layout/
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Replaces the start menu layout for a single user.
|
||||
|
||||
.DESCRIPTION
|
||||
Backs up the current start2.bin file (if it exists), then copies the
|
||||
specified template over it. When using the default template this clears
|
||||
all pinned apps from the start menu. Validates that the template file
|
||||
exists and has a .bin extension before proceeding.
|
||||
|
||||
Credit: https://lazyadmin.nl/win-11/customize-windows-11-start-menu-layout/
|
||||
|
||||
.PARAMETER startMenuBinFile
|
||||
The full path to the user's start2.bin file to replace.
|
||||
|
||||
.PARAMETER startMenuTemplate
|
||||
Path to the .bin template file to apply. Defaults to the blank template
|
||||
bundled with the script (Assets/Start/start2.bin).
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -startMenuTemplate "C:\CustomLayout.bin"
|
||||
#>
|
||||
function ReplaceStartMenu {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
@@ -88,6 +130,24 @@ function ReplaceStartMenu {
|
||||
Write-Host "Replaced start menu for user $userName"
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Returns the full path to the start menu bin file for a given user.
|
||||
|
||||
.DESCRIPTION
|
||||
Resolves the path to the start2.bin file for the specified username.
|
||||
When no username is provided or the value is empty, falls back to
|
||||
the current user's local app data path via $env:LOCALAPPDATA.
|
||||
|
||||
.PARAMETER UserName
|
||||
The target username. Pass an empty string or omit to resolve for the current user.
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuBinPathForUser -UserName "Jeff"
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuBinPathForUser -UserName "Default"
|
||||
#>
|
||||
function GetStartMenuBinPathForUser {
|
||||
param(
|
||||
[string]$UserName
|
||||
@@ -100,6 +160,21 @@ function GetStartMenuBinPathForUser {
|
||||
return (GetUserDirectory -userName $UserName -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -exitIfPathNotFound $false)
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extracts the username from a start2.bin file path.
|
||||
|
||||
.DESCRIPTION
|
||||
Parses a typical C:\Users\<UserName>\AppData\... path and returns the
|
||||
username portion. Returns 'unknown' if the path does not match the
|
||||
expected pattern.
|
||||
|
||||
.PARAMETER StartMenuBinFile
|
||||
The full path to a start2.bin file.
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuUserNameFromPath -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
#>
|
||||
function GetStartMenuUserNameFromPath {
|
||||
param(
|
||||
[string]$StartMenuBinFile
|
||||
@@ -115,6 +190,29 @@ function GetStartMenuUserNameFromPath {
|
||||
|
||||
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores a user's start menu from a backup file.
|
||||
|
||||
.DESCRIPTION
|
||||
Moves the current start2.bin to a .restore.bak safety copy, then copies
|
||||
the specified backup file into place. Returns a PSCustomObject with
|
||||
UserName, Result ($true/$false), and Message properties describing
|
||||
the outcome.
|
||||
|
||||
.PARAMETER StartMenuBinFile
|
||||
The full path to the user's start2.bin file to restore.
|
||||
|
||||
.PARAMETER BackupFilePath
|
||||
Path to the backup file to restore from. If omitted, defaults to
|
||||
StartMenuBinFile with a .bak extension.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -BackupFilePath "C:\Backups\start2.bin"
|
||||
#>
|
||||
function RestoreStartMenuFromBackup {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -169,6 +267,26 @@ function RestoreStartMenuFromBackup {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores the start menu for the current target user from a backup.
|
||||
|
||||
.DESCRIPTION
|
||||
Resolves the start2.bin path for the current user (or the user specified
|
||||
via the -User parameter), then delegates to RestoreStartMenuFromBackup.
|
||||
Returns early with a warning if the user's start menu path cannot
|
||||
be resolved.
|
||||
|
||||
.PARAMETER BackupFilePath
|
||||
Path to the backup file to restore from. If omitted, defaults to
|
||||
the .bak file alongside the current start2.bin.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenu
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenu -BackupFilePath "C:\Backups\start2.bin"
|
||||
#>
|
||||
function RestoreStartMenu {
|
||||
param(
|
||||
[string]$BackupFilePath
|
||||
@@ -177,11 +295,40 @@ function RestoreStartMenu {
|
||||
$targetUserName = GetUserName
|
||||
$startMenuBinFile = GetStartMenuBinPathForUser -UserName $targetUserName
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
||||
Write-Host "Unable to resolve start menu path for user $targetUserName, nothing to restore" -ForegroundColor Yellow
|
||||
return [PSCustomObject]@{
|
||||
UserName = $targetUserName
|
||||
Result = $false
|
||||
Message = "Could not resolve start menu path for user $targetUserName."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Restoring start menu for user $targetUserName from backup..."
|
||||
|
||||
return RestoreStartMenuFromBackup -StartMenuBinFile $startMenuBinFile -BackupFilePath $BackupFilePath
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores the start menu for all user profiles from a backup.
|
||||
|
||||
.DESCRIPTION
|
||||
Iterates over every existing user profile and restores each user's
|
||||
start2.bin from its .bak backup. For the Default user profile, removes
|
||||
the start2.bin file (which was previously copied from a template) so
|
||||
that new profiles revert to the system default start menu.
|
||||
|
||||
.PARAMETER BackupFilePath
|
||||
Path to the backup file to restore from. If omitted, defaults to
|
||||
the .bak file alongside each user's current start2.bin.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuForAllUsers
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuForAllUsers -BackupFilePath "C:\Backups\start2.bin"
|
||||
#>
|
||||
function RestoreStartMenuForAllUsers {
|
||||
param(
|
||||
[string]$BackupFilePath
|
||||
|
||||
Reference in New Issue
Block a user