Compare commits

5 Commits

Author SHA1 Message Date
Jeffrey
67c9cc6ba3 Bump version 2026-06-10 17:41:13 +02:00
Jeffrey
157d26bb22 Add option to show & undo applied tweaks + more (#599)
* Remove RemoveCommApps and RemoveW11Outlook presets. These are largely redundant. Use -RemoveApps parameter instead

* Add additional options to change the `All Apps` view in the start menu (Hide, Grid, Category, List)

* Add clean start menu backup validation to start menu restore function

* Resolve nested quoting bug in Run.bat when path has spaces, see #583

* Fix desync issue when toggling "Only Show Installed" checkbox too fast

* Fix: add missing keys in Sysprep/Undo regfiles for Disabling Recall and Windows Suggested content

* Fix 'Disable Animations' Sysprep settings not being set for new users

* Update README.md

* Update CONTRIBUTING.md
2026-06-10 17:40:31 +02:00
HetCreep
53ca51dffd fix(appx): expose swallowed exceptions during Appx Package uninstallation via Write-Verbose (#617)
Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
2026-06-10 16:43:37 +02:00
soccerzockt
db24865051 Add Support for "-user" Parameter running under SYSTEM (#609)
Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
2026-06-07 22:51:01 +02:00
Jeffrey
33b77f19a0 Add confirmation dialogs & warning for Windows Terminal Removal
Removal of this app can cause Win11Debloat to fail, if the script is launched via Windows Terminal
2026-06-01 22:53:28 +02:00
53 changed files with 3803 additions and 2137 deletions

View File

@@ -67,7 +67,7 @@ Win11Debloat/
│ ├── DefaultSettings.json # Default configuration preset │ ├── DefaultSettings.json # Default configuration preset
│ ├── Features.json # All features with metadata │ ├── Features.json # All features with metadata
│ └── LastUsedSettings.json # Last used configuration (generated during use) │ └── LastUsedSettings.json # Last used configuration (generated during use)
├── Regfiles/ # Registry files for each feature ├── Regfiles/ # Registry files for all features
└── Schemas/ # XAML Schemas for GUI elements └── Schemas/ # XAML Schemas for GUI elements
``` ```
@@ -98,16 +98,16 @@ Avoid these common mistakes when contributing:
1. **Forgetting Get.ps1**: When adding a new command-line parameter, contributors often remember to add it to `Win11Debloat.ps1` but forget to add the same parameter to `Scripts/Get.ps1`. Both files **must** have matching parameters. 1. **Forgetting Get.ps1**: When adding a new command-line parameter, contributors often remember to add it to `Win11Debloat.ps1` but forget to add the same parameter to `Scripts/Get.ps1`. Both files **must** have matching parameters.
2. **Missing Registry Files**: Always create an `Undo` registry file for reversibility, aswell as a `Sysprep` registry file for Sysprep mode. 2. **Missing Registry Files**: Always create an `Undo` registry file for reversibility, aswell as a `Sysprep` registry file for applying changes to other users and Sysprep mode.
3. **Incorrect Registry Hives for Sysprep**: Sysprep registry files apply changes to Windows' default user, registry keys in the `HKEY_CURRENT_USER` hive must use `hkey_users\default` instead. Ensure you update **all** registry keys in the file. 3. **Incorrect Registry Hives for Sysprep**: Sysprep registry files are meant to apply changes to a different user. Registry keys in the `HKEY_CURRENT_USER` hive must use `hkey_users\default` instead. Ensure you update **all** registry keys in the file.
4. **Wrong Registry File Location**: 4. **Wrong Registry File Location**:
- Main action files go in `Regfiles/` - Main action files go in `Regfiles/`
- Undo files go in `Regfiles/Undo/` - Undo files go in `Regfiles/Undo/`
- Sysprep files go in `Regfiles/Sysprep/` - Sysprep files go in `Regfiles/Sysprep/`
Placing files in the wrong directory will cause the script to fail when trying to apply or undo changes. Placing files in the wrong directory may cause the script to fail when trying to apply or undo changes.
6. **Not Testing Undo Functionality**: Always test that your undo registry file properly reverts all changes. 6. **Not Testing Undo Functionality**: Always test that your undo registry file properly reverts all changes.
@@ -204,19 +204,20 @@ Add your feature to the `"Features"` array in `Config/Features.json`:
``` ```
**Field Descriptions**: **Field Descriptions**:
- `FeatureId`: Unique identifier (must match parameter name in Win11Debloat.ps1 and Get.ps1) - `FeatureId`: Unique identifier, this must match parameter name in the Win11Debloat.ps1 and Get.ps1 files.
- `Label`: Short description shown in the UI, written in a way to fit with the Action or UndoAction prefixed - `Label`: Short description shown in the UI and wiki documentation.
- `ToolTip`: Detailed explanation of what the feature does, used for tooltips in the GUI - `ToolTip`: Detailed explanation of what the feature does, used for tooltips in the GUI.
- `Category`: One of the predefined categories (see Categories array in Features.json), features without a category won't be loaded into the GUI. - `Category`: One of the predefined categories (see Categories array in Features.json), features without a category won't be loaded into the GUI.
- `Priority`: Optional. The priority value (int) is used to sort features within a category. If this field is omitted the feature will be sorted based on the order in the Features.json file. - `Priority`: Optional. The priority value (int) is used to sort features within a category. If this field is omitted the feature will be sorted based on the order in the Features.json file.
- `Action`: Action word for the feature (e.g., "Disable", "Enable", "Hide", "Show") - `RegistryKey`: Filename of the registry file to apply (in Regfiles/ directory) or null if feature does not require registry changes.
- `RegistryKey`: Filename of the registry file to apply (in Regfiles/ directory) or null if feature does not require registry changes - `ApplyText`: Message shown when applying the feature.
- `ApplyText`: Message shown when applying the feature - `UndoLabel`: Short description for the undo shown in the UI.
- `UndoAction`: Action word for reverting (e.g., "Enable", "Show") - `ApplyUndoText`: Message shown when undoing the feature.
- `RegistryUndoKey`: Filename of the registry file to revert changes or null if feature does not require registry changes - `RegistryUndoKey`: Filename of the registry file to revert changes or null if feature does not require registry changes.
- `RequiresReboot`: Optional boolean. Set to `true` if the feature requires a system reboot to take effect - `RequiresReboot`: Optional boolean. Set to `true` if the feature requires a system reboot to take effect.
- `MinVersion`: Minimum Windows build version (e.g., "22000") or null - `DisableWhenApplied`: Optional boolean. Set to `true` if the feature has no supported undo method.
- `MaxVersion`: Maximum Windows version or null - `MinVersion`: Minimum Windows build version (e.g., "22000") or null.
- `MaxVersion`: Maximum Windows version or null.
#### 3. Add Command-Line Parameter #### 3. Add Command-Line Parameter

View File

@@ -781,9 +781,9 @@
{ {
"FriendlyName": "Windows Terminal", "FriendlyName": "Windows Terminal",
"AppId": "Microsoft.WindowsTerminal", "AppId": "Microsoft.WindowsTerminal",
"Description": "Default terminal app in windows 11 (Command Prompt, PowerShell, WSL)", "Description": "Default terminal app in windows 11 (Command Prompt, PowerShell, WSL), WARNING: Do not remove if you launched Win11Debloat from Windows Terminal, as this will cause the script to fail.",
"SelectedByDefault": false, "SelectedByDefault": false,
"Recommendation": "optional" "Recommendation": "unsafe"
}, },
{ {
"FriendlyName": "Xbox TCUI Framework", "FriendlyName": "Xbox TCUI Framework",

View File

@@ -1,4 +1,4 @@
{ {
"Version": "1.0", "Version": "1.0",
"Categories": [ "Categories": [
{ {
@@ -280,6 +280,38 @@
] ]
} }
] ]
},
{
"GroupId": "StartAllAppsView",
"Label": "Start menu 'All Apps' view",
"ToolTip": "This setting allows you to change the layout of the 'All Apps' section in the start menu, or hide it entirely. Hiding this section may make it harder to find installed apps on your system. This feature uses policies, which will lock down certain settings.",
"Category": "Start Menu & Search",
"Values": [
{
"Label": "Hide",
"FeatureIds": [
"DisableStartAllApps"
]
},
{
"Label": "Category (Default)",
"FeatureIds": [
"StartAllAppsCategory"
]
},
{
"Label": "Grid",
"FeatureIds": [
"StartAllAppsGrid"
]
},
{
"Label": "List",
"FeatureIds": [
"StartAllAppsList"
]
}
]
} }
], ],
"Features": [ "Features": [
@@ -288,7 +320,9 @@
"Label": "Remove the apps specified with the 'Apps' parameter", "Label": "Remove the apps specified with the 'Apps' parameter",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing selected apps...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -298,7 +332,9 @@
"Label": "The selection of apps to remove, specified as a comma separated list. Use 'Default' (or omit) to use the default apps list", "Label": "The selection of apps to remove, specified as a comma separated list. Use 'Default' (or omit) to use the default apps list",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing selected apps...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -308,27 +344,9 @@
"Label": "Remove custom selection of apps", "Label": "Remove custom selection of apps",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing selected apps...",
"RegistryUndoKey": null, "UndoLabel": null,
"MinVersion": null, "ApplyUndoText": null,
"MaxVersion": null
},
{
"FeatureId": "RemoveCommApps",
"Label": "Remove the Mail, Calendar, and People apps",
"Category": null,
"RegistryKey": null,
"ApplyText": null,
"RegistryUndoKey": null,
"MinVersion": null,
"MaxVersion": null
},
{
"FeatureId": "RemoveW11Outlook",
"Label": "Remove the new Outlook for Windows app",
"Category": null,
"RegistryKey": null,
"ApplyText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -338,7 +356,9 @@
"Label": "Remove the Xbox App and Xbox Gamebar", "Label": "Remove the Xbox App and Xbox Gamebar",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing gaming related apps...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -348,7 +368,9 @@
"Label": "Remove HP OEM applications", "Label": "Remove HP OEM applications",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing HP apps...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -359,6 +381,8 @@
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": null,
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -370,6 +394,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Telemetry.reg", "RegistryKey": "Disable_Telemetry.reg",
"ApplyText": "Disabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...", "ApplyText": "Disabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...",
"UndoLabel": "Enable telemetry, tracking & targeted ads",
"ApplyUndoText": "Enabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...",
"RegistryUndoKey": "Enable_Telemetry.reg", "RegistryUndoKey": "Enable_Telemetry.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -381,6 +407,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Windows_Suggestions.reg", "RegistryKey": "Disable_Windows_Suggestions.reg",
"ApplyText": "Disabling tips, tricks, suggestions and ads throughout Windows...", "ApplyText": "Disabling tips, tricks, suggestions and ads throughout Windows...",
"UndoLabel": "Enable tips, tricks & suggested content throughout Windows",
"ApplyUndoText": "Enabling tips, tricks, suggestions and ads throughout Windows...",
"RegistryUndoKey": "Enable_Windows_Suggestions.reg", "RegistryUndoKey": "Enable_Windows_Suggestions.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -392,6 +420,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Location_Services.reg", "RegistryKey": "Disable_Location_Services.reg",
"ApplyText": "Disabling Windows location services and app location access...", "ApplyText": "Disabling Windows location services and app location access...",
"UndoLabel": "Enable Windows location services & app location access",
"ApplyUndoText": "Enabling Windows location services and app location access...",
"RegistryUndoKey": "Enable_Location_Services.reg", "RegistryUndoKey": "Enable_Location_Services.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -403,6 +433,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Find_My_Device.reg", "RegistryKey": "Disable_Find_My_Device.reg",
"ApplyText": "Disabling Find My Device location tracking...", "ApplyText": "Disabling Find My Device location tracking...",
"UndoLabel": "Enable Find My Device location tracking",
"ApplyUndoText": "Enabling Find My Device location tracking...",
"RegistryUndoKey": "Enable_Find_My_Device.reg", "RegistryUndoKey": "Enable_Find_My_Device.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -414,6 +446,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Lockscreen_Tips.reg", "RegistryKey": "Disable_Lockscreen_Tips.reg",
"ApplyText": "Disabling tips & tricks on the lock screen...", "ApplyText": "Disabling tips & tricks on the lock screen...",
"UndoLabel": "Enable tips & tricks on the lock screen",
"ApplyUndoText": "Enabling tips & tricks on the lock screen...",
"RegistryUndoKey": "Enable_Lockscreen_Tips.reg", "RegistryUndoKey": "Enable_Lockscreen_Tips.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -425,6 +459,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Desktop_Spotlight.reg", "RegistryKey": "Disable_Desktop_Spotlight.reg",
"ApplyText": "Disabling the 'Windows Spotlight' desktop background option...", "ApplyText": "Disabling the 'Windows Spotlight' desktop background option...",
"UndoLabel": "Enable Windows Spotlight for desktop",
"ApplyUndoText": "Enabling the 'Windows Spotlight' desktop background option...",
"RegistryUndoKey": "Enable_Desktop_Spotlight.reg", "RegistryUndoKey": "Enable_Desktop_Spotlight.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -436,6 +472,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Edge_Ads_And_Suggestions.reg", "RegistryKey": "Disable_Edge_Ads_And_Suggestions.reg",
"ApplyText": "Disabling ads, suggestions and the MSN news feed in Microsoft Edge...", "ApplyText": "Disabling ads, suggestions and the MSN news feed in Microsoft Edge...",
"UndoLabel": "Enable ads, suggestions and newsfeed in Edge",
"ApplyUndoText": "Enabling ads, suggestions and the MSN news feed in Microsoft Edge...",
"RegistryUndoKey": "Enable_Edge_Ads_And_Suggestions.reg", "RegistryUndoKey": "Enable_Edge_Ads_And_Suggestions.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -447,6 +485,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_Copilot.reg", "RegistryKey": "Disable_Copilot.reg",
"ApplyText": "Disabling Microsoft Copilot...", "ApplyText": "Disabling Microsoft Copilot...",
"UndoLabel": "Enable Microsoft Copilot",
"ApplyUndoText": "Enabling Microsoft Copilot...",
"RegistryUndoKey": "Enable_Copilot.reg", "RegistryUndoKey": "Enable_Copilot.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -458,6 +498,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_AI_Recall.reg", "RegistryKey": "Disable_AI_Recall.reg",
"ApplyText": "Disabling Windows Recall...", "ApplyText": "Disabling Windows Recall...",
"UndoLabel": "Enable Windows Recall",
"ApplyUndoText": "Enabling Windows Recall...",
"RegistryUndoKey": "Enable_AI_Recall.reg", "RegistryUndoKey": "Enable_AI_Recall.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -469,6 +511,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_Click_to_Do.reg", "RegistryKey": "Disable_Click_to_Do.reg",
"ApplyText": "Disabling Click to Do...", "ApplyText": "Disabling Click to Do...",
"UndoLabel": "Enable Click To Do, AI text & image analysis",
"ApplyUndoText": "Enabling Click to Do...",
"RegistryUndoKey": "Enable_Click_to_Do.reg", "RegistryUndoKey": "Enable_Click_to_Do.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -480,6 +524,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_AI_Service_Auto_Start.reg", "RegistryKey": "Disable_AI_Service_Auto_Start.reg",
"ApplyText": "Preventing AI service from starting automatically...", "ApplyText": "Preventing AI service from starting automatically...",
"UndoLabel": "Allow AI service to start automatically",
"ApplyUndoText": "Allowing AI service to start automatically...",
"RegistryUndoKey": "Enable_AI_Service_Auto_Start.reg", "RegistryUndoKey": "Enable_AI_Service_Auto_Start.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -491,6 +537,8 @@
"Category": "Gaming", "Category": "Gaming",
"RegistryKey": "Disable_DVR.reg", "RegistryKey": "Disable_DVR.reg",
"ApplyText": "Disabling Xbox game/screen recording...", "ApplyText": "Disabling Xbox game/screen recording...",
"UndoLabel": "Enable Xbox game/screen recording",
"ApplyUndoText": "Enabling Xbox game/screen recording...",
"RegistryUndoKey": "Enable_DVR.reg", "RegistryUndoKey": "Enable_DVR.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -502,6 +550,8 @@
"Category": "Gaming", "Category": "Gaming",
"RegistryKey": "Disable_Game_Bar_Integration.reg", "RegistryKey": "Disable_Game_Bar_Integration.reg",
"ApplyText": "Disabling Game Bar integration...", "ApplyText": "Disabling Game Bar integration...",
"UndoLabel": "Enable Game Bar integration",
"ApplyUndoText": "Enabling Game Bar integration...",
"RegistryUndoKey": "Enable_Game_Bar_Integration.reg", "RegistryUndoKey": "Enable_Game_Bar_Integration.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -511,7 +561,9 @@
"Label": "Remove all pinned apps from the start menu for this user only", "Label": "Remove all pinned apps from the start menu for this user only",
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing all pinned apps from the start menu",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -521,7 +573,9 @@
"Label": "Remove all pinned apps from the start menu for all existing and new users", "Label": "Remove all pinned apps from the start menu for all existing and new users",
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Removing all pinned apps from the start menu for all users",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -531,7 +585,9 @@
"Label": "Replace the start menu layout for this user only with the provided template file", "Label": "Replace the start menu layout for this user only with the provided template file",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Replacing the start menu",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -541,7 +597,9 @@
"Label": "Replace the start menu layout for all existing and new users with the provided template file", "Label": "Replace the start menu layout for all existing and new users with the provided template file",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Replacing the start menu for all users",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -553,6 +611,8 @@
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": "Disable_Start_Recommended.reg", "RegistryKey": "Disable_Start_Recommended.reg",
"ApplyText": "Disabling the start menu recommended section...", "ApplyText": "Disabling the start menu recommended section...",
"UndoLabel": "Show recommended section in the start menu",
"ApplyUndoText": "Enabling the start menu recommended section...",
"RegistryUndoKey": "Enable_Start_Recommended.reg", "RegistryUndoKey": "Enable_Start_Recommended.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -564,6 +624,8 @@
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": "Disable_Start_All_Apps.reg", "RegistryKey": "Disable_Start_All_Apps.reg",
"ApplyText": "Disabling the 'All Apps' section in the start menu...", "ApplyText": "Disabling the 'All Apps' section in the start menu...",
"UndoLabel": "Show 'All Apps' section in the start menu",
"ApplyUndoText": "Enabling the 'All Apps' section in the start menu...",
"RegistryUndoKey": "Enable_Start_All_Apps.reg", "RegistryUndoKey": "Enable_Start_All_Apps.reg",
"MinVersion": 26200, "MinVersion": 26200,
"MaxVersion": null "MaxVersion": null
@@ -575,6 +637,8 @@
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": "Disable_Phone_Link_In_Start.reg", "RegistryKey": "Disable_Phone_Link_In_Start.reg",
"ApplyText": "Disabling the Phone Link mobile devices integration in the start menu...", "ApplyText": "Disabling the Phone Link mobile devices integration in the start menu...",
"UndoLabel": "Enable Phone Link integration in the start menu",
"ApplyUndoText": "Enabling the Phone Link mobile devices integration in the start menu...",
"RegistryUndoKey": "Enable_Phone_Link_In_Start.reg", "RegistryUndoKey": "Enable_Phone_Link_In_Start.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -586,6 +650,8 @@
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": "Disable_Bing_Cortana_In_Search.reg", "RegistryKey": "Disable_Bing_Cortana_In_Search.reg",
"ApplyText": "Disabling Bing web search & Copilot integration in Windows search...", "ApplyText": "Disabling Bing web search & Copilot integration in Windows search...",
"UndoLabel": "Enable Bing web search & Copilot integration in search",
"ApplyUndoText": "Enabling Bing web search & Copilot integration in Windows search...",
"RegistryUndoKey": "Enable_Bing_Cortana_In_Search.reg", "RegistryUndoKey": "Enable_Bing_Cortana_In_Search.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -596,7 +662,9 @@
"ToolTip": "This will disable the Microsoft Store app suggestions in Windows search.", "ToolTip": "This will disable the Microsoft Store app suggestions in Windows search.",
"Category": "Start Menu & Search", "Category": "Start Menu & Search",
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Disabling Microsoft Store app suggestions in search...",
"UndoLabel": "Enable Microsoft Store app suggestions in search",
"ApplyUndoText": "Enabling Microsoft Store app suggestions in search...",
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -608,6 +676,8 @@
"Category": "Privacy & Suggested Content", "Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Settings_365_Ads.reg", "RegistryKey": "Disable_Settings_365_Ads.reg",
"ApplyText": "Disabling Microsoft 365 Copilot ads in Settings Home...", "ApplyText": "Disabling Microsoft 365 Copilot ads in Settings Home...",
"UndoLabel": "Show Microsoft 365 Copilot ads in Settings Home",
"ApplyUndoText": "Enabling Microsoft 365 Copilot ads in Settings Home...",
"RegistryUndoKey": "Enable_Settings_365_Ads.reg", "RegistryUndoKey": "Enable_Settings_365_Ads.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -619,6 +689,8 @@
"Category": "Other", "Category": "Other",
"RegistryKey": "Disable_Settings_Home.reg", "RegistryKey": "Disable_Settings_Home.reg",
"ApplyText": "Disabling the Settings Home page...", "ApplyText": "Disabling the Settings Home page...",
"UndoLabel": "Show Settings 'Home' page",
"ApplyUndoText": "Enabling the Settings Home page...",
"RegistryUndoKey": "Enable_Settings_Home.reg", "RegistryUndoKey": "Enable_Settings_Home.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -630,6 +702,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_Edge_AI_Features.reg", "RegistryKey": "Disable_Edge_AI_Features.reg",
"ApplyText": "Disabling AI features in Microsoft Edge...", "ApplyText": "Disabling AI features in Microsoft Edge...",
"UndoLabel": "Enable AI features in Microsoft Edge",
"ApplyUndoText": "Enabling AI features in Microsoft Edge...",
"RegistryUndoKey": "Enable_Edge_AI_Features.reg", "RegistryUndoKey": "Enable_Edge_AI_Features.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -641,6 +715,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_Paint_AI_Features.reg", "RegistryKey": "Disable_Paint_AI_Features.reg",
"ApplyText": "Disabling AI features in Paint...", "ApplyText": "Disabling AI features in Paint...",
"UndoLabel": "Enable AI features in Paint",
"ApplyUndoText": "Enabling AI features in Paint...",
"RegistryUndoKey": "Enable_Paint_AI_Features.reg", "RegistryUndoKey": "Enable_Paint_AI_Features.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -652,6 +728,8 @@
"Category": "AI", "Category": "AI",
"RegistryKey": "Disable_Notepad_AI_Features.reg", "RegistryKey": "Disable_Notepad_AI_Features.reg",
"ApplyText": "Disabling AI features in Notepad...", "ApplyText": "Disabling AI features in Notepad...",
"UndoLabel": "Enable AI features in Notepad",
"ApplyUndoText": "Enabling AI features in Notepad...",
"RegistryUndoKey": "Enable_Notepad_AI_Features.reg", "RegistryUndoKey": "Enable_Notepad_AI_Features.reg",
"MinVersion": 22621, "MinVersion": 22621,
"MaxVersion": null "MaxVersion": null
@@ -663,6 +741,8 @@
"Category": "Appearance", "Category": "Appearance",
"RegistryKey": "Enable_Dark_Mode.reg", "RegistryKey": "Enable_Dark_Mode.reg",
"ApplyText": "Enabling dark mode for system and apps...", "ApplyText": "Enabling dark mode for system and apps...",
"UndoLabel": "Disable dark theme for system and apps",
"ApplyUndoText": "Disabling dark mode for system and apps...",
"RegistryUndoKey": "Enable_Light_Mode.reg", "RegistryUndoKey": "Enable_Light_Mode.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -674,6 +754,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Share_Drag_Tray.reg", "RegistryKey": "Disable_Share_Drag_Tray.reg",
"ApplyText": "Disabling Drag Tray...", "ApplyText": "Disabling Drag Tray...",
"UndoLabel": "Enable 'Drag Tray' for sharing & moving files",
"ApplyUndoText": "Enabling Drag Tray...",
"RegistryUndoKey": "Enable_Share_Drag_Tray.reg", "RegistryUndoKey": "Enable_Share_Drag_Tray.reg",
"MinVersion": 26200, "MinVersion": 26200,
"MaxVersion": null "MaxVersion": null
@@ -685,6 +767,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Show_More_Options_Context_Menu.reg", "RegistryKey": "Disable_Show_More_Options_Context_Menu.reg",
"ApplyText": "Restoring the classic Windows 10 style context menu...", "ApplyText": "Restoring the classic Windows 10 style context menu...",
"UndoLabel": "Use Windows 11 context menu style",
"ApplyUndoText": "Restoring the Windows 11 style context menu...",
"RegistryUndoKey": "Enable_W11_Style_Context_Menu.reg", "RegistryUndoKey": "Enable_W11_Style_Context_Menu.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -696,6 +780,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Enhance_Pointer_Precision.reg", "RegistryKey": "Disable_Enhance_Pointer_Precision.reg",
"ApplyText": "Turning off Enhanced Pointer Precision...", "ApplyText": "Turning off Enhanced Pointer Precision...",
"UndoLabel": "Enable Enhance Pointer Precision (mouse acceleration)",
"ApplyUndoText": "Turning on Enhanced Pointer Precision...",
"RegistryUndoKey": "Enable_Enhance_Pointer_Precision.reg", "RegistryUndoKey": "Enable_Enhance_Pointer_Precision.reg",
"RequiresReboot": true, "RequiresReboot": true,
"MinVersion": null, "MinVersion": null,
@@ -708,6 +794,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Sticky_Keys_Shortcut.reg", "RegistryKey": "Disable_Sticky_Keys_Shortcut.reg",
"ApplyText": "Disabling the Sticky Keys keyboard shortcut...", "ApplyText": "Disabling the Sticky Keys keyboard shortcut...",
"UndoLabel": "Enable Sticky Keys keyboard shortcut (5x shift)",
"ApplyUndoText": "Enabling the Sticky Keys keyboard shortcut...",
"RegistryUndoKey": "Enable_Sticky_Keys_Shortcut.reg", "RegistryUndoKey": "Enable_Sticky_Keys_Shortcut.reg",
"RequiresReboot": true, "RequiresReboot": true,
"MinVersion": 26100, "MinVersion": 26100,
@@ -721,6 +809,8 @@
"Priority": 1, "Priority": 1,
"RegistryKey": "Disable_Window_Snapping.reg", "RegistryKey": "Disable_Window_Snapping.reg",
"ApplyText": "Disabling window snapping...", "ApplyText": "Disabling window snapping...",
"UndoLabel": "Enable window snapping",
"ApplyUndoText": "Enabling window snapping...",
"RegistryUndoKey": "Enable_Window_Snapping.reg", "RegistryUndoKey": "Enable_Window_Snapping.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -733,6 +823,8 @@
"Priority": 2, "Priority": 2,
"RegistryKey": "Disable_Snap_Assist.reg", "RegistryKey": "Disable_Snap_Assist.reg",
"ApplyText": "Disabling the Snap Assist suggestions...", "ApplyText": "Disabling the Snap Assist suggestions...",
"UndoLabel": "Enable showing app suggestions when snapping windows",
"ApplyUndoText": "Enabling the Snap Assist suggestions...",
"RegistryUndoKey": "Enable_Snap_Assist.reg", "RegistryUndoKey": "Enable_Snap_Assist.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -745,6 +837,8 @@
"Priority": 3, "Priority": 3,
"RegistryKey": "Disable_Snap_Layouts.reg", "RegistryKey": "Disable_Snap_Layouts.reg",
"ApplyText": "Hiding snap layouts when dragging windows to top of the screen and on maximize button...", "ApplyText": "Hiding snap layouts when dragging windows to top of the screen and on maximize button...",
"UndoLabel": "Show snap layout flyout at top of screen and on maximize button",
"ApplyUndoText": "Showing snap layouts when dragging windows to top of the screen and on maximize button...",
"RegistryUndoKey": "Enable_Snap_Layouts.reg", "RegistryUndoKey": "Enable_Snap_Layouts.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -755,6 +849,8 @@
"Category": "Multi-tasking", "Category": "Multi-tasking",
"RegistryKey": "Hide_Tabs_In_Alt_Tab.reg", "RegistryKey": "Hide_Tabs_In_Alt_Tab.reg",
"ApplyText": "Disable showing tabs from apps when snapping or pressing Alt+Tab...", "ApplyText": "Disable showing tabs from apps when snapping or pressing Alt+Tab...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -765,6 +861,8 @@
"Category": "Multi-tasking", "Category": "Multi-tasking",
"RegistryKey": "Show_3_Tabs_In_Alt_Tab.reg", "RegistryKey": "Show_3_Tabs_In_Alt_Tab.reg",
"ApplyText": "Enable showing 3 tabs from apps when snapping or pressing Alt+Tab...", "ApplyText": "Enable showing 3 tabs from apps when snapping or pressing Alt+Tab...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -775,6 +873,8 @@
"Category": "Multi-tasking", "Category": "Multi-tasking",
"RegistryKey": "Show_5_Tabs_In_Alt_Tab.reg", "RegistryKey": "Show_5_Tabs_In_Alt_Tab.reg",
"ApplyText": "Enable showing 5 tabs from apps when snapping or pressing Alt+Tab...", "ApplyText": "Enable showing 5 tabs from apps when snapping or pressing Alt+Tab...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -785,6 +885,8 @@
"Category": "Multi-tasking", "Category": "Multi-tasking",
"RegistryKey": "Show_20_Tabs_In_Alt_Tab.reg", "RegistryKey": "Show_20_Tabs_In_Alt_Tab.reg",
"ApplyText": "Enable showing 20 tabs from apps when snapping or pressing Alt+Tab...", "ApplyText": "Enable showing 20 tabs from apps when snapping or pressing Alt+Tab...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -797,6 +899,8 @@
"Priority": 1, "Priority": 1,
"RegistryKey": "Align_Taskbar_Left.reg", "RegistryKey": "Align_Taskbar_Left.reg",
"ApplyText": "Aligning taskbar buttons to the left...", "ApplyText": "Aligning taskbar buttons to the left...",
"UndoLabel": "Align taskbar to the center",
"ApplyUndoText": "Aligning taskbar buttons to the center...",
"RegistryUndoKey": "Align_Taskbar_Center.reg", "RegistryUndoKey": "Align_Taskbar_Center.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -807,6 +911,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Hide_Search_Taskbar.reg", "RegistryKey": "Hide_Search_Taskbar.reg",
"ApplyText": "Hiding the search icon from the taskbar...", "ApplyText": "Hiding the search icon from the taskbar...",
"UndoLabel": "Show search box on the taskbar",
"ApplyUndoText": "Changing taskbar search to search box...",
"RegistryUndoKey": "Show_Search_Box.reg", "RegistryUndoKey": "Show_Search_Box.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -817,6 +923,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Show_Search_Icon.reg", "RegistryKey": "Show_Search_Icon.reg",
"ApplyText": "Changing taskbar search to icon only...", "ApplyText": "Changing taskbar search to icon only...",
"UndoLabel": "Show search box on the taskbar",
"ApplyUndoText": "Changing taskbar search to search box...",
"RegistryUndoKey": "Show_Search_Box.reg", "RegistryUndoKey": "Show_Search_Box.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -827,6 +935,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Show_Search_Icon_And_Label.reg", "RegistryKey": "Show_Search_Icon_And_Label.reg",
"ApplyText": "Changing taskbar search to icon with label...", "ApplyText": "Changing taskbar search to icon with label...",
"UndoLabel": "Show search box on the taskbar",
"ApplyUndoText": "Changing taskbar search to search box...",
"RegistryUndoKey": "Show_Search_Box.reg", "RegistryUndoKey": "Show_Search_Box.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -837,6 +947,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Show_Search_Box.reg", "RegistryKey": "Show_Search_Box.reg",
"ApplyText": "Changing taskbar search to search box...", "ApplyText": "Changing taskbar search to search box...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -849,6 +961,8 @@
"Priority": 3, "Priority": 3,
"RegistryKey": "Hide_Taskview_Taskbar.reg", "RegistryKey": "Hide_Taskview_Taskbar.reg",
"ApplyText": "Hiding the taskview button from the taskbar...", "ApplyText": "Hiding the taskview button from the taskbar...",
"UndoLabel": "Show 'Task view' button on the taskbar",
"ApplyUndoText": "Showing the taskview button from the taskbar...",
"RegistryUndoKey": "Show_Taskview_Taskbar.reg", "RegistryUndoKey": "Show_Taskview_Taskbar.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -860,8 +974,11 @@
"Category": "Taskbar", "Category": "Taskbar",
"Priority": 4, "Priority": 4,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Disabling widgets on the taskbar & lock screen...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"DisableWhenApplied": true,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
}, },
@@ -873,6 +990,8 @@
"Priority": 5, "Priority": 5,
"RegistryKey": "Disable_Chat_Taskbar.reg", "RegistryKey": "Disable_Chat_Taskbar.reg",
"ApplyText": "Hiding the chat icon from the taskbar...", "ApplyText": "Hiding the chat icon from the taskbar...",
"UndoLabel": "Show Chat (meet now) icon on the taskbar",
"ApplyUndoText": "Showing the chat icon from the taskbar...",
"RegistryUndoKey": "Enable_Chat_Taskbar.reg", "RegistryUndoKey": "Enable_Chat_Taskbar.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 22621 "MaxVersion": 22621
@@ -884,6 +1003,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Storage_Sense.reg", "RegistryKey": "Disable_Storage_Sense.reg",
"ApplyText": "Disabling Storage Sense automatic disk cleanup...", "ApplyText": "Disabling Storage Sense automatic disk cleanup...",
"UndoLabel": "Enable Storage Sense automatic disk cleanup",
"ApplyUndoText": "Enabling Storage Sense automatic disk cleanup...",
"RegistryUndoKey": "Enable_Storage_Sense.reg", "RegistryUndoKey": "Enable_Storage_Sense.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -895,6 +1016,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Fast_Startup.reg", "RegistryKey": "Disable_Fast_Startup.reg",
"ApplyText": "Disabling Fast Start-up...", "ApplyText": "Disabling Fast Start-up...",
"UndoLabel": "Enable fast start-up",
"ApplyUndoText": "Enabling Fast Start-up...",
"RegistryUndoKey": "Enable_Fast_Startup.reg", "RegistryUndoKey": "Enable_Fast_Startup.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -906,6 +1029,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Bitlocker_Auto_Encryption.reg", "RegistryKey": "Disable_Bitlocker_Auto_Encryption.reg",
"ApplyText": "Disabling BitLocker automatic device encryption...", "ApplyText": "Disabling BitLocker automatic device encryption...",
"UndoLabel": "Enable BitLocker automatic device encryption",
"ApplyUndoText": "Enabling BitLocker automatic device encryption...",
"RegistryUndoKey": "Enable_Bitlocker_Auto_Encryption.reg", "RegistryUndoKey": "Enable_Bitlocker_Auto_Encryption.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -917,6 +1042,8 @@
"Category": "System", "Category": "System",
"RegistryKey": "Disable_Modern_Standby_Networking.reg", "RegistryKey": "Disable_Modern_Standby_Networking.reg",
"ApplyText": "Disabling network connectivity during Modern Standby...", "ApplyText": "Disabling network connectivity during Modern Standby...",
"UndoLabel": "Enable Modern Standby network connectivity",
"ApplyUndoText": "Enabling network connectivity during Modern Standby...",
"RegistryUndoKey": "Enable_Modern_Standby_Networking.reg", "RegistryUndoKey": "Enable_Modern_Standby_Networking.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -929,6 +1056,8 @@
"Priority": 6, "Priority": 6,
"RegistryKey": "Enable_End_Task.reg", "RegistryKey": "Enable_End_Task.reg",
"ApplyText": "Enabling the 'End Task' option in the taskbar right click menu...", "ApplyText": "Enabling the 'End Task' option in the taskbar right click menu...",
"UndoLabel": "Hide 'End Task' option in taskbar context menu",
"ApplyUndoText": "Disabling the 'End Task' option in the taskbar right click menu...",
"RegistryUndoKey": "Disable_End_Task.reg", "RegistryUndoKey": "Disable_End_Task.reg",
"MinVersion": 22631, "MinVersion": 22631,
"MaxVersion": null "MaxVersion": null
@@ -941,6 +1070,8 @@
"Priority": 7, "Priority": 7,
"RegistryKey": "Enable_Last_Active_Click.reg", "RegistryKey": "Enable_Last_Active_Click.reg",
"ApplyText": "Enabling the 'Last Active Click' behavior in the taskbar app area...", "ApplyText": "Enabling the 'Last Active Click' behavior in the taskbar app area...",
"UndoLabel": "Disable 'Last Active Click' behavior for taskbar apps",
"ApplyUndoText": "Disabling the 'Last Active Click' behavior in the taskbar app area...",
"RegistryUndoKey": "Disable_Last_Active_Click.reg", "RegistryUndoKey": "Disable_Last_Active_Click.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -951,6 +1082,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_Taskbar_Always.reg", "RegistryKey": "Combine_Taskbar_Always.reg",
"ApplyText": "Setting the taskbar on the main display to always combine buttons and hide labels...", "ApplyText": "Setting the taskbar on the main display to always combine buttons and hide labels...",
"UndoLabel": "Use default taskbar combine behavior",
"ApplyUndoText": "Resetting the taskbar on the main display to always combine buttons and hide labels...",
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -961,6 +1094,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_MMTaskbar_Always.reg", "RegistryKey": "Combine_MMTaskbar_Always.reg",
"ApplyText": "Setting the taskbar on secondary displays to always combine buttons and hide labels...", "ApplyText": "Setting the taskbar on secondary displays to always combine buttons and hide labels...",
"UndoLabel": "Use default taskbar combine behavior",
"ApplyUndoText": "Resetting the taskbar on secondary displays to always combine buttons and hide labels...",
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -971,6 +1106,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_Taskbar_When_Full.reg", "RegistryKey": "Combine_Taskbar_When_Full.reg",
"ApplyText": "Setting the taskbar on the main display to only combine buttons and hide labels when the taskbar is full...", "ApplyText": "Setting the taskbar on the main display to only combine buttons and hide labels when the taskbar is full...",
"UndoLabel": "Always combine taskbar buttons and hide labels for the main display",
"ApplyUndoText": "Setting the taskbar on the main display to always combine buttons and hide labels...",
"RegistryUndoKey": "Combine_Taskbar_Always.reg", "RegistryUndoKey": "Combine_Taskbar_Always.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -981,6 +1118,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_MMTaskbar_When_Full.reg", "RegistryKey": "Combine_MMTaskbar_When_Full.reg",
"ApplyText": "Setting the taskbar on secondary displays to only combine buttons and hide labels when the taskbar is full...", "ApplyText": "Setting the taskbar on secondary displays to only combine buttons and hide labels when the taskbar is full...",
"UndoLabel": "Always combine taskbar buttons and hide labels for secondary displays",
"ApplyUndoText": "Setting the taskbar on secondary displays to always combine buttons and hide labels...",
"RegistryUndoKey": "Combine_MMTaskbar_Always.reg", "RegistryUndoKey": "Combine_MMTaskbar_Always.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -991,6 +1130,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_Taskbar_Never.reg", "RegistryKey": "Combine_Taskbar_Never.reg",
"ApplyText": "Setting the taskbar on the main display to never combine buttons or hide labels...", "ApplyText": "Setting the taskbar on the main display to never combine buttons or hide labels...",
"UndoLabel": "Always combine taskbar buttons and hide labels for the main display",
"ApplyUndoText": "Setting the taskbar on the main display to always combine buttons and hide labels...",
"RegistryUndoKey": "Combine_Taskbar_Always.reg", "RegistryUndoKey": "Combine_Taskbar_Always.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1001,6 +1142,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "Combine_MMTaskbar_Never.reg", "RegistryKey": "Combine_MMTaskbar_Never.reg",
"ApplyText": "Setting the taskbar on secondary displays to never combine buttons or hide labels...", "ApplyText": "Setting the taskbar on secondary displays to never combine buttons or hide labels...",
"UndoLabel": "Always combine taskbar buttons and hide labels for secondary displays",
"ApplyUndoText": "Setting the taskbar on secondary displays to always combine buttons and hide labels...",
"RegistryUndoKey": "Combine_MMTaskbar_Always.reg", "RegistryUndoKey": "Combine_MMTaskbar_Always.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1011,6 +1154,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "MMTaskbarMode_All.reg", "RegistryKey": "MMTaskbarMode_All.reg",
"ApplyText": "Setting the taskbar to show app icons on all taskbars...", "ApplyText": "Setting the taskbar to show app icons on all taskbars...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1021,6 +1166,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "MMTaskbarMode_Main_Active.reg", "RegistryKey": "MMTaskbarMode_Main_Active.reg",
"ApplyText": "Setting the taskbar to show app icons on main taskbar and on taskbar where the windows is open...", "ApplyText": "Setting the taskbar to show app icons on main taskbar and on taskbar where the windows is open...",
"UndoLabel": "Show app icons on all taskbars",
"ApplyUndoText": "Setting the taskbar to show app icons on all taskbars...",
"RegistryUndoKey": "MMTaskbarMode_All.reg", "RegistryUndoKey": "MMTaskbarMode_All.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1031,6 +1178,8 @@
"Category": "Taskbar", "Category": "Taskbar",
"RegistryKey": "MMTaskbarMode_Active.reg", "RegistryKey": "MMTaskbarMode_Active.reg",
"ApplyText": "Setting the taskbar to only show app icons on the taskbar where the window is open...", "ApplyText": "Setting the taskbar to only show app icons on the taskbar where the window is open...",
"UndoLabel": "Show app icons on all taskbars",
"ApplyUndoText": "Setting the taskbar to show app icons on all taskbars...",
"RegistryUndoKey": "MMTaskbarMode_All.reg", "RegistryUndoKey": "MMTaskbarMode_All.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1041,6 +1190,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Launch_File_Explorer_To_Home.reg", "RegistryKey": "Launch_File_Explorer_To_Home.reg",
"ApplyText": "Changing the default location that File Explorer opens to, to 'Home'...", "ApplyText": "Changing the default location that File Explorer opens to, to 'Home'...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1051,6 +1202,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Launch_File_Explorer_To_This_PC.reg", "RegistryKey": "Launch_File_Explorer_To_This_PC.reg",
"ApplyText": "Changing the default location that File Explorer opens to, to 'This PC'...", "ApplyText": "Changing the default location that File Explorer opens to, to 'This PC'...",
"UndoLabel": "Change the default location that File Explorer opens to 'Home'",
"ApplyUndoText": "Changing the default location that File Explorer opens to, to 'Home'...",
"RegistryUndoKey": "Launch_File_Explorer_To_Home.reg", "RegistryUndoKey": "Launch_File_Explorer_To_Home.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1061,6 +1214,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Launch_File_Explorer_To_Downloads.reg", "RegistryKey": "Launch_File_Explorer_To_Downloads.reg",
"ApplyText": "Changing the default location that File Explorer opens to, to 'Downloads'...", "ApplyText": "Changing the default location that File Explorer opens to, to 'Downloads'...",
"UndoLabel": "Change the default location that File Explorer opens to 'Home'",
"ApplyUndoText": "Changing the default location that File Explorer opens to, to 'Home'...",
"RegistryUndoKey": "Launch_File_Explorer_To_Home.reg", "RegistryUndoKey": "Launch_File_Explorer_To_Home.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1071,6 +1226,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Launch_File_Explorer_To_OneDrive.reg", "RegistryKey": "Launch_File_Explorer_To_OneDrive.reg",
"ApplyText": "Changing the default location that File Explorer opens to, to 'OneDrive'...", "ApplyText": "Changing the default location that File Explorer opens to, to 'OneDrive'...",
"UndoLabel": "Change the default location that File Explorer opens to 'Home'",
"ApplyUndoText": "Changing the default location that File Explorer opens to, to 'Home'...",
"RegistryUndoKey": "Launch_File_Explorer_To_Home.reg", "RegistryUndoKey": "Launch_File_Explorer_To_Home.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1083,6 +1240,8 @@
"Priority": 2, "Priority": 2,
"RegistryKey": "Show_Extensions_For_Known_File_Types.reg", "RegistryKey": "Show_Extensions_For_Known_File_Types.reg",
"ApplyText": "Enabling file extensions for known file types...", "ApplyText": "Enabling file extensions for known file types...",
"UndoLabel": "Hide file extensions for known file types",
"ApplyUndoText": "Disabling file extensions for known file types...",
"RegistryUndoKey": "Hide_Extensions_For_Known_File_Types.reg", "RegistryUndoKey": "Hide_Extensions_For_Known_File_Types.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1095,6 +1254,8 @@
"Priority": 3, "Priority": 3,
"RegistryKey": "Show_Hidden_Folders.reg", "RegistryKey": "Show_Hidden_Folders.reg",
"ApplyText": "Unhiding hidden files, folders and drives...", "ApplyText": "Unhiding hidden files, folders and drives...",
"UndoLabel": "Hide hidden files, folders and drives",
"ApplyUndoText": "Hiding hidden files, folders and drives...",
"RegistryUndoKey": "Hide_Hidden_Folders.reg", "RegistryUndoKey": "Hide_Hidden_Folders.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1107,6 +1268,8 @@
"Priority": 4, "Priority": 4,
"RegistryKey": "Hide_duplicate_removable_drives_from_navigation_pane_of_File_Explorer.reg", "RegistryKey": "Hide_duplicate_removable_drives_from_navigation_pane_of_File_Explorer.reg",
"ApplyText": "Hiding duplicate removable drive entries from the File Explorer navigation pane...", "ApplyText": "Hiding duplicate removable drive entries from the File Explorer navigation pane...",
"UndoLabel": "Show duplicate removable drive entries",
"ApplyUndoText": "Showing duplicate removable drive entries from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_duplicate_removable_drives_in_navigation_pane_of_File_Explorer.reg", "RegistryUndoKey": "Show_duplicate_removable_drives_in_navigation_pane_of_File_Explorer.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1119,6 +1282,8 @@
"Priority": 5, "Priority": 5,
"RegistryKey": "Hide_Home_from_Explorer.reg", "RegistryKey": "Hide_Home_from_Explorer.reg",
"ApplyText": "Hiding the 'Home' section from the File Explorer navigation pane...", "ApplyText": "Hiding the 'Home' section from the File Explorer navigation pane...",
"UndoLabel": "Show 'Home' from navigation pane",
"ApplyUndoText": "Showing the 'Home' section from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_Home_in_Explorer.reg", "RegistryUndoKey": "Show_Home_in_Explorer.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1131,6 +1296,8 @@
"Priority": 6, "Priority": 6,
"RegistryKey": "Hide_Gallery_from_Explorer.reg", "RegistryKey": "Hide_Gallery_from_Explorer.reg",
"ApplyText": "Hiding the 'Gallery' section from the File Explorer navigation pane...", "ApplyText": "Hiding the 'Gallery' section from the File Explorer navigation pane...",
"UndoLabel": "Show 'Gallery' from navigation pane",
"ApplyUndoText": "Showing the 'Gallery' section from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_Gallery_in_Explorer.reg", "RegistryUndoKey": "Show_Gallery_in_Explorer.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1142,6 +1309,8 @@
"Category": "Appearance", "Category": "Appearance",
"RegistryKey": "Disable_Transparency.reg", "RegistryKey": "Disable_Transparency.reg",
"ApplyText": "Disabling transparency effects...", "ApplyText": "Disabling transparency effects...",
"UndoLabel": "Enable transparency effects",
"ApplyUndoText": "Enabling transparency effects...",
"RegistryUndoKey": "Enable_Transparency.reg", "RegistryUndoKey": "Enable_Transparency.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1153,6 +1322,8 @@
"Category": "Appearance", "Category": "Appearance",
"RegistryKey": "Disable_Animations.reg", "RegistryKey": "Disable_Animations.reg",
"ApplyText": "Disabling animations and visual effects...", "ApplyText": "Disabling animations and visual effects...",
"UndoLabel": "Enable animations and visual effects",
"ApplyUndoText": "Enabling animations and visual effects...",
"RegistryUndoKey": "Enable_Animations.reg", "RegistryUndoKey": "Enable_Animations.reg",
"RequiresReboot": true, "RequiresReboot": true,
"MinVersion": null, "MinVersion": null,
@@ -1165,6 +1336,8 @@
"Category": "Windows Update", "Category": "Windows Update",
"RegistryKey": "Disable_Update_ASAP.reg", "RegistryKey": "Disable_Update_ASAP.reg",
"ApplyText": "Preventing Windows from getting updates as soon as they are available...", "ApplyText": "Preventing Windows from getting updates as soon as they are available...",
"UndoLabel": "Allow getting updates as soon as they're available",
"ApplyUndoText": "Allowing Windows to get updates as soon as they are available...",
"RegistryUndoKey": "Enable_Update_ASAP.reg", "RegistryUndoKey": "Enable_Update_ASAP.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1176,6 +1349,8 @@
"Category": "Windows Update", "Category": "Windows Update",
"RegistryKey": "Prevent_Auto_Reboot.reg", "RegistryKey": "Prevent_Auto_Reboot.reg",
"ApplyText": "Preventing automatic restarts after updates while signed in...", "ApplyText": "Preventing automatic restarts after updates while signed in...",
"UndoLabel": "Allow automatic restarts after updates while signed in",
"ApplyUndoText": "Allowing automatic restarts after updates while signed in...",
"RegistryUndoKey": "Allow_Auto_Reboot.reg", "RegistryUndoKey": "Allow_Auto_Reboot.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1187,6 +1362,8 @@
"Category": "Windows Update", "Category": "Windows Update",
"RegistryKey": "Disable_Delivery_Optimization.reg", "RegistryKey": "Disable_Delivery_Optimization.reg",
"ApplyText": "Disabling sharing of downloaded updates with other PCs...", "ApplyText": "Disabling sharing of downloaded updates with other PCs...",
"UndoLabel": "Enable sharing downloaded updates with other PCs",
"ApplyUndoText": "Enabling sharing of downloaded updates with other PCs...",
"RegistryUndoKey": "Enable_Delivery_Optimization.reg", "RegistryUndoKey": "Enable_Delivery_Optimization.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1196,7 +1373,9 @@
"Label": "Forcefully uninstall Microsoft Edge. NOT RECOMMENDED!", "Label": "Forcefully uninstall Microsoft Edge. NOT RECOMMENDED!",
"Category": null, "Category": null,
"RegistryKey": null, "RegistryKey": null,
"ApplyText": null, "ApplyText": "Forcefully uninstalling Microsoft Edge...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1209,6 +1388,8 @@
"Priority": 7, "Priority": 7,
"RegistryKey": "Hide_Onedrive_Folder.reg", "RegistryKey": "Hide_Onedrive_Folder.reg",
"ApplyText": "Hiding the 'OneDrive' section from the File Explorer navigation pane...", "ApplyText": "Hiding the 'OneDrive' section from the File Explorer navigation pane...",
"UndoLabel": "Show 'OneDrive' from navigation pane",
"ApplyUndoText": "Showing the 'OneDrive' section from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_Onedrive_Folder.reg", "RegistryUndoKey": "Show_Onedrive_Folder.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1221,6 +1402,8 @@
"Priority": 8, "Priority": 8,
"RegistryKey": "Hide_3D_Objects_Folder.reg", "RegistryKey": "Hide_3D_Objects_Folder.reg",
"ApplyText": "Hiding the '3D objects' folder from the File Explorer navigation pane...", "ApplyText": "Hiding the '3D objects' folder from the File Explorer navigation pane...",
"UndoLabel": "Show '3D objects' folder under 'This PC'",
"ApplyUndoText": "Showing the '3D objects' folder from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_3D_Objects_Folder.reg", "RegistryUndoKey": "Show_3D_Objects_Folder.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 21999 "MaxVersion": 21999
@@ -1233,6 +1416,8 @@
"Priority": 9, "Priority": 9,
"RegistryKey": "Hide_Music_Folder.reg", "RegistryKey": "Hide_Music_Folder.reg",
"ApplyText": "Hiding the 'Music' folder from the File Explorer navigation pane...", "ApplyText": "Hiding the 'Music' folder from the File Explorer navigation pane...",
"UndoLabel": "Show 'Music' folder under 'This PC'",
"ApplyUndoText": "Showing the 'Music' folder from the File Explorer navigation pane...",
"RegistryUndoKey": "Show_Music_Folder.reg", "RegistryUndoKey": "Show_Music_Folder.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 21999 "MaxVersion": 21999
@@ -1245,6 +1430,8 @@
"Priority": 10, "Priority": 10,
"RegistryKey": "Add_All_Folders_Under_This_PC.reg", "RegistryKey": "Add_All_Folders_Under_This_PC.reg",
"ApplyText": "Adding all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...", "ApplyText": "Adding all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...",
"UndoLabel": "Remove common folders back to 'This PC' page",
"ApplyUndoText": "Removing all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...",
"RegistryUndoKey": "Remove_All_Folders_Under_This_PC.reg", "RegistryUndoKey": "Remove_All_Folders_Under_This_PC.reg",
"MinVersion": 22000, "MinVersion": 22000,
"MaxVersion": null "MaxVersion": null
@@ -1257,6 +1444,8 @@
"Priority": 11, "Priority": 11,
"RegistryKey": "Disable_Include_in_library_from_context_menu.reg", "RegistryKey": "Disable_Include_in_library_from_context_menu.reg",
"ApplyText": "Hiding 'Include in library' in the context menu...", "ApplyText": "Hiding 'Include in library' in the context menu...",
"UndoLabel": "Show 'Include in library' option in the context menu",
"ApplyUndoText": "Showing 'Include in library' in the context menu...",
"RegistryUndoKey": "Enable_Include_in_library_in_context_menu.reg", "RegistryUndoKey": "Enable_Include_in_library_in_context_menu.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 21999 "MaxVersion": 21999
@@ -1269,6 +1458,8 @@
"Priority": 12, "Priority": 12,
"RegistryKey": "Disable_Give_access_to_context_menu.reg", "RegistryKey": "Disable_Give_access_to_context_menu.reg",
"ApplyText": "Hiding 'Give access to' in the context menu...", "ApplyText": "Hiding 'Give access to' in the context menu...",
"UndoLabel": "Show 'Give access to' option in the context menu",
"ApplyUndoText": "Showing 'Give access to' in the context menu...",
"RegistryUndoKey": "Enable_Give_access_to_context_menu.reg", "RegistryUndoKey": "Enable_Give_access_to_context_menu.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 21999 "MaxVersion": 21999
@@ -1281,6 +1472,8 @@
"Priority": 13, "Priority": 13,
"RegistryKey": "Disable_Share_from_context_menu.reg", "RegistryKey": "Disable_Share_from_context_menu.reg",
"ApplyText": "Hiding 'Share' in the context menu...", "ApplyText": "Hiding 'Share' in the context menu...",
"UndoLabel": "Show 'Share' option in the context menu",
"ApplyUndoText": "Showing 'Share' in the context menu...",
"RegistryUndoKey": "Enable_Share_in_context_menu.reg", "RegistryUndoKey": "Enable_Share_in_context_menu.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": 21999 "MaxVersion": 21999
@@ -1292,6 +1485,8 @@
"Category": "Other", "Category": "Other",
"RegistryKey": "Disable_Brave_Bloat.reg", "RegistryKey": "Disable_Brave_Bloat.reg",
"ApplyText": "Disabling Brave AI, Crypto, News, Rewards, Talk and VPN in Brave browser...", "ApplyText": "Disabling Brave AI, Crypto, News, Rewards, Talk and VPN in Brave browser...",
"UndoLabel": "Enable Brave browser features (AI, Crypto, etc.)",
"ApplyUndoText": "Enabling Brave AI, Crypto, News, Rewards, Talk and VPN in Brave browser...",
"RegistryUndoKey": "Enable_Brave_Bloat.reg", "RegistryUndoKey": "Enable_Brave_Bloat.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1303,6 +1498,8 @@
"Category": "Optional Windows Features", "Category": "Optional Windows Features",
"RegistryKey": null, "RegistryKey": null,
"ApplyText": "Enabling Windows Sandbox...", "ApplyText": "Enabling Windows Sandbox...",
"UndoLabel": "Disable Windows Sandbox",
"ApplyUndoText": "Disabling Windows Sandbox...",
"RegistryUndoKey": null, "RegistryUndoKey": null,
"RequiresReboot": true, "RequiresReboot": true,
"MinVersion": 22483, "MinVersion": 22483,
@@ -1315,6 +1512,8 @@
"Category": "Optional Windows Features", "Category": "Optional Windows Features",
"RegistryKey": null, "RegistryKey": null,
"ApplyText": "Enabling Windows Subsystem for Linux...", "ApplyText": "Enabling Windows Subsystem for Linux...",
"UndoLabel": "Disable Windows Subsystem for Linux",
"ApplyUndoText": "Disabling Windows Subsystem for Linux...",
"RegistryUndoKey": null, "RegistryUndoKey": null,
"RequiresReboot": true, "RequiresReboot": true,
"MinVersion": 22000, "MinVersion": 22000,
@@ -1327,6 +1526,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Show_Drive_Letters_First.reg", "RegistryKey": "Show_Drive_Letters_First.reg",
"ApplyText": "Showing drive letters before drive label...", "ApplyText": "Showing drive letters before drive label...",
"UndoLabel": "Show drive letters after drive label",
"ApplyUndoText": "Showing drive letters after drive label...",
"RegistryUndoKey": "Show_Drive_Letters_Last.reg", "RegistryUndoKey": "Show_Drive_Letters_Last.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1338,6 +1539,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Show_Drive_Letters_Last.reg", "RegistryKey": "Show_Drive_Letters_Last.reg",
"ApplyText": "Showing drive letters after drive label...", "ApplyText": "Showing drive letters after drive label...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null, "RegistryUndoKey": null,
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1349,6 +1552,8 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Show_Network_Drive_Letters_First.reg", "RegistryKey": "Show_Network_Drive_Letters_First.reg",
"ApplyText": "Showing network drive letters before drive label...", "ApplyText": "Showing network drive letters before drive label...",
"UndoLabel": "Show drive letters after drive label",
"ApplyUndoText": "Showing drive letters after drive label...",
"RegistryUndoKey": "Show_Drive_Letters_Last.reg", "RegistryUndoKey": "Show_Drive_Letters_Last.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
@@ -1360,9 +1565,50 @@
"Category": "File Explorer", "Category": "File Explorer",
"RegistryKey": "Hide_Drive_Letters.reg", "RegistryKey": "Hide_Drive_Letters.reg",
"ApplyText": "Hiding all drive letters...", "ApplyText": "Hiding all drive letters...",
"UndoLabel": "Show drive letters after drive label",
"ApplyUndoText": "Showing drive letters after drive label...",
"RegistryUndoKey": "Show_Drive_Letters_Last.reg", "RegistryUndoKey": "Show_Drive_Letters_Last.reg",
"MinVersion": null, "MinVersion": null,
"MaxVersion": null "MaxVersion": null
},
{
"FeatureId": "StartAllAppsCategory",
"Label": "Show All Apps in Category view (Default)",
"ToolTip": "This will set the All Apps section in the start menu to show apps grouped by category.",
"Category": "Start Menu & Search",
"RegistryKey": "Start_AllApps_Category.reg",
"ApplyText": "Setting All Apps view to Category...",
"UndoLabel": null,
"ApplyUndoText": null,
"RegistryUndoKey": null,
"MinVersion": 26200,
"MaxVersion": null
},
{
"FeatureId": "StartAllAppsGrid",
"Label": "Show All Apps in Grid view",
"ToolTip": "This will set the All Apps section in the start menu to show apps in an alphabetical grid layout.",
"Category": "Start Menu & Search",
"RegistryKey": "Start_AllApps_Grid.reg",
"ApplyText": "Setting All Apps view to Grid...",
"UndoLabel": "Show All Apps in Category view",
"ApplyUndoText": "Setting All Apps view to Category...",
"RegistryUndoKey": "Start_AllApps_Category.reg",
"MinVersion": 26200,
"MaxVersion": null
},
{
"FeatureId": "StartAllAppsList",
"Label": "Show All Apps in List view",
"ToolTip": "This will set the All Apps section in the start menu to show apps in an alphabetical list layout.",
"Category": "Start Menu & Search",
"RegistryKey": "Start_AllApps_List.reg",
"ApplyText": "Setting All Apps view to List...",
"UndoLabel": "Show All Apps in Category view",
"ApplyUndoText": "Setting All Apps view to Category...",
"RegistryUndoKey": "Start_AllApps_Category.reg",
"MinVersion": 26200,
"MaxVersion": null
} }
] ]
} }

