From 95b583606d0fb14ff1f9cebaa7ec11fbc342b0ca Mon Sep 17 00:00:00 2001 From: Jeffrey <9938813+Raphire@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:32:45 +0200 Subject: [PATCH] Update start menu backup/restore with timestamped filenames (#672) --- Scripts/Features/ReplaceStartMenu.ps1 | 122 +++++++++++++++++------ Scripts/GUI/Show-RestoreBackupDialog.ps1 | 30 ++---- 2 files changed, 102 insertions(+), 50 deletions(-) diff --git a/Scripts/Features/ReplaceStartMenu.ps1 b/Scripts/Features/ReplaceStartMenu.ps1 index 89bd47e..6314269 100644 --- a/Scripts/Features/ReplaceStartMenu.ps1 +++ b/Scripts/Features/ReplaceStartMenu.ps1 @@ -113,11 +113,15 @@ function ReplaceStartMenu { return } - $backupBinFile = $startMenuBinFile + ".bak" + $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' + $backupFileName = "Win11Debloat-StartBackup-$timestamp.bak" + $startMenuDir = Split-Path $startMenuBinFile -Parent + $backupBinFile = Join-Path $startMenuDir $backupFileName if (Test-Path $startMenuBinFile) { # Backup current start menu file - Move-Item -Path $startMenuBinFile -Destination $backupBinFile -Force + Copy-Item -Path $startMenuBinFile -Destination $backupBinFile -Force + Write-Verbose "Start menu backup for user $userName saved to $backupFileName" } else { Write-Host "Unable to find original start2.bin file for user $userName, no backup was created for this user" -ForegroundColor Yellow @@ -189,6 +193,55 @@ function GetStartMenuUserNameFromPath { } +<# + .SYNOPSIS + Returns the path to the latest start menu backup file for the given scope. + + .DESCRIPTION + Resolves the LocalState folder for the specified scope and returns the + full path to the most recent Win11Debloat-StartBackup-*.bak file, or + $null if no backup exists. + + For CurrentUser, uses $env:LOCALAPPDATA directly. For AllUsers, scans + every user profile. + + .PARAMETER Scope + The scope to check: CurrentUser or AllUsers. + + .EXAMPLE + $backupPath = Get-StartMenuBackupPath -Scope 'CurrentUser' + + .EXAMPLE + $backupPath = Get-StartMenuBackupPath -Scope 'AllUsers' +#> +function Get-StartMenuBackupPath { + param( + [Parameter(Mandatory)] + [ValidateSet('CurrentUser', 'AllUsers')] + [string]$Scope + ) + + if ($Scope -eq 'CurrentUser') { + $localStateDir = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" + $latestBackup = Get-ChildItem -Path (Join-Path $localStateDir 'Win11Debloat-StartBackup-*.bak') -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | + Select-Object -First 1 + if ($latestBackup) { return $latestBackup.FullName } + return $null + } + else { + $userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" + $usersStartMenuPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue + foreach ($startMenuPath in $usersStartMenuPaths) { + $latestBackup = Get-ChildItem -Path (Join-Path $startMenuPath.FullName 'Win11Debloat-StartBackup-*.bak') -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | + Select-Object -First 1 + if ($latestBackup) { return $latestBackup.FullName } + } + return $null + } +} + <# .SYNOPSIS @@ -204,14 +257,14 @@ function GetStartMenuUserNameFromPath { 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. + Path to the backup file to restore from. If omitted, automatically + finds the latest Win11Debloat-StartBackup-*.bak file. .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" + RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak" #> function RestoreStartMenuFromBackup { param( @@ -222,12 +275,28 @@ function RestoreStartMenuFromBackup { $userName = GetStartMenuUserNameFromPath -StartMenuBinFile $StartMenuBinFile $backupBinFile = if ([string]::IsNullOrWhiteSpace($BackupFilePath)) { - $StartMenuBinFile + '.bak' + # Auto-detect latest backup in the same folder as the start2.bin + $startMenuDir = Split-Path $StartMenuBinFile -Parent + $latestBackup = Get-ChildItem -Path (Join-Path $startMenuDir 'Win11Debloat-StartBackup-*.bak') -ErrorAction SilentlyContinue | + Sort-Object Name -Descending | + Select-Object -First 1 + + if ($latestBackup) { $latestBackup.FullName } else { $null } } else { $BackupFilePath } - $currentBinBackup = $StartMenuBinFile + '.restore.bak' + $restoreTimestamp = Get-Date -Format 'yyyyMMdd_HHmmss' + $restoreBackupFileName = "Win11Debloat-StartRestore-$restoreTimestamp.bak" + $currentBinBackup = Join-Path (Split-Path $StartMenuBinFile -Parent) $restoreBackupFileName + + if ([string]::IsNullOrWhiteSpace($backupBinFile)) { + return [PSCustomObject]@{ + UserName = $userName + Result = $false + Message = "No start menu backup file found for user $userName." + } + } if ($script:Params.ContainsKey("WhatIf")) { Write-Host "[WhatIf] Restore start menu for user $userName from backup $backupBinFile" -ForegroundColor Cyan @@ -272,37 +341,26 @@ function RestoreStartMenuFromBackup { 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. + Resolves the start2.bin path for the currently logged-in user, then + delegates to RestoreStartMenuFromBackup. .PARAMETER BackupFilePath - Path to the backup file to restore from. If omitted, defaults to - the .bak file alongside the current start2.bin. + Path to the backup file to restore from. If omitted, automatically + finds the latest Win11Debloat-StartBackup-*.bak file. .EXAMPLE RestoreStartMenu .EXAMPLE - RestoreStartMenu -BackupFilePath "C:\Backups\start2.bin" + RestoreStartMenu -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak" #> function RestoreStartMenu { param( [string]$BackupFilePath ) - $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." - } - } + $targetUserName = $env:USERNAME + $startMenuBinFile = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" Write-Host "Restoring start menu for user $targetUserName from backup..." @@ -315,19 +373,21 @@ function RestoreStartMenu { .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. + start2.bin from the latest backup in their LocalState folder. 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. + Path to the backup file to restore from. If omitted, automatically + finds the latest Win11Debloat-StartBackup-*.bak in each user's + LocalState folder. .EXAMPLE RestoreStartMenuForAllUsers .EXAMPLE - RestoreStartMenuForAllUsers -BackupFilePath "C:\Backups\start2.bin" + RestoreStartMenuForAllUsers -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak" #> function RestoreStartMenuForAllUsers { param( diff --git a/Scripts/GUI/Show-RestoreBackupDialog.ps1 b/Scripts/GUI/Show-RestoreBackupDialog.ps1 index 0b6f0f0..8cacede 100644 --- a/Scripts/GUI/Show-RestoreBackupDialog.ps1 +++ b/Scripts/GUI/Show-RestoreBackupDialog.ps1 @@ -196,6 +196,10 @@ function Show-RestoreBackupDialog { $primaryActionBtn.Visibility = 'Visible' $primaryActionBtn.IsDefault = $true $chooseRegistryBtn.IsDefault = $false + + # Show intro panel so user can configure scope & auto-detect + $startMenuAutoBackupCheck.IsChecked = $true + $state.SelectedStartMenuBackupFilePath = $null & $refreshStartMenuUi } @@ -323,27 +327,14 @@ function Show-RestoreBackupDialog { } if (-not $useManualBackupFile) { - $autoBackupExists = $false - if ($scope -eq 'AllUsers') { - $userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" - $usersStartMenuPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue - foreach ($startMenuPath in $usersStartMenuPaths) { - if (Test-Path -LiteralPath (Join-Path $startMenuPath.FullName 'start2.bin.bak')) { - $autoBackupExists = $true - break - } - } - } - else { - $autoBackupPath = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin.bak" - $autoBackupExists = Test-Path -LiteralPath $autoBackupPath - } - - if (-not $autoBackupExists) { - $scopeText = (& $getStartMenuScopeInfo).SummaryText - Show-MessageBox -Owner $window -Title 'No Backup Found' -Message "No Start Menu backup file was found. You can uncheck the 'Automatically find Start Menu backup' option to select a backup file manually." -Button 'OK' -Icon 'Warning' | Out-Null + $scopeInfo = & $getStartMenuScopeInfo + $autoBackupPath = Get-StartMenuBackupPath -Scope $scopeInfo.Scope + if ($null -eq $autoBackupPath) { + $scopeText = $scopeInfo.SummaryText + Show-MessageBox -Owner $window -Title 'No Backup Found' -Message "No Start Menu backup file was found for $scopeText. Uncheck 'Automatically find Start Menu backup' to select a backup file manually." -Button 'OK' -Icon 'Warning' | Out-Null return } + $state.SelectedStartMenuBackupFilePath = if ($scopeInfo.Scope -eq 'CurrentUser') { $autoBackupPath } else { $null } } $window.Tag = @{ @@ -377,6 +368,7 @@ function Show-RestoreBackupDialog { }) $startMenuScopeCombo.Add_SelectionChanged({ + $state.SelectedStartMenuBackupFilePath = $null & $refreshStartMenuUi })