mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-04-03 14:06:27 +00:00
Compare commits
12 Commits
2026.03.15
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd61902ed | ||
|
|
54edf224a3 | ||
|
|
3eade4ddba | ||
|
|
487b1fbee9 | ||
|
|
774c8ecd92 | ||
|
|
e05af92acc | ||
|
|
edd815fdbb | ||
|
|
17ee530962 | ||
|
|
999e442658 | ||
|
|
1d4cf4a801 | ||
|
|
7a3431e56b | ||
|
|
1b41f05743 |
@@ -647,7 +647,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"FriendlyName": "Microsoft Edge",
|
"FriendlyName": "Microsoft Edge",
|
||||||
"AppId": "Microsoft.Edge",
|
"AppId": ["Microsoft.Edge", "XPFFTQ037JWMHS"],
|
||||||
"Description": "Windows' default browser, WARNING: Removing this app also removes the only browser from Windows Sandbox and could affect other apps",
|
"Description": "Windows' default browser, WARNING: Removing this app also removes the only browser from Windows Sandbox and could affect other apps",
|
||||||
"SelectedByDefault": false,
|
"SelectedByDefault": false,
|
||||||
"Recommendation": "unsafe"
|
"Recommendation": "unsafe"
|
||||||
|
|||||||
@@ -187,6 +187,7 @@
|
|||||||
"Label": "Open File Explorer to",
|
"Label": "Open File Explorer to",
|
||||||
"ToolTip": "This setting allows you to choose the default location that File Explorer opens to.",
|
"ToolTip": "This setting allows you to choose the default location that File Explorer opens to.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 1,
|
||||||
"Values": [
|
"Values": [
|
||||||
{
|
{
|
||||||
"Label": "Home",
|
"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",
|
"GroupId": "ShowTabsInAltTab",
|
||||||
"Label": "Show tabs from apps when snapping or pressing Alt+Tab",
|
"Label": "Show tabs from apps when snapping or pressing Alt+Tab",
|
||||||
@@ -1220,6 +1254,7 @@
|
|||||||
"Label": "file extensions for known file types",
|
"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.",
|
"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",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 2,
|
||||||
"Action": "Show",
|
"Action": "Show",
|
||||||
"RegistryKey": "Show_Extensions_For_Known_File_Types.reg",
|
"RegistryKey": "Show_Extensions_For_Known_File_Types.reg",
|
||||||
"ApplyText": "Enabling file extensions for known file types...",
|
"ApplyText": "Enabling file extensions for known file types...",
|
||||||
@@ -1233,6 +1268,7 @@
|
|||||||
"Label": "hidden files, folders and drives",
|
"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.",
|
"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",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 3,
|
||||||
"Action": "Show",
|
"Action": "Show",
|
||||||
"RegistryKey": "Show_Hidden_Folders.reg",
|
"RegistryKey": "Show_Hidden_Folders.reg",
|
||||||
"ApplyText": "Unhiding hidden files, folders and drives...",
|
"ApplyText": "Unhiding hidden files, folders and drives...",
|
||||||
@@ -1246,6 +1282,7 @@
|
|||||||
"Label": "'Home' from navigation pane",
|
"Label": "'Home' from navigation pane",
|
||||||
"ToolTip": "Hides the 'Home' section from the File Explorer navigation pane.",
|
"ToolTip": "Hides the 'Home' section from the File Explorer navigation pane.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 4,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_Home_from_Explorer.reg",
|
"RegistryKey": "Hide_Home_from_Explorer.reg",
|
||||||
"ApplyText": "Hiding the home section from the File Explorer navigation pane...",
|
"ApplyText": "Hiding the home section from the File Explorer navigation pane...",
|
||||||
@@ -1259,6 +1296,7 @@
|
|||||||
"Label": "'Gallery' from navigation pane",
|
"Label": "'Gallery' from navigation pane",
|
||||||
"ToolTip": "Hides the 'Gallery' section from the File Explorer navigation pane.",
|
"ToolTip": "Hides the 'Gallery' section from the File Explorer navigation pane.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 5,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_Gallery_from_Explorer.reg",
|
"RegistryKey": "Hide_Gallery_from_Explorer.reg",
|
||||||
"ApplyText": "Hiding the gallery section from the File Explorer navigation pane...",
|
"ApplyText": "Hiding the gallery section from the File Explorer navigation pane...",
|
||||||
@@ -1272,6 +1310,7 @@
|
|||||||
"Label": "duplicate removable drive entries",
|
"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'.",
|
"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",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 6,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_duplicate_removable_drives_from_navigation_pane_of_File_Explorer.reg",
|
"RegistryKey": "Hide_duplicate_removable_drives_from_navigation_pane_of_File_Explorer.reg",
|
||||||
"ApplyText": "Hiding duplicate removable drive entries from the File Explorer navigation pane...",
|
"ApplyText": "Hiding duplicate removable drive entries from the File Explorer navigation pane...",
|
||||||
@@ -1285,6 +1324,7 @@
|
|||||||
"Label": "common folders back to 'This PC' page",
|
"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.",
|
"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",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 7,
|
||||||
"Action": "Add",
|
"Action": "Add",
|
||||||
"RegistryKey": "Add_All_Folders_Under_This_PC.reg",
|
"RegistryKey": "Add_All_Folders_Under_This_PC.reg",
|
||||||
"ApplyText": "Adding all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...",
|
"ApplyText": "Adding all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer...",
|
||||||
@@ -1376,6 +1416,7 @@
|
|||||||
"Label": "'Include in library' option in the context menu",
|
"Label": "'Include in library' option in the context menu",
|
||||||
"ToolTip": "Hides the 'Include in library' option from the File Explorer context menu.",
|
"ToolTip": "Hides the 'Include in library' option from the File Explorer context menu.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 8,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Disable_Include_in_library_from_context_menu.reg",
|
"RegistryKey": "Disable_Include_in_library_from_context_menu.reg",
|
||||||
"ApplyText": "Hiding 'Include in library' in the context menu...",
|
"ApplyText": "Hiding 'Include in library' in the context menu...",
|
||||||
@@ -1389,6 +1430,7 @@
|
|||||||
"Label": "'Give access to' option in the context menu",
|
"Label": "'Give access to' option in the context menu",
|
||||||
"ToolTip": "Hides the 'Give access to' option from the File Explorer context menu.",
|
"ToolTip": "Hides the 'Give access to' option from the File Explorer context menu.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 9,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Disable_Give_access_to_context_menu.reg",
|
"RegistryKey": "Disable_Give_access_to_context_menu.reg",
|
||||||
"ApplyText": "Hiding 'Give access to' in the context menu...",
|
"ApplyText": "Hiding 'Give access to' in the context menu...",
|
||||||
@@ -1402,6 +1444,7 @@
|
|||||||
"Label": "'Share' option in the context menu",
|
"Label": "'Share' option in the context menu",
|
||||||
"ToolTip": "Hides the 'Share' option from the File Explorer context menu.",
|
"ToolTip": "Hides the 'Share' option from the File Explorer context menu.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 10,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Disable_Share_from_context_menu.reg",
|
"RegistryKey": "Disable_Share_from_context_menu.reg",
|
||||||
"ApplyText": "Hiding 'Share' in the context menu...",
|
"ApplyText": "Hiding 'Share' in the context menu...",
|
||||||
@@ -1415,6 +1458,7 @@
|
|||||||
"Label": "'OneDrive' folder from navigation pane",
|
"Label": "'OneDrive' folder from navigation pane",
|
||||||
"ToolTip": "Hides the 'OneDrive' folder from the File Explorer navigation pane.",
|
"ToolTip": "Hides the 'OneDrive' folder from the File Explorer navigation pane.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 11,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_Onedrive_Folder.reg",
|
"RegistryKey": "Hide_Onedrive_Folder.reg",
|
||||||
"ApplyText": "Hiding the OneDrive folder from the File Explorer navigation pane...",
|
"ApplyText": "Hiding the OneDrive folder from the File Explorer navigation pane...",
|
||||||
@@ -1428,6 +1472,7 @@
|
|||||||
"Label": "'3D objects' folder under 'This PC'",
|
"Label": "'3D objects' folder under 'This PC'",
|
||||||
"ToolTip": "Hides the '3D objects' folder from the File Explorer navigation pane.",
|
"ToolTip": "Hides the '3D objects' folder from the File Explorer navigation pane.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 12,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_3D_Objects_Folder.reg",
|
"RegistryKey": "Hide_3D_Objects_Folder.reg",
|
||||||
"ApplyText": "Hiding the 3D objects folder from the File Explorer navigation pane...",
|
"ApplyText": "Hiding the 3D objects folder from the File Explorer navigation pane...",
|
||||||
@@ -1441,6 +1486,7 @@
|
|||||||
"Label": "'Music' folder under 'This PC'",
|
"Label": "'Music' folder under 'This PC'",
|
||||||
"ToolTip": "Hides the 'Music' folder from the File Explorer navigation pane.",
|
"ToolTip": "Hides the 'Music' folder from the File Explorer navigation pane.",
|
||||||
"Category": "File Explorer",
|
"Category": "File Explorer",
|
||||||
|
"Priority": 13,
|
||||||
"Action": "Hide",
|
"Action": "Hide",
|
||||||
"RegistryKey": "Hide_Music_Folder.reg",
|
"RegistryKey": "Hide_Music_Folder.reg",
|
||||||
"ApplyText": "Hiding the music folder from the File Explorer navigation pane...",
|
"ApplyText": "Hiding the music folder from the File Explorer navigation pane...",
|
||||||
@@ -1489,6 +1535,58 @@
|
|||||||
"RequiresReboot": true,
|
"RequiresReboot": true,
|
||||||
"MinVersion": 22000,
|
"MinVersion": 22000,
|
||||||
"MaxVersion": null
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -160,6 +160,7 @@ Below is an overview of the key features and functionality offered by Win11Deblo
|
|||||||
- Add all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer.
|
- Add all common folders (Desktop, Downloads, etc.) back to 'This PC' in File Explorer.
|
||||||
- Hide the 3D objects, music or OneDrive folder from the File Explorer navigation pane.
|
- Hide the 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.
|
- 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
|
#### Multi-tasking
|
||||||
|
|
||||||
|
|||||||
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"
|
WindowStyle="None"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Topmost="True"
|
Topmost="False"
|
||||||
ShowInTaskbar="False">
|
ShowInTaskbar="False">
|
||||||
|
|
||||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
WindowStyle="None"
|
WindowStyle="None"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Topmost="True"
|
Topmost="False"
|
||||||
ShowInTaskbar="False">
|
ShowInTaskbar="False">
|
||||||
|
|
||||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||||
|
|||||||
@@ -7,23 +7,33 @@
|
|||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<Border Name="BubbleBorder"
|
<Grid Grid.Row="0"
|
||||||
Grid.Row="0"
|
Margin="10,10,10,8">
|
||||||
Background="{DynamicResource CardBgColor}"
|
<Border Name="BubbleBorder"
|
||||||
BorderBrush="{DynamicResource ButtonBorderColor}"
|
Background="{DynamicResource CardBgColor}"
|
||||||
BorderThickness="1"
|
BorderBrush="{DynamicResource ButtonBorderColor}"
|
||||||
CornerRadius="8"
|
BorderThickness="1"
|
||||||
Padding="10,7,10,7">
|
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"
|
<TextBlock Name="BubbleText"
|
||||||
Text="View the selected changes here"
|
Text="View the selected changes here"
|
||||||
TextWrapping="Wrap"
|
TextWrapping="Wrap"
|
||||||
MaxWidth="260"
|
MaxWidth="260"
|
||||||
Foreground="{DynamicResource FgColor}"/>
|
Foreground="{DynamicResource FgColor}"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Row="1"
|
<Grid Grid.Row="1"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Margin="0,-1,0,0"
|
Margin="0,-9,0,0"
|
||||||
|
Panel.ZIndex="1"
|
||||||
Width="12"
|
Width="12"
|
||||||
Height="8">
|
Height="8">
|
||||||
<Polygon Name="BubblePointer"
|
<Polygon Name="BubblePointer"
|
||||||
|
|||||||
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,15 +1,22 @@
|
|||||||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||||||
Title="Win11Debloat"
|
Title="Win11Debloat"
|
||||||
MinWidth="1130" MinHeight="600"
|
MinWidth="1130" MinHeight="600"
|
||||||
MaxWidth="1400"
|
|
||||||
ResizeMode="CanResize"
|
ResizeMode="CanResize"
|
||||||
SnapsToDevicePixels="True"
|
SnapsToDevicePixels="True"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
WindowStyle="None"
|
WindowStyle="None"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="False"
|
||||||
Background="Transparent"
|
Background="{DynamicResource BgColor}"
|
||||||
Foreground="{DynamicResource FgColor}">
|
Foreground="{DynamicResource FgColor}">
|
||||||
|
<shell:WindowChrome.WindowChrome>
|
||||||
|
<shell:WindowChrome ResizeBorderThickness="5"
|
||||||
|
CaptionHeight="32"
|
||||||
|
CornerRadius="8"
|
||||||
|
GlassFrameThickness="0"
|
||||||
|
UseAeroCaptionButtons="False"/>
|
||||||
|
</shell:WindowChrome.WindowChrome>
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<!-- Sort column header hover style -->
|
<!-- Sort column header hover style -->
|
||||||
<Style x:Key="SortHeaderBtnStyle" TargetType="StackPanel">
|
<Style x:Key="SortHeaderBtnStyle" TargetType="StackPanel">
|
||||||
@@ -601,18 +608,11 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
<Border x:Name="MainBorder" BorderBrush="{DynamicResource BorderColor}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="8"
|
CornerRadius="8"
|
||||||
Background="{DynamicResource BgColor}"
|
Background="{DynamicResource BgColor}"
|
||||||
Margin="25">
|
Margin="0">
|
||||||
<Border.Effect>
|
|
||||||
<DropShadowEffect Color="Black"
|
|
||||||
Opacity="0.15"
|
|
||||||
BlurRadius="20"
|
|
||||||
ShadowDepth="0"
|
|
||||||
Direction="0"/>
|
|
||||||
</Border.Effect>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="32"/>
|
<RowDefinition Height="32"/>
|
||||||
@@ -621,18 +621,18 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Resize Borders -->
|
<!-- 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="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"/>
|
<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"/>
|
<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"/>
|
<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"/>
|
<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"/>
|
<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"/>
|
<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"/>
|
<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 -->
|
<!-- Custom Title Bar -->
|
||||||
<Grid Grid.Row="0" x:Name="TitleBar">
|
<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>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
@@ -645,10 +645,33 @@
|
|||||||
Margin="12,0,0,0"
|
Margin="12,0,0,0"
|
||||||
FontSize="12"/>
|
FontSize="12"/>
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
<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="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" Content="" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
|
<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>
|
<Button.ContextMenu>
|
||||||
<ContextMenu x:Name="MainMenu">
|
<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 x:Name="MenuDocumentation" Header="Documentation" AutomationProperties.Name="Documentation">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="16" Foreground="{DynamicResource FgColor}"/>
|
||||||
@@ -672,7 +695,7 @@
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Button.ContextMenu>
|
</Button.ContextMenu>
|
||||||
</Button>
|
</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>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
WindowStyle="None"
|
WindowStyle="None"
|
||||||
AllowsTransparency="True"
|
AllowsTransparency="True"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Topmost="True"
|
Topmost="False"
|
||||||
ShowInTaskbar="False">
|
ShowInTaskbar="False">
|
||||||
|
|
||||||
<Border BorderBrush="{DynamicResource BorderColor}"
|
<Border BorderBrush="{DynamicResource BorderColor}"
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ function RemoveApps {
|
|||||||
|
|
||||||
$appIndex = 0
|
$appIndex = 0
|
||||||
$appCount = @($appsList).Count
|
$appCount = @($appsList).Count
|
||||||
|
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
||||||
|
$edgeUninstallSucceeded = $false
|
||||||
|
$edgeScheduledTaskAdded = $false
|
||||||
|
|
||||||
Foreach ($app in $appsList) {
|
Foreach ($app in $appsList) {
|
||||||
if ($script:CancelRequested) {
|
if ($script:CancelRequested) {
|
||||||
@@ -25,20 +28,27 @@ function RemoveApps {
|
|||||||
Write-Host "Attempting to remove $app..."
|
Write-Host "Attempting to remove $app..."
|
||||||
|
|
||||||
# Use WinGet only to remove OneDrive and Edge
|
# 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) {
|
if ($script:WingetInstalled -eq $false) {
|
||||||
Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
|
Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
|
||||||
continue
|
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
|
# Uninstall app via WinGet, or create a scheduled task to uninstall it later
|
||||||
if ($script:Params.ContainsKey("User")) {
|
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")) {
|
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 {
|
else {
|
||||||
# Uninstall app via WinGet
|
# Uninstall app via WinGet
|
||||||
@@ -47,21 +57,35 @@ function RemoveApps {
|
|||||||
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
||||||
} -ArgumentList $app
|
} -ArgumentList $app
|
||||||
|
|
||||||
If (($app -eq "Microsoft.Edge") -and (Select-String -InputObject $wingetOutput -Pattern "Uninstall failed with exit code")) {
|
$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
|
||||||
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red
|
if ($isEdgeId) {
|
||||||
|
if (-not $wingetFailed) {
|
||||||
|
$edgeUninstallSucceeded = $true
|
||||||
|
}
|
||||||
|
|
||||||
if ($script:GuiWindow) {
|
# Prompt immediately after the final selected Edge ID attempt (if all attempts failed)
|
||||||
$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'
|
$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 ""
|
Write-Host ""
|
||||||
ForceRemoveEdge
|
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'
|
PrintHeader 'Default Mode'
|
||||||
|
|
||||||
# Add default settings based on user input
|
|
||||||
try {
|
try {
|
||||||
# Select app removal options based on user input
|
# Select app removal options based on user input
|
||||||
switch ($RemoveAppsInput) {
|
switch ($RemoveAppsInput) {
|
||||||
@@ -35,7 +34,6 @@ function ShowCLIDefaultModeOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load settings from DefaultSettings.json and add to params
|
|
||||||
LoadSettings -filePath $script:DefaultSettingsFilePath -expectedVersion "1.0"
|
LoadSettings -filePath $script:DefaultSettingsFilePath -expectedVersion "1.0"
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -45,8 +43,8 @@ function ShowCLIDefaultModeOptions {
|
|||||||
|
|
||||||
SaveSettings
|
SaveSettings
|
||||||
|
|
||||||
# Skip change summary if Silent parameter was passed
|
|
||||||
if ($Silent) {
|
if ($Silent) {
|
||||||
|
# Skip change summary and confirmation prompt
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ function ShowCLILastUsedSettings {
|
|||||||
PrintHeader 'Custom Mode'
|
PrintHeader 'Custom Mode'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Load settings from LastUsedSettings.json and add to params
|
|
||||||
LoadSettings -filePath $script:SavedSettingsFilePath -expectedVersion "1.0"
|
LoadSettings -filePath $script:SavedSettingsFilePath -expectedVersion "1.0"
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -11,6 +10,11 @@ function ShowCLILastUsedSettings {
|
|||||||
AwaitKeyToExit
|
AwaitKeyToExit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($Silent) {
|
||||||
|
# Skip change summary and confirmation prompt
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
PrintPendingChanges
|
PrintPendingChanges
|
||||||
PrintHeader 'Custom Mode'
|
PrintHeader 'Custom Mode'
|
||||||
}
|
}
|
||||||
@@ -7,17 +7,25 @@ function ImportRegistryFile {
|
|||||||
|
|
||||||
Write-Host $message
|
Write-Host $message
|
||||||
|
|
||||||
# Validate that the regfile exists in both locations
|
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
|
||||||
if (-not (Test-Path "$script:RegfilesPath\$path") -or -not (Test-Path "$script:RegfilesPath\Sysprep\$path")) {
|
$regFilePath = if ($usesOfflineHive) {
|
||||||
Write-Host "Error: Unable to find registry file: $path" -ForegroundColor Red
|
"$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 ""
|
Write-Host ""
|
||||||
return
|
throw $errorMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reset exit code before running reg.exe for reliable success detection
|
# Reset exit code before running reg.exe for reliable success detection
|
||||||
$global:LASTEXITCODE = 0
|
$global:LASTEXITCODE = 0
|
||||||
|
|
||||||
if ($script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")) {
|
if ($usesOfflineHive) {
|
||||||
# Sysprep targets Default user, User targets the specified user
|
# Sysprep targets Default user, User targets the specified user
|
||||||
$hiveDatPath = if ($script:Params.ContainsKey("Sysprep")) {
|
$hiveDatPath = if ($script:Params.ContainsKey("Sysprep")) {
|
||||||
GetUserDirectory -userName "Default" -fileName "NTUSER.DAT"
|
GetUserDirectory -userName "Default" -fileName "NTUSER.DAT"
|
||||||
@@ -26,26 +34,62 @@ function ImportRegistryFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
$regResult = Invoke-NonBlocking -ScriptBlock {
|
||||||
param($datPath, $regFilePath)
|
param($hivePath, $targetRegFilePath)
|
||||||
$global:LASTEXITCODE = 0
|
$result = @{
|
||||||
reg load "HKU\Default" $datPath | Out-Null
|
Output = @()
|
||||||
$output = reg import $regFilePath 2>&1
|
ExitCode = 0
|
||||||
$code = $LASTEXITCODE
|
Error = $null
|
||||||
reg unload "HKU\Default" | Out-Null
|
}
|
||||||
return @{ Output = $output; ExitCode = $code }
|
|
||||||
} -ArgumentList @($hiveDatPath, "$script:RegfilesPath\Sysprep\$path")
|
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 {
|
else {
|
||||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
$regResult = Invoke-NonBlocking -ScriptBlock {
|
||||||
param($regFilePath)
|
param($targetRegFilePath)
|
||||||
$global:LASTEXITCODE = 0
|
$global:LASTEXITCODE = 0
|
||||||
$output = reg import $regFilePath 2>&1
|
$output = reg import $targetRegFilePath 2>&1
|
||||||
return @{ Output = $output; ExitCode = $LASTEXITCODE }
|
return @{ Output = @($output); ExitCode = $LASTEXITCODE; Error = $null }
|
||||||
} -ArgumentList "$script:RegfilesPath\$path"
|
} -ArgumentList $regFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
$regOutput = $regResult.Output
|
$regOutput = @($regResult.Output)
|
||||||
$hasSuccess = $regResult.ExitCode -eq 0
|
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
|
||||||
|
|
||||||
if ($regOutput) {
|
if ($regOutput) {
|
||||||
foreach ($line in $regOutput) {
|
foreach ($line in $regOutput) {
|
||||||
@@ -62,7 +106,11 @@ function ImportRegistryFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasSuccess) {
|
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 ""
|
Write-Host ""
|
||||||
|
|||||||
@@ -16,24 +16,36 @@ function LoadAppsDetailsFromJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($appData in $jsonContent.Apps) {
|
foreach ($appData in $jsonContent.Apps) {
|
||||||
$appId = $appData.AppId.Trim()
|
# Handle AppId as array (could be single or multiple IDs)
|
||||||
if ($appId.length -eq 0) { continue }
|
$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 ($OnlyInstalled) {
|
||||||
if (-not ($InstalledList -like ("*$appId*")) -and -not (Get-AppxPackage -Name $appId)) {
|
$isInstalled = $false
|
||||||
continue
|
foreach ($appId in $appIdArray) {
|
||||||
}
|
if (($InstalledList -like ("*$appId*")) -or (Get-AppxPackage -Name $appId)) {
|
||||||
if (($appId -eq "Microsoft.Edge") -and -not ($InstalledList -like "* Microsoft.Edge *")) {
|
$isInstalled = $true
|
||||||
continue
|
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 }
|
# Use first AppId for fallback names, join all for display
|
||||||
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appId)" } else { $appId }
|
$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 }
|
$isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false }
|
||||||
|
|
||||||
$apps += [PSCustomObject]@{
|
$apps += [PSCustomObject]@{
|
||||||
AppId = $appId
|
AppId = $appIdArray
|
||||||
|
AppIdDisplay = $appIdDisplay
|
||||||
FriendlyName = $friendlyName
|
FriendlyName = $friendlyName
|
||||||
DisplayName = $displayName
|
DisplayName = $displayName
|
||||||
IsChecked = $isChecked
|
IsChecked = $isChecked
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ function LoadAppsFromFile {
|
|||||||
# JSON file format
|
# JSON file format
|
||||||
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
|
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
|
||||||
Foreach ($appData in $jsonContent.Apps) {
|
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
|
$selectedByDefault = $appData.SelectedByDefault
|
||||||
if ($selectedByDefault -and $appId.length -gt 0) {
|
if ($selectedByDefault -and $appIdArray.Count -gt 0) {
|
||||||
$appsList += $appId
|
$appsList += $appIdArray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ function SaveSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (-not (SaveToFile -Config $settings -FilePath $script:SavedSettingsFilePath)) {
|
||||||
$settings | ConvertTo-Json -Depth 10 | Set-Content $script:SavedSettingsFilePath
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
Write-Host "Error: Failed to save settings to LastUsedSettings.json file" -ForegroundColor Red
|
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
|
$appsList
|
||||||
)
|
)
|
||||||
|
|
||||||
$supportedAppsList = (LoadAppsDetailsFromJson | ForEach-Object { $_.AppId })
|
$supportedAppsList = @(LoadAppsDetailsFromJson | ForEach-Object { @($_.AppId) }) | ForEach-Object { $_.Trim() } | Where-Object { $_.Length -gt 0 }
|
||||||
$validatedAppsList = @()
|
$validatedAppsList = @()
|
||||||
|
|
||||||
# Validate provided appsList against supportedAppsList
|
# Validate provided appsList against supportedAppsList
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function Show-AboutDialog {
|
|||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply theme resources
|
# Apply theme resources
|
||||||
SetWindowThemeResources -window $aboutWindow -usesDarkMode $usesDarkMode
|
SetWindowThemeResources -window $aboutWindow -usesDarkMode $usesDarkMode
|
||||||
|
|
||||||
@@ -83,13 +83,16 @@ function Show-AboutDialog {
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Show dialog
|
# Show dialog
|
||||||
$aboutWindow.ShowDialog() | Out-Null
|
try {
|
||||||
|
$aboutWindow.ShowDialog() | Out-Null
|
||||||
# Hide overlay after dialog closes
|
}
|
||||||
if ($overlay) {
|
finally {
|
||||||
try {
|
# Hide overlay after dialog closes
|
||||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
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 = New-Object System.Windows.Controls.CheckBox
|
||||||
$checkbox.Content = $_.DisplayName
|
$checkbox.Content = $_.DisplayName
|
||||||
$checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $_.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.IsChecked = $_.IsChecked
|
||||||
$checkbox.ToolTip = $_.Description
|
$checkbox.ToolTip = $_.Description
|
||||||
$checkbox.Style = $window.Resources["AppsPanelCheckBoxStyle"]
|
$checkbox.Style = $window.Resources["AppsPanelCheckBoxStyle"]
|
||||||
@@ -118,9 +119,10 @@ function Show-AppSelectionWindow {
|
|||||||
$selectedApps = @()
|
$selectedApps = @()
|
||||||
foreach ($child in $appsPanel.Children) {
|
foreach ($child in $appsPanel.Children) {
|
||||||
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
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
|
# Close form without saving if no apps were selected
|
||||||
if ($selectedApps.Count -eq 0) {
|
if ($selectedApps.Count -eq 0) {
|
||||||
|
|||||||
@@ -242,13 +242,16 @@ function Show-ApplyModal {
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Show dialog
|
# Show dialog
|
||||||
$applyWindow.ShowDialog() | Out-Null
|
try {
|
||||||
|
$applyWindow.ShowDialog() | Out-Null
|
||||||
# Hide overlay after dialog closes
|
}
|
||||||
if ($overlay) {
|
finally {
|
||||||
try {
|
# Hide overlay after dialog closes
|
||||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
if ($overlay) {
|
||||||
|
try {
|
||||||
|
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@ function Show-MainWindow {
|
|||||||
SetWindowThemeResources -window $window -usesDarkMode $usesDarkMode
|
SetWindowThemeResources -window $window -usesDarkMode $usesDarkMode
|
||||||
|
|
||||||
# Get named elements
|
# Get named elements
|
||||||
$titleBar = $window.FindName('TitleBar')
|
$mainBorder = $window.FindName('MainBorder')
|
||||||
|
$titleBarBackground = $window.FindName('TitleBarBackground')
|
||||||
$kofiBtn = $window.FindName('KofiBtn')
|
$kofiBtn = $window.FindName('KofiBtn')
|
||||||
$menuBtn = $window.FindName('MenuBtn')
|
$menuBtn = $window.FindName('MenuBtn')
|
||||||
$closeBtn = $window.FindName('CloseBtn')
|
$closeBtn = $window.FindName('CloseBtn')
|
||||||
@@ -27,11 +28,89 @@ function Show-MainWindow {
|
|||||||
$menuReportBug = $window.FindName('MenuReportBug')
|
$menuReportBug = $window.FindName('MenuReportBug')
|
||||||
$menuLogs = $window.FindName('MenuLogs')
|
$menuLogs = $window.FindName('MenuLogs')
|
||||||
$menuAbout = $window.FindName('MenuAbout')
|
$menuAbout = $window.FindName('MenuAbout')
|
||||||
|
$importConfigBtn = $window.FindName('ImportConfigBtn')
|
||||||
|
$exportConfigBtn = $window.FindName('ExportConfigBtn')
|
||||||
|
|
||||||
# Title bar event handlers
|
$windowStateNormal = [System.Windows.WindowState]::Normal
|
||||||
$titleBar.Add_MouseLeftButtonDown({
|
$windowStateMaximized = [System.Windows.WindowState]::Maximized
|
||||||
if ($_.OriginalSource -is [System.Windows.Controls.Grid] -or $_.OriginalSource -is [System.Windows.Controls.Border] -or $_.OriginalSource -is [System.Windows.Controls.TextBlock]) {
|
$normalWindowShadow = $mainBorder.Effect
|
||||||
$window.DragMove()
|
$windowResizeBorder = [System.Windows.SystemParameters]::WindowResizeBorderThickness
|
||||||
|
$maximizedChromeInset = [System.Windows.Thickness]::new($windowResizeBorder.Left, $windowResizeBorder.Top, $windowResizeBorder.Right, 0)
|
||||||
|
$initialNormalMaxWidth = 1400.0
|
||||||
|
|
||||||
|
$convertScreenPixelsToDip = {
|
||||||
|
param(
|
||||||
|
[double]$width,
|
||||||
|
[double]$height
|
||||||
|
)
|
||||||
|
|
||||||
|
$source = [System.Windows.PresentationSource]::FromVisual($window)
|
||||||
|
if ($null -eq $source -or $null -eq $source.CompositionTarget) {
|
||||||
|
return [System.Windows.Size]::new($width, $height)
|
||||||
|
}
|
||||||
|
|
||||||
|
$topLeft = $source.CompositionTarget.TransformFromDevice.Transform([System.Windows.Point]::new(0, 0))
|
||||||
|
$bottomRight = $source.CompositionTarget.TransformFromDevice.Transform([System.Windows.Point]::new($width, $height))
|
||||||
|
return [System.Windows.Size]::new($bottomRight.X - $topLeft.X, $bottomRight.Y - $topLeft.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
$getWindowScreen = {
|
||||||
|
$hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle
|
||||||
|
if ($hwnd -eq [IntPtr]::Zero) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return [System.Windows.Forms.Screen]::FromHandle($hwnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateWindowChrome = {
|
||||||
|
if ($window.WindowState -eq $windowStateMaximized) {
|
||||||
|
$mainBorder.Margin = $maximizedChromeInset
|
||||||
|
$mainBorder.BorderThickness = [System.Windows.Thickness]::new(1)
|
||||||
|
$mainBorder.CornerRadius = [System.Windows.CornerRadius]::new(0)
|
||||||
|
$mainBorder.Effect = $null
|
||||||
|
$titleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(0)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$mainBorder.Margin = [System.Windows.Thickness]::new(0)
|
||||||
|
$mainBorder.BorderThickness = [System.Windows.Thickness]::new(1)
|
||||||
|
$mainBorder.CornerRadius = [System.Windows.CornerRadius]::new(8)
|
||||||
|
$mainBorder.Effect = $normalWindowShadow
|
||||||
|
$titleBarBackground.CornerRadius = [System.Windows.CornerRadius]::new(8, 8, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateMaximizedBounds = {
|
||||||
|
& $updateWindowChrome
|
||||||
|
}
|
||||||
|
|
||||||
|
$applyInitialWindowSize = {
|
||||||
|
if ($window.WindowState -ne $windowStateNormal) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$screen = & $getWindowScreen
|
||||||
|
if ($null -eq $screen) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$workingAreaDip = & $convertScreenPixelsToDip $screen.WorkingArea.Width $screen.WorkingArea.Height
|
||||||
|
$window.Width = [Math]::Min($initialNormalMaxWidth, $workingAreaDip.Width)
|
||||||
|
$window.Left = $screen.WorkingArea.Left + (($workingAreaDip.Width - $window.Width) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
$window.Add_SourceInitialized({
|
||||||
|
& $applyInitialWindowSize
|
||||||
|
& $updateMaximizedBounds
|
||||||
|
})
|
||||||
|
|
||||||
|
$window.Add_StateChanged({
|
||||||
|
& $updateMaximizedBounds
|
||||||
|
})
|
||||||
|
|
||||||
|
$window.Add_LocationChanged({
|
||||||
|
if ($window.WindowState -eq $windowStateMaximized) {
|
||||||
|
& $updateMaximizedBounds
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,6 +146,22 @@ function Show-MainWindow {
|
|||||||
Show-AboutDialog -Owner $window
|
Show-AboutDialog -Owner $window
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# --- Import/Export Configuration ---
|
||||||
|
$exportConfigBtn.Add_Click({
|
||||||
|
Export-Configuration -Owner $window -UsesDarkMode $usesDarkMode -AppsPanel $appsPanel -UiControlMappings $script:UiControlMappings -UserSelectionCombo $userSelectionCombo -OtherUsernameTextBox $otherUsernameTextBox
|
||||||
|
})
|
||||||
|
|
||||||
|
$importConfigBtn.Add_Click({
|
||||||
|
Import-Configuration -Owner $window -UsesDarkMode $usesDarkMode -AppsPanel $appsPanel -UiControlMappings $script:UiControlMappings -UserSelectionCombo $userSelectionCombo -OtherUsernameTextBox $otherUsernameTextBox -OnAppsImported { UpdateAppSelectionStatus; UpdatePresetStates } -OnImportCompleted {
|
||||||
|
$tabControl.SelectedIndex = 3
|
||||||
|
UpdateNavigationButtons
|
||||||
|
|
||||||
|
$window.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Loaded, [action]{
|
||||||
|
Show-Bubble -TargetControl $reviewChangesBtn -Message 'View the selected changes here'
|
||||||
|
}) | Out-Null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$closeBtn.Add_Click({
|
$closeBtn.Add_Click({
|
||||||
$window.Close()
|
$window.Close()
|
||||||
})
|
})
|
||||||
@@ -76,135 +171,6 @@ function Show-MainWindow {
|
|||||||
$script:CancelRequested = $true
|
$script:CancelRequested = $true
|
||||||
})
|
})
|
||||||
|
|
||||||
# Implement window resize functionality
|
|
||||||
$resizeLeft = $window.FindName('ResizeLeft')
|
|
||||||
$resizeRight = $window.FindName('ResizeRight')
|
|
||||||
$resizeTop = $window.FindName('ResizeTop')
|
|
||||||
$resizeBottom = $window.FindName('ResizeBottom')
|
|
||||||
$resizeTopLeft = $window.FindName('ResizeTopLeft')
|
|
||||||
$resizeTopRight = $window.FindName('ResizeTopRight')
|
|
||||||
$resizeBottomLeft = $window.FindName('ResizeBottomLeft')
|
|
||||||
$resizeBottomRight = $window.FindName('ResizeBottomRight')
|
|
||||||
|
|
||||||
$script:resizing = $false
|
|
||||||
$script:resizeEdges = $null
|
|
||||||
$script:resizeStart = $null
|
|
||||||
$script:windowStart = $null
|
|
||||||
$script:resizeElement = $null
|
|
||||||
|
|
||||||
$resizeHandler = {
|
|
||||||
param($sender, $e)
|
|
||||||
|
|
||||||
$script:resizing = $true
|
|
||||||
$script:resizeElement = $sender
|
|
||||||
$script:resizeStart = [System.Windows.Forms.Cursor]::Position
|
|
||||||
$script:windowStart = @{
|
|
||||||
Left = $window.Left
|
|
||||||
Top = $window.Top
|
|
||||||
Width = $window.ActualWidth
|
|
||||||
Height = $window.ActualHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse direction tag into edge flags for cleaner resize logic
|
|
||||||
$direction = $sender.Tag
|
|
||||||
$script:resizeEdges = @{
|
|
||||||
Left = $direction -match 'Left'
|
|
||||||
Right = $direction -match 'Right'
|
|
||||||
Top = $direction -match 'Top'
|
|
||||||
Bottom = $direction -match 'Bottom'
|
|
||||||
}
|
|
||||||
|
|
||||||
$sender.CaptureMouse()
|
|
||||||
$e.Handled = $true
|
|
||||||
}
|
|
||||||
|
|
||||||
$moveHandler = {
|
|
||||||
param($sender, $e)
|
|
||||||
if (-not $script:resizing) { return }
|
|
||||||
|
|
||||||
$current = [System.Windows.Forms.Cursor]::Position
|
|
||||||
$deltaX = $current.X - $script:resizeStart.X
|
|
||||||
$deltaY = $current.Y - $script:resizeStart.Y
|
|
||||||
|
|
||||||
# Handle horizontal resize
|
|
||||||
if ($script:resizeEdges.Left) {
|
|
||||||
$newWidth = [Math]::Max($window.MinWidth, $script:windowStart.Width - $deltaX)
|
|
||||||
if ($newWidth -ne $window.Width) {
|
|
||||||
$window.Left = $script:windowStart.Left + ($script:windowStart.Width - $newWidth)
|
|
||||||
$window.Width = $newWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif ($script:resizeEdges.Right) {
|
|
||||||
$window.Width = [Math]::Max($window.MinWidth, $script:windowStart.Width + $deltaX)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Handle vertical resize
|
|
||||||
if ($script:resizeEdges.Top) {
|
|
||||||
$newHeight = [Math]::Max($window.MinHeight, $script:windowStart.Height - $deltaY)
|
|
||||||
if ($newHeight -ne $window.Height) {
|
|
||||||
$window.Top = $script:windowStart.Top + ($script:windowStart.Height - $newHeight)
|
|
||||||
$window.Height = $newHeight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif ($script:resizeEdges.Bottom) {
|
|
||||||
$window.Height = [Math]::Max($window.MinHeight, $script:windowStart.Height + $deltaY)
|
|
||||||
}
|
|
||||||
|
|
||||||
$e.Handled = $true
|
|
||||||
}
|
|
||||||
|
|
||||||
$releaseHandler = {
|
|
||||||
param($sender, $e)
|
|
||||||
if ($script:resizing -and $script:resizeElement) {
|
|
||||||
$script:resizing = $false
|
|
||||||
$script:resizeEdges = $null
|
|
||||||
$script:resizeElement.ReleaseMouseCapture()
|
|
||||||
$script:resizeElement = $null
|
|
||||||
$e.Handled = $true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Set tags and add event handlers for resize borders
|
|
||||||
$resizeLeft.Tag = 'Left'
|
|
||||||
$resizeLeft.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeLeft.Add_MouseMove($moveHandler)
|
|
||||||
$resizeLeft.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeRight.Tag = 'Right'
|
|
||||||
$resizeRight.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeRight.Add_MouseMove($moveHandler)
|
|
||||||
$resizeRight.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeTop.Tag = 'Top'
|
|
||||||
$resizeTop.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeTop.Add_MouseMove($moveHandler)
|
|
||||||
$resizeTop.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeBottom.Tag = 'Bottom'
|
|
||||||
$resizeBottom.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeBottom.Add_MouseMove($moveHandler)
|
|
||||||
$resizeBottom.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeTopLeft.Tag = 'TopLeft'
|
|
||||||
$resizeTopLeft.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeTopLeft.Add_MouseMove($moveHandler)
|
|
||||||
$resizeTopLeft.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeTopRight.Tag = 'TopRight'
|
|
||||||
$resizeTopRight.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeTopRight.Add_MouseMove($moveHandler)
|
|
||||||
$resizeTopRight.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeBottomLeft.Tag = 'BottomLeft'
|
|
||||||
$resizeBottomLeft.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeBottomLeft.Add_MouseMove($moveHandler)
|
|
||||||
$resizeBottomLeft.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
$resizeBottomRight.Tag = 'BottomRight'
|
|
||||||
$resizeBottomRight.Add_PreviewMouseLeftButtonDown($resizeHandler)
|
|
||||||
$resizeBottomRight.Add_MouseMove($moveHandler)
|
|
||||||
$resizeBottomRight.Add_MouseLeftButtonUp($releaseHandler)
|
|
||||||
|
|
||||||
# Integrated App Selection UI
|
# Integrated App Selection UI
|
||||||
$appsPanel = $window.FindName('AppSelectionPanel')
|
$appsPanel = $window.FindName('AppSelectionPanel')
|
||||||
$onlyInstalledAppsBox = $window.FindName('OnlyInstalledAppsBox')
|
$onlyInstalledAppsBox = $window.FindName('OnlyInstalledAppsBox')
|
||||||
@@ -224,6 +190,32 @@ function Show-MainWindow {
|
|||||||
$presetsArrow = $window.FindName('PresetsArrow')
|
$presetsArrow = $window.FindName('PresetsArrow')
|
||||||
$clearAppSelectionBtn = $window.FindName('ClearAppSelectionBtn')
|
$clearAppSelectionBtn = $window.FindName('ClearAppSelectionBtn')
|
||||||
|
|
||||||
|
function NormalizeCheckboxState {
|
||||||
|
param([System.Windows.Controls.CheckBox]$checkBox)
|
||||||
|
|
||||||
|
$isChecked = ($checkBox.IsChecked -eq $true)
|
||||||
|
if ($null -eq $checkBox.IsChecked) {
|
||||||
|
$checkBox.IsChecked = $false
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
return $isChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
function AnimatePresetsArrow {
|
||||||
|
param([double]$angle)
|
||||||
|
|
||||||
|
$animation = New-Object System.Windows.Media.Animation.DoubleAnimation
|
||||||
|
$animation.To = $angle
|
||||||
|
$animation.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
|
||||||
|
|
||||||
|
$ease = New-Object System.Windows.Media.Animation.CubicEase
|
||||||
|
$ease.EasingMode = 'EaseOut'
|
||||||
|
$animation.EasingFunction = $ease
|
||||||
|
|
||||||
|
$presetsArrow.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $animation)
|
||||||
|
}
|
||||||
|
|
||||||
# Load JSON-defined presets and build dynamic preset checkboxes
|
# Load JSON-defined presets and build dynamic preset checkboxes
|
||||||
$script:JsonPresetCheckboxes = @()
|
$script:JsonPresetCheckboxes = @()
|
||||||
foreach ($preset in (LoadAppPresetsFromJson)) {
|
foreach ($preset in (LoadAppPresetsFromJson)) {
|
||||||
@@ -238,10 +230,9 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
$checkbox.Add_Click({
|
$checkbox.Add_Click({
|
||||||
if ($script:UpdatingPresets) { return }
|
if ($script:UpdatingPresets) { return }
|
||||||
$check = ($this.IsChecked -eq $true)
|
$check = NormalizeCheckboxState -checkBox $this
|
||||||
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
|
||||||
$presetIds = $this.PresetAppIds
|
$presetIds = $this.PresetAppIds
|
||||||
ApplyPresetToApps -MatchFilter { param($c) $presetIds -contains $c.Tag }.GetNewClosure() -Check $check
|
ApplyPresetToApps -MatchFilter { param($c) (@($c.AppIds) | Where-Object { $presetIds -contains $_ }).Count -gt 0 }.GetNewClosure() -Check $check
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +245,11 @@ function Show-MainWindow {
|
|||||||
$script:PendingDefaultMode = $false
|
$script:PendingDefaultMode = $false
|
||||||
# Holds apps data preloaded before ShowDialog() so the first load skips the background job
|
# Holds apps data preloaded before ShowDialog() so the first load skips the background job
|
||||||
$script:PreloadedAppData = $null
|
$script:PreloadedAppData = $null
|
||||||
|
|
||||||
|
# Prevent app import until the apps list has finished initial population.
|
||||||
|
if ($importConfigBtn) {
|
||||||
|
$importConfigBtn.IsEnabled = $false
|
||||||
|
}
|
||||||
|
|
||||||
# Set script-level variable for GUI window reference
|
# Set script-level variable for GUI window reference
|
||||||
$script:GuiWindow = $window
|
$script:GuiWindow = $window
|
||||||
@@ -292,12 +288,32 @@ function Show-MainWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Rebuilds $script:AppSearchMatches by scanning appsPanel children in their current order,
|
||||||
|
# collecting any that are still highlighted. Preserves the active match across reorderings.
|
||||||
|
function RebuildAppSearchIndex {
|
||||||
|
param($activeMatch = $null)
|
||||||
|
$newMatches = @()
|
||||||
|
$newActiveIndex = -1
|
||||||
|
$i = 0
|
||||||
|
foreach ($child in $appsPanel.Children) {
|
||||||
|
if ($child -is [System.Windows.Controls.CheckBox] -and $child.Background -ne [System.Windows.Media.Brushes]::Transparent) {
|
||||||
|
$newMatches += $child
|
||||||
|
if ($null -ne $activeMatch -and [System.Object]::ReferenceEquals($child, $activeMatch)) {
|
||||||
|
$newActiveIndex = $i
|
||||||
|
}
|
||||||
|
$i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$script:AppSearchMatches = $newMatches
|
||||||
|
$script:AppSearchMatchIndex = if ($newActiveIndex -ge 0) { $newActiveIndex } elseif ($newMatches.Count -gt 0) { 0 } else { -1 }
|
||||||
|
}
|
||||||
|
|
||||||
function SortApps {
|
function SortApps {
|
||||||
$children = @($appsPanel.Children)
|
$children = @($appsPanel.Children)
|
||||||
$key = switch ($script:SortColumn) {
|
$key = switch ($script:SortColumn) {
|
||||||
'Name' { { $_.AppName } }
|
'Name' { { $_.AppName } }
|
||||||
'Description' { { $_.AppDescription } }
|
'Description' { { $_.AppDescription } }
|
||||||
'AppId' { { $_.Tag } }
|
'AppId' { { $_.AppIdDisplay } }
|
||||||
}
|
}
|
||||||
$sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending)
|
$sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending)
|
||||||
$appsPanel.Children.Clear()
|
$appsPanel.Children.Clear()
|
||||||
@@ -305,6 +321,14 @@ function Show-MainWindow {
|
|||||||
$appsPanel.Children.Add($checkbox) | Out-Null
|
$appsPanel.Children.Add($checkbox) | Out-Null
|
||||||
}
|
}
|
||||||
UpdateSortArrows
|
UpdateSortArrows
|
||||||
|
|
||||||
|
# Rebuild search match list in new sorted order so keyboard navigation stays correct
|
||||||
|
if ($script:AppSearchMatches.Count -gt 0) {
|
||||||
|
$activeMatch = if ($script:AppSearchMatchIndex -ge 0 -and $script:AppSearchMatchIndex -lt $script:AppSearchMatches.Count) {
|
||||||
|
$script:AppSearchMatches[$script:AppSearchMatchIndex]
|
||||||
|
} else { $null }
|
||||||
|
RebuildAppSearchIndex -activeMatch $activeMatch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetSortColumn($column) {
|
function SetSortColumn($column) {
|
||||||
@@ -351,14 +375,6 @@ function Show-MainWindow {
|
|||||||
function UpdatePresetStates {
|
function UpdatePresetStates {
|
||||||
$script:UpdatingPresets = $true
|
$script:UpdatingPresets = $true
|
||||||
try {
|
try {
|
||||||
# Build a set of currently checked app tags for fast lookup
|
|
||||||
$checkedTags = @{}
|
|
||||||
foreach ($child in $appsPanel.Children) {
|
|
||||||
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
|
||||||
$checkedTags[$child.Tag] = $true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Helper: count matching and checked apps, set checkbox state
|
# Helper: count matching and checked apps, set checkbox state
|
||||||
function SetPresetState($checkbox, [scriptblock]$MatchFilter) {
|
function SetPresetState($checkbox, [scriptblock]$MatchFilter) {
|
||||||
$total = 0; $checked = 0
|
$total = 0; $checked = 0
|
||||||
@@ -366,7 +382,7 @@ function Show-MainWindow {
|
|||||||
if ($child -is [System.Windows.Controls.CheckBox]) {
|
if ($child -is [System.Windows.Controls.CheckBox]) {
|
||||||
if (& $MatchFilter $child) {
|
if (& $MatchFilter $child) {
|
||||||
$total++
|
$total++
|
||||||
if ($checkedTags.ContainsKey($child.Tag)) { $checked++ }
|
if ($child.IsChecked) { $checked++ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,12 +404,12 @@ function Show-MainWindow {
|
|||||||
SetPresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true }
|
SetPresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true }
|
||||||
foreach ($jsonCb in $script:JsonPresetCheckboxes) {
|
foreach ($jsonCb in $script:JsonPresetCheckboxes) {
|
||||||
$localIds = $jsonCb.PresetAppIds
|
$localIds = $jsonCb.PresetAppIds
|
||||||
SetPresetState $jsonCb { param($c) $localIds -contains $c.Tag }.GetNewClosure()
|
SetPresetState $jsonCb { param($c) (@($c.AppIds) | Where-Object { $localIds -contains $_ }).Count -gt 0 }.GetNewClosure()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Last used preset: only update if it's visible (has saved apps)
|
# Last used preset: only update if it's visible (has saved apps)
|
||||||
if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) {
|
if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) {
|
||||||
SetPresetState $presetLastUsed { param($c) $script:SavedAppIds -contains $c.Tag }
|
SetPresetState $presetLastUsed { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -547,8 +563,8 @@ function Show-MainWindow {
|
|||||||
$helpBtn.Tag = (GetWikiUrlForCategory -category $categoryName)
|
$helpBtn.Tag = (GetWikiUrlForCategory -category $categoryName)
|
||||||
$helpBtn.Style = $window.Resources['CategoryHelpLinkButtonStyle']
|
$helpBtn.Style = $window.Resources['CategoryHelpLinkButtonStyle']
|
||||||
$helpBtn.Add_Click({
|
$helpBtn.Add_Click({
|
||||||
param($sender, $e)
|
param($button, $e)
|
||||||
if ($sender.Tag) { Start-Process $sender.Tag }
|
if ($button.Tag) { Start-Process $button.Tag }
|
||||||
})
|
})
|
||||||
$headerRow.Children.Add($helpBtn) | Out-Null
|
$headerRow.Children.Add($helpBtn) | Out-Null
|
||||||
|
|
||||||
@@ -718,6 +734,9 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
if ($appsToAdd.Count -eq 0) {
|
if ($appsToAdd.Count -eq 0) {
|
||||||
$window.FindName('DeploymentApplyBtn').IsEnabled = $true
|
$window.FindName('DeploymentApplyBtn').IsEnabled = $true
|
||||||
|
if ($importConfigBtn) {
|
||||||
|
$importConfigBtn.IsEnabled = $true
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,9 +751,9 @@ function Show-MainWindow {
|
|||||||
$app = $appsToAdd[$i]
|
$app = $appsToAdd[$i]
|
||||||
|
|
||||||
$checkbox = New-Object System.Windows.Controls.CheckBox
|
$checkbox = New-Object System.Windows.Controls.CheckBox
|
||||||
$automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppId) { $app.AppId } else { $null }
|
$automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppIdDisplay) { $app.AppIdDisplay } else { $null }
|
||||||
if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) }
|
if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) }
|
||||||
$checkbox.Tag = $app.AppId
|
$checkbox.Tag = $app.AppIdDisplay
|
||||||
$checkbox.IsChecked = $app.IsChecked
|
$checkbox.IsChecked = $app.IsChecked
|
||||||
$checkbox.Style = $window.Resources['AppsPanelCheckBoxStyle']
|
$checkbox.Style = $window.Resources['AppsPanelCheckBoxStyle']
|
||||||
|
|
||||||
@@ -770,9 +789,9 @@ function Show-MainWindow {
|
|||||||
[System.Windows.Controls.Grid]::SetColumn($tbDesc, 2)
|
[System.Windows.Controls.Grid]::SetColumn($tbDesc, 2)
|
||||||
|
|
||||||
$tbId = New-Object System.Windows.Controls.TextBlock
|
$tbId = New-Object System.Windows.Controls.TextBlock
|
||||||
$tbId.Text = $app.AppId
|
$tbId.Text = $app.AppIdDisplay
|
||||||
$tbId.Style = $window.Resources['AppIdTextStyle']
|
$tbId.Style = $window.Resources["AppIdTextStyle"]
|
||||||
$tbId.ToolTip = $app.AppId
|
$tbId.ToolTip = $app.AppIdDisplay
|
||||||
[System.Windows.Controls.Grid]::SetColumn($tbId, 3)
|
[System.Windows.Controls.Grid]::SetColumn($tbId, 3)
|
||||||
|
|
||||||
$row.Children.Add($dot) | Out-Null
|
$row.Children.Add($dot) | Out-Null
|
||||||
@@ -784,6 +803,8 @@ function Show-MainWindow {
|
|||||||
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName
|
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName
|
||||||
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description
|
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description
|
||||||
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault
|
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault
|
||||||
|
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($app.AppId)
|
||||||
|
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIdDisplay' -Value $app.AppIdDisplay
|
||||||
|
|
||||||
$checkbox.Add_Checked({ UpdateAppSelectionStatus })
|
$checkbox.Add_Checked({ UpdateAppSelectionStatus })
|
||||||
$checkbox.Add_Unchecked({ UpdateAppSelectionStatus })
|
$checkbox.Add_Unchecked({ UpdateAppSelectionStatus })
|
||||||
@@ -808,6 +829,9 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
# Re-enable Apply button now that the full, correctly-checked app list is ready
|
# Re-enable Apply button now that the full, correctly-checked app list is ready
|
||||||
$window.FindName('DeploymentApplyBtn').IsEnabled = $true
|
$window.FindName('DeploymentApplyBtn').IsEnabled = $true
|
||||||
|
if ($importConfigBtn) {
|
||||||
|
$importConfigBtn.IsEnabled = $true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Loads apps into the UI
|
# Loads apps into the UI
|
||||||
@@ -816,6 +840,10 @@ function Show-MainWindow {
|
|||||||
if ($script:IsLoadingApps) { return }
|
if ($script:IsLoadingApps) { return }
|
||||||
$script:IsLoadingApps = $true
|
$script:IsLoadingApps = $true
|
||||||
|
|
||||||
|
if ($importConfigBtn) {
|
||||||
|
$importConfigBtn.IsEnabled = $false
|
||||||
|
}
|
||||||
|
|
||||||
# Show loading indicator and clear existing apps
|
# Show loading indicator and clear existing apps
|
||||||
$loadingAppsIndicator.Visibility = 'Visible'
|
$loadingAppsIndicator.Visibility = 'Visible'
|
||||||
$appsPanel.Children.Clear()
|
$appsPanel.Children.Clear()
|
||||||
@@ -863,34 +891,29 @@ function Show-MainWindow {
|
|||||||
# Animate arrow when popup opens/closes, and lazily update preset states
|
# Animate arrow when popup opens/closes, and lazily update preset states
|
||||||
$presetsPopup.Add_Opened({
|
$presetsPopup.Add_Opened({
|
||||||
UpdatePresetStates
|
UpdatePresetStates
|
||||||
$animation = New-Object System.Windows.Media.Animation.DoubleAnimation
|
AnimatePresetsArrow -angle 180
|
||||||
$animation.To = 180
|
|
||||||
$animation.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
|
|
||||||
$animation.EasingFunction = New-Object System.Windows.Media.Animation.CubicEase
|
|
||||||
$animation.EasingFunction.EasingMode = 'EaseOut'
|
|
||||||
$presetsArrow.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $animation)
|
|
||||||
})
|
})
|
||||||
$presetsPopup.Add_Closed({
|
$presetsPopup.Add_Closed({
|
||||||
$animation = New-Object System.Windows.Media.Animation.DoubleAnimation
|
AnimatePresetsArrow -angle 0
|
||||||
$animation.To = 0
|
|
||||||
$animation.Duration = [System.Windows.Duration]::new([System.TimeSpan]::FromMilliseconds(200))
|
|
||||||
$animation.EasingFunction = New-Object System.Windows.Media.Animation.CubicEase
|
|
||||||
$animation.EasingFunction.EasingMode = 'EaseOut'
|
|
||||||
$presetsArrow.RenderTransform.BeginAnimation([System.Windows.Media.RotateTransform]::AngleProperty, $animation)
|
|
||||||
$presetsBtn.IsChecked = $false
|
$presetsBtn.IsChecked = $false
|
||||||
})
|
})
|
||||||
|
|
||||||
# Close popup when clicking anywhere outside the popup or the presets button.
|
# Close popup when clicking anywhere outside the popup or the presets button.
|
||||||
$window.Add_PreviewMouseDown({
|
$window.Add_PreviewMouseDown({
|
||||||
if (-not $presetsPopup.IsOpen) { return }
|
if (-not $presetsPopup.IsOpen) { return }
|
||||||
if ($presetsPopup.Child -ne $null -and $presetsPopup.Child.IsMouseOver) { return }
|
if ($null -ne $presetsPopup.Child -and $presetsPopup.Child.IsMouseOver) { return }
|
||||||
$src = $_.OriginalSource -as [System.Windows.DependencyObject]
|
$src = $_.OriginalSource -as [System.Windows.DependencyObject]
|
||||||
if ($src -ne $null) {
|
if ($null -ne $src) {
|
||||||
$inBtn = $presetsBtn.IsAncestorOf($src) -or [System.Object]::ReferenceEquals($presetsBtn, $src)
|
$inBtn = $presetsBtn.IsAncestorOf($src) -or [System.Object]::ReferenceEquals($presetsBtn, $src)
|
||||||
if (-not $inBtn) { $presetsPopup.IsOpen = $false }
|
if (-not $inBtn) { $presetsPopup.IsOpen = $false }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Close the preset menu when the main window loses focus (e.g., user switches to another app).
|
||||||
|
$window.Add_Deactivated({
|
||||||
|
if ($presetsPopup.IsOpen) { $presetsPopup.IsOpen = $false }
|
||||||
|
})
|
||||||
|
|
||||||
# Toggle popup on button click
|
# Toggle popup on button click
|
||||||
$presetsBtn.Add_Click({
|
$presetsBtn.Add_Click({
|
||||||
$presetsPopup.IsOpen = -not $presetsPopup.IsOpen
|
$presetsPopup.IsOpen = -not $presetsPopup.IsOpen
|
||||||
@@ -900,8 +923,7 @@ function Show-MainWindow {
|
|||||||
# Preset: Default selection
|
# Preset: Default selection
|
||||||
$presetDefaultApps.Add_Click({
|
$presetDefaultApps.Add_Click({
|
||||||
if ($script:UpdatingPresets) { return }
|
if ($script:UpdatingPresets) { return }
|
||||||
$check = ($this.IsChecked -eq $true)
|
$check = NormalizeCheckboxState -checkBox $this
|
||||||
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
|
||||||
ApplyPresetToApps -MatchFilter { param($c) $c.SelectedByDefault -eq $true } -Check $check
|
ApplyPresetToApps -MatchFilter { param($c) $c.SelectedByDefault -eq $true } -Check $check
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1018,7 +1040,7 @@ function Show-MainWindow {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$appSearchBox.Add_KeyDown({
|
$appSearchBox.Add_KeyDown({
|
||||||
param($sender, $e)
|
param($sourceControl, $e)
|
||||||
if ($e.Key -eq [System.Windows.Input.Key]::Enter -and $script:AppSearchMatches.Count -gt 0) {
|
if ($e.Key -eq [System.Windows.Input.Key]::Enter -and $script:AppSearchMatches.Count -gt 0) {
|
||||||
# Reset background of current active match
|
# Reset background of current active match
|
||||||
$script:AppSearchMatches[$script:AppSearchMatchIndex].Background = $window.Resources["SearchHighlightColor"]
|
$script:AppSearchMatches[$script:AppSearchMatchIndex].Background = $window.Resources["SearchHighlightColor"]
|
||||||
@@ -1141,7 +1163,7 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
# Add Ctrl+F keyboard shortcut to focus search box on current tab
|
# Add Ctrl+F keyboard shortcut to focus search box on current tab
|
||||||
$window.Add_KeyDown({
|
$window.Add_KeyDown({
|
||||||
param($sender, $e)
|
param($sourceControl, $e)
|
||||||
|
|
||||||
# Check if Ctrl+F was pressed
|
# Check if Ctrl+F was pressed
|
||||||
if ($e.Key -eq [System.Windows.Input.Key]::F -and
|
if ($e.Key -eq [System.Windows.Input.Key]::F -and
|
||||||
@@ -1250,7 +1272,7 @@ function Show-MainWindow {
|
|||||||
$appRemovalScopeCombo.SelectedIndex = 0
|
$appRemovalScopeCombo.SelectedIndex = 0
|
||||||
}
|
}
|
||||||
1 {
|
1 {
|
||||||
$userSelectionDescription.Text = "Changes will be applied to a different user profile on this system. Note: changes may not apply correctly if the target user is currently logged in."
|
$userSelectionDescription.Text = "Changes will be applied to a different user profile on this system."
|
||||||
$otherUserPanel.Visibility = 'Visible'
|
$otherUserPanel.Visibility = 'Visible'
|
||||||
$usernameValidationMessage.Text = ""
|
$usernameValidationMessage.Text = ""
|
||||||
# Hide "Current user only" option, show "Target user only" option
|
# Hide "Current user only" option, show "Target user only" option
|
||||||
@@ -1320,13 +1342,13 @@ function Show-MainWindow {
|
|||||||
$successBrush = $window.Resources['ValidationSuccessColor']
|
$successBrush = $window.Resources['ValidationSuccessColor']
|
||||||
|
|
||||||
if ($username.Length -eq 0) {
|
if ($username.Length -eq 0) {
|
||||||
$usernameValidationMessage.Text = "[X] Please enter a username"
|
$usernameValidationMessage.Text = "Please enter a username"
|
||||||
$usernameValidationMessage.Foreground = $errorBrush
|
$usernameValidationMessage.Foreground = $errorBrush
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($username -eq $env:USERNAME) {
|
if ($username -eq $env:USERNAME) {
|
||||||
$usernameValidationMessage.Text = "[X] Cannot enter your own username, use 'Current User' option instead"
|
$usernameValidationMessage.Text = "Cannot enter your own username, use 'Current User' option instead"
|
||||||
$usernameValidationMessage.Foreground = $errorBrush
|
$usernameValidationMessage.Foreground = $errorBrush
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
@@ -1334,12 +1356,18 @@ function Show-MainWindow {
|
|||||||
$userExists = CheckIfUserExists -Username $username
|
$userExists = CheckIfUserExists -Username $username
|
||||||
|
|
||||||
if ($userExists) {
|
if ($userExists) {
|
||||||
$usernameValidationMessage.Text = "[OK] User found: $username"
|
if (TestIfUserIsLoggedIn -Username $username) {
|
||||||
|
$usernameValidationMessage.Text = "User '$username' is currently logged in. Please sign out that user first."
|
||||||
|
$usernameValidationMessage.Foreground = $errorBrush
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$usernameValidationMessage.Text = "User found: $username"
|
||||||
$usernameValidationMessage.Foreground = $successBrush
|
$usernameValidationMessage.Foreground = $successBrush
|
||||||
return $true
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
$usernameValidationMessage.Text = "[X] User not found, please enter a valid username"
|
$usernameValidationMessage.Text = "User not found, please enter a valid username"
|
||||||
$usernameValidationMessage.Foreground = $errorBrush
|
$usernameValidationMessage.Foreground = $errorBrush
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1510,13 @@ function Show-MainWindow {
|
|||||||
$deploymentApplyBtn = $window.FindName('DeploymentApplyBtn')
|
$deploymentApplyBtn = $window.FindName('DeploymentApplyBtn')
|
||||||
$deploymentApplyBtn.Add_Click({
|
$deploymentApplyBtn.Add_Click({
|
||||||
if (-not (ValidateOtherUsername)) {
|
if (-not (ValidateOtherUsername)) {
|
||||||
Show-MessageBox -Message "Please enter a valid username." -Title "Invalid Username" -Button 'OK' -Icon 'Warning' | Out-Null
|
$validationMessage = if (-not [string]::IsNullOrWhiteSpace($usernameValidationMessage.Text)) {
|
||||||
|
$usernameValidationMessage.Text
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"Please enter a valid username."
|
||||||
|
}
|
||||||
|
Show-MessageBox -Message $validationMessage -Title "Invalid Username" -Button 'OK' -Icon 'Warning' | Out-Null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,9 +1526,10 @@ function Show-MainWindow {
|
|||||||
$selectedApps = @()
|
$selectedApps = @()
|
||||||
foreach ($child in $appsPanel.Children) {
|
foreach ($child in $appsPanel.Children) {
|
||||||
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
|
||||||
$selectedApps += $child.Tag
|
$selectedApps += @($child.AppIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$selectedApps = @($selectedApps | Where-Object { $_ } | Select-Object -Unique)
|
||||||
|
|
||||||
if ($selectedApps.Count -gt 0) {
|
if ($selectedApps.Count -gt 0) {
|
||||||
# Check if Microsoft Store is selected
|
# Check if Microsoft Store is selected
|
||||||
@@ -1713,9 +1748,8 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
$presetLastUsed.Add_Click({
|
$presetLastUsed.Add_Click({
|
||||||
if ($script:UpdatingPresets) { return }
|
if ($script:UpdatingPresets) { return }
|
||||||
$check = ($this.IsChecked -eq $true)
|
$check = NormalizeCheckboxState -checkBox $this
|
||||||
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
ApplyPresetToApps -MatchFilter { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 } -Check $check
|
||||||
ApplyPresetToApps -MatchFilter { param($c) $script:SavedAppIds -contains $c.Tag } -Check $check
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1749,5 +1783,12 @@ function Show-MainWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Show the window
|
# Show the window
|
||||||
return $window.ShowDialog()
|
$frame = [System.Windows.Threading.DispatcherFrame]::new()
|
||||||
|
$window.Add_Closed({
|
||||||
|
$frame.Continue = $false
|
||||||
|
})
|
||||||
|
|
||||||
|
$window.Show() | Out-Null
|
||||||
|
[System.Windows.Threading.Dispatcher]::PushFrame($frame)
|
||||||
|
return $null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,14 +152,17 @@ function Show-MessageBox {
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Show dialog and return result from Tag
|
# Show dialog and return result from Tag
|
||||||
$msgWindow.ShowDialog() | Out-Null
|
try {
|
||||||
|
$msgWindow.ShowDialog() | Out-Null
|
||||||
# Hide overlay after dialog closes (only if this dialog was the one that showed it)
|
}
|
||||||
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
finally {
|
||||||
try {
|
# Hide overlay after dialog closes (only if this dialog was the one that showed it)
|
||||||
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||||
|
try {
|
||||||
|
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $msgWindow.Tag
|
return $msgWindow.Tag
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ param (
|
|||||||
[switch]$RunDefaults,
|
[switch]$RunDefaults,
|
||||||
[switch]$RunDefaultsLite,
|
[switch]$RunDefaultsLite,
|
||||||
[switch]$RunSavedSettings,
|
[switch]$RunSavedSettings,
|
||||||
|
[string]$Config,
|
||||||
[string]$Apps,
|
[string]$Apps,
|
||||||
[string]$AppRemovalTarget,
|
[string]$AppRemovalTarget,
|
||||||
[switch]$RemoveApps,
|
[switch]$RemoveApps,
|
||||||
@@ -95,7 +96,11 @@ param (
|
|||||||
[switch]$HideMusic,
|
[switch]$HideMusic,
|
||||||
[switch]$HideIncludeInLibrary,
|
[switch]$HideIncludeInLibrary,
|
||||||
[switch]$HideGiveAccessTo,
|
[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
|
# Show error if current powershell environment does not have LanguageMode set to FullLanguage
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ param (
|
|||||||
[switch]$RunDefaults,
|
[switch]$RunDefaults,
|
||||||
[switch]$RunDefaultsLite,
|
[switch]$RunDefaultsLite,
|
||||||
[switch]$RunSavedSettings,
|
[switch]$RunSavedSettings,
|
||||||
|
[string]$Config,
|
||||||
[string]$Apps,
|
[string]$Apps,
|
||||||
[string]$AppRemovalTarget,
|
[string]$AppRemovalTarget,
|
||||||
[switch]$RemoveApps,
|
[switch]$RemoveApps,
|
||||||
@@ -97,7 +98,11 @@ param (
|
|||||||
[switch]$HideMusic,
|
[switch]$HideMusic,
|
||||||
[switch]$HideIncludeInLibrary,
|
[switch]$HideIncludeInLibrary,
|
||||||
[switch]$HideGiveAccessTo,
|
[switch]$HideGiveAccessTo,
|
||||||
[switch]$HideShare
|
[switch]$HideShare,
|
||||||
|
[switch]$ShowDriveLettersFirst,
|
||||||
|
[switch]$ShowDriveLettersLast,
|
||||||
|
[switch]$ShowNetworkDriveLettersFirst,
|
||||||
|
[switch]$HideDriveLetters
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -119,9 +124,10 @@ $script:AboutWindowSchema = "$PSScriptRoot/Schemas/AboutWindow.xaml"
|
|||||||
$script:ApplyChangesWindowSchema = "$PSScriptRoot/Schemas/ApplyChangesWindow.xaml"
|
$script:ApplyChangesWindowSchema = "$PSScriptRoot/Schemas/ApplyChangesWindow.xaml"
|
||||||
$script:SharedStylesSchema = "$PSScriptRoot/Schemas/SharedStyles.xaml"
|
$script:SharedStylesSchema = "$PSScriptRoot/Schemas/SharedStyles.xaml"
|
||||||
$script:BubbleHintSchema = "$PSScriptRoot/Schemas/BubbleHint.xaml"
|
$script:BubbleHintSchema = "$PSScriptRoot/Schemas/BubbleHint.xaml"
|
||||||
|
$script:ImportExportConfigSchema = "$PSScriptRoot/Schemas/ImportExportConfigWindow.xaml"
|
||||||
$script:LoadAppsDetailsScriptPath = "$PSScriptRoot/Scripts/FileIO/LoadAppsDetailsFromJson.ps1"
|
$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-level variables for GUI elements
|
||||||
$script:GuiWindow = $null
|
$script:GuiWindow = $null
|
||||||
@@ -249,6 +255,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
|
|
||||||
# File I/O functions
|
# File I/O functions
|
||||||
. "$PSScriptRoot/Scripts/FileIO/LoadJsonFile.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/LoadJsonFile.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/FileIO/SaveToFile.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/SaveSettings.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/SaveSettings.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/LoadSettings.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/LoadSettings.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/SaveCustomAppsListToFile.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/SaveCustomAppsListToFile.ps1"
|
||||||
@@ -263,6 +270,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
. "$PSScriptRoot/Scripts/GUI/AttachShiftClickBehavior.ps1"
|
. "$PSScriptRoot/Scripts/GUI/AttachShiftClickBehavior.ps1"
|
||||||
. "$PSScriptRoot/Scripts/GUI/ApplySettingsToUiControls.ps1"
|
. "$PSScriptRoot/Scripts/GUI/ApplySettingsToUiControls.ps1"
|
||||||
. "$PSScriptRoot/Scripts/GUI/Show-MessageBox.ps1"
|
. "$PSScriptRoot/Scripts/GUI/Show-MessageBox.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/GUI/Show-ConfigWindow.ps1"
|
||||||
. "$PSScriptRoot/Scripts/GUI/Show-ApplyModal.ps1"
|
. "$PSScriptRoot/Scripts/GUI/Show-ApplyModal.ps1"
|
||||||
. "$PSScriptRoot/Scripts/GUI/Show-AppSelectionWindow.ps1"
|
. "$PSScriptRoot/Scripts/GUI/Show-AppSelectionWindow.ps1"
|
||||||
. "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1"
|
. "$PSScriptRoot/Scripts/GUI/Show-MainWindow.ps1"
|
||||||
@@ -275,9 +283,11 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
. "$PSScriptRoot/Scripts/Helpers/CheckModernStandbySupport.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/CheckModernStandbySupport.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GenerateAppsList.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GetFriendlyTargetUserName.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetFriendlyTargetUserName.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/Helpers/ImportConfigToParams.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GetTargetUserForAppRemoval.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetTargetUserForAppRemoval.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1"
|
||||||
|
|
||||||
# Threading functions
|
# Threading functions
|
||||||
. "$PSScriptRoot/Scripts/Threading/DoEvents.ps1"
|
. "$PSScriptRoot/Scripts/Threading/DoEvents.ps1"
|
||||||
@@ -351,6 +361,9 @@ if ((Test-Path $script:SavedSettingsFilePath) -and ([String]::IsNullOrWhiteSpace
|
|||||||
Remove-Item -Path $script:SavedSettingsFilePath -recurse
|
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
|
# Only run the app selection form if the 'RunAppsListGenerator' parameter was passed to the script
|
||||||
if ($RunAppsListGenerator) {
|
if ($RunAppsListGenerator) {
|
||||||
PrintHeader "Custom Apps List Generator"
|
PrintHeader "Custom Apps List Generator"
|
||||||
@@ -370,7 +383,7 @@ if ($RunAppsListGenerator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Change script execution based on provided parameters or user input
|
# 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) {
|
if ($RunDefaults -or $RunDefaultsLite) {
|
||||||
ShowCLIDefaultModeOptions
|
ShowCLIDefaultModeOptions
|
||||||
}
|
}
|
||||||
@@ -383,8 +396,23 @@ if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSa
|
|||||||
|
|
||||||
ShowCLILastUsedSettings
|
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 {
|
else {
|
||||||
if ($CLI) {
|
if ($launchInCLI) {
|
||||||
$Mode = ShowCLIMenuOptions
|
$Mode = ShowCLIMenuOptions
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
Reference in New Issue
Block a user