View File

@@ -4,9 +4,9 @@
[![Join the Discussion](https://img.shields.io/badge/Join-the%20Discussion-2D9F2D?style=for-the-badge&logo=github&logoColor=white)](https://github.com/Raphire/Win11Debloat/discussions) [![Join the Discussion](https://img.shields.io/badge/Join-the%20Discussion-2D9F2D?style=for-the-badge&logo=github&logoColor=white)](https://github.com/Raphire/Win11Debloat/discussions)
[![Static Badge](https://img.shields.io/badge/Documentation-_?style=for-the-badge&logo=bookstack&color=grey)](https://github.com/Raphire/Win11Debloat/wiki/) [![Static Badge](https://img.shields.io/badge/Documentation-_?style=for-the-badge&logo=bookstack&color=grey)](https://github.com/Raphire/Win11Debloat/wiki/)
Win11Debloat is a lightweight, easy to use PowerShell script that allows you to quickly declutter and customize your Windows experience. It can remove pre-installed bloatware 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 option 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. 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)

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000000
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000001
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000002
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000000
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000001
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

View File

@@ -0,0 +1,7 @@
Windows Registry Editor Version 5.00
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Start]
"AllAppsViewMode"=dword:00000002
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]
"NoStartMenuMorePrograms"=-

Binary file not shown.

View File

@@ -19,15 +19,17 @@ if exist "%wtDefaultPath%" (
set "wtPath=" set "wtPath="
) )
set "SCRIPT_PATH=\"%~dp0Win11Debloat.ps1\""
:: Launch script :: Launch script
if defined wtPath ( if defined wtPath (
call :Log Launching Win11Debloat.ps1 with Windows Terminal... call :Log Launching Win11Debloat.ps1 with Windows Terminal...
PowerShell -Command "Start-Process -FilePath '%wtPath%' -ArgumentList 'PowerShell -NoProfile -ExecutionPolicy Bypass -File ""%~dp0Win11Debloat.ps1""' -Verb RunAs" >> "%logFile%" || call :Error "PowerShell command failed" PowerShell -Command "Start-Process -FilePath '%wtPath%' -ArgumentList 'PowerShell -NoProfile -ExecutionPolicy Bypass -File %SCRIPT_PATH%' -Verb RunAs" >> "%logFile%" || call :Error "PowerShell command failed"
call :Log Script execution passed successfully to Win11Debloat.ps1 call :Log Script execution passed successfully to Win11Debloat.ps1
) else ( ) else (
echo Windows Terminal not found. Using default PowerShell instead... echo Windows Terminal not found. Using default PowerShell instead...
call :Log Windows Terminal not found. Using default PowerShell to launch Win11Debloat.ps1... call :Log Windows Terminal not found. Using default PowerShell to launch Win11Debloat.ps1...
PowerShell -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%~dp0Win11Debloat.ps1""' -Verb RunAs}" >> "%logFile%" || call :Error "PowerShell command failed" PowerShell -ExecutionPolicy Bypass -Command "& {Start-Process PowerShell -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File %SCRIPT_PATH%' -Verb RunAs}" >> "%logFile%" || call :Error "PowerShell command failed"
call :Log Script execution passed successfully to Win11Debloat.ps1 call :Log Script execution passed successfully to Win11Debloat.ps1
) )

