Compare commits

..
19 Commits
Author SHA1 Message Date
Jeffrey ce36b8edfb Bump version 2026-07-11 20:07:47 +02:00
JeffreyandGitHub 3b78e8d4ed Fix: Show reboot warning for undoing features that require it (#699) 2026-07-11 20:04:51 +02:00
Jeffrey 74dedc00e9 Add tooltip to user selection selectbox 2026-07-11 00:04:08 +02:00
Jeffrey 5571700634 Disabling Window Snapping now warns users that a reboot is required to apply 2026-07-09 22:02:18 +02:00
Jeffrey c2ea697552 Hide irrelevant Settings home page settings in Windows 10 2026-07-09 22:01:33 +02:00
JeffreyandGitHub ce8c10042e Fix issues with domain accounts & fix run.bat breaking when launched from path with certain characters (#693) 2026-07-08 23:18:04 +02:00
JeffreyandGitHub 89a5d508d8 Fix win10 icons by falling back to Segoe MDL2 Assets (#696) 2026-07-08 23:16:01 +02:00
Jeffrey e38aafe01e Update README.md 2026-07-08 21:40:33 +02:00
Jeffrey d336f98b39 Update feature wording 2026-07-08 21:38:48 +02:00
Raphire a70a2ccbd6 Update README.md 2026-07-08 19:30:25 +02:00
JeffreyandGitHub 0ef789cc3a Add option to prevent auto install of device apps & add LG monitor app removal (#695)
* Add support for removing LG monitor app

* Add option to prevent auto install of device companion apps via Windows Update

* Update CONTRIBUTING.md with missing Apps.json fields
2026-07-08 17:54:13 +02:00
HetCreepandGitHub 82f50d6d13 fix(startup): refuse to run under PowerShell 7 with a clear message (#676) 2026-07-08 15:22:36 +02:00
SqidyandGitHub 949515da3e Added Feature Disabling Notifications Through Win11Debloat (#690) 2026-07-06 19:29:09 +02:00
0eeb8f323a fix(elevation): quote script path and parameter values in admin relaunch (#683)
Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
2026-07-04 17:35:24 +02:00
HetCreepandGitHub a9a89c1f13 fix(store-search): correct deny permission removal in undo path (#684) 2026-07-04 13:57:50 +02:00
HetCreepandGitHub 339764eb18 Fix broken contributing link in README.md (#688) 2026-07-02 23:03:44 +02:00
3b4a5f16ad fix(restore-gui): surface registry-backup load failures to the user (#685)
Co-authored-by: Jeffrey <9938813+Raphire@users.noreply.github.com>
2026-07-02 21:16:47 +02:00
JeffreyandGitHub 9a700afaed Consolidate Get-Dev.ps1 into Get.ps1, add WhatIf support to Get.ps1 (#681) 2026-06-28 22:53:27 +02:00
Jeffrey 26b313372b Update README.md 2026-06-25 16:41:32 +02:00
32 changed files with 980 additions and 420 deletions
+18 -2
View File
@@ -18,7 +18,7 @@ You can help us test the latest changes and additions to the script. If you enco
You can launch the prerelease version of Win11Debloat by running this command:
```ps1
& ([scriptblock]::Create((irm "https://debloat.raphi.re/dev")))
& ([scriptblock]::Create((irm "https://debloat.raphi.re/"))) -Dev
```
# Contributing Code
@@ -159,10 +159,26 @@ To add a new app that can be removed via Win11Debloat:
"FriendlyName": "Display Name",
"AppId": "AppPackageIdentifier",
"Description": "Brief description of the app",
"SelectedByDefault": true|false
"SelectedByDefault": false,
"Recommendation": "optional",
"RemovalMethod": "Appx"
}
```
**Field Descriptions**:
- `FriendlyName`: Display name shown in the GUI.
- `AppId`: The `AppPackageIdentifier` from `Get-AppxPackage` or the `Id` from `winget list`, depending on removal method.
- `Description`: Brief description of the app shown in the GUI.
- `SelectedByDefault`: Set to `true` only for apps that are largely considered bloatware, otherwise set to `false`.
- `Recommendation`: Indicates how strongly the app is recommended for removal. One of:
- `safe` — safe to remove for most users
- `optional` — can be safely removed if the user doesn't need the app
- `unsafe` — should only remove if the user knows what they are doing
- `RemovalMethod`: The method used to remove the app. One of:
- `Appx` — remove as a standard Appx package via `Remove-AppxPackage` (most apps)
- `WinGet` — remove via WinGet (`winget uninstall`). Use for non-Appx apps such as Microsoft Copilot.
3. **Follow the Guidelines**:
- Use clear, user-friendly names for `FriendlyName`
+10 -1
View File
@@ -921,6 +921,14 @@
"Recommendation": "optional",
"RemovalMethod": "Appx"
},
{
"FriendlyName": "LG Monitor App",
"AppId": "LGElectronics.LGMonitorApp",
"Description": "LG OEM software for controlling your LG monitor, Windows Update may silently reinstall this app if you have an LG monitor connected",
"SelectedByDefault": false,
"Recommendation": "optional",
"RemovalMethod": "Appx"
},
{
"FriendlyName": "HP AI Experience Center",
"AppId": "AD2F1837.HPAIExperienceCenter",
@@ -1132,7 +1140,7 @@
]
},
{
"Name": "OEM software (Dell, HP, Lenovo)",
"Name": "OEM software (Dell, HP, Lenovo, LG)",
"AppIds": [
"AD2F1837.HPAIExperienceCenter",
"AD2F1837.HPConnectedMusic",
@@ -1156,6 +1164,7 @@
"AD2F1837.myHP",
"E046963F.LenovoCompanion",
"LenovoCompanyLimited.LenovoVantageService",
"LGElectronics.LGMonitorApp",
"DellInc.DellSupportAssistforPCs",
"DellInc.DellDigitalDelivery",
"DellInc.DellMobileConnect"
+29 -2
View File
@@ -401,6 +401,19 @@
"MinVersion": null,
"MaxVersion": null
},
{
"FeatureId": "DisableNotifications",
"Label": "Disable Windows Notifications (From apps and other senders)",
"ToolTip": "Disables native Windows notifications from apps and other senders, this includes notifications from apps like Discord, WhatsApp, Teams, and Slack.",
"Category": "Privacy & Suggested Content",
"RegistryKey": "Disable_Notifications.reg",
"ApplyText": "Disabling Windows notifications and reminders",
"UndoLabel": "Enable Windows notifications and reminders",
"ApplyUndoText": "Enabling Windows notifications and reminders",
"RegistryUndoKey": "Enable_Notifications.reg",
"MinVersion": null,
"MaxVersion": null
},
{
"FeatureId": "DisableLocationServices",
"Label": "Disable Windows location services & app location access",
@@ -667,7 +680,7 @@
"UndoLabel": "Show Microsoft 365 Copilot ads in Settings Home",
"ApplyUndoText": "Enabling Microsoft 365 Copilot ads in Settings Home",
"RegistryUndoKey": "Enable_Settings_365_Ads.reg",
"MinVersion": null,
"MinVersion": 22000,
"MaxVersion": null
},
{
@@ -680,7 +693,7 @@
"UndoLabel": "Show Settings 'Home' page",
"ApplyUndoText": "Enabling the Settings Home page",
"RegistryUndoKey": "Enable_Settings_Home.reg",
"MinVersion": null,
"MinVersion": 22000,
"MaxVersion": null
},
{
@@ -800,6 +813,7 @@
"UndoLabel": "Enable window snapping",
"ApplyUndoText": "Enabling window snapping",
"RegistryUndoKey": "Enable_Window_Snapping.reg",
"RequiresReboot": true,
"MinVersion": null,
"MaxVersion": null
},
@@ -1356,6 +1370,19 @@
"MinVersion": null,
"MaxVersion": null
},
{
"FeatureId": "DisableDeviceAutoAppDownload",
"Label": "Prevent Windows from auto-installing device companion apps",
"ToolTip": "Stops Windows from silently installing device companion apps via Windows Update when you connect certain devices, like monitors from LG and Alienware. These devices will still work as expected, this only stops Windows Update from automatically installing additional apps without consent.",
"Category": "Windows Update",
"RegistryKey": "Disable_Device_Auto_App_Download.reg",
"ApplyText": "Preventing automatic installation of device companion apps",
"UndoLabel": "Allow automatic installation of device companion apps",
"ApplyUndoText": "Allowing automatic installation of device companion apps",
"RegistryUndoKey": "Enable_Device_Auto_App_Download.reg",
"MinVersion": null,
"MaxVersion": null
},
{
"FeatureId": "ForceRemoveEdge",
"Label": "Forcefully uninstall Microsoft Edge. NOT RECOMMENDED!",
+23 -42
View File
@@ -6,7 +6,7 @@
Win11Debloat is a lightweight, easy to use PowerShell script that allows you to quickly declutter and customize your Windows experience, no installation required! You can use it to remove pre-installed apps, disable telemetry, remove intrusive interface elements and much more. No need to painstakingly go through all the settings yourself or remove apps one by one. Win11Debloat makes the process quick and easy!
The script also includes many features that system administrators and power users will enjoy. Such as a powerful command-line interface, support for Windows Audit mode and the ability to make changes to other Windows users. Please refer to our [wiki](https://github.com/Raphire/Win11Debloat/wiki/) for more details.
The script also includes many features that system administrators and power users will enjoy. Such as a powerful command-line interface, support for Windows Audit mode and the ability to make changes to other Windows users. You can also easily export & import your preferred settings, allowing you to quickly apply the same settings on all your systems. Please refer to our [wiki](https://github.com/Raphire/Win11Debloat/wiki) for more details.
![Win11Debloat Menu](/Assets/Images/menu.png)
@@ -23,14 +23,14 @@ The script also includes many features that system administrators and power user
Download & run the script automatically via PowerShell.
1. Open PowerShell or Terminal, preferably as an administrator.
1. Open PowerShell or Terminal.
2. Copy and paste the command below into PowerShell:
```PowerShell
& ([scriptblock]::Create((irm "https://debloat.raphi.re/")))
```
3. Wait for the script to automatically download Win11Debloat.
3. Wait for the script to automatically download and launch Win11Debloat.
4. Carefully read through and follow the on-screen instructions.
This method supports command-line parameters to customize the behaviour of the script. Please click [here](https://github.com/Raphire/Win11Debloat/wiki/Command%E2%80%90line-Interface#parameters) for more information.
@@ -74,10 +74,10 @@ This method supports command-line parameters to customize the behaviour of the s
## Features
Below is an overview of the key features and functionality offered by Win11Debloat. Please refer to [the wiki](https://github.com/Raphire/Win11Debloat/wiki/Default-Settings) for more information about the default settings preset.
Below is an overview of the key features and functionality offered by Win11Debloat. You can visit the [the wiki](https://github.com/Raphire/Win11Debloat/wiki) for more details.
> [!Tip]
> All of the changes made by Win11Debloat can easily be reverted and almost all of the apps can be reinstalled through the Microsoft Store. A full guide on how to revert changes can be found [here](https://github.com/Raphire/Win11Debloat/wiki/Reverting-Changes).
> All of the changes made by Win11Debloat can easily be reverted and almost all of the apps can be reinstalled through the Microsoft Store. You can visit [the wiki](https://github.com/Raphire/Win11Debloat/wiki/Reverting-Changes) for more information on reverting changes.
#### App Removal
@@ -86,29 +86,21 @@ Below is an overview of the key features and functionality offered by Win11Deblo
#### Privacy & Suggested Content
- Disable telemetry, diagnostic data, activity history, app-launch tracking & targeted ads.
- Disable tips, tricks, suggestions & ads across Windows.
- Disable Windows location services & app location access.
- Disable Find My Device location tracking.
- Disable 'Windows Spotlight' and tips & tricks on the lock screen.
- Disable 'Windows Spotlight' desktop background option.
- Disable ads, suggestions and the MSN news feed in Microsoft Edge.
- Disable tips, tricks, suggestions & ads across Windows, the lock screen and Microsoft Edge.
- Disable Windows location services, app location access and Find My Device location tracking.
- Hide Microsoft 365 ads on the Settings 'Home' page, or hide the 'Home' page entirely.
#### AI Features
- Disable & remove Microsoft Copilot.
- Disable Windows Recall.
- Disable Click to Do, AI text & image analysis tool.
- Disable & remove Microsoft Copilot, Windows Recall and Click to Do.
- Prevent AI service (WSAIFabricSvc) from starting automatically.
- Disable AI Features in Edge.
- Disable AI Features in Paint.
- Disable AI Features in Notepad.
- Disable AI Features in Edge, Paint and Notepad.
#### System
- Disable the Drag Tray for sharing & moving files.
- Restore the old Windows 10 style context menu.
- Turn off Enhance Pointer Precision, also known as mouse acceleration.
- Turn off Enhance Pointer Precision (mouse acceleration).
- Disable the Sticky Keys keyboard shortcut.
- Disable Storage Sense automatic disk cleanup.
- Disable fast start-up to ensure a full shutdown.
@@ -120,54 +112,43 @@ Below is an overview of the key features and functionality offered by Win11Deblo
- Prevent Windows from getting updates as soon as they're available.
- Prevent automatic restarts after updates while signed in.
- Disable sharing of downloaded updates with other PCs, also known as Delivery Optimization.
- Prevent Windows from auto-installing device companion apps.
#### Appearance
- Enable dark mode for system and apps.
- Disable transparency effects
- Disable animations and visual effects.
- Disable transparency, animations and visual effects.
#### Start Menu & Search
- Remove or replace all pinned apps from the start menu.
- Hide the recommended section in the start menu.
- Hide the 'All Apps' section in the start menu.
- Customize the start menu by removing pinned apps, hiding recommendations, and customizing the 'All Apps' section.
- Disable the Phone Link mobile devices integration in the start menu.
- Disable Bing web search & Copilot integration in Windows search.
- Disable Microsoft Store app suggestions in Windows search.
- Disable Search Highlights (dynamic/branded content) in the taskbar search box.
- Disable local Windows search history.
- Disable Bing web search & Copilot integration and Microsoft Store app suggestions in Windows search.
#### Taskbar
- Align taskbar icons to the left.
- Hide or change the search icon/box on the taskbar.
- Hide the taskview button from the taskbar.
- Change taskbar alignment.
- Customize or hide taskbar buttons like the search bar, taskview and more.
- Disable widgets on the taskbar & lock screen.
- Hide the chat (meet now) icon from the taskbar.
- Enable the 'End Task' option in the taskbar right click menu.
- Enable the 'End Task' option in the taskbar right click menu to quickly force-close apps.
- Enable the 'Last Active Click' behavior in the taskbar app area. This allows you to repeatedly click on an application's icon in the taskbar to switch focus between the open windows of that application.
- Choose how app icons are shown on the taskbar when using multiple monitors.
- Choose combine mode for taskbar buttons and labels.
- Customize how app buttons are shown on the taskbar.
#### File Explorer
- Change the default location that File Explorer opens to.
- Show file extensions for known file types.
- Show hidden files, folders and drives.
- Hide the Home or Gallery section from the File Explorer navigation pane.
- Hide the Home, Gallery or OneDrive section from the File Explorer navigation pane.
- Hide duplicate removable drive entries from the File Explorer navigation pane, so only the entry under 'This PC' remains.
- Add all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer.
- Hide the 3D objects, music or OneDrive folder from the File Explorer navigation pane.
- Hide the 'Include in library', 'Give access to' and 'Share' options from the context menu.
- Change drive letter position or visibility in File Explorer.
#### Multi-tasking
- Disable window snapping.
- Disable Snap Assist suggestions when snapping a window.
- Disable Snap Layout suggestions when dragging windows to the top of screen and when hovering on the maximize button.
- Change if tabs are shown when snapping or pressing Alt+Tab.
- Disable Snap Assist and Snap Layout suggestions when dragging or snapping windows.
- Change whether tabs are shown when snapping windows or pressing Alt+Tab.
#### Optional Windows Features
@@ -181,12 +162,12 @@ Below is an overview of the key features and functionality offered by Win11Deblo
#### Advanced Features
- Option to [apply changes to a different user](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#running-as-another-user), instead of the currently logged in user.
- Ability to [apply changes to a different user](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#running-as-another-user), instead of the currently logged in user.
- [Sysprep mode](https://github.com/Raphire/Win11Debloat/wiki/Advanced-Features#sysprep-mode) to apply changes to the Windows Default user profile. Which ensures, all new users will have the changes automatically applied to them.
## Contributing
We welcome contributions of all kinds! Please see our [Contributing Guidelines](/.github/CONTRIBUTING.md) for detailed instructions on how to get started and best practices for contributing.
We welcome contributions of all kinds! Please see our [Contributing Guidelines](https://github.com/Raphire/Win11Debloat/blob/master/.github/CONTRIBUTING.md) for detailed instructions on how to get started and best practices for contributing.
## License
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -11
View File
@@ -1,5 +1,5 @@
@echo off
setlocal EnableDelayedExpansion
setlocal
:: Set Windows Terminal installation paths. (Default and Scoop installation)
set "wtDefaultPath=%LOCALAPPDATA%\Microsoft\WindowsApps\wt.exe"
@@ -15,22 +15,20 @@ if exist "%wtDefaultPath%" (
) else if exist "%wtScoopPath%" (
set "wtPath=%wtScoopPath%"
) else (
echo Windows Terminal not found. Using default PowerShell instead.
set "wtPath="
)
set "SCRIPT_PATH=\"%~dp0Win11Debloat.ps1\""
:: Interpolated into a PS single-quoted string below;
:: Apostrophes escaped via %:'=''% and -File arg uses [char]34 to avoid quote-parity bugs.
set "SCRIPT_PATH=%~dp0Win11Debloat.ps1"
:: Launch script
if defined wtPath (
call :Log Launching Win11Debloat.ps1 with Windows Terminal...
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
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "$p='%SCRIPT_PATH:'=''%'; $q=[char]34; Start-Process -FilePath '%wtPath%' -ArgumentList ('PowerShell -NoProfile -ExecutionPolicy Bypass -File ' + $q + $p + $q) -Verb RunAs" >> "%logFile%" || call :Error "PowerShell command failed"
) else (
echo Windows Terminal not found. Using default PowerShell instead...
echo Windows Terminal not found, using default PowerShell...
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 %SCRIPT_PATH%' -Verb RunAs}" >> "%logFile%" || call :Error "PowerShell command failed"
call :Log Script execution passed successfully to Win11Debloat.ps1
PowerShell -NoProfile -ExecutionPolicy Bypass -Command "$p='%SCRIPT_PATH:'=''%'; $q=[char]34; Start-Process PowerShell -ArgumentList ('-NoProfile -ExecutionPolicy Bypass -File ' + $q + $p + $q) -Verb RunAs" >> "%logFile%" || call :Error "PowerShell command failed"
)
echo.
@@ -40,11 +38,13 @@ goto :EOF
:: Logging Function
:Log
echo %* >> "%logFile%"
echo(%* >> "%logFile%"
goto :EOF
:: Error Handler
:Error
echo ERROR: %*
echo(ERROR: %*
call :Log ERROR: %*
echo Logged in %logFile%
pause
goto :EOF
+1 -1
View File
@@ -105,7 +105,7 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="SemiBold"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
Text="&#xEB52;"
Foreground="{DynamicResource TitleBarCloseHoverColor}"
Margin="0,0,8,0"/>
+1 -1
View File
@@ -16,7 +16,7 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Width" Value="46"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
<Setter Property="FontFamily" Value="{DynamicResource AppIconFontFamily}"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="Template">
<Setter.Value>
+5 -5
View File
@@ -44,7 +44,7 @@
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock Text="&#xE8BB;" FontFamily="Segoe Fluent Icons" FontSize="10"/>
<TextBlock Text="&#xE8BB;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="10"/>
</Button>
<StackPanel Grid.Row="0" Grid.RowSpan="2">
@@ -54,7 +54,7 @@
<!-- Loading icon (spinning) -->
<TextBlock x:Name="ApplySpinnerIcon"
Text="&#xE895;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="36"
Foreground="{DynamicResource ButtonBgColor}"
HorizontalAlignment="Center"
@@ -115,7 +115,7 @@
<!-- Success icon -->
<TextBlock x:Name="ApplyCompletionIcon"
Text="&#xE73E;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="40"
Foreground="{DynamicResource ButtonBgColor}"
HorizontalAlignment="Center"
@@ -141,7 +141,7 @@
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBlock Text="&#xE7BA;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="14"
Foreground="#e8912d"
VerticalAlignment="Center"
@@ -163,7 +163,7 @@
Style="{DynamicResource ModalSecondaryStretchedButtonStyle}"
AutomationProperties.Name="Support the creator">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xEB52;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,-1"/>
<TextBlock Text="&#xEB52;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,-1"/>
<TextBlock Text="Support the creator" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,1"/>
</StackPanel>
</Button>
+30 -30
View File
@@ -65,7 +65,7 @@
<!-- Category header icon style -->
<Style x:Key="CategoryHeaderIcon" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Segoe Fluent Icons"/>
<Setter Property="FontFamily" Value="{DynamicResource AppIconFontFamily}"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="LineHeight" Value="20"/>
<Setter Property="Foreground" Value="{DynamicResource AppFgColor}"/>
@@ -196,7 +196,7 @@
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Width" Value="46"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FontFamily" Value="Segoe Fluent Icons"/>
<Setter Property="FontFamily" Value="{DynamicResource AppIconFontFamily}"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="Template">
<Setter.Value>
@@ -362,8 +362,8 @@
</TextBlock.Style>
</TextBlock>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button x:Name="KofiBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="&#xEB52;" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Support the creator" AutomationProperties.Name="Support the creator"/>
<Button x:Name="MenuBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="&#xE700;" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
<Button x:Name="KofiBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="&#xEB52;" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Support the creator" AutomationProperties.Name="Support the creator"/>
<Button x:Name="MenuBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="&#xE700;" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
<Button.ContextMenu>
<ContextMenu x:Name="MainMenu">
<ContextMenu.Resources>
@@ -380,38 +380,38 @@
</ContextMenu.Resources>
<MenuItem x:Name="ImportConfigBtn" Header="Import config" AutomationProperties.Name="Import configuration">
<MenuItem.Icon>
<TextBlock Text="&#xe838;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xe838;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="ExportConfigBtn" Header="Export config" AutomationProperties.Name="Export configuration">
<MenuItem.Icon>
<TextBlock Text="&#xe74e;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xe74e;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="RestoreBackupBtn" Header="Restore backup" AutomationProperties.Name="Restore registry backup">
<MenuItem.Icon>
<TextBlock Text="&#xe81c;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xe81c;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem x:Name="MenuDocumentation" Header="Documentation" AutomationProperties.Name="Documentation">
<MenuItem.Icon>
<TextBlock Text="&#xe736;" FontFamily="Segoe MDL2 Assets" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xe736;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="MenuReportBug" Header="Report a bug" AutomationProperties.Name="Report a bug">
<MenuItem.Icon>
<TextBlock Text="&#xEBE8;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xEBE8;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="MenuLogs" Header="Logs" AutomationProperties.Name="Logs">
<MenuItem.Icon>
<TextBlock Text="&#xf000;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xf000;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem x:Name="MenuAbout" Header="About" AutomationProperties.Name="About">
<MenuItem.Icon>
<TextBlock Text="&#xE946;" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock Text="&#xE946;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
@@ -490,7 +490,7 @@
<Border HorizontalAlignment="Center" BorderBrush="{DynamicResource AppBorderColor}" BorderThickness="1" CornerRadius="4" Background="{DynamicResource CardBgColor}" Padding="16,12" Width="500">
<StackPanel>
<TextBlock Text="What user do you want to apply changes to?" Style="{StaticResource CategoryHeaderTextBlock}"/>
<ComboBox x:Name="UserSelectionCombo" Margin="0,0,0,6" AutomationProperties.Name="Apply Changes To">
<ComboBox x:Name="UserSelectionCombo" Margin="0,0,0,6" AutomationProperties.Name="Apply Changes To" ToolTip="The currently logged-in user profile">
<ComboBoxItem Content="Current User" IsSelected="True"/>
<ComboBoxItem Content="Other User"/>
<ComboBoxItem Content="Windows Default User (Sysprep)"/>
@@ -506,7 +506,7 @@
</Grid.ColumnDefinitions>
<TextBlock x:Name="UsernameTextBoxPlaceholder" Grid.Column="0" Text="Enter username" Foreground="{DynamicResource AppFgColor}" Opacity="0.7" FontSize="13" Margin="3,0,0,1" VerticalAlignment="Center" IsHitTestVisible="False"/>
<TextBox x:Name="OtherUsernameTextBox" Grid.Column="0" Style="{DynamicResource TextBoxInputStyle}" Text="" AutomationProperties.Name="Enter username"/>
<TextBlock Grid.Column="1" Text="&#xE77B;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
<TextBlock Grid.Column="1" Text="&#xE77B;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
</Grid>
</Border>
</Border>
@@ -517,13 +517,13 @@
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,8,0,4">
<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="&#xe735;" FontFamily="{DynamicResource AppIconFontFamily}" 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="&#xe90f;" FontFamily="{DynamicResource AppIconFontFamily}" 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>
@@ -585,9 +585,9 @@
</Style>
</ToggleButton.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE735;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,1,6,0"/>
<TextBlock Text="&#xE735;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="0,1,6,0"/>
<TextBlock Text="Quick Select" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,1"/>
<TextBlock x:Name="PresetsArrow" Text="&#xE70D;" FontFamily="Segoe Fluent Icons" FontSize="10" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock x:Name="PresetsArrow" Text="&#xE70D;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="10" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform x:Name="PresetsArrowRotation" Angle="0"/>
</TextBlock.RenderTransform>
@@ -596,7 +596,7 @@
</ToggleButton>
<Button x:Name="ClearAppSelectionBtn" ToolTip="Clear all selected apps" Style="{DynamicResource SecondaryButtonStyle}" Height="32" Padding="10,0" Margin="0,0,10,0" AutomationProperties.Name="Clear Selection">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xE711;" FontFamily="Segoe Fluent Icons" FontSize="15" VerticalAlignment="Center" Margin="0,3,6,0"/>
<TextBlock Text="&#xE711;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="15" VerticalAlignment="Center" Margin="0,3,6,0"/>
<TextBlock Text="Clear Selection" FontSize="13" VerticalAlignment="Center" Margin="0,0,0,1"/>
</StackPanel>
</Button>
@@ -627,7 +627,7 @@
</Grid.ColumnDefinitions>
<TextBlock x:Name="AppSearchPlaceholder" Grid.Column="0" Text="Search apps..." Foreground="{DynamicResource AppFgColor}" Opacity="0.7" FontSize="13" Margin="3,0,0,1" VerticalAlignment="Center" IsHitTestVisible="False"/>
<TextBox x:Name="AppSearchBox" Grid.Column="0" Style="{DynamicResource TextBoxInputStyle}" Text="" AutomationProperties.Name="Search app"/>
<TextBlock Grid.Column="1" Text="&#xE721;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
<TextBlock Grid.Column="1" Text="&#xE721;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
</Grid>
</Border>
</Border>
@@ -658,19 +658,19 @@
</Grid.ColumnDefinitions>
<StackPanel x:Name="HeaderNameBtn" Grid.Column="1" Orientation="Horizontal" Cursor="Hand" VerticalAlignment="Center" Style="{StaticResource SortHeaderBtnStyle}">
<TextBlock Text="Name" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock x:Name="SortArrowName" Text="&#xE70E;" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock x:Name="SortArrowName" Text="&#xE70E;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform><RotateTransform Angle="0"/></TextBlock.RenderTransform>
</TextBlock>
</StackPanel>
<StackPanel x:Name="HeaderDescriptionBtn" Grid.Column="2" Orientation="Horizontal" Cursor="Hand" VerticalAlignment="Center" Margin="8,0,0,0" Style="{StaticResource SortHeaderBtnStyle}">
<TextBlock Text="Description" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock x:Name="SortArrowDescription" Text="&#xE70E;" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock x:Name="SortArrowDescription" Text="&#xE70E;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform><RotateTransform Angle="0"/></TextBlock.RenderTransform>
</TextBlock>
</StackPanel>
<StackPanel x:Name="HeaderAppIdBtn" Grid.Column="3" Orientation="Horizontal" Cursor="Hand" VerticalAlignment="Center" Style="{StaticResource SortHeaderBtnStyle}">
<TextBlock Text="App ID" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource AppFgColor}"/>
<TextBlock x:Name="SortArrowAppId" Text="&#xE70E;" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock x:Name="SortArrowAppId" Text="&#xE70E;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="11" Foreground="{DynamicResource AppFgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform><RotateTransform Angle="0"/></TextBlock.RenderTransform>
</TextBlock>
</StackPanel>
@@ -684,7 +684,7 @@
</ScrollViewer>
<Border x:Name="LoadingAppsIndicator" CornerRadius="0,0,4,4" Background="{DynamicResource CardBgColor}" Opacity="0.8" Visibility="Collapsed">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="&#xF16A;" FontFamily="Segoe Fluent Icons" FontSize="28" Foreground="{DynamicResource AppFgColor}" HorizontalAlignment="Center" Margin="0,0,0,8" RenderTransformOrigin="0.5,0.5">
<TextBlock Text="&#xF16A;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="28" Foreground="{DynamicResource AppFgColor}" HorizontalAlignment="Center" Margin="0,0,0,8" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="0"/>
</TextBlock.RenderTransform>
@@ -789,9 +789,9 @@
</Style>
</ToggleButton.Style>
<StackPanel Orientation="Horizontal">
<TextBlock Text="&#xE735;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,1,6,0"/>
<TextBlock Text="&#xE735;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="0,1,6,0"/>
<TextBlock Text="Quick Select" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,1"/>
<TextBlock x:Name="TweaksPresetsArrow" Text="&#xE70D;" FontFamily="Segoe Fluent Icons" FontSize="10" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock x:Name="TweaksPresetsArrow" Text="&#xE70D;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="10" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="0"/>
</TextBlock.RenderTransform>
@@ -800,7 +800,7 @@
</ToggleButton>
<Button x:Name="ClearAllTweaksBtn" ToolTip="Clear all selected tweaks" Style="{DynamicResource SecondaryButtonStyle}" Padding="10,0" Height="32" Margin="0,0,10,0" AutomationProperties.Name="Clear Selection">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xE711;" FontFamily="Segoe Fluent Icons" FontSize="15" VerticalAlignment="Center" Margin="0,3,6,0"/>
<TextBlock Text="&#xE711;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="15" VerticalAlignment="Center" Margin="0,3,6,0"/>
<TextBlock Text="Clear Selection" FontSize="13" VerticalAlignment="Center" Margin="0,0,0,1"/>
</StackPanel>
</Button>
@@ -832,7 +832,7 @@
</Grid.ColumnDefinitions>
<TextBlock x:Name="TweakSearchPlaceholder" Grid.Column="0" Text="Search tweaks..." Foreground="{DynamicResource AppFgColor}" Opacity="0.7" FontSize="13" Margin="3,0,0,1" VerticalAlignment="Center" IsHitTestVisible="False"/>
<TextBox x:Name="TweakSearchBox" Grid.Column="0" Style="{DynamicResource TextBoxInputStyle}" Text="" AutomationProperties.Name="Search tweaks"/>
<TextBlock Grid.Column="1" Text="&#xE721;" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
<TextBlock Grid.Column="1" Text="&#xE721;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="14" VerticalAlignment="Center" Margin="8,0,4,0" Foreground="{DynamicResource AppFgColor}" Opacity="0.7"/>
</Grid>
</Border>
</Border>
@@ -986,7 +986,7 @@
</Button>
<Button x:Name="DeploymentApplyBtn" Style="{DynamicResource PrimaryButtonStyle}" Width="200" Height="44" HorizontalAlignment="Center" AutomationProperties.Name="Apply Changes">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xE73E;" FontFamily="Segoe Fluent Icons" FontSize="20" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="&#xE73E;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="20" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="Apply Changes" VerticalAlignment="Center" FontSize="18" FontWeight="SemiBold" Margin="8,0,0,4"/>
</StackPanel>
</Button>
@@ -1006,7 +1006,7 @@
</Grid.ColumnDefinitions>
<Button x:Name="PreviousBtn" Grid.Column="0" Width="120" Height="36" Style="{DynamicResource SecondaryButtonStyle}" Visibility="Collapsed" AutomationProperties.Name="Back">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="&#xE72B;" FontFamily="Segoe Fluent Icons" FontSize="12" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="&#xE72B;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="12" Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="Back" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,3"/>
</StackPanel>
</Button>
@@ -1015,7 +1015,7 @@
<Button x:Name="NextBtn" Width="120" Height="36" Style="{DynamicResource PrimaryButtonStyle}" AutomationProperties.Name="Next">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="Next" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,3"/>
<TextBlock Text="&#xE72A;" FontFamily="Segoe Fluent Icons" FontSize="12" Margin="8,0,0,0" VerticalAlignment="Center"/>
<TextBlock Text="&#xE72A;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="12" Margin="8,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
+1 -1
View File
@@ -49,7 +49,7 @@
<!-- Icon -->
<TextBlock x:Name="IconText"
Grid.Column="0"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="24"
Foreground="{DynamicResource AppFgColor}"
VerticalAlignment="Center"
+3 -3
View File
@@ -92,7 +92,7 @@
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock Text="&#xE8BB;" FontFamily="Segoe Fluent Icons" FontSize="10"/>
<TextBlock Text="&#xE8BB;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="10"/>
</Button>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" Margin="0">
@@ -135,7 +135,7 @@
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="&#xEF58;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="24"
VerticalAlignment="Center"
Margin="14,0,14,0"/>
@@ -165,7 +165,7 @@
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="&#xE8FC;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="24"
VerticalAlignment="Center"
Margin="14,0,14,0"/>
+3 -3
View File
@@ -272,7 +272,7 @@
<Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4">
<TextBlock x:Name="Arrow"
Text="&#xE70D;"
FontFamily="Segoe Fluent Icons"
FontFamily="{DynamicResource AppIconFontFamily}"
FontSize="10"
HorizontalAlignment="Right"
VerticalAlignment="Center"
@@ -393,12 +393,12 @@
</Grid.ColumnDefinitions>
<Border x:Name="CheckBoxBorder" Grid.Column="0" Width="20" Height="20" Background="{DynamicResource CheckBoxBgColor}" BorderBrush="{DynamicResource CheckBoxBorderColor}" BorderThickness="1" CornerRadius="4" Margin="0,0,8,0">
<Grid>
<TextBlock x:Name="CheckMark" Text="&#xE73E;" FontFamily="Segoe Fluent Icons" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource ButtonBgColor}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
<TextBlock x:Name="CheckMark" Text="&#xE73E;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource ButtonBgColor}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
<TextBlock.Clip>
<RectangleGeometry x:Name="CheckMarkClip" Rect="0,0,0,16"/>
</TextBlock.Clip>
</TextBlock>
<TextBlock x:Name="IndeterminateMark" Text="&#xe9ae;" FontFamily="Segoe Fluent Icons" FontSize="13" FontWeight="Bold" Foreground="{DynamicResource ButtonBgColor}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0" Margin="1,0,0,1">
<TextBlock x:Name="IndeterminateMark" Text="&#xe9ae;" FontFamily="{DynamicResource AppIconFontFamily}" FontSize="13" FontWeight="Bold" Foreground="{DynamicResource ButtonBgColor}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0" Margin="1,0,0,1">
<TextBlock.Clip>
<RectangleGeometry x:Name="IndeterminateMarkClip" Rect="0,0,0,12"/>
</TextBlock.Clip>
+10 -11
View File
@@ -1,10 +1,11 @@
# Restart the Windows Explorer process
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
}
<#
.SYNOPSIS
Restarts Windows Explorer to apply system changes.
.DESCRIPTION
Restarts the Explorer process to ensure all UI modifications take effect. Shows a warning if any of the applied features require a reboot to take full effect.
#>
function RestartExplorer {
if ($script:Params.ContainsKey("WhatIf")) {
Write-Host "[WhatIf] Restart the Windows Explorer process" -ForegroundColor Cyan
return
@@ -17,11 +18,9 @@ function RestartExplorer {
return
}
foreach ($paramKey in $script:Params.Keys) {
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
$feature = $script:Features[$paramKey]
Write-Host "Warning: '$($feature.Label)' requires a reboot to take full effect" -ForegroundColor Yellow
}
$rebootFeatures = Get-RebootFeatureLabels
foreach ($displayLabel in $rebootFeatures) {
Write-Host "Warning: '$displayLabel' requires a reboot to take full effect" -ForegroundColor Yellow
}
# Only restart if the powershell process matches the OS architecture.
+63 -4
View File
@@ -1,3 +1,19 @@
<#
.SYNOPSIS
Loads a registry backup from a JSON file and normalizes its contents.
.DESCRIPTION
Loads a registry backup from disk and returns a normalized representation
of its contents suitable for use by the restore workflow. Throws if the
file is missing, unreadable, or not valid JSON.
.PARAMETER FilePath
The absolute path to the registry backup JSON file to load.
.OUTPUTS
PSCustomObject
A normalized registry backup object produced by Normalize-RegistryBackup.
#>
function Load-RegistryBackupFromFile {
param(
[Parameter(Mandatory)]
@@ -18,6 +34,24 @@ function Load-RegistryBackupFromFile {
return Normalize-RegistryBackup -Backup $rawBackup
}
<#
.SYNOPSIS
Validates and normalizes a raw registry backup object.
.DESCRIPTION
Validates the structure and content of the supplied backup and converts
it into a normalized representation that can be safely consumed by the
restore workflow. Throws if validation fails.
.PARAMETER Backup
The raw backup object (typically parsed from JSON) to normalize.
.OUTPUTS
PSCustomObject
A normalized backup with Version, BackupType, CreatedAt, CreatedBy,
ComputerName, Target, SelectedFeatures, SelectedUndoFeatures, and
RegistryKeys properties.
#>
function Normalize-RegistryBackup {
param(
[Parameter(Mandatory)]
@@ -59,7 +93,8 @@ function Normalize-RegistryBackup {
}
elseif ($normalizedTarget -like 'CurrentUser:*') {
$targetCurrentUserName = $normalizedTarget.Substring(12)
if ([string]::IsNullOrWhiteSpace($targetCurrentUserName) -or ($targetCurrentUserName -ne $env:USERNAME)) {
if ([string]::IsNullOrWhiteSpace($targetCurrentUserName) -or
-not (Test-UserNameMatch -UserNameA $targetCurrentUserName -UserNameB $env:USERNAME)) {
$errors.Add("Backup was made for '$targetCurrentUserName', this does not match current user '$env:USERNAME'.")
}
}
@@ -97,9 +132,16 @@ function Normalize-RegistryBackup {
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) {
$errors.Add([string]$allowListValidationError)
else {
try {
$allowListValidationErrors = @(Test-RegistryBackupMatchesSelectedFeatures -SelectedFeatureIds @($selectedFeatures) -SelectedUndoFeatureIds @($selectedUndoFeatures) -Target $normalizedTarget -RegistryKeys @($normalizedKeys))
foreach ($allowListValidationError in $allowListValidationErrors) {
$errors.Add([string]$allowListValidationError)
}
}
catch {
$errors.Add("Failed to validate backup: $($_.Exception.Message)")
}
}
if ($errors.Count -gt 0) {
@@ -125,6 +167,23 @@ function Normalize-RegistryBackup {
}
}
<#
.SYNOPSIS
Restores registry state from a normalized backup object.
.DESCRIPTION
Applies the registry state described by the supplied backup back to the
registry, loading the appropriate user hive when required.
.PARAMETER Backup
A normalized backup object (as produced by Normalize-RegistryBackup) whose
RegistryKeys snapshots should be restored.
.OUTPUTS
PSCustomObject
Returns an object with a Result property set to $true when the restore
completes successfully.
#>
function Restore-RegistryBackupState {
param(
[Parameter(Mandatory)]
+8 -3
View File
@@ -158,9 +158,14 @@ function EnableStoreSearchSuggestions {
$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 })
if ($_.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Deny) { return $false }
if (($_.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -eq 0) { return $false }
try {
return ($_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $everyoneSid)
}
catch {
return $false
}
}
)
+11 -1
View File
@@ -13,6 +13,13 @@ function Get-UndoFeatureLabel {
return [string]$script:FeatureLabelLookup[$FeatureId]
}
<#
.SYNOPSIS
Returns the tweak actions that are pending based on the current UI state.
.OUTPUTS
[PSCustomObject[]] Objects with Action, FeatureId, and Label properties.
#>
function Get-PendingTweakActions {
param(
[System.Windows.Window]$Window,
@@ -54,7 +61,7 @@ function Get-PendingTweakActions {
$actions.Add([PSCustomObject]@{
Action = 'Undo'
FeatureId = [string]$mapping.FeatureId
Label = [string]$script:FeatureLabelLookup[$mapping.FeatureId]
Label = [string](Get-UndoFeatureLabel -FeatureId $mapping.FeatureId)
})
}
}
@@ -435,6 +442,9 @@ function Update-UserSelectionDescription {
$UserSelectionDescription.Text = "The default user template, affecting all new users created after this point. Useful for Sysprep deployment."
}
}
# Mirror the description text on the combo's tooltip so the same context is shown on hover.
$UserSelectionCombo.ToolTip = $UserSelectionDescription.Text
}
function Test-OtherUsername {
+8 -3
View File
@@ -132,6 +132,9 @@ function Build-DynamicTweaks {
# Convert HTML entity to character (e.g., &#xE72E; -> actual character)
if ($categoryIcon -match '&#x([0-9A-Fa-f]+);') {
$hexValue = [Convert]::ToInt32($matches[1], 16)
if ($WinVersion -lt 22000 -and $hexValue -eq 0xE794) {
$hexValue = 0xE734
}
$icon.Text = [char]$hexValue
}
$icon.Style = $Window.Resources['CategoryHeaderIcon']
@@ -201,9 +204,8 @@ function Build-DynamicTweaks {
foreach ($categoryObj in $orderedCategories) {
$categoryName = $categoryObj.Name
# Create/get card for this category
$panel = GetOrCreateCategoryCard -categoryObj $categoryObj
if (-not $panel) { continue }
# Card is created lazily on the first rendered item
$panel = $null
# Collect groups and features for this category, then sort by priority
$categoryItems = @()
@@ -287,6 +289,7 @@ function Build-DynamicTweaks {
if ($soleFeature.FeatureId -match '^Disable') { $opt = 'Disable' } elseif ($soleFeature.FeatureId -match '^Enable') { $opt = 'Enable' }
$items = @('No Change', $opt)
$comboName = ("Feature_{0}_Combo" -f $soleFeature.FeatureId) -replace '[^a-zA-Z0-9_]', ''
if (-not $panel) { $panel = GetOrCreateCategoryCard -categoryObj $categoryObj }
$combo = CreateLabeledCombo -parent $panel -labelText $soleFeature.Label -comboName $comboName -items $items
# attach tooltip from Features.json if present
if ($soleFeature.ToolTip -or $soleFeature.DisableWhenApplied -eq $true) {
@@ -311,6 +314,7 @@ function Build-DynamicTweaks {
$items = @('No Change') + ($filteredValues | ForEach-Object { $_.Label })
$comboName = 'Group_{0}Combo' -f $group.GroupId
if (-not $panel) { $panel = GetOrCreateCategoryCard -categoryObj $categoryObj }
$combo = CreateLabeledCombo -parent $panel -labelText $group.Label -comboName $comboName -items $items
# attach tooltip from UiGroups if present
if ($group.ToolTip) {
@@ -331,6 +335,7 @@ function Build-DynamicTweaks {
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_]', ''
if (-not $panel) { $panel = GetOrCreateCategoryCard -categoryObj $categoryObj }
$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) {
+8 -1
View File
@@ -7,7 +7,8 @@
populates the window's Resources with SolidColorBrush entries keyed by
category and resource name (e.g. "AppAccentColor"). Additionally loads and
merges shared XAML styles from the script's SharedStylesSchema path if
available.
available. Also resolves the icon font: Segoe Fluent Icons on Windows 11
and Segoe MDL2 Assets on Windows 10.
.PARAMETER window
The WPF Window whose resource dictionary will be populated.
@@ -122,6 +123,12 @@ function SetWindowThemeResources {
}
}
# Segoe Fluent Icons ships only on Windows 11 (build >= 22000).
# On Windows 10, fall back to Segoe MDL2 Assets.
$winBuild = Get-ItemPropertyValue 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' CurrentBuild
$iconFontName = if ($winBuild -ge 22000) { 'Segoe Fluent Icons' } else { 'Segoe MDL2 Assets' }
$window.Resources['AppIconFontFamily'] = [System.Windows.Media.FontFamily]::new($iconFontName)
# Load and merge shared styles
if ($script:SharedStylesSchema -and (Test-Path $script:SharedStylesSchema)) {
$sharedXaml = Get-Content -Path $script:SharedStylesSchema -Raw
+1 -7
View File
@@ -144,13 +144,7 @@ function Show-ApplyModal {
# Show completion message with reboot instructions if any applied features require reboot
if ($RestartExplorer) {
$rebootFeatures = @()
foreach ($paramKey in $script:Params.Keys) {
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
$feature = $script:Features[$paramKey]
$rebootFeatures += "$($feature.Label)"
}
}
$rebootFeatures = Get-RebootFeatureLabels
if ($rebootFeatures.Count -gt 0) {
foreach ($featureName in $rebootFeatures) {
+30 -2
View File
@@ -1,3 +1,24 @@
<#
.SYNOPSIS
Displays the Restore Backup wizard dialog.
.DESCRIPTION
Presents a modal wizard that lets the user choose and restore either a
registry backup or a Start Menu pinned-apps backup. Returns the user's
selection via $window.Tag.
.PARAMETER Owner
Optional parent WPF Window used to host this modal dialog. Defaults to the
shared $script:GuiWindow when not supplied.
.OUTPUTS
Hashtable
Returns a Hashtable describing the user's choice. Possible shapes:
RestoreRegistry - @{ Result='RestoreRegistry'; Backup=<normalizedBackup> }
RestoreStartMenu - @{ Result='RestoreStartMenu'; StartMenuScope=<scope>;
UseManualBackupFile=<bool>; BackupFilePath=<path|string> }
Cancelled - @{ Result='Cancelled' } (from New-RestoreDialogState)
#>
function Show-RestoreBackupDialog {
param(
[System.Windows.Window]$Owner = $null
@@ -295,9 +316,16 @@ function Show-RestoreBackupDialog {
}
Write-Host "Backup file selected: $($openDialog.FileName)"
$selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName
if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) {
try {
$selectedBackup = Load-RegistryBackupFromFile -FilePath $openDialog.FileName
if (-not (& $showRegistryOverview -SelectedBackup $selectedBackup -SelectedBackupFilePath $openDialog.FileName)) {
return
}
}
catch {
Show-MessageBox -Owner $window -Title 'Invalid Backup File' -Message "The selected file could not be loaded:`n$($_.Exception.Message)" -Button 'OK' -Icon 'Error' | Out-Null
return
}
-231
View File
@@ -1,231 +0,0 @@
param (
[switch]$CLI,
[switch]$Silent,
[switch]$Verbose,
[switch]$Sysprep,
[string]$LogPath,
[string]$User,
[switch]$NoRestartExplorer,
[switch]$CreateRestorePoint,
[switch]$RunDefaults,
[switch]$RunDefaultsLite,
[switch]$RunSavedSettings,
[string]$Config,
[string]$Apps,
[string]$AppRemovalTarget,
[switch]$RemoveApps,
[switch]$RemoveGamingApps,
[switch]$RemoveHPApps,
[switch]$ForceRemoveEdge,
[switch]$DisableDVR,
[switch]$DisableGameBarIntegration,
[switch]$EnableWindowsSandbox,
[switch]$EnableWindowsSubsystemForLinux,
[switch]$DisableTelemetry,
[switch]$DisableSearchHistory,
[switch]$DisableFastStartup,
[switch]$DisableBitlockerAutoEncryption,
[switch]$DisableModernStandbyNetworking,
[switch]$DisableStorageSense,
[switch]$DisableUpdateASAP,
[switch]$PreventUpdateAutoReboot,
[switch]$DisableDeliveryOptimization,
[switch]$DisableBing,
[switch]$DisableStoreSearchSuggestions,
[switch]$DisableDesktopSpotlight,
[switch]$DisableLockscreenTips,
[switch]$DisableSuggestions,
[switch]$DisableLocationServices,
[switch]$DisableFindMyDevice,
[switch]$DisableEdgeAds,
[switch]$DisableBraveBloat,
[switch]$DisableSettings365Ads,
[switch]$DisableSettingsHome,
[switch]$ShowHiddenFolders,
[switch]$ShowKnownFileExt,
[switch]$HideDupliDrive,
[switch]$EnableDarkMode,
[switch]$DisableTransparency,
[switch]$DisableAnimations,
[switch]$TaskbarAlignLeft,
[switch]$CombineTaskbarAlways, [switch]$CombineTaskbarWhenFull, [switch]$CombineTaskbarNever,
[switch]$CombineMMTaskbarAlways, [switch]$CombineMMTaskbarWhenFull, [switch]$CombineMMTaskbarNever,
[switch]$MMTaskbarModeAll, [switch]$MMTaskbarModeMainActive, [switch]$MMTaskbarModeActive,
[switch]$HideSearchTb, [switch]$ShowSearchIconTb, [switch]$ShowSearchLabelTb, [switch]$ShowSearchBoxTb,
[switch]$HideTaskview,
[switch]$DisableStartRecommended,
[switch]$DisableStartAllApps, [switch]$StartAllAppsCategory, [switch]$StartAllAppsGrid, [switch]$StartAllAppsList,
[switch]$DisableStartPhoneLink,
[switch]$DisableCopilot,
[switch]$DisableRecall,
[switch]$DisableClickToDo,
[switch]$DisableAISvcAutoStart,
[switch]$DisablePaintAI,
[switch]$DisableNotepadAI,
[switch]$DisableEdgeAI,
[switch]$DisableSearchHighlights,
[switch]$DisableWidgets,
[switch]$HideChat,
[switch]$EnableEndTask,
[switch]$EnableLastActiveClick,
[switch]$ClearStart,
[string]$ReplaceStart,
[switch]$ClearStartAllUsers,
[string]$ReplaceStartAllUsers,
[switch]$RevertContextMenu,
[switch]$DisableDragTray,
[switch]$DisableMouseAcceleration,
[switch]$DisableStickyKeys,
[switch]$DisableWindowSnapping,
[switch]$DisableSnapAssist,
[switch]$DisableSnapLayouts,
[switch]$HideTabsInAltTab, [switch]$Show3TabsInAltTab, [switch]$Show5TabsInAltTab, [switch]$Show20TabsInAltTab,
[switch]$HideHome,
[switch]$HideGallery,
[switch]$ExplorerToHome,
[switch]$ExplorerToThisPC,
[switch]$ExplorerToDownloads,
[switch]$ExplorerToOneDrive,
[switch]$AddFoldersToThisPC,
[switch]$HideOnedrive,
[switch]$Hide3dObjects,
[switch]$HideMusic,
[switch]$HideIncludeInLibrary,
[switch]$HideGiveAccessTo,
[switch]$HideShare,
[switch]$ShowDriveLettersFirst,
[switch]$ShowDriveLettersLast,
[switch]$ShowNetworkDriveLettersFirst,
[switch]$HideDriveLetters
)
# Show error if current powershell environment does not have LanguageMode set to FullLanguage
if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
Write-Host "Error: Win11Debloat is unable to run on your system. PowerShell execution is restricted by security policies" -ForegroundColor Red
Write-Output ""
Write-Output "Press enter to exit..."
Read-Host | Out-Null
Exit
}
Clear-Host
Write-Output "-------------------------------------------------------------------------------------------"
Write-Output " Win11Debloat Script - Get Dev"
Write-Output "-------------------------------------------------------------------------------------------"
$tempRootPath = $env:TEMP
$tempWorkPath = Join-Path $tempRootPath 'Win11Debloat'
$tempArchivePath = Join-Path $tempRootPath 'win11debloat.zip'
Write-Output "> Downloading Win11Debloat for development..."
# Download latest version of Win11Debloat from GitHub master branch as zip archive
try {
Invoke-RestMethod "https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip" -OutFile $tempArchivePath
}
catch {
Write-Host "Error: Unable to fetch master branch from GitHub. Please check your internet connection and try again." -ForegroundColor Red
Write-Output ""
Write-Output "Press enter to exit..."
Read-Host | Out-Null
Exit
}
# Remove old script folder if it exists, but keep configs, logs and backups
if (Test-Path $tempWorkPath) {
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'
$backupDir = Join-Path $tempWorkPath 'ConfigOld'
# Temporarily move existing config files if they exist to prevent them from being overwritten by the new script files, will be moved back after the new script is unpacked
if (Test-Path "$configDir") {
Write-Output ""
Write-Output "> Backing up existing config files..."
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
$filesToKeep = @(
'LastUsedSettings.json'
)
Get-ChildItem -Path "$configDir" -Recurse | Where-Object { $_.Name -in $filesToKeep } | Move-Item -Destination "$backupDir"
Remove-Item "$configDir" -Recurse -Force
}
Write-Output ""
Write-Output "> Unpacking..."
# Unzip archive to Win11Debloat folder
Expand-Archive $tempArchivePath $tempWorkPath
# Remove archive
Remove-Item $tempArchivePath
# Move files
Get-ChildItem -Path (Join-Path $tempWorkPath '*Win11Debloat-*') -Recurse | Move-Item -Destination $tempWorkPath
# Add existing config files back to Config folder
if (Test-Path "$backupDir") {
if (-not (Test-Path "$configDir")) {
New-Item -ItemType Directory -Path "$configDir" -Force | Out-Null
}
Write-Output ""
Write-Output "> Restoring existing config files..."
Get-ChildItem -Path "$backupDir" -Recurse | Move-Item -Destination "$configDir"
Remove-Item "$backupDir" -Recurse -Force
}
# Make list of arguments to pass on to the script
$arguments = $($PSBoundParameters.GetEnumerator() | ForEach-Object {
if ($_.Value -eq $true) {
"-$($_.Key)"
}
else {
"-$($_.Key) ""$($_.Value)"""
}
})
Write-Output ""
Write-Output "> Launching Win11Debloat..."
# Minimize the powershell window when no parameters are provided
if ($arguments.Count -eq 0) {
$windowStyle = "Minimized"
}
else {
$windowStyle = "Normal"
}
# Remove Powershell 7 modules from path to prevent module loading issues in the script
if ($PSVersionTable.PSVersion.Major -ge 7) {
$NewPSModulePath = $env:PSModulePath -split ';' | Where-Object -FilterScript { $_ -like '*WindowsPowerShell*' }
$env:PSModulePath = $NewPSModulePath -join ';'
}
# Run Win11Debloat script with the provided arguments
$debloatScriptPath = Join-Path $tempWorkPath 'Win11Debloat.ps1'
$debloatProcess = Start-Process powershell.exe -WindowStyle $windowStyle -PassThru -ArgumentList "-executionpolicy bypass -File `"$debloatScriptPath`" $arguments" -Verb RunAs
# Wait for the process to finish before continuing
if ($null -ne $debloatProcess) {
$debloatProcess.WaitForExit()
}
# Remove all remaining script files, except for configs, logs and backups
if (Test-Path $tempWorkPath) {
Write-Output ""
Write-Output "> Cleaning up..."
# Cleanup, remove Win11Debloat directory
Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
}
Write-Output ""
+17 -9
View File
@@ -1,7 +1,9 @@
param (
[switch]$Verbose,
[switch]$WhatIf,
[switch]$Dev,
[switch]$CLI,
[switch]$Silent,
[switch]$Verbose,
[switch]$Sysprep,
[string]$LogPath,
[string]$User,
@@ -26,10 +28,12 @@ param (
[switch]$DisableFastStartup,
[switch]$DisableBitlockerAutoEncryption,
[switch]$DisableModernStandbyNetworking,
[switch]$DisableNotifications,
[switch]$DisableStorageSense,
[switch]$DisableUpdateASAP,
[switch]$PreventUpdateAutoReboot,
[switch]$DisableDeliveryOptimization,
[switch]$DisableDeviceAutoAppDownload,
[switch]$DisableBing,
[switch]$DisableStoreSearchSuggestions,
[switch]$DisableDesktopSpotlight,
@@ -110,7 +114,7 @@ if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
Clear-Host
Write-Output "-------------------------------------------------------------------------------------------"
Write-Output " Win11Debloat Script - Get"
Write-Output " Win11Debloat Script"
Write-Output "-------------------------------------------------------------------------------------------"
$tempRootPath = $env:TEMP
@@ -119,13 +123,17 @@ $tempArchivePath = Join-Path $tempRootPath 'win11debloat.zip'
Write-Output "> Downloading Win11Debloat..."
# Download latest version of Win11Debloat from GitHub as zip archive
# Download Win11Debloat from GitHub as a zip archive.
try {
$LatestReleaseUri = (Invoke-RestMethod https://api.github.com/repos/Raphire/Win11Debloat/releases/latest).zipball_url
Invoke-RestMethod $LatestReleaseUri -OutFile $tempArchivePath
if ($Dev) {
$sourceUri = "https://github.com/Raphire/Win11Debloat/archive/refs/heads/master.zip"
} else {
$sourceUri = (Invoke-RestMethod https://api.github.com/repos/Raphire/Win11Debloat/releases/latest).zipball_url
}
Invoke-RestMethod $sourceUri -OutFile $tempArchivePath
}
catch {
Write-Host "Error: Unable to fetch latest release from GitHub. Please check your internet connection and try again." -ForegroundColor Red
Write-Host "Error: Unable to fetch required files from GitHub. Please check your internet connection and try again." -ForegroundColor Red
Write-Output ""
Write-Output "Press enter to exit..."
Read-Host | Out-Null
@@ -135,7 +143,7 @@ catch {
# Remove old script folder if it exists, but keep configs, logs and backups
if (Test-Path $tempWorkPath) {
Write-Output ""
Write-Output "> Cleaning up old Win11Debloat folder..."
Write-Output "> Cleaning up old script files..."
Get-ChildItem -Path $tempWorkPath -Exclude Config,Logs,Backups | Remove-Item -Recurse -Force
}
@@ -184,8 +192,8 @@ if (Test-Path "$backupDir") {
Remove-Item "$backupDir" -Recurse -Force
}
# Make list of arguments to pass on to the script
$arguments = $($PSBoundParameters.GetEnumerator() | ForEach-Object {
# Make list of arguments to pass on to the script (exclude the -Dev switch, which only affects this launcher)
$arguments = $($PSBoundParameters.GetEnumerator() | Where-Object { $_.Key -ne 'Dev' } | ForEach-Object {
if ($_.Value -eq $true) {
"-$($_.Key)"
}
@@ -0,0 +1,27 @@
<#
.SYNOPSIS
Resolves display labels for selected features that require reboot.
.DESCRIPTION
Combines parameter keys from both forward and undo selections, removes duplicates,
and returns the feature label that should be shown to users. Undo selections use
UndoLabel when available.
#>
function Get-RebootFeatureLabels {
$rebootFeatureLabels = [System.Collections.Generic.List[string]]::new()
$candidateParamKeys = (@($script:Params.Keys) + @($script:UndoParams.Keys)) | Select-Object -Unique
foreach ($paramKey in $candidateParamKeys) {
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
$feature = $script:Features[$paramKey]
$isUndo = $script:UndoParams.ContainsKey($paramKey)
$displayLabel = if ($isUndo -and $feature.UndoLabel) { $feature.UndoLabel } else { $feature.Label }
if (-not [string]::IsNullOrWhiteSpace([string]$displayLabel)) {
[void]$rebootFeatureLabels.Add([string]$displayLabel)
}
}
}
return $rebootFeatureLabels
}
+629 -40
View File
@@ -1,3 +1,17 @@
<#
.SYNOPSIS
Normalize a user-name string for lookup and comparison.
.DESCRIPTION
Strips zero-width chars and collapses whitespace so cosmetic input
differences don't break downstream lookups. Returns '' for blank input.
.PARAMETER Value
Raw user-supplied name to normalize.
.OUTPUTS
System.String
#>
function NormalizeUserLookupValue {
param(
[string]$Value
@@ -7,7 +21,6 @@ function NormalizeUserLookupValue {
return ''
}
# Remove zero-width characters and normalize whitespace for robust comparisons.
$normalized = $Value -replace '[\u200B-\u200D\uFEFF]', ''
$normalized = $normalized.Trim() -replace '\s+', ' '
return $normalized
@@ -17,6 +30,20 @@ if (-not $script:ResolvedUserSidCache) {
$script:ResolvedUserSidCache = @{}
}
<#
.SYNOPSIS
Build a form-agnostic cache key for a user name.
.DESCRIPTION
Normalized + lower-cased so the same identity hits regardless of
case, whitespace, or qualifier form. Returns '' for blank input.
.PARAMETER Value
User name to derive a key from.
.OUTPUTS
System.String
#>
function GetUserLookupCacheKey {
param(
[string]$Value
@@ -30,6 +57,49 @@ function GetUserLookupCacheKey {
return $normalizedValue.ToLowerInvariant()
}
<#
.SYNOPSIS
Normalize and de-duplicate a set of user-name candidates.
.DESCRIPTION
Centralizes the normalize/filter/dedupe step shared by the SID
resolution fallbacks so the dedupe semantic lives in one place.
Returns @() for empty or all-blank input.
.PARAMETER Candidates
Equivalent name forms to normalize.
.OUTPUTS
System.String[]
#>
function GetNormalizedLookupCandidates {
param(
[string[]]$Candidates
)
$normalized = @($Candidates) |
ForEach-Object { NormalizeUserLookupValue -Value $_ } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
Select-Object -Unique
# The unary comma prevents PowerShell from unwrapping a single-element array.
return ,@($normalized)
}
<#
.SYNOPSIS
Escape a string for safe embedding in a WQL single-quoted literal.
.DESCRIPTION
Doubles embedded single quotes; without this a user name containing
an apostrophe could break the WQL filter.
.PARAMETER Value
String to escape.
.OUTPUTS
System.String
#>
function EscapeWqlString {
param(
[string]$Value
@@ -42,6 +112,20 @@ function EscapeWqlString {
return $Value -replace "'", "''"
}
<#
.SYNOPSIS
Extract the local name segment from a possibly domain-qualified identity.
.DESCRIPTION
Reduces DOMAIN\user or user@domain to the bare leaf, since profile
folder leafs never carry the domain prefix. Returns '' for blank input.
.PARAMETER UserName
User name that may be domain-qualified.
.OUTPUTS
System.String
#>
function GetLocalUserNameSegment {
param(
[string]$UserName
@@ -63,6 +147,322 @@ function GetLocalUserNameSegment {
return $normalizedName
}
<#
.SYNOPSIS
Reduce a Win32_ComputerSystem.Domain value to a NetBIOS label.
.DESCRIPTION
Win32_ComputerSystem.Domain may be DNS-style (e.g. contoso.com);
prefer Win32_NTDomain.DomainName and fall back to the first DNS label
so cached suffixes stay single-label (user.CONTOSO, not
user.contoso.com). Returns the trimmed value as-is when already a
single label. Falls back safely (no match) for FQDNs like
corp.contoso.com whose NetBIOS label is CONTOSO.
.PARAMETER RawDomain
Value reported by Win32_ComputerSystem.Domain.
.OUTPUTS
System.String
#>
function ResolveNetBiosDomainName {
param(
[string]$RawDomain
)
$trimmed = NormalizeUserLookupValue -Value $RawDomain
if ([string]::IsNullOrWhiteSpace($trimmed)) {
return ''
}
try {
# Prefer the joined-domain instance over the local SAM shadow
# (DomainName == COMPUTERNAME); DomainControllerName may be $null.
$computerName = $env:COMPUTERNAME
$ntDomainInstances = @(Get-CimInstance -ClassName Win32_NTDomain -OperationTimeoutSec 5 -ErrorAction Stop |
Where-Object {
-not [string]::IsNullOrWhiteSpace($_.DomainName) -and
$_.DomainName -ine 'WORKGROUP' -and
$_.DomainName -ine $computerName
})
$ntDomainInstance = $ntDomainInstances |
Where-Object { -not [string]::IsNullOrWhiteSpace($_.DomainControllerName) } |
Select-Object -First 1
if (-not $ntDomainInstance -and $ntDomainInstances.Count -gt 0) {
$ntDomainInstance = $ntDomainInstances | Select-Object -First 1
}
if ($ntDomainInstance -and -not [string]::IsNullOrWhiteSpace($ntDomainInstance.DomainName)) {
$fromNtDomain = NormalizeUserLookupValue -Value $ntDomainInstance.DomainName
if (-not [string]::IsNullOrWhiteSpace($fromNtDomain)) {
return $fromNtDomain
}
}
}
catch {
# Fall through to DNS-label derivation.
}
if ($trimmed.Contains('.')) {
$leaf = NormalizeUserLookupValue -Value (($trimmed -split '\.')[0])
if (-not [string]::IsNullOrWhiteSpace($leaf)) {
return $leaf
}
}
return $trimmed
}
<#
.SYNOPSIS
Determine whether the local machine is joined to a domain.
.DESCRIPTION
Cached in script scope for the process lifetime. Returns $false on
error or workgroup. When joined, also caches the NetBIOS domain label
(ResolveNetBiosDomainName) for use as a profile-folder suffix.
.OUTPUTS
System.Boolean
#>
function Test-MachineIsDomainJoined {
if ($null -ne $script:MachineDomainJoinStateKnown) {
return [bool]$script:MachineIsDomainJoined
}
$script:MachineDomainJoinStateKnown = $true
$script:MachineIsDomainJoined = $false
$script:MachineNetBiosDomain = ''
try {
$computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
if ($null -ne $computerSystem -and $computerSystem.PartOfDomain) {
$script:MachineIsDomainJoined = $true
$script:MachineNetBiosDomain = ResolveNetBiosDomainName -RawDomain ([string]$computerSystem.Domain)
}
}
catch {
# Leave as $false; callers fall back to legacy matching.
}
return [bool]$script:MachineIsDomainJoined
}
<#
.SYNOPSIS
Return the NetBIOS domain suffix Windows appends to profile folders.
.DESCRIPTION
On domain-joined machines Windows writes profile folders as
user.CONTOSO; knowing the suffix lets a bare name match that folder.
Excluded on workgroup (USERDOMAIN == COMPUTERNAME) to avoid false
matches. Returns '' when not applicable.
.OUTPUTS
System.String
#>
function GetProfileFolderDomainSuffix {
if (-not (Test-MachineIsDomainJoined)) {
return ''
}
$domain = $env:USERDOMAIN
if ([string]::IsNullOrWhiteSpace($domain)) {
# USERDOMAIN can be empty in restricted contexts; fall back to the
# cached NetBIOS label (single-label, safe as a folder suffix).
$domain = $script:MachineNetBiosDomain
}
if ([string]::IsNullOrWhiteSpace($domain)) {
return ''
}
# USERDOMAIN == COMPUTERNAME means effectively standalone; a suffix here
# would produce false matches instead of disambiguation.
if ($domain -ieq $env:COMPUTERNAME) {
return ''
}
return $domain.Trim()
}
<#
.SYNOPSIS
Enumerate the name forms equivalent to a given identity.
.DESCRIPTION
One identity surfaces in different forms (bare, qualified,
domain-suffixed); enumerating equivalents lets cross-source matching
succeed without broadening workgroup validation. Returns @() for blank.
.PARAMETER Value
User name to expand into equivalent forms.
.OUTPUTS
System.String[]
#>
function GetUserNameMatchCandidates {
param(
[string]$Value
)
$normalized = NormalizeUserLookupValue -Value $Value
if ([string]::IsNullOrWhiteSpace($normalized)) {
return @()
}
$candidates = New-Object 'System.Collections.Generic.List[string]'
[void]$candidates.Add($normalized)
$localSegment = GetLocalUserNameSegment -UserName $normalized
if (-not [string]::IsNullOrWhiteSpace($localSegment) -and ($localSegment -ine $normalized)) {
[void]$candidates.Add($localSegment)
}
# Domain-suffixed forms only apply where Windows writes them.
$domainSuffix = GetProfileFolderDomainSuffix
if (-not [string]::IsNullOrWhiteSpace($domainSuffix)) {
# Prefer the local segment as the stem so DOMAIN\user still yields
# user.CONTOSO rather than the fully qualified string.
$stem = if (-not [string]::IsNullOrWhiteSpace($localSegment)) { $localSegment } else { $normalized }
if (-not [string]::IsNullOrWhiteSpace($stem)) {
$suffixedForm = "$stem.$domainSuffix"
$alreadyPresent = $false
foreach ($existing in $candidates) {
if ($existing -ieq $suffixedForm) { $alreadyPresent = $true; break }
}
if (-not $alreadyPresent) {
[void]$candidates.Add($suffixedForm)
}
}
# A suffixed input can also be referenced by its bare stem elsewhere
# (registry, backup metadata), so add that form too.
$suffixWithDot = ".$domainSuffix"
if ($normalized.Length -gt $suffixWithDot.Length -and $normalized.EndsWith($suffixWithDot, [System.StringComparison]::OrdinalIgnoreCase)) {
$bareStem = NormalizeUserLookupValue -Value ($normalized.Substring(0, $normalized.Length - $suffixWithDot.Length))
if (-not [string]::IsNullOrWhiteSpace($bareStem)) {
$alreadyPresent = $false
foreach ($existing in $candidates) {
if ($existing -ieq $bareStem) { $alreadyPresent = $true; break }
}
if (-not $alreadyPresent) {
[void]$candidates.Add($bareStem)
}
}
}
}
return $candidates.ToArray() | Select-Object -Unique
}
<#
.SYNOPSIS
Test whether a user name and a profile folder leaf share an account.
.DESCRIPTION
Compares candidate sets (via GetUserNameMatchCandidates) instead of
raw strings, so different forms of the same account still match.
.PARAMETER UserName
User-supplied name to compare.
.PARAMETER ProfileLeaf
On-disk profile folder leaf name to compare.
.OUTPUTS
System.Boolean
#>
function Test-UserNameMatchesProfileLeaf {
param(
[string]$UserName,
[string]$ProfileLeaf
)
if ([string]::IsNullOrWhiteSpace($UserName) -or [string]::IsNullOrWhiteSpace($ProfileLeaf)) {
return $false
}
$leafCandidates = @(GetUserNameMatchCandidates -Value $ProfileLeaf)
$userCandidates = @(GetUserNameMatchCandidates -Value $UserName)
foreach ($leaf in $leafCandidates) {
foreach ($user in $userCandidates) {
if ($leaf -ieq $user) {
return $true
}
}
}
return $false
}
<#
.SYNOPSIS
Test whether two user-name strings refer to the same account.
.DESCRIPTION
Accepts any equivalent candidate form so restore works across
sessions and join states. Workgroup stays a strict normalized equality
check to avoid broadening validation where suffixes aren't meaningful.
.PARAMETER UserNameA
First user name to compare.
.PARAMETER UserNameB
Second user name to compare.
.OUTPUTS
System.Boolean
#>
function Test-UserNameMatch {
param(
[string]$UserNameA,
[string]$UserNameB
)
if ([string]::IsNullOrWhiteSpace($UserNameA) -and [string]::IsNullOrWhiteSpace($UserNameB)) {
return $true
}
if ([string]::IsNullOrWhiteSpace($UserNameA) -or [string]::IsNullOrWhiteSpace($UserNameB)) {
return $false
}
# Workgroup: strict equality (no suffix disambiguation available).
if (-not (Test-MachineIsDomainJoined)) {
$normalizedA = NormalizeUserLookupValue -Value $UserNameA
$normalizedB = NormalizeUserLookupValue -Value $UserNameB
return ($normalizedA -ieq $normalizedB)
}
$candidatesA = @(GetUserNameMatchCandidates -Value $UserNameA)
$candidatesB = @(GetUserNameMatchCandidates -Value $UserNameB)
foreach ($a in $candidatesA) {
foreach ($b in $candidatesB) {
if ($a -ieq $b) {
return $true
}
}
}
return $false
}
<#
.SYNOPSIS
Memoize a resolved SID under every equivalent name form.
.DESCRIPTION
Keyed under all equivalent forms so later lookups short-circuit
regardless of which variant the caller holds. No-op on blank SID.
.PARAMETER Candidates
Equivalent name forms to key the cache entry under.
.PARAMETER Sid
Resolved SID to cache.
#>
function SetResolvedUserSidCache {
param(
[string[]]$Candidates,
@@ -81,6 +481,19 @@ function SetResolvedUserSidCache {
}
}
<#
.SYNOPSIS
Retrieve a previously cached resolved SID.
.DESCRIPTION
Probes every equivalent form; returns $null on a miss.
.PARAMETER Candidates
Equivalent name forms to probe the cache with.
.OUTPUTS
System.String
#>
function GetCachedResolvedUserSid {
param(
[string[]]$Candidates
@@ -96,6 +509,20 @@ function GetCachedResolvedUserSid {
return $null
}
<#
.SYNOPSIS
Attempt SID resolution via NTAccount translation.
.DESCRIPTION
Most authoritative name->SID source; only resolves names reachable
through standard security APIs. Returns $null on failure.
.PARAMETER UserName
Name to translate.
.OUTPUTS
System.String
#>
function TryResolveSidByNtAccount {
param(
[string]$UserName
@@ -119,12 +546,26 @@ function TryResolveSidByNtAccount {
return $null
}
<#
.SYNOPSIS
Attempt SID resolution against the local account database.
.DESCRIPTION
Prefers Get-LocalUser (typed, fast), falls back to Win32_UserAccount
CIM for older hosts or unavailable cmdlet. Returns $null if no match.
.PARAMETER Candidates
Equivalent name forms to try.
.OUTPUTS
System.String
#>
function TryResolveSidByLocalLookup {
param(
[string[]]$Candidates
)
$lookupCandidates = @($Candidates) | ForEach-Object { NormalizeUserLookupValue -Value $_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
$lookupCandidates = GetNormalizedLookupCandidates -Candidates $Candidates
if ($lookupCandidates.Count -eq 0) {
return $null
}
@@ -162,12 +603,26 @@ function TryResolveSidByLocalLookup {
return $null
}
<#
.SYNOPSIS
Recover a SID from the ProfileList registry hive.
.DESCRIPTION
Last-resort heuristic: matches the profile folder leaf to recover a
SID when name-resolution APIs fail. Returns $null on failure.
.PARAMETER Candidates
Equivalent name forms to match against profile folder leafs.
.OUTPUTS
System.String
#>
function TryResolveSidFromProfileList {
param(
[string[]]$Candidates
)
$lookupCandidates = @($Candidates) | ForEach-Object { NormalizeUserLookupValue -Value $_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique
$lookupCandidates = GetNormalizedLookupCandidates -Candidates $Candidates
if ($lookupCandidates.Count -eq 0) {
return $null
}
@@ -183,7 +638,12 @@ function TryResolveSidFromProfileList {
$leafName = NormalizeUserLookupValue -Value (Split-Path -Leaf $expandedPath)
foreach ($candidate in $lookupCandidates) {
if ($leafName -ieq $candidate) {
if (Test-MachineIsDomainJoined) {
if (Test-UserNameMatchesProfileLeaf -UserName $candidate -ProfileLeaf $leafName) {
return $sidKey.PSChildName
}
}
elseif ($leafName -ieq $candidate) {
return $sidKey.PSChildName
}
}
@@ -200,6 +660,26 @@ function TryResolveSidFromProfileList {
return $null
}
<#
.SYNOPSIS
Construct a resolved user context object.
.DESCRIPTION
Bundles UserName/UserSid/ProfilePath so callers don't re-derive or
thread three loose values.
.PARAMETER UserName
Normalized user name.
.PARAMETER UserSid
Resolved SID, if available.
.PARAMETER ProfilePath
Resolved profile folder path.
.OUTPUTS
System.Management.Automation.PSCustomObject
#>
function NewResolvedUserContext {
param(
[string]$UserName,
@@ -214,6 +694,74 @@ function NewResolvedUserContext {
}
}
<#
.SYNOPSIS
Return the qualified (DOMAIN\user) name of the current process when it matches the input.
.DESCRIPTION
Used to qualify a bare name on domain-joined boxes; returns $null when
the input doesn't match the current process identity.
.PARAMETER Candidate
Bare user name to compare against the current process identity.
.OUTPUTS
System.String
#>
function GetQualifiedProcessIdentityName {
param(
[string]$Candidate
)
$normalizedCandidate = NormalizeUserLookupValue -Value $Candidate
if ([string]::IsNullOrWhiteSpace($normalizedCandidate)) {
return $null
}
try {
$currentIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
if ($null -eq $currentIdentity) {
return $null
}
# Skip service/SYSTEM identities (no user profile).
$currentSidString = [string]$currentIdentity.User.Value
if ($currentSidString -in @('S-1-5-18', 'S-1-5-19', 'S-1-5-20')) {
return $null
}
$currentName = [string]$currentIdentity.Name
if ([string]::IsNullOrWhiteSpace($currentName)) {
return $null
}
$currentLocalSegment = GetLocalUserNameSegment -UserName $currentName
if (-not [string]::IsNullOrWhiteSpace($currentLocalSegment) -and $currentLocalSegment -ieq $normalizedCandidate) {
return $currentName
}
}
catch {
# Fall through to name-based resolution.
}
return $null
}
<#
.SYNOPSIS
Resolve a user name to its SID.
.DESCRIPTION
Always qualifies the input first (DOMAIN\user) and resolves that form;
never guesses from a bare name on domain-joined boxes to avoid
same-named local SAM shadowing.
.PARAMETER UserName
User name to resolve. May be bare, DOMAIN\user, or user@domain.
.OUTPUTS
System.String
#>
function ResolveUserSid {
param(
[Parameter(Mandatory)]
@@ -232,20 +780,15 @@ function ResolveUserSid {
$leafNameCandidates = @($localNameSegment)
}
$cacheCandidates = if ($hasQualifiedIdentity) {
# Unqualified inputs probe both the bare name and (for qualified inputs) the
# local leaf segment; qualified inputs pin to the caller's full form only.
$lookupCandidates = if ($hasQualifiedIdentity) {
@($candidateUserName)
}
else {
@($candidateUserName) + $leafNameCandidates | Select-Object -Unique
}
$localLookupCandidates = if ($hasQualifiedIdentity) {
@()
}
else {
@($candidateUserName) + $leafNameCandidates | Select-Object -Unique
}
$profileHeuristicCandidates = if ($leafNameCandidates.Count -gt 0) {
$leafNameCandidates
}
@@ -253,69 +796,99 @@ function ResolveUserSid {
@($candidateUserName)
}
$cachedSid = GetCachedResolvedUserSid -Candidates $cacheCandidates
$cachedSid = GetCachedResolvedUserSid -Candidates $lookupCandidates
if ($cachedSid) {
return $cachedSid
}
# Resolve fully-qualified identities first to avoid accidentally matching a local leaf account.
# Step 1: derive the qualified form(s) to resolve; never guess from a bare
# name on domain-joined boxes (local SAM nameshare risk).
$qualifiedNamesToTry = New-Object 'System.Collections.Generic.List[string]'
if ($hasQualifiedIdentity) {
$resolvedSid = TryResolveSidByNtAccount -UserName $candidateUserName
# Caller already qualified; honor verbatim.
[void]$qualifiedNamesToTry.Add($candidateUserName)
}
elseif (Test-MachineIsDomainJoined) {
# Prefer process identity (authoritative), then USERDOMAIN\input.
$processQualifiedName = GetQualifiedProcessIdentityName -Candidate $candidateUserName
if (-not [string]::IsNullOrWhiteSpace($processQualifiedName)) {
[void]$qualifiedNamesToTry.Add($processQualifiedName)
}
$domainSuffix = GetProfileFolderDomainSuffix
if (-not [string]::IsNullOrWhiteSpace($domainSuffix)) {
$domainQualifiedName = "$domainSuffix\$candidateUserName"
if (-not ($qualifiedNamesToTry -contains $domainQualifiedName)) {
[void]$qualifiedNamesToTry.Add($domainQualifiedName)
}
}
}
else {
# Workgroup: bare name is unambiguous.
[void]$qualifiedNamesToTry.Add($candidateUserName)
}
# Step 2: resolve qualified form(s) via NTAccount.Translate.
foreach ($qualifiedName in $qualifiedNamesToTry) {
$resolvedSid = TryResolveSidByNtAccount -UserName $qualifiedName
if ($resolvedSid) {
SetResolvedUserSidCache -Candidates $cacheCandidates -Sid $resolvedSid
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
}
$resolvedSid = TryResolveSidByLocalLookup -Candidates $localLookupCandidates
if ($resolvedSid) {
SetResolvedUserSidCache -Candidates $cacheCandidates -Sid $resolvedSid
return $resolvedSid
}
# Last-ditch NTAccount translation for non-qualified names.
if (-not $hasQualifiedIdentity) {
$resolvedSid = TryResolveSidByNtAccount -UserName $candidateUserName
# Step 3: local SAM fallback (workgroup only; skipped on domain to avoid
# nameshare shadowing).
if (-not (Test-MachineIsDomainJoined)) {
$resolvedSid = TryResolveSidByLocalLookup -Candidates $lookupCandidates
if ($resolvedSid) {
SetResolvedUserSidCache -Candidates $cacheCandidates -Sid $resolvedSid
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
}
# Step 4: ProfileList leaf heuristic (last resort; disambiguates by
# on-disk folder name, suffix-aware on domain boxes).
$resolvedSid = TryResolveSidFromProfileList -Candidates $profileHeuristicCandidates
if ($resolvedSid) {
SetResolvedUserSidCache -Candidates $cacheCandidates -Sid $resolvedSid
$allCacheKeys = @($candidateUserName) + $qualifiedNamesToTry | Select-Object -Unique
SetResolvedUserSidCache -Candidates $allCacheKeys -Sid $resolvedSid
return $resolvedSid
}
return $null
}
function ResolveUserProfilePath {
param(
[Parameter(Mandatory)]
[string]$UserName
)
<#
.SYNOPSIS
Resolve a user name to a full profile context (name, SID, path).
$userContext = ResolveUserProfileContext -UserName $UserName
if ($userContext) {
return $userContext.ProfilePath
}
.DESCRIPTION
SID-keyed registry data first (authoritative), then on-disk path
probing so resolution still succeeds when the account or SID can't be
looked up (deleted account, restricted context). Returns $null if not
found.
return $null
}
.PARAMETER UserName
User name whose profile context is required.
.OUTPUTS
System.Management.Automation.PSCustomObject
#>
function ResolveUserProfileContext {
param(
[Parameter(Mandatory)]
[string]$UserName
)
if ([string]::IsNullOrWhiteSpace($UserName)) {
$candidateUserName = NormalizeUserLookupValue -Value $UserName
if ([string]::IsNullOrWhiteSpace($candidateUserName)) {
return $null
}
$candidateUserName = NormalizeUserLookupValue -Value $UserName
$rootPaths = @(
(Join-Path $env:SystemDrive 'Users')
(Split-Path -Path $env:USERPROFILE -Parent)
@@ -372,10 +945,26 @@ function ResolveUserProfileContext {
continue
}
# Exact leaf match first (common case; avoids an unnecessary scan).
$candidateUserPath = Join-Path $rootPath $candidateUserName
if (Test-Path -LiteralPath $candidateUserPath -PathType Container) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $candidateUserPath)
}
# Only domain-joined boxes write suffixed folders; scanning workgroup
# roots would risk matching the wrong account.
if (Test-MachineIsDomainJoined) {
try {
foreach ($child in @(Get-ChildItem -LiteralPath $rootPath -Directory -ErrorAction SilentlyContinue)) {
if (Test-UserNameMatchesProfileLeaf -UserName $candidateUserName -ProfileLeaf $child.Name) {
return (NewResolvedUserContext -UserName $candidateUserName -UserSid $userSid -ProfilePath $child.FullName)
}
}
}
catch {
# Fall through to the next root path.
}
}
}
return $null
+1 -1
View File
@@ -15,7 +15,7 @@ function Test-TargetUserName {
}
}
if ($normalizedUserName -eq $env:USERNAME) {
if (Test-UserNameMatch -UserNameA $normalizedUserName -UserNameB $env:USERNAME) {
return [PSCustomObject]@{
IsValid = $false
UserName = $normalizedUserName
+32 -5
View File
@@ -30,7 +30,9 @@ param (
[switch]$DisableUpdateASAP,
[switch]$PreventUpdateAutoReboot,
[switch]$DisableDeliveryOptimization,
[switch]$DisableDeviceAutoAppDownload,
[switch]$DisableBing,
[switch]$DisableNotifications,
[switch]$DisableStoreSearchSuggestions,
[switch]$DisableSearchHighlights,
[switch]$DisableDesktopSpotlight,
@@ -99,6 +101,17 @@ param (
[switch]$HideDriveLetters
)
# Win11Debloat depends on Windows PowerShell 5.1 cmdlets (the Appx module's Get-AppxPackage /
# Remove-AppxPackage, and Get-ComputerRestorePoint) that do not load in PowerShell 7 (pwsh), where the
# Appx module fails with "Operation is not supported on this platform" (0x80131539). Without this guard
# the run continues and silently fails to remove any apps while still reporting success. See issue #675.
if ($PSVersionTable.PSEdition -eq 'Core') {
Write-Host "Win11Debloat requires Windows PowerShell 5.1, but it is running under PowerShell $($PSVersionTable.PSVersion) (pwsh / Core edition)." -ForegroundColor Red
Write-Host "App removal and system restore points rely on modules that are not available in PowerShell 7, so the run cannot complete correctly here." -ForegroundColor Red
Write-Host "Please re-run this script with Windows PowerShell instead (powershell.exe)." -ForegroundColor Yellow
exit 1
}
# Check if script is running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] `
[Security.Principal.WindowsIdentity]::GetCurrent()
@@ -111,7 +124,14 @@ if (-not $isAdmin) {
$choice = Read-Host "Restart as Administrator? (y/n)"
if ($choice -match '^[Yy]$') {
$elevatedArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath)
# Win32-safe escaping for arguments to pass to elevated process
function Format-ElevatedArg([string]$Value) {
$escaped = $Value -replace '(\\*)"', '$1$1\"'
$escaped = $escaped -replace '(\\+)$', '$1$1'
return '"' + $escaped + '"'
}
$elevatedArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Format-ElevatedArg $PSCommandPath))
foreach ($paramName in $PSBoundParameters.Keys) {
$paramValue = $PSBoundParameters[$paramName]
@@ -123,12 +143,14 @@ if (-not $isAdmin) {
}
else {
$elevatedArgs += "-$paramName"
$elevatedArgs += "$paramValue"
$elevatedArgs += (Format-ElevatedArg $paramValue)
}
}
if ($MyInvocation.UnboundArguments.Count -gt 0) {
$elevatedArgs += $MyInvocation.UnboundArguments
foreach ($unboundArg in $MyInvocation.UnboundArguments) {
$elevatedArgs += (Format-ElevatedArg "$unboundArg")
}
}
Start-Process powershell -ArgumentList $elevatedArgs -Verb RunAs
@@ -137,7 +159,7 @@ if (-not $isAdmin) {
}
# Define script-level variables & paths
$script:Version = "2026.06.24"
$script:Version = "2026.07.11"
$configPath = Join-Path $PSScriptRoot 'Config'
$logsPath = Join-Path $PSScriptRoot 'Logs'
$schemasPath = Join-Path $PSScriptRoot 'Schemas'
@@ -360,6 +382,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1"
. "$PSScriptRoot/Scripts/Helpers/GetFriendlyRegistryBackupTarget.ps1"
. "$PSScriptRoot/Scripts/Helpers/GetFriendlyTargetUserName.ps1"
. "$PSScriptRoot/Scripts/Helpers/Get-RebootFeatureLabels.ps1"
. "$PSScriptRoot/Scripts/Helpers/ImportConfigToParams.ps1"
. "$PSScriptRoot/Scripts/Helpers/GetTargetUserForAppRemoval.ps1"
. "$PSScriptRoot/Scripts/Helpers/Get-RegFileOperations.ps1"
@@ -540,7 +563,11 @@ if (($controlParamsCount -eq $script:Params.Keys.Count) -or ($script:Params.Keys
# (This also handles restore point creation if requested)
Invoke-AllChanges
RestartExplorer
# Restart Explorer process unless running in Sysprep or User context
if (-not ($script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User"))) {
RestartExplorer
}
Write-Output ""
Write-Output ""