mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-04-03 14:06:27 +00:00
Compare commits
15 Commits
2026.03.09
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd61902ed | ||
|
|
54edf224a3 | ||
|
|
3eade4ddba | ||
|
|
487b1fbee9 | ||
|
|
774c8ecd92 | ||
|
|
e05af92acc | ||
|
|
edd815fdbb | ||
|
|
17ee530962 | ||
|
|
999e442658 | ||
|
|
1d4cf4a801 | ||
|
|
7a3431e56b | ||
|
|
1b41f05743 | ||
|
|
c6535803ec | ||
|
|
c37bdcf5f2 | ||
|
|
d187679cd0 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -77,7 +77,7 @@ Win11Debloat/
|
||||
2. **Document Changes**: Update the `README.md` and other relevant documentation. Wiki documentation will be generated/updated based on the `Features.json` and `Apps.json` files.
|
||||
3. **Follow Existing Patterns**: Look at existing implementations for guidance.
|
||||
4. **Use Clear Naming**: Choose descriptive names for features, IDs, and registry files.
|
||||
5. **Minimal Changes**: Registry files should only modify what's necessary.
|
||||
5. **Minimal Changes**: Registry files should only modify what's necessary. Avoid using policies where possible.
|
||||
6. **Comment Your Code**: Add comments explaining your reasoning for complex logic in PowerShell scripts.
|
||||
7. **Version Constraints**: Use `MinVersion` and `MaxVersion` if a feature only applies to specific Windows versions.
|
||||
8. **Limit pull requests to 1 feature**: Keep pull requests limited to just one feature, this makes it easier to review your changes.
|
||||
|
||||
489
Config/Apps.json
489
Config/Apps.json
File diff suppressed because it is too large
Load Diff
@@ -187,6 +187,7 @@
|
||||
"Label": "Open File Explorer to",
|
||||
"ToolTip": "This setting allows you to choose the default location that File Explorer opens to.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 1,
|
||||
"Values": [
|
||||
{
|
||||
"Label": "Home",
|
||||
@@ -214,6 +215,39 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"GroupId": "DriveLetterPosition",
|
||||
"Label": "Drive letter position",
|
||||
"ToolTip": "This setting allows you to choose where drive letters are shown in File Explorer.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 20,
|
||||
"Values": [
|
||||
{
|
||||
"Label": "Show drive letters after drive label (Default)",
|
||||
"FeatureIds": [
|
||||
"ShowDriveLettersLast"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Label": "Show drive letters before drive label",
|
||||
"FeatureIds": [
|
||||
"ShowDriveLettersFirst"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Label": "Show network drive letters before drive label",
|
||||
"FeatureIds": [
|
||||
"ShowNetworkDriveLettersFirst"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Label": "Hide all drive letters",
|
||||
"FeatureIds": [
|
||||
"HideDriveLetters"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"GroupId": "ShowTabsInAltTab",
|
||||
"Label": "Show tabs from apps when snapping or pressing Alt+Tab",
|
||||
@@ -1220,6 +1254,7 @@
|
||||
"Label": "file extensions for known file types",
|
||||
"ToolTip": "This will show file extensions for known file types. By default, Windows hides file extensions for known file types which can lead to confusion and security risks.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 2,
|
||||
"Action": "Show",
|
||||
"RegistryKey": "Show_Extensions_For_Known_File_Types.reg",
|
||||
"ApplyText": "Enabling file extensions for known file types...",
|
||||
@@ -1233,6 +1268,7 @@
|
||||
"Label": "hidden files, folders and drives",
|
||||
"ToolTip": "By default, Windows hides certain files, folders and drives to prevent accidental modification or deletion. Turn this on to show all files in File Explorer.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 3,
|
||||
"Action": "Show",
|
||||
"RegistryKey": "Show_Hidden_Folders.reg",
|
||||
"ApplyText": "Unhiding hidden files, folders and drives...",
|
||||
@@ -1246,6 +1282,7 @@
|
||||
"Label": "'Home' from navigation pane",
|
||||
"ToolTip": "Hides the 'Home' section from the File Explorer navigation pane.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 4,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_Home_from_Explorer.reg",
|
||||
"ApplyText": "Hiding the home section from the File Explorer navigation pane...",
|
||||
@@ -1259,6 +1296,7 @@
|
||||
"Label": "'Gallery' from navigation pane",
|
||||
"ToolTip": "Hides the 'Gallery' section from the File Explorer navigation pane.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 5,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_Gallery_from_Explorer.reg",
|
||||
"ApplyText": "Hiding the gallery section from the File Explorer navigation pane...",
|
||||
@@ -1272,6 +1310,7 @@
|
||||
"Label": "duplicate removable drive entries",
|
||||
"ToolTip": "By default, Windows shows removable drives both under 'This PC' and in the navigation pane with its own entry. Enable this setting to only show removable drives under 'This PC'.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 6,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_duplicate_removable_drives_from_navigation_pane_of_File_Explorer.reg",
|
||||
"ApplyText": "Hiding duplicate removable drive entries from the File Explorer navigation pane...",
|
||||
@@ -1285,6 +1324,7 @@
|
||||
"Label": "common folders back to 'This PC' page",
|
||||
"ToolTip": "This setting will add common folders like Desktop, Documents, Downloads, Music, Pictures and Videos back to the 'This PC' page in File Explorer.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 7,
|
||||
"Action": "Add",
|
||||
"RegistryKey": "Add_All_Folders_Under_This_PC.reg",
|
||||
"ApplyText": "Adding all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...",
|
||||
@@ -1376,6 +1416,7 @@
|
||||
"Label": "'Include in library' option in the context menu",
|
||||
"ToolTip": "Hides the 'Include in library' option from the File Explorer context menu.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 8,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Disable_Include_in_library_from_context_menu.reg",
|
||||
"ApplyText": "Hiding 'Include in library' in the context menu...",
|
||||
@@ -1389,6 +1430,7 @@
|
||||
"Label": "'Give access to' option in the context menu",
|
||||
"ToolTip": "Hides the 'Give access to' option from the File Explorer context menu.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 9,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Disable_Give_access_to_context_menu.reg",
|
||||
"ApplyText": "Hiding 'Give access to' in the context menu...",
|
||||
@@ -1402,6 +1444,7 @@
|
||||
"Label": "'Share' option in the context menu",
|
||||
"ToolTip": "Hides the 'Share' option from the File Explorer context menu.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 10,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Disable_Share_from_context_menu.reg",
|
||||
"ApplyText": "Hiding 'Share' in the context menu...",
|
||||
@@ -1415,6 +1458,7 @@
|
||||
"Label": "'OneDrive' folder from navigation pane",
|
||||
"ToolTip": "Hides the 'OneDrive' folder from the File Explorer navigation pane.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 11,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_Onedrive_Folder.reg",
|
||||
"ApplyText": "Hiding the OneDrive folder from the File Explorer navigation pane...",
|
||||
@@ -1428,6 +1472,7 @@
|
||||
"Label": "'3D objects' folder under 'This PC'",
|
||||
"ToolTip": "Hides the '3D objects' folder from the File Explorer navigation pane.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 12,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_3D_Objects_Folder.reg",
|
||||
"ApplyText": "Hiding the 3D objects folder from the File Explorer navigation pane...",
|
||||
@@ -1441,6 +1486,7 @@
|
||||
"Label": "'Music' folder under 'This PC'",
|
||||
"ToolTip": "Hides the 'Music' folder from the File Explorer navigation pane.",
|
||||
"Category": "File Explorer",
|
||||
"Priority": 13,
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_Music_Folder.reg",
|
||||
"ApplyText": "Hiding the music folder from the File Explorer navigation pane...",
|
||||
@@ -1489,6 +1535,58 @@
|
||||
"RequiresReboot": true,
|
||||
"MinVersion": 22000,
|
||||
"MaxVersion": null
|
||||
},
|
||||
{
|
||||
"FeatureId": "ShowDriveLettersFirst",
|
||||
"Label": "show drive letters before drive label",
|
||||
"ToolTip": "This setting will show drive letters before the drive label in File Explorer.",
|
||||
"Category": "File Explorer",
|
||||
"Action": "Show first",
|
||||
"RegistryKey": "Show_Drive_Letters_First.reg",
|
||||
"ApplyText": "Showing drive letters before drive label...",
|
||||
"UndoAction": "Show last",
|
||||
"RegistryUndoKey": "Undo/Show_Drive_Letters_Last.reg",
|
||||
"MinVersion": null,
|
||||
"MaxVersion": null
|
||||
},
|
||||
{
|
||||
"FeatureId": "ShowDriveLettersLast",
|
||||
"Label": "show drive letters after drive label",
|
||||
"ToolTip": "This setting will show drive letters after the drive label in File Explorer (Default Windows behavior).",
|
||||
"Category": "File Explorer",
|
||||
"Action": "Show last",
|
||||
"RegistryKey": "Show_Drive_Letters_Last.reg",
|
||||
"ApplyText": "Showing drive letters after drive label...",
|
||||
"UndoAction": null,
|
||||
"RegistryUndoKey": null,
|
||||
"MinVersion": null,
|
||||
"MaxVersion": null
|
||||
},
|
||||
{
|
||||
"FeatureId": "ShowNetworkDriveLettersFirst",
|
||||
"Label": "show network drive letters before drive label",
|
||||
"ToolTip": "This setting will show only network drive letters before the drive label in File Explorer.",
|
||||
"Category": "File Explorer",
|
||||
"Action": "Show network first",
|
||||
"RegistryKey": "Show_Network_Drive_Letters_First.reg",
|
||||
"ApplyText": "Showing network drive letters before drive label...",
|
||||
"UndoAction": "Show last",
|
||||
"RegistryUndoKey": "Undo/Show_Drive_Letters_Last.reg",
|
||||
"MinVersion": null,
|
||||
"MaxVersion": null
|
||||
},
|
||||
{
|
||||
"FeatureId": "HideDriveLetters",
|
||||
"Label": "hide all drive letters",
|
||||
"ToolTip": "This setting will hide all drive letters from the File Explorer navigation pane and 'This PC'.",
|
||||
"Category": "File Explorer",
|
||||
"Action": "Hide",
|
||||
"RegistryKey": "Hide_Drive_Letters.reg",
|
||||
"ApplyText": "Hiding all drive letters...",
|
||||
"UndoAction": "Show last",
|
||||
"RegistryUndoKey": "Undo/Show_Drive_Letters_Last.reg",
|
||||
"MinVersion": null,
|
||||
"MaxVersion": null
|
||||
}
|
||||
]
|
||||
}
|
||||
67
README.md
67
README.md
@@ -97,23 +97,23 @@ Below is an overview of the key features and functionality offered by Win11Deblo
|
||||
#### AI Features
|
||||
|
||||
- Disable & remove Microsoft Copilot.
|
||||
- Disable Windows Recall. (W11 only)
|
||||
- Disable Click to Do, AI text & image analysis tool. (W11 only)
|
||||
- Prevent AI service (WSAIFabricSvc) from starting automatically. (W11 only)
|
||||
- Disable AI Features in Edge. (W11 only)
|
||||
- Disable AI Features in Paint. (W11 only)
|
||||
- Disable AI Features in Notepad. (W11 only)
|
||||
- Disable Windows Recall.
|
||||
- Disable Click to Do, AI text & image analysis tool.
|
||||
- Prevent AI service (WSAIFabricSvc) from starting automatically.
|
||||
- Disable AI Features in Edge.
|
||||
- Disable AI Features in Paint.
|
||||
- Disable AI Features in Notepad.
|
||||
|
||||
#### System
|
||||
|
||||
- Disable the Drag Tray for sharing & moving files. (W11 only)
|
||||
- Restore the old Windows 10 style context menu. (W11 only)
|
||||
- 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.
|
||||
- Disable the Sticky Keys keyboard shortcut. (W11 only)
|
||||
- Disable the Sticky Keys keyboard shortcut.
|
||||
- Disable Storage Sense automatic disk cleanup.
|
||||
- Disable fast start-up to ensure a full shutdown.
|
||||
- Disable BitLocker automatic device encryption.
|
||||
- Disable network connectivity during Modern Standby to reduce battery drain. (W11 only)
|
||||
- Disable network connectivity during Modern Standby to reduce battery drain.
|
||||
|
||||
#### Windows Update
|
||||
|
||||
@@ -129,49 +129,50 @@ Below is an overview of the key features and functionality offered by Win11Deblo
|
||||
|
||||
#### Start Menu & Search
|
||||
|
||||
- Remove or replace all pinned apps from the start menu. (W11 only)
|
||||
- Hide the recommended section in the start menu. (W11 only)
|
||||
- Hide the 'All Apps' section in the start menu. (W11 only)
|
||||
- Disable the Phone Link mobile devices integration in the start menu. (W11 only)
|
||||
- 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.
|
||||
- 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. (W11 only)
|
||||
- Disable Search Highlights (dynamic/branded content) in the taskbar search box. (W11 only)
|
||||
- Disable Microsoft Store app suggestions in Windows search.
|
||||
- Disable Search Highlights (dynamic/branded content) in the taskbar search box.
|
||||
- Disable local Windows search history.
|
||||
|
||||
#### Taskbar
|
||||
|
||||
- Align taskbar icons to the left. (W11 only)
|
||||
- Hide or change the search icon/box on the taskbar. (W11 only)
|
||||
- Hide the taskview button from the taskbar. (W11 only)
|
||||
- Align taskbar icons to the left.
|
||||
- Hide or change the search icon/box on the taskbar.
|
||||
- Hide the taskview button from the taskbar.
|
||||
- Disable widgets on the taskbar & lock screen.
|
||||
- Hide the chat (meet now) icon from the taskbar. (W10 only)
|
||||
- Enable the 'End Task' option in the taskbar right click menu. (W11 only)
|
||||
- Hide the chat (meet now) icon from the taskbar.
|
||||
- Enable the 'End Task' option in the taskbar right click menu.
|
||||
- Enable the 'Last Active Click' behavior in the taskbar app area. This allows you to repeatedly click on an application's icon in the taskbar to switch focus between the open windows of that application.
|
||||
- Choose how app icons are shown on the taskbar when using multiple monitors. (W11 only)
|
||||
- Choose combine mode for taskbar buttons and labels. (W11 only)
|
||||
- Choose how app icons are shown on the taskbar when using multiple monitors.
|
||||
- Choose combine mode for taskbar buttons and labels.
|
||||
|
||||
#### 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. (W11 only)
|
||||
- Hide the Home or Gallery 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. (W11 only)
|
||||
- Hide the 3D objects, music or OneDrive folder from the File Explorer navigation pane. (W10 only)
|
||||
- Hide the 'Include in library', 'Give access to' and 'Share' options from the context menu. (W10 only)
|
||||
- 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. (W11 only)
|
||||
- Disable Snap Assist suggestions when snapping a window. (W11 only)
|
||||
- Disable Snap Layout suggestions when dragging windows to the top of screen and when hovering on the maximize button. (W11 only)
|
||||
- Change if tabs are shown when snapping or pressing Alt+Tab. (W11 only)
|
||||
- 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.
|
||||
|
||||
#### Optional Windows Features
|
||||
|
||||
- Enable Windows Sandbox, a lightweight desktop environment for safely running applications in isolation. (W11 only)
|
||||
- Enable Windows Subsystem for Linux which allows you to run a Linux environment directly on Windows. (W11 only)
|
||||
- Enable Windows Sandbox, a lightweight desktop environment for safely running applications in isolation.
|
||||
- Enable Windows Subsystem for Linux which allows you to run a Linux environment directly on Windows.
|
||||
|
||||
#### Other
|
||||
|
||||
|
||||
4
Regfiles/Hide_Drive_Letters.reg
Normal file
4
Regfiles/Hide_Drive_Letters.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000002
|
||||
4
Regfiles/Show_Drive_Letters_First.reg
Normal file
4
Regfiles/Show_Drive_Letters_First.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000004
|
||||
4
Regfiles/Show_Drive_Letters_Last.reg
Normal file
4
Regfiles/Show_Drive_Letters_Last.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000000
|
||||
4
Regfiles/Show_Network_Drive_Letters_First.reg
Normal file
4
Regfiles/Show_Network_Drive_Letters_First.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000001
|
||||
4
Regfiles/Sysprep/Hide_Drive_Letters.reg
Normal file
4
Regfiles/Sysprep/Hide_Drive_Letters.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000002
|
||||
4
Regfiles/Sysprep/Show_Drive_Letters_First.reg
Normal file
4
Regfiles/Sysprep/Show_Drive_Letters_First.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000004
|
||||
4
Regfiles/Sysprep/Show_Drive_Letters_Last.reg
Normal file
4
Regfiles/Sysprep/Show_Drive_Letters_Last.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000000
|
||||
4
Regfiles/Sysprep/Show_Network_Drive_Letters_First.reg
Normal file
4
Regfiles/Sysprep/Show_Network_Drive_Letters_First.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[hkey_users\default\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000001
|
||||
4
Regfiles/Undo/Show_Drive_Letters_Last.reg
Normal file
4
Regfiles/Undo/Show_Drive_Letters_Last.reg
Normal file
@@ -0,0 +1,4 @@
|
||||
Windows Registry Editor Version 5.00
|
||||
|
||||
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
|
||||
"ShowDriveLettersFirst"=dword:00000000
|
||||
@@ -8,7 +8,7 @@
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="True"
|
||||
Topmost="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="True"
|
||||
Topmost="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||
|
||||
51
Schemas/BubbleHint.xaml
Normal file
51
Schemas/BubbleHint.xaml
Normal file
@@ -0,0 +1,51 @@
|
||||
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="BubblePanel"
|
||||
SnapsToDevicePixels="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0"
|
||||
Margin="10,10,10,8">
|
||||
<Border Name="BubbleBorder"
|
||||
Background="{DynamicResource CardBgColor}"
|
||||
BorderBrush="{DynamicResource ButtonBorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="10,7,10,7">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="9"
|
||||
Opacity="0.16"
|
||||
ShadowDepth="2"
|
||||
Direction="270"
|
||||
Color="Black"/>
|
||||
</Border.Effect>
|
||||
<TextBlock Name="BubbleText"
|
||||
Text="View the selected changes here"
|
||||
TextWrapping="Wrap"
|
||||
MaxWidth="260"
|
||||
Foreground="{DynamicResource FgColor}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,-9,0,0"
|
||||
Panel.ZIndex="1"
|
||||
Width="12"
|
||||
Height="8">
|
||||
<Polygon Name="BubblePointer"
|
||||
Points="0,0 12,0 6,7"
|
||||
Fill="{DynamicResource CardBgColor}"
|
||||
Stroke="{DynamicResource ButtonBorderColor}"
|
||||
StrokeThickness="1"
|
||||
Stretch="Fill"/>
|
||||
|
||||
<Rectangle VerticalAlignment="Top"
|
||||
Height="2"
|
||||
Margin="1,-1,1,0"
|
||||
Fill="{DynamicResource CardBgColor}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
77
Schemas/ImportExportConfigWindow.xaml
Normal file
77
Schemas/ImportExportConfigWindow.xaml
Normal file
@@ -0,0 +1,77 @@
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Select Settings to Import/Export"
|
||||
Width="440"
|
||||
SizeToContent="Height"
|
||||
MaxHeight="501"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Background="{DynamicResource CardBgColor}"
|
||||
Margin="25">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black"
|
||||
Opacity="0.15"
|
||||
BlurRadius="20"
|
||||
ShadowDepth="0"
|
||||
Direction="0"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Title Bar -->
|
||||
<Grid Grid.Row="0" x:Name="TitleBar" Height="40" Background="Transparent">
|
||||
<TextBlock x:Name="TitleText"
|
||||
Foreground="{DynamicResource FgColor}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Center"
|
||||
Margin="16,0,0,0"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Content -->
|
||||
<StackPanel Grid.Row="1" x:Name="ContentPanel" Margin="20,12,20,9">
|
||||
<TextBlock x:Name="PromptText"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="14"
|
||||
LineHeight="20"
|
||||
Foreground="{DynamicResource FgColor}"
|
||||
Margin="0,0,0,14"/>
|
||||
<!-- Checkboxes are added dynamically at runtime -->
|
||||
<StackPanel x:Name="CheckboxPanel"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Button Footer -->
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource BgColor}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Padding="16,12"
|
||||
CornerRadius="0,0,8,8">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Name="OkButton"
|
||||
Content="OK"
|
||||
Height="32" MinWidth="80" Margin="4,0"
|
||||
Style="{DynamicResource PrimaryButtonStyle}"/>
|
||||
<Button x:Name="CancelButton"
|
||||
Content="Cancel"
|
||||
Height="32" MinWidth="80" Margin="4,0"
|
||||
Style="{DynamicResource SecondaryButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,16 +1,33 @@
|
||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||||
Title="Win11Debloat"
|
||||
MinWidth="1130" MinHeight="600"
|
||||
MaxWidth="1400"
|
||||
ResizeMode="CanResize"
|
||||
SnapsToDevicePixels="True"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
AllowsTransparency="False"
|
||||
Background="{DynamicResource BgColor}"
|
||||
Foreground="{DynamicResource FgColor}">
|
||||
<shell:WindowChrome.WindowChrome>
|
||||
<shell:WindowChrome ResizeBorderThickness="5"
|
||||
CaptionHeight="32"
|
||||
CornerRadius="8"
|
||||
GlassFrameThickness="0"
|
||||
UseAeroCaptionButtons="False"/>
|
||||
</shell:WindowChrome.WindowChrome>
|
||||
<Window.Resources>
|
||||
<!-- Sort column header hover style -->
|
||||
<Style x:Key="SortHeaderBtnStyle" TargetType="StackPanel">
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Opacity" Value="0.75"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<!-- ComboBox Style -->
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Background" Value="{DynamicResource ComboBgColor}"/>
|
||||
@@ -99,13 +116,16 @@
|
||||
PlacementTarget="{Binding ElementName=ToggleButton}"
|
||||
VerticalOffset="1"
|
||||
HorizontalOffset="0">
|
||||
<Grid x:Name="DropDown" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||
<Grid x:Name="DropDown" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}" Margin="12">
|
||||
<Border x:Name="DropDownBorder"
|
||||
Background="{DynamicResource ComboItemBgColor}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="5,4,5,1">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="12" Opacity="0.25" ShadowDepth="4"/>
|
||||
</Border.Effect>
|
||||
<ScrollViewer Margin="0,2,0,0"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
@@ -338,7 +358,10 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border x:Name="CheckBoxBorder" Grid.Column="0" Width="18" Height="18" Background="{DynamicResource CheckBoxBgColor}" BorderBrush="{DynamicResource CheckBoxBorderColor}" BorderThickness="1" CornerRadius="4" Margin="0,0,8,0">
|
||||
<TextBlock x:Name="CheckMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="12" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"/>
|
||||
<Grid>
|
||||
<TextBlock x:Name="CheckMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="12" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"/>
|
||||
<TextBlock x:Name="IndeterminateMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed" Margin="1,1,0,0" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="0,0,0,2"/>
|
||||
</Grid>
|
||||
@@ -353,6 +376,13 @@
|
||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
||||
<Setter TargetName="CheckMark" Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="{x:Null}">
|
||||
<Setter TargetName="IndeterminateMark" Property="Visibility" Value="Visible"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
||||
<Setter TargetName="IndeterminateMark" Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonDisabled}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
|
||||
@@ -368,6 +398,15 @@
|
||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
||||
</MultiTrigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="{x:Null}"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
||||
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
@@ -407,10 +446,37 @@
|
||||
<Setter Property="Margin" Value="8,0,8,0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Column widths for the apps table row grid -->
|
||||
<GridLength x:Key="AppTableCol0Width">160</GridLength>
|
||||
<GridLength x:Key="AppTableCol1Width">1*</GridLength>
|
||||
<GridLength x:Key="AppTableCol2Width">286</GridLength>
|
||||
<!-- Column widths for the app table rows and header (dot | name | description | id) -->
|
||||
<GridLength x:Key="AppTableDotColWidth">16</GridLength>
|
||||
<GridLength x:Key="AppTableNameColWidth">151</GridLength>
|
||||
<GridLength x:Key="AppTableDescColWidth">1*</GridLength>
|
||||
<GridLength x:Key="AppTableIdColWidth">261</GridLength>
|
||||
|
||||
<!-- Recommendation dot shape style for app table rows -->
|
||||
<Style x:Key="AppRecommendationDotStyle" TargetType="Ellipse">
|
||||
<Setter Property="Width" Value="9"/>
|
||||
<Setter Property="Height" Value="9"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<!-- Container style for each dynamically-created app table row -->
|
||||
<Style x:Key="AppTableRowStyle" TargetType="Grid">
|
||||
<Setter Property="Margin" Value="0,1,0,0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Style for dynamically-created preset checkboxes in the Quick Select popup -->
|
||||
<Style x:Key="PresetCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||
<Setter Property="Margin" Value="8,4"/>
|
||||
</Style>
|
||||
|
||||
<!-- Progress step indicator fill colors -->
|
||||
<SolidColorBrush x:Key="ProgressActiveColor" Color="#0067c0"/>
|
||||
<SolidColorBrush x:Key="ProgressInactiveColor" Color="#808080"/>
|
||||
|
||||
<!-- Validation feedback colors for username input -->
|
||||
<SolidColorBrush x:Key="ValidationErrorColor" Color="#c42b1c"/>
|
||||
<SolidColorBrush x:Key="ValidationSuccessColor" Color="#28a745"/>
|
||||
|
||||
<!-- Title Bar Button Style -->
|
||||
<Style x:Key="TitleBarButton" TargetType="Button">
|
||||
@@ -450,10 +516,10 @@
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonHover}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource TitlebarButtonHover}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonPressed}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource TitlebarButtonPressed}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
@@ -480,15 +546,22 @@
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="4"/>
|
||||
<Setter Property="HasDropShadow" Value="True"/>
|
||||
<Setter Property="HorizontalOffset" Value="-12"/>
|
||||
<Setter Property="VerticalOffset" Value="-12"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ContextMenu">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<StackPanel IsItemsHost="True"/>
|
||||
<Border Margin="12">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="12" Opacity="0.25" ShadowDepth="4"/>
|
||||
</Border.Effect>
|
||||
<StackPanel IsItemsHost="True"/>
|
||||
</Border>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
@@ -535,18 +608,11 @@
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||
<Border x:Name="MainBorder" BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Background="{DynamicResource BgColor}"
|
||||
Margin="25">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="Black"
|
||||
Opacity="0.15"
|
||||
BlurRadius="20"
|
||||
ShadowDepth="0"
|
||||
Direction="0"/>
|
||||
</Border.Effect>
|
||||
Margin="0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32"/>
|
||||
@@ -555,18 +621,18 @@
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Resize Borders -->
|
||||
<Rectangle x:Name="ResizeLeft" Width="5" HorizontalAlignment="Left" Fill="Transparent" Cursor="SizeWE" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="100"/>
|
||||
<Rectangle x:Name="ResizeRight" Width="5" HorizontalAlignment="Right" Fill="Transparent" Cursor="SizeWE" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="100"/>
|
||||
<Rectangle x:Name="ResizeTop" Height="5" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNS" Grid.Row="0" Panel.ZIndex="100"/>
|
||||
<Rectangle x:Name="ResizeBottom" Height="5" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNS" Grid.Row="2" Panel.ZIndex="100"/>
|
||||
<Rectangle x:Name="ResizeTopLeft" Width="8" Height="8" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNWSE" Grid.Row="0" Panel.ZIndex="101"/>
|
||||
<Rectangle x:Name="ResizeTopRight" Width="8" Height="8" HorizontalAlignment="Right" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNESW" Grid.Row="0" Panel.ZIndex="101"/>
|
||||
<Rectangle x:Name="ResizeBottomLeft" Width="8" Height="8" HorizontalAlignment="Left" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNESW" Grid.Row="2" Panel.ZIndex="101"/>
|
||||
<Rectangle x:Name="ResizeBottomRight" Width="8" Height="8" HorizontalAlignment="Right" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNWSE" Grid.Row="2" Panel.ZIndex="101"/>
|
||||
<Rectangle x:Name="ResizeLeft" Width="5" HorizontalAlignment="Left" Fill="Transparent" Cursor="SizeWE" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="100" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeRight" Width="5" HorizontalAlignment="Right" Fill="Transparent" Cursor="SizeWE" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="100" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeTop" Height="5" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNS" Grid.Row="0" Panel.ZIndex="100" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeBottom" Height="5" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNS" Grid.Row="2" Panel.ZIndex="100" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeTopLeft" Width="8" Height="8" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNWSE" Grid.Row="0" Panel.ZIndex="101" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeTopRight" Width="8" Height="8" HorizontalAlignment="Right" VerticalAlignment="Top" Fill="Transparent" Cursor="SizeNESW" Grid.Row="0" Panel.ZIndex="101" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeBottomLeft" Width="8" Height="8" HorizontalAlignment="Left" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNESW" Grid.Row="2" Panel.ZIndex="101" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
<Rectangle x:Name="ResizeBottomRight" Width="8" Height="8" HorizontalAlignment="Right" VerticalAlignment="Bottom" Fill="Transparent" Cursor="SizeNWSE" Grid.Row="2" Panel.ZIndex="101" Visibility="Collapsed" IsHitTestVisible="False"/>
|
||||
|
||||
<!-- Custom Title Bar -->
|
||||
<Grid Grid.Row="0" x:Name="TitleBar">
|
||||
<Border Background="{DynamicResource BgColor}" CornerRadius="8,8,0,0">
|
||||
<Border x:Name="TitleBarBackground" Background="{DynamicResource BgColor}" CornerRadius="8,8,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -579,10 +645,33 @@
|
||||
Margin="12,0,0,0"
|
||||
FontSize="12"/>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<Button x:Name="KofiBtn" Content="" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Support the creator" AutomationProperties.Name="Support the creator"/>
|
||||
<Button x:Name="MenuBtn" Content="" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
|
||||
<Button x:Name="KofiBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="" 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="" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu x:Name="MainMenu">
|
||||
<ContextMenu.Resources>
|
||||
<Style x:Key="{x:Static MenuItem.SeparatorStyleKey}" TargetType="Separator">
|
||||
<Setter Property="Margin" Value="4,6"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Separator">
|
||||
<Border Height="1" Background="{DynamicResource BorderColor}" SnapsToDevicePixels="True" HorizontalAlignment="Stretch"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ContextMenu.Resources>
|
||||
<MenuItem x:Name="ImportConfigBtn" Header="Import config" AutomationProperties.Name="Import configuration">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem x:Name="ExportConfigBtn" Header="Export config" AutomationProperties.Name="Export configuration">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="" FontFamily="Segoe Fluent Icons" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem x:Name="MenuDocumentation" Header="Documentation" AutomationProperties.Name="Documentation">
|
||||
<MenuItem.Icon>
|
||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
||||
@@ -606,7 +695,7 @@
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
</Button>
|
||||
<Button x:Name="CloseBtn" Content="" Style="{StaticResource CloseButton}" ToolTip="Close" AutomationProperties.Name="Close"/>
|
||||
<Button x:Name="CloseBtn" shell:WindowChrome.IsHitTestVisibleInChrome="True" Content="" Style="{StaticResource CloseButton}" ToolTip="Close" AutomationProperties.Name="Close"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -710,9 +799,60 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Column="0">
|
||||
<Button x:Name="DefaultAppsBtn" Content="Select Default Apps" ToolTip="Select the default selection of apps" Style="{DynamicResource SecondaryButtonStyle}" Height="32" Padding="10,0" Margin="0,0,10,0" AutomationProperties.Name="Select Default Apps"/>
|
||||
<Button x:Name="LoadLastUsedAppsBtn" Content="Select Last Used Selection" ToolTip="Select the apps that were selected the last time Win11Debloat was run" Style="{DynamicResource SecondaryButtonStyle}" Height="32" Padding="10,0" Margin="0,0,10,0" AutomationProperties.Name="Select Last Used Selection"/>
|
||||
<ToggleButton x:Name="PresetsBtn" ToolTip="Select or clear app presets" Height="32" Padding="10,0" Margin="0,0,10,0" AutomationProperties.Name="App Presets">
|
||||
<ToggleButton.Style>
|
||||
<Style TargetType="ToggleButton">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonBg}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderColor}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border x:Name="Border" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,1"/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonHover}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonPressed}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryButtonHover}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Quick Select" FontSize="13" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||
<TextBlock x:Name="PresetsArrow" Text="" FontFamily="Segoe Fluent Icons" FontSize="10" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5">
|
||||
<TextBlock.RenderTransform>
|
||||
<RotateTransform x:Name="PresetsArrowRotation" Angle="0"/>
|
||||
</TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ToggleButton>
|
||||
<Button x:Name="ClearAppSelectionBtn" Content="Clear Selection" ToolTip="Clear all selected apps" Style="{DynamicResource SecondaryButtonStyle}" Height="32" Padding="10,0" Margin="0,0,10,0" AutomationProperties.Name="Clear Selection"/>
|
||||
<Popup x:Name="PresetsPopup" PlacementTarget="{Binding ElementName=PresetsBtn}" Placement="Bottom" StaysOpen="True" AllowsTransparency="True" VerticalOffset="2">
|
||||
<Border Background="{DynamicResource CardBgColor}" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="6" Padding="4,6" Margin="12">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="12" Opacity="0.25" ShadowDepth="4"/>
|
||||
</Border.Effect>
|
||||
<StackPanel x:Name="PresetsPanel" MinWidth="220">
|
||||
<CheckBox x:Name="PresetDefaultApps" Content="Default selection" IsThreeState="True" Foreground="{DynamicResource FgColor}" Margin="8,4" AutomationProperties.Name="Default selection"/>
|
||||
<CheckBox x:Name="PresetLastUsed" Content="Last used selection" IsThreeState="True" Foreground="{DynamicResource FgColor}" Margin="8,4" AutomationProperties.Name="Last used selection"/>
|
||||
<Separator Margin="4,6" Background="{DynamicResource BorderColor}"/>
|
||||
<StackPanel x:Name="JsonPresetsPanel"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Popup>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox x:Name="OnlyInstalledAppsBox" Grid.Column="2" Content="Only show installed apps" IsChecked="False" Foreground="{DynamicResource FgColor}" VerticalAlignment="Center" AutomationProperties.Name="Only show installed apps"/>
|
||||
@@ -754,15 +894,31 @@
|
||||
</Grid.RowDefinitions>
|
||||
<!-- Column Headers -->
|
||||
<Border Grid.Row="0" Background="{DynamicResource TableHeaderColor}" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1,1,1,0" CornerRadius="4,4,0,0">
|
||||
<Grid Margin="26,6,8,8">
|
||||
<Grid Margin="42,6,23,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition Width="{StaticResource AppTableDotColWidth}"/>
|
||||
<ColumnDefinition Width="{StaticResource AppTableNameColWidth}"/>
|
||||
<ColumnDefinition Width="{StaticResource AppTableDescColWidth}"/>
|
||||
<ColumnDefinition Width="{StaticResource AppTableIdColWidth}"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Name" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource FgColor}" Margin="16,0,0,0"/>
|
||||
<TextBlock Grid.Column="1" Text="Description" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource FgColor}" Margin="24,0,0,0"/>
|
||||
<TextBlock Grid.Column="2" Text="App ID" FontWeight="SemiBold" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
||||
<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 FgColor}"/>
|
||||
<TextBlock x:Name="SortArrowName" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource FgColor}" 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 FgColor}"/>
|
||||
<TextBlock x:Name="SortArrowDescription" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource FgColor}" 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 FgColor}"/>
|
||||
<TextBlock x:Name="SortArrowAppId" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource FgColor}" VerticalAlignment="Center" Margin="5,1,0,0" Opacity="0.3" RenderTransformOrigin="0.5,0.5">
|
||||
<TextBlock.RenderTransform><RotateTransform Angle="0"/></TextBlock.RenderTransform>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<!-- Apps content -->
|
||||
@@ -772,7 +928,26 @@
|
||||
<StackPanel x:Name="AppSelectionPanel" Margin="10,4,0,4"/>
|
||||
</ScrollViewer>
|
||||
<Border x:Name="LoadingAppsIndicator" CornerRadius="0,0,4,4" Background="{DynamicResource CardBgColor}" Opacity="0.8" Visibility="Collapsed">
|
||||
<TextBlock Text="Loading apps..." FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource FgColor}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock Text="" FontFamily="Segoe Fluent Icons" FontSize="28" Foreground="{DynamicResource FgColor}" HorizontalAlignment="Center" Margin="0,0,0,8" RenderTransformOrigin="0.5,0.5">
|
||||
<TextBlock.RenderTransform>
|
||||
<RotateTransform Angle="0"/>
|
||||
</TextBlock.RenderTransform>
|
||||
<TextBlock.Triggers>
|
||||
<EventTrigger RoutedEvent="Loaded">
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
|
||||
From="0" To="360"
|
||||
Duration="0:0:1.5"
|
||||
RepeatBehavior="Forever"/>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</TextBlock.Triggers>
|
||||
</TextBlock>
|
||||
<TextBlock Text="Loading apps..." FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource FgColor}" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -988,7 +1163,7 @@
|
||||
|
||||
<!-- Review & Apply Section -->
|
||||
<StackPanel Grid.Row="1" HorizontalAlignment="Stretch" Background="{DynamicResource BgColor}">
|
||||
<Button x:Name="ReviewChangesBtn" Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Center" Margin="0,4,0,8" AutomationProperties.Name="Review selected changes">
|
||||
<Button x:Name="ReviewChangesBtn" Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Center" Margin="0,4,0,10" AutomationProperties.Name="Review selected changes">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<TextBlock x:Name="LinkText" Text="Review selected changes" FontSize="14" Foreground="{DynamicResource ButtonBg}" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
Title="MessageBox"
|
||||
Width="440"
|
||||
SizeToContent="Height"
|
||||
MaxHeight="500"
|
||||
MaxHeight="501"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Topmost="True"
|
||||
Topmost="False"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||
|
||||
31
Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1
Normal file
31
Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
# Run winget list and return installed apps.
|
||||
# Use -NonBlocking to keep the UI responsive (GUI mode) via Invoke-NonBlocking.
|
||||
function GetInstalledAppsViaWinget {
|
||||
param (
|
||||
[int]$TimeOut = 10,
|
||||
[switch]$NonBlocking
|
||||
)
|
||||
|
||||
if (-not $script:WingetInstalled) { return $null }
|
||||
|
||||
$fetchBlock = {
|
||||
param($timeOut)
|
||||
$job = Start-Job { return winget list --accept-source-agreements --disable-interactivity }
|
||||
$done = $job | Wait-Job -Timeout $timeOut
|
||||
if ($done) {
|
||||
$result = Receive-Job -Job $job
|
||||
Remove-Job -Job $job -ErrorAction SilentlyContinue
|
||||
return $result
|
||||
}
|
||||
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($NonBlocking) {
|
||||
return Invoke-NonBlocking -ScriptBlock $fetchBlock -ArgumentList $TimeOut
|
||||
}
|
||||
else {
|
||||
return & $fetchBlock $TimeOut
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ function RemoveApps {
|
||||
|
||||
$appIndex = 0
|
||||
$appCount = @($appsList).Count
|
||||
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
||||
$edgeUninstallSucceeded = $false
|
||||
$edgeScheduledTaskAdded = $false
|
||||
|
||||
Foreach ($app in $appsList) {
|
||||
if ($script:CancelRequested) {
|
||||
@@ -25,20 +28,27 @@ function RemoveApps {
|
||||
Write-Host "Attempting to remove $app..."
|
||||
|
||||
# Use WinGet only to remove OneDrive and Edge
|
||||
if (($app -eq "Microsoft.OneDrive") -or ($app -eq "Microsoft.Edge")) {
|
||||
if (($app -eq "Microsoft.OneDrive") -or ($edgeIds -contains $app)) {
|
||||
if ($script:WingetInstalled -eq $false) {
|
||||
Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
|
||||
continue
|
||||
}
|
||||
|
||||
$appName = $app -replace '\.', '_'
|
||||
$isEdgeId = $edgeIds -contains $app
|
||||
$appName = if ($isEdgeId) { 'Microsoft_Edge' } else { $app -replace '\.', '_' }
|
||||
|
||||
# Uninstall app via WinGet, or create a scheduled task to uninstall it later
|
||||
if ($script:Params.ContainsKey("User")) {
|
||||
ImportRegistryFile "Adding scheduled task to uninstall $app for user $(GetUserName)..." "Uninstall_$($appName).reg"
|
||||
if (-not ($isEdgeId -and $edgeScheduledTaskAdded)) {
|
||||
ImportRegistryFile "Adding scheduled task to uninstall $app for user $(GetUserName)..." "Uninstall_$($appName).reg"
|
||||
if ($isEdgeId) { $edgeScheduledTaskAdded = $true }
|
||||
}
|
||||
}
|
||||
elseif ($script:Params.ContainsKey("Sysprep")) {
|
||||
ImportRegistryFile "Adding scheduled task to uninstall $app after for new users..." "Uninstall_$($appName).reg"
|
||||
if (-not ($isEdgeId -and $edgeScheduledTaskAdded)) {
|
||||
ImportRegistryFile "Adding scheduled task to uninstall $app after for new users..." "Uninstall_$($appName).reg"
|
||||
if ($isEdgeId) { $edgeScheduledTaskAdded = $true }
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Uninstall app via WinGet
|
||||
@@ -47,21 +57,35 @@ function RemoveApps {
|
||||
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
||||
} -ArgumentList $app
|
||||
|
||||
If (($app -eq "Microsoft.Edge") -and (Select-String -InputObject $wingetOutput -Pattern "Uninstall failed with exit code")) {
|
||||
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red
|
||||
$wingetFailed = Select-String -InputObject $wingetOutput -Pattern "Uninstall failed with exit code|No installed package found matching input criteria|No package found matching input criteria" -SimpleMatch:$false
|
||||
if ($isEdgeId) {
|
||||
if (-not $wingetFailed) {
|
||||
$edgeUninstallSucceeded = $true
|
||||
}
|
||||
|
||||
if ($script:GuiWindow) {
|
||||
$result = Show-MessageBox -Message 'Unable to uninstall Microsoft Edge via WinGet. Would you like to forcefully uninstall it? NOT RECOMMENDED!' -Title 'Force Uninstall Microsoft Edge?' -Button 'YesNo' -Icon 'Warning'
|
||||
# Prompt immediately after the final selected Edge ID attempt (if all attempts failed)
|
||||
$hasRemainingEdgeIds = $false
|
||||
if ($appIndex -lt $appCount) {
|
||||
$remainingApps = @($appsList)[($appIndex)..($appCount - 1)]
|
||||
$hasRemainingEdgeIds = @($remainingApps | Where-Object { $edgeIds -contains $_ }).Count -gt 0
|
||||
}
|
||||
|
||||
if ($result -eq 'Yes') {
|
||||
if (-not $hasRemainingEdgeIds -and -not $edgeUninstallSucceeded) {
|
||||
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red
|
||||
|
||||
if ($script:GuiWindow) {
|
||||
$result = Show-MessageBox -Message 'Unable to uninstall Microsoft Edge via WinGet. Would you like to forcefully uninstall it? NOT RECOMMENDED!' -Title 'Force Uninstall Microsoft Edge?' -Button 'YesNo' -Icon 'Warning'
|
||||
|
||||
if ($result -eq 'Yes') {
|
||||
Write-Host ""
|
||||
ForceRemoveEdge
|
||||
}
|
||||
}
|
||||
elseif ($( Read-Host -Prompt "Would you like to forcefully uninstall Microsoft Edge? NOT RECOMMENDED! (y/n)" ) -eq 'y') {
|
||||
Write-Host ""
|
||||
ForceRemoveEdge
|
||||
}
|
||||
}
|
||||
elseif ($( Read-Host -Prompt "Would you like to forcefully uninstall Microsoft Edge? NOT RECOMMENDED! (y/n)" ) -eq 'y') {
|
||||
Write-Host ""
|
||||
ForceRemoveEdge
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ function ShowCLIDefaultModeOptions {
|
||||
|
||||
PrintHeader 'Default Mode'
|
||||
|
||||
# Add default settings based on user input
|
||||
try {
|
||||
# Select app removal options based on user input
|
||||
switch ($RemoveAppsInput) {
|
||||
@@ -35,7 +34,6 @@ function ShowCLIDefaultModeOptions {
|
||||
}
|
||||
}
|
||||
|
||||
# Load settings from DefaultSettings.json and add to params
|
||||
LoadSettings -filePath $script:DefaultSettingsFilePath -expectedVersion "1.0"
|
||||
}
|
||||
catch {
|
||||
@@ -45,8 +43,8 @@ function ShowCLIDefaultModeOptions {
|
||||
|
||||
SaveSettings
|
||||
|
||||
# Skip change summary if Silent parameter was passed
|
||||
if ($Silent) {
|
||||
# Skip change summary and confirmation prompt
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ function ShowCLILastUsedSettings {
|
||||
PrintHeader 'Custom Mode'
|
||||
|
||||
try {
|
||||
# Load settings from LastUsedSettings.json and add to params
|
||||
LoadSettings -filePath $script:SavedSettingsFilePath -expectedVersion "1.0"
|
||||
}
|
||||
catch {
|
||||
@@ -11,6 +10,11 @@ function ShowCLILastUsedSettings {
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
if ($Silent) {
|
||||
# Skip change summary and confirmation prompt
|
||||
return
|
||||
}
|
||||
|
||||
PrintPendingChanges
|
||||
PrintHeader 'Custom Mode'
|
||||
}
|
||||
@@ -5,31 +5,25 @@ function CreateSystemRestorePoint {
|
||||
if ($SysRestore.RPSessionInterval -eq 0) {
|
||||
# In GUI mode, skip the prompt and just try to enable it
|
||||
if ($script:GuiWindow -or $Silent -or $( Read-Host -Prompt "System restore is disabled, would you like to enable it and create a restore point? (y/n)") -eq 'y') {
|
||||
$enableSystemRestoreJob = Start-Job {
|
||||
try {
|
||||
Enable-ComputerRestore -Drive "$env:SystemDrive"
|
||||
try {
|
||||
$enableResult = Invoke-NonBlocking -TimeoutSeconds 20 -ScriptBlock {
|
||||
try {
|
||||
Enable-ComputerRestore -Drive "$env:SystemDrive"
|
||||
return $null
|
||||
}
|
||||
catch {
|
||||
return "Error: Failed to enable System Restore: $_"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return "Error: Failed to enable System Restore: $_"
|
||||
}
|
||||
return $null
|
||||
}
|
||||
catch {
|
||||
$enableResult = "Error: Failed to enable System Restore: $_"
|
||||
}
|
||||
|
||||
$enableSystemRestoreJobDone = $enableSystemRestoreJob | Wait-Job -TimeOut 20
|
||||
|
||||
if (-not $enableSystemRestoreJobDone) {
|
||||
Remove-Job -Job $enableSystemRestoreJob -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Error: Failed to enable system restore and create restore point, operation timed out" -ForegroundColor Red
|
||||
if ($enableResult) {
|
||||
Write-Host $enableResult -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
else {
|
||||
$result = Receive-Job $enableSystemRestoreJob
|
||||
Remove-Job -Job $enableSystemRestoreJob -ErrorAction SilentlyContinue
|
||||
if ($result) {
|
||||
Write-Host $result -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
@@ -38,46 +32,43 @@ function CreateSystemRestorePoint {
|
||||
}
|
||||
|
||||
if (-not $failed) {
|
||||
$createRestorePointJob = Start-Job {
|
||||
# Find existing restore points that are less than 24 hours old
|
||||
try {
|
||||
$recentRestorePoints = Get-ComputerRestorePoint | Where-Object { (Get-Date) - [System.Management.ManagementDateTimeConverter]::ToDateTime($_.CreationTime) -le (New-TimeSpan -Hours 24) }
|
||||
}
|
||||
catch {
|
||||
return @{ Success = $false; Message = "Error: Unable to retrieve existing restore points: $_" }
|
||||
}
|
||||
|
||||
if ($recentRestorePoints.Count -eq 0) {
|
||||
try {
|
||||
$result = Invoke-NonBlocking -TimeoutSeconds 20 -ScriptBlock {
|
||||
try {
|
||||
Checkpoint-Computer -Description "Restore point created by Win11Debloat" -RestorePointType "MODIFY_SETTINGS"
|
||||
return @{ Success = $true; Message = "System restore point created successfully" }
|
||||
$recentRestorePoints = Get-ComputerRestorePoint | Where-Object { (Get-Date) - [System.Management.ManagementDateTimeConverter]::ToDateTime($_.CreationTime) -le (New-TimeSpan -Hours 24) }
|
||||
}
|
||||
catch {
|
||||
return @{ Success = $false; Message = "Error: Unable to create restore point: $_" }
|
||||
return [PSCustomObject]@{ Success = $false; Message = "Error: Unable to retrieve existing restore points: $_" }
|
||||
}
|
||||
|
||||
if ($recentRestorePoints.Count -eq 0) {
|
||||
try {
|
||||
Checkpoint-Computer -Description "Restore point created by Win11Debloat" -RestorePointType "MODIFY_SETTINGS"
|
||||
return [PSCustomObject]@{ Success = $true; Message = "System restore point created successfully" }
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{ Success = $false; Message = "Error: Unable to create restore point: $_" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
return [PSCustomObject]@{ Success = $true; Message = "A recent restore point already exists, no new restore point was created" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
return @{ Success = $true; Message = "A recent restore point already exists, no new restore point was created" }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result = [PSCustomObject]@{ Success = $false; Message = "Error: Failed to create system restore point: $_" }
|
||||
}
|
||||
|
||||
$createRestorePointJobDone = $createRestorePointJob | Wait-Job -TimeOut 20
|
||||
|
||||
if (-not $createRestorePointJobDone) {
|
||||
Remove-Job -Job $createRestorePointJob -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Error: Failed to create system restore point, operation timed out" -ForegroundColor Red
|
||||
if ($result -and $result.Success) {
|
||||
Write-Host $result.Message
|
||||
}
|
||||
elseif ($result) {
|
||||
Write-Host $result.Message -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
else {
|
||||
$result = Receive-Job $createRestorePointJob
|
||||
Remove-Job -Job $createRestorePointJob -ErrorAction SilentlyContinue
|
||||
if ($result.Success) {
|
||||
Write-Host $result.Message
|
||||
}
|
||||
else {
|
||||
Write-Host $result.Message -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
Write-Host "Error: Failed to create system restore point" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
196
Scripts/Features/ExecuteChanges.ps1
Normal file
196
Scripts/Features/ExecuteChanges.ps1
Normal file
@@ -0,0 +1,196 @@
|
||||
# Executes a single parameter/feature based on its key
|
||||
# Parameters:
|
||||
# $paramKey - The parameter name to execute
|
||||
function ExecuteParameter {
|
||||
param (
|
||||
[string]$paramKey
|
||||
)
|
||||
|
||||
# Check if this feature has metadata in Features.json
|
||||
$feature = $null
|
||||
if ($script:Features.ContainsKey($paramKey)) {
|
||||
$feature = $script:Features[$paramKey]
|
||||
}
|
||||
|
||||
# If feature has RegistryKey and ApplyText, use dynamic ImportRegistryFile
|
||||
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
||||
ImportRegistryFile "> $($feature.ApplyText)" $feature.RegistryKey
|
||||
|
||||
# Handle special cases that have additional logic after ImportRegistryFile
|
||||
switch ($paramKey) {
|
||||
'DisableBing' {
|
||||
# Also remove the app package for Bing search
|
||||
RemoveApps 'Microsoft.BingSearch'
|
||||
}
|
||||
'DisableCopilot' {
|
||||
# Also remove the app package for Copilot
|
||||
RemoveApps 'Microsoft.Copilot'
|
||||
}
|
||||
'DisableWidgets' {
|
||||
# Also remove the app package for Widgets
|
||||
RemoveApps 'Microsoft.StartExperiencesApp'
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Handle features without RegistryKey or with special logic
|
||||
switch ($paramKey) {
|
||||
'RemoveApps' {
|
||||
Write-Host "> Removing selected apps for $(GetFriendlyTargetUserName)..."
|
||||
$appsList = GenerateAppsList
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
}
|
||||
'RemoveAppsCustom' {
|
||||
Write-Host "> Removing selected apps..."
|
||||
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
}
|
||||
'RemoveCommApps' {
|
||||
$appsList = 'Microsoft.windowscommunicationsapps', 'Microsoft.People'
|
||||
Write-Host "> Removing Mail, Calendar and People apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveW11Outlook' {
|
||||
$appsList = 'Microsoft.OutlookForWindows'
|
||||
Write-Host "> Removing new Outlook for Windows app..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveGamingApps' {
|
||||
$appsList = 'Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay'
|
||||
Write-Host "> Removing gaming related apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveHPApps' {
|
||||
$appsList = 'AD2F1837.HPAIExperienceCenter', 'AD2F1837.HPJumpStarts', 'AD2F1837.HPPCHardwareDiagnosticsWindows', 'AD2F1837.HPPowerManager', 'AD2F1837.HPPrivacySettings', 'AD2F1837.HPSupportAssistant', 'AD2F1837.HPSureShieldAI', 'AD2F1837.HPSystemInformation', 'AD2F1837.HPQuickDrop', 'AD2F1837.HPWorkWell', 'AD2F1837.myHP', 'AD2F1837.HPDesktopSupportUtilities', 'AD2F1837.HPQuickTouch', 'AD2F1837.HPEasyClean', 'AD2F1837.HPConnectedMusic', 'AD2F1837.HPFileViewer', 'AD2F1837.HPRegistration', 'AD2F1837.HPWelcome', 'AD2F1837.HPConnectedPhotopoweredbySnapfish', 'AD2F1837.HPPrinterControl'
|
||||
Write-Host "> Removing HP apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
"EnableWindowsSandbox" {
|
||||
Write-Host "> Enabling Windows Sandbox..."
|
||||
EnableWindowsFeature "Containers-DisposableClientVM"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
"EnableWindowsSubsystemForLinux" {
|
||||
Write-Host "> Enabling Windows Subsystem for Linux..."
|
||||
EnableWindowsFeature "VirtualMachinePlatform"
|
||||
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStart' {
|
||||
Write-Host "> Removing all pinned apps from the start menu for user $(GetUserName)..."
|
||||
ReplaceStartMenu
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ReplaceStart' {
|
||||
Write-Host "> Replacing the start menu for user $(GetUserName)..."
|
||||
ReplaceStartMenu $script:Params.Item("ReplaceStart")
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers
|
||||
return
|
||||
}
|
||||
'ReplaceStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers $script:Params.Item("ReplaceStartAllUsers")
|
||||
return
|
||||
}
|
||||
'DisableStoreSearchSuggestions' {
|
||||
if ($script:Params.ContainsKey("Sysprep")) {
|
||||
Write-Host "> Disabling Microsoft Store search suggestions in the start menu for all users..."
|
||||
DisableStoreSearchSuggestionsForAllUsers
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "> Disabling Microsoft Store search suggestions for user $(GetUserName)..."
|
||||
DisableStoreSearchSuggestions
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Executes all selected parameters/features
|
||||
function ExecuteAllChanges {
|
||||
# Build list of actionable parameters (skip control params and data-only params)
|
||||
$actionableKeys = @()
|
||||
foreach ($paramKey in $script:Params.Keys) {
|
||||
if ($script:ControlParams -contains $paramKey) { continue }
|
||||
if ($paramKey -eq 'Apps') { continue }
|
||||
if ($paramKey -eq 'CreateRestorePoint') { continue }
|
||||
$actionableKeys += $paramKey
|
||||
}
|
||||
|
||||
$totalSteps = $actionableKeys.Count
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||
$currentStep = 0
|
||||
|
||||
# Create restore point if requested (CLI only - GUI handles this separately)
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||
$currentStep++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point"
|
||||
}
|
||||
Write-Host "> Attempting to create a system restore point..."
|
||||
CreateSystemRestorePoint
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Execute all parameters
|
||||
foreach ($paramKey in $actionableKeys) {
|
||||
if ($script:CancelRequested) {
|
||||
return
|
||||
}
|
||||
|
||||
$currentStep++
|
||||
|
||||
# Get friendly name for the step
|
||||
$stepName = $paramKey
|
||||
if ($script:Features.ContainsKey($paramKey)) {
|
||||
$feature = $script:Features[$paramKey]
|
||||
if ($feature.ApplyText) {
|
||||
# Prefer explicit ApplyText when provided
|
||||
$stepName = $feature.ApplyText
|
||||
} elseif ($feature.Label) {
|
||||
# Fallback: construct a name from Action and Label, or just Label
|
||||
if ($feature.Action) {
|
||||
$stepName = "$($feature.Action) $($feature.Label)"
|
||||
} else {
|
||||
$stepName = $feature.Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps $stepName
|
||||
}
|
||||
|
||||
ExecuteParameter -paramKey $paramKey
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,25 @@ function ImportRegistryFile {
|
||||
|
||||
Write-Host $message
|
||||
|
||||
# Validate that the regfile exists in both locations
|
||||
if (-not (Test-Path "$script:RegfilesPath\$path") -or -not (Test-Path "$script:RegfilesPath\Sysprep\$path")) {
|
||||
Write-Host "Error: Unable to find registry file: $path" -ForegroundColor Red
|
||||
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
|
||||
$regFilePath = if ($usesOfflineHive) {
|
||||
"$script:RegfilesPath\Sysprep\$path"
|
||||
}
|
||||
else {
|
||||
"$script:RegfilesPath\$path"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $regFilePath)) {
|
||||
$errorMessage = "Unable to find registry file: $path ($regFilePath)"
|
||||
Write-Host "Error: $errorMessage" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
return
|
||||
throw $errorMessage
|
||||
}
|
||||
|
||||
# Reset exit code before running reg.exe for reliable success detection
|
||||
$global:LASTEXITCODE = 0
|
||||
|
||||
if ($script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")) {
|
||||
if ($usesOfflineHive) {
|
||||
# Sysprep targets Default user, User targets the specified user
|
||||
$hiveDatPath = if ($script:Params.ContainsKey("Sysprep")) {
|
||||
GetUserDirectory -userName "Default" -fileName "NTUSER.DAT"
|
||||
@@ -26,26 +34,62 @@ function ImportRegistryFile {
|
||||
}
|
||||
|
||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
||||
param($datPath, $regFilePath)
|
||||
$global:LASTEXITCODE = 0
|
||||
reg load "HKU\Default" $datPath | Out-Null
|
||||
$output = reg import $regFilePath 2>&1
|
||||
$code = $LASTEXITCODE
|
||||
reg unload "HKU\Default" | Out-Null
|
||||
return @{ Output = $output; ExitCode = $code }
|
||||
} -ArgumentList @($hiveDatPath, "$script:RegfilesPath\Sysprep\$path")
|
||||
param($hivePath, $targetRegFilePath)
|
||||
$result = @{
|
||||
Output = @()
|
||||
ExitCode = 0
|
||||
Error = $null
|
||||
}
|
||||
|
||||
try {
|
||||
$global:LASTEXITCODE = 0
|
||||
reg load "HKU\Default" $hivePath | Out-Null
|
||||
$loadExitCode = $LASTEXITCODE
|
||||
|
||||
if ($loadExitCode -ne 0) {
|
||||
throw "Failed to load user hive at '$hivePath' (exit code: $loadExitCode)"
|
||||
}
|
||||
|
||||
$output = reg import $targetRegFilePath 2>&1
|
||||
$importExitCode = $LASTEXITCODE
|
||||
|
||||
if ($output) {
|
||||
$result.Output = @($output)
|
||||
}
|
||||
$result.ExitCode = $importExitCode
|
||||
|
||||
if ($importExitCode -ne 0) {
|
||||
throw "Registry import failed with exit code $importExitCode for '$targetRegFilePath'"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Error = $_.Exception.Message
|
||||
$result.ExitCode = if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 1 }
|
||||
}
|
||||
finally {
|
||||
$global:LASTEXITCODE = 0
|
||||
reg unload "HKU\Default" | Out-Null
|
||||
$unloadExitCode = $LASTEXITCODE
|
||||
if ($unloadExitCode -ne 0 -and -not $result.Error) {
|
||||
$result.Error = "Failed to unload temporary hive HKU\\Default (exit code: $unloadExitCode)"
|
||||
$result.ExitCode = $unloadExitCode
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
} -ArgumentList @($hiveDatPath, $regFilePath)
|
||||
}
|
||||
else {
|
||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
||||
param($regFilePath)
|
||||
param($targetRegFilePath)
|
||||
$global:LASTEXITCODE = 0
|
||||
$output = reg import $regFilePath 2>&1
|
||||
return @{ Output = $output; ExitCode = $LASTEXITCODE }
|
||||
} -ArgumentList "$script:RegfilesPath\$path"
|
||||
$output = reg import $targetRegFilePath 2>&1
|
||||
return @{ Output = @($output); ExitCode = $LASTEXITCODE; Error = $null }
|
||||
} -ArgumentList $regFilePath
|
||||
}
|
||||
|
||||
$regOutput = $regResult.Output
|
||||
$hasSuccess = $regResult.ExitCode -eq 0
|
||||
$regOutput = @($regResult.Output)
|
||||
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
|
||||
|
||||
if ($regOutput) {
|
||||
foreach ($line in $regOutput) {
|
||||
@@ -62,7 +106,11 @@ function ImportRegistryFile {
|
||||
}
|
||||
|
||||
if (-not $hasSuccess) {
|
||||
Write-Host "Failed importing registry file: $path" -ForegroundColor Red
|
||||
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
|
||||
$errorMessage = "Failed importing registry file '$path'. $details"
|
||||
Write-Host $errorMessage -ForegroundColor Red
|
||||
Write-Host ""
|
||||
throw $errorMessage
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
22
Scripts/FileIO/LoadAppPresetsFromJson.ps1
Normal file
22
Scripts/FileIO/LoadAppPresetsFromJson.ps1
Normal file
@@ -0,0 +1,22 @@
|
||||
# Read Apps.json and return the list of preset objects (Name + AppIds).
|
||||
# Returns an empty array if the file cannot be read or contains no presets.
|
||||
function LoadAppPresetsFromJson {
|
||||
try {
|
||||
$jsonContent = Get-Content -Path $script:AppsListFilePath -Raw | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Failed to read Apps.json: $_"
|
||||
return @()
|
||||
}
|
||||
|
||||
if (-not $jsonContent.Presets) {
|
||||
return @()
|
||||
}
|
||||
|
||||
return @($jsonContent.Presets | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
Name = $_.Name
|
||||
AppIds = @($_.AppIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -16,29 +16,42 @@ function LoadAppsDetailsFromJson {
|
||||
}
|
||||
|
||||
foreach ($appData in $jsonContent.Apps) {
|
||||
$appId = $appData.AppId.Trim()
|
||||
if ($appId.length -eq 0) { continue }
|
||||
# Handle AppId as array (could be single or multiple IDs)
|
||||
$appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
|
||||
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
|
||||
if ($appIdArray.Count -eq 0) { continue }
|
||||
|
||||
if ($OnlyInstalled) {
|
||||
if (-not ($InstalledList -like ("*$appId*")) -and -not (Get-AppxPackage -Name $appId)) {
|
||||
continue
|
||||
}
|
||||
if (($appId -eq "Microsoft.Edge") -and -not ($InstalledList -like "* Microsoft.Edge *")) {
|
||||
continue
|
||||
$isInstalled = $false
|
||||
foreach ($appId in $appIdArray) {
|
||||
if (($InstalledList -like ("*$appId*")) -or (Get-AppxPackage -Name $appId)) {
|
||||
$isInstalled = $true
|
||||
break
|
||||
}
|
||||
if (($appId -eq "Microsoft.Edge") -and ($InstalledList -like "* Microsoft.Edge *")) {
|
||||
$isInstalled = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $isInstalled) { continue }
|
||||
}
|
||||
|
||||
$friendlyName = if ($appData.FriendlyName) { $appData.FriendlyName } else { $appId }
|
||||
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appId)" } else { $appId }
|
||||
# Use first AppId for fallback names, join all for display
|
||||
$primaryAppId = $appIdArray[0]
|
||||
$appIdDisplay = $appIdArray -join ', '
|
||||
$friendlyName = if ($appData.FriendlyName) { $appData.FriendlyName } else { $primaryAppId }
|
||||
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appIdDisplay)" } else { $appIdDisplay }
|
||||
$isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false }
|
||||
|
||||
$apps += [PSCustomObject]@{
|
||||
AppId = $appId
|
||||
AppId = $appIdArray
|
||||
AppIdDisplay = $appIdDisplay
|
||||
FriendlyName = $friendlyName
|
||||
DisplayName = $displayName
|
||||
IsChecked = $isChecked
|
||||
Description = $appData.Description
|
||||
SelectedByDefault = $appData.SelectedByDefault
|
||||
Recommendation = $appData.Recommendation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,12 @@ function LoadAppsFromFile {
|
||||
# JSON file format
|
||||
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
|
||||
Foreach ($appData in $jsonContent.Apps) {
|
||||
$appId = $appData.AppId.Trim()
|
||||
# Handle AppId as array (could be single or multiple IDs)
|
||||
$appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
|
||||
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
|
||||
$selectedByDefault = $appData.SelectedByDefault
|
||||
if ($selectedByDefault -and $appId.length -gt 0) {
|
||||
$appsList += $appId
|
||||
if ($selectedByDefault -and $appIdArray.Count -gt 0) {
|
||||
$appsList += $appIdArray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,7 @@ function SaveSettings {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$settings | ConvertTo-Json -Depth 10 | Set-Content $script:SavedSettingsFilePath
|
||||
}
|
||||
catch {
|
||||
if (-not (SaveToFile -Config $settings -FilePath $script:SavedSettingsFilePath)) {
|
||||
Write-Output ""
|
||||
Write-Host "Error: Failed to save settings to LastUsedSettings.json file" -ForegroundColor Red
|
||||
}
|
||||
|
||||
19
Scripts/FileIO/SaveToFile.ps1
Normal file
19
Scripts/FileIO/SaveToFile.ps1
Normal file
@@ -0,0 +1,19 @@
|
||||
# Saves configuration JSON to a file.
|
||||
# Returns $true on success, $false on failure.
|
||||
function SaveToFile {
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[hashtable]$Config,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FilePath
|
||||
)
|
||||
|
||||
try {
|
||||
$Config | ConvertTo-Json -Depth 10 | Set-Content -Path $FilePath -Encoding UTF8
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ function ValidateAppslist {
|
||||
$appsList
|
||||
)
|
||||
|
||||
$supportedAppsList = (LoadAppsDetailsFromJson | ForEach-Object { $_.AppId })
|
||||
$supportedAppsList = @(LoadAppsDetailsFromJson | ForEach-Object { @($_.AppId) }) | ForEach-Object { $_.Trim() } | Where-Object { $_.Length -gt 0 }
|
||||
$validatedAppsList = @()
|
||||
|
||||
# Validate provided appsList against supportedAppsList
|
||||
|
||||
@@ -11,60 +11,64 @@ function ApplySettingsToUiControls {
|
||||
return $false
|
||||
}
|
||||
|
||||
# First, reset all tweaks to "No Change" (index 0) or unchecked
|
||||
if ($uiControlMappings) {
|
||||
foreach ($comboName in $uiControlMappings.Keys) {
|
||||
$control = $window.FindName($comboName)
|
||||
if ($control -is [System.Windows.Controls.CheckBox]) {
|
||||
$control.IsChecked = $false
|
||||
}
|
||||
elseif ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = 0
|
||||
if (-not $uiControlMappings) {
|
||||
return $true
|
||||
}
|
||||
|
||||
# Build control cache and reverse index (featureId -> control info) in a single pass
|
||||
$controlCache = @{}
|
||||
$featureIdIndex = @{}
|
||||
|
||||
foreach ($comboName in $uiControlMappings.Keys) {
|
||||
$control = $window.FindName($comboName)
|
||||
if (-not $control) { continue }
|
||||
$controlCache[$comboName] = $control
|
||||
|
||||
$mapping = $uiControlMappings[$comboName]
|
||||
if ($mapping.Type -eq 'group') {
|
||||
$i = 1
|
||||
foreach ($val in $mapping.Values) {
|
||||
foreach ($fid in $val.FeatureIds) {
|
||||
$featureIdIndex[$fid] = @{ ComboName = $comboName; Control = $control; Index = $i; MappingType = 'group' }
|
||||
}
|
||||
$i++
|
||||
}
|
||||
}
|
||||
elseif ($mapping.Type -eq 'feature') {
|
||||
$featureIdIndex[$mapping.FeatureId] = @{ ComboName = $comboName; Control = $control; MappingType = 'feature' }
|
||||
}
|
||||
|
||||
# Reset control to default state
|
||||
if ($control -is [System.Windows.Controls.CheckBox]) {
|
||||
$control.IsChecked = $false
|
||||
}
|
||||
elseif ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
# Apply settings from JSON
|
||||
# Apply settings using O(1) lookups
|
||||
foreach ($setting in $settingsJson.Settings) {
|
||||
if ($setting.Value -ne $true) { continue }
|
||||
$paramName = $setting.Name
|
||||
if ($setting.Name -eq 'CreateRestorePoint') { continue }
|
||||
|
||||
# Skip RestorePointCheckBox, this is always checked by default
|
||||
if ($paramName -eq 'CreateRestorePoint') {
|
||||
continue
|
||||
$entry = $featureIdIndex[$setting.Name]
|
||||
if (-not $entry) { continue }
|
||||
|
||||
$control = $entry.Control
|
||||
if (-not $control -or $control.Visibility -ne 'Visible') { continue }
|
||||
|
||||
if ($entry.MappingType -eq 'group') {
|
||||
if ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = $entry.Index
|
||||
}
|
||||
}
|
||||
|
||||
if ($uiControlMappings) {
|
||||
foreach ($comboName in $uiControlMappings.Keys) {
|
||||
$mapping = $uiControlMappings[$comboName]
|
||||
if ($mapping.Type -eq 'group') {
|
||||
$i = 1
|
||||
foreach ($val in $mapping.Values) {
|
||||
if ($val.FeatureIds -contains $paramName) {
|
||||
$control = $window.FindName($comboName)
|
||||
if ($control -and $control.Visibility -eq 'Visible') {
|
||||
if ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = $i
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
$i++
|
||||
}
|
||||
}
|
||||
elseif ($mapping.Type -eq 'feature') {
|
||||
if ($mapping.FeatureId -eq $paramName) {
|
||||
$control = $window.FindName($comboName)
|
||||
if ($control -and $control.Visibility -eq 'Visible') {
|
||||
if ($control -is [System.Windows.Controls.CheckBox]) {
|
||||
$control.IsChecked = $true
|
||||
}
|
||||
elseif ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($control -is [System.Windows.Controls.CheckBox]) {
|
||||
$control.IsChecked = $true
|
||||
}
|
||||
elseif ($control -is [System.Windows.Controls.ComboBox]) {
|
||||
$control.SelectedIndex = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ function SetWindowThemeResources {
|
||||
$window.Resources.Add("InputFocusColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#1f1f1f")))
|
||||
$window.Resources.Add("ScrollBarThumbColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#3d3d3d")))
|
||||
$window.Resources.Add("ScrollBarThumbHoverColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#4b4b4b")))
|
||||
$window.Resources.Add("TitlebarButtonHover", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#2d2d2d")))
|
||||
$window.Resources.Add("TitlebarButtonPressed", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#292929")))
|
||||
$window.Resources.Add("AppIdColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#afafaf")))
|
||||
$window.Resources.Add("SearchHighlightColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#4A4A2A")))
|
||||
$window.Resources.Add("SearchHighlightActiveColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#8A7000")))
|
||||
@@ -60,6 +62,8 @@ function SetWindowThemeResources {
|
||||
$window.Resources.Add("InputFocusColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#fbfbfb")))
|
||||
$window.Resources.Add("ScrollBarThumbColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#b9b9b9")))
|
||||
$window.Resources.Add("ScrollBarThumbHoverColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#8b8b8b")))
|
||||
$window.Resources.Add("TitlebarButtonHover", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#e1e1e1")))
|
||||
$window.Resources.Add("TitlebarButtonPressed", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#e6e6e6")))
|
||||
$window.Resources.Add("AppIdColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#666666")))
|
||||
$window.Resources.Add("SearchHighlightColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#FFF4CE")))
|
||||
$window.Resources.Add("SearchHighlightActiveColor", [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#FFD966")))
|
||||
|
||||
@@ -83,13 +83,16 @@ function Show-AboutDialog {
|
||||
})
|
||||
|
||||
# Show dialog
|
||||
$aboutWindow.ShowDialog() | Out-Null
|
||||
|
||||
# Hide overlay after dialog closes
|
||||
if ($overlay) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
try {
|
||||
$aboutWindow.ShowDialog() | Out-Null
|
||||
}
|
||||
finally {
|
||||
# Hide overlay after dialog closes
|
||||
if ($overlay) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,8 @@ function Show-AppSelectionWindow {
|
||||
$checkbox = New-Object System.Windows.Controls.CheckBox
|
||||
$checkbox.Content = $_.DisplayName
|
||||
$checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $_.DisplayName)
|
||||
$checkbox.Tag = $_.AppId
|
||||
$checkbox.Tag = $_.AppIdDisplay
|
||||
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($_.AppId)
|
||||
$checkbox.IsChecked = $_.IsChecked
|
||||
$checkbox.ToolTip = $_.Description
|
||||
$checkbox.Style = $window.Resources["AppsPanelCheckBoxStyle"]
|
||||
@@ -118,9 +119,10 @@ function Show-AppSelectionWindow {
|
||||
$selectedApps = @()
|
||||
foreach ($child in $appsPanel.Children) {
|
||||
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
||||
$selectedApps += $child.Tag
|
||||
$selectedApps += @($child.AppIds)
|
||||
}
|
||||
}
|
||||
$selectedApps = @($selectedApps | Where-Object { $_ } | Select-Object -Unique)
|
||||
|
||||
# Close form without saving if no apps were selected
|
||||
if ($selectedApps.Count -eq 0) {
|
||||
@@ -143,7 +145,7 @@ function Show-AppSelectionWindow {
|
||||
|
||||
# Load apps after window is shown (allows UI to render first)
|
||||
$window.Add_ContentRendered({
|
||||
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ LoadApps })
|
||||
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ LoadApps }) | Out-Null
|
||||
})
|
||||
|
||||
# Show the window and return dialog result
|
||||
|
||||
@@ -216,7 +216,7 @@ function Show-ApplyModal {
|
||||
$script:ApplyProgressCallback = $null
|
||||
$script:ApplySubStepCallback = $null
|
||||
}
|
||||
})
|
||||
}) | Out-Null
|
||||
|
||||
# Button handlers
|
||||
$applyCloseBtn.Add_Click({
|
||||
@@ -242,13 +242,16 @@ function Show-ApplyModal {
|
||||
})
|
||||
|
||||
# Show dialog
|
||||
$applyWindow.ShowDialog() | Out-Null
|
||||
|
||||
# Hide overlay after dialog closes
|
||||
if ($overlay) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
try {
|
||||
$applyWindow.ShowDialog() | Out-Null
|
||||
}
|
||||
finally {
|
||||
# Hide overlay after dialog closes
|
||||
if ($overlay) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
115
Scripts/GUI/Show-Bubble.ps1
Normal file
115
Scripts/GUI/Show-Bubble.ps1
Normal file
@@ -0,0 +1,115 @@
|
||||
function Hide-Bubble {
|
||||
param (
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$Immediate
|
||||
)
|
||||
|
||||
if ($script:BubbleTimer) {
|
||||
$script:BubbleTimer.Stop()
|
||||
$script:BubbleTimer = $null
|
||||
}
|
||||
|
||||
if (-not $script:BubblePopup) { return }
|
||||
|
||||
if ($Immediate -or -not $script:BubblePopup.Child) {
|
||||
$script:BubblePopup.IsOpen = $false
|
||||
$script:BubblePopup = $null
|
||||
$script:BubbleIsClosing = $false
|
||||
return
|
||||
}
|
||||
|
||||
if ($script:BubbleIsClosing) { return }
|
||||
$script:BubbleIsClosing = $true
|
||||
|
||||
$bubblePanel = $script:BubblePopup.Child
|
||||
$fadeOut = New-Object System.Windows.Media.Animation.DoubleAnimation
|
||||
$fadeOut.From = [double]$bubblePanel.Opacity
|
||||
$fadeOut.To = 0
|
||||
$fadeOut.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds(220))
|
||||
$fadeOut.Add_Completed({
|
||||
if ($script:BubblePopup) {
|
||||
$script:BubblePopup.IsOpen = $false
|
||||
$script:BubblePopup = $null
|
||||
}
|
||||
$script:BubbleIsClosing = $false
|
||||
})
|
||||
|
||||
$bubblePanel.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeOut)
|
||||
}
|
||||
|
||||
function Show-Bubble {
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[System.Windows.Controls.Control]$TargetControl,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$Message = 'View the selected changes here',
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]$DurationSeconds = 5
|
||||
)
|
||||
|
||||
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase | Out-Null
|
||||
|
||||
if (-not $TargetControl) { return }
|
||||
|
||||
Hide-Bubble -Immediate
|
||||
|
||||
$xaml = Get-Content -Path $script:BubbleHintSchema -Raw
|
||||
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
|
||||
try {
|
||||
$bubblePanel = [System.Windows.Markup.XamlReader]::Load($reader)
|
||||
}
|
||||
finally {
|
||||
$reader.Close()
|
||||
}
|
||||
|
||||
$bubbleText = $bubblePanel.FindName('BubbleText')
|
||||
if ($bubbleText) {
|
||||
$bubbleText.Text = $Message
|
||||
}
|
||||
|
||||
$bubblePanel.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $null)
|
||||
$bubblePanel.Opacity = 0
|
||||
|
||||
$popup = New-Object System.Windows.Controls.Primitives.Popup
|
||||
$popup.AllowsTransparency = $true
|
||||
$popup.PopupAnimation = 'None'
|
||||
$popup.StaysOpen = $true
|
||||
$popup.PlacementTarget = $TargetControl
|
||||
$popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Top
|
||||
$popup.VerticalOffset = -1
|
||||
$popup.Child = $bubblePanel
|
||||
|
||||
$popup.Add_Opened({
|
||||
param($sender, $e)
|
||||
|
||||
if (-not $sender) { return }
|
||||
$panel = $sender.Child
|
||||
$target = $sender.PlacementTarget
|
||||
if (-not $panel -or -not $target) { return }
|
||||
|
||||
$panel.Measure([System.Windows.Size]::new([double]::PositiveInfinity, [double]::PositiveInfinity))
|
||||
$bubbleWidth = $panel.DesiredSize.Width
|
||||
$targetWidth = $target.ActualWidth
|
||||
$sender.HorizontalOffset = ($targetWidth - $bubbleWidth) / 2
|
||||
|
||||
$fadeIn = New-Object System.Windows.Media.Animation.DoubleAnimation
|
||||
$fadeIn.From = 0
|
||||
$fadeIn.To = 1
|
||||
$fadeIn.BeginTime = [TimeSpan]::FromMilliseconds(30)
|
||||
$fadeIn.Duration = [System.Windows.Duration]::new([TimeSpan]::FromMilliseconds(320))
|
||||
$panel.BeginAnimation([System.Windows.UIElement]::OpacityProperty, $fadeIn)
|
||||
})
|
||||
|
||||
$script:BubbleIsClosing = $false
|
||||
$script:BubblePopup = $popup
|
||||
$script:BubblePopup.IsOpen = $true
|
||||
|
||||
$script:BubbleTimer = New-Object System.Windows.Threading.DispatcherTimer
|
||||
$script:BubbleTimer.Interval = [TimeSpan]::FromSeconds([Math]::Max(1, $DurationSeconds))
|
||||
$script:BubbleTimer.Add_Tick({
|
||||
Hide-Bubble
|
||||
})
|
||||
$script:BubbleTimer.Start()
|
||||
}
|
||||
389
Scripts/GUI/Show-ConfigWindow.ps1
Normal file
389
Scripts/GUI/Show-ConfigWindow.ps1
Normal file
@@ -0,0 +1,389 @@
|
||||
function Show-ImportExportConfigWindow {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[bool]$UsesDarkMode,
|
||||
[string]$Title,
|
||||
[string]$Prompt,
|
||||
[string[]]$Categories = @('Applications', 'System Tweaks', 'Deployment Settings'),
|
||||
[string[]]$DisabledCategories = @()
|
||||
)
|
||||
|
||||
# Show overlay on owner window
|
||||
$overlay = $null
|
||||
$overlayWasAlreadyVisible = $false
|
||||
try {
|
||||
$overlay = $Owner.FindName('ModalOverlay')
|
||||
if ($overlay) {
|
||||
$overlayWasAlreadyVisible = ($overlay.Visibility -eq 'Visible')
|
||||
if (-not $overlayWasAlreadyVisible) {
|
||||
$Owner.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Visible' })
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Load XAML from schema file
|
||||
$schemaPath = $script:ImportExportConfigSchema
|
||||
|
||||
if (-not $schemaPath -or -not (Test-Path $schemaPath)) {
|
||||
Show-MessageBox -Message 'Import/Export window schema file could not be found.' -Title 'Error' -Button 'OK' -Icon 'Error' -Owner $Owner | Out-Null
|
||||
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||
try { $Owner.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' }) } catch { }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
$xaml = Get-Content -Path $schemaPath -Raw
|
||||
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
|
||||
try {
|
||||
$dlg = [System.Windows.Markup.XamlReader]::Load($reader)
|
||||
}
|
||||
finally {
|
||||
$reader.Close()
|
||||
}
|
||||
|
||||
$dlg.Owner = $Owner
|
||||
SetWindowThemeResources -window $dlg -usesDarkMode $UsesDarkMode
|
||||
|
||||
# Copy the CheckBox default style from the main window so checkboxes get the themed template
|
||||
try {
|
||||
$mainCheckBoxStyle = $Owner.FindResource([type][System.Windows.Controls.CheckBox])
|
||||
if ($mainCheckBoxStyle) {
|
||||
$dlg.Resources.Add([type][System.Windows.Controls.CheckBox], $mainCheckBoxStyle)
|
||||
}
|
||||
} catch { }
|
||||
|
||||
# Populate named elements
|
||||
$dlg.Title = $Title
|
||||
$dlg.FindName('TitleText').Text = $Title
|
||||
$dlg.FindName('PromptText').Text = $Prompt
|
||||
|
||||
$titleBar = $dlg.FindName('TitleBar')
|
||||
$titleBar.Add_MouseLeftButtonDown({ $dlg.DragMove() })
|
||||
|
||||
# Add a themed checkbox per category
|
||||
$checkboxPanel = $dlg.FindName('CheckboxPanel')
|
||||
$checkboxes = @{}
|
||||
foreach ($cat in $Categories) {
|
||||
$cb = New-Object System.Windows.Controls.CheckBox
|
||||
$cb.Content = $cat
|
||||
$cb.IsChecked = $true
|
||||
$cb.Margin = [System.Windows.Thickness]::new(0,0,0,8)
|
||||
$cb.FontSize = 14
|
||||
$cb.Foreground = $dlg.FindResource('FgColor')
|
||||
if ($DisabledCategories -contains $cat) {
|
||||
$cb.IsChecked = $false
|
||||
$cb.IsEnabled = $false
|
||||
$cb.Opacity = 0.65
|
||||
$cb.ToolTip = 'No selected settings available in this category.'
|
||||
}
|
||||
$checkboxPanel.Children.Add($cb) | Out-Null
|
||||
$checkboxes[$cat] = $cb
|
||||
}
|
||||
|
||||
$okBtn = $dlg.FindName('OkButton')
|
||||
$cancelBtn = $dlg.FindName('CancelButton')
|
||||
$okBtn.Add_Click({ $dlg.Tag = 'OK'; $dlg.Close() })
|
||||
$cancelBtn.Add_Click({ $dlg.Tag = 'Cancel'; $dlg.Close() })
|
||||
|
||||
# Handle Escape key
|
||||
$dlg.Add_KeyDown({
|
||||
param($s, $e)
|
||||
if ($e.Key -eq 'Escape') { $dlg.Tag = 'Cancel'; $dlg.Close() }
|
||||
})
|
||||
|
||||
try {
|
||||
$dlg.ShowDialog() | Out-Null
|
||||
}
|
||||
finally {
|
||||
# Hide overlay
|
||||
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||
try { $Owner.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' }) } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
if ($dlg.Tag -ne 'OK') { return $null }
|
||||
|
||||
$selected = @()
|
||||
foreach ($cat in $Categories) {
|
||||
if ($checkboxes[$cat].IsEnabled -and $checkboxes[$cat].IsChecked) { $selected += $cat }
|
||||
}
|
||||
if ($selected.Count -eq 0) { return $null }
|
||||
return $selected
|
||||
}
|
||||
|
||||
function Get-SelectedApplications {
|
||||
param (
|
||||
[System.Windows.Controls.Panel]$AppsPanel
|
||||
)
|
||||
|
||||
$selectedApps = @()
|
||||
foreach ($child in $AppsPanel.Children) {
|
||||
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
||||
$selectedApps += $child.Tag
|
||||
}
|
||||
}
|
||||
|
||||
return $selectedApps
|
||||
}
|
||||
|
||||
function Get-SelectedTweakSettings {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[hashtable]$UiControlMappings
|
||||
)
|
||||
|
||||
$tweakSettings = @()
|
||||
if (-not $UiControlMappings) {
|
||||
return $tweakSettings
|
||||
}
|
||||
|
||||
foreach ($mappingKey in $UiControlMappings.Keys) {
|
||||
$control = $Owner.FindName($mappingKey)
|
||||
if (-not $control) { continue }
|
||||
|
||||
$mapping = $UiControlMappings[$mappingKey]
|
||||
if ($control -is [System.Windows.Controls.CheckBox] -and $control.IsChecked) {
|
||||
if ($mapping.Type -eq 'feature') {
|
||||
$tweakSettings += @{ Name = $mapping.FeatureId; Value = $true }
|
||||
}
|
||||
}
|
||||
elseif ($control -is [System.Windows.Controls.ComboBox] -and $control.SelectedIndex -gt 0) {
|
||||
if ($mapping.Type -eq 'group') {
|
||||
$selectedValue = $mapping.Values[$control.SelectedIndex - 1]
|
||||
foreach ($fid in $selectedValue.FeatureIds) {
|
||||
$tweakSettings += @{ Name = $fid; Value = $true }
|
||||
}
|
||||
}
|
||||
elseif ($mapping.Type -eq 'feature') {
|
||||
$tweakSettings += @{ Name = $mapping.FeatureId; Value = $true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tweakSettings
|
||||
}
|
||||
|
||||
function Get-DeploymentSettings {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
|
||||
[System.Windows.Controls.TextBox]$OtherUsernameTextBox
|
||||
)
|
||||
|
||||
$deploySettings = @(
|
||||
@{ Name = 'UserSelectionIndex'; Value = $UserSelectionCombo.SelectedIndex }
|
||||
)
|
||||
|
||||
if ($UserSelectionCombo.SelectedIndex -eq 1) {
|
||||
$deploySettings += @{ Name = 'OtherUsername'; Value = $OtherUsernameTextBox.Text.Trim() }
|
||||
}
|
||||
|
||||
$appRemovalScopeCombo = $Owner.FindName('AppRemovalScopeCombo')
|
||||
if ($appRemovalScopeCombo) {
|
||||
$deploySettings += @{ Name = 'AppRemovalScopeIndex'; Value = $appRemovalScopeCombo.SelectedIndex }
|
||||
}
|
||||
|
||||
$restorePointCheckBox = $Owner.FindName('RestorePointCheckBox')
|
||||
if ($restorePointCheckBox) {
|
||||
$deploySettings += @{ Name = 'CreateRestorePoint'; Value = [bool]$restorePointCheckBox.IsChecked }
|
||||
}
|
||||
|
||||
$restartExplorerCheckBox = $Owner.FindName('RestartExplorerCheckBox')
|
||||
if ($restartExplorerCheckBox) {
|
||||
$deploySettings += @{ Name = 'RestartExplorer'; Value = [bool]$restartExplorerCheckBox.IsChecked }
|
||||
}
|
||||
|
||||
return $deploySettings
|
||||
}
|
||||
|
||||
function Get-AvailableImportExportCategories {
|
||||
param (
|
||||
$Config
|
||||
)
|
||||
|
||||
$availableCategories = @()
|
||||
if ($Config.Apps) { $availableCategories += 'Applications' }
|
||||
if ($Config.Tweaks) { $availableCategories += 'System Tweaks' }
|
||||
if ($Config.Deployment) { $availableCategories += 'Deployment Settings' }
|
||||
|
||||
return $availableCategories
|
||||
}
|
||||
|
||||
function Apply-ImportedApplications {
|
||||
param (
|
||||
[System.Windows.Controls.Panel]$AppsPanel,
|
||||
[string[]]$AppIds
|
||||
)
|
||||
|
||||
foreach ($child in $AppsPanel.Children) {
|
||||
if ($child -is [System.Windows.Controls.CheckBox]) {
|
||||
$child.IsChecked = ($AppIds -contains $child.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Apply-ImportedTweakSettings {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[hashtable]$UiControlMappings,
|
||||
[array]$TweakSettings
|
||||
)
|
||||
|
||||
$settingsJson = [PSCustomObject]@{ Settings = @($TweakSettings) }
|
||||
ApplySettingsToUiControls -window $Owner -settingsJson $settingsJson -uiControlMappings $UiControlMappings
|
||||
}
|
||||
|
||||
function Apply-ImportedDeploymentSettings {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
|
||||
[System.Windows.Controls.TextBox]$OtherUsernameTextBox,
|
||||
[array]$DeploymentSettings
|
||||
)
|
||||
|
||||
$lookup = @{}
|
||||
foreach ($setting in $DeploymentSettings) {
|
||||
$lookup[$setting.Name] = $setting.Value
|
||||
}
|
||||
|
||||
if ($lookup.ContainsKey('UserSelectionIndex')) {
|
||||
$UserSelectionCombo.SelectedIndex = [int]$lookup['UserSelectionIndex']
|
||||
}
|
||||
if ($lookup.ContainsKey('OtherUsername') -and $UserSelectionCombo.SelectedIndex -eq 1) {
|
||||
$OtherUsernameTextBox.Text = $lookup['OtherUsername']
|
||||
}
|
||||
|
||||
$appRemovalScopeCombo = $Owner.FindName('AppRemovalScopeCombo')
|
||||
if ($lookup.ContainsKey('AppRemovalScopeIndex') -and $appRemovalScopeCombo) {
|
||||
$appRemovalScopeCombo.SelectedIndex = [int]$lookup['AppRemovalScopeIndex']
|
||||
}
|
||||
|
||||
$restorePointCheckBox = $Owner.FindName('RestorePointCheckBox')
|
||||
if ($lookup.ContainsKey('CreateRestorePoint') -and $restorePointCheckBox) {
|
||||
$restorePointCheckBox.IsChecked = [bool]$lookup['CreateRestorePoint']
|
||||
}
|
||||
|
||||
$restartExplorerCheckBox = $Owner.FindName('RestartExplorerCheckBox')
|
||||
if ($lookup.ContainsKey('RestartExplorer') -and $restartExplorerCheckBox) {
|
||||
$restartExplorerCheckBox.IsChecked = [bool]$lookup['RestartExplorer']
|
||||
}
|
||||
}
|
||||
|
||||
function Export-Configuration {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[bool]$UsesDarkMode,
|
||||
[System.Windows.Controls.Panel]$AppsPanel,
|
||||
[hashtable]$UiControlMappings,
|
||||
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
|
||||
[System.Windows.Controls.TextBox]$OtherUsernameTextBox
|
||||
)
|
||||
|
||||
# Precompute exportable data so empty categories can be disabled in the picker.
|
||||
$selectedApps = Get-SelectedApplications -AppsPanel $AppsPanel
|
||||
$tweakSettings = Get-SelectedTweakSettings -Owner $Owner -UiControlMappings $UiControlMappings
|
||||
|
||||
$disabledCategories = @()
|
||||
if ($selectedApps.Count -eq 0) { $disabledCategories += 'Applications' }
|
||||
if ($tweakSettings.Count -eq 0) { $disabledCategories += 'System Tweaks' }
|
||||
|
||||
$categories = Show-ImportExportConfigWindow -Owner $Owner -UsesDarkMode $UsesDarkMode -Title 'Export Configuration' -Prompt 'Select which settings to include in the export:' -DisabledCategories $disabledCategories
|
||||
if (-not $categories) { return }
|
||||
|
||||
$config = @{ Version = '1.0' }
|
||||
|
||||
if ($categories -contains 'Applications') {
|
||||
$config['Apps'] = @($selectedApps)
|
||||
}
|
||||
if ($categories -contains 'System Tweaks') {
|
||||
$config['Tweaks'] = @($tweakSettings)
|
||||
}
|
||||
if ($categories -contains 'Deployment Settings') {
|
||||
$config['Deployment'] = @(Get-DeploymentSettings -Owner $Owner -UserSelectionCombo $UserSelectionCombo -OtherUsernameTextBox $OtherUsernameTextBox)
|
||||
}
|
||||
|
||||
# Show native save-file dialog
|
||||
$saveDialog = New-Object Microsoft.Win32.SaveFileDialog
|
||||
$saveDialog.Title = 'Export Configuration'
|
||||
$saveDialog.Filter = 'JSON files (*.json)|*.json|All files (*.*)|*.*'
|
||||
$saveDialog.DefaultExt = '.json'
|
||||
$saveDialog.FileName = "Win11Debloat-Config-$(Get-Date -Format 'yyyyMMdd').json"
|
||||
|
||||
if ($saveDialog.ShowDialog($Owner) -ne $true) { return }
|
||||
|
||||
if (SaveToFile -Config $config -FilePath $saveDialog.FileName) {
|
||||
Show-MessageBox -Message "Configuration exported successfully." -Title 'Export Configuration' -Button 'OK' -Icon 'Information' | Out-Null
|
||||
}
|
||||
else {
|
||||
Show-MessageBox -Message "Failed to export configuration" -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Import-Configuration {
|
||||
param (
|
||||
[System.Windows.Window]$Owner,
|
||||
[bool]$UsesDarkMode,
|
||||
[System.Windows.Controls.Panel]$AppsPanel,
|
||||
[hashtable]$UiControlMappings,
|
||||
[System.Windows.Controls.ComboBox]$UserSelectionCombo,
|
||||
[System.Windows.Controls.TextBox]$OtherUsernameTextBox,
|
||||
[scriptblock]$OnAppsImported,
|
||||
[scriptblock]$OnImportCompleted
|
||||
)
|
||||
|
||||
# Show native open-file dialog
|
||||
$openDialog = New-Object Microsoft.Win32.OpenFileDialog
|
||||
$openDialog.Title = 'Import Configuration'
|
||||
$openDialog.Filter = 'JSON files (*.json)|*.json|All files (*.*)|*.*'
|
||||
$openDialog.DefaultExt = '.json'
|
||||
|
||||
if ($openDialog.ShowDialog($Owner) -ne $true) { return }
|
||||
|
||||
$config = LoadJsonFile -filePath $openDialog.FileName -expectedVersion '1.0'
|
||||
if (-not $config) {
|
||||
Show-MessageBox -Message "Failed to read configuration file" -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $config.Version) {
|
||||
Show-MessageBox -Message "Invalid configuration file format." -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
$availableCategories = Get-AvailableImportExportCategories -Config $config
|
||||
|
||||
if ($availableCategories.Count -eq 0) {
|
||||
Show-MessageBox -Message "The configuration file contains no importable data." -Title 'Import Configuration' -Button 'OK' -Icon 'Information' | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
$categories = Show-ImportExportConfigWindow -Owner $Owner -UsesDarkMode $UsesDarkMode -Title 'Import Configuration' -Prompt 'Select which settings to import:' -Categories $availableCategories
|
||||
if (-not $categories) { return }
|
||||
|
||||
if ($categories -contains 'Applications' -and $config.Apps) {
|
||||
$appIds = @(
|
||||
$config.Apps |
|
||||
Where-Object { $_ -is [string] } |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
)
|
||||
|
||||
Apply-ImportedApplications -AppsPanel $AppsPanel -AppIds $appIds
|
||||
|
||||
if ($OnAppsImported) {
|
||||
& $OnAppsImported
|
||||
}
|
||||
}
|
||||
if ($categories -contains 'System Tweaks' -and $config.Tweaks) {
|
||||
Apply-ImportedTweakSettings -Owner $Owner -UiControlMappings $UiControlMappings -TweakSettings @($config.Tweaks)
|
||||
}
|
||||
if ($categories -contains 'Deployment Settings' -and $config.Deployment) {
|
||||
Apply-ImportedDeploymentSettings -Owner $Owner -UserSelectionCombo $UserSelectionCombo -OtherUsernameTextBox $OtherUsernameTextBox -DeploymentSettings @($config.Deployment)
|
||||
}
|
||||
|
||||
Show-MessageBox -Message "Configuration imported successfully." -Title 'Import Configuration' -Button 'OK' -Icon 'Information' | Out-Null
|
||||
|
||||
if ($OnImportCompleted) {
|
||||
& $OnImportCompleted $categories
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -152,14 +152,17 @@ function Show-MessageBox {
|
||||
})
|
||||
|
||||
# Show dialog and return result from Tag
|
||||
$msgWindow.ShowDialog() | Out-Null
|
||||
|
||||
# Hide overlay after dialog closes (only if this dialog was the one that showed it)
|
||||
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
try {
|
||||
$msgWindow.ShowDialog() | Out-Null
|
||||
}
|
||||
finally {
|
||||
# Hide overlay after dialog closes (only if this dialog was the one that showed it)
|
||||
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||
try {
|
||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return $msgWindow.Tag
|
||||
|
||||
@@ -11,6 +11,7 @@ param (
|
||||
[switch]$RunDefaults,
|
||||
[switch]$RunDefaultsLite,
|
||||
[switch]$RunSavedSettings,
|
||||
[string]$Config,
|
||||
[string]$Apps,
|
||||
[string]$AppRemovalTarget,
|
||||
[switch]$RemoveApps,
|
||||
@@ -95,7 +96,11 @@ param (
|
||||
[switch]$HideMusic,
|
||||
[switch]$HideIncludeInLibrary,
|
||||
[switch]$HideGiveAccessTo,
|
||||
[switch]$HideShare
|
||||
[switch]$HideShare,
|
||||
[switch]$ShowDriveLettersFirst,
|
||||
[switch]$ShowDriveLettersLast,
|
||||
[switch]$ShowNetworkDriveLettersFirst,
|
||||
[switch]$HideDriveLetters
|
||||
)
|
||||
|
||||
# Show error if current powershell environment does not have LanguageMode set to FullLanguage
|
||||
@@ -215,7 +220,7 @@ if (Test-Path "$env:TEMP/Win11Debloat") {
|
||||
Write-Output "> Cleaning up..."
|
||||
|
||||
# Cleanup, remove Win11Debloat directory
|
||||
Get-ChildItem -Path "$env:TEMP/Win11Debloat" -Exclude CustomAppsList,LastUsedSettings.json,Win11Debloat.log,Config,Logs | Remove-Item -Recurse -Force
|
||||
Get-ChildItem -Path "$env:TEMP/Win11Debloat" -Exclude CustomAppsList,LastUsedSettings.json,Win11Debloat.log,Win11Debloat-Run.log,Config,Logs | Remove-Item -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Output ""
|
||||
|
||||
15
Scripts/Helpers/AddParameter.ps1
Normal file
15
Scripts/Helpers/AddParameter.ps1
Normal file
@@ -0,0 +1,15 @@
|
||||
# Add parameter to script and write to file
|
||||
function AddParameter {
|
||||
param (
|
||||
$parameterName,
|
||||
$value = $true
|
||||
)
|
||||
|
||||
# Add parameter or update its value if key already exists
|
||||
if (-not $script:Params.ContainsKey($parameterName)) {
|
||||
$script:Params.Add($parameterName, $value)
|
||||
}
|
||||
else {
|
||||
$script:Params[$parameterName] = $value
|
||||
}
|
||||
}
|
||||
32
Scripts/Helpers/CheckIfUserExists.ps1
Normal file
32
Scripts/Helpers/CheckIfUserExists.ps1
Normal file
@@ -0,0 +1,32 @@
|
||||
function CheckIfUserExists {
|
||||
param (
|
||||
$userName
|
||||
)
|
||||
|
||||
if ($userName -match '[<>:"|?*]') {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($userName)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
$userExists = Test-Path "$env:SystemDrive\Users\$userName"
|
||||
|
||||
if ($userExists) {
|
||||
return $true
|
||||
}
|
||||
|
||||
$userExists = Test-Path ($env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName")
|
||||
|
||||
if ($userExists) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Something went wrong when trying to find the user directory path for user $userName. Please ensure the user exists on this system"
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
27
Scripts/Helpers/CheckModernStandbySupport.ps1
Normal file
27
Scripts/Helpers/CheckModernStandbySupport.ps1
Normal file
@@ -0,0 +1,27 @@
|
||||
# Check if this machine supports S0 Modern Standby power state. Returns true if S0 Modern Standby is supported, false otherwise.
|
||||
function CheckModernStandbySupport {
|
||||
$count = 0
|
||||
|
||||
try {
|
||||
switch -Regex (powercfg /a) {
|
||||
':' {
|
||||
$count += 1
|
||||
}
|
||||
|
||||
'(.*S0.{1,}\))' {
|
||||
if ($count -eq 1) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error: Unable to check for S0 Modern Standby support, powercfg command failed" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to continue..."
|
||||
$null = [System.Console]::ReadKey()
|
||||
return $true
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
20
Scripts/Helpers/GenerateAppsList.ps1
Normal file
20
Scripts/Helpers/GenerateAppsList.ps1
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generates a list of apps to remove based on the Apps parameter
|
||||
function GenerateAppsList {
|
||||
if (-not ($script:Params["Apps"] -and $script:Params["Apps"] -is [string])) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$appMode = $script:Params["Apps"].toLower()
|
||||
|
||||
switch ($appMode) {
|
||||
'default' {
|
||||
$appsList = LoadAppsFromFile $script:AppsListFilePath
|
||||
return $appsList
|
||||
}
|
||||
default {
|
||||
$appsList = $script:Params["Apps"].Split(',') | ForEach-Object { $_.Trim() }
|
||||
$validatedAppsList = ValidateAppslist $appsList
|
||||
return $validatedAppsList
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Scripts/Helpers/GetFriendlyTargetUserName.ps1
Normal file
9
Scripts/Helpers/GetFriendlyTargetUserName.ps1
Normal file
@@ -0,0 +1,9 @@
|
||||
function GetFriendlyTargetUserName {
|
||||
$target = GetTargetUserForAppRemoval
|
||||
|
||||
switch ($target) {
|
||||
"AllUsers" { return "all users" }
|
||||
"CurrentUser" { return "the current user" }
|
||||
default { return "user $target" }
|
||||
}
|
||||
}
|
||||
9
Scripts/Helpers/GetTargetUserForAppRemoval.ps1
Normal file
9
Scripts/Helpers/GetTargetUserForAppRemoval.ps1
Normal file
@@ -0,0 +1,9 @@
|
||||
# Target is determined from $script:Params["AppRemovalTarget"] or defaults to "AllUsers"
|
||||
# Target values: "AllUsers" (removes for all users + from image), "CurrentUser", or a specific username
|
||||
function GetTargetUserForAppRemoval {
|
||||
if ($script:Params.ContainsKey("AppRemovalTarget")) {
|
||||
return $script:Params["AppRemovalTarget"]
|
||||
}
|
||||
|
||||
return "AllUsers"
|
||||
}
|
||||
36
Scripts/Helpers/GetUserDirectory.ps1
Normal file
36
Scripts/Helpers/GetUserDirectory.ps1
Normal file
@@ -0,0 +1,36 @@
|
||||
# Returns the directory path of the specified user, exits script if user path can't be found
|
||||
function GetUserDirectory {
|
||||
param (
|
||||
$userName,
|
||||
$fileName = "",
|
||||
$exitIfPathNotFound = $true
|
||||
)
|
||||
|
||||
try {
|
||||
if (-not (CheckIfUserExists -userName $userName) -and $userName -ne "*") {
|
||||
Write-Error "User $userName does not exist on this system"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
$userDirectoryExists = Test-Path "$env:SystemDrive\Users\$userName"
|
||||
$userPath = "$env:SystemDrive\Users\$userName\$fileName"
|
||||
|
||||
if ((Test-Path $userPath) -or ($userDirectoryExists -and (-not $exitIfPathNotFound))) {
|
||||
return $userPath
|
||||
}
|
||||
|
||||
$userDirectoryExists = Test-Path ($env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName")
|
||||
$userPath = $env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName\$fileName"
|
||||
|
||||
if ((Test-Path $userPath) -or ($userDirectoryExists -and (-not $exitIfPathNotFound))) {
|
||||
return $userPath
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Something went wrong when trying to find the user directory path for user $userName. Please ensure the user exists on this system"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
Write-Error "Unable to find user directory path for user $userName"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
7
Scripts/Helpers/GetUserName.ps1
Normal file
7
Scripts/Helpers/GetUserName.ps1
Normal file
@@ -0,0 +1,7 @@
|
||||
function GetUserName {
|
||||
if ($script:Params.ContainsKey("User")) {
|
||||
return $script:Params.Item("User")
|
||||
}
|
||||
|
||||
return $env:USERNAME
|
||||
}
|
||||
127
Scripts/Helpers/ImportConfigToParams.ps1
Normal file
127
Scripts/Helpers/ImportConfigToParams.ps1
Normal file
@@ -0,0 +1,127 @@
|
||||
function ImportConfigToParams {
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ConfigPath,
|
||||
[int]$CurrentBuild,
|
||||
[string]$ExpectedVersion = '1.0'
|
||||
)
|
||||
|
||||
$resolvedConfigPath = $null
|
||||
try {
|
||||
$resolvedConfigPath = (Resolve-Path -LiteralPath $ConfigPath -ErrorAction Stop).Path
|
||||
}
|
||||
catch {
|
||||
throw "Unable to find config file at path: $ConfigPath"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedConfigPath -PathType Leaf)) {
|
||||
throw "Provided config path is not a file: $resolvedConfigPath"
|
||||
}
|
||||
|
||||
if ([System.IO.Path]::GetExtension($resolvedConfigPath) -ne '.json') {
|
||||
throw "Provided config file must be a .json file: $resolvedConfigPath"
|
||||
}
|
||||
|
||||
$configJson = LoadJsonFile -filePath $resolvedConfigPath -expectedVersion $ExpectedVersion
|
||||
if ($null -eq $configJson) {
|
||||
throw "Failed to read config file: $resolvedConfigPath"
|
||||
}
|
||||
|
||||
$importedItems = 0
|
||||
|
||||
if ($configJson.Apps) {
|
||||
$appIds = @(
|
||||
$configJson.Apps |
|
||||
Where-Object { $_ -is [string] } |
|
||||
ForEach-Object { $_.Trim() } |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
)
|
||||
|
||||
if ($appIds.Count -gt 0) {
|
||||
AddParameter 'RemoveApps'
|
||||
AddParameter 'Apps' ($appIds -join ',')
|
||||
$importedItems++
|
||||
}
|
||||
}
|
||||
|
||||
if ($configJson.Tweaks) {
|
||||
foreach ($setting in @($configJson.Tweaks)) {
|
||||
if (-not $setting -or -not $setting.Name -or $setting.Value -ne $true) {
|
||||
continue
|
||||
}
|
||||
|
||||
$feature = $script:Features[$setting.Name]
|
||||
if (-not $feature) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (($feature.MinVersion -and $CurrentBuild -lt $feature.MinVersion) -or ($feature.MaxVersion -and $CurrentBuild -gt $feature.MaxVersion) -or ($feature.FeatureId -eq 'DisableModernStandbyNetworking' -and (-not $script:ModernStandbySupported))) {
|
||||
continue
|
||||
}
|
||||
|
||||
AddParameter $setting.Name $true
|
||||
$importedItems++
|
||||
}
|
||||
}
|
||||
|
||||
if ($configJson.Deployment) {
|
||||
$deploymentLookup = @{}
|
||||
foreach ($setting in @($configJson.Deployment)) {
|
||||
if ($setting -and $setting.Name) {
|
||||
$deploymentLookup[$setting.Name] = $setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('CreateRestorePoint') -and [bool]$deploymentLookup['CreateRestorePoint']) {
|
||||
AddParameter 'CreateRestorePoint'
|
||||
$importedItems++
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('RestartExplorer') -and -not [bool]$deploymentLookup['RestartExplorer']) {
|
||||
AddParameter 'NoRestartExplorer'
|
||||
$importedItems++
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('UserSelectionIndex')) {
|
||||
switch ([int]$deploymentLookup['UserSelectionIndex']) {
|
||||
1 {
|
||||
$otherUserName = if ($deploymentLookup.ContainsKey('OtherUsername')) { "$($deploymentLookup['OtherUsername'])".Trim() } else { '' }
|
||||
if (-not [string]::IsNullOrWhiteSpace($otherUserName)) {
|
||||
AddParameter 'User' $otherUserName
|
||||
$importedItems++
|
||||
}
|
||||
}
|
||||
2 {
|
||||
AddParameter 'Sysprep'
|
||||
$importedItems++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($deploymentLookup.ContainsKey('AppRemovalScopeIndex') -and $script:Params.ContainsKey('RemoveApps')) {
|
||||
switch ([int]$deploymentLookup['AppRemovalScopeIndex']) {
|
||||
0 {
|
||||
AddParameter 'AppRemovalTarget' 'AllUsers'
|
||||
$importedItems++
|
||||
}
|
||||
1 {
|
||||
AddParameter 'AppRemovalTarget' 'CurrentUser'
|
||||
$importedItems++
|
||||
}
|
||||
2 {
|
||||
$targetUser = if ($deploymentLookup.ContainsKey('OtherUsername')) { "$($deploymentLookup['OtherUsername'])".Trim() } else { '' }
|
||||
if (-not [string]::IsNullOrWhiteSpace($targetUser)) {
|
||||
AddParameter 'AppRemovalTarget' $targetUser
|
||||
$importedItems++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($importedItems -eq 0) {
|
||||
throw "The config file contains no importable data: $resolvedConfigPath"
|
||||
}
|
||||
|
||||
return $resolvedConfigPath
|
||||
}
|
||||
42
Scripts/Helpers/TestIfUserIsLoggedIn.ps1
Normal file
42
Scripts/Helpers/TestIfUserIsLoggedIn.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
function TestIfUserIsLoggedIn {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$Username
|
||||
)
|
||||
|
||||
try {
|
||||
$quserOutput = @(& quser 2>$null)
|
||||
if ($LASTEXITCODE -ne 0 -or -not $quserOutput) {
|
||||
return $false
|
||||
}
|
||||
|
||||
foreach ($line in ($quserOutput | Select-Object -Skip 1)) {
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||
|
||||
# Remove current-session marker and split columns.
|
||||
$normalizedLine = $line.TrimStart('>', ' ')
|
||||
$parts = $normalizedLine -split '\s+'
|
||||
if ($parts.Count -eq 0) { continue }
|
||||
|
||||
$sessionUser = $parts[0]
|
||||
if ([string]::IsNullOrWhiteSpace($sessionUser)) { continue }
|
||||
|
||||
# Normalize possible DOMAIN\user or user@domain formats.
|
||||
if ($sessionUser.Contains('\')) {
|
||||
$sessionUser = ($sessionUser -split '\\')[-1]
|
||||
}
|
||||
if ($sessionUser.Contains('@')) {
|
||||
$sessionUser = ($sessionUser -split '@')[0]
|
||||
}
|
||||
|
||||
if ($sessionUser.Equals($Username, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
16
Scripts/Threading/DoEvents.ps1
Normal file
16
Scripts/Threading/DoEvents.ps1
Normal file
@@ -0,0 +1,16 @@
|
||||
# Processes all pending WPF window messages (input, render, etc.) to keep the UI responsive
|
||||
# during long-running operations on the UI thread. Equivalent to Application.DoEvents().
|
||||
function DoEvents {
|
||||
if (-not $script:GuiWindow) { return }
|
||||
$frame = [System.Windows.Threading.DispatcherFrame]::new()
|
||||
$null = [System.Windows.Threading.Dispatcher]::CurrentDispatcher.BeginInvoke(
|
||||
[System.Windows.Threading.DispatcherPriority]::Background,
|
||||
[System.Windows.Threading.DispatcherOperationCallback]{
|
||||
param($f)
|
||||
$f.Continue = $false
|
||||
return $null
|
||||
},
|
||||
$frame
|
||||
)
|
||||
$null = [System.Windows.Threading.Dispatcher]::PushFrame($frame)
|
||||
}
|
||||
55
Scripts/Threading/Invoke-NonBlocking.ps1
Normal file
55
Scripts/Threading/Invoke-NonBlocking.ps1
Normal file
@@ -0,0 +1,55 @@
|
||||
# Runs a scriptblock in a background PowerShell runspace while keeping the UI responsive.
|
||||
# In GUI mode, the work executes on a separate thread and the UI thread pumps messages (~60fps).
|
||||
# In CLI mode, the scriptblock runs directly in the current session.
|
||||
function Invoke-NonBlocking {
|
||||
param(
|
||||
[scriptblock]$ScriptBlock,
|
||||
[object[]]$ArgumentList = @(),
|
||||
[int]$TimeoutSeconds = 0
|
||||
)
|
||||
|
||||
# CLI mode without timeout: run directly in-process
|
||||
if (-not $script:GuiWindow -and $TimeoutSeconds -eq 0) {
|
||||
return (& $ScriptBlock @ArgumentList)
|
||||
}
|
||||
|
||||
$ps = [powershell]::Create()
|
||||
try {
|
||||
$null = $ps.AddScript($ScriptBlock.ToString())
|
||||
foreach ($arg in $ArgumentList) {
|
||||
$null = $ps.AddArgument($arg)
|
||||
}
|
||||
|
||||
$handle = $ps.BeginInvoke()
|
||||
|
||||
if ($script:GuiWindow) {
|
||||
# GUI mode: pump UI messages while waiting
|
||||
$stopwatch = if ($TimeoutSeconds -gt 0) { [System.Diagnostics.Stopwatch]::StartNew() } else { $null }
|
||||
|
||||
while (-not $handle.IsCompleted) {
|
||||
if ($stopwatch -and $stopwatch.Elapsed.TotalSeconds -ge $TimeoutSeconds) {
|
||||
$ps.Stop()
|
||||
throw "Operation timed out after $TimeoutSeconds seconds"
|
||||
}
|
||||
DoEvents
|
||||
Start-Sleep -Milliseconds 16
|
||||
}
|
||||
}
|
||||
else {
|
||||
# CLI mode with timeout: block until completion or timeout
|
||||
if (-not $handle.AsyncWaitHandle.WaitOne($TimeoutSeconds * 1000)) {
|
||||
$ps.Stop()
|
||||
throw "Operation timed out after $TimeoutSeconds seconds"
|
||||
}
|
||||
}
|
||||
|
||||
$result = $ps.EndInvoke($handle)
|
||||
|
||||
if ($result.Count -eq 0) { return $null }
|
||||
if ($result.Count -eq 1) { return $result[0] }
|
||||
return @($result)
|
||||
}
|
||||
finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
545
Win11Debloat.ps1
Executable file → Normal file
545
Win11Debloat.ps1
Executable file → Normal file
@@ -13,6 +13,7 @@ param (
|
||||
[switch]$RunDefaults,
|
||||
[switch]$RunDefaultsLite,
|
||||
[switch]$RunSavedSettings,
|
||||
[string]$Config,
|
||||
[string]$Apps,
|
||||
[string]$AppRemovalTarget,
|
||||
[switch]$RemoveApps,
|
||||
@@ -97,13 +98,17 @@ param (
|
||||
[switch]$HideMusic,
|
||||
[switch]$HideIncludeInLibrary,
|
||||
[switch]$HideGiveAccessTo,
|
||||
[switch]$HideShare
|
||||
[switch]$HideShare,
|
||||
[switch]$ShowDriveLettersFirst,
|
||||
[switch]$ShowDriveLettersLast,
|
||||
[switch]$ShowNetworkDriveLettersFirst,
|
||||
[switch]$HideDriveLetters
|
||||
)
|
||||
|
||||
|
||||
|
||||
# Define script-level variables & paths
|
||||
$script:Version = "2026.03.09"
|
||||
$script:Version = "2026.03.15"
|
||||
$script:AppsListFilePath = "$PSScriptRoot/Config/Apps.json"
|
||||
$script:DefaultSettingsFilePath = "$PSScriptRoot/Config/DefaultSettings.json"
|
||||
$script:FeaturesFilePath = "$PSScriptRoot/Config/Features.json"
|
||||
@@ -118,8 +123,11 @@ $script:MessageBoxSchema = "$PSScriptRoot/Schemas/MessageBoxWindow.xaml"
|
||||
$script:AboutWindowSchema = "$PSScriptRoot/Schemas/AboutWindow.xaml"
|
||||
$script:ApplyChangesWindowSchema = "$PSScriptRoot/Schemas/ApplyChangesWindow.xaml"
|
||||
$script:SharedStylesSchema = "$PSScriptRoot/Schemas/SharedStyles.xaml"
|
||||
$script:BubbleHintSchema = "$PSScriptRoot/Schemas/BubbleHint.xaml"
|
||||
$script:ImportExportConfigSchema = "$PSScriptRoot/Schemas/ImportExportConfigWindow.xaml"
|
||||
$script:LoadAppsDetailsScriptPath = "$PSScriptRoot/Scripts/FileIO/LoadAppsDetailsFromJson.ps1"
|
||||
|
||||
$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'RunAppsListGenerator', 'CLI', 'AppRemovalTarget'
|
||||
$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'Config', 'RunAppsListGenerator', 'CLI', 'AppRemovalTarget'
|
||||
|
||||
# Script-level variables for GUI elements
|
||||
$script:GuiWindow = $null
|
||||
@@ -167,7 +175,7 @@ else {
|
||||
}
|
||||
|
||||
# Check if script has all required files
|
||||
if (-not ((Test-Path $script:DefaultSettingsFilePath) -and (Test-Path $script:AppsListFilePath) -and (Test-Path $script:RegfilesPath) -and (Test-Path $script:AssetsPath) -and (Test-Path $script:AppSelectionSchema) -and (Test-Path $script:ApplyChangesWindowSchema) -and (Test-Path $script:SharedStylesSchema) -and (Test-Path $script:FeaturesFilePath))) {
|
||||
if (-not ((Test-Path $script:DefaultSettingsFilePath) -and (Test-Path $script:AppsListFilePath) -and (Test-Path $script:RegfilesPath) -and (Test-Path $script:AssetsPath) -and (Test-Path $script:AppSelectionSchema) -and (Test-Path $script:ApplyChangesWindowSchema) -and (Test-Path $script:SharedStylesSchema) -and (Test-Path $script:BubbleHintSchema) -and (Test-Path $script:FeaturesFilePath))) {
|
||||
Write-Error "Win11Debloat is unable to find required files, please ensure all script files are present"
|
||||
Write-Output ""
|
||||
Write-Output "Press any key to exit..."
|
||||
@@ -217,15 +225,16 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
||||
|
||||
##################################################################################################################
|
||||
# #
|
||||
# FUNCTION IMPORTS/DEFINITIONS #
|
||||
# FUNCTION IMPORTS #
|
||||
# #
|
||||
##################################################################################################################
|
||||
|
||||
# Load app removal functions
|
||||
# App removal functions
|
||||
. "$PSScriptRoot/Scripts/AppRemoval/ForceRemoveEdge.ps1"
|
||||
. "$PSScriptRoot/Scripts/AppRemoval/RemoveApps.ps1"
|
||||
. "$PSScriptRoot/Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1"
|
||||
|
||||
# Load CLI functions
|
||||
# CLI functions
|
||||
. "$PSScriptRoot/Scripts/CLI/AwaitKeyToExit.ps1"
|
||||
. "$PSScriptRoot/Scripts/CLI/ShowCLILastUsedSettings.ps1"
|
||||
. "$PSScriptRoot/Scripts/CLI/ShowCLIDefaultModeAppRemovalOptions.ps1"
|
||||
@@ -235,7 +244,8 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
||||
. "$PSScriptRoot/Scripts/CLI/PrintPendingChanges.ps1"
|
||||
. "$PSScriptRoot/Scripts/CLI/PrintHeader.ps1"
|
||||
|
||||
# Load Feature functions
|
||||
# Features functions
|
||||
. "$PSScriptRoot/Scripts/Features/ExecuteChanges.ps1"
|
||||
. "$PSScriptRoot/Scripts/Features/CreateSystemRestorePoint.ps1"
|
||||
. "$PSScriptRoot/Scripts/Features/DisableStoreSearchSuggestions.ps1"
|
||||
. "$PSScriptRoot/Scripts/Features/EnableWindowsFeature.ps1"
|
||||
@@ -243,478 +253,45 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
||||
. "$PSScriptRoot/Scripts/Features/ReplaceStartMenu.ps1"
|
||||
. "$PSScriptRoot/Scripts/Features/RestartExplorer.ps1"
|
||||
|
||||
# Load GUI functions
|
||||
. "$PSScriptRoot/Scripts/GUI/GetSystemUsesDarkMode.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/SetWindowThemeResources.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/AttachShiftClickBehavior.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/ApplySettingsToUiControls.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-MessageBox.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-ApplyModal.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-AppSelectionWindow.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1"
|
||||
|
||||
# Load File I/O functions
|
||||
# File I/O functions
|
||||
. "$PSScriptRoot/Scripts/FileIO/LoadJsonFile.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/SaveToFile.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/SaveSettings.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/LoadSettings.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/SaveCustomAppsListToFile.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/ValidateAppslist.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/LoadAppsFromFile.ps1"
|
||||
. "$PSScriptRoot/Scripts/FileIO/LoadAppsDetailsFromJson.ps1"
|
||||
|
||||
# Processes all pending WPF window messages (input, render, etc.) to keep the UI responsive
|
||||
# during long-running operations on the UI thread. Equivalent to Application.DoEvents().
|
||||
function DoEvents {
|
||||
if (-not $script:GuiWindow) { return }
|
||||
$frame = [System.Windows.Threading.DispatcherFrame]::new()
|
||||
[System.Windows.Threading.Dispatcher]::CurrentDispatcher.BeginInvoke(
|
||||
[System.Windows.Threading.DispatcherPriority]::Background,
|
||||
[System.Windows.Threading.DispatcherOperationCallback]{
|
||||
param($f)
|
||||
$f.Continue = $false
|
||||
return $null
|
||||
},
|
||||
$frame
|
||||
)
|
||||
[System.Windows.Threading.Dispatcher]::PushFrame($frame)
|
||||
}
|
||||
|
||||
|
||||
# Runs a scriptblock in a background PowerShell runspace while keeping the UI responsive.
|
||||
# In GUI mode, the work executes on a separate thread and the UI thread pumps messages (~60fps).
|
||||
# In CLI mode, the scriptblock runs directly in the current session.
|
||||
function Invoke-NonBlocking {
|
||||
param(
|
||||
[scriptblock]$ScriptBlock,
|
||||
[object[]]$ArgumentList = @()
|
||||
)
|
||||
|
||||
if (-not $script:GuiWindow) {
|
||||
return (& $ScriptBlock @ArgumentList)
|
||||
}
|
||||
|
||||
$ps = [powershell]::Create()
|
||||
try {
|
||||
$null = $ps.AddScript($ScriptBlock.ToString())
|
||||
foreach ($arg in $ArgumentList) {
|
||||
$null = $ps.AddArgument($arg)
|
||||
}
|
||||
|
||||
$handle = $ps.BeginInvoke()
|
||||
|
||||
while (-not $handle.IsCompleted) {
|
||||
DoEvents
|
||||
Start-Sleep -Milliseconds 16
|
||||
}
|
||||
|
||||
$result = $ps.EndInvoke($handle)
|
||||
|
||||
if ($result.Count -eq 0) { return $null }
|
||||
if ($result.Count -eq 1) { return $result[0] }
|
||||
return @($result)
|
||||
}
|
||||
finally {
|
||||
$ps.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Add parameter to script and write to file
|
||||
function AddParameter {
|
||||
param (
|
||||
$parameterName,
|
||||
$value = $true
|
||||
)
|
||||
|
||||
# Add parameter or update its value if key already exists
|
||||
if (-not $script:Params.ContainsKey($parameterName)) {
|
||||
$script:Params.Add($parameterName, $value)
|
||||
}
|
||||
else {
|
||||
$script:Params[$parameterName] = $value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Run winget list and return installed apps (sync or async)
|
||||
function GetInstalledAppsViaWinget {
|
||||
param (
|
||||
[int]$TimeOut = 10,
|
||||
[switch]$Async
|
||||
)
|
||||
|
||||
if (-not $script:WingetInstalled) { return $null }
|
||||
|
||||
if ($Async) {
|
||||
$wingetListJob = Start-Job { return winget list --accept-source-agreements --disable-interactivity }
|
||||
return @{ Job = $wingetListJob; StartTime = Get-Date }
|
||||
}
|
||||
else {
|
||||
$wingetListJob = Start-Job { return winget list --accept-source-agreements --disable-interactivity }
|
||||
$jobDone = $wingetListJob | Wait-Job -TimeOut $TimeOut
|
||||
if (-not $jobDone) {
|
||||
Remove-Job -Job $wingetListJob -Force -ErrorAction SilentlyContinue
|
||||
return $null
|
||||
}
|
||||
$result = Receive-Job -Job $wingetListJob
|
||||
Remove-Job -Job $wingetListJob -ErrorAction SilentlyContinue
|
||||
return $result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GetUserName {
|
||||
if ($script:Params.ContainsKey("User")) {
|
||||
return $script:Params.Item("User")
|
||||
}
|
||||
|
||||
return $env:USERNAME
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Returns the directory path of the specified user, exits script if user path can't be found
|
||||
function GetUserDirectory {
|
||||
param (
|
||||
$userName,
|
||||
$fileName = "",
|
||||
$exitIfPathNotFound = $true
|
||||
)
|
||||
|
||||
try {
|
||||
if (-not (CheckIfUserExists -userName $userName) -and $userName -ne "*") {
|
||||
Write-Error "User $userName does not exist on this system"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
$userDirectoryExists = Test-Path "$env:SystemDrive\Users\$userName"
|
||||
$userPath = "$env:SystemDrive\Users\$userName\$fileName"
|
||||
|
||||
if ((Test-Path $userPath) -or ($userDirectoryExists -and (-not $exitIfPathNotFound))) {
|
||||
return $userPath
|
||||
}
|
||||
|
||||
$userDirectoryExists = Test-Path ($env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName")
|
||||
$userPath = $env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName\$fileName"
|
||||
|
||||
if ((Test-Path $userPath) -or ($userDirectoryExists -and (-not $exitIfPathNotFound))) {
|
||||
return $userPath
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Something went wrong when trying to find the user directory path for user $userName. Please ensure the user exists on this system"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
Write-Error "Unable to find user directory path for user $userName"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
|
||||
function CheckIfUserExists {
|
||||
param (
|
||||
$userName
|
||||
)
|
||||
|
||||
if ($userName -match '[<>:"|?*]') {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($userName)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
$userExists = Test-Path "$env:SystemDrive\Users\$userName"
|
||||
|
||||
if ($userExists) {
|
||||
return $true
|
||||
}
|
||||
|
||||
$userExists = Test-Path ($env:USERPROFILE -Replace ('\\' + $env:USERNAME + '$'), "\$userName")
|
||||
|
||||
if ($userExists) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Something went wrong when trying to find the user directory path for user $userName. Please ensure the user exists on this system"
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
# Target is determined from $script:Params["AppRemovalTarget"] or defaults to "AllUsers"
|
||||
# Target values: "AllUsers" (removes for all users + from image), "CurrentUser", or a specific username
|
||||
function GetTargetUserForAppRemoval {
|
||||
if ($script:Params.ContainsKey("AppRemovalTarget")) {
|
||||
return $script:Params["AppRemovalTarget"]
|
||||
}
|
||||
|
||||
return "AllUsers"
|
||||
}
|
||||
|
||||
|
||||
function GetFriendlyTargetUserName {
|
||||
$target = GetTargetUserForAppRemoval
|
||||
|
||||
switch ($target) {
|
||||
"AllUsers" { return "all users" }
|
||||
"CurrentUser" { return "the current user" }
|
||||
default { return "user $target" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Check if this machine supports S0 Modern Standby power state. Returns true if S0 Modern Standby is supported, false otherwise.
|
||||
function CheckModernStandbySupport {
|
||||
$count = 0
|
||||
|
||||
try {
|
||||
switch -Regex (powercfg /a) {
|
||||
':' {
|
||||
$count += 1
|
||||
}
|
||||
|
||||
'(.*S0.{1,}\))' {
|
||||
if ($count -eq 1) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "Error: Unable to check for S0 Modern Standby support, powercfg command failed" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Press any key to continue..."
|
||||
$null = [System.Console]::ReadKey()
|
||||
return $true
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
# Generates a list of apps to remove based on the Apps parameter
|
||||
function GenerateAppsList {
|
||||
if (-not ($script:Params["Apps"] -and $script:Params["Apps"] -is [string])) {
|
||||
return @()
|
||||
}
|
||||
|
||||
$appMode = $script:Params["Apps"].toLower()
|
||||
|
||||
switch ($appMode) {
|
||||
'default' {
|
||||
$appsList = LoadAppsFromFile $script:AppsListFilePath
|
||||
return $appsList
|
||||
}
|
||||
default {
|
||||
$appsList = $script:Params["Apps"].Split(',') | ForEach-Object { $_.Trim() }
|
||||
$validatedAppsList = ValidateAppslist $appsList
|
||||
return $validatedAppsList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Executes a single parameter/feature based on its key
|
||||
# Parameters:
|
||||
# $paramKey - The parameter name to execute
|
||||
function ExecuteParameter {
|
||||
param (
|
||||
[string]$paramKey
|
||||
)
|
||||
|
||||
# Check if this feature has metadata in Features.json
|
||||
$feature = $null
|
||||
if ($script:Features.ContainsKey($paramKey)) {
|
||||
$feature = $script:Features[$paramKey]
|
||||
}
|
||||
|
||||
# If feature has RegistryKey and ApplyText, use dynamic ImportRegistryFile
|
||||
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
||||
ImportRegistryFile "> $($feature.ApplyText)" $feature.RegistryKey
|
||||
|
||||
# Handle special cases that have additional logic after ImportRegistryFile
|
||||
switch ($paramKey) {
|
||||
'DisableBing' {
|
||||
# Also remove the app package for Bing search
|
||||
RemoveApps 'Microsoft.BingSearch'
|
||||
}
|
||||
'DisableCopilot' {
|
||||
# Also remove the app package for Copilot
|
||||
RemoveApps 'Microsoft.Copilot'
|
||||
}
|
||||
'DisableWidgets' {
|
||||
# Also remove the app package for Widgets
|
||||
RemoveApps 'Microsoft.StartExperiencesApp'
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Handle features without RegistryKey or with special logic
|
||||
switch ($paramKey) {
|
||||
'RemoveApps' {
|
||||
Write-Host "> Removing selected apps for $(GetFriendlyTargetUserName)..."
|
||||
$appsList = GenerateAppsList
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
}
|
||||
'RemoveAppsCustom' {
|
||||
Write-Host "> Removing selected apps..."
|
||||
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
}
|
||||
'RemoveCommApps' {
|
||||
$appsList = 'Microsoft.windowscommunicationsapps', 'Microsoft.People'
|
||||
Write-Host "> Removing Mail, Calendar and People apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveW11Outlook' {
|
||||
$appsList = 'Microsoft.OutlookForWindows'
|
||||
Write-Host "> Removing new Outlook for Windows app..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveGamingApps' {
|
||||
$appsList = 'Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay'
|
||||
Write-Host "> Removing gaming related apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveHPApps' {
|
||||
$appsList = 'AD2F1837.HPAIExperienceCenter', 'AD2F1837.HPJumpStarts', 'AD2F1837.HPPCHardwareDiagnosticsWindows', 'AD2F1837.HPPowerManager', 'AD2F1837.HPPrivacySettings', 'AD2F1837.HPSupportAssistant', 'AD2F1837.HPSureShieldAI', 'AD2F1837.HPSystemInformation', 'AD2F1837.HPQuickDrop', 'AD2F1837.HPWorkWell', 'AD2F1837.myHP', 'AD2F1837.HPDesktopSupportUtilities', 'AD2F1837.HPQuickTouch', 'AD2F1837.HPEasyClean', 'AD2F1837.HPConnectedMusic', 'AD2F1837.HPFileViewer', 'AD2F1837.HPRegistration', 'AD2F1837.HPWelcome', 'AD2F1837.HPConnectedPhotopoweredbySnapfish', 'AD2F1837.HPPrinterControl'
|
||||
Write-Host "> Removing HP apps..."
|
||||
RemoveApps $appsList
|
||||
return
|
||||
}
|
||||
"EnableWindowsSandbox" {
|
||||
Write-Host "> Enabling Windows Sandbox..."
|
||||
EnableWindowsFeature "Containers-DisposableClientVM"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
"EnableWindowsSubsystemForLinux" {
|
||||
Write-Host "> Enabling Windows Subsystem for Linux..."
|
||||
EnableWindowsFeature "VirtualMachinePlatform"
|
||||
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStart' {
|
||||
Write-Host "> Removing all pinned apps from the start menu for user $(GetUserName)..."
|
||||
ReplaceStartMenu
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ReplaceStart' {
|
||||
Write-Host "> Replacing the start menu for user $(GetUserName)..."
|
||||
ReplaceStartMenu $script:Params.Item("ReplaceStart")
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers
|
||||
return
|
||||
}
|
||||
'ReplaceStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers $script:Params.Item("ReplaceStartAllUsers")
|
||||
return
|
||||
}
|
||||
'DisableStoreSearchSuggestions' {
|
||||
if ($script:Params.ContainsKey("Sysprep")) {
|
||||
Write-Host "> Disabling Microsoft Store search suggestions in the start menu for all users..."
|
||||
DisableStoreSearchSuggestionsForAllUsers
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "> Disabling Microsoft Store search suggestions for user $(GetUserName)..."
|
||||
DisableStoreSearchSuggestions
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Executes all selected parameters/features
|
||||
# Parameters:
|
||||
function ExecuteAllChanges {
|
||||
# Build list of actionable parameters (skip control params and data-only params)
|
||||
$actionableKeys = @()
|
||||
foreach ($paramKey in $script:Params.Keys) {
|
||||
if ($script:ControlParams -contains $paramKey) { continue }
|
||||
if ($paramKey -eq 'Apps') { continue }
|
||||
if ($paramKey -eq 'CreateRestorePoint') { continue }
|
||||
$actionableKeys += $paramKey
|
||||
}
|
||||
|
||||
$totalSteps = $actionableKeys.Count
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||
$currentStep = 0
|
||||
|
||||
# Create restore point if requested (CLI only - GUI handles this separately)
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||
$currentStep++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point"
|
||||
}
|
||||
Write-Host "> Attempting to create a system restore point..."
|
||||
CreateSystemRestorePoint
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Execute all parameters
|
||||
foreach ($paramKey in $actionableKeys) {
|
||||
if ($script:CancelRequested) {
|
||||
return
|
||||
}
|
||||
|
||||
$currentStep++
|
||||
|
||||
# Get friendly name for the step
|
||||
$stepName = $paramKey
|
||||
if ($script:Features.ContainsKey($paramKey)) {
|
||||
$feature = $script:Features[$paramKey]
|
||||
if ($feature.ApplyText) {
|
||||
# Prefer explicit ApplyText when provided
|
||||
$stepName = $feature.ApplyText
|
||||
} elseif ($feature.Label) {
|
||||
# Fallback: construct a name from Action and Label, or just Label
|
||||
if ($feature.Action) {
|
||||
$stepName = "$($feature.Action) $($feature.Label)"
|
||||
} else {
|
||||
$stepName = $feature.Label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $currentStep $totalSteps $stepName
|
||||
}
|
||||
|
||||
ExecuteParameter -paramKey $paramKey
|
||||
}
|
||||
}
|
||||
. "$PSScriptRoot/Scripts/FileIO/LoadAppPresetsFromJson.ps1"
|
||||
|
||||
# GUI functions
|
||||
. "$PSScriptRoot/Scripts/GUI/GetSystemUsesDarkMode.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/SetWindowThemeResources.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/AttachShiftClickBehavior.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/ApplySettingsToUiControls.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-MessageBox.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-ConfigWindow.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-ApplyModal.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-AppSelectionWindow.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1"
|
||||
. "$PSScriptRoot/Scripts/GUI/Show-Bubble.ps1"
|
||||
|
||||
# Helper functions
|
||||
. "$PSScriptRoot/Scripts/Helpers/AddParameter.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/CheckIfUserExists.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/CheckModernStandbySupport.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/GetFriendlyTargetUserName.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/ImportConfigToParams.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/GetTargetUserForAppRemoval.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
|
||||
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1"
|
||||
|
||||
# Threading functions
|
||||
. "$PSScriptRoot/Scripts/Threading/DoEvents.ps1"
|
||||
. "$PSScriptRoot/Scripts/Threading/Invoke-NonBlocking.ps1"
|
||||
|
||||
|
||||
|
||||
@@ -784,6 +361,9 @@ if ((Test-Path $script:SavedSettingsFilePath) -and ([String]::IsNullOrWhiteSpace
|
||||
Remove-Item -Path $script:SavedSettingsFilePath -recurse
|
||||
}
|
||||
|
||||
# Default to CLI mode for deployment-targeted parameters.
|
||||
$launchInCLI = $CLI -or $script:Params.ContainsKey("User") -or $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("AppRemovalTarget")
|
||||
|
||||
# Only run the app selection form if the 'RunAppsListGenerator' parameter was passed to the script
|
||||
if ($RunAppsListGenerator) {
|
||||
PrintHeader "Custom Apps List Generator"
|
||||
@@ -803,7 +383,7 @@ if ($RunAppsListGenerator) {
|
||||
}
|
||||
|
||||
# Change script execution based on provided parameters or user input
|
||||
if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSavedSettings -or ($controlParamsCount -eq $script:Params.Count)) {
|
||||
if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSavedSettings -or $Config -or ($controlParamsCount -eq $script:Params.Count)) {
|
||||
if ($RunDefaults -or $RunDefaultsLite) {
|
||||
ShowCLIDefaultModeOptions
|
||||
}
|
||||
@@ -816,8 +396,23 @@ if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSa
|
||||
|
||||
ShowCLILastUsedSettings
|
||||
}
|
||||
elseif ($Config) {
|
||||
try {
|
||||
ImportConfigToParams -ConfigPath $Config -CurrentBuild $WinVersion -ExpectedVersion '1.0'
|
||||
}
|
||||
catch {
|
||||
Write-Error "$_"
|
||||
AwaitKeyToExit
|
||||
}
|
||||
|
||||
if (-not $Silent) {
|
||||
PrintHeader 'Custom Mode'
|
||||
PrintPendingChanges
|
||||
PrintHeader 'Custom Mode'
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($CLI) {
|
||||
if ($launchInCLI) {
|
||||
$Mode = ShowCLIMenuOptions
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user