View File

@@ -58,7 +58,7 @@
<Style x:Key="CategoryHeaderTextBlock" TargetType="TextBlock"> <Style x:Key="CategoryHeaderTextBlock" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/> <Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="16"/> <Setter Property="FontSize" Value="16"/>
<Setter Property="Margin" Value="0,0,0,13"/> <Setter Property="Margin" Value="0,0,0,12"/>
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/> <Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
</Style> </Style>
@@ -485,48 +485,81 @@
<!-- Home Tab --> <!-- Home Tab -->
<TabItem Header="Home" x:Name="HomeTab"> <TabItem Header="Home" x:Name="HomeTab">
<Grid> <Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Width="600"> <StackPanel x:Name="HomeContentPanel" HorizontalAlignment="Center" VerticalAlignment="Top">
<!-- Logo --> <!-- Logo -->
<Viewbox Width="220" Height="220" Margin="0,32,0,10" HorizontalAlignment="Center"> <Viewbox Width="250" Height="250" Margin="0,0,0,10" HorizontalAlignment="Center">
<Grid Width="220" Height="220"> <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"
Fill="{DynamicResource ButtonBg}" Stretch="Uniform" Margin="10"/> Fill="{DynamicResource ButtonBg}" Stretch="Uniform" Margin="10"/>
<!-- Sparkle effects --> <!-- Sparkle effects -->
<Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="45" Height="45" Margin="0,0,2,2"> <Canvas HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="50" Height="50" Margin="0,0,2,2">
<Path Canvas.Left="10" Canvas.Top="16" Data="M12,0 L14,10 L24,12 L14,14 L12,24 L10,14 L0,12 L10,10 Z" <Path Canvas.Left="10" Canvas.Top="16" Data="M12,0 L14,10 L24,12 L14,14 L12,24 L10,14 L0,12 L10,10 Z"
Fill="{DynamicResource AccentColor}" Width="38" Height="38" Stretch="Uniform"/> Fill="{DynamicResource AccentColor}" Width="40" Height="40" Stretch="Uniform"/>
<Path Canvas.Left="0" Canvas.Top="0" Data="M6,0 L7,5 L12,6 L7,7 L6,12 L5,7 L0,6 L5,5 Z" <Path Canvas.Left="0" Canvas.Top="0" Data="M6,0 L7,5 L12,6 L7,7 L6,12 L5,7 L0,6 L5,5 Z"
Fill="{DynamicResource AccentColor}" Width="20" Height="20" Stretch="Uniform"/> Fill="{DynamicResource AccentColor}" Width="22" Height="22" Stretch="Uniform"/>
<Path Canvas.Left="35" Canvas.Top="8" Data="M4,0 L5,3 L8,4 L5,5 L4,8 L3,5 L0,4 L3,3 Z" <Path Canvas.Left="35" Canvas.Top="8" Data="M4,0 L5,3 L8,4 L5,5 L4,8 L3,5 L0,4 L3,3 Z"
Fill="{DynamicResource AccentColor}" Width="15" Height="15" Stretch="Uniform"/> Fill="{DynamicResource AccentColor}" Width="17" Height="17" Stretch="Uniform"/>
</Canvas> </Canvas>
<!-- Actual logo image if available -->
<Image x:Name="LogoImage" Stretch="Uniform"/>
</Grid> </Grid>
</Viewbox> </Viewbox>
<!-- Title --> <!-- Title -->
<TextBlock Text="Welcome to Win11Debloat" FontSize="28" FontWeight="Bold" Foreground="{DynamicResource FgColor}" HorizontalAlignment="Center" Margin="0,0,0,10"/> <TextBlock Text="Welcome to Win11Debloat" FontSize="30" FontWeight="Bold" Foreground="{DynamicResource FgColor}" HorizontalAlignment="Center" Margin="0,0,0,10"/>
<TextBlock TextWrapping="Wrap" Foreground="{DynamicResource FgColor}" FontSize="16" LineHeight="22" HorizontalAlignment="Center" Margin="0,0,0,30"> <TextBlock TextWrapping="Wrap" Foreground="{DynamicResource FgColor}" FontSize="18" LineHeight="22" HorizontalAlignment="Center" Margin="0,0,0,60">
<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>
<!-- Action Buttons --> <Border HorizontalAlignment="Center" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Width="500">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,0"> <StackPanel>
<Button x:Name="HomeDefaultModeBtn" Width="180" Height="50" Style="{DynamicResource PrimaryButtonStyle}" Margin="0,0,12,0" AutomationProperties.Name="Default Mode"> <TextBlock Text="What user do you want to apply changes to?" Style="{StaticResource CategoryHeaderTextBlock}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <ComboBox x:Name="UserSelectionCombo" Margin="0,0,0,6" AutomationProperties.Name="Apply Changes To">
<TextBlock Text="&#xe735;" FontFamily="Segoe Fluent Icons" FontSize="16" VerticalAlignment="Center" Margin="0,0,8,0"/> <ComboBoxItem Content="Current User" IsSelected="True"/>
<TextBlock Text="Default Mode" ToolTip="Quickly select the recommended settings" FontWeight="SemiBold" VerticalAlignment="Center" FontSize="17" Margin="0,0,0,2"/> <ComboBoxItem Content="Other User"/>
<ComboBoxItem Content="Windows Default User (Sysprep)"/>
</ComboBox>
<StackPanel x:Name="OtherUserPanel" Visibility="Collapsed" Margin="0,0,0,6">
<TextBlock x:Name="UsernameValidationMessage" Text="" FontStyle="Italic" Foreground="{DynamicResource CloseHover}" FontSize="11" Margin="3,0,0,4" TextWrapping="Wrap"/>
<Border IsEnabled="{Binding ElementName=OtherUsernameTextBox, Path=IsEnabled}">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource UserTextBoxBorderStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=OtherUsernameTextBox, Path=IsFocused}" Value="True">
<Setter Property="Background" Value="{DynamicResource InputFocusColor}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="&#xE77B;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="4,0,8,0" Foreground="{DynamicResource FgColor}"/>
<TextBlock x:Name="UsernameTextBoxPlaceholder" Grid.Column="1" Text="Enter username" Foreground="{DynamicResource FgColor}" Opacity="0.5" FontSize="13" Margin="3,0,0,1" VerticalAlignment="Center" IsHitTestVisible="False"/>
<TextBox x:Name="OtherUsernameTextBox" Grid.Column="1" Style="{StaticResource UserTextBoxStyle}" Text="" AutomationProperties.Name="Enter username"/>
</Grid>
</Border>
</StackPanel> </StackPanel>
</Button>
<Button x:Name="HomeStartBtn" Width="180" Height="50" Style="{DynamicResource SecondaryButtonStyle}" AutomationProperties.Name="Custom Setup"> <Separator Margin="0,10,0,8" Background="{DynamicResource BorderColor}"/>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xe90f;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,0"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,8,0,0">
<TextBlock Text="Custom Setup" ToolTip="Manually select your preferred settings" FontWeight="SemiBold" VerticalAlignment="Center" FontSize="17" Margin="0,0,0,2"/> <Button x:Name="HomeDefaultModeBtn" Width="227" Height="50" Style="{DynamicResource PrimaryButtonStyle}" Margin="0,0,12,0" AutomationProperties.Name="Default Mode">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xe735;" FontFamily="Segoe Fluent Icons" FontSize="16" VerticalAlignment="Center" Margin="0,0,8,-1"/>
<TextBlock Text="Default Mode" ToolTip="Quickly select the recommended settings" FontWeight="SemiBold" VerticalAlignment="Center" FontSize="17" Margin="0,0,0,1"/>
</StackPanel>
</Button>
<Button x:Name="HomeStartBtn" Width="227" Height="50" Style="{DynamicResource SecondaryButtonStyle}" AutomationProperties.Name="Custom Setup">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xe90f;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,-1"/>
<TextBlock Text="Custom Setup" ToolTip="Manually select your preferred settings" FontWeight="SemiBold" VerticalAlignment="Center" FontSize="17" Margin="0,0,0,1"/>
</StackPanel>
</Button>
</StackPanel> </StackPanel>
</Button> </StackPanel>
</StackPanel> </Border>
</StackPanel> </StackPanel>
</Grid> </Grid>
</TabItem> </TabItem>
@@ -732,6 +765,8 @@
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal"> <StackPanel Grid.Column="0" Orientation="Horizontal">
@@ -798,7 +833,9 @@
</Popup> </Popup>
</StackPanel> </StackPanel>
<Border x:Name="TweakSearchBorder" Grid.Column="2"> <CheckBox x:Name="ShowCurrentlyAppliedTweaksCheckBox" Grid.Column="2" Content="Detect applied tweaks" IsChecked="True" Foreground="{DynamicResource FgColor}" VerticalAlignment="Center" AutomationProperties.Name="Detect applied tweaks" ToolTip="Detect all tweaks currently applied for the current user."/>
<Border x:Name="TweakSearchBorder" Grid.Column="4">
<Border.Style> <Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource SearchBoxBorderStyle}"> <Style TargetType="Border" BasedOn="{StaticResource SearchBoxBorderStyle}">
<Style.Triggers> <Style.Triggers>
@@ -891,43 +928,15 @@
<!-- Apply Changes To --> <!-- Apply Changes To -->
<Border Grid.Row="0" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Margin="0,0,0,16"> <Border Grid.Row="0" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Margin="0,0,0,16">
<StackPanel> <StackPanel>
<TextBlock Text="Apply Changes To" Style="{StaticResource CategoryHeaderTextBlock}"/> <TextBlock Text="Changes will be applied to" Style="{StaticResource CategoryHeaderTextBlock}"/>
<ComboBox x:Name="UserSelectionCombo" Margin="0,0,0,6" AutomationProperties.Name="Apply Changes To"> <TextBlock x:Name="UserSelectionDescription" Text="The currently logged-in user profile." Foreground="{DynamicResource FgColor}" FontSize="12" TextWrapping="Wrap" Margin="0,0,0,3"/>
<ComboBoxItem Content="Current User" IsSelected="True"/>
<ComboBoxItem Content="Other User"/>
<ComboBoxItem Content="Windows Default User (Sysprep)"/>
</ComboBox>
<StackPanel x:Name="OtherUserPanel" Visibility="Collapsed" Margin="0,0,0,6">
<TextBlock x:Name="UsernameValidationMessage" Text="" FontStyle="Italic" Foreground="{DynamicResource CloseHover}" FontSize="11" Margin="3,0,0,4" TextWrapping="Wrap"/>
<Border IsEnabled="{Binding ElementName=OtherUsernameTextBox, Path=IsEnabled}">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource UserTextBoxBorderStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=OtherUsernameTextBox, Path=IsFocused}" Value="True">
<Setter Property="Background" Value="{DynamicResource InputFocusColor}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="&#xE77B;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="4,0,8,0" Foreground="{DynamicResource FgColor}"/>
<TextBlock x:Name="UsernameTextBoxPlaceholder" Grid.Column="1" Text="Enter username" Foreground="{DynamicResource FgColor}" Opacity="0.5" FontSize="13" Margin="3,0,0,1" VerticalAlignment="Center" IsHitTestVisible="False"/>
<TextBox x:Name="OtherUsernameTextBox" Grid.Column="1" Style="{StaticResource UserTextBoxStyle}" Text="" AutomationProperties.Name="Enter username"/>
</Grid>
</Border>
</StackPanel>
<TextBlock x:Name="UserSelectionDescription" Text="Changes will be applied to the currently logged-in user profile." Foreground="{DynamicResource FgColor}" FontSize="12" TextWrapping="Wrap" Margin="0,6,0,3"/>
</StackPanel> </StackPanel>
</Border> </Border>
<!-- App Removal Scope --> <!-- App Removal Scope -->
<Border Grid.Row="1" x:Name="AppRemovalScopeSection" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Margin="0,0,0,16"> <Border Grid.Row="1" x:Name="AppRemovalScopeSection" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Margin="0,0,0,16">
<StackPanel> <StackPanel>
<TextBlock Text="Remove Apps For" Style="{StaticResource CategoryHeaderTextBlock}"/> <TextBlock Text="Apps will be removed for" Style="{StaticResource CategoryHeaderTextBlock}"/>
<ComboBox x:Name="AppRemovalScopeCombo" Margin="0,0,0,6" AutomationProperties.Name="App Removal Scope"> <ComboBox x:Name="AppRemovalScopeCombo" Margin="0,0,0,6" AutomationProperties.Name="App Removal Scope">
<ComboBoxItem x:Name="AppRemovalScopeAllUsers" Content="All users" IsSelected="True"/> <ComboBoxItem x:Name="AppRemovalScopeAllUsers" Content="All users" IsSelected="True"/>
<ComboBoxItem x:Name="AppRemovalScopeCurrentUser" Content="Current user only"/> <ComboBoxItem x:Name="AppRemovalScopeCurrentUser" Content="Current user only"/>

View File

@@ -222,6 +222,8 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,16"> <Grid Grid.Row="0" Margin="0,0,0,16">
@@ -282,9 +284,38 @@
Visibility="Collapsed" Visibility="Collapsed"
Text="This will restore the Start Menu pinned apps layout for the current user."/> Text="This will restore the Start Menu pinned apps layout for the current user."/>
<Border x:Name="NonRevertibleSeparator" Grid.Row="3" Height="1" Background="{DynamicResource BorderColor}" Margin="0,12,0,12" Visibility="Collapsed"/> <Border x:Name="ReappliedSeparator" Grid.Row="3" Height="1" Background="{DynamicResource BorderColor}" Margin="0,12,0,12" Visibility="Collapsed"/>
<Grid x:Name="NonRevertiblePanel" Grid.Row="4" Visibility="Collapsed"> <Grid x:Name="ReappliedPanel" Grid.Row="4" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="The following changes will be re-applied:"
FontSize="13"
Foreground="{DynamicResource FgColor}"
FontWeight="SemiBold"
Margin="0,0,0,8"/>
<ItemsControl x:Name="ReappliedFeaturesItemsControl" Grid.Row="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayText}" FontSize="12" Foreground="{DynamicResource FgColor}" TextWrapping="Wrap" Margin="0,0,0,6"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<Border x:Name="NonRevertibleSeparator" Grid.Row="5" Height="1" Background="{DynamicResource BorderColor}" Margin="0,12,0,12" Visibility="Collapsed"/>
<Grid x:Name="NonRevertiblePanel" Grid.Row="6" Visibility="Collapsed">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
@@ -343,7 +374,7 @@
LineHeight="20" LineHeight="20"
Margin="0,0,0,12" Margin="0,0,0,12"
Foreground="{DynamicResource FgColor}" Foreground="{DynamicResource FgColor}"
Text="This will restore the Start Menu pinned apps layout for the selected user(s) using a backup. Win11Debloat can automatically find the backup created by the script."/> Text="This will restore the Start Menu pinned apps layout for the selected user(s) using a backup that is automatically created by Win11Debloat. Manually created backups can also be used."/>
<ComboBox x:Name="StartMenuScopeCombo" <ComboBox x:Name="StartMenuScopeCombo"
Grid.Row="1" Grid.Row="1"

View File

@@ -124,10 +124,7 @@ function RemoveApps {
} }
} }
catch { catch {
if ($DebugPreference -ne "SilentlyContinue") { Write-Verbose "Something went wrong while trying to remove $($app): $_"
Write-Host "Something went wrong while trying to remove $app" -ForegroundColor Yellow
Write-Host $psitem.Exception.StackTrace -ForegroundColor Gray
}
} }
} }

View File

@@ -14,8 +14,7 @@ function Get-FeatureId {
function Get-RegistryBackedFeatures { function Get-RegistryBackedFeatures {
param( param(
[Parameter(Mandatory)] [object[]]$Features = @()
[object[]]$Features
) )
return @($Features | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.RegistryKey) }) return @($Features | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.RegistryKey) })

View File

