Compare commits

..

10 Commits

Author SHA1 Message Date
HetCreep
339764eb18 Fix broken contributing link in README.md (#688) 2026-07-02 23:03:44 +02:00
HetCreep
3b4a5f16ad fix(restore-gui): surface registry-backup load failures to the user (#685)
Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
2026-07-02 21:16:47 +02:00
Jeffrey
9a700afaed Consolidate Get-Dev.ps1 into Get.ps1, add WhatIf support to Get.ps1 (#681) 2026-06-28 22:53:27 +02:00
Jeffrey
26b313372b Update README.md 2026-06-25 16:41:32 +02:00
Jeffrey
9ee0126259 Bump version 2026-06-24 22:13:48 +02:00
Jeffrey
e23ecf36d6 Update minimum window sizing 2026-06-24 22:00:44 +02:00
Jeffrey
32dc3d6bdf Fix maximized window sizing (#673) 2026-06-24 21:45:53 +02:00
Jeffrey
f76adc5054 Add docstrings 2026-06-24 20:55:17 +02:00
Jeffrey
95b583606d Update start menu backup/restore with timestamped filenames (#672) 2026-06-24 17:32:45 +02:00
Jeffrey
693b805114 Simplify Window management (#671) 2026-06-24 14:40:48 +02:00
14 changed files with 389 additions and 511 deletions

View File

@@ -18,7 +18,7 @@ You can help us test the latest changes and additions to the script. If you enco
You can launch the prerelease version of Win11Debloat by running this command: You can launch the prerelease version of Win11Debloat by running this command:
```ps1 ```ps1
& ([scriptblock]::Create((irm "https://debloat.raphi.re/dev"))) & ([scriptblock]::Create((irm "https://debloat.raphi.re/"))) -Dev
``` ```
# Contributing Code # Contributing Code

View File

@@ -6,7 +6,7 @@
Win11Debloat is a lightweight, easy to use PowerShell script that allows you to quickly declutter and customize your Windows experience, no installation required! You can use it to remove pre-installed apps, disable telemetry, remove intrusive interface elements and much more. No need to painstakingly go through all the settings yourself or remove apps one by one. Win11Debloat makes the process quick and easy! Win11Debloat is a lightweight, easy to use PowerShell script that allows you to quickly declutter and customize your Windows experience, no installation required! You can use it to remove pre-installed apps, disable telemetry, remove intrusive interface elements and much more. No need to painstakingly go through all the settings yourself or remove apps one by one. Win11Debloat makes the process quick and easy!
The script also includes many features that system administrators and power users will enjoy. Such as a powerful command-line interface, support for Windows Audit mode and the ability to make changes to other Windows users. Please refer to our [wiki](https://github.com/Raphire/Win11Debloat/wiki/) for more details. The script also includes many features that system administrators and power users will enjoy. Such as a powerful command-line interface, support for Windows Audit mode and the ability to make changes to other Windows users. You can also easily export & import your preferred settings, allowing you to quickly apply the same settings on all your systems. Please refer to our [wiki](https://github.com/Raphire/Win11Debloat/wiki) for more details.
![Win11Debloat Menu](/Assets/Images/menu.png) ![Win11Debloat Menu](/Assets/Images/menu.png)
@@ -23,14 +23,14 @@ The script also includes many features that system administrators and power user
Download & run the script automatically via PowerShell. Download & run the script automatically via PowerShell.
1. Open PowerShell or Terminal, preferably as an administrator. 1. Open PowerShell or Terminal.
2. Copy and paste the command below into PowerShell: 2. Copy and paste the command below into PowerShell:
```PowerShell ```PowerShell
& ([scriptblock]::Create((irm "https://debloat.raphi.re/"))) & ([scriptblock]::Create((irm "https://debloat.raphi.re/")))
``` ```
3. Wait for the script to automatically download Win11Debloat. 3. Wait for the script to automatically download and launch Win11Debloat.
4. Carefully read through and follow the on-screen instructions. 4. Carefully read through and follow the on-screen instructions.
This method supports command-line parameters to customize the behaviour of the script. Please click [here](https://github.com/Raphire/Win11Debloat/wiki/Command%E2%80%90line-Interface#parameters) for more information. This method supports command-line parameters to customize the behaviour of the script. Please click [here](https://github.com/Raphire/Win11Debloat/wiki/Command%E2%80%90line-Interface#parameters) for more information.
@@ -74,10 +74,10 @@ This method supports command-line parameters to customize the behaviour of the s
## Features ## Features
Below is an overview of the key features and functionality offered by Win11Debloat. Please refer to [the wiki](https://github.com/Raphire/Win11Debloat/wiki/Default-Settings) for more information about the default settings preset. Below is an overview of the key features and functionality offered by Win11Debloat. You can visit the [the wiki](https://github.com/Raphire/Win11Debloat/wiki) for more details.
> [!Tip] > [!Tip]
> All of the changes made by Win11Debloat can easily be reverted and almost all of the apps can be reinstalled through the Microsoft Store. A full guide on how to revert changes can be found [here](https://github.com/Raphire/Win11Debloat/wiki/Reverting-Changes). > All of the changes made by Win11Debloat can easily be reverted and almost all of the apps can be reinstalled through the Microsoft Store. You can visit [the wiki](https://github.com/Raphire/Win11Debloat/wiki/Reverting-Changes) for more information on reverting changes.
#### App Removal #### App Removal
@@ -86,23 +86,15 @@ Below is an overview of the key features and functionality offered by Win11Deblo
#### Privacy & Suggested Content #### Privacy & Suggested Content
- Disable telemetry, diagnostic data, activity history, app-launch tracking & targeted ads. - Disable telemetry, diagnostic data, activity history, app-launch tracking & targeted ads.
- Disable tips, tricks, suggestions & ads across Windows. - Disable tips, tricks, suggestions & ads across Windows, the lock screen and Microsoft Edge.
- Disable Windows location services & app location access. - Disable Windows location services, app location access and Find My Device location tracking.
- Disable Find My Device location tracking.
- Disable 'Windows Spotlight' and tips & tricks on the lock screen.
- Disable 'Windows Spotlight' desktop background option.
- Disable ads, suggestions and the MSN news feed in Microsoft Edge.
- Hide Microsoft 365 ads on the Settings 'Home' page, or hide the 'Home' page entirely. - Hide Microsoft 365 ads on the Settings 'Home' page, or hide the 'Home' page entirely.
#### AI Features #### AI Features
- Disable & remove Microsoft Copilot. - Disable & remove Microsoft Copilot, Windows Recall and Click to Do.
- Disable Windows Recall.
- Disable Click to Do, AI text & image analysis tool.
- Prevent AI service (WSAIFabricSvc) from starting automatically. - Prevent AI service (WSAIFabricSvc) from starting automatically.
- Disable AI Features in Edge. - Disable AI Features in Edge, Paint and Notepad.
- Disable AI Features in Paint.
- Disable AI Features in Notepad.
#### System #### System
@@ -124,50 +116,38 @@ Below is an overview of the key features and functionality offered by Win11Deblo
#### Appearance #### Appearance
- Enable dark mode for system and apps. - Enable dark mode for system and apps.
- Disable transparency effects - Disable transparency, animations and visual effects.
- Disable animations and visual effects.
#### Start Menu & Search #### Start Menu & Search
- Remove or replace all pinned apps from the start menu. - Customize the start menu by removing pinned apps, hiding recommendations, and customizing the 'All Apps' section.
- Hide the recommended section in the start menu.
- Hide the 'All Apps' section in the start menu.
- Disable the Phone Link mobile devices integration in the start menu. - Disable the Phone Link mobile devices integration in the start menu.
- Disable Bing web search & Copilot integration in Windows search. - Disable Bing web search & Copilot integration and Microsoft Store app suggestions in Windows search.
- Disable Microsoft Store app suggestions in Windows search.
- Disable Search Highlights (dynamic/branded content) in the taskbar search box.
- Disable local Windows search history.
#### Taskbar #### Taskbar
- Align taskbar icons to the left. - Change taskbar alignment.
- Hide or change the search icon/box on the taskbar. - Customize or hide taskbar buttons like the search bar, taskview and more.
- Hide the taskview button from the taskbar.
- Disable widgets on the taskbar & lock screen. - Disable widgets on the taskbar & lock screen.
- Hide the chat (meet now) icon from the taskbar. - Enable the 'End Task' option in the taskbar right click menu to quickly force-close apps.
- Enable the 'End Task' option in the taskbar right click menu.
- Enable the 'Last Active Click' behavior in the taskbar app area. This allows you to repeatedly click on an application's icon in the taskbar to switch focus between the open windows of that application. - Enable the 'Last Active Click' behavior in the taskbar app area. This allows you to repeatedly click on an application's icon in the taskbar to switch focus between the open windows of that application.
- Choose how app icons are shown on the taskbar when using multiple monitors. - Customize how app buttons are shown on the taskbar.
- Choose combine mode for taskbar buttons and labels.
#### File Explorer #### File Explorer
- Change the default location that File Explorer opens to. - Change the default location that File Explorer opens to.
- Show file extensions for known file types. - Show file extensions for known file types.
- Show hidden files, folders and drives. - Show hidden files, folders and drives.
- Hide the Home or Gallery section from the File Explorer navigation pane. - Hide the Home, Gallery or OneDrive section from the File Explorer navigation pane.
- Hide duplicate removable drive entries from the File Explorer navigation pane, so only the entry under 'This PC' remains. - Hide duplicate removable drive entries from the File Explorer navigation pane, so only the entry under 'This PC' remains.
- Add all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer. - Add all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer.
- Hide the 3D objects, music or OneDrive folder from the File Explorer navigation pane.
- Hide the 'Include in library', 'Give access to' and 'Share' options from the context menu.
- Change drive letter position or visibility in File Explorer. - Change drive letter position or visibility in File Explorer.
#### Multi-tasking #### Multi-tasking
- Disable window snapping. - Disable window snapping.
- Disable Snap Assist suggestions when snapping a window. - Disable Snap Assist and Snap Layout suggestions when dragging or snapping windows.
- Disable Snap Layout suggestions when dragging windows to the top of screen and when hovering on the maximize button. - Change whether tabs are shown when snapping windows or pressing Alt+Tab.
- Change if tabs are shown when snapping or pressing Alt+Tab.
#### Optional Windows Features #### Optional Windows Features
@@ -181,12 +161,12 @@ Below is an overview of the key features and functionality offered by Win11Deblo
#### Advanced Features #### Advanced Features
- Option to [apply changes to a different user](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#running-as-another-user), instead of the currently logged in user. - Ability to [apply changes to a different user](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#running-as-another-user), instead of the currently logged in user.
- [Sysprep mode](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#sysprep-mode) to apply changes to the Windows Default user profile. Which ensures, all new users will have the changes automatically applied to them. - [Sysprep mode](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#sysprep-mode) to apply changes to the Windows Default user profile. Which ensures, all new users will have the changes automatically applied to them.
## Contributing ## Contributing
We welcome contributions of all kinds! Please see our [Contributing Guidelines](/.github/CONTRIBUTING.md) for detailed instructions on how to get started and best practices for contributing. We welcome contributions of all kinds! Please see our [Contributing Guidelines](https://github.com/Raphire/Win11Debloat/blob/master/.github/CONTRIBUTING.md) for detailed instructions on how to get started and best practices for contributing.
## License ## License

View File

@@ -4,10 +4,10 @@ setlocal EnableDelayedExpansion
:: Set Windows Terminal installation paths. (Default and Scoop installation) :: Set Windows Terminal installation paths. (Default and Scoop installation)
set "wtDefaultPath=%LOCALAPPDATA%\Microsoft\WindowsApps\wt.exe" set "wtDefaultPath=%LOCALAPPDATA%\Microsoft\WindowsApps\wt.exe"
set "wtScoopPath=%USERPROFILE%\scoop\apps\windows-terminal\current\wt.exe" set "wtScoopPath=%USERPROFILE%\scoop\apps\windows-terminal\current\wt.exe"
set "logFile=%LOCALAPPDATA%\Win11Debloat\Logs\Win11Debloat-Run.log" set "logFile=%~dp0Logs\Win11Debloat-Run.log"
:: Ensure Logs folder exists :: Ensure Logs folder exists
if not exist "%LOCALAPPDATA%\Win11Debloat\Logs" mkdir "%LOCALAPPDATA%\Win11Debloat\Logs" if not exist "%~dp0Logs" mkdir "%~dp0Logs"
:: Determine which terminal exists :: Determine which terminal exists
if exist "%wtDefaultPath%" ( if exist "%wtDefaultPath%" (

View File

@@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework" xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
Title="Win11Debloat" Title="Win11Debloat"
MinWidth="860" MinHeight="600" MinWidth="860" MinHeight="640"
ResizeMode="CanResize" ResizeMode="CanResize"
SnapsToDevicePixels="True" SnapsToDevicePixels="True"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
WindowStyle="None" WindowStyle="None"
AllowsTransparency="True" AllowsTransparency="False"
Background="Transparent" Background="{DynamicResource AppBorderColor}"
Foreground="{DynamicResource AppFgColor}"> Foreground="{DynamicResource AppFgColor}">
<shell:WindowChrome.WindowChrome> <shell:WindowChrome.WindowChrome>
<shell:WindowChrome ResizeBorderThickness="5" <shell:WindowChrome ResizeBorderThickness="5"
@@ -464,7 +464,7 @@
<Grid> <Grid>
<StackPanel x:Name="HomeContentPanel" HorizontalAlignment="Center" VerticalAlignment="Top"> <StackPanel x:Name="HomeContentPanel" HorizontalAlignment="Center" VerticalAlignment="Top">
<!-- Logo --> <!-- Logo -->
<Viewbox Width="250" Height="250" Margin="0,0,0,24" HorizontalAlignment="Center"> <Viewbox Width="250" Height="250" Margin="0,0,0,16" HorizontalAlignment="Center">
<Grid Width="250" Height="250"> <Grid Width="250" Height="250">
<!-- Windows logo style icon --> <!-- Windows logo style icon -->
<Path x:Name="LogoFallback" Data="M0,0 L80,0 L80,80 L0,80 Z M90,0 L170,0 L170,80 L90,80 Z M0,90 L80,90 L80,170 L0,170 Z M90,90 L170,90 L170,170 L90,170 Z" <Path x:Name="LogoFallback" Data="M0,0 L80,0 L80,80 L0,80 Z M90,0 L170,0 L170,80 L90,80 Z M0,90 L80,90 L80,170 L0,170 Z M90,90 L170,90 L170,170 L90,170 Z"
@@ -483,7 +483,7 @@
<!-- Title --> <!-- Title -->
<TextBlock Text="Welcome to Win11Debloat" FontSize="40" FontWeight="SemiBold" Foreground="{DynamicResource AppFgColor}" HorizontalAlignment="Center"/> <TextBlock Text="Welcome to Win11Debloat" FontSize="40" FontWeight="SemiBold" Foreground="{DynamicResource AppFgColor}" HorizontalAlignment="Center"/>
<TextBlock TextWrapping="Wrap" Foreground="{DynamicResource AppFgColor}" FontSize="20" HorizontalAlignment="Center" Margin="0,8,0,64"> <TextBlock TextWrapping="Wrap" Foreground="{DynamicResource AppFgColor}" FontSize="20" HorizontalAlignment="Center" Margin="0,4,0,48">
<Run Text="Your clean Windows experience is just a few clicks away!"/> <Run Text="Your clean Windows experience is just a few clicks away!"/>
</TextBlock> </TextBlock>

View File

@@ -113,16 +113,15 @@ function ReplaceStartMenu {
return return
} }
$startMenuBackupsDir = Join-Path $script:AppDataPath 'Backups' $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
if (-not (Test-Path $startMenuBackupsDir)) { $backupFileName = "Win11Debloat-StartBackup-$timestamp.bak"
New-Item -ItemType Directory -Path $startMenuBackupsDir -Force | Out-Null $startMenuDir = Split-Path $startMenuBinFile -Parent
} $backupBinFile = Join-Path $startMenuDir $backupFileName
$backupTimestamp = (Get-Date).ToString('yyyyMMdd_HHmmss')
$backupBinFile = Join-Path $startMenuBackupsDir "Win11Debloat-Start2BinBackup-$userName-$backupTimestamp.bak"
if (Test-Path $startMenuBinFile) { if (Test-Path $startMenuBinFile) {
# Backup current start menu file # 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 { else {
Write-Host "Unable to find original start2.bin file for user $userName, no backup was created for this user" -ForegroundColor Yellow Write-Host "Unable to find original start2.bin file for user $userName, no backup was created for this user" -ForegroundColor Yellow
@@ -194,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 .SYNOPSIS
@@ -209,14 +257,14 @@ function GetStartMenuUserNameFromPath {
The full path to the user's start2.bin file to restore. The full path to the user's start2.bin file to restore.
.PARAMETER BackupFilePath .PARAMETER BackupFilePath
Path to the backup file to restore from. If omitted, defaults to Path to the backup file to restore from. If omitted, automatically
StartMenuBinFile with a .bak extension. finds the latest Win11Debloat-StartBackup-*.bak file.
.EXAMPLE .EXAMPLE
RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
.EXAMPLE .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 { function RestoreStartMenuFromBackup {
param( param(
@@ -226,20 +274,32 @@ function RestoreStartMenuFromBackup {
) )
$userName = GetStartMenuUserNameFromPath -StartMenuBinFile $StartMenuBinFile $userName = GetStartMenuUserNameFromPath -StartMenuBinFile $StartMenuBinFile
$backupTimestamp = (Get-Date).ToString('yyyyMMdd_HHmmss') $backupBinFile = if ([string]::IsNullOrWhiteSpace($BackupFilePath)) {
$startMenuBackupsDir = Join-Path $script:AppDataPath 'Backups' # Auto-detect latest backup in the same folder as the start2.bin
if (-not (Test-Path $startMenuBackupsDir)) { New-Item -ItemType Directory -Path $startMenuBackupsDir -Force | Out-Null } $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
$resolvedBackupPath = if ([string]::IsNullOrWhiteSpace($BackupFilePath)) { if ($latestBackup) { $latestBackup.FullName } else { $null }
Join-Path $startMenuBackupsDir "Win11Debloat-Start2BinBackup-$userName-$backupTimestamp.bak"
} }
else { else {
$BackupFilePath $BackupFilePath
} }
$currentBinBackup = Join-Path $startMenuBackupsDir "Win11Debloat-Start2BinRestore-$userName-$backupTimestamp.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")) { if ($script:Params.ContainsKey("WhatIf")) {
Write-Host "[WhatIf] Restore start menu for user $userName from backup $resolvedBackupPath" -ForegroundColor Cyan Write-Host "[WhatIf] Restore start menu for user $userName from backup $backupBinFile" -ForegroundColor Cyan
return [PSCustomObject]@{ return [PSCustomObject]@{
UserName = $userName UserName = $userName
Result = $true Result = $true
@@ -247,11 +307,11 @@ function RestoreStartMenuFromBackup {
} }
} }
if (-not (Test-Path -LiteralPath $resolvedBackupPath)) { if (-not (Test-Path -LiteralPath $backupBinFile)) {
return [PSCustomObject]@{ return [PSCustomObject]@{
UserName = $userName UserName = $userName
Result = $false Result = $false
Message = "Start menu backup file not found: $resolvedBackupPath" Message = "No start menu backup file found for user $userName."
} }
} }
@@ -260,7 +320,7 @@ function RestoreStartMenuFromBackup {
Move-Item -Path $StartMenuBinFile -Destination $currentBinBackup -Force Move-Item -Path $StartMenuBinFile -Destination $currentBinBackup -Force
} }
Copy-Item -Path $resolvedBackupPath -Destination $StartMenuBinFile -Force Copy-Item -Path $backupBinFile -Destination $StartMenuBinFile -Force
return [PSCustomObject]@{ return [PSCustomObject]@{
UserName = $userName UserName = $userName
Result = $true Result = $true
@@ -281,34 +341,26 @@ function RestoreStartMenuFromBackup {
Restores the start menu for the current target user from a backup. Restores the start menu for the current target user from a backup.
.DESCRIPTION .DESCRIPTION
Resolves the start2.bin path for the current user (or the user specified Resolves the start2.bin path for the currently logged-in user, then
via the -User parameter), then delegates to RestoreStartMenuFromBackup. delegates to RestoreStartMenuFromBackup.
Returns early with a warning if the user's start menu path cannot
be resolved.
.PARAMETER BackupFilePath .PARAMETER BackupFilePath
Path to the backup file to restore from. Path to the backup file to restore from. If omitted, automatically
finds the latest Win11Debloat-StartBackup-*.bak file.
.EXAMPLE .EXAMPLE
RestoreStartMenu -BackupFilePath "$env:LOCALAPPDATA\Win11Debloat\Backups\Win11Debloat-Start2BinBackup-Jeff-20260623_143000.bak" RestoreStartMenu
.EXAMPLE
RestoreStartMenu -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
#> #>
function RestoreStartMenu { function RestoreStartMenu {
param( param(
[Parameter(Mandatory)]
[string]$BackupFilePath [string]$BackupFilePath
) )
$targetUserName = GetUserName $targetUserName = $env:USERNAME
$startMenuBinFile = GetStartMenuBinPathForUser -UserName $targetUserName $startMenuBinFile = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
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..." Write-Host "Restoring start menu for user $targetUserName from backup..."
@@ -321,19 +373,24 @@ function RestoreStartMenu {
.DESCRIPTION .DESCRIPTION
Iterates over every existing user profile and restores each user's Iterates over every existing user profile and restores each user's
start2.bin from the specified backup file. For the Default user profile, start2.bin from the latest backup in their LocalState folder. For the
removes the start2.bin file (which was previously copied from a template) Default user profile, removes the start2.bin file (which was previously
so that new profiles revert to the system default start menu. copied from a template) so that new profiles revert to the system
default start menu.
.PARAMETER BackupFilePath .PARAMETER BackupFilePath
Path to the backup file to restore from. Path to the backup file to restore from. If omitted, automatically
finds the latest Win11Debloat-StartBackup-*.bak in each user's
LocalState folder.
.EXAMPLE .EXAMPLE
RestoreStartMenuForAllUsers -BackupFilePath "$env:LOCALAPPDATA\Win11Debloat\Backups\Win11Debloat-Start2BinBackup-Jeff-20260623_143000.bak" RestoreStartMenuForAllUsers
.EXAMPLE
RestoreStartMenuForAllUsers -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
#> #>
function RestoreStartMenuForAllUsers { function RestoreStartMenuForAllUsers {
param( param(
[Parameter(Mandatory)]
[string]$BackupFilePath [string]$BackupFilePath
) )

View File

@@ -1,3 +1,19 @@
<#
.SYNOPSIS
Loads a registry backup from a JSON file and normalizes its contents.
.DESCRIPTION
Loads a registry backup from disk and returns a normalized representation
of its contents suitable for use by the restore workflow. Throws if the
file is missing, unreadable, or not valid JSON.
.PARAMETER FilePath
The absolute path to the registry backup JSON file to load.
.OUTPUTS
PSCustomObject
A normalized registry backup object produced by Normalize-RegistryBackup.
#>
function Load-RegistryBackupFromFile { function Load-RegistryBackupFromFile {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -18,6 +34,24 @@ function Load-RegistryBackupFromFile {
return Normalize-RegistryBackup -Backup $rawBackup return Normalize-RegistryBackup -Backup $rawBackup
} }
<#
.SYNOPSIS
Validates and normalizes a raw registry backup object.
.DESCRIPTION
Validates the structure and content of the supplied backup and converts
it into a normalized representation that can be safely consumed by the
restore workflow. Throws if validation fails.
.PARAMETER Backup
The raw backup object (typically parsed from JSON) to normalize.
.OUTPUTS
PSCustomObject
A normalized backup with Version, BackupType, CreatedAt, CreatedBy,
ComputerName, Target, SelectedFeatures, SelectedUndoFeatures, and
RegistryKeys properties.
#>
function Normalize-RegistryBackup { function Normalize-RegistryBackup {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -97,10 +131,17 @@ function Normalize-RegistryBackup {
if ($allSelectedFeatures.Count -eq 0) { if ($allSelectedFeatures.Count -eq 0) {
$errors.Add('Backup must contain at least one feature ID in SelectedFeatures or SelectedUndoFeatures.') $errors.Add('Backup must contain at least one feature ID in SelectedFeatures or SelectedUndoFeatures.')
} }
else {
try {
$allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -SelectedUndoFeatureIds @($selectedUndoFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys)) $allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -SelectedUndoFeatureIds @($selectedUndoFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys))
foreach ($allowListValidationError in $allowListValidationErrors) { foreach ($allowListValidationError in $allowListValidationErrors) {
$errors.Add([string]$allowListValidationError) $errors.Add([string]$allowListValidationError)
} }
}
catch {
$errors.Add("Failed to validate backup: $($_.Exception.Message)")
}
}
if ($errors.Count -gt 0) { if ($errors.Count -gt 0) {
Write-Error "Backup validation failed: $($errors -join ' ')" Write-Error "Backup validation failed: $($errors -join ' ')"
@@ -125,6 +166,23 @@ function Normalize-RegistryBackup {
} }
} }
<#
.SYNOPSIS
Restores registry state from a normalized backup object.
.DESCRIPTION
Applies the registry state described by the supplied backup back to the
registry, loading the appropriate user hive when required.
.PARAMETER Backup
A normalized backup object (as produced by Normalize-RegistryBackup) whose
RegistryKeys snapshots should be restored.
.OUTPUTS
PSCustomObject
Returns an object with a Result property set to $true when the restore
completes successfully.
#>
function Restore-RegistryBackupState { function Restore-RegistryBackupState {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]

View File

@@ -1,71 +1,5 @@
# MainWindow-WindowChrome.ps1 # MainWindow-WindowChrome.ps1
# Window sizing, DPI-aware coordinate conversion, maximized-window taskbar-constraint helpers, and UI animations. # Window sizing, DPI-aware coordinate conversion, and UI animations.
function Register-MaximizedWindowHelper {
if (-not ([System.Management.Automation.PSTypeName]'Win11Debloat.MaximizedWindowHelper').Type) {
Add-Type -Namespace Win11Debloat -Name MaximizedWindowHelper `
-ReferencedAssemblies 'PresentationFramework','System.Windows.Forms','System.Drawing' `
-MemberDefinition @'
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct MINMAXINFO {
public POINT ptReserved, ptMaxSize, ptMaxPosition, ptMinTrackSize, ptMaxTrackSize;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct POINT { public int x, y; }
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern System.IntPtr MonitorFromWindow(System.IntPtr hwnd, uint dwFlags);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool GetMonitorInfo(System.IntPtr hMonitor, ref MONITORINFO lpmi);
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct RECT {
public int Left, Top, Right, Bottom;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private struct MONITORINFO {
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
public static System.IntPtr WmGetMinMaxInfoHook(
System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) {
if (msg == 0x0024) { // WM_GETMINMAXINFO
var mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(
lParam, typeof(MINMAXINFO));
const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
var monitorInfo = new MONITORINFO();
monitorInfo.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(MONITORINFO));
if (monitor != System.IntPtr.Zero && GetMonitorInfo(monitor, ref monitorInfo)) {
mmi.ptMaxPosition.x = monitorInfo.rcWork.Left - monitorInfo.rcMonitor.Left;
mmi.ptMaxPosition.y = monitorInfo.rcWork.Top - monitorInfo.rcMonitor.Top;
mmi.ptMaxSize.x = monitorInfo.rcWork.Right - monitorInfo.rcWork.Left;
mmi.ptMaxSize.y = monitorInfo.rcWork.Bottom - monitorInfo.rcWork.Top;
}
else {
var screen = System.Windows.Forms.Screen.FromHandle(hwnd);
var wa = screen.WorkingArea;
var bounds = screen.Bounds;
mmi.ptMaxPosition.x = wa.Left - bounds.Left;
mmi.ptMaxPosition.y = wa.Top - bounds.Top;
mmi.ptMaxSize.x = wa.Width;
mmi.ptMaxSize.y = wa.Height;
}
System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true);
}
return System.IntPtr.Zero;
}
'@
}
}
# Convert screen-pixel coordinates to WPF device-independent pixels (DIP) # Convert screen-pixel coordinates to WPF device-independent pixels (DIP)
function ConvertTo-ScreenPointToDip { function ConvertTo-ScreenPointToDip {
@@ -118,16 +52,35 @@ function Update-MainWindowChrome {
) )
$windowStateMaximized = [System.Windows.WindowState]::Maximized $windowStateMaximized = [System.Windows.WindowState]::Maximized
$chrome = [System.Windows.Shell.WindowChrome]::GetWindowChrome($Window)
if ($Window.WindowState -eq $windowStateMaximized) { if ($Window.WindowState -eq $windowStateMaximized) {
$MainBorder.Margin = [System.Windows.Thickness]::new(0) $chrome = [System.Windows.Shell.WindowChrome]::GetWindowChrome($Window)
$resizeBorder = if ($chrome) { $chrome.ResizeBorderThickness } else { [System.Windows.SystemParameters]::WindowResizeBorderThickness }
# Compute margins using screen bounds vs working area
$marginLeft = $resizeBorder.Left
$marginTop = $resizeBorder.Top
$marginRight = $resizeBorder.Right
$marginBottom = $resizeBorder.Bottom
$screen = Get-WindowScreen -Window $Window
if ($screen) {
$workTL = ConvertTo-ScreenPointToDip -Window $Window -X $screen.WorkingArea.Left -Y $screen.WorkingArea.Top
$workSize = ConvertTo-ScreenPixelsToDip -Window $Window -Width $screen.WorkingArea.Width -Height $screen.WorkingArea.Height
$screenTL = ConvertTo-ScreenPointToDip -Window $Window -X $screen.Bounds.Left -Y $screen.Bounds.Top
$screenSize = ConvertTo-ScreenPixelsToDip -Window $Window -Width $screen.Bounds.Width -Height $screen.Bounds.Height
$marginLeft += ($workTL.X - $screenTL.X)
$marginTop += ($workTL.Y - $screenTL.Y)
$marginRight += ($screenTL.X + $screenSize.Width) - ($workTL.X + $workSize.Width)
$marginBottom += ($screenTL.Y + $screenSize.Height) - ($workTL.Y + $workSize.Height)
}
$MainBorder.Margin = [System.Windows.Thickness]::new($marginLeft, $marginTop, $marginRight, $marginBottom)
$MainBorder.BorderThickness = [System.Windows.Thickness]::new(0) $MainBorder.BorderThickness = [System.Windows.Thickness]::new(0)
$MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(0) $MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(0)
$MainBorder.Effect = $null $MainBorder.Effect = $null
$TitleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(0) $TitleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(0)
# Zero out resize borders when maximized so the entire title bar row is draggable
if ($chrome) { $chrome.ResizeBorderThickness = [System.Windows.Thickness]::new(0) }
} }
else { else {
$MainBorder.Margin = [System.Windows.Thickness]::new(0) $MainBorder.Margin = [System.Windows.Thickness]::new(0)
@@ -135,7 +88,6 @@ function Update-MainWindowChrome {
$MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(8) $MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(8)
$MainBorder.Effect = $NormalWindowShadow $MainBorder.Effect = $NormalWindowShadow
$TitleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(8, 8, 0, 0) $TitleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(8, 8, 0, 0)
if ($chrome) { $chrome.ResizeBorderThickness = [System.Windows.Thickness]::new(5) }
} }
} }

View File

@@ -1,3 +1,23 @@
<#
.SYNOPSIS
Hides the currently displayed bubble popup.
.DESCRIPTION
Closes the bubble popup with a smooth fade-out animation (220ms). If the
-Immediate switch is used, the popup is closed instantly without animation.
This function is called automatically by Show-Bubble's timer and can also
be invoked manually to dismiss the bubble early.
.PARAMETER Immediate
If specified, the bubble popup is closed instantly without a fade-out
animation. Any pending close timer is also stopped.
.EXAMPLE
Hide-Bubble
.EXAMPLE
Hide-Bubble -Immediate
#>
function Hide-Bubble { function Hide-Bubble {
param ( param (
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
@@ -37,6 +57,34 @@ function Hide-Bubble {
$bubblePanel.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeOut) $bubblePanel.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeOut)
} }
<#
.SYNOPSIS
Displays a transient bubble popup hint anchored above a target control.
.DESCRIPTION
Shows a WPF popup styled as a speech bubble above the specified target
control. The bubble fades in with a animation, displays for a configurable
duration, then fades out. Any previously shown bubble is dismissed
immediately before showing the new one.
.PARAMETER TargetControl
The WPF Control above which the bubble popup will be placed. This
parameter is mandatory.
.PARAMETER Message
The text message to display inside the bubble. Defaults to
'View the selected changes here'.
.PARAMETER DurationSeconds
The number of seconds the bubble remains visible before auto-hiding.
The minimum value is 1 second. Defaults to 5 seconds.
.EXAMPLE
Show-Bubble -TargetControl $myButton
.EXAMPLE
Show-Bubble -TargetControl $myButton -Message 'Changes saved!' -DurationSeconds 3
#>
function Show-Bubble { function Show-Bubble {
param ( param (
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]

View File

@@ -1,71 +1,6 @@
function Show-MainWindow { function Show-MainWindow {
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase,System.Windows.Forms | Out-Null Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase,System.Windows.Forms | Out-Null
# ---- Constrain maximized window to taskbar work area ----
if (-not ([System.Management.Automation.PSTypeName]'Win11Debloat.MaximizedWindowHelper').Type) {
Add-Type -Namespace Win11Debloat -Name MaximizedWindowHelper `
-ReferencedAssemblies 'PresentationFramework','System.Windows.Forms','System.Drawing' `
-MemberDefinition @'
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct MINMAXINFO {
public POINT ptReserved, ptMaxSize, ptMaxPosition, ptMinTrackSize, ptMaxTrackSize;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct POINT { public int x, y; }
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern System.IntPtr MonitorFromWindow(System.IntPtr hwnd, uint dwFlags);
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool GetMonitorInfo(System.IntPtr hMonitor, ref MONITORINFO lpmi);
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
private struct RECT {
public int Left, Top, Right, Bottom;
}
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private struct MONITORINFO {
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
public static System.IntPtr WmGetMinMaxInfoHook(
System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) {
if (msg == 0x0024) { // WM_GETMINMAXINFO
var mmi = (MINMAXINFO)System.Runtime.InteropServices.Marshal.PtrToStructure(
lParam, typeof(MINMAXINFO));
const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
var monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
var monitorInfo = new MONITORINFO();
monitorInfo.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(MONITORINFO));
if (monitor != System.IntPtr.Zero && GetMonitorInfo(monitor, ref monitorInfo)) {
mmi.ptMaxPosition.x = monitorInfo.rcWork.Left - monitorInfo.rcMonitor.Left;
mmi.ptMaxPosition.y = monitorInfo.rcWork.Top - monitorInfo.rcMonitor.Top;
mmi.ptMaxSize.x = monitorInfo.rcWork.Right - monitorInfo.rcWork.Left;
mmi.ptMaxSize.y = monitorInfo.rcWork.Bottom - monitorInfo.rcWork.Top;
}
else {
var screen = System.Windows.Forms.Screen.FromHandle(hwnd);
var wa = screen.WorkingArea;
var bounds = screen.Bounds;
mmi.ptMaxPosition.x = wa.Left - bounds.Left;
mmi.ptMaxPosition.y = wa.Top - bounds.Top;
mmi.ptMaxSize.x = wa.Width;
mmi.ptMaxSize.y = wa.Height;
}
System.Runtime.InteropServices.Marshal.StructureToPtr(mmi, lParam, true);
}
return System.IntPtr.Zero;
}
'@
}
$WinVersion = Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' CurrentBuild $WinVersion = Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' CurrentBuild
$usesDarkMode = GetSystemUsesDarkMode $usesDarkMode = GetSystemUsesDarkMode
@@ -123,12 +58,6 @@
$window.Add_SourceInitialized({ $window.Add_SourceInitialized({
& $applyInitialWindowSize & $applyInitialWindowSize
& $updateWindowChrome & $updateWindowChrome
$hwndHelper = New-Object System.Windows.Interop.WindowInteropHelper($window)
$hwndSource = [System.Windows.Interop.HwndSource]::FromHwnd($hwndHelper.Handle)
$hookMethod = [Win11Debloat.MaximizedWindowHelper].GetMethod('WmGetMinMaxInfoHook')
$hook = [System.Delegate]::CreateDelegate([System.Windows.Interop.HwndSourceHook], $hookMethod)
$hwndSource.AddHook($hook)
}) })
$window.Add_SizeChanged({ $window.Add_SizeChanged({
@@ -159,7 +88,7 @@
$menuReportBug.Add_Click({ Start-Process "https://github.com/Raphire/Win11Debloat/issues" }) $menuReportBug.Add_Click({ Start-Process "https://github.com/Raphire/Win11Debloat/issues" })
$menuLogs.Add_Click({ $menuLogs.Add_Click({
$logsFolder = Split-Path $script:DefaultLogPath -Parent $logsFolder = Join-Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) 'Logs'
if (Test-Path $logsFolder) { if (Test-Path $logsFolder) {
Start-Process "explorer.exe" -ArgumentList $logsFolder Start-Process "explorer.exe" -ArgumentList $logsFolder
} }

View File

@@ -1,3 +1,24 @@
<#
.SYNOPSIS
Displays the Restore Backup wizard dialog.
.DESCRIPTION
Presents a modal wizard that lets the user choose and restore either a
registry backup or a Start Menu pinned-apps backup. Returns the user's
selection via $window.Tag.
.PARAMETER Owner
Optional parent WPF Window used to host this modal dialog. Defaults to the
shared $script:GuiWindow when not supplied.
.OUTPUTS
Hashtable
Returns a Hashtable describing the user's choice. Possible shapes:
RestoreRegistry - @{ Result='RestoreRegistry'; Backup=<normalizedBackup> }
RestoreStartMenu - @{ Result='RestoreStartMenu'; StartMenuScope=<scope>;
UseManualBackupFile=<bool>; BackupFilePath=<path|string> }
Cancelled - @{ Result='Cancelled' } (from New-RestoreDialogState)
#>
function Show-RestoreBackupDialog { function Show-RestoreBackupDialog {
param( param(
[System.Windows.Window]$Owner = $null [System.Windows.Window]$Owner = $null
@@ -61,7 +82,6 @@ function Show-RestoreBackupDialog {
$startMenuIntroPanel = $window.FindName('StartMenuIntroPanel') $startMenuIntroPanel = $window.FindName('StartMenuIntroPanel')
$startMenuScopeCombo = $window.FindName('StartMenuScopeCombo') $startMenuScopeCombo = $window.FindName('StartMenuScopeCombo')
$startMenuAutoBackupCheck = $window.FindName('StartMenuAutoBackupCheck') $startMenuAutoBackupCheck = $window.FindName('StartMenuAutoBackupCheck')
$startMenuAutoBackupCheck.Visibility = 'Collapsed'
$introInfoPanel = $window.FindName('IntroInfoPanel') $introInfoPanel = $window.FindName('IntroInfoPanel')
$overviewPanel = $window.FindName('OverviewPanel') $overviewPanel = $window.FindName('OverviewPanel')
$overviewFeaturesSection = $window.FindName('OverviewFeaturesSection') $overviewFeaturesSection = $window.FindName('OverviewFeaturesSection')
@@ -149,8 +169,9 @@ function Show-RestoreBackupDialog {
return return
} }
$hasSelectedFile = -not [string]::IsNullOrWhiteSpace($state.SelectedStartMenuBackupFilePath) $isAutoBackupEnabled = ($startMenuAutoBackupCheck.IsChecked -eq $true)
if ($hasSelectedFile) { $hasSelectedManualFile = -not [string]::IsNullOrWhiteSpace($state.SelectedStartMenuBackupFilePath)
if ($isAutoBackupEnabled -or $hasSelectedManualFile) {
$primaryActionBtn.Content = 'Restore backup' $primaryActionBtn.Content = 'Restore backup'
} }
else { else {
@@ -196,6 +217,10 @@ function Show-RestoreBackupDialog {
$primaryActionBtn.Visibility = 'Visible' $primaryActionBtn.Visibility = 'Visible'
$primaryActionBtn.IsDefault = $true $primaryActionBtn.IsDefault = $true
$chooseRegistryBtn.IsDefault = $false $chooseRegistryBtn.IsDefault = $false
# Show intro panel so user can configure scope & auto-detect
$startMenuAutoBackupCheck.IsChecked = $true
$state.SelectedStartMenuBackupFilePath = $null
& $refreshStartMenuUi & $refreshStartMenuUi
} }
@@ -291,22 +316,33 @@ function Show-RestoreBackupDialog {
} }
Write-Host "Backup file selected: $($openDialog.FileName)" Write-Host "Backup file selected: $($openDialog.FileName)"
try {
$selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName $selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName
if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) { if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) {
return return
} }
}
catch {
Show-MessageBox -Owner $window -Title 'Invalid Backup File' -Message "The selected file could not be loaded:`n$($_.Exception.Message)" -Button 'OK' -Icon 'Error' | Out-Null
return
}
$state.SelectedRegistryBackup = $selectedBackup $state.SelectedRegistryBackup = $selectedBackup
$primaryActionBtn.Content = 'Restore from backup' $primaryActionBtn.Content = 'Restore from backup'
} }
$handleStartMenuPrimaryAction = { $handleStartMenuPrimaryAction = {
if ([string]::IsNullOrWhiteSpace($state.SelectedStartMenuBackupFilePath)) { $scope = (& $getStartMenuScopeInfo).Scope
$useManualBackupFile = -not ($startMenuAutoBackupCheck.IsChecked -eq $true)
if ($useManualBackupFile -and [string]::IsNullOrWhiteSpace($state.SelectedStartMenuBackupFilePath)) {
$openDialog = New-Object Microsoft.Win32.OpenFileDialog $openDialog = New-Object Microsoft.Win32.OpenFileDialog
$openDialog.Title = 'Select Start Menu Backup File' $openDialog.Title = 'Select Start Menu Backup File'
$openDialog.Filter = 'Start Menu backup (*.bak)|*.bak' $openDialog.Filter = 'Start Menu backup (*.bak)|*.bak'
$openDialog.InitialDirectory = (Join-Path $script:AppDataPath 'Backups') $openDialog.InitialDirectory = "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
$openDialog.DefaultExt = '.bak'
if ($openDialog.ShowDialog($window) -ne $true) { if ($openDialog.ShowDialog($window) -ne $true) {
return return
@@ -318,10 +354,21 @@ function Show-RestoreBackupDialog {
return return
} }
$scope = (& $getStartMenuScopeInfo).Scope if (-not $useManualBackupFile) {
$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 = @{ $window.Tag = @{
Result = 'RestoreStartMenu' Result = 'RestoreStartMenu'
StartMenuScope = $scope StartMenuScope = $scope
UseManualBackupFile = $useManualBackupFile
BackupFilePath = $state.SelectedStartMenuBackupFilePath BackupFilePath = $state.SelectedStartMenuBackupFilePath
} }
$window.DialogResult = $true $window.DialogResult = $true
@@ -340,7 +387,16 @@ function Show-RestoreBackupDialog {
} }
} }
$startMenuAutoBackupCheck.Add_Checked({
$state.SelectedStartMenuBackupFilePath = $null
& $refreshStartMenuUi
})
$startMenuAutoBackupCheck.Add_Unchecked({
& $refreshStartMenuUi
})
$startMenuScopeCombo.Add_SelectionChanged({ $startMenuScopeCombo.Add_SelectionChanged({
$state.SelectedStartMenuBackupFilePath = $null
& $refreshStartMenuUi & $refreshStartMenuUi
}) })
@@ -374,6 +430,7 @@ function Show-RestoreBackupDialog {
if ($state.WizardStep -eq 'StartMenu') { if ($state.WizardStep -eq 'StartMenu') {
$state.SelectedStartMenuBackupFilePath = $null $state.SelectedStartMenuBackupFilePath = $null
$startMenuAutoBackupCheck.IsChecked = $true
} }
& $setWizardStep 'SelectType' & $setWizardStep 'SelectType'

View File

@@ -40,6 +40,7 @@ function Show-RestoreBackupWindow {
} }
elseif ($dialogResult.Result -eq 'RestoreStartMenu') { elseif ($dialogResult.Result -eq 'RestoreStartMenu') {
$scope = $dialogResult.StartMenuScope $scope = $dialogResult.StartMenuScope
$useManualBackupFile = ($dialogResult.UseManualBackupFile -eq $true)
$backupFilePath = $null $backupFilePath = $null
if ($dialogResult -is [hashtable] -and $dialogResult.ContainsKey('BackupFilePath')) { if ($dialogResult -is [hashtable] -and $dialogResult.ContainsKey('BackupFilePath')) {
$backupFilePath = $dialogResult['BackupFilePath'] $backupFilePath = $dialogResult['BackupFilePath']
@@ -48,7 +49,7 @@ function Show-RestoreBackupWindow {
$backupFilePath = $dialogResult.BackupFilePath $backupFilePath = $dialogResult.BackupFilePath
} }
if ([string]::IsNullOrWhiteSpace($backupFilePath)) { if ($useManualBackupFile -and [string]::IsNullOrWhiteSpace($backupFilePath)) {
throw 'Start Menu restore canceled: no backup file selected.' throw 'Start Menu restore canceled: no backup file selected.'
} }

View File

@@ -1,219 +0,0 @@
param (
[switch]$CLI,
[switch]$Silent,
[switch]$Verbose,
[switch]$Sysprep,
[string]$LogPath,
[string]$User,
[switch]$NoRestartExplorer,
[switch]$CreateRestorePoint,
[switch]$RunDefaults,
[switch]$RunDefaultsLite,
[switch]$RunSavedSettings,
[string]$Config,
[string]$Apps,
[string]$AppRemovalTarget,
[switch]$RemoveApps,
[switch]$RemoveGamingApps,
[switch]$RemoveHPApps,
[switch]$ForceRemoveEdge,
[switch]$DisableDVR,
[switch]$DisableGameBarIntegration,
[switch]$EnableWindowsSandbox,
[switch]$EnableWindowsSubsystemForLinux,
[switch]$DisableTelemetry,
[switch]$DisableSearchHistory,
[switch]$DisableFastStartup,
[switch]$DisableBitlockerAutoEncryption,
[switch]$DisableModernStandbyNetworking,
[switch]$DisableStorageSense,
[switch]$DisableUpdateASAP,
[switch]$PreventUpdateAutoReboot,
[switch]$DisableDeliveryOptimization,
[switch]$DisableBing,
[switch]$DisableStoreSearchSuggestions,
[switch]$DisableDesktopSpotlight,
[switch]$DisableLockscreenTips,
[switch]$DisableSuggestions,
[switch]$DisableLocationServices,
[switch]$DisableFindMyDevice,
[switch]$DisableEdgeAds,
[switch]$DisableBraveBloat,
[switch]$DisableSettings365Ads,
[switch]$DisableSettingsHome,
[switch]$ShowHiddenFolders,
[switch]$ShowKnownFileExt,
[switch]$HideDupliDrive,
[switch]$EnableDarkMode,
[switch]$DisableTransparency,
[switch]$DisableAnimations,
[switch]$TaskbarAlignLeft,
[switch]$CombineTaskbarAlways, [switch]$CombineTaskbarWhenFull, [switch]$CombineTaskbarNever,
[switch]$CombineMMTaskbarAlways, [switch]$CombineMMTaskbarWhenFull, [switch]$CombineMMTaskbarNever,
[switch]$MMTaskbarModeAll, [switch]$MMTaskbarModeMainActive, [switch]$MMTaskbarModeActive,
[switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb,
[switch]$HideTaskview,
[switch]$DisableStartRecommended,
[switch]$DisableStartAllApps, [switch]$StartAllAppsCategory, [switch]$StartAllAppsGrid, [switch]$StartAllAppsList,
[switch]$DisableStartPhoneLink,
[switch]$DisableCopilot,
[switch]$DisableRecall,
[switch]$DisableClickToDo,
[switch]$DisableAISvcAutoStart,
[switch]$DisablePaintAI,
[switch]$DisableNotepadAI,
[switch]$DisableEdgeAI,
[switch]$DisableSearchHighlights,
[switch]$DisableWidgets,
[switch]$HideChat,
[switch]$EnableEndTask,
[switch]$EnableLastActiveClick,
[switch]$ClearStart,
[string]$ReplaceStart,
[switch]$ClearStartAllUsers,
[string]$ReplaceStartAllUsers,
[switch]$RevertContextMenu,
[switch]$DisableDragTray,
[switch]$DisableMouseAcceleration,
[switch]$DisableStickyKeys,
[switch]$DisableWindowSnapping,
[switch]$DisableSnapAssist,
[switch]$DisableSnapLayouts,
[switch]$HideTabsInAltTab, [switch]$Show3TabsInAltTab, [switch]$Show5TabsInAltTab, [switch]$Show20TabsInAltTab,
[switch]$HideHome,
[switch]$HideGallery,
[switch]$ExplorerToHome,
[switch]$ExplorerToThisPC,
[switch]$ExplorerToDownloads,
[switch]$ExplorerToOneDrive,
[switch]$AddFoldersToThisPC,
[switch]$HideOnedrive,
[switch]$Hide3dObjects,
[switch]$HideMusic,
[switch]$HideIncludeInLibrary,
[switch]$HideGiveAccessTo,
[switch]$HideShare,
[switch]$ShowDriveLettersFirst,
[switch]$ShowDriveLettersLast,
[switch]$ShowNetworkDriveLettersFirst,
[switch]$HideDriveLetters
)
# Show error if current powershell environment does not have LanguageMode set to FullLanguage
if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
Write-Host "Error: Win11Debloat is unable to run on your system. PowerShell execution is restricted by security policies" -ForegroundColor Red
Write-Output ""
Write-Output "Press enter to exit..."
Read-Host | Out-Null
Exit
}
Clear-Host
Write-Output "-------------------------------------------------------------------------------------------"
Write-Output " Win11Debloat Script - Get Dev"
Write-Output "-------------------------------------------------------------------------------------------"
$tempRootPath = $env:TEMP
$tempWorkPath = Join-Path $tempRootPath 'Win11Debloat'
$tempArchivePath = Join-Path $tempRootPath 'win11debloat.zip'
Write-Output "> Downloading Win11Debloat for development..."
# Download latest version of Win11Debloat from GitHub master branch as zip archive
try {
Invoke-RestMethod "https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip" -OutFile $tempArchivePath
}
catch {
Write-Host "Error: Unable to fetch master branch from GitHub. Please check your internet connection and try again." -ForegroundColor Red
Write-Output ""
Write-Output "Press enter to exit..."
Read-Host | Out-Null
Exit
}
# Migrate old user data from previous runs to AppData before cleanup
if (Test-Path $tempWorkPath) {
$appDataPath = Join-Path $env:LOCALAPPDATA 'Win11Debloat'
if (-not (Test-Path $appDataPath)) { New-Item -ItemType Directory -Path $appDataPath -Force | Out-Null }
$oldBackupsDir = Join-Path $tempWorkPath 'Backups'
$oldLogsDir = Join-Path $tempWorkPath 'Logs'
$oldSettingsFile = Join-Path $tempWorkPath 'Config\LastUsedSettings.json'
if ((Test-Path $oldBackupsDir) -and (Get-ChildItem -Path $oldBackupsDir -ErrorAction SilentlyContinue)) {
$newBackupsDir = Join-Path $appDataPath 'Backups'
if (-not (Test-Path $newBackupsDir)) { New-Item -ItemType Directory -Path $newBackupsDir -Force | Out-Null }
Get-ChildItem -Path $oldBackupsDir | Move-Item -Destination $newBackupsDir -Force -ErrorAction SilentlyContinue
}
if ((Test-Path $oldLogsDir) -and (Get-ChildItem -Path $oldLogsDir -ErrorAction SilentlyContinue)) {
$newLogsDir = Join-Path $appDataPath 'Logs'
if (-not (Test-Path $newLogsDir)) { New-Item -ItemType Directory -Path $newLogsDir -Force | Out-Null }
Get-ChildItem -Path $oldLogsDir | Move-Item -Destination $newLogsDir -Force -ErrorAction SilentlyContinue
}
if (Test-Path $oldSettingsFile) {
Move-Item -Path $oldSettingsFile -Destination $appDataPath -Force -ErrorAction SilentlyContinue
}
Write-Output ""
Write-Output "> Cleaning up old Win11Debloat folder..."
Remove-Item $tempWorkPath -Recurse -Force
}
Write-Output ""
Write-Output "> Unpacking..."
# Unzip archive to Win11Debloat folder
Expand-Archive $tempArchivePath $tempWorkPath
# Remove archive
Remove-Item $tempArchivePath
# Move files
Get-ChildItem -Path (Join-Path $tempWorkPath '*Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath
# Make list of arguments to pass on to the script
$arguments = $($PSBoundParameters.GetEnumerator() | ForEach-Object {
if ($_.Value -eq $true) {
"-$($_.Key)"
}
else {
"-$($_.Key) ""$($_.Value)"""
}
})
Write-Output ""
Write-Output "> Launching Win11Debloat..."
# Minimize the powershell window when no parameters are provided
if ($arguments.Count -eq 0) {
$windowStyle = "Minimized"
}
else {
$windowStyle = "Normal"
}
# Remove Powershell 7 modules from path to prevent module loading issues in the script
if ($PSVersionTable.PSVersion.Major -ge 7) {
$NewPSModulePath = $env:PSModulePath -split ';' | Where-Object -FilterScript { $_ -like '*WindowsPowerShell*' }
$env:PSModulePath = $NewPSModulePath -join ';'
}
# Run Win11Debloat script with the provided arguments
$debloatScriptPath = Join-Path $tempWorkPath 'Win11Debloat.ps1'
$debloatProcess = Start-Process powershell.exe -WindowStyle $windowStyle -PassThru -ArgumentList "-executionpolicy bypass -File `"$debloatScriptPath`" $arguments" -Verb RunAs
# Wait for the process to finish before continuing
if ($null -ne $debloatProcess) {
$debloatProcess.WaitForExit()
}
# Remove all remaining script files
if (Test-Path $tempWorkPath) {
Write-Output ""
Write-Output "> Cleaning up..."
Remove-Item $tempWorkPath -Recurse -Force
}
Write-Output ""

View File

@@ -1,7 +1,9 @@
param ( param (
[switch]$Verbose,
[switch]$WhatIf,
[switch]$Dev,
[switch]$CLI, [switch]$CLI,
[switch]$Silent, [switch]$Silent,
[switch]$Verbose,
[switch]$Sysprep, [switch]$Sysprep,
[string]$LogPath, [string]$LogPath,
[string]$User, [string]$User,
@@ -110,7 +112,7 @@ if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
Clear-Host Clear-Host
Write-Output "-------------------------------------------------------------------------------------------" Write-Output "-------------------------------------------------------------------------------------------"
Write-Output " Win11Debloat Script - Get" Write-Output " Win11Debloat Script"
Write-Output "-------------------------------------------------------------------------------------------" Write-Output "-------------------------------------------------------------------------------------------"
$tempRootPath = $env:TEMP $tempRootPath = $env:TEMP
@@ -119,46 +121,48 @@ $tempArchivePath = Join-Path $tempRootPath 'win11debloat.zip'
Write-Output "> Downloading Win11Debloat..." Write-Output "> Downloading Win11Debloat..."
# Download latest version of Win11Debloat from GitHub as zip archive # Download Win11Debloat from GitHub as a zip archive.
try { try {
$LatestReleaseUri = (Invoke-RestMethod https://api.github.com/repos/Raphire/Win11Debloat/releases/latest).zipball_url if ($Dev) {
Invoke-RestMethod $LatestReleaseUri -OutFile $tempArchivePath $sourceUri = "https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip"
} else {
$sourceUri = (Invoke-RestMethod https://api.github.com/repos/Raphire/Win11Debloat/releases/latest).zipball_url
}
Invoke-RestMethod $sourceUri -OutFile $tempArchivePath
} }
catch { catch {
Write-Host "Error: Unable to fetch latest release from GitHub. Please check your internet connection and try again." -ForegroundColor Red Write-Host "Error: Unable to fetch required files from GitHub. Please check your internet connection and try again." -ForegroundColor Red
Write-Output "" Write-Output ""
Write-Output "Press enter to exit..." Write-Output "Press enter to exit..."
Read-Host | Out-Null Read-Host | Out-Null
Exit Exit
} }
# Migrate old user data from previous runs to AppData before cleanup # Remove old script folder if it exists, but keep configs, logs and backups
if (Test-Path $tempWorkPath) { if (Test-Path $tempWorkPath) {
$appDataPath = Join-Path $env:LOCALAPPDATA 'Win11Debloat'
if (-not (Test-Path $appDataPath)) { New-Item -ItemType Directory -Path $appDataPath -Force | Out-Null }
$oldBackupsDir = Join-Path $tempWorkPath 'Backups'
$oldLogsDir = Join-Path $tempWorkPath 'Logs'
$oldSettingsFile = Join-Path $tempWorkPath 'Config\LastUsedSettings.json'
if ((Test-Path $oldBackupsDir) -and (Get-ChildItem -Path $oldBackupsDir -ErrorAction SilentlyContinue)) {
$newBackupsDir = Join-Path $appDataPath 'Backups'
if (-not (Test-Path $newBackupsDir)) { New-Item -ItemType Directory -Path $newBackupsDir -Force | Out-Null }
Get-ChildItem -Path $oldBackupsDir | Move-Item -Destination $newBackupsDir -Force -ErrorAction SilentlyContinue
}
if ((Test-Path $oldLogsDir) -and (Get-ChildItem -Path $oldLogsDir -ErrorAction SilentlyContinue)) {
$newLogsDir = Join-Path $appDataPath 'Logs'
if (-not (Test-Path $newLogsDir)) { New-Item -ItemType Directory -Path $newLogsDir -Force | Out-Null }
Get-ChildItem -Path $oldLogsDir | Move-Item -Destination $newLogsDir -Force -ErrorAction SilentlyContinue
}
if (Test-Path $oldSettingsFile) {
Move-Item -Path $oldSettingsFile -Destination $appDataPath -Force -ErrorAction SilentlyContinue
}
Write-Output "" Write-Output ""
Write-Output "> Cleaning up old Win11Debloat folder..." Write-Output "> Cleaning up old script files..."
Remove-Item $tempWorkPath -Recurse -Force Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
}
$configDir = Join-Path $tempWorkPath 'Config'
$backupDir = Join-Path $tempWorkPath 'ConfigOld'
# Temporarily move existing config files if they exist to prevent them from being overwritten by the new script files, will be moved back after the new script is unpacked
if (Test-Path "$configDir") {
Write-Output ""
Write-Output "> Backing up existing config files..."
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
$filesToKeep = @(
'LastUsedSettings.json'
)
Get-ChildItem -Path "$configDir" -Recurse | Where-Object { $_.Name -in $filesToKeep } | Move-Item -Destination "$backupDir"
Remove-Item "$configDir" -Recurse -Force
} }
Write-Output "" Write-Output ""
@@ -173,8 +177,21 @@ Remove-Item $tempArchivePath
# Move files # Move files
Get-ChildItem -Path (Join-Path $tempWorkPath '*Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath Get-ChildItem -Path (Join-Path $tempWorkPath '*Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath
# Make list of arguments to pass on to the script # Add existing config files back to Config folder
$arguments = $($PSBoundParameters.GetEnumerator() | ForEach-Object { if (Test-Path "$backupDir") {
if (-not (Test-Path "$configDir")) {
New-Item -ItemType Directory -Path "$configDir" -Force | Out-Null
}
Write-Output ""
Write-Output "> Restoring existing config files..."
Get-ChildItem -Path "$backupDir" -Recurse | Move-Item -Destination "$configDir"
Remove-Item "$backupDir" -Recurse -Force
}
# Make list of arguments to pass on to the script (exclude the -Dev switch, which only affects this launcher)
$arguments = $($PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -ne 'Dev' } | ForEach-Object {
if ($_.Value -eq $true) { if ($_.Value -eq $true) {
"-$($_.Key)" "-$($_.Key)"
} }
@@ -209,12 +226,13 @@ if ($null -ne $debloatProcess) {
$debloatProcess.WaitForExit() $debloatProcess.WaitForExit()
} }
# Remove all remaining script files # Remove all remaining script files, except for configs, logs and backups
if (Test-Path $tempWorkPath) { if (Test-Path $tempWorkPath) {
Write-Output "" Write-Output ""
Write-Output "> Cleaning up..." Write-Output "> Cleaning up..."
Remove-Item $tempWorkPath -Recurse -Force # Cleanup, remove Win11Debloat directory
Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
} }
Write-Output "" Write-Output ""

View File

@@ -137,19 +137,19 @@ if (-not $isAdmin) {
} }
# Define script-level variables & paths # Define script-level variables & paths
$script:Version = "2026.06.14" $script:Version = "2026.06.24"
$configPath = Join-Path $PSScriptRoot 'Config' $configPath = Join-Path $PSScriptRoot 'Config'
$logsPath = Join-Path $PSScriptRoot 'Logs'
$schemasPath = Join-Path $PSScriptRoot 'Schemas' $schemasPath = Join-Path $PSScriptRoot 'Schemas'
$scriptsPath = Join-Path $PSScriptRoot 'Scripts' $scriptsPath = Join-Path $PSScriptRoot 'Scripts'
$script:AppDataPath = Join-Path $env:LOCALAPPDATA 'Win11Debloat'
$script:AppsListFilePath = Join-Path $configPath 'Apps.json' $script:AppsListFilePath = Join-Path $configPath 'Apps.json'
$script:DefaultSettingsFilePath = Join-Path $configPath 'DefaultSettings.json' $script:DefaultSettingsFilePath = Join-Path $configPath 'DefaultSettings.json'
$script:FeaturesFilePath = Join-Path $configPath 'Features.json' $script:FeaturesFilePath = Join-Path $configPath 'Features.json'
$script:SavedSettingsFilePath = Join-Path $script:AppDataPath 'LastUsedSettings.json' $script:SavedSettingsFilePath = Join-Path $configPath 'LastUsedSettings.json'
$script:DefaultLogPath = Join-Path (Join-Path $script:AppDataPath 'Logs') 'Win11Debloat.log' $script:DefaultLogPath = Join-Path $logsPath 'Win11Debloat.log'
$script:RegfilesPath = Join-Path $PSScriptRoot 'Regfiles' $script:RegfilesPath = Join-Path $PSScriptRoot 'Regfiles'
$script:RegistryBackupsPath = Join-Path $script:AppDataPath 'Backups' $script:RegistryBackupsPath = Join-Path $PSScriptRoot 'Backups'
$script:AssetsPath = Join-Path $PSScriptRoot 'Assets' $script:AssetsPath = Join-Path $PSScriptRoot 'Assets'
$script:AppSelectionSchema = Join-Path $schemasPath 'AppSelectionWindow.xaml' $script:AppSelectionSchema = Join-Path $schemasPath 'AppSelectionWindow.xaml'
$script:MainWindowSchema = Join-Path $schemasPath 'MainWindow.xaml' $script:MainWindowSchema = Join-Path $schemasPath 'MainWindow.xaml'
@@ -212,9 +212,6 @@ Write-Host ""
Write-Host "" Write-Host ""
# Log script output to 'Win11Debloat.log' at the specified path # Log script output to 'Win11Debloat.log' at the specified path
$logDir = if ($LogPath) { $LogPath } else { Split-Path $script:DefaultLogPath -Parent }
if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
if ($LogPath -and (Test-Path $LogPath)) { if ($LogPath -and (Test-Path $LogPath)) {
Start-Transcript -Path (Join-Path $LogPath 'Win11Debloat.log') -Append -IncludeInvocationHeader -Force | Out-Null Start-Transcript -Path (Join-Path $LogPath 'Win11Debloat.log') -Append -IncludeInvocationHeader -Force | Out-Null
} }