@@ -1,47 +1,44 @@
function Get-RegistryBackupCapturePlans { function Get-RegistryBackupCapturePlans {
param( param(
[Parameter(Mandatory)] [object[]]$SelectedRegistryFeatures = @(),
[object[]]$SelectedRegistryFeatures, [object[]]$UndoRegistryFeatures = @(),
[switch]$UseSysprepRegFiles [switch]$UseSysprepRegFiles
) )
$planMap = @{} $planMap = @{}
foreach ($feature in $SelectedRegistryFeatures) { foreach ($feature in $SelectedRegistryFeatures) {
$regFilePath = Get-RegistryFilePathForFeature -Feature $feature -UseSysprepRegFiles:$UseSysprepRegFiles $regFilePath = Get-RegistryFilePathForFeature -RegistryKey $feature.RegistryKey -UseSysprepRegFiles:$UseSysprepRegFiles
if (-not (Test-Path $regFilePath)) { if (-not (Test-Path $regFilePath)) {
throw "Unable to find registry file for backup: $($feature.RegistryKey) ($regFilePath)" throw "Unable to find registry file for backup: $($feature.RegistryKey) ($regFilePath)"
} }
foreach ($operation in @(Get-RegFileOperations -regFilePath $regFilePath)) { foreach ($operation in @(Get-RegFileOperations -regFilePath $regFilePath)) {
if (-not $operation.KeyPath) { continue } if (-not $operation.KeyPath) { continue }
Add-RegistryPlanOperation -PlanMap $planMap -Operation $operation
}
}
$mapKey = $operation.KeyPath.ToLowerInvariant() foreach ($feature in $UndoRegistryFeatures) {
if (-not $planMap.ContainsKey($mapKey)) { $regFilePath = Resolve-RegistryBackupUndoFilePath -Feature $feature
$planMap[$mapKey] = [PSCustomObject]@{ if ([string]::IsNullOrWhiteSpace($regFilePath)) {
Path = $operation.KeyPath continue
IncludeSubKeys = $false }
CaptureAllValues = $false
ValueNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) if (-not (Test-Path $regFilePath)) {
} $undoKeyDescription = if (-not [string]::IsNullOrWhiteSpace([string]$feature.RegistryUndoKey)) {
[string]$feature.RegistryUndoKey
}
else {
[string]$feature.RegistryKey
} }
$plan = $planMap[$mapKey] throw "Unable to find registry undo file for backup: $undoKeyDescription ($regFilePath)"
switch ($operation.OperationType) { }
'DeleteKey' {
$plan.IncludeSubKeys = $true foreach ($operation in @(Get-RegFileOperations -regFilePath $regFilePath)) {
$plan.CaptureAllValues = $true if (-not $operation.KeyPath) { continue }
} Add-RegistryPlanOperation -PlanMap $planMap -Operation $operation
'SetValue' {
if (-not $plan.CaptureAllValues) {
$null = $plan.ValueNames.Add([string]$operation.ValueName)
}
}
'DeleteValue' {
if (-not $plan.CaptureAllValues) {
$null = $plan.ValueNames.Add([string]$operation.ValueName)
}
}
}
} }
} }
@@ -57,10 +54,68 @@ function Get-RegistryBackupCapturePlans {
) )
} }
function Get-RegistrySnapshotsForBackup { function Add-RegistryPlanOperation {
param(
[hashtable]$PlanMap,
[PSCustomObject]$Operation
)
$mapKey = $Operation.KeyPath.ToLowerInvariant()
if (-not $PlanMap.ContainsKey($mapKey)) {
$PlanMap[$mapKey] = [PSCustomObject]@{
Path = $Operation.KeyPath
IncludeSubKeys = $false
CaptureAllValues = $false
ValueNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
}
}
$plan = $PlanMap[$mapKey]
switch ($Operation.OperationType) {
'DeleteKey' {
$plan.IncludeSubKeys = $true
$plan.CaptureAllValues = $true
}
'SetValue' {
if (-not $plan.CaptureAllValues) {
$null = $plan.ValueNames.Add([string]$Operation.ValueName)
}
}
'DeleteValue' {
if (-not $plan.CaptureAllValues) {
$null = $plan.ValueNames.Add([string]$Operation.ValueName)
}
}
}
}
function Resolve-RegistryBackupUndoFilePath {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[object[]]$CapturePlans $Feature
)
$undoRegistryKey = [string]$Feature.RegistryUndoKey
if (-not [string]::IsNullOrWhiteSpace($undoRegistryKey)) {
$resolvedUndoPath = Resolve-UndoRegFilePath -FileName $undoRegistryKey
return Join-Path $script:RegfilesPath $resolvedUndoPath
}
$resolvedRegistryKey = [string]$Feature.RegistryKey
if ([string]::IsNullOrWhiteSpace($resolvedRegistryKey)) {
return $null
}
if ([System.IO.Path]::IsPathRooted($resolvedRegistryKey)) {
return $resolvedRegistryKey
}
return Join-Path $script:RegfilesPath $resolvedRegistryKey
}
function Get-RegistrySnapshotsForBackup {
param(
[object[]]$CapturePlans = @()
) )
if ($CapturePlans.Count -eq 0) { if ($CapturePlans.Count -eq 0) {
@@ -92,31 +147,14 @@ function Invoke-WithLoadedBackupHive {
$ArgumentObject = $null $ArgumentObject = $null
) )
$hiveDatPath = if ($script:Params.ContainsKey('Sysprep')) { $targetUserName = if ($script:Params.ContainsKey('Sysprep')) {
GetUserDirectory -userName 'Default' -fileName 'NTUSER.DAT' 'Default'
} }
else { else {
GetUserDirectory -userName $script:Params.Item('User') -fileName 'NTUSER.DAT' $script:Params.Item('User')
} }
$global:LASTEXITCODE = 0 return Invoke-WithTargetUserHive -TargetUserName $targetUserName -ScriptBlock $ScriptBlock -ArgumentObject $ArgumentObject
reg load 'HKU\Default' "$hiveDatPath" | Out-Null
$loadExitCode = $LASTEXITCODE
if ($loadExitCode -ne 0) {
throw "Failed to load user hive for registry backup at '$hiveDatPath' (exit code: $loadExitCode)"
}
try {
return & $ScriptBlock $ArgumentObject
}
finally {
$global:LASTEXITCODE = 0
reg unload 'HKU\Default' | Out-Null
$unloadExitCode = $LASTEXITCODE
if ($unloadExitCode -ne 0) {
throw "Failed to unload registry hive 'HKU\Default' (exit code: $unloadExitCode)"
}
}
} }
function Get-RegistryKeySnapshot { function Get-RegistryKeySnapshot {

View File

@@ -1,11 +1,14 @@
function New-RegistrySettingsBackup { function New-RegistrySettingsBackup {
param( param(
[string[]]$ActionableKeys [string[]]$ActionableKeys,
[object[]]$ExtraFeatures = @()
) )
$ActionableKeys = @($ActionableKeys) $ActionableKeys = @($ActionableKeys)
$selectedFeatures = Get-SelectedFeatures -ActionableKeys $ActionableKeys $selectedFeatures = @(Get-SelectedFeatures -ActionableKeys $ActionableKeys)
if (@($selectedFeatures | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.RegistryKey) }).Count -eq 0) { $undoFeatures = @($ExtraFeatures | Where-Object { $_ -ne $null })
$allFeatures = @($selectedFeatures) + @($undoFeatures)
if (@($allFeatures | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.RegistryKey) }).Count -eq 0) {
return $null return $null
} }
@@ -18,7 +21,7 @@ function New-RegistrySettingsBackup {
$backupFileName = 'Win11Debloat-RegistryBackup-{0}.json' -f $timestamp.ToString('yyyyMMdd_HHmmss') $backupFileName = 'Win11Debloat-RegistryBackup-{0}.json' -f $timestamp.ToString('yyyyMMdd_HHmmss')
$backupFilePath = Join-Path $backupDirectory $backupFileName $backupFilePath = Join-Path $backupDirectory $backupFileName
$backupConfig = Get-RegistryBackupPayload -SelectedFeatures $selectedFeatures -CreatedAt $timestamp $backupConfig = Get-RegistryBackupPayload -SelectedFeatures $selectedFeatures -UndoFeatures $undoFeatures -CreatedAt $timestamp
if (-not (SaveToFile -Config $backupConfig -FilePath $backupFilePath -MaxDepth 25)) { if (-not (SaveToFile -Config $backupConfig -FilePath $backupFilePath -MaxDepth 25)) {
throw "Failed to save registry backup to '$backupFilePath'" throw "Failed to save registry backup to '$backupFilePath'"
} }
@@ -55,8 +58,8 @@ function Get-SelectedFeatures {
function Get-RegistryBackupPayload { function Get-RegistryBackupPayload {
param( param(
[Parameter(Mandatory)] [object[]]$SelectedFeatures = @(),
[object[]]$SelectedFeatures, [object[]]$UndoFeatures = @(),
[Parameter(Mandatory)] [Parameter(Mandatory)]
[datetime]$CreatedAt [datetime]$CreatedAt
) )
@@ -71,11 +74,24 @@ function Get-RegistryBackupPayload {
} }
} }
$selectedRegistryFeatures = Get-RegistryBackedFeatures -Features $SelectedFeatures $selectedUndoFeatureIds = New-Object System.Collections.Generic.List[string]
$capturePlans = Get-RegistryBackupCapturePlans -SelectedRegistryFeatures $SelectedRegistryFeatures $seenUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($feature in $UndoFeatures) {
$featureId = Get-FeatureId -Feature $feature
if ($seenUndoFeatureIds.Add($featureId)) {
$selectedUndoFeatureIds.Add($featureId)
}
}
$selectedRegistryFeatures = @(Get-RegistryBackedFeatures -Features $SelectedFeatures)
$undoRegistryFeatures = @($UndoFeatures | Where-Object {
-not [string]::IsNullOrWhiteSpace([string]$_.RegistryUndoKey) -or -not [string]::IsNullOrWhiteSpace([string]$_.RegistryKey)
})
$capturePlans = @(Get-RegistryBackupCapturePlans -SelectedRegistryFeatures $selectedRegistryFeatures -UndoRegistryFeatures $undoRegistryFeatures)
$registryKeys = @(Get-RegistrySnapshotsForBackup -CapturePlans $capturePlans) $registryKeys = @(Get-RegistrySnapshotsForBackup -CapturePlans $capturePlans)
return @{ $backupPayload = @{
Version = '1.0' Version = '1.0'
BackupType = 'RegistryState' BackupType = 'RegistryState'
CreatedAt = $CreatedAt.ToString('o') CreatedAt = $CreatedAt.ToString('o')
@@ -85,4 +101,10 @@ function Get-RegistryBackupPayload {
SelectedFeatures = @($selectedFeatureIds) SelectedFeatures = @($selectedFeatureIds)
RegistryKeys = @($registryKeys) RegistryKeys = @($registryKeys)
} }
if ($selectedUndoFeatureIds.Count -gt 0) {
$backupPayload['SelectedUndoFeatures'] = @($selectedUndoFeatureIds)
}
return $backupPayload
} }

View File

@@ -49,4 +49,137 @@ function DisableStoreSearchSuggestions {
Set-Acl -Path $StoreAppsDatabase -AclObject $Acl | Out-Null Set-Acl -Path $StoreAppsDatabase -AclObject $Acl | Out-Null
Write-Host "Disabled Microsoft Store search suggestions for user $userName" Write-Host "Disabled Microsoft Store search suggestions for user $userName"
}
function EnableStoreSearchSuggestionsForAllUsers {
# Get path to Store app database for all users
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages"
$usersStoreDbPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
# Go through all users and re-enable start search suggestions
ForEach ($storeDbPath in $usersStoreDbPaths) {
EnableStoreSearchSuggestions ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
}
# Also re-enable for the default user profile
$defaultStoreDbPath = GetUserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false
EnableStoreSearchSuggestions $defaultStoreDbPath
}
function EnableStoreSearchSuggestions {
param (
$StoreAppsDatabase = "$env:LocalAppData\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db"
)
# Change path to correct user if a user was specified
if ($script:Params.ContainsKey("User")) {
$StoreAppsDatabase = GetUserDirectory -userName "$(GetUserName)" -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false
}
$userName = [regex]::Match($StoreAppsDatabase, '(?:Users\\)([^\\]+)(?:\\AppData)').Groups[1].Value
if (-not $userName) { $userName = '<unknown>' }
if (-not (Test-Path -Path $StoreAppsDatabase)) {
Write-Host "Store app database not found for user $userName, nothing to undo"
return
}
# Ensure we can modify/delete the file even if restrictive ACLs were set.
$global:LASTEXITCODE = 0
takeown /F "$StoreAppsDatabase" /A | Out-Null
icacls "$StoreAppsDatabase" /grant *S-1-5-32-544:F /C | Out-Null
$everyoneSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-1-0') # 'EVERYONE' group
try {
$acl = Get-Acl -Path $StoreAppsDatabase
$denyRules = @(
$acl.Access | Where-Object {
$_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny -and
(($_.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -ne 0) -and
(try { $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $everyoneSid } catch { $false })
}
)
foreach ($denyRule in $denyRules) {
$null = $acl.RemoveAccessRuleSpecific($denyRule)
}
Set-Acl -Path $StoreAppsDatabase -AclObject $acl | Out-Null
}
catch {
Write-Warning "Failed to normalize ACL for store database '$StoreAppsDatabase': $($_.Exception.Message)"
}
try {
Remove-Item -Path $StoreAppsDatabase -Force -ErrorAction Stop
Write-Host "Re-enabled Microsoft Store search suggestions for user $userName"
}
catch {
throw "Failed to remove '$StoreAppsDatabase' while undoing Microsoft Store search suggestions for user $userName. $($_.Exception.Message)"
}
}
function Test-StoreSearchSuggestionsDisabled {
param(
[Parameter(Mandatory)]
[string]$StoreAppsDatabase
)
if (-not (Test-Path -Path $StoreAppsDatabase)) {
return $false
}
try {
$acl = Get-Acl -Path $StoreAppsDatabase
}
catch {
return $false
}
$everyoneSid = [System.Security.Principal.SecurityIdentifier]::new('S-1-1-0')
foreach ($accessRule in @($acl.Access)) {
$isDenyFullControl = $accessRule.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Deny -and
(($accessRule.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -ne 0)
if (-not $isDenyFullControl) { continue }
$isEveryone = $false
try {
$isEveryone = $accessRule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $everyoneSid
} catch { }
if ($isEveryone) {
return $true
}
}
return $false
}
function Test-StoreSearchSuggestionsDisabledForAllUsers {
$paths = @()
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages"
$usersStoreDbPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
foreach ($storeDbPath in $usersStoreDbPaths) {
$paths += ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
}
$defaultStoreDbPath = GetUserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false
if ($defaultStoreDbPath) {
$paths += $defaultStoreDbPath
}
if ($paths.Count -eq 0) {
return $false
}
foreach ($path in $paths) {
if (-not (Test-StoreSearchSuggestionsDisabled -StoreAppsDatabase $path)) {
return $false
}
}
return $true
} }

View File

@@ -1,16 +0,0 @@
# Enables a Windows optional feature and pipes its output to the console
function EnableWindowsFeature {
param (
[string]$FeatureName
)
$result = Invoke-NonBlocking -ScriptBlock {
param($name)
Enable-WindowsOptionalFeature -Online -FeatureName $name -All -NoRestart
} -ArgumentList $FeatureName
$dismResult = @($result) | Where-Object { $_ -is [Microsoft.Dism.Commands.ImageObject] }
if ($dismResult) {
Write-Host ($dismResult | Out-String).Trim()
}
}

View File

@@ -20,11 +20,11 @@ function ExecuteParameter {
switch ($paramKey) { switch ($paramKey) {
'DisableBing' { 'DisableBing' {
# Also remove the app package for Bing search # Also remove the app package for Bing search
RemoveApps 'Microsoft.BingSearch' RemoveApps @('Microsoft.BingSearch')
} }
'DisableCopilot' { 'DisableCopilot' {
# Also remove the app package for Copilot # Also remove the app package for Copilot
RemoveApps 'Microsoft.Copilot' RemoveApps @('Microsoft.Copilot')
} }
} }
return return
@@ -33,7 +33,7 @@ function ExecuteParameter {
# Handle features without RegistryKey or with special logic # Handle features without RegistryKey or with special logic
switch ($paramKey) { switch ($paramKey) {
'RemoveApps' { 'RemoveApps' {
Write-Host "> Removing selected apps for $(GetFriendlyTargetUserName)..." Write-Host "> $($feature.ApplyText) for $(GetFriendlyTargetUserName)..."
$appsList = GenerateAppsList $appsList = GenerateAppsList
if ($appsList.Count -eq 0) { if ($appsList.Count -eq 0) {
@@ -46,7 +46,7 @@ function ExecuteParameter {
RemoveApps $appsList RemoveApps $appsList
} }
'RemoveAppsCustom' { 'RemoveAppsCustom' {
Write-Host "> Removing selected apps..." Write-Host "> $($feature.ApplyText)..."
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath $appsList = LoadAppsFromFile $script:CustomAppsListFilePath
if ($appsList.Count -eq 0) { if ($appsList.Count -eq 0) {
@@ -58,58 +58,46 @@ function ExecuteParameter {
Write-Host "$($appsList.Count) apps selected for removal" Write-Host "$($appsList.Count) apps selected for removal"
RemoveApps $appsList RemoveApps $appsList
} }
'RemoveCommApps' {
$appsList = 'Microsoft.windowscommunicationsapps', 'Microsoft.People'
Write-Host "> Removing Mail, Calendar and People apps..."
RemoveApps $appsList
return
}
'RemoveW11Outlook' {
$appsList = 'Microsoft.OutlookForWindows'
Write-Host "> Removing new Outlook for Windows app..."
RemoveApps $appsList
return
}
'RemoveGamingApps' { 'RemoveGamingApps' {
$appsList = 'Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay' $appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
Write-Host "> Removing gaming related apps..." Write-Host "> $($feature.ApplyText)..."
RemoveApps $appsList RemoveApps $appsList
return return
} }
'RemoveHPApps' { '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' $appsList = @('AD2F1837.HPAIExperienceCenter', 'AD2F1837.HPJumpStarts', 'AD2F1837.HPPCHardwareDiagnosticsWindows', 'AD2F1837.HPPowerManager', 'AD2F1837.HPPrivacySettings', 'AD2F1837.HPSupportAssistant', 'AD2F1837.HPSureShieldAI', 'AD2F1837.HPSystemInformation', 'AD2F1837.HPQuickDrop', 'AD2F1837.HPWorkWell', 'AD2F1837.myHP', 'AD2F1837.HPDesktopSupportUtilities', 'AD2F1837.HPQuickTouch', 'AD2F1837.HPEasyClean', 'AD2F1837.HPConnectedMusic', 'AD2F1837.HPFileViewer', 'AD2F1837.HPRegistration', 'AD2F1837.HPWelcome', 'AD2F1837.HPConnectedPhotopoweredbySnapfish', 'AD2F1837.HPPrinterControl')
Write-Host "> Removing HP apps..." Write-Host "> $($feature.ApplyText)..."
RemoveApps $appsList RemoveApps $appsList
return return
} }
'DisableWidgets' { 'DisableWidgets' {
Write-Host "> Disabling widgets on the taskbar & lock screen..." Write-Host "> $($feature.ApplyText)..."
# Stop widgets related processes before removing the app packages to prevent potential issues # Stop widgets related processes before removing the app packages to prevent potential issues
Get-Process *Widget* | Stop-Process Get-Process *Widget* -ErrorAction SilentlyContinue | Stop-Process
RemoveApps 'Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime' RemoveApps @('Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime')
} }
"EnableWindowsSandbox" { 'EnableWindowsSandbox' {
Write-Host "> Enabling Windows Sandbox..." Write-Host "> $($feature.ApplyText)..."
EnableWindowsFeature "Containers-DisposableClientVM" EnableWindowsFeature "Containers-DisposableClientVM"
Write-Host "" Write-Host ""
return return
} }
"EnableWindowsSubsystemForLinux" { 'EnableWindowsSubsystemForLinux' {
Write-Host "> Enabling Windows Subsystem for Linux..." Write-Host "> $($feature.ApplyText)..."
EnableWindowsFeature "VirtualMachinePlatform" EnableWindowsFeature "VirtualMachinePlatform"
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux" EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
Write-Host "" Write-Host ""
return return
} }
'ClearStart' { 'ClearStart' {
Write-Host "> Removing all pinned apps from the start menu for user $(GetUserName)..." Write-Host "> $($feature.ApplyText) for user $(GetUserName)..."
ReplaceStartMenu ReplaceStartMenu
Write-Host "" Write-Host ""
return return
} }
'ReplaceStart' { 'ReplaceStart' {
Write-Host "> Replacing the start menu for user $(GetUserName)..." Write-Host "> $($feature.ApplyText) for user $(GetUserName)..."
ReplaceStartMenu $script:Params.Item("ReplaceStart") ReplaceStartMenu $script:Params.Item("ReplaceStart")
Write-Host "" Write-Host ""
return return
@@ -140,7 +128,14 @@ function ExecuteParameter {
# Executes all selected parameters/features # Executes all selected parameters/features
function ExecuteAllChanges { 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."
}
$script:RegistryImportFailures = 0 $script:RegistryImportFailures = 0
# Build list of actionable parameters (skip control params and data-only params) # Build list of actionable parameters (skip control params and data-only params)
@@ -162,8 +157,15 @@ function ExecuteAllChanges {
break 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 $totalSteps = $actionableKeys.Count + $script:UndoParams.Count
if ($hasRegistryBackedFeature) { $totalSteps++ } if ($hasRegistryBackedFeature) { $totalSteps++ }
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ } if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
$currentStep = 0 $currentStep = 0
@@ -176,7 +178,13 @@ function ExecuteAllChanges {
Write-Host "> Creating registry backup..." Write-Host "> Creating registry backup..."
try { try {
New-RegistrySettingsBackup -ActionableKeys $actionableKeys | Out-Null $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 { catch {
throw "Registry backup failed before applying changes. $($_.Exception.Message)" throw "Registry backup failed before applying changes. $($_.Exception.Message)"
@@ -196,9 +204,7 @@ function ExecuteAllChanges {
# Execute all parameters # Execute all parameters
foreach ($paramKey in $actionableKeys) { foreach ($paramKey in $actionableKeys) {
if ($script:CancelRequested) { if ($script:CancelRequested) { return }
return
}
$currentStep++ $currentStep++
@@ -222,8 +228,82 @@ function ExecuteAllChanges {
ExecuteParameter -paramKey $paramKey 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)
} else {
Invoke-UndoFeatureAction -FeatureId $featureId
}
}
if ($script:RegistryImportFailures -gt 0) { if ($script:RegistryImportFailures -gt 0) {
Write-Host "" Write-Host ""
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow 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 {
param(
[Parameter(Mandatory)]
[string]$FeatureId
)
$feature = if ($script:Features.ContainsKey($FeatureId)) { $script:Features[$FeatureId] } else { $null }
switch ($FeatureId) {
'DisableStoreSearchSuggestions' {
if ($script:Params.ContainsKey('Sysprep')) {
Write-Host "> Re-enabling Microsoft Store search suggestions in the start menu for all users..."
EnableStoreSearchSuggestionsForAllUsers
Write-Host ""
return
}
Write-Host "> Re-enabling Microsoft Store search suggestions for user $(GetUserName)..."
EnableStoreSearchSuggestions
Write-Host ""
return
}
'EnableWindowsSandbox' {
Write-Host "> $($feature.ApplyUndoText)..."
DisableWindowsFeature 'Containers-DisposableClientVM'
Write-Host ""
return
}
'EnableWindowsSubsystemForLinux' {
Write-Host "> $($feature.ApplyUndoText)..."
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
DisableWindowsFeature 'VirtualMachinePlatform'
Write-Host ""
return
}
default {
Write-Host "> No undo action defined for $FeatureId, skipping..." -ForegroundColor Yellow
Write-Host ""
return
}
}
}

View File

@@ -0,0 +1,175 @@
# Tests whether the registry operations in a feature's .reg file currently match the live registry.
# Returns $true if ALL operations in the apply reg file match current system state.
# Returns $false if the feature has no RegistryKey, the file is missing, or any operation mismatches.
function Get-ExpectedRegistryValueKind {
param(
[Parameter(Mandatory)]
$Operation
)
switch ([string]$Operation.ValueType) {
'DWord' { return [Microsoft.Win32.RegistryValueKind]::DWord }
'QWord' { return [Microsoft.Win32.RegistryValueKind]::QWord }
'String' { return [Microsoft.Win32.RegistryValueKind]::String }
'Binary' { return [Microsoft.Win32.RegistryValueKind]::Binary }
'Hex2' { return [Microsoft.Win32.RegistryValueKind]::ExpandString }
'Hex7' { return [Microsoft.Win32.RegistryValueKind]::MultiString }
default { return $null }
}
}
function Test-FeatureApplied {
param (
[Parameter(Mandatory)]
[string]$FeatureId
)
if (-not $script:Features.ContainsKey($FeatureId)) { return $false }
$feature = $script:Features[$FeatureId]
switch ($FeatureId) {
'DisableWidgets' {
# Widgets packages cannot be reinstalled automatically, so we treat their
# absence as the applied state (checked) and presence as not-yet-applied.
$widgetAppIds = @(
'Microsoft.StartExperiencesApp',
'MicrosoftWindows.Client.WebExperience',
'Microsoft.WidgetsPlatformRuntime'
)
foreach ($appId in $widgetAppIds) {
if (Get-AppxPackage -Name $appId -AllUsers -ErrorAction SilentlyContinue) {
return $false
}
}
return $true
}
'DisableStoreSearchSuggestions' {
if ($script:Params.ContainsKey('Sysprep')) {
return (Test-StoreSearchSuggestionsDisabledForAllUsers)
}
$storeDbPath = "$env:LocalAppData\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db"
if ($script:Params.ContainsKey('User')) {
$storeDbPath = GetUserDirectory -userName "$(GetUserName)" -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false
}
return (Test-StoreSearchSuggestionsDisabled -StoreAppsDatabase $storeDbPath)
}
'EnableWindowsSandbox' {
return (Test-WindowsOptionalFeatureEnabled -FeatureName 'Containers-DisposableClientVM')
}
'EnableWindowsSubsystemForLinux' {
$wslEnabled = Test-WindowsOptionalFeatureEnabled -FeatureName 'Microsoft-Windows-Subsystem-Linux'
$vmpEnabled = Test-WindowsOptionalFeatureEnabled -FeatureName 'VirtualMachinePlatform'
return ($wslEnabled -and $vmpEnabled)
}
}
if (-not $feature.RegistryKey) { return $false }
$regFilePath = Join-Path $script:RegfilesPath $feature.RegistryKey
if (-not (Test-Path $regFilePath)) { return $false }
try {
$operations = @(Get-RegFileOperations -regFilePath $regFilePath)
}
catch { return $false }
if ($operations.Count -eq 0) { return $false }
foreach ($op in $operations) {
$parts = Split-RegistryPath -path $op.KeyPath
if (-not $parts) { return $false }
$rootKey = Get-RegistryRootKey -hiveName $parts.Hive
if (-not $rootKey) { return $false }
$key = $null
try {
$key = $rootKey.OpenSubKey($parts.SubKey, $false)
switch ($op.OperationType) {
'DeleteKey' {
if ($null -ne $key) { return $false }
}
'DeleteValue' {
if ($null -ne $key) {
$names = @($key.GetValueNames())
if ($names -icontains $op.ValueName) { return $false }
}
# key missing = value also gone = operation matches
}
'SetValue' {
if ($null -eq $key) { return $false }
$names = @($key.GetValueNames())
if (-not ($names -icontains $op.ValueName)) { return $false }
$actualKind = $key.GetValueKind($op.ValueName)
$expectedKind = Get-ExpectedRegistryValueKind -Operation $op
if ($null -eq $expectedKind -or $actualKind -ne $expectedKind) { return $false }
$actualRaw = $key.GetValue($op.ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$actual = switch ($actualKind) {
([Microsoft.Win32.RegistryValueKind]::DWord) {
[BitConverter]::ToUInt32([BitConverter]::GetBytes([int32]$actualRaw), 0)
}
([Microsoft.Win32.RegistryValueKind]::QWord) {
[BitConverter]::ToUInt64([BitConverter]::GetBytes([int64]$actualRaw), 0)
}
([Microsoft.Win32.RegistryValueKind]::Binary) {
@($actualRaw | ForEach-Object { [int]$_ })
}
([Microsoft.Win32.RegistryValueKind]::MultiString) {
@($actualRaw)
}
default {
if ($null -ne $actualRaw) { [string]$actualRaw } else { $null }
}
}
$expected = $op.ValueData
$match = if (($actual -is [array]) -and ($expected -is [array])) {
(Compare-Object $actual $expected).Count -eq 0
} else {
$actual -eq $expected
}
if (-not $match) { return $false }
}
}
}
catch { return $false }
finally {
if ($null -ne $key) { $key.Close() }
}
}
return $true
}
# Returns the 1-based index of the UiGroup option whose features all match current system state,
# or 0 if no option fully matches (meaning the current state is unknown / "No Change").
function Get-CurrentGroupActiveIndex {
param (
[Parameter(Mandatory)]
[object]$Group
)
$i = 1
foreach ($val in $Group.Values) {
$allApplied = $true
foreach ($fid in $val.FeatureIds) {
if (-not (Test-FeatureApplied -FeatureId $fid)) {
$allApplied = $false
break
}
}
if ($allApplied) { return $i }
$i++
}
return 0
}

View File

@@ -8,13 +8,7 @@ function ImportRegistryFile {
Write-Host $message Write-Host $message
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User") $usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
$regFileDirectory = if ($usesOfflineHive) { $regFilePath = Get-RegistryFilePathForFeature -RegistryKey $path
Join-Path $script:RegfilesPath "Sysprep"
}
else {
$script:RegfilesPath
}
$regFilePath = Join-Path $regFileDirectory $path
if (-not (Test-Path $regFilePath)) { if (-not (Test-Path $regFilePath)) {
$errorMessage = "Unable to find registry file: $path ($regFilePath)" $errorMessage = "Unable to find registry file: $path ($regFilePath)"
@@ -24,24 +18,19 @@ function ImportRegistryFile {
throw $errorMessage throw $errorMessage
} }
$regResult = $null $importScript = {
$offlineHiveLoaded = $false param($targetRegFilePath, $hiveContext)
try { # When the target user's hive is already loaded under their SID, the .reg file's
if ($usesOfflineHive) { # HKEY_USERS\Default paths won't match. Use the PowerShell registry writer instead,
# Sysprep targets Default user, User targets the specified user # which remaps Default → SID via Split-RegistryPath.
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") } $usePowerShellFallbackOnly = $hiveContext -and [bool]$hiveContext.WasAlreadyLoaded
$hiveDatPath = GetUserDirectory -userName $targetUserName -fileName "NTUSER.DAT"
$global:LASTEXITCODE = 0 if ($usePowerShellFallbackOnly) {
reg load "HKU\Default" $hiveDatPath | Out-Null Invoke-RegistryOperationsFromRegFile -RegFilePath $targetRegFilePath
$loadExitCode = $LASTEXITCODE Write-Host "The operation completed successfully via PowerShell registry writer."
Write-Host ""
if ($loadExitCode -ne 0) { return
throw "Failed importing registry file '$path'. Offline hive load failed: Failed to load user hive at '$hiveDatPath' (exit code: $loadExitCode)"
}
$offlineHiveLoaded = $true
} }
$regResult = Invoke-NonBlocking -ScriptBlock { $regResult = Invoke-NonBlocking -ScriptBlock {
@@ -72,7 +61,7 @@ function ImportRegistryFile {
} }
return $result return $result
} -ArgumentList $regFilePath } -ArgumentList $targetRegFilePath
$regOutput = @($regResult.Output) $regOutput = @($regResult.Output)
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error $hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
@@ -94,26 +83,26 @@ function ImportRegistryFile {
if (-not $hasSuccess) { if (-not $hasSuccess) {
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" } $details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
Write-Warning "reg import failed for '$path'. Falling back to PowerShell registry writer. Details: $details" Write-Warning "reg import failed for '$path'. Falling back to PowerShell registry writer. Details: $details"
Invoke-RegistryOperationsFromRegFile -RegFilePath $regFilePath Invoke-RegistryOperationsFromRegFile -RegFilePath $targetRegFilePath
Write-Host "Fallback import succeeded for '$path'." -ForegroundColor Yellow Write-Host "The operation completed successfully via PowerShell registry writer."
} }
Write-Host "" Write-Host ""
} }
try {
if ($usesOfflineHive) {
# Sysprep targets Default user, User targets the specified user. Logged-in users already have their hive mounted under HKU\<SID>.
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
Invoke-WithTargetUserHive -TargetUserName $targetUserName -ScriptBlock $importScript -ArgumentObject $regFilePath -PassHiveContext
}
else {
& $importScript $regFilePath $null
}
}
catch { catch {
$script:RegistryImportFailures++ $script:RegistryImportFailures++
Write-Host $_.Exception.Message -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host "" Write-Host ""
} }
finally { }
if ($offlineHiveLoaded) {
$global:LASTEXITCODE = 0
reg unload "HKU\Default" | Out-Null
$unloadExitCode = $LASTEXITCODE
if ($unloadExitCode -ne 0) {
Write-Warning "Failed to unload registry hive HKU\Default after importing '$path' (exit code: $unloadExitCode)"
}
}
}
}

View File

@@ -33,12 +33,49 @@ function Get-NormalizedSelectedFeatureIdsFromBackup {
$errors.Add('SelectedFeatures must contain non-empty string feature IDs.') $errors.Add('SelectedFeatures must contain non-empty string feature IDs.')
} }
if ($selectedFeatures.Count -eq 0) { return [PSCustomObject]@{
$errors.Add('SelectedFeatures must contain at least one feature ID.') SelectedFeatures = $selectedFeatures.ToArray()
Errors = $errors.ToArray()
}
}
function Get-NormalizedSelectedUndoFeatureIdsFromBackup {
param(
[Parameter(Mandatory)]
$Backup
)
$selectedUndoFeatures = New-Object System.Collections.Generic.List[string]
$selectedUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
$errors = New-Object System.Collections.Generic.List[string]
# SelectedUndoFeatures is optional - only process if present
if (-not $Backup.PSObject.Properties['SelectedUndoFeatures']) {
return [PSCustomObject]@{
SelectedUndoFeatures = $selectedUndoFeatures.ToArray()
Errors = $errors.ToArray()
}
}
$hasInvalidSelectedUndoFeatureId = $false
foreach ($featureId in @($Backup.SelectedUndoFeatures)) {
if ($featureId -isnot [string] -or [string]::IsNullOrWhiteSpace([string]$featureId)) {
$hasInvalidSelectedUndoFeatureId = $true
continue
}
$normalizedFeatureId = [string]$featureId
if ($selectedUndoFeatureIds.Add($normalizedFeatureId)) {
$selectedUndoFeatures.Add($normalizedFeatureId)
}
}
if ($hasInvalidSelectedUndoFeatureId) {
$errors.Add('SelectedUndoFeatures must contain non-empty string feature IDs.')
} }
return [PSCustomObject]@{ return [PSCustomObject]@{
SelectedFeatures = $selectedFeatures.ToArray() SelectedUndoFeatures = $selectedUndoFeatures.ToArray()
Errors = $errors.ToArray() Errors = $errors.ToArray()
} }
} }
@@ -96,6 +133,9 @@ function Test-RegistryBackupMatchesSelectedFeatures {
[AllowEmptyCollection()] [AllowEmptyCollection()]
[string[]]$SelectedFeatureIds, [string[]]$SelectedFeatureIds,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[AllowEmptyCollection()]
[string[]]$SelectedUndoFeatureIds,
[Parameter(Mandatory)]
[string]$Target, [string]$Target,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[AllowEmptyCollection()] [AllowEmptyCollection()]
@@ -109,18 +149,19 @@ function Test-RegistryBackupMatchesSelectedFeatures {
return $errors.ToArray() return $errors.ToArray()
} }
$selectedRegistryFeatures = @(Get-SelectedRegistryFeaturesForBackupValidation -SelectedFeatureIds @($SelectedFeatureIds) -Errors $errors) $selectedRegistryFeatures = @(Get-SelectedRegistryFeaturesForBackupValidation -SelectedFeatureIds @($SelectedFeatureIds) -IsUndoFeature:$false -Errors $errors)
$undoRegistryFeatures = @(Get-SelectedRegistryFeaturesForBackupValidation -SelectedFeatureIds @($SelectedUndoFeatureIds) -IsUndoFeature:$true -Errors $errors)
$useSysprepRegFiles = ($Target -eq 'DefaultUserProfile') -or ($Target -like 'User:*') $useSysprepRegFiles = ($Target -eq 'DefaultUserProfile') -or ($Target -like 'User:*')
$capturePlans = @() $capturePlans = @()
if ($errors.Count -eq 0 -and $selectedRegistryFeatures.Count -gt 0) { if ($errors.Count -eq 0 -and ($selectedRegistryFeatures.Count -gt 0 -or $undoRegistryFeatures.Count -gt 0)) {
$capturePlans = @(Get-RegistryBackupCapturePlans -SelectedRegistryFeatures @($selectedRegistryFeatures) -UseSysprepRegFiles:$useSysprepRegFiles) $capturePlans = @(Get-RegistryBackupCapturePlans -SelectedRegistryFeatures @($selectedRegistryFeatures) -UndoRegistryFeatures @($undoRegistryFeatures) -UseSysprepRegFiles:$useSysprepRegFiles)
} }
$planMap = New-RegistryBackupAllowListPlanMap -CapturePlans @($capturePlans) $planMap = New-RegistryBackupAllowListPlanMap -CapturePlans @($capturePlans)
if ($planMap.Count -eq 0 -and @($RegistryKeys).Count -gt 0) { if ($planMap.Count -eq 0 -and @($RegistryKeys).Count -gt 0) {
$errors.Add('Backup contains registry snapshots but no allowed registry paths were derived from SelectedFeatures.') $errors.Add('Backup contains registry snapshots but no allowed registry paths were derived from the selected features.')
} }
foreach ($rootSnapshot in @($RegistryKeys)) { foreach ($rootSnapshot in @($RegistryKeys)) {
@@ -136,6 +177,8 @@ function Get-SelectedRegistryFeaturesForBackupValidation {
[AllowEmptyCollection()] [AllowEmptyCollection()]
[string[]]$SelectedFeatureIds, [string[]]$SelectedFeatureIds,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[bool]$IsUndoFeature,
[Parameter(Mandatory)]
[AllowEmptyCollection()] [AllowEmptyCollection()]
$Errors $Errors
) )
@@ -152,7 +195,26 @@ function Get-SelectedRegistryFeaturesForBackupValidation {
} }
$feature = $script:Features[$featureId] $feature = $script:Features[$featureId]
if ($feature -and -not [string]::IsNullOrWhiteSpace([string]$feature.RegistryKey)) { if (-not $feature) {
continue
}
# For undo features, check RegistryUndoKey if present (real features)
# Otherwise check RegistryKey (for synthetic features from backup capture)
$registryKeyToUse = if ($IsUndoFeature) {
$key = [string]$feature.RegistryUndoKey
if (-not [string]::IsNullOrWhiteSpace($key)) {
$key
}
else {
[string]$feature.RegistryKey
}
}
else {
[string]$feature.RegistryKey
}
if (-not [string]::IsNullOrWhiteSpace($registryKeyToUse)) {
$selectedRegistryFeatures.Add($feature) $selectedRegistryFeatures.Add($feature)
} }
} }

View File

@@ -39,7 +39,7 @@ function ReplaceStartMenuForAllUsers {
} }
# Replace the startmenu for all users, when using the default startmenuTemplate this clears all pinned apps # 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/ # Credit: https://lazyadmin.nl/win-11/customize-windows-11-start-menu-layout/
function ReplaceStartMenu { function ReplaceStartMenu {
param ( param (

View File

@@ -1,8 +1,13 @@
# Restart the Windows Explorer process # Restart the Windows Explorer process
function RestartExplorer { function RestartExplorer {
# Restarting Explorer while running in Sysprep or User context is not necessary
if ($script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")) {
return
}
Write-Host "> Attempting to restart the Windows Explorer process to apply all changes..." Write-Host "> Attempting to restart the Windows Explorer process to apply all changes..."
if ($script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User") -or $script:Params.ContainsKey("NoRestartExplorer")) { if ($script:Params.ContainsKey("NoRestartExplorer")) {
Write-Host "Explorer process restart was skipped, please manually reboot your PC to apply all changes" -ForegroundColor Yellow Write-Host "Explorer process restart was skipped, please manually reboot your PC to apply all changes" -ForegroundColor Yellow
return return
} }

View File

@@ -7,38 +7,21 @@ function Invoke-WithLoadedRestoreHive {
$ArgumentObject = $null $ArgumentObject = $null
) )
$hiveDatPath = if ($Target -eq 'DefaultUserProfile') { $targetUserName = if ($Target -eq 'DefaultUserProfile') {
GetUserDirectory -userName 'Default' -fileName 'NTUSER.DAT' 'Default'
} }
elseif ($Target -like 'User:*') { elseif ($Target -like 'User:*') {
$userName = $Target.Substring(5) $userName = $Target.Substring(5)
if ([string]::IsNullOrWhiteSpace($userName)) { if ([string]::IsNullOrWhiteSpace($userName)) {
throw 'Invalid backup target format for user restore.' throw 'Invalid backup target format for user restore.'
} }
GetUserDirectory -userName $userName -fileName 'NTUSER.DAT' $userName
} }
else { else {
throw "Unsupported backup target '$Target'." throw "Unsupported backup target '$Target'."
} }
$global:LASTEXITCODE = 0 Invoke-WithTargetUserHive -TargetUserName $targetUserName -ScriptBlock $ScriptBlock -ArgumentObject $ArgumentObject
reg load 'HKU\Default' "$hiveDatPath" | Out-Null
$loadExitCode = $LASTEXITCODE
if ($loadExitCode -ne 0) {
throw "Failed to load target user hive '$hiveDatPath' (exit code: $loadExitCode)."
}
try {
& $ScriptBlock $ArgumentObject
}
finally {
$global:LASTEXITCODE = 0
reg unload 'HKU\Default' | Out-Null
$unloadExitCode = $LASTEXITCODE
if ($unloadExitCode -ne 0) {
throw "Failed to unload registry hive 'HKU\Default' (exit code: $unloadExitCode)"
}
}
} }
function Restore-RegistryKeySnapshot { function Restore-RegistryKeySnapshot {

View File

@@ -87,7 +87,17 @@ function Normalize-RegistryBackup {
$errors.Add([string]$selectedFeatureParseError) $errors.Add([string]$selectedFeatureParseError)
} }
$allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys)) $selectedUndoFeatureParseResult = Get-NormalizedSelectedUndoFeatureIdsFromBackup -Backup $Backup
$selectedUndoFeatures = @($selectedUndoFeatureParseResult.SelectedUndoFeatures)
foreach ($selectedUndoFeatureParseError in @($selectedUndoFeatureParseResult.Errors)) {
$errors.Add([string]$selectedUndoFeatureParseError)
}
$allSelectedFeatures = @($selectedFeatures) + @($selectedUndoFeatures)
if ($allSelectedFeatures.Count -eq 0) {
$errors.Add('Backup must contain at least one feature ID in SelectedFeatures or SelectedUndoFeatures.')
}
$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)
} }
@@ -110,6 +120,7 @@ function Normalize-RegistryBackup {
ComputerName = [string]$Backup.ComputerName ComputerName = [string]$Backup.ComputerName
Target = $normalizedTarget Target = $normalizedTarget
SelectedFeatures = @($selectedFeatures) SelectedFeatures = @($selectedFeatures)
SelectedUndoFeatures = @($selectedUndoFeatures)
RegistryKeys = @($normalizedKeys) RegistryKeys = @($normalizedKeys)
} }
} }

View File

@@ -0,0 +1,49 @@
# Enables a Windows optional feature and pipes its output to the console
function EnableWindowsFeature {
param (
[string]$FeatureName
)
$result = Invoke-NonBlocking -ScriptBlock {
param($name)
Enable-WindowsOptionalFeature -Online -FeatureName $name -All -NoRestart
} -ArgumentList $FeatureName
$dismResult = @($result) | Where-Object { $_ -is [Microsoft.Dism.Commands.ImageObject] }
if ($dismResult) {
Write-Host ($dismResult | Out-String).Trim()
}
}
# Disables a Windows optional feature and pipes its output to the console
function DisableWindowsFeature {
param (
[string]$FeatureName
)
$result = Invoke-NonBlocking -ScriptBlock {
param($name)
Disable-WindowsOptionalFeature -Online -FeatureName $name -NoRestart
} -ArgumentList $FeatureName
$dismResult = @($result) | Where-Object { $_ -is [Microsoft.Dism.Commands.ImageObject] }
if ($dismResult) {
Write-Host ($dismResult | Out-String).Trim()
}
}
function Test-WindowsOptionalFeatureEnabled {
param (
[Parameter(Mandatory)]
[string]$FeatureName
)
try {
$feature = Get-WindowsOptionalFeature -Online -FeatureName $FeatureName -ErrorAction Stop
}
catch {
return $false
}
return ($feature.State -eq 'Enabled')
}

View File

@@ -1,7 +1,14 @@
# Checks if the system is set to use dark mode for apps # Checks if the system is set to use dark mode for apps
function GetSystemUsesDarkMode { function GetSystemUsesDarkMode {
try { try {
return (Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize' -Name 'AppsUseLightTheme').AppsUseLightTheme -eq 0 $personalizeKey = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize'
if ($null -eq $personalizeKey) {
Write-Host "WARNING: Unable to retrieve personalization settings." -ForegroundColor Yellow
return $false
}
return $personalizeKey.AppsUseLightTheme -eq 0
} }
catch { catch {
return $false return $false

View File

@@ -0,0 +1,540 @@
# MainWindow-AppSelection.ps1
# App-selection panel functions: tri-state helpers, sorting, search/highlight, app loading, preset management, and removal scope.
function Add-TriStateClickBehavior {
param([System.Windows.Controls.CheckBox]$CheckBox)
if (-not $CheckBox -or -not $CheckBox.IsThreeState) { return }
if (-not $CheckBox.PSObject.Properties['WasIndeterminateBeforeClick']) {
Add-Member -InputObject $CheckBox -MemberType NoteProperty -Name 'WasIndeterminateBeforeClick' -Value $false
}
$CheckBox.Add_PreviewMouseLeftButtonDown({
$this.WasIndeterminateBeforeClick = ($this.IsChecked -eq [System.Nullable[bool]]$null)
})
}
function ConvertTo-NormalizedCheckboxState {
param([System.Windows.Controls.CheckBox]$CheckBox)
if ($CheckBox.PSObject.Properties['WasIndeterminateBeforeClick'] -and $CheckBox.WasIndeterminateBeforeClick) {
# WPF toggles null -> false before Click handlers fire; restore desired mixed -> checked behavior.
$CheckBox.WasIndeterminateBeforeClick = $false
$CheckBox.IsChecked = $true
return $true
}
return ($CheckBox.IsChecked -eq $true)
}
function Set-TriStatePresetCheckBoxState {
param(
[System.Windows.Controls.CheckBox]$CheckBox,
[int]$Total,
[int]$Selected
)
if (-not $CheckBox) { return }
if ($Total -eq 0) {
$CheckBox.IsEnabled = $false
$CheckBox.IsChecked = $false
return
}
$CheckBox.IsEnabled = $true
if ($Selected -eq 0) {
$CheckBox.IsChecked = $false
}
elseif ($Selected -eq $Total) {
$CheckBox.IsChecked = $true
}
else {
$CheckBox.IsChecked = [System.Nullable[bool]]$null
}
}
function Update-SortArrows {
param(
[System.Windows.Controls.TextBlock]$SortArrowName,
[System.Windows.Controls.TextBlock]$SortArrowDescription,
[System.Windows.Controls.TextBlock]$SortArrowAppId
)
$ease = New-Object System.Windows.Media.Animation.CubicEase
$ease.EasingMode = 'EaseOut'
$arrows = @{
'Name' = $SortArrowName
'Description' = $SortArrowDescription
'AppId' = $SortArrowAppId
}
foreach ($col in $arrows.Keys) {
$tb = $arrows[$col]
# Active column: full opacity, rotate to indicate direction (0 = up/asc, 180 = down/desc)
# Inactive columns: dim, reset to 0
if ($col -eq $script:SortColumn) {
$targetAngle = if ($script:SortAscending) { 0 } else { 180 }
$tb.Opacity = 1.0
}
else {
$targetAngle = 0
$tb.Opacity = 0.3
}
$anim = New-Object System.Windows.Media.Animation.DoubleAnimation
$anim.To = $targetAngle
$anim.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
$anim.EasingFunction = $ease
$tb.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $anim)
}
}
function Update-AppsPanelRebuildSearchIndex {
param(
[System.Windows.Controls.Panel]$AppsPanel,
$ActiveMatch = $null
)
$newMatches = @()
$newActiveIndex = -1
$i = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.Background -ne [System.Windows.Media.Brushes]::Transparent) {
$newMatches += $child
if ($null -ne $ActiveMatch -and [System.Object]::ReferenceEquals($child, $ActiveMatch)) {
$newActiveIndex = $i
}
$i++
}
}
$script:AppSearchMatches = $newMatches
$script:AppSearchMatchIndex = if ($newActiveIndex -ge 0) { $newActiveIndex } elseif ($newMatches.Count -gt 0) { 0 } else { -1 }
}
function Update-AppsPanelSort {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.TextBlock]$SortArrowName,
[System.Windows.Controls.TextBlock]$SortArrowDescription,
[System.Windows.Controls.TextBlock]$SortArrowAppId
)
$children = @($AppsPanel.Children)
$key = switch ($script:SortColumn) {
'Name' { { $_.AppName } }
'Description' { { $_.AppDescription } }
'AppId' { { $_.AppIdDisplay } }
}
$sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending)
$AppsPanel.Children.Clear()
foreach ($checkbox in $sorted) {
$AppsPanel.Children.Add($checkbox) | Out-Null
}
Update-SortArrows -SortArrowName $SortArrowName -SortArrowDescription $SortArrowDescription -SortArrowAppId $SortArrowAppId
# Rebuild search match list in new sorted order so keyboard navigation stays correct
if ($script:AppSearchMatches.Count -gt 0) {
$activeMatch = if ($script:AppSearchMatchIndex -ge 0 -and $script:AppSearchMatchIndex -lt $script:AppSearchMatches.Count) {
$script:AppSearchMatches[$script:AppSearchMatchIndex]
}
else { $null }
Update-AppsPanelRebuildSearchIndex -AppsPanel $AppsPanel -ActiveMatch $activeMatch
}
}
function Update-AppSelectionStatus {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.TextBlock]$AppSelectionStatus,
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo,
[System.Windows.Controls.Border]$AppRemovalScopeSection,
[System.Windows.Controls.TextBlock]$AppRemovalScopeDescription,
[System.Windows.Controls.ComboBox]$UserSelectionCombo
)
$selectedCount = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$selectedCount++
}
}
$AppSelectionStatus.Text = "$selectedCount app(s) selected for removal"
if ($AppRemovalScopeCombo -and $AppRemovalScopeSection -and $AppRemovalScopeDescription) {
if ($selectedCount -gt 0) {
$AppRemovalScopeSection.Visibility = 'Visible'
if ($UserSelectionCombo.SelectedIndex -ne 2) {
$AppRemovalScopeCombo.IsEnabled = $true
}
Update-AppRemovalScopeDescription -AppRemovalScopeCombo $AppRemovalScopeCombo -AppRemovalScopeDescription $AppRemovalScopeDescription
}
else {
$AppRemovalScopeSection.Visibility = 'Collapsed'
}
}
}
function Update-AppRemovalScopeDescription {
param(
[System.Windows.Controls.ComboBox]$AppRemovalScopeCombo,
[System.Windows.Controls.TextBlock]$AppRemovalScopeDescription
)
$selectedItem = $AppRemovalScopeCombo.SelectedItem
if ($selectedItem) {
switch ($selectedItem.Content) {
"All users" {
$AppRemovalScopeDescription.Text = "Apps will be removed for all users and from the Windows image to prevent reinstallation for new users."
}
"Current user only" {
$AppRemovalScopeDescription.Text = "Apps will only be removed for the current user. Existing and new users will not be affected."
}
"Target user only" {
$AppRemovalScopeDescription.Text = "Apps will only be removed for the specified target user. Existing and new users will not be affected."
}
}
}
}
function Invoke-AppPreset {
param(
[System.Windows.Controls.Panel]$AppsPanel,
[scriptblock]$MatchFilter,
[bool]$Check,
[switch]$Exclusive
)
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox]) {
if ($Exclusive) {
$child.IsChecked = (& $MatchFilter $child)
}
elseif (& $MatchFilter $child) {
$child.IsChecked = $Check
}
}
}
Update-AppPresetStates -AppsPanel $AppsPanel
}
function Update-AppPresetStates {
param([System.Windows.Controls.Panel]$AppsPanel)
$script:UpdatingPresets = $true
try {
# Helper: count matching and checked apps, set checkbox state
function SetPresetState($CheckBox, [scriptblock]$MatchFilter) {
$total = 0; $checked = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox]) {
if (& $MatchFilter $child) {
$total++
if ($child.IsChecked) { $checked++ }
}
}
}
Set-TriStatePresetCheckBoxState -CheckBox $CheckBox -Total $total -Selected $checked
}
# Find preset checkboxes via window
$window = $script:MainWindow
$presetDefaultApps = $window.FindName('PresetDefaultApps')
$presetLastUsed = $window.FindName('PresetLastUsed')
SetPresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true }
foreach ($jsonCb in $script:JsonPresetCheckboxes) {
$localIds = $jsonCb.PresetAppIds
SetPresetState $jsonCb { param($c) (@($c.AppIds) | Where-Object { $localIds -contains $_ }).Count -gt 0 }.GetNewClosure()
}
# Last used preset: only update if it's visible (has saved apps)
if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) {
SetPresetState $presetLastUsed { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 }
}
}
finally {
$script:UpdatingPresets = $false
}
}
function Scroll-ToItemIfNotVisible {
param (
[System.Windows.Controls.ScrollViewer]$ScrollViewer,
[System.Windows.UIElement]$Item,
[System.Windows.UIElement]$Container
)
if (-not $ScrollViewer -or -not $Item -or -not $Container) { return }
try {
$itemPosition = $Item.TransformToAncestor($Container).Transform([System.Windows.Point]::new(0, 0)).Y
$viewportHeight = $ScrollViewer.ViewportHeight
$itemHeight = $Item.ActualHeight
$currentOffset = $ScrollViewer.VerticalOffset
# Check if the item is currently visible in the viewport
$itemTop = $itemPosition - $currentOffset
$itemBottom = $itemTop + $itemHeight
$isVisible = ($itemTop -ge 0) -and ($itemBottom -le $viewportHeight)
# Only scroll if the item is not visible
if (-not $isVisible) {
# Center the item in the viewport
$targetOffset = $itemPosition - ($viewportHeight / 2) + ($itemHeight / 2)
$ScrollViewer.ScrollToVerticalOffset([Math]::Max(0, $targetOffset))
}
}
catch {
# Fallback to simple bring into view
$Item.BringIntoView()
}
}
function Find-ParentScrollViewer {
param ([System.Windows.UIElement]$Element)
$parent = [System.Windows.Media.VisualTreeHelper]::GetParent($Element)
while ($null -ne $parent) {
if ($parent -is [System.Windows.Controls.ScrollViewer]) {
return $parent
}
$parent = [System.Windows.Media.VisualTreeHelper]::GetParent($parent)
}
return $null
}
function Load-AppsWithList {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
[System.Windows.Controls.Border]$LoadingAppsIndicator,
[System.Windows.Controls.MenuItem]$ImportConfigBtn,
[string]$ListOfApps
)
$script:MainWindowLastSelectedCheckbox = $null
$loaderScriptPath = $script:LoadAppsDetailsScriptPath
$appsFilePath = $script:AppsListFilePath
$onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked
# Use preloaded data if available; otherwise load in background job
if (-not $onlyInstalled -and $script:PreloadedAppData) {
$rawAppData = $script:PreloadedAppData
$script:PreloadedAppData = $null
}
else {
# Load apps details in a background job to keep the UI responsive
$rawAppData = Invoke-NonBlocking -ScriptBlock {
param($loaderScript, $appsListFilePath, $installedList, $onlyInstalled)
$script:AppsListFilePath = $appsListFilePath
. $loaderScript
LoadAppsDetailsFromJson -OnlyInstalled:$onlyInstalled -InstalledList $installedList -InitialCheckedFromJson:$false
} -ArgumentList $loaderScriptPath, $appsFilePath, $ListOfApps, $onlyInstalled
}
$appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName)
$LoadingAppsIndicator.Visibility = 'Collapsed'
if ($appsToAdd.Count -eq 0) {
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $true
}
return
}
$brushSafe = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#4CAF50')
$brushUnsafe = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#F44336')
$brushDefault = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#FFC107')
$brushSafe.Freeze(); $brushUnsafe.Freeze(); $brushDefault.Freeze()
# Create WPF controls; pump the Dispatcher every batch so the spinner keeps animating.
$batchSize = 20
for ($i = 0; $i -lt $appsToAdd.Count; $i++) {
$app = $appsToAdd[$i]
$checkbox = New-Object System.Windows.Controls.CheckBox
$automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppIdDisplay) { $app.AppIdDisplay } else { $null }
if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) }
$checkbox.Tag = $app.AppIdDisplay
$checkbox.IsChecked = $app.IsChecked
$checkbox.Style = $Window.Resources['AppsPanelCheckBoxStyle']
# Build table row: Recommendation dot | Name | Description | App ID
$row = New-Object System.Windows.Controls.Grid
$row.Style = $Window.Resources['AppTableRowStyle']
$c0 = New-Object System.Windows.Controls.ColumnDefinition; $c0.Width = $Window.Resources['AppTableDotColWidth']
$c1 = New-Object System.Windows.Controls.ColumnDefinition; $c1.Width = $Window.Resources['AppTableNameColWidth']
$c2 = New-Object System.Windows.Controls.ColumnDefinition; $c2.Width = $Window.Resources['AppTableDescColWidth']
$c3 = New-Object System.Windows.Controls.ColumnDefinition; $c3.Width = $Window.Resources['AppTableIdColWidth']
$row.ColumnDefinitions.Add($c0); $row.ColumnDefinitions.Add($c1)
$row.ColumnDefinitions.Add($c2); $row.ColumnDefinitions.Add($c3)
$dot = New-Object System.Windows.Shapes.Ellipse
$dot.Style = $Window.Resources['AppRecommendationDotStyle']
$dot.Fill = switch ($app.Recommendation) { 'safe' { $brushSafe } 'unsafe' { $brushUnsafe } default { $brushDefault } }
$dot.ToolTip = switch ($app.Recommendation) {
'safe' { '[Recommended] Safe to remove for most users' }
'unsafe' { '[Not Recommended] Only remove if you know what you are doing' }
default { "[Optional] Remove if you don't need this app" }
}
[System.Windows.Controls.Grid]::SetColumn($dot, 0)
$tbName = New-Object System.Windows.Controls.TextBlock
$tbName.Text = $app.FriendlyName
$tbName.Style = $Window.Resources['AppNameTextStyle']
[System.Windows.Controls.Grid]::SetColumn($tbName, 1)
$tbDesc = New-Object System.Windows.Controls.TextBlock
$tbDesc.Text = $app.Description
$tbDesc.Style = $Window.Resources['AppDescTextStyle']
$tbDesc.ToolTip = $app.Description
[System.Windows.Controls.Grid]::SetColumn($tbDesc, 2)
$tbId = New-Object System.Windows.Controls.TextBlock
$tbId.Text = $app.AppIdDisplay
$tbId.Style = $Window.Resources["AppIdTextStyle"]
$tbId.ToolTip = $app.AppIdDisplay
[System.Windows.Controls.Grid]::SetColumn($tbId, 3)
$row.Children.Add($dot) | Out-Null
$row.Children.Add($tbName) | Out-Null
$row.Children.Add($tbDesc) | Out-Null
$row.Children.Add($tbId) | Out-Null
$checkbox.Content = $row
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($app.AppId)
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIdDisplay' -Value $app.AppIdDisplay
$checkbox.Add_Checked({
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
})
$checkbox.Add_Unchecked({
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
})
AttachShiftClickBehavior -checkbox $checkbox -appsPanel $AppsPanel `
-lastSelectedCheckboxRef ([ref]$script:MainWindowLastSelectedCheckbox) `
-updateStatusCallback {
$w = $script:MainWindow
Update-AppSelectionStatus -AppsPanel $w.FindName('AppSelectionPanel') `
-AppSelectionStatus $w.FindName('AppSelectionStatus') `
-AppRemovalScopeCombo $w.FindName('AppRemovalScopeCombo') `
-AppRemovalScopeSection $w.FindName('AppRemovalScopeSection') `
-AppRemovalScopeDescription $w.FindName('AppRemovalScopeDescription') `
-UserSelectionCombo $w.FindName('UserSelectionCombo')
}
$AppsPanel.Children.Add($checkbox) | Out-Null
if (($i + 1) % $batchSize -eq 0) { DoEvents }
}
$sortArrowName = $Window.FindName('SortArrowName')
$sortArrowDescription = $Window.FindName('SortArrowDescription')
$sortArrowAppId = $Window.FindName('SortArrowAppId')
Update-AppsPanelSort -AppsPanel $AppsPanel -SortArrowName $sortArrowName -SortArrowDescription $sortArrowDescription -SortArrowAppId $sortArrowAppId
# If Default Mode was clicked while apps were still loading, apply defaults now
if ($script:PendingDefaultMode) {
$script:PendingDefaultMode = $false
Invoke-AppPreset -AppsPanel $AppsPanel -MatchFilter { param($c) $c.SelectedByDefault -eq $true } -Exclusive
}
$appSelectionStatusText = $Window.FindName('AppSelectionStatus')
$appRemovalScopeCombo = $Window.FindName('AppRemovalScopeCombo')
$appRemovalScopeSection = $Window.FindName('AppRemovalScopeSection')
$appRemovalScopeDescription = $Window.FindName('AppRemovalScopeDescription')
$userSelectionCombo = $Window.FindName('UserSelectionCombo')
Update-AppSelectionStatus -AppsPanel $AppsPanel -AppSelectionStatus $appSelectionStatusText `
-AppRemovalScopeCombo $appRemovalScopeCombo -AppRemovalScopeSection $appRemovalScopeSection `
-AppRemovalScopeDescription $appRemovalScopeDescription -UserSelectionCombo $userSelectionCombo
# Re-enable controls now that the full, correctly-checked app list is ready
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $true
}
}
function Load-AppsIntoMainUI {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Panel]$AppsPanel,
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
[System.Windows.Controls.Border]$LoadingAppsIndicator,
[System.Windows.Controls.MenuItem]$ImportConfigBtn
)
# Prevent concurrent loads
if ($script:IsLoadingApps) { return }
$script:IsLoadingApps = $true
if ($ImportConfigBtn) {
$ImportConfigBtn.IsEnabled = $false
}
# Show loading indicator and clear existing apps
$LoadingAppsIndicator.Visibility = 'Visible'
$AppsPanel.Children.Clear()
# Disable controls while apps are loading so they can't be interacted with mid-load
$Window.FindName('DeploymentApplyBtn').IsEnabled = $false
$OnlyInstalledAppsBox.IsHitTestVisible = $false
# Update navigation buttons to disable Next/Previous
Update-NavigationButtons -Window $Window -TabControl $Window.FindName('MainTabControl')
# Force a render so the loading indicator is visible, then schedule the
# actual loading at Background priority so this call returns immediately.
# This is critical when called from Add_Loaded: the window must finish
# its initialization before we start a nested message pump via DoEvents.
$Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action] {})
$Window.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action] {
try {
$listOfApps = ""
if ($OnlyInstalledAppsBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
$listOfApps = GetInstalledAppsViaWinget -TimeOut 10 -NonBlocking
if ($null -eq $listOfApps) {
Show-MessageBox -Message 'Unable to load list of installed apps via WinGet.' -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
$OnlyInstalledAppsBox.IsChecked = $false
}
}
Load-AppsWithList -Window $Window -AppsPanel $AppsPanel -OnlyInstalledAppsBox $OnlyInstalledAppsBox `
-LoadingAppsIndicator $LoadingAppsIndicator -ImportConfigBtn $ImportConfigBtn -ListOfApps $listOfApps
}
catch {
Write-Warning "Failed to load apps list: $($_.Exception.Message)"
$LoadingAppsIndicator.Visibility = 'Collapsed'
$OnlyInstalledAppsBox.IsHitTestVisible = $true
$Window.FindName('DeploymentApplyBtn').IsEnabled = $true
if ($ImportConfigBtn) { $ImportConfigBtn.IsEnabled = $true }
}
finally {
$script:IsLoadingApps = $false
}
}) | Out-Null
}

View File

@@ -0,0 +1,487 @@
# MainWindow-Deployment.ps1
# Overview generation, pending tweak actions, feature labels, tweak preset maps, apply logic, user mode state, user selection, and validation.
function Get-FeatureLabel {
param(
[string]$FeatureId,
$FallbackLabel = $null
)
$label = $script:FeatureLabelLookup[$FeatureId]
if (-not [string]::IsNullOrWhiteSpace([string]$label)) {
return [string]$label
}
if (-not [string]::IsNullOrWhiteSpace([string]$FallbackLabel)) {
return [string]$FallbackLabel
}
return [string]$FeatureId
}
function Get-UndoFeatureLabel {
param(
[string]$FeatureId,
$FallbackLabel = $null
)
$undoLabel = $script:UndoFeatureLabelLookup[$FeatureId]
if (-not [string]::IsNullOrWhiteSpace([string]$undoLabel)) {
return [string]$undoLabel
}
# Fall back to the regular label (prefixed for undo context)
$label = Get-FeatureLabel -FeatureId $FeatureId -FallbackLabel $FallbackLabel
return [string]$label
}
function Get-PendingTweakActions {
param(
[System.Windows.Window]$Window,
[bool]$ShowAppliedTweaksMode
)
$actions = New-Object System.Collections.Generic.List[object]
if (-not $script:UiControlMappings) {
return @($actions.ToArray())
}
foreach ($mappingKey in $script:UiControlMappings.Keys) {
$control = $Window.FindName($mappingKey)
if (-not $control) { continue }
$mapping = $script:UiControlMappings[$mappingKey]
if ($control -is [System.Windows.Controls.CheckBox] -and $mapping.Type -eq 'feature') {
$wasApplied = $false
if ($ShowAppliedTweaksMode -and $null -ne $control.PSObject.Properties['SystemState']) {
$wasApplied = [bool]$control.SystemState
}
elseif ($null -ne $control.PSObject.Properties['InitialState']) {
$wasApplied = [bool]$control.InitialState
}
elseif ($null -ne $control.PSObject.Properties['SystemState']) {
$wasApplied = [bool]$control.SystemState
}
$isNowChecked = $control.IsChecked -eq $true
if (-not $wasApplied -and $isNowChecked) {
$actions.Add([PSCustomObject]@{
Action = 'Apply'
FeatureId = [string]$mapping.FeatureId
Label = (Get-FeatureLabel -FeatureId $mapping.FeatureId -FallbackLabel $mapping.Label)
})
}
elseif ($wasApplied -and -not $isNowChecked) {
$actions.Add([PSCustomObject]@{
Action = 'Undo'
FeatureId = [string]$mapping.FeatureId
Label = (Get-FeatureLabel -FeatureId $mapping.FeatureId -FallbackLabel $mapping.Label)
})
}
}
elseif ($control -is [System.Windows.Controls.ComboBox] -and $mapping.Type -eq 'group') {
$wasIndex = 0
if ($ShowAppliedTweaksMode -and $null -ne $control.PSObject.Properties['SystemIndex']) {
$wasIndex = [int]$control.SystemIndex
}
elseif ($null -ne $control.PSObject.Properties['InitialIndex']) {
$wasIndex = [int]$control.InitialIndex
}
elseif ($null -ne $control.PSObject.Properties['SystemIndex']) {
$wasIndex = [int]$control.SystemIndex
}
$isNowIndex = $control.SelectedIndex
if ($wasIndex -eq $isNowIndex) { continue }
if ($isNowIndex -gt 0 -and $isNowIndex -le $mapping.Values.Count) {
$selectedValue = $mapping.Values[$isNowIndex - 1]
foreach ($fid in $selectedValue.FeatureIds) {
$actions.Add([PSCustomObject]@{
Action = 'Apply'
FeatureId = [string]$fid
Label = (Get-FeatureLabel -FeatureId $fid)
})
}
}
}
}
return @($actions.ToArray())
}
function New-Overview {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.StackPanel]$AppsPanel,
$ShowCurrentlyAppliedTweaksCheckBox
)
$changesList = @()
$showAppliedTweaksMode = ($ShowCurrentlyAppliedTweaksCheckBox -and $ShowCurrentlyAppliedTweaksCheckBox.IsChecked -eq $true)
# Collect selected apps
$selectedAppsCount = 0
foreach ($child in $AppsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$selectedAppsCount++
}
}
if ($selectedAppsCount -gt 0) {
$changesList += "Remove $selectedAppsCount application(s)"
}
foreach ($tweakAction in @(Get-PendingTweakActions -Window $Window -ShowAppliedTweaksMode:$showAppliedTweaksMode)) {
if ($tweakAction.Action -eq 'Undo') {
$changesList += "Undo: $($tweakAction.Label)"
}
else {
$changesList += $tweakAction.Label
}
}
return $changesList
}
function Invoke-ShowChangesOverview {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.StackPanel]$AppsPanel,
$ShowCurrentlyAppliedTweaksCheckBox
)
$changesList = New-Overview -Window $Window -AppsPanel $AppsPanel -ShowCurrentlyAppliedTweaksCheckBox $ShowCurrentlyAppliedTweaksCheckBox
if ($changesList.Count -eq 0) {
Show-MessageBox -Message 'No changes have been selected.' -Title 'Selected Changes' -Button 'OK' -Icon 'Information'
return
}
$message = ($changesList | ForEach-Object { "$([char]0x2022) $_" }) -join "`n"
Show-MessageBox -Message $message -Title 'Selected Changes' -Button 'OK' -Icon 'None' -Width 600
}
function Build-TweakPresetControlMap {
param(
[System.Windows.Window]$Window,
$SettingsJson
)
$presetMap = @{}
if (-not $SettingsJson -or -not $SettingsJson.Settings -or -not $script:UiControlMappings) {
return $presetMap
}
# FeatureId -> control metadata, similar to ApplySettingsToUiControls lookup.
$featureIdIndex = @{}
foreach ($controlName in $script:UiControlMappings.Keys) {
$control = $Window.FindName($controlName)
if (-not $control -or $control.Visibility -ne 'Visible') { continue }
$mapping = $script:UiControlMappings[$controlName]
if ($mapping.Type -eq 'group') {
$i = 1
foreach ($val in $mapping.Values) {
foreach ($fid in $val.FeatureIds) {
$featureIdIndex[$fid] = @{ ControlName = $controlName; Control = $control; MappingType = 'group'; Index = $i }
}
$i++
}
}
elseif ($mapping.Type -eq 'feature') {
$featureIdIndex[$mapping.FeatureId] = @{ ControlName = $controlName; Control = $control; MappingType = 'feature' }
}
}
foreach ($setting in $SettingsJson.Settings) {
if ($setting.Value -ne $true) { continue }
if ($setting.Name -eq 'CreateRestorePoint') { continue }
$entry = $featureIdIndex[$setting.Name]
if (-not $entry) { continue }
if ($presetMap.ContainsKey($entry.ControlName)) { continue }
$controlType = if ($entry.Control -is [System.Windows.Controls.CheckBox]) { 'CheckBox' } else { 'ComboBox' }
$desiredValue = switch ($entry.MappingType) {
'group' { $entry.Index }
default { if ($controlType -eq 'CheckBox') { $true } else { 1 } }
}
$presetMap[$entry.ControlName] = @{ Control = $entry.Control; ControlType = $controlType; DesiredValue = $desiredValue }
}
return $presetMap
}
function Build-CategoryTweakPresetMap {
param(
[System.Windows.Window]$Window,
[string]$Category
)
$presetMap = @{}
if (-not $script:UiControlMappings) { return $presetMap }
foreach ($controlName in $script:UiControlMappings.Keys) {
$mapping = $script:UiControlMappings[$controlName]
if ($mapping.Category -ne $Category) { continue }
$control = $Window.FindName($controlName)
if (-not $control -or $control.Visibility -ne 'Visible') { continue }
$controlType = if ($control -is [System.Windows.Controls.CheckBox]) { 'CheckBox' } else { 'ComboBox' }
$desiredValue = if ($controlType -eq 'CheckBox') { $true } else { 1 }
$presetMap[$controlName] = @{ Control = $control; ControlType = $controlType; DesiredValue = $desiredValue }
}
return $presetMap
}
function Get-SavedAppIdsFromSettingsJson {
param($SettingsJson)
if (-not $SettingsJson -or -not $SettingsJson.Settings) {
return $null
}
$appsValue = $null
foreach ($setting in $SettingsJson.Settings) {
if ($setting.Name -eq 'Apps' -and $setting.Value) {
$appsValue = $setting.Value
break
}
}
if (-not $appsValue) {
return $null
}
$savedAppIds = @()
if ($appsValue -is [string]) {
$savedAppIds = $appsValue.Split(',')
}
elseif ($appsValue -is [array]) {
$savedAppIds = $appsValue
}
$savedAppIds = $savedAppIds | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }
if ($savedAppIds.Count -eq 0) {
return $null
}
return $savedAppIds
}
function Invoke-ApplyTweakPresetMap {
param(
[hashtable]$PresetMap,
[bool]$Check
)
if (-not $PresetMap) {
$PresetMap = @{}
}
$wasUpdatingTweakPresets = [bool]$script:UpdatingTweakPresets
$script:UpdatingTweakPresets = $true
try {
foreach ($target in $PresetMap.Values) {
$control = $target.Control
if (-not $control) { continue }
if ($target.ControlType -eq 'CheckBox') {
$control.IsChecked = $Check
}
elseif ($target.ControlType -eq 'ComboBox') {
$desiredIndex = [int]$target.DesiredValue
if ($Check) {
$control.SelectedIndex = $desiredIndex
}
elseif ($control.SelectedIndex -eq $desiredIndex) {
$control.SelectedIndex = 0
}
}
}
}
finally {
$script:UpdatingTweakPresets = $wasUpdatingTweakPresets
}
if (-not $wasUpdatingTweakPresets) {
Update-TweakPresetStates -Window $script:MainWindow
}
}
function Set-TweakPresetCheckBoxState {
param(
[System.Windows.Controls.CheckBox]$PresetCheckBox,
[hashtable]$PresetMap
)
if (-not $PresetCheckBox) { return }
if (-not $PresetMap) {
$PresetMap = @{}
}
$total = $PresetMap.Count
$selected = 0
foreach ($target in $PresetMap.Values) {
$control = $target.Control
if (-not $control) { continue }
if ($target.ControlType -eq 'CheckBox' -and $control.IsChecked -eq $true) {
$selected++
}
elseif ($target.ControlType -eq 'ComboBox' -and $control.SelectedIndex -eq [int]$target.DesiredValue) {
$selected++
}
}
Set-TriStatePresetCheckBoxState -CheckBox $PresetCheckBox -Total $total -Selected $selected
}
function Update-TweakPresetStates {
param([System.Windows.Window]$Window)
$script:UpdatingTweakPresets = $true
try {
$presetDefaultTweaksBtn = $Window.FindName('PresetDefaultTweaksBtn')
$presetLastUsedTweaksBtn = $Window.FindName('PresetLastUsedTweaksBtn')
$presetPrivacyTweaksBtn = $Window.FindName('PresetPrivacyTweaksBtn')
$presetAITweaksBtn = $Window.FindName('PresetAITweaksBtn')
Set-TweakPresetCheckBoxState -PresetCheckBox $presetDefaultTweaksBtn -PresetMap $script:DefaultTweakPresetMap
if ($presetLastUsedTweaksBtn -and $presetLastUsedTweaksBtn.Visibility -ne 'Collapsed') {
Set-TweakPresetCheckBoxState -PresetCheckBox $presetLastUsedTweaksBtn -PresetMap $script:LastUsedTweakPresetMap
}
Set-TweakPresetCheckBoxState -PresetCheckBox $presetPrivacyTweaksBtn -PresetMap $script:PrivacyTweakPresetMap
Set-TweakPresetCheckBoxState -PresetCheckBox $presetAITweaksBtn -PresetMap $script:AITweakPresetMap
}
finally {
$script:UpdatingTweakPresets = $false
}
}
function Register-TweakPresetControlStateHandlers {
param([System.Windows.Window]$Window)
if (-not $script:UiControlMappings) { return }
foreach ($controlName in $script:UiControlMappings.Keys) {
$control = $Window.FindName($controlName)
if (-not $control) { continue }
if ($control -is [System.Windows.Controls.CheckBox]) {
$control.Add_Checked({ if (-not $script:UpdatingTweakPresets) { Update-TweakPresetStates -Window $script:MainWindow } })
$control.Add_Unchecked({ if (-not $script:UpdatingTweakPresets) { Update-TweakPresetStates -Window $script:MainWindow } })
}
elseif ($control -is [System.Windows.Controls.ComboBox]) {
$control.Add_SelectionChanged({ if (-not $script:UpdatingTweakPresets) { Update-TweakPresetStates -Window $script:MainWindow } })
}
}
}
function Initialize-TweakPresetSources {
param(
[System.Windows.Window]$Window,
$DefaultSettingsJson,
$LastUsedSettingsJson
)
$script:DefaultTweakPresetMap = Build-TweakPresetControlMap -Window $Window -SettingsJson $DefaultSettingsJson
$script:LastUsedTweakPresetMap = Build-TweakPresetControlMap -Window $Window -SettingsJson $LastUsedSettingsJson
$script:PrivacyTweakPresetMap = Build-CategoryTweakPresetMap -Window $Window -Category 'Privacy & Suggested Content'
$script:AITweakPresetMap = Build-CategoryTweakPresetMap -Window $Window -Category 'AI'
$presetLastUsedTweaksBtn = $Window.FindName('PresetLastUsedTweaksBtn')
if ($presetLastUsedTweaksBtn) {
$presetLastUsedTweaksBtn.Visibility = if ($script:LastUsedTweakPresetMap.Count -gt 0) { 'Visible' } else { 'Collapsed' }
}
}
function Update-AppliedTweaksUserModeState {
param(
[System.Windows.Controls.CheckBox]$ShowCurrentlyAppliedTweaksCheckBox,
[System.Windows.Controls.ComboBox]$UserSelectionCombo
)
# Show/hide detect applied tweaks checkbox based on user mode
if ($ShowCurrentlyAppliedTweaksCheckBox) {
if ($UserSelectionCombo.SelectedIndex -eq 0) {
$ShowCurrentlyAppliedTweaksCheckBox.Visibility = 'Visible'
}
else {
$ShowCurrentlyAppliedTweaksCheckBox.Visibility = 'Collapsed'
}
}
# Enable/disable user mode combo based on params only (not checkbox)
if ($script:Params.ContainsKey('Sysprep') -or $script:Params.ContainsKey('User')) {
$UserSelectionCombo.IsEnabled = $false
}
else {
$UserSelectionCombo.IsEnabled = $true
}
}
function Update-UserSelectionDescription {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
[System.Windows.Controls.TextBox]$OtherUsernameTextBox,
[System.Windows.Controls.TextBlock]$UserSelectionDescription
)
switch ($UserSelectionCombo.SelectedIndex) {
0 {
$currentUserName = GetUserName
if ([string]::IsNullOrWhiteSpace($currentUserName)) {
$UserSelectionDescription.Text = "The currently logged-in user profile"
}
else {
$UserSelectionDescription.Text = "The currently logged-in user profile: $currentUserName"
}
}
1 {
$targetUserName = $OtherUsernameTextBox.Text.Trim()
if ([string]::IsNullOrWhiteSpace($targetUserName)) {
$UserSelectionDescription.Text = "A different user profile on this system"
}
else {
$UserSelectionDescription.Text = "A different user profile on this system: $targetUserName"
}
}
default {
$UserSelectionDescription.Text = "The default user template, affecting all new users created after this point. Useful for Sysprep deployment."
}
}
}
function Test-OtherUsername {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
[System.Windows.Controls.TextBox]$OtherUsernameTextBox,
[System.Windows.Controls.TextBlock]$UsernameValidationMessage
)
# Only validate if "Other User" is selected
if ($UserSelectionCombo.SelectedIndex -ne 1) {
return $true
}
$errorBrush = $Window.Resources['ValidationErrorColor']
$successBrush = $Window.Resources['ValidationSuccessColor']
$validationResult = Test-TargetUserName -UserName $OtherUsernameTextBox.Text
$UsernameValidationMessage.Text = $validationResult.Message
if ($validationResult.IsValid) {
$UsernameValidationMessage.Foreground = $successBrush
return $true
}
$UsernameValidationMessage.Foreground = $errorBrush
return $false
}

View File

@@ -0,0 +1,72 @@
# MainWindow-Navigation.ps1
# Wizard navigation helpers: tab navigation buttons and progress indicators.
function Update-NavigationButtons {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.TabControl]$TabControl
)
$currentIndex = $TabControl.SelectedIndex
$totalTabs = $TabControl.Items.Count
$previousBtn = $Window.FindName('PreviousBtn')
$nextBtn = $Window.FindName('NextBtn')
$homeIndex = 0
$overviewIndex = $totalTabs - 1
# Navigation button visibility
if ($currentIndex -eq $homeIndex) {
$nextBtn.Visibility = 'Collapsed'
$previousBtn.Visibility = 'Collapsed'
}
elseif ($currentIndex -eq $overviewIndex) {
$nextBtn.Visibility = 'Collapsed'
$previousBtn.Visibility = 'Visible'
}
else {
$nextBtn.Visibility = 'Visible'
$previousBtn.Visibility = 'Visible'
}
# Update progress indicators
# Tab indices: 0=Home, 1=App Removal, 2=Tweaks, 3=Deployment Settings
$progressIndicator1 = $Window.FindName('ProgressIndicator1') # App Removal
$progressIndicator2 = $Window.FindName('ProgressIndicator2') # Tweaks
$progressIndicator3 = $Window.FindName('ProgressIndicator3') # Deployment Settings
$bottomNavGrid = $Window.FindName('BottomNavGrid')
# Hide bottom navigation on home page
if ($currentIndex -eq 0) {
$bottomNavGrid.Visibility = 'Collapsed'
}
else {
$bottomNavGrid.Visibility = 'Visible'
}
# Update indicator colors based on current tab
# Indicator 1 (App Removal) - tab index 1
if ($currentIndex -ge 1) {
$progressIndicator1.Fill = $Window.Resources['ProgressActiveColor']
}
else {
$progressIndicator1.Fill = $Window.Resources['ProgressInactiveColor']
}
# Indicator 2 (Tweaks) - tab index 2
if ($currentIndex -ge 2) {
$progressIndicator2.Fill = $Window.Resources['ProgressActiveColor']
}
else {
$progressIndicator2.Fill = $Window.Resources['ProgressInactiveColor']
}
# Indicator 3 (Deployment Settings) - tab index 3
if ($currentIndex -ge 3) {
$progressIndicator3.Fill = $Window.Resources['ProgressActiveColor']
}
else {
$progressIndicator3.Fill = $Window.Resources['ProgressInactiveColor']
}
}

View File

@@ -0,0 +1,511 @@
# MainWindow-TweaksBuilder.ps1
# Dynamic tweaks UI construction from Features.json, tweak state management, selection clear, and search/highlight.
function Build-DynamicTweaks {
param(
[System.Windows.Window]$Window,
[int]$WinVersion
)
$featuresJson = LoadJsonFile -filePath $script:FeaturesFilePath -expectedVersion "1.0"
if (-not $featuresJson) {
throw "Unable to load Features.json file. The GUI cannot continue without feature definitions."
}
# Column containers
$col0 = $Window.FindName('Column0Panel')
$col1 = $Window.FindName('Column1Panel')
$col2 = $Window.FindName('Column2Panel')
$columns = @($col0, $col1, $col2) | Where-Object { $_ -ne $null }
# Clear all columns for fully dynamic panel creation
foreach ($col in $columns) {
if ($col) { $col.Children.Clear() }
}
$script:UiControlMappings = @{}
$script:CategoryCardMap = @{}
$script:TweaksCompactMode = $null
$script:TweaksCardsMovedFromCol2 = @()
function CreateLabeledCombo($parent, $labelText, $comboName, $items) {
# If only 2 items (No Change + one option), use a checkbox instead
if ($items.Count -eq 2) {
$checkbox = New-Object System.Windows.Controls.CheckBox
$checkbox.Content = $labelText
$checkbox.Name = $comboName
$checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $labelText)
$checkbox.IsChecked = $false
$checkbox.Style = $Window.Resources["FeatureCheckboxStyle"]
$parent.Children.Add($checkbox) | Out-Null
# Register the checkbox with the window's name scope
try {
[System.Windows.NameScope]::SetNameScope($checkbox, [System.Windows.NameScope]::GetNameScope($Window))
$Window.RegisterName($comboName, $checkbox)
}
catch {
# Name might already be registered, ignore
}
return $checkbox
}
# Otherwise use a combobox for multiple options
# Wrap label in a Border for search highlighting
$lblBorder = New-Object System.Windows.Controls.Border
$lblBorder.Style = $Window.Resources['LabelBorderStyle']
$lblBorderName = "$comboName`_LabelBorder"
$lblBorder.Name = $lblBorderName
$lbl = New-Object System.Windows.Controls.TextBlock
$lbl.Text = $labelText
$lbl.Style = $Window.Resources['LabelStyle']
$labelName = "$comboName`_Label"
$lbl.Name = $labelName
$lblBorder.Child = $lbl
$parent.Children.Add($lblBorder) | Out-Null
# Register the label border with the window's name scope
try {
[System.Windows.NameScope]::SetNameScope($lblBorder, [System.Windows.NameScope]::GetNameScope($Window))
$Window.RegisterName($lblBorderName, $lblBorder)
}
catch {
# Name might already be registered, ignore
}
$combo = New-Object System.Windows.Controls.ComboBox
$combo.Name = $comboName
$combo.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $labelText)
foreach ($item in $items) { $comboItem = New-Object System.Windows.Controls.ComboBoxItem; $comboItem.Content = $item; $combo.Items.Add($comboItem) | Out-Null }
$combo.SelectedIndex = 0
$parent.Children.Add($combo) | Out-Null
# Register the combo box with the window's name scope
try {
[System.Windows.NameScope]::SetNameScope($combo, [System.Windows.NameScope]::GetNameScope($Window))
$Window.RegisterName($comboName, $combo)
}
catch {
# Name might already be registered, ignore
}
return $combo
}
function GetWikiUrlForCategory($category) {
if (-not $category) { return 'https://github.com/Raphire/Win11Debloat/wiki/Features' }
$slug = $category.ToLowerInvariant()
$slug = $slug -replace '&', ''
$slug = $slug -replace '[^a-z0-9\s-]', ''
$slug = $slug -replace '\s', '-'
return "https://github.com/Raphire/Win11Debloat/wiki/Features#$slug"
}
function GetOrCreateCategoryCard($categoryObj) {
$categoryName = $categoryObj.Name
$categoryIcon = $categoryObj.Icon
if ($script:CategoryCardMap.ContainsKey($categoryName)) { return $script:CategoryCardMap[$categoryName] }
# Create a new card Border + StackPanel and add to shortest column
$target = $columns | Sort-Object @{Expression = { $_.Children.Count }; Ascending = $true }, @{Expression = { $columns.IndexOf($_) }; Ascending = $true } | Select-Object -First 1
$border = New-Object System.Windows.Controls.Border
$border.Style = $Window.Resources['CategoryCardBorderStyle']
$border.Tag = 'DynamicCategory'
$panel = New-Object System.Windows.Controls.StackPanel
$safe = ($categoryName -replace '[^a-zA-Z0-9_]', '_')
$panel.Name = "Category_{0}_Panel" -f $safe
$headerRow = New-Object System.Windows.Controls.StackPanel
$headerRow.Orientation = 'Horizontal'
# Add category icon
$icon = New-Object System.Windows.Controls.TextBlock
# Convert HTML entity to character (e.g., &#xE72E; -> actual character)
if ($categoryIcon -match '&#x([0-9A-Fa-f]+);') {
$hexValue = [Convert]::ToInt32($matches[1], 16)
$icon.Text = [char]$hexValue
}
$icon.Style = $Window.Resources['CategoryHeaderIcon']
$headerRow.Children.Add($icon) | Out-Null
$header = New-Object System.Windows.Controls.TextBlock
$header.Text = $categoryName
$header.Style = $Window.Resources['CategoryHeaderTextBlock']
$headerRow.Children.Add($header) | Out-Null
$helpIcon = New-Object System.Windows.Controls.TextBlock
$helpIcon.Text = '(?)'
$helpIcon.Style = $Window.Resources['CategoryHelpLinkTextStyle']
$helpBtn = New-Object System.Windows.Controls.Button
$helpBtn.Content = $helpIcon
$helpBtn.ToolTip = "Open wiki for more info on '$categoryName' tweaks"
$helpBtn.Tag = (GetWikiUrlForCategory -category $categoryName)
$helpBtn.Style = $Window.Resources['CategoryHelpLinkButtonStyle']
$helpBtn.Add_Click({
param($button, $e)
if ($button.Tag) { Start-Process $button.Tag }
})
$headerRow.Children.Add($helpBtn) | Out-Null
$panel.Children.Add($headerRow) | Out-Null
$border.Child = $panel
$target.Children.Add($border) | Out-Null
$script:CategoryCardMap[$categoryName] = $panel
return $panel
}
# Determine categories present (from lists and features)
$categoriesPresent = @{}
if ($featuresJson.UiGroups) {
foreach ($g in $featuresJson.UiGroups) { if ($g.Category) { $categoriesPresent[$g.Category] = $true } }
}
foreach ($f in $featuresJson.Features) { if ($f.Category) { $categoriesPresent[$f.Category] = $true } }
# Create cards in the order defined in Features.json Categories (if present)
$orderedCategories = @()
if ($featuresJson.Categories) {
foreach ($c in $featuresJson.Categories) {
$categoryName = if ($c -is [string]) { $c } else { $c.Name }
if ($categoriesPresent.ContainsKey($categoryName)) {
# Store the full category object (or create one with default icon for string categories)
$categoryObj = if ($c -is [string]) { @{Name = $c; Icon = '&#xE712;' } } else { $c }
$orderedCategories += $categoryObj
}
}
}
else {
# For backward compatibility, create category objects from keys
foreach ($catName in $categoriesPresent.Keys) {
$orderedCategories += @{Name = $catName; Icon = '&#xE712;' }
}
}
foreach ($categoryObj in $orderedCategories) {
$categoryName = $categoryObj.Name
# Create/get card for this category
$panel = GetOrCreateCategoryCard -categoryObj $categoryObj
if (-not $panel) { continue }
# Collect groups and features for this category, then sort by priority
$categoryItems = @()
# Add any groups for this category
if ($featuresJson.UiGroups) {
$groupIndex = 0
foreach ($group in $featuresJson.UiGroups) {
if ($group.Category -ne $categoryName) { $groupIndex++; continue }
$categoryItems += [PSCustomObject]@{
Type = 'group'
Data = $group
Priority = if ($null -ne $group.Priority) { $group.Priority } else { [int]::MaxValue }
OriginalIndex = $groupIndex
}
$groupIndex++
}
}
# Add individual features for this category
$featureIndex = 0
foreach ($feature in $featuresJson.Features) {
if ($feature.Category -ne $categoryName) { $featureIndex++; continue }
# Check version and feature compatibility using Features.json
if (($feature.MinVersion -and $WinVersion -lt $feature.MinVersion) -or ($feature.MaxVersion -and $WinVersion -gt $feature.MaxVersion) -or ($feature.FeatureId -eq 'DisableModernStandbyNetworking' -and (-not $script:ModernStandbySupported))) {
$featureIndex++; continue
}
# Skip if feature part of a group
$inGroup = $false
if ($featuresJson.UiGroups) {
foreach ($g in $featuresJson.UiGroups) { foreach ($val in $g.Values) { if ($val.FeatureIds -contains $feature.FeatureId) { $inGroup = $true; break } }; if ($inGroup) { break } }
}
if ($inGroup) { $featureIndex++; continue }
$categoryItems += [PSCustomObject]@{
Type = 'feature'
Data = $feature
Priority = if ($null -ne $feature.Priority) { $feature.Priority } else { [int]::MaxValue }
OriginalIndex = $featureIndex
}
$featureIndex++
}
# Sort by priority first, then by original index for items with same/no priority
$sortedItems = $categoryItems | Sort-Object -Property Priority, OriginalIndex
# Render sorted items
foreach ($item in $sortedItems) {
if ($item.Type -eq 'group') {
$group = $item.Data
$items = @('No Change') + ($group.Values | ForEach-Object { $_.Label })
$comboName = 'Group_{0}Combo' -f $group.GroupId
$combo = CreateLabeledCombo -parent $panel -labelText $group.Label -comboName $comboName -items $items
# attach tooltip from UiGroups if present
if ($group.ToolTip) {
$tipBlock = New-Object System.Windows.Controls.TextBlock
$tipBlock.Text = $group.ToolTip
$tipBlock.TextWrapping = 'Wrap'
$tipBlock.MaxWidth = 420
$combo.ToolTip = $tipBlock
$lblBorderObj = $null
try { $lblBorderObj = $Window.FindName("$comboName`_LabelBorder") } catch {}
if ($lblBorderObj) { $lblBorderObj.ToolTip = $tipBlock }
}
$script:UiControlMappings[$comboName] = @{ Type = 'group'; Values = $group.Values; Label = $group.Label; Category = $categoryName }
}
elseif ($item.Type -eq 'feature') {
$feature = $item.Data
$opt = 'Apply'
if ($feature.FeatureId -match '^Disable') { $opt = 'Disable' } elseif ($feature.FeatureId -match '^Enable') { $opt = 'Enable' }
$items = @('No Change', $opt)
$comboName = ("Feature_{0}_Combo" -f $feature.FeatureId) -replace '[^a-zA-Z0-9_]', ''
$combo = CreateLabeledCombo -parent $panel -labelText $feature.Label -comboName $comboName -items $items
# attach tooltip from Features.json if present, and include the disabled-state reason
if ($feature.ToolTip -or $feature.DisableWhenApplied -eq $true) {
$tooltipText = $feature.ToolTip
if ($feature.DisableWhenApplied -eq $true) {
$tooltipText = "This tweak is already applied and cannot be undone automatically. Visit the Win11Debloat wiki for instructions on how to manually revert this change."
}
$tipBlock = New-Object System.Windows.Controls.TextBlock
$tipBlock.Text = $tooltipText
$tipBlock.TextWrapping = 'Wrap'
$tipBlock.MaxWidth = 420
$combo.ToolTip = $tipBlock
[System.Windows.Controls.ToolTipService]::SetShowOnDisabled($combo, $true)
$lblBorderObj = $null
try { $lblBorderObj = $Window.FindName("$comboName`_LabelBorder") } catch {}
if ($lblBorderObj) { $lblBorderObj.ToolTip = $tipBlock }
}
$script:UiControlMappings[$comboName] = @{ Type = 'feature'; FeatureId = $feature.FeatureId; Label = $feature.Label; Category = $categoryName }
}
}
}
# Build a feature-label lookup so GenerateOverview can resolve feature IDs without reloading JSON
$script:FeatureLabelLookup = @{}
$script:UndoFeatureLabelLookup = @{}
foreach ($f in $featuresJson.Features) {
$script:FeatureLabelLookup[$f.FeatureId] = $f.Label
$script:UndoFeatureLabelLookup[$f.FeatureId] = $f.UndoLabel
}
}
function Update-CurrentTweakSystemState {
param(
[System.Windows.Window]$Window,
[bool]$ApplyToUi
)
if (-not $script:UiControlMappings) { return }
if (-not $script:Features) { return }
$featuresJson = LoadJsonFile -filePath $script:FeaturesFilePath -expectedVersion "1.0"
if (-not $featuresJson) { return }
$groupMap = @{}
if ($featuresJson.UiGroups) {
foreach ($g in $featuresJson.UiGroups) {
$groupMap[$g.GroupId] = $g
}
}
foreach ($controlName in $script:UiControlMappings.Keys) {
$control = $Window.FindName($controlName)
if (-not $control) { continue }
$mapping = $script:UiControlMappings[$controlName]
if ($control -is [System.Windows.Controls.CheckBox] -and $mapping.Type -eq 'feature') {
$applied = $false
try { $applied = [bool](Test-FeatureApplied -FeatureId $mapping.FeatureId) } catch {}
$featureObj = $script:Features[$mapping.FeatureId]
$disableWhenApplied = $featureObj -and $featureObj.DisableWhenApplied -eq $true
Add-Member -InputObject $control -MemberType NoteProperty -Name 'SystemState' -Value $applied -Force
Add-Member -InputObject $control -MemberType NoteProperty -Name 'DisableWhenApplied' -Value $disableWhenApplied -Force
if ($ApplyToUi) {
$control.IsChecked = $applied
$control.IsEnabled = -not ($applied -and $disableWhenApplied)
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialState' -Value $applied -Force
}
}
elseif ($control -is [System.Windows.Controls.ComboBox] -and $mapping.Type -eq 'group') {
$groupId = $null
if ($controlName -match '^Group_(.+)Combo$') { $groupId = $matches[1] }
$activeIndex = 0
if ($groupId -and $groupMap.ContainsKey($groupId)) {
try { $activeIndex = Get-CurrentGroupActiveIndex -Group $groupMap[$groupId] } catch {}
}
Add-Member -InputObject $control -MemberType NoteProperty -Name 'SystemIndex' -Value $activeIndex -Force
if ($ApplyToUi) {
$control.SelectedIndex = $activeIndex
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialIndex' -Value $activeIndex -Force
}
}
}
}
function Load-CurrentTweakStateIntoUI {
param([System.Windows.Window]$Window)
Update-CurrentTweakSystemState -Window $Window -ApplyToUi:$true
}
function Reset-TweaksToSystemState {
param(
[System.Windows.Window]$Window,
[bool]$LoadSystemState
)
if (-not $script:UiControlMappings) { return }
foreach ($controlName in $script:UiControlMappings.Keys) {
$control = $Window.FindName($controlName)
if (-not $control) { continue }
if ($control -is [System.Windows.Controls.CheckBox]) {
if ($LoadSystemState) {
# Set checkbox to the currently applied state from registry
$applied = if ($null -ne $control.PSObject.Properties['SystemState']) { [bool]$control.SystemState } else { $false }
$disableWhenApplied = $null -ne $control.PSObject.Properties['DisableWhenApplied'] -and [bool]$control.DisableWhenApplied
$control.IsChecked = $applied
$control.IsEnabled = -not ($applied -and $disableWhenApplied)
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialState' -Value $applied -Force
}
else {
# Clear the checkbox
$control.IsChecked = $false
$control.IsEnabled = $true
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialState' -Value $false -Force
}
}
elseif ($control -is [System.Windows.Controls.ComboBox]) {
if ($LoadSystemState) {
# Set combobox to the currently applied state from registry
$idx = if ($null -ne $control.PSObject.Properties['SystemIndex']) { [int]$control.SystemIndex } else { 0 }
$control.SelectedIndex = $idx
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialIndex' -Value $idx -Force
}
else {
# Reset to first item (No Change)
$control.SelectedIndex = 0
Add-Member -InputObject $control -MemberType NoteProperty -Name 'InitialIndex' -Value 0 -Force
}
}
}
}
function Update-TweaksResponsiveColumns {
param([System.Windows.Window]$Window)
$tweaksGrid = $Window.FindName('TweaksGrid')
$col0 = $Window.FindName('Column0Panel')
$col1 = $Window.FindName('Column1Panel')
$col2 = $Window.FindName('Column2Panel')
if (-not $tweaksGrid -or -not $col0 -or -not $col1 -or -not $col2) { return }
if ($tweaksGrid.ColumnDefinitions.Count -lt 3) { return }
if ($null -eq $script:TweaksCardsMovedFromCol2) { $script:TweaksCardsMovedFromCol2 = @() }
$useTwoColumns = $Window.ActualWidth -lt 1200
if ($script:TweaksCompactMode -eq $useTwoColumns) { return }
$script:TweaksCompactMode = $useTwoColumns
if ($useTwoColumns) {
$tweaksGrid.ColumnDefinitions[0].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
$tweaksGrid.ColumnDefinitions[1].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
$tweaksGrid.ColumnDefinitions[2].Width = [System.Windows.GridLength]::new(0)
$col2.Visibility = 'Collapsed'
# Move third-column cards once when entering compact mode.
$cardsToMove = @($col2.Children) | Where-Object { $_ -is [System.Windows.UIElement] }
$script:TweaksCardsMovedFromCol2 = @($cardsToMove)
$col2.Children.Clear()
$targetColumns = @($col0, $col1)
foreach ($card in $cardsToMove) {
$target = $targetColumns |
Sort-Object @{Expression = { $_.Children.Count }; Ascending = $true }, @{Expression = { $targetColumns.IndexOf($_) }; Ascending = $true } |
Select-Object -First 1
$target.Children.Add($card) | Out-Null
}
return
}
$tweaksGrid.ColumnDefinitions[0].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
$tweaksGrid.ColumnDefinitions[1].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
$tweaksGrid.ColumnDefinitions[2].Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
$col2.Visibility = 'Visible'
foreach ($card in (@($script:TweaksCardsMovedFromCol2) | Where-Object { $_ -is [System.Windows.UIElement] })) {
if ($col0.Children.Contains($card)) {
$col0.Children.Remove($card) | Out-Null
}
elseif ($col1.Children.Contains($card)) {
$col1.Children.Remove($card) | Out-Null
}
$col2.Children.Add($card) | Out-Null
}
$script:TweaksCardsMovedFromCol2 = @()
}
function Clear-TweakSelections {
param([System.Windows.Window]$Window)
if (-not $script:UiControlMappings) { return }
foreach ($controlName in $script:UiControlMappings.Keys) {
$control = $Window.FindName($controlName)
if ($control -is [System.Windows.Controls.CheckBox]) {
$control.IsChecked = $false
$control.IsEnabled = $true
}
elseif ($control -is [System.Windows.Controls.ComboBox]) {
$control.SelectedIndex = 0
}
}
}
function Clear-TweakHighlights {
param([System.Windows.Window]$Window)
$col0 = $Window.FindName('Column0Panel')
$col1 = $Window.FindName('Column1Panel')
$col2 = $Window.FindName('Column2Panel')
$columns = @($col0, $col1, $col2) | Where-Object { $_ -ne $null }
foreach ($column in $columns) {
foreach ($card in $column.Children) {
if ($card -is [System.Windows.Controls.Border] -and $card.Child -is [System.Windows.Controls.StackPanel]) {
foreach ($control in $card.Child.Children) {
if ($control -is [System.Windows.Controls.CheckBox] -or
($control -is [System.Windows.Controls.Border] -and $control.Name -like '*_LabelBorder')) {
$control.Background = [System.Windows.Media.Brushes]::Transparent
}
}
}
}
}
}
function Test-ComboBoxContainsMatch {
param ([System.Windows.Controls.ComboBox]$ComboBox, [string]$SearchText)
foreach ($item in $ComboBox.Items) {
$itemText = if ($item -is [System.Windows.Controls.ComboBoxItem]) { $item.Content.ToString().ToLower() } else { $item.ToString().ToLower() }
if ($itemText.Contains($SearchText)) { return $true }
}
return $false
}

View File

@@ -0,0 +1,215 @@
# MainWindow-WindowChrome.ps1
# Window sizing, DPI-aware coordinate conversion, maximized-window taskbar-constraint helpers, 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)
function ConvertTo-ScreenPointToDip {
param(
[System.Windows.Window]$Window,
[double]$X,
[double]$Y
)
$source = [System.Windows.PresentationSource]::FromVisual($Window)
if ($null -eq $source -or $null -eq $source.CompositionTarget) {
return [System.Windows.Point]::new($X, $Y)
}
return $source.CompositionTarget.TransformFromDevice.Transform([System.Windows.Point]::new($X, $Y))
}
# Convert screen-pixel size to WPF device-independent size
function ConvertTo-ScreenPixelsToDip {
param(
[System.Windows.Window]$Window,
[double]$Width,
[double]$Height
)
$topLeft = ConvertTo-ScreenPointToDip -Window $Window -X 0 -Y 0
$bottomRight = ConvertTo-ScreenPointToDip -Window $Window -X $Width -Y $Height
return [System.Windows.Size]::new($bottomRight.X - $topLeft.X, $bottomRight.Y - $topLeft.Y)
}
# Get the screen that currently contains the window
function Get-WindowScreen {
param([System.Windows.Window]$Window)
$hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($Window)).Handle
if ($hwnd -eq [IntPtr]::Zero) {
return $null
}
return [System.Windows.Forms.Screen]::FromHandle($hwnd)
}
# Update window border/corner chrome when transitioning between Normal and Maximized
function Update-MainWindowChrome {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Border]$MainBorder,
[System.Windows.Controls.Border]$TitleBarBackground,
[object]$NormalWindowShadow
)
$windowStateMaximized = [System.Windows.WindowState]::Maximized
$chrome = [System.Windows.Shell.WindowChrome]::GetWindowChrome($Window)
if ($Window.WindowState -eq $windowStateMaximized) {
$MainBorder.Margin = [System.Windows.Thickness]::new(0)
$MainBorder.BorderThickness = [System.Windows.Thickness]::new(0)
$MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(0)
$MainBorder.Effect = $null
$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 {
$MainBorder.Margin = [System.Windows.Thickness]::new(0)
$MainBorder.BorderThickness = [System.Windows.Thickness]::new(1)
$MainBorder.CornerRadius = [System.Windows.CornerRadius]::new(8)
$MainBorder.Effect = $NormalWindowShadow
$TitleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(8, 8, 0, 0)
if ($chrome) { $chrome.ResizeBorderThickness = [System.Windows.Thickness]::new(5) }
}
}
# Set the initial window size and center on screen (normal state only)
function Set-MainWindowInitialSize {
param(
[System.Windows.Window]$Window,
[double]$InitialNormalMaxWidth = 1400.0
)
if ($Window.WindowState -ne [System.Windows.WindowState]::Normal) {
return
}
$screen = Get-WindowScreen -Window $Window
if ($null -eq $screen) {
return
}
$workingAreaTopLeftDip = ConvertTo-ScreenPointToDip -Window $Window -X $screen.WorkingArea.Left -Y $screen.WorkingArea.Top
$workingAreaDip = ConvertTo-ScreenPixelsToDip -Window $Window -Width $screen.WorkingArea.Width -Height $screen.WorkingArea.Height
$Window.Width = [Math]::Min($InitialNormalMaxWidth, $workingAreaDip.Width)
$Window.Left = $workingAreaTopLeftDip.X + (($workingAreaDip.Width - $Window.Width) / 2)
}
# Update the content grid margin to constrain max content width
function Update-MainWindowContentMargin {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Grid]$ContentGrid,
[double]$MaxContentWidth = 1600.0
)
$w = $Window.ActualWidth
if ($w -gt $MaxContentWidth) {
$gutter = [Math]::Floor(($w - $MaxContentWidth) / 2)
$ContentGrid.Margin = [System.Windows.Thickness]::new($gutter, 0, $gutter, 0)
}
else {
$ContentGrid.Margin = [System.Windows.Thickness]::new(0)
}
}
# Vertically center the home content panel
function Update-MainWindowHomeContentPosition {
param(
[System.Windows.Window]$Window,
[System.Windows.Controls.Panel]$HomeContentPanel
)
if ($HomeContentPanel) {
$availableHeight = $Window.ActualHeight - 32 # subtract title bar height
if ($availableHeight -gt 0) {
$topMargin = ($availableHeight - 584) * 0.5
$HomeContentPanel.Margin = [System.Windows.Thickness]::new(0, $topMargin, 0, 0)
}
}
}
function Start-DropdownArrowAnimation {
param(
[System.Windows.Controls.TextBlock]$Arrow,
[double]$Angle
)
if (-not $Arrow) { return }
$animation = New-Object System.Windows.Media.Animation.DoubleAnimation
$animation.To = $Angle
$animation.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
$ease = New-Object System.Windows.Media.Animation.CubicEase
$ease.EasingMode = 'EaseOut'
$animation.EasingFunction = $ease
$Arrow.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $animation)
}

View File

@@ -79,16 +79,70 @@ function Test-RestoreDialogFeatureVisibleInOverview {
return -not [string]::IsNullOrWhiteSpace([string]$featureDefinition.Category) return -not [string]::IsNullOrWhiteSpace([string]$featureDefinition.Category)
} }
function Get-SelectedForwardFeatureIdsFromBackup {
param($SelectedBackup)
$selectedFeatureIds = New-Object System.Collections.Generic.List[string]
$seenSelectedFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($featureId in @($SelectedBackup.SelectedFeatures)) {
if ([string]::IsNullOrWhiteSpace([string]$featureId)) {
continue
}
$normalizedId = [string]$featureId
if ($seenSelectedFeatureIds.Add($normalizedId)) {
$selectedFeatureIds.Add($normalizedId)
}
}
return @($selectedFeatureIds.ToArray())
}
function Get-SelectedUndoFeatureIdsFromBackup {
param($SelectedBackup)
$selectedUndoFeatureIds = New-Object System.Collections.Generic.List[string]
$seenUndoFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($featureId in @($SelectedBackup.SelectedUndoFeatures)) {
if ([string]::IsNullOrWhiteSpace([string]$featureId)) {
continue
}
$normalizedId = [string]$featureId
if ($seenUndoFeatureIds.Add($normalizedId)) {
$selectedUndoFeatureIds.Add($normalizedId)
}
}
return @($selectedUndoFeatureIds.ToArray())
}
function Get-CombinedSelectedFeatureIdsFromBackup {
param($SelectedBackup)
$featureIds = New-Object System.Collections.Generic.List[string]
$seenIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($featureId in @(Get-SelectedForwardFeatureIdsFromBackup -SelectedBackup $SelectedBackup) + @(Get-SelectedUndoFeatureIdsFromBackup -SelectedBackup $SelectedBackup)) {
if ([string]::IsNullOrWhiteSpace([string]$featureId)) {
continue
}
$normalizedId = [string]$featureId
if ($seenIds.Add($normalizedId)) {
$featureIds.Add($normalizedId)
}
}
return @($featureIds.ToArray())
}
function Get-SelectedFeatureIdsFromBackup { function Get-SelectedFeatureIdsFromBackup {
param($SelectedBackup) param($SelectedBackup)
return @( return @(Get-CombinedSelectedFeatureIdsFromBackup -SelectedBackup $SelectedBackup)
foreach ($featureId in @($SelectedBackup.SelectedFeatures)) {
if (-not [string]::IsNullOrWhiteSpace([string]$featureId)) {
[string]$featureId
}
}
)
} }
function Get-RestoreBackupFeatureLists { function Get-RestoreBackupFeatureLists {

View File

@@ -130,12 +130,8 @@ function Show-AppSelectionWindow {
return return
} }
if ($selectedApps -contains "Microsoft.WindowsStore" -and -not $Silent) { if (-not (ConfirmUnsafeAppRemoval -SelectedApps $selectedApps -Owner $window)) {
$result = Show-MessageBox -Message 'Are you sure you wish to uninstall the Microsoft Store? This app cannot easily be reinstalled.' -Title 'Are you sure?' -Button 'YesNo' -Icon 'Warning' -Owner $window return
if ($result -eq 'No') {
return
}
} }
SaveCustomAppsListToFile -appsList $selectedApps SaveCustomAppsListToFile -appsList $selectedApps

View File

@@ -186,7 +186,7 @@ function Show-ApplyModal {
$applyRebootPanel.Visibility = 'Visible' $applyRebootPanel.Visibility = 'Visible'
} }
else { else {
$script:ApplyCompletionMessageEl.Text = "Your clean system is ready. Thanks for using Win11Debloat!" $script:ApplyCompletionMessageEl.Text = "Your system is ready. Thanks for using Win11Debloat!"
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -70,6 +70,9 @@ function Show-RestoreBackupDialog {
$backupCreatedText = $window.FindName('BackupCreatedText') $backupCreatedText = $window.FindName('BackupCreatedText')
$backupTargetText = $window.FindName('BackupTargetText') $backupTargetText = $window.FindName('BackupTargetText')
$featuresItemsControl = $window.FindName('FeaturesItemsControl') $featuresItemsControl = $window.FindName('FeaturesItemsControl')
$reappliedSeparator = $window.FindName('ReappliedSeparator')
$reappliedPanel = $window.FindName('ReappliedPanel')
$reappliedFeaturesItemsControl = $window.FindName('ReappliedFeaturesItemsControl')
$nonRevertibleSeparator = $window.FindName('NonRevertibleSeparator') $nonRevertibleSeparator = $window.FindName('NonRevertibleSeparator')
$nonRevertiblePanel = $window.FindName('NonRevertiblePanel') $nonRevertiblePanel = $window.FindName('NonRevertiblePanel')
$nonRevertibleFeaturesItemsControl = $window.FindName('NonRevertibleFeaturesItemsControl') $nonRevertibleFeaturesItemsControl = $window.FindName('NonRevertibleFeaturesItemsControl')
@@ -119,6 +122,8 @@ function Show-RestoreBackupDialog {
$overviewFeaturesSection.Visibility = 'Collapsed' $overviewFeaturesSection.Visibility = 'Collapsed'
$overviewSummaryText.Visibility = 'Visible' $overviewSummaryText.Visibility = 'Visible'
$reappliedSeparator.Visibility = 'Collapsed'
$reappliedPanel.Visibility = 'Collapsed'
$nonRevertibleSeparator.Visibility = 'Collapsed' $nonRevertibleSeparator.Visibility = 'Collapsed'
$nonRevertiblePanel.Visibility = 'Collapsed' $nonRevertiblePanel.Visibility = 'Collapsed'
$introInfoPanel.Visibility = 'Collapsed' $introInfoPanel.Visibility = 'Collapsed'
@@ -215,13 +220,33 @@ function Show-RestoreBackupDialog {
} }
} }
$selectedFeatureIds = Get-SelectedFeatureIdsFromBackup -SelectedBackup $SelectedBackup $selectedForwardFeatureIds = @(Get-SelectedForwardFeatureIdsFromBackup -SelectedBackup $SelectedBackup)
$featureLists = Get-RestoreBackupFeatureLists -SelectedFeatureIds $selectedFeatureIds -Features $script:Features $selectedUndoFeatureIds = @(Get-SelectedUndoFeatureIdsFromBackup -SelectedBackup $SelectedBackup)
$revertibleFeaturesList = @($featureLists.Revertible)
$nonRevertibleFeaturesList = @($featureLists.NonRevertible)
Write-Host "Backup overview prepared. Revertible=$($revertibleFeaturesList.Count), NonRevertible=$($nonRevertibleFeaturesList.Count)"
if ($revertibleFeaturesList.Count -eq 0) { $seenForwardFeatureIds = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
foreach ($featureId in $selectedForwardFeatureIds) {
[void]$seenForwardFeatureIds.Add([string]$featureId)
}
$filteredUndoFeatureIds = New-Object System.Collections.Generic.List[string]
foreach ($featureId in $selectedUndoFeatureIds) {
if ($seenForwardFeatureIds.Contains([string]$featureId)) {
continue
}
$filteredUndoFeatureIds.Add([string]$featureId)
}
$forwardFeatureLists = Get-RestoreBackupFeatureLists -SelectedFeatureIds $selectedForwardFeatureIds -Features $script:Features
$undoFeatureLists = Get-RestoreBackupFeatureLists -SelectedFeatureIds @($filteredUndoFeatureIds.ToArray()) -Features $script:Features
$combinedFeatureLists = Get-RestoreBackupFeatureLists -SelectedFeatureIds (Get-SelectedFeatureIdsFromBackup -SelectedBackup $SelectedBackup) -Features $script:Features
$revertibleFeaturesList = @($forwardFeatureLists.Revertible)
$reappliedFeaturesList = @($undoFeatureLists.Revertible)
$nonRevertibleFeaturesList = @($combinedFeatureLists.NonRevertible)
Write-Host "Backup overview prepared. Reverted=$($revertibleFeaturesList.Count), ReApplied=$($reappliedFeaturesList.Count), NonRevertible=$($nonRevertibleFeaturesList.Count)"
if ($revertibleFeaturesList.Count -eq 0 -and $reappliedFeaturesList.Count -eq 0) {
throw 'The selected backup does not contain any changes that can be restored.' throw 'The selected backup does not contain any changes that can be restored.'
} }
@@ -229,13 +254,16 @@ function Show-RestoreBackupDialog {
$backupCreatedText.Text = $createdText $backupCreatedText.Text = $createdText
$backupTargetText.Text = GetFriendlyRegistryBackupTarget -Target ([string]$SelectedBackup.Target) $backupTargetText.Text = GetFriendlyRegistryBackupTarget -Target ([string]$SelectedBackup.Target)
$featuresItemsControl.ItemsSource = $revertibleFeaturesList $featuresItemsControl.ItemsSource = $revertibleFeaturesList
$overviewFeaturesSection.Visibility = 'Visible' $overviewFeaturesSection.Visibility = if ($revertibleFeaturesList.Count -gt 0) { 'Visible' } else { 'Collapsed' }
$reappliedFeaturesItemsControl.ItemsSource = $reappliedFeaturesList
if ($reappliedFeaturesList.Count -gt 0) { $reappliedPanel.Visibility = 'Visible' } else { $reappliedPanel.Visibility = 'Collapsed' }
if ($revertibleFeaturesList.Count -gt 0 -and $reappliedFeaturesList.Count -gt 0) { $reappliedSeparator.Visibility = 'Visible' } else { $reappliedSeparator.Visibility = 'Collapsed' }
$overviewSummaryText.Visibility = 'Collapsed' $overviewSummaryText.Visibility = 'Collapsed'
$nonRevertibleFeaturesItemsControl.ItemsSource = $nonRevertibleFeaturesList $nonRevertibleFeaturesItemsControl.ItemsSource = $nonRevertibleFeaturesList
$hasNonRevertibleItems = ($nonRevertibleFeaturesList.Count -gt 0) $hasNonRevertibleItems = ($nonRevertibleFeaturesList.Count -gt 0)
if ($hasNonRevertibleItems) { $nonRevertiblePanel.Visibility = 'Visible' } else { $nonRevertiblePanel.Visibility = 'Collapsed' } if ($hasNonRevertibleItems) { $nonRevertiblePanel.Visibility = 'Visible' } else { $nonRevertiblePanel.Visibility = 'Collapsed' }
if ($hasNonRevertibleItems) { $nonRevertibleSeparator.Visibility = 'Visible' } else { $nonRevertibleSeparator.Visibility = 'Collapsed' } if ($hasNonRevertibleItems -and ($revertibleFeaturesList.Count -gt 0 -or $reappliedFeaturesList.Count -gt 0)) { $nonRevertibleSeparator.Visibility = 'Visible' } else { $nonRevertibleSeparator.Visibility = 'Collapsed' }
$introInfoPanel.Visibility = 'Collapsed' $introInfoPanel.Visibility = 'Collapsed'
$overviewPanel.Visibility = 'Visible' $overviewPanel.Visibility = 'Visible'
@@ -295,6 +323,30 @@ function Show-RestoreBackupDialog {
return return
} }
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
return
}
}
$window.Tag = @{ $window.Tag = @{
Result = 'RestoreStartMenu' Result = 'RestoreStartMenu'
StartMenuScope = $scope StartMenuScope = $scope

View File

@@ -7,10 +7,15 @@ function Show-RestoreBackupWindow {
try { try {
Write-Host 'Opening restore backup dialog.' Write-Host 'Opening restore backup dialog.'
$restoreResult = [PSCustomObject]@{
RestoredRegistry = $false
RestoredStartMenu = $false
}
$dialogResult = Show-RestoreBackupDialog -Owner $Owner $dialogResult = Show-RestoreBackupDialog -Owner $Owner
if (-not $dialogResult -or $dialogResult.Result -eq 'Cancel') { if (-not $dialogResult -or $dialogResult.Result -eq 'Cancel') {
Write-Host 'Restore canceled by user.' Write-Host 'Restore canceled by user.'
return return $restoreResult
} }
$successMessage = $null $successMessage = $null
@@ -24,7 +29,8 @@ function Show-RestoreBackupWindow {
Write-Host "User confirmed registry restore for $($backup.Target)." Write-Host "User confirmed registry restore for $($backup.Target)."
Restore-RegistryBackupState -Backup $backup Restore-RegistryBackupState -Backup $backup
$successMessage = 'Registry backup restored successfully. Please restart your computer for all changes to take effect.' $restoreResult.RestoredRegistry = $true
$successMessage = 'Registry backup restored successfully. Some changes may require a restart to take effect.'
} }
elseif ($dialogResult.Result -eq 'RestoreStartMenu') { elseif ($dialogResult.Result -eq 'RestoreStartMenu') {
$scope = $dialogResult.StartMenuScope $scope = $dialogResult.StartMenuScope
@@ -69,6 +75,8 @@ function Show-RestoreBackupWindow {
$successMessage = "The Start Menu backup was successfully restored for the current user. The changes will apply the next time you sign in." $successMessage = "The Start Menu backup was successfully restored for the current user. The changes will apply the next time you sign in."
} }
} }
$restoreResult.RestoredStartMenu = $true
} }
if ($warningMessage) { if ($warningMessage) {
@@ -79,10 +87,16 @@ function Show-RestoreBackupWindow {
Write-Host "$successMessage" Write-Host "$successMessage"
Show-MessageBox -Title 'Backup Restored' -Message $successMessage -Icon Success Show-MessageBox -Title 'Backup Restored' -Message $successMessage -Icon Success
} }
return $restoreResult
} }
catch { catch {
$errorMessage = if ($_.Exception.Message) { $_.Exception.Message } else { 'An unexpected error occurred.' } $errorMessage = if ($_.Exception.Message) { $_.Exception.Message } else { 'An unexpected error occurred.' }
Write-Error "Restore operation failed: $errorMessage" Write-Error "Restore operation failed: $errorMessage"
Show-MessageBox -Title 'Error' -Message "Restore failed: $errorMessage" -Icon Error Show-MessageBox -Title 'Error' -Message "Restore failed: $errorMessage" -Icon Error
return [PSCustomObject]@{
RestoredRegistry = $false
RestoredStartMenu = $false
}
} }
} }

View File

@@ -17,9 +17,7 @@ param (
[switch]$RemoveApps, [switch]$RemoveApps,
[switch]$RemoveAppsCustom, [switch]$RemoveAppsCustom,
[switch]$RemoveGamingApps, [switch]$RemoveGamingApps,
[switch]$RemoveCommApps,
[switch]$RemoveHPApps, [switch]$RemoveHPApps,
[switch]$RemoveW11Outlook,
[switch]$ForceRemoveEdge, [switch]$ForceRemoveEdge,
[switch]$DisableDVR, [switch]$DisableDVR,
[switch]$DisableGameBarIntegration, [switch]$DisableGameBarIntegration,
@@ -58,7 +56,7 @@ param (
[switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb, [switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb,
[switch]$HideTaskview, [switch]$HideTaskview,
[switch]$DisableStartRecommended, [switch]$DisableStartRecommended,
[switch]$DisableStartAllApps, [switch]$DisableStartAllApps, [switch]$StartAllAppsCategory, [switch]$StartAllAppsGrid, [switch]$StartAllAppsList,
[switch]$DisableStartPhoneLink, [switch]$DisableStartPhoneLink,
[switch]$DisableCopilot, [switch]$DisableCopilot,
[switch]$DisableRecall, [switch]$DisableRecall,
@@ -135,12 +133,12 @@ catch {
Exit Exit
} }
Write-Output "" # Remove old script folder if it exists, but keep configs, logs and backups
Write-Output "> Cleaning up old Win11Debloat folder..."
# Remove old script folder if it exists, but keep config and log files
if (Test-Path $tempWorkPath) { if (Test-Path $tempWorkPath) {
Get-ChildItem -Path $tempWorkPath -Exclude CustomAppsList,LastUsedSettings.json,Win11Debloat.log,Config,Logs,Backups | Remove-Item -Recurse -Force Write-Output ""
Write-Output "> Cleaning up old Win11Debloat folder..."
Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
} }
$configDir = Join-Path $tempWorkPath 'Config' $configDir = Join-Path $tempWorkPath 'Config'
@@ -148,6 +146,9 @@ $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 # 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") { if (Test-Path "$configDir") {
Write-Output ""
Write-Output "> Backing up existing config files..."
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
$filesToKeep = @( $filesToKeep = @(
@@ -178,6 +179,9 @@ if (Test-Path "$backupDir") {
New-Item -ItemType Directory -Path "$configDir" -Force | Out-Null 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" Get-ChildItem -Path "$backupDir" -Recurse | Move-Item -Destination "$configDir"
Remove-Item "$backupDir" -Recurse -Force Remove-Item "$backupDir" -Recurse -Force
} }
@@ -218,13 +222,13 @@ if ($null -ne $debloatProcess) {
$debloatProcess.WaitForExit() $debloatProcess.WaitForExit()
} }
# Remove all remaining script files, except for CustomAppsList and LastUsedSettings.json 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..."
# Cleanup, remove Win11Debloat directory # Cleanup, remove Win11Debloat directory
Get-ChildItem -Path $tempWorkPath -Exclude CustomAppsList,LastUsedSettings.json,Win11Debloat.log,Win11Debloat-Run.log,Config,Logs,Backups | Remove-Item -Recurse -Force Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
} }
Write-Output "" Write-Output ""

View File

@@ -17,9 +17,7 @@ param (
[switch]$RemoveApps, [switch]$RemoveApps,
[switch]$RemoveAppsCustom, [switch]$RemoveAppsCustom,
[switch]$RemoveGamingApps, [switch]$RemoveGamingApps,
[switch]$RemoveCommApps,
[switch]$RemoveHPApps, [switch]$RemoveHPApps,
[switch]$RemoveW11Outlook,
[switch]$ForceRemoveEdge, [switch]$ForceRemoveEdge,
[switch]$DisableDVR, [switch]$DisableDVR,
[switch]$DisableGameBarIntegration, [switch]$DisableGameBarIntegration,
@@ -58,7 +56,7 @@ param (
[switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb, [switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb,
[switch]$HideTaskview, [switch]$HideTaskview,
[switch]$DisableStartRecommended, [switch]$DisableStartRecommended,
[switch]$DisableStartAllApps, [switch]$DisableStartAllApps, [switch]$StartAllAppsCategory, [switch]$StartAllAppsGrid, [switch]$StartAllAppsList,
[switch]$DisableStartPhoneLink, [switch]$DisableStartPhoneLink,
[switch]$DisableCopilot, [switch]$DisableCopilot,
[switch]$DisableRecall, [switch]$DisableRecall,
@@ -174,7 +172,7 @@ Expand-Archive $tempArchivePath $tempWorkPath
Remove-Item $tempArchivePath Remove-Item $tempArchivePath
# Move files # Move files
Get-ChildItem -Path (Join-Path $tempWorkPath 'Raphire-Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath Get-ChildItem -Path (Join-Path $tempWorkPath '*Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath
# Add existing config files back to Config folder # Add existing config files back to Config folder
if (Test-Path "$backupDir") { if (Test-Path "$backupDir") {

View File

@@ -0,0 +1,34 @@
# Shows confirmation dialogs for apps that require extra caution before removal.
# Returns $true if the user confirmed all warnings (or if no warnings were triggered),
# $false if the user declined any warning.
function ConfirmUnsafeAppRemoval {
param (
[string[]]$SelectedApps,
$Owner = $null
)
# Skip all warnings in Silent mode
if ($Silent) {
return $true
}
# Microsoft Store warning
if ($SelectedApps -contains "Microsoft.WindowsStore") {
$result = Show-MessageBox -Message 'Are you sure that you wish to uninstall the Microsoft Store? This app cannot easily be reinstalled.' -Title 'Are you sure?' -Button 'YesNo' -Icon 'Warning' -Owner $Owner
if ($result -eq 'No') {
return $false
}
}
# Windows Terminal warning
if ($SelectedApps -contains "Microsoft.WindowsTerminal") {
$result = Show-MessageBox -Message 'Are you sure that you wish to remove Windows Terminal? Windows Terminal is the default command-line app for Windows. Ensure you are not running Win11Debloat via Windows Terminal before proceeding to avoid a mid-process failure.' -Title 'Are you sure?' -Button 'YesNo' -Icon 'Warning' -Owner $Owner
if ($result -eq 'No') {
return $false
}
}
return $true
}

View File

@@ -129,10 +129,13 @@ function Convert-RegValueData {
} }
if ($valueData -match '^"(?<value>.*)"$') { if ($valueData -match '^"(?<value>.*)"$') {
$stringValue = $matches.value
# Unescape registry string escape sequences
$stringValue = $stringValue -replace '\\"', '"' -replace '\\\\', '\'
return [PSCustomObject]@{ return [PSCustomObject]@{
OperationType = 'SetValue' OperationType = 'SetValue'
ValueType = 'String' ValueType = 'String'
ValueData = $matches.value ValueData = $stringValue
} }
} }

View File

@@ -27,16 +27,19 @@ function Split-RegistryPath {
$null $null
} }
if ($hiveName.Equals('HKEY_USERS', [System.StringComparison]::OrdinalIgnoreCase) -and -not [string]::IsNullOrWhiteSpace($normalizedSubKey)) { if ($hiveName.Equals('HKEY_USERS', [System.StringComparison]::OrdinalIgnoreCase) -and
-not [string]::IsNullOrWhiteSpace($normalizedSubKey) -and
-not [string]::IsNullOrWhiteSpace([string]$script:RegistryTargetHiveMountName)) {
if ($normalizedSubKey -match '^(?<mount>[^\\]+)(?:\\(?<rest>.*))?$') { if ($normalizedSubKey -match '^(?<mount>[^\\]+)(?:\\(?<rest>.*))?$') {
$mountName = [string]$matches.mount $mountName = [string]$matches.mount
if ($mountName.Equals('.DEFAULT', [System.StringComparison]::OrdinalIgnoreCase)) { if ($mountName.Equals('Default', [System.StringComparison]::OrdinalIgnoreCase)) {
$remainingSubKey = if ($matches.rest) { [string]$matches.rest } else { '' } $remainingSubKey = if ($matches.rest) { [string]$matches.rest } else { '' }
$targetMountName = [string]$script:RegistryTargetHiveMountName
if ([string]::IsNullOrWhiteSpace($remainingSubKey)) { if ([string]::IsNullOrWhiteSpace($remainingSubKey)) {
$normalizedSubKey = 'Default' $normalizedSubKey = $targetMountName
} }
else { else {
$normalizedSubKey = "Default\$remainingSubKey" $normalizedSubKey = "$targetMountName\$remainingSubKey"
} }
} }
} }
@@ -67,14 +70,14 @@ function Get-RegistryRootKey {
function Get-RegistryFilePathForFeature { function Get-RegistryFilePathForFeature {
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
$Feature, [string]$RegistryKey,
[switch]$UseSysprepRegFiles [switch]$UseSysprepRegFiles
) )
$useSysprepLayout = $UseSysprepRegFiles -or $script:Params.ContainsKey('Sysprep') -or $script:Params.ContainsKey('User') $useSysprepLayout = $UseSysprepRegFiles -or $script:Params.ContainsKey('Sysprep') -or $script:Params.ContainsKey('User')
if ($useSysprepLayout) { if ($useSysprepLayout) {
return Join-Path (Join-Path $script:RegfilesPath 'Sysprep') $Feature.RegistryKey return Join-Path (Join-Path $script:RegfilesPath 'Sysprep') $RegistryKey
} }
return Join-Path $script:RegfilesPath $Feature.RegistryKey return Join-Path $script:RegfilesPath $RegistryKey
} }

View File

@@ -31,14 +31,6 @@ function Test-TargetUserName {
} }
} }
if (TestIfUserIsLoggedIn -Username $normalizedUserName) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
Message = "User '$normalizedUserName' is currently logged in. Please sign out that user first."
}
}
return [PSCustomObject]@{ return [PSCustomObject]@{
IsValid = $true IsValid = $true
UserName = $normalizedUserName UserName = $normalizedUserName

View File

@@ -1,42 +0,0 @@
function TestIfUserIsLoggedIn {
param(
[Parameter(Mandatory)]
[string]$Username
)
try {
$quserOutput = @(& quser 2>$null)
if ($LASTEXITCODE -ne 0 -or -not $quserOutput) {
return $false
}
foreach ($line in ($quserOutput | Select-Object -Skip 1)) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
# Remove current-session marker and split columns.
$normalizedLine = $line.TrimStart('>', ' ')
$parts = $normalizedLine -split '\s+'
if ($parts.Count -eq 0) { continue }
$sessionUser = $parts[0]
if ([string]::IsNullOrWhiteSpace($sessionUser)) { continue }
# Normalize possible DOMAIN\user or user@domain formats.
if ($sessionUser.Contains('\')) {
$sessionUser = ($sessionUser -split '\\')[-1]
}
if ($sessionUser.Contains('@')) {
$sessionUser = ($sessionUser -split '@')[0]
}
if ($sessionUser.Equals($Username, [System.StringComparison]::OrdinalIgnoreCase)) {
return $true
}
}
}
catch {
return $false
}
return $false
}

View File

@@ -0,0 +1,152 @@
function New-TargetUserHiveContext {
param(
[Parameter(Mandatory)]
[string]$TargetUserName,
[AllowNull()]
[object]$UserContext,
[Parameter(Mandatory)]
[string]$HiveDatPath,
[AllowNull()]
[string]$MountName,
[bool]$WasAlreadyLoaded = $false,
[bool]$WasLoadedByScript = $false
)
$effectiveMountName = if ([string]::IsNullOrWhiteSpace($MountName)) { 'Default' } else { $MountName }
return [PSCustomObject]@{
TargetUserName = $TargetUserName
UserSid = if ($UserContext) { $UserContext.UserSid } else { $null }
ProfilePath = if ($UserContext) { $UserContext.ProfilePath } else { $null }
HiveDatPath = $HiveDatPath
MountName = $effectiveMountName
WasAlreadyLoaded = $WasAlreadyLoaded
WasLoadedByScript = $WasLoadedByScript
}
}
function Resolve-TargetUserHiveContext {
param(
[Parameter(Mandatory)]
[string]$TargetUserName
)
$normalizedTargetUserName = NormalizeUserLookupValue -Value $TargetUserName
if ([string]::IsNullOrWhiteSpace($normalizedTargetUserName)) {
throw 'Target user name for registry hive resolution is empty.'
}
$userContext = ResolveUserProfileContext -UserName $normalizedTargetUserName
if (-not $userContext -or [string]::IsNullOrWhiteSpace([string]$userContext.ProfilePath)) {
throw "Unable to resolve profile path for target user '$normalizedTargetUserName'."
}
$hiveDatPath = Join-Path $userContext.ProfilePath 'NTUSER.DAT'
if (-not (Test-Path -LiteralPath $hiveDatPath)) {
throw "Unable to find target user hive at '$hiveDatPath'."
}
$isDefaultProfile = $normalizedTargetUserName.Equals('Default', [System.StringComparison]::OrdinalIgnoreCase)
$userSid = if ($userContext) { [string]$userContext.UserSid } else { '' }
if ((-not $isDefaultProfile) -and (-not [string]::IsNullOrWhiteSpace($userSid))) {
$loadedHivePath = "Registry::HKEY_USERS\$userSid"
if (Test-Path -LiteralPath $loadedHivePath) {
return (New-TargetUserHiveContext `
-TargetUserName $normalizedTargetUserName `
-UserContext $userContext `
-HiveDatPath $hiveDatPath `
-MountName $userSid `
-WasAlreadyLoaded $true `
-WasLoadedByScript $false)
}
}
return (New-TargetUserHiveContext `
-TargetUserName $normalizedTargetUserName `
-UserContext $userContext `
-HiveDatPath $hiveDatPath `
-MountName 'Default' `
-WasAlreadyLoaded $false `
-WasLoadedByScript $false)
}
function Resolve-LoadedTargetUserHiveContext {
param(
[Parameter(Mandatory)]
$HiveContext
)
$userSid = [string]$HiveContext.UserSid
if ([string]::IsNullOrWhiteSpace($userSid)) {
return $null
}
$loadedHivePath = "Registry::HKEY_USERS\$userSid"
if (-not (Test-Path -LiteralPath $loadedHivePath)) {
return $null
}
return (New-TargetUserHiveContext `
-TargetUserName $HiveContext.TargetUserName `
-UserContext ([PSCustomObject]@{ UserSid = $HiveContext.UserSid; ProfilePath = $HiveContext.ProfilePath }) `
-HiveDatPath $HiveContext.HiveDatPath `
-MountName $userSid `
-WasAlreadyLoaded $true `
-WasLoadedByScript $false)
}
function Invoke-WithTargetUserHive {
param(
[Parameter(Mandatory)]
[string]$TargetUserName,
[Parameter(Mandatory)]
[scriptblock]$ScriptBlock,
$ArgumentObject = $null,
[switch]$PassHiveContext
)
$hiveContext = Resolve-TargetUserHiveContext -TargetUserName $TargetUserName
$previousHiveMountName = $script:RegistryTargetHiveMountName
try {
if (-not $hiveContext.WasAlreadyLoaded) {
$global:LASTEXITCODE = 0
reg load "HKU\$($hiveContext.MountName)" "$($hiveContext.HiveDatPath)" | Out-Null
$loadExitCode = $LASTEXITCODE
if ($loadExitCode -ne 0) {
$loadedSidContext = Resolve-LoadedTargetUserHiveContext -HiveContext $hiveContext
if ($loadedSidContext) {
$hiveContext = $loadedSidContext
}
else {
throw "Failed to load target user hive '$($hiveContext.HiveDatPath)' (exit code: $loadExitCode)."
}
}
else {
$hiveContext.WasLoadedByScript = $true
}
}
$script:RegistryTargetHiveMountName = [string]$hiveContext.MountName
if ($PassHiveContext) {
return & $ScriptBlock $ArgumentObject $hiveContext
}
return & $ScriptBlock $ArgumentObject
}
finally {
$script:RegistryTargetHiveMountName = $previousHiveMountName
if ($hiveContext -and $hiveContext.WasLoadedByScript) {
$global:LASTEXITCODE = 0
reg unload "HKU\$($hiveContext.MountName)" | Out-Null
$unloadExitCode = $LASTEXITCODE
if ($unloadExitCode -ne 0) {
Write-Warning "Failed to unload registry hive 'HKU\$($hiveContext.MountName)' (exit code: $unloadExitCode)"
}
}
}
}

View File

@@ -17,9 +17,7 @@ param (
[switch]$RemoveApps, [switch]$RemoveApps,
[switch]$RemoveAppsCustom, [switch]$RemoveAppsCustom,
[switch]$RemoveGamingApps, [switch]$RemoveGamingApps,
[switch]$RemoveCommApps,
[switch]$RemoveHPApps, [switch]$RemoveHPApps,
[switch]$RemoveW11Outlook,
[switch]$ForceRemoveEdge, [switch]$ForceRemoveEdge,
[switch]$DisableDVR, [switch]$DisableDVR,
[switch]$DisableGameBarIntegration, [switch]$DisableGameBarIntegration,
@@ -59,7 +57,7 @@ param (
[switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb, [switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb,
[switch]$HideTaskview, [switch]$HideTaskview,
[switch]$DisableStartRecommended, [switch]$DisableStartRecommended,
[switch]$DisableStartAllApps, [switch]$DisableStartAllApps, [switch]$StartAllAppsCategory, [switch]$StartAllAppsGrid, [switch]$StartAllAppsList,
[switch]$DisableStartPhoneLink, [switch]$DisableStartPhoneLink,
[switch]$DisableCopilot, [switch]$DisableCopilot,
[switch]$DisableRecall, [switch]$DisableRecall,
@@ -141,7 +139,7 @@ if (-not $isAdmin) {
} }
# Define script-level variables & paths # Define script-level variables & paths
$script:Version = "2026.05.20" $script:Version = "2026.06.10"
$configPath = Join-Path $PSScriptRoot 'Config' $configPath = Join-Path $PSScriptRoot 'Config'
$logsPath = Join-Path $PSScriptRoot 'Logs' $logsPath = Join-Path $PSScriptRoot 'Logs'
$schemasPath = Join-Path $PSScriptRoot 'Schemas' $schemasPath = Join-Path $PSScriptRoot 'Schemas'
@@ -293,6 +291,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/CLI/PrintHeader.ps1" . "$PSScriptRoot/Scripts/CLI/PrintHeader.ps1"
# Features functions # Features functions
. "$PSScriptRoot/Scripts/Features/GetCurrentTweakState.ps1"
. "$PSScriptRoot/Scripts/Features/ExecuteChanges.ps1" . "$PSScriptRoot/Scripts/Features/ExecuteChanges.ps1"
. "$PSScriptRoot/Scripts/Features/CreateSystemRestorePoint.ps1" . "$PSScriptRoot/Scripts/Features/CreateSystemRestorePoint.ps1"
. "$PSScriptRoot/Scripts/Features/BackupRegistryFeatureSelection.ps1" . "$PSScriptRoot/Scripts/Features/BackupRegistryFeatureSelection.ps1"
@@ -302,7 +301,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/Features/RestoreRegistryApplyState.ps1" . "$PSScriptRoot/Scripts/Features/RestoreRegistryApplyState.ps1"
. "$PSScriptRoot/Scripts/Features/RestoreRegistryBackup.ps1" . "$PSScriptRoot/Scripts/Features/RestoreRegistryBackup.ps1"
. "$PSScriptRoot/Scripts/Features/DisableStoreSearchSuggestions.ps1" . "$PSScriptRoot/Scripts/Features/DisableStoreSearchSuggestions.ps1"
. "$PSScriptRoot/Scripts/Features/EnableWindowsFeature.ps1" . "$PSScriptRoot/Scripts/Features/WindowsOptionalFeatures.ps1"
. "$PSScriptRoot/Scripts/Features/ImportRegistryFile.ps1" . "$PSScriptRoot/Scripts/Features/ImportRegistryFile.ps1"
. "$PSScriptRoot/Scripts/Features/ReplaceStartMenu.ps1" . "$PSScriptRoot/Scripts/Features/ReplaceStartMenu.ps1"
. "$PSScriptRoot/Scripts/Features/RestartExplorer.ps1" . "$PSScriptRoot/Scripts/Features/RestartExplorer.ps1"
@@ -330,6 +329,11 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/GUI/Show-RestoreBackupWindow.ps1" . "$PSScriptRoot/Scripts/GUI/Show-RestoreBackupWindow.ps1"
. "$PSScriptRoot/Scripts/GUI/RestoreBackupDialogFeatureLists.ps1" . "$PSScriptRoot/Scripts/GUI/RestoreBackupDialogFeatureLists.ps1"
. "$PSScriptRoot/Scripts/GUI/Show-RestoreBackupDialog.ps1" . "$PSScriptRoot/Scripts/GUI/Show-RestoreBackupDialog.ps1"
. "$PSScriptRoot/Scripts/GUI/MainWindow-WindowChrome.ps1"
. "$PSScriptRoot/Scripts/GUI/MainWindow-AppSelection.ps1"
. "$PSScriptRoot/Scripts/GUI/MainWindow-TweaksBuilder.ps1"
. "$PSScriptRoot/Scripts/GUI/MainWindow-Navigation.ps1"
. "$PSScriptRoot/Scripts/GUI/MainWindow-Deployment.ps1"
. "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1" . "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1"
. "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1" . "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1"
. "$PSScriptRoot/Scripts/GUI/Show-Bubble.ps1" . "$PSScriptRoot/Scripts/GUI/Show-Bubble.ps1"
@@ -337,6 +341,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
# Helper functions # Helper functions
. "$PSScriptRoot/Scripts/Helpers/AddParameter.ps1" . "$PSScriptRoot/Scripts/Helpers/AddParameter.ps1"
. "$PSScriptRoot/Scripts/Helpers/ResolveUserProfilePath.ps1" . "$PSScriptRoot/Scripts/Helpers/ResolveUserProfilePath.ps1"
. "$PSScriptRoot/Scripts/Helpers/UserHiveHelpers.ps1"
. "$PSScriptRoot/Scripts/Helpers/CheckIfUserExists.ps1" . "$PSScriptRoot/Scripts/Helpers/CheckIfUserExists.ps1"
. "$PSScriptRoot/Scripts/Helpers/CheckModernStandbySupport.ps1" . "$PSScriptRoot/Scripts/Helpers/CheckModernStandbySupport.ps1"
. "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1" . "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1"
@@ -350,7 +355,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1" . "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
. "$PSScriptRoot/Scripts/Helpers/RegistryPathHelpers.ps1" . "$PSScriptRoot/Scripts/Helpers/RegistryPathHelpers.ps1"
. "$PSScriptRoot/Scripts/Helpers/ApplyRegistryRegFile.ps1" . "$PSScriptRoot/Scripts/Helpers/ApplyRegistryRegFile.ps1"
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1" . "$PSScriptRoot/Scripts/Helpers/ConfirmUnsafeAppRemoval.ps1"
# Threading functions # Threading functions
. "$PSScriptRoot/Scripts/Threading/DoEvents.ps1" . "$PSScriptRoot/Scripts/Threading/DoEvents.ps1"
@@ -373,6 +378,7 @@ $WinVersion = Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\Current
$script:ModernStandbySupported = CheckModernStandbySupport $script:ModernStandbySupported = CheckModernStandbySupport
$script:Params = $PSBoundParameters $script:Params = $PSBoundParameters
$script:UndoParams = @{}
# Add default Apps parameter when RemoveApps is requested and Apps was not explicitly provided # Add default Apps parameter when RemoveApps is requested and Apps was not explicitly provided
if ((-not $script:Params.ContainsKey("Apps")) -and $script:Params.ContainsKey("RemoveApps")) { if ((-not $script:Params.ContainsKey("Apps")) -and $script:Params.ContainsKey("RemoveApps")) {