mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-04-04 06:26:27 +00:00
Compare commits
25 Commits
2026.03.15
...
undo-tweak
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c79c05f286 | ||
|
|
105198e396 | ||
|
|
f8f85ca861 | ||
|
|
bd16457552 | ||
|
|
774c8ecd92 | ||
|
|
ad225cdf9d | ||
|
|
e05af92acc | ||
|
|
2eddbe5638 | ||
|
|
b5c576519b | ||
|
|
b1cf364c7d | ||
|
|
bc8fc1a284 | ||
|
|
e9bccccc09 | ||
|
|
b0125ddcd2 | ||
|
|
c15a18c376 | ||
|
|
85bdf765e5 | ||
|
|
bcfed9daff | ||
|
|
91a9beed0c | ||
|
|
cfc868ba91 | ||
|
|
a54c3c6918 | ||
|
|
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"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -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}"
|
||||||
|
|||||||
@@ -9,55 +9,6 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Foreground="{DynamicResource FgColor}">
|
Foreground="{DynamicResource FgColor}">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<!-- CheckBox Style -->
|
|
||||||
<Style TargetType="CheckBox">
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
|
||||||
<Setter Property="Padding" Value="4,2"/>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="CheckBox">
|
|
||||||
<Border Background="{TemplateBinding Background}" BorderThickness="0" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border x:Name="CheckBoxBorder" Grid.Column="0" Width="18" Height="18" Background="{DynamicResource CheckBoxBgColor}" BorderBrush="{DynamicResource CheckBoxBorderColor}" BorderThickness="1" CornerRadius="4" Margin="0,0,8,0">
|
|
||||||
<TextBlock x:Name="CheckMark" Text="" FontFamily="Segoe MDL2 Assets" FontSize="12" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"/>
|
|
||||||
</Border>
|
|
||||||
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="0,0,0,1"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource CheckBoxHoverColor}"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsChecked" Value="True">
|
|
||||||
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckMark" Property="Foreground" Value="White"/>
|
|
||||||
</Trigger>
|
|
||||||
<MultiTrigger>
|
|
||||||
<MultiTrigger.Conditions>
|
|
||||||
<Condition Property="IsMouseOver" Value="True"/>
|
|
||||||
<Condition Property="IsChecked" Value="True"/>
|
|
||||||
</MultiTrigger.Conditions>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
</MultiTrigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- CheckBox style for apps panels -->
|
|
||||||
<Style x:Key="AppsPanelCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
|
||||||
<Setter Property="Margin" Value="2,3,2,3"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- Title Bar Button Style -->
|
<!-- Title Bar Button Style -->
|
||||||
<Style x:Key="TitleBarButton" TargetType="Button">
|
<Style x:Key="TitleBarButton" TargetType="Button">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
|||||||
@@ -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}"
|
||||||
@@ -180,20 +180,20 @@
|
|||||||
<StackPanel x:Name="ButtonPanel"
|
<StackPanel x:Name="ButtonPanel"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
HorizontalAlignment="Center">
|
HorizontalAlignment="Center">
|
||||||
<Button x:Name="ApplyKofiBtn" Width="210" Height="32"
|
<Button x:Name="ApplyKofiBtn"
|
||||||
Style="{DynamicResource SecondaryButtonStyle}"
|
Width="200" Height="32" Margin="4,0"
|
||||||
Margin="0,0,12,0"
|
Style="{DynamicResource SecondaryButtonStyle}"
|
||||||
AutomationProperties.Name="Support the creator">
|
AutomationProperties.Name="Support the creator">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<TextBlock Text="" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,-1"/>
|
<TextBlock Text="" FontFamily="Segoe Fluent Icons" FontSize="14" VerticalAlignment="Center" Margin="0,0,8,-1"/>
|
||||||
<TextBlock Text="Support the creator" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,1"/>
|
<TextBlock Text="Support the creator" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,1"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="ApplyCloseBtn" Width="100" Height="32"
|
<Button x:Name="ApplyCloseBtn"
|
||||||
|
Content="Close"
|
||||||
|
Width="200" Height="32" Margin="4,0"
|
||||||
Style="{DynamicResource PrimaryButtonStyle}"
|
Style="{DynamicResource PrimaryButtonStyle}"
|
||||||
AutomationProperties.Name="Close">
|
AutomationProperties.Name="Close"/>
|
||||||
<TextBlock Text="Close" VerticalAlignment="Center" FontSize="14" Margin="0,0,0,1"/>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -336,89 +336,6 @@
|
|||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- CheckBox Style -->
|
|
||||||
<Style TargetType="CheckBox">
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
|
||||||
<Setter Property="Padding" Value="4,2"/>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="CheckBox">
|
|
||||||
<Border Background="{TemplateBinding Background}" BorderThickness="0" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border x:Name="CheckBoxBorder" Grid.Column="0" Width="18" Height="18" Background="{DynamicResource CheckBoxBgColor}" BorderBrush="{DynamicResource CheckBoxBorderColor}" BorderThickness="1" CornerRadius="4" Margin="0,0,8,0">
|
|
||||||
<Grid>
|
|
||||||
<TextBlock x:Name="CheckMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="12" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"/>
|
|
||||||
<TextBlock x:Name="IndeterminateMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed" Margin="1,1,0,0" />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="0,0,0,2"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource CheckBoxHoverColor}"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsChecked" Value="True">
|
|
||||||
<Setter TargetName="CheckMark" Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckMark" Property="Foreground" Value="White"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsChecked" Value="{x:Null}">
|
|
||||||
<Setter TargetName="IndeterminateMark" Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
|
||||||
<Setter TargetName="IndeterminateMark" Property="Foreground" Value="White"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsEnabled" Value="False">
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonDisabled}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource ButtonTextDisabled}"/>
|
|
||||||
<Setter Property="Opacity" Value="0.6"/>
|
|
||||||
<Setter TargetName="CheckMark" Property="Foreground" Value="{DynamicResource ButtonTextDisabled}"/>
|
|
||||||
</Trigger>
|
|
||||||
<MultiTrigger>
|
|
||||||
<MultiTrigger.Conditions>
|
|
||||||
<Condition Property="IsMouseOver" Value="True"/>
|
|
||||||
<Condition Property="IsChecked" Value="True"/>
|
|
||||||
</MultiTrigger.Conditions>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
</MultiTrigger>
|
|
||||||
<MultiTrigger>
|
|
||||||
<MultiTrigger.Conditions>
|
|
||||||
<Condition Property="IsMouseOver" Value="True"/>
|
|
||||||
<Condition Property="IsChecked" Value="{x:Null}"/>
|
|
||||||
</MultiTrigger.Conditions>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
|
||||||
</MultiTrigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- CheckBox style for feature toggles -->
|
|
||||||
<Style x:Key="FeatureCheckboxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
|
||||||
<Setter Property="Margin" Value="-4,-2,-4,10"/>
|
|
||||||
<Setter Property="Padding" Value="4,2"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- CheckBox style for apps panels -->
|
|
||||||
<Style x:Key="AppsPanelCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
|
||||||
<Setter Property="Margin" Value="2,3,2,3"/>
|
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- TextBlock style for App ID column in apps table -->
|
<!-- TextBlock style for App ID column in apps table -->
|
||||||
<Style x:Key="AppIdTextStyle" TargetType="TextBlock">
|
<Style x:Key="AppIdTextStyle" TargetType="TextBlock">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource AppIdColor}"/>
|
<Setter Property="Foreground" Value="{DynamicResource AppIdColor}"/>
|
||||||
@@ -458,11 +375,6 @@
|
|||||||
<Setter Property="Margin" Value="0,1,0,0"/>
|
<Setter Property="Margin" Value="0,1,0,0"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- Style for dynamically-created preset checkboxes in the Quick Select popup -->
|
|
||||||
<Style x:Key="PresetCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
|
||||||
<Setter Property="Margin" Value="8,4"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- Progress step indicator fill colors -->
|
<!-- Progress step indicator fill colors -->
|
||||||
<SolidColorBrush x:Key="ProgressActiveColor" Color="#0067c0"/>
|
<SolidColorBrush x:Key="ProgressActiveColor" Color="#0067c0"/>
|
||||||
<SolidColorBrush x:Key="ProgressInactiveColor" Color="#808080"/>
|
<SolidColorBrush x:Key="ProgressInactiveColor" Color="#808080"/>
|
||||||
@@ -649,6 +561,29 @@
|
|||||||
<Button x:Name="MenuBtn" Content="" FontFamily="Segoe Fluent Icons" FontSize="15" Style="{StaticResource TitlebarButton}" ToolTip="Options" AutomationProperties.Name="Options">
|
<Button x:Name="MenuBtn" 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}"/>
|
||||||
@@ -753,6 +688,11 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Revert link -->
|
||||||
|
<Button x:Name="HomeRevertLinkBtn" HorizontalAlignment="Center" Margin="0,11,0,0" Style="{DynamicResource ActionLinkButtonStyle}" AutomationProperties.Name="Revert previous changes">
|
||||||
|
<TextBlock Text="Revert previous changes" Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -1106,12 +1046,12 @@
|
|||||||
|
|
||||||
<!-- Restore Point Option -->
|
<!-- Restore Point Option -->
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<CheckBox x:Name="RestorePointCheckBox" Style="{StaticResource FeatureCheckboxStyle}" IsChecked="True" Content="Create a system restore point (Recommended)" AutomationProperties.Name="Create a system restore point (Recommended)"/>
|
<CheckBox x:Name="RestorePointCheckBox" Style="{DynamicResource FeatureCheckboxStyle}" IsChecked="True" Content="Create a system restore point (Recommended)" AutomationProperties.Name="Create a system restore point (Recommended)"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Restart Explorer Option -->
|
<!-- Restart Explorer Option -->
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<CheckBox x:Name="RestartExplorerCheckBox" Style="{StaticResource FeatureCheckboxStyle}" Content="Restart the Windows Explorer process to apply all changes immediately" AutomationProperties.Name="Restart the Windows Explorer process to apply all changes immediately"/>
|
<CheckBox x:Name="RestartExplorerCheckBox" Style="{DynamicResource FeatureCheckboxStyle}" Content="Restart the Windows Explorer process to apply all changes immediately" AutomationProperties.Name="Restart the Windows Explorer process to apply all changes immediately"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -1140,20 +1080,8 @@
|
|||||||
|
|
||||||
<!-- Review & Apply Section -->
|
<!-- Review & Apply Section -->
|
||||||
<StackPanel Grid.Row="1" HorizontalAlignment="Stretch" Background="{DynamicResource BgColor}">
|
<StackPanel Grid.Row="1" HorizontalAlignment="Stretch" Background="{DynamicResource BgColor}">
|
||||||
<Button x:Name="ReviewChangesBtn" Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Center" Margin="0,4,0,10" AutomationProperties.Name="Review selected changes">
|
<Button x:Name="ReviewChangesBtn" Style="{DynamicResource ActionLinkButtonStyle}" HorizontalAlignment="Center" Margin="0,4,0,10" AutomationProperties.Name="Review selected changes">
|
||||||
<Button.Template>
|
<TextBlock Text="Review selected changes" Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
||||||
<ControlTemplate TargetType="Button">
|
|
||||||
<TextBlock x:Name="LinkText" Text="Review selected changes" FontSize="14" Foreground="{DynamicResource ButtonBg}" FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter TargetName="LinkText" Property="Foreground" Value="{DynamicResource ButtonHover}"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsPressed" Value="True">
|
|
||||||
<Setter TargetName="LinkText" Property="Foreground" Value="{DynamicResource ButtonPressed}"/>
|
|
||||||
</Trigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Button.Template>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="DeploymentApplyBtn" Style="{DynamicResource PrimaryButtonStyle}" Width="190" Height="44" HorizontalAlignment="Center" AutomationProperties.Name="Apply Changes">
|
<Button x:Name="DeploymentApplyBtn" Style="{DynamicResource PrimaryButtonStyle}" Width="190" Height="44" HorizontalAlignment="Center" AutomationProperties.Name="Apply Changes">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
160
Schemas/RevertSettingsWindow.xaml
Normal file
160
Schemas/RevertSettingsWindow.xaml
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Revert Changes"
|
||||||
|
Width="620"
|
||||||
|
Height="620"
|
||||||
|
MinWidth="560"
|
||||||
|
MinHeight="420"
|
||||||
|
ResizeMode="CanResize"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
WindowStyle="None"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Background="Transparent"
|
||||||
|
Topmost="False"
|
||||||
|
ShowInTaskbar="False">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<Style x:Key="RevertItemBorderStyle" TargetType="Border">
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,0,1"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
|
||||||
|
<Setter Property="Padding" Value="4,6,4,6"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="RevertItemRowStyle" TargetType="StackPanel">
|
||||||
|
<Setter Property="Orientation" Value="Vertical"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="RevertItemUndoTextStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
||||||
|
<Setter Property="Opacity" Value="0.75"/>
|
||||||
|
<Setter Property="Margin" Value="26,-8,0,3"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="RevertEmptyTextStyle" TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
||||||
|
<Setter Property="Opacity" Value="0.85"/>
|
||||||
|
<Setter Property="Margin" Value="6,0,6,0"/>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<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="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" x:Name="TitleBar" Height="40" Background="Transparent">
|
||||||
|
<TextBlock x:Name="TitleText"
|
||||||
|
Text="Revert Changes"
|
||||||
|
Foreground="{DynamicResource FgColor}"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="16,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Margin="20,12,1,12">
|
||||||
|
<TextBlock x:Name="MessageText"
|
||||||
|
Grid.Column="1"
|
||||||
|
Text="Select which previously applied tweaks should be reverted to Windows defaults."
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="14"
|
||||||
|
LineHeight="20"
|
||||||
|
Foreground="{DynamicResource FgColor}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid Grid.Row="2" Margin="20,0,20,10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<DockPanel Grid.Row="0" Margin="0,0,0,8">
|
||||||
|
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
|
||||||
|
<Button x:Name="RevertSelectAllBtn"
|
||||||
|
Content="Select All"
|
||||||
|
Height="30"
|
||||||
|
Padding="12,0"
|
||||||
|
Style="{DynamicResource SecondaryButtonStyle}"
|
||||||
|
Margin="0,0,8,0"
|
||||||
|
AutomationProperties.Name="Select All"/>
|
||||||
|
<Button x:Name="RevertClearBtn"
|
||||||
|
Content="Clear"
|
||||||
|
Height="30"
|
||||||
|
Padding="12,0"
|
||||||
|
Style="{DynamicResource SecondaryButtonStyle}"
|
||||||
|
AutomationProperties.Name="Clear"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock x:Name="RevertSelectionCount"
|
||||||
|
Text="0 settings selected"
|
||||||
|
Foreground="{DynamicResource FgColor}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Margin="0,0,6,0"/>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource BorderColor}"
|
||||||
|
CornerRadius="6"
|
||||||
|
Background="{DynamicResource BgColor}">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="8" Margin="0,1,1,1">
|
||||||
|
<StackPanel x:Name="RevertItemsPanel"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<CheckBox x:Name="RevertRestartExplorerCheckBox"
|
||||||
|
Grid.Row="2"
|
||||||
|
Content="Restart the Windows Explorer process to apply all changes immediately"
|
||||||
|
IsChecked="False"
|
||||||
|
Margin="2,10,0,0"
|
||||||
|
AutomationProperties.Name="Restart the Windows Explorer process to apply all changes immediately"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Button Panel -->
|
||||||
|
<Border Grid.Row="3"
|
||||||
|
Background="{DynamicResource BgColor}"
|
||||||
|
BorderBrush="{DynamicResource BorderColor}"
|
||||||
|
BorderThickness="0,1,0,0"
|
||||||
|
Padding="16,12"
|
||||||
|
CornerRadius="0,0,8,8">
|
||||||
|
<StackPanel x:Name="ButtonPanel"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button x:Name="RevertApplyBtn"
|
||||||
|
Content="Revert Selected Settings"
|
||||||
|
Height="32" MinWidth="200" Margin="4,0"
|
||||||
|
Style="{DynamicResource PrimaryButtonStyle}"
|
||||||
|
IsEnabled="False"
|
||||||
|
AutomationProperties.Name="Revert Selected Settings"/>
|
||||||
|
<Button x:Name="RevertCancelBtn"
|
||||||
|
Content="Cancel"
|
||||||
|
Height="32" MinWidth="80" Margin="4,0"
|
||||||
|
Style="{DynamicResource SecondaryButtonStyle}"
|
||||||
|
AutomationProperties.Name="Cancel"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
BorderThickness="{TemplateBinding BorderThickness}"
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
CornerRadius="4"
|
CornerRadius="4"
|
||||||
Padding="{TemplateBinding Padding}">
|
Padding="{TemplateBinding Padding}">
|
||||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,1"/>
|
||||||
</Border>
|
</Border>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter.Value>
|
</Setter.Value>
|
||||||
@@ -90,6 +90,32 @@
|
|||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Shared link-style button used for text actions like review/revert links -->
|
||||||
|
<Style x:Key="ActionLinkButtonStyle" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonBg}"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Padding" Value="0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonHover}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource ButtonPressed}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- ProgressBar Style -->
|
<!-- ProgressBar Style -->
|
||||||
<Style x:Key="ApplyProgressBarStyle" TargetType="ProgressBar">
|
<Style x:Key="ApplyProgressBarStyle" TargetType="ProgressBar">
|
||||||
<Setter Property="Background" Value="{DynamicResource ButtonBorderColor}"/>
|
<Setter Property="Background" Value="{DynamicResource ButtonBorderColor}"/>
|
||||||
@@ -125,6 +151,138 @@
|
|||||||
<Setter Property="TextAlignment" Value="Center"/>
|
<Setter Property="TextAlignment" Value="Center"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!-- Base CheckBox style used across windows -->
|
||||||
|
<Style TargetType="CheckBox">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource FgColor}"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="4,2"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="CheckBox">
|
||||||
|
<Border Background="{TemplateBinding Background}" BorderThickness="0" CornerRadius="4" Padding="{TemplateBinding Padding}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Border x:Name="CheckBoxBorder" Grid.Column="0" Width="18" Height="18" Background="{DynamicResource CheckBoxBgColor}" BorderBrush="{DynamicResource CheckBoxBorderColor}" BorderThickness="1" CornerRadius="4" Margin="0,0,8,0">
|
||||||
|
<Grid>
|
||||||
|
<TextBlock x:Name="CheckMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="12" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
|
||||||
|
<TextBlock.Clip>
|
||||||
|
<RectangleGeometry x:Name="CheckMarkClip" Rect="0,0,0,16"/>
|
||||||
|
</TextBlock.Clip>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock x:Name="IndeterminateMark" Text="" FontFamily="Segoe Fluent Icons" FontSize="11" Foreground="{DynamicResource ButtonBg}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0" Margin="1,1,0,0">
|
||||||
|
<TextBlock.Clip>
|
||||||
|
<RectangleGeometry x:Name="IndeterminateMarkClip" Rect="0,0,0,16"/>
|
||||||
|
</TextBlock.Clip>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" Margin="0,0,0,2"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource CheckBoxHoverColor}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
||||||
|
<Setter TargetName="CheckMark" Property="Foreground" Value="White"/>
|
||||||
|
<Setter TargetName="CheckMark" Property="Opacity" Value="1"/>
|
||||||
|
<Setter TargetName="IndeterminateMark" Property="Opacity" Value="0"/>
|
||||||
|
<Trigger.EnterActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" FillBehavior="HoldEnd"/>
|
||||||
|
<RectAnimation Storyboard.TargetName="CheckMarkClip" Storyboard.TargetProperty="Rect" From="0,0,0,16" To="0,0,16,16" Duration="0:0:0.25" FillBehavior="HoldEnd"/>
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</Trigger.EnterActions>
|
||||||
|
<Trigger.ExitActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<RectAnimation Storyboard.TargetName="CheckMarkClip" Storyboard.TargetProperty="Rect" From="0,0,16,16" To="16,0,0,16" Duration="0:0:0.15" FillBehavior="HoldEnd"/>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="CheckMark" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.15" FillBehavior="HoldEnd"/>
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</Trigger.ExitActions>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="{x:Null}">
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonBg}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonBg}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
||||||
|
<Setter TargetName="IndeterminateMark" Property="Foreground" Value="White"/>
|
||||||
|
<Setter TargetName="IndeterminateMark" Property="Opacity" Value="1"/>
|
||||||
|
<Setter TargetName="CheckMark" Property="Opacity" Value="0"/>
|
||||||
|
<Trigger.EnterActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="IndeterminateMark" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" FillBehavior="HoldEnd"/>
|
||||||
|
<RectAnimation Storyboard.TargetName="IndeterminateMarkClip" Storyboard.TargetProperty="Rect" From="0,0,0,16" To="0,0,16,16" Duration="0:0:0.25" FillBehavior="HoldEnd"/>
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</Trigger.EnterActions>
|
||||||
|
<Trigger.ExitActions>
|
||||||
|
<BeginStoryboard>
|
||||||
|
<Storyboard>
|
||||||
|
<RectAnimation Storyboard.TargetName="IndeterminateMarkClip" Storyboard.TargetProperty="Rect" From="0,0,16,16" To="16,0,0,16" Duration="0:0:0.15" FillBehavior="HoldEnd"/>
|
||||||
|
<DoubleAnimation Storyboard.TargetName="IndeterminateMark" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.15" FillBehavior="HoldEnd"/>
|
||||||
|
</Storyboard>
|
||||||
|
</BeginStoryboard>
|
||||||
|
</Trigger.ExitActions>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="False">
|
||||||
|
<Setter TargetName="CheckMark" Property="Opacity" Value="0"/>
|
||||||
|
<Setter TargetName="IndeterminateMark" Property="Opacity" Value="0"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonDisabled}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource BorderColor}"/>
|
||||||
|
<Setter Property="Opacity" Value="0.4"/>
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsMouseOver" Value="True"/>
|
||||||
|
<Condition Property="IsChecked" Value="True"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsMouseOver" Value="True"/>
|
||||||
|
<Condition Property="IsChecked" Value="{x:Null}"/>
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Background" Value="{DynamicResource ButtonHover}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="BorderBrush" Value="{DynamicResource ButtonHover}"/>
|
||||||
|
<Setter TargetName="CheckBoxBorder" Property="Opacity" Value="0.8"/>
|
||||||
|
</MultiTrigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Shared checkbox style used for feature toggles across windows -->
|
||||||
|
<Style x:Key="FeatureCheckboxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||||
|
<Setter Property="Margin" Value="-4,-2,-4,10"/>
|
||||||
|
<Setter Property="Padding" Value="4,2"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Shared CheckBox style for app list items -->
|
||||||
|
<Style x:Key="AppsPanelCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||||
|
<Setter Property="Margin" Value="2,3,2,3"/>
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Shared CheckBox style for preset picker entries -->
|
||||||
|
<Style x:Key="PresetCheckBoxStyle" TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
|
||||||
|
<Setter Property="Margin" Value="8,4"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- ScrollBar Style -->
|
<!-- ScrollBar Style -->
|
||||||
<Style TargetType="{x:Type ScrollBar}">
|
<Style TargetType="{x:Type ScrollBar}">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
# Prints all pending changes that will be made by the script
|
# Prints all pending changes that will be made by the script
|
||||||
function PrintPendingChanges {
|
function PrintPendingChanges {
|
||||||
Write-Output "Win11Debloat will make the following changes:"
|
$skippedParams = @()
|
||||||
|
$undoChanges = $script:Params.ContainsKey('Undo')
|
||||||
|
|
||||||
|
if ($undoChanges) {
|
||||||
|
Write-Output "Win11Debloat will make the following changes to revert the selected settings to Windows defaults:"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output "Win11Debloat will make the following changes:"
|
||||||
|
}
|
||||||
|
|
||||||
if ($script:Params['CreateRestorePoint']) {
|
if ($script:Params['CreateRestorePoint']) {
|
||||||
Write-Output "- $($script:Features['CreateRestorePoint'].Label)"
|
Write-Output "- $($script:Features['CreateRestorePoint'].Action)"
|
||||||
}
|
}
|
||||||
foreach ($parameterName in $script:Params.Keys) {
|
foreach ($parameterName in $script:Params.Keys) {
|
||||||
if ($script:ControlParams -contains $parameterName) {
|
if ($script:ControlParams -contains $parameterName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if ($parameterName -eq 'Apps' -or $parameterName -eq 'CreateRestorePoint') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($undoChanges) {
|
||||||
|
$undoFeature = GetUndoFeatureForParam -paramKey $parameterName
|
||||||
|
if (-not $undoFeature) {
|
||||||
|
$skippedParams += $parameterName
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Print parameter description
|
# Print parameter description
|
||||||
switch ($parameterName) {
|
switch ($parameterName) {
|
||||||
@@ -46,9 +65,18 @@ function PrintPendingChanges {
|
|||||||
}
|
}
|
||||||
default {
|
default {
|
||||||
if ($script:Features -and $script:Features.ContainsKey($parameterName)) {
|
if ($script:Features -and $script:Features.ContainsKey($parameterName)) {
|
||||||
$action = $script:Features[$parameterName].Action
|
$message = if ($undoChanges -and $script:Features[$parameterName].UndoAction) {
|
||||||
$message = $script:Features[$parameterName].Label
|
$script:Features[$parameterName].UndoAction
|
||||||
Write-Output "- $action $message"
|
}
|
||||||
|
else {
|
||||||
|
$script:Features[$parameterName].Action
|
||||||
|
}
|
||||||
|
if ($message) {
|
||||||
|
Write-Output "- $message"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output "- $parameterName"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Fallback: show the parameter name if no feature description is available
|
# Fallback: show the parameter name if no feature description is available
|
||||||
@@ -59,8 +87,18 @@ function PrintPendingChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($undoChanges -and $skippedParams.Count -gt 0) {
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "The following changes cannot be automatically undone and will be skipped:"
|
||||||
|
|
||||||
|
$uniqueSkipped = $skippedParams | Sort-Object -Unique
|
||||||
|
foreach ($skippedParam in $uniqueSkipped) {
|
||||||
|
Write-Output "- $($script:Features[$skippedParam].Action)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
Write-Output "Press enter to execute the script or press CTRL+C to quit..."
|
Write-Output "Press enter to execute the script or press CTRL+C to quit..."
|
||||||
Read-Host | Out-Null
|
Read-Host | Out-Null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
||||||
}
|
}
|
||||||
@@ -6,13 +6,35 @@ function ExecuteParameter {
|
|||||||
[string]$paramKey
|
[string]$paramKey
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if this feature has metadata in Features.json
|
# Check if this feature exists in Features.json
|
||||||
$feature = $null
|
$feature = $null
|
||||||
if ($script:Features.ContainsKey($paramKey)) {
|
if ($script:Features.ContainsKey($paramKey)) {
|
||||||
$feature = $script:Features[$paramKey]
|
$feature = $script:Features[$paramKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if undo is requested and if this feature supports undo
|
||||||
|
$undoChanges = $script:Params.ContainsKey('Undo')
|
||||||
|
|
||||||
|
if ($undoChanges) {
|
||||||
|
$undoFeature = GetUndoFeatureForParam -paramKey $paramKey
|
||||||
|
|
||||||
|
if ($null -eq $undoFeature) {
|
||||||
|
# This parameter doesn't support undo, so skip it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$undoRegFile = $undoFeature.RegistryUndoKey
|
||||||
|
$undoFolderPath = Join-Path $script:RegfilesPath (Join-Path 'Undo' $undoRegFile)
|
||||||
|
|
||||||
|
if (Test-Path $undoFolderPath) {
|
||||||
|
$undoRegFile = Join-Path 'Undo' $undoRegFile
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportRegistryFile "> $($undoFeature.UndoText)" $undoRegFile
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
# If feature has RegistryKey and ApplyText, use dynamic ImportRegistryFile
|
# If feature has RegistryKey and ApplyText, dynamically import the registry file for this feature
|
||||||
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
||||||
ImportRegistryFile "> $($feature.ApplyText)" $feature.RegistryKey
|
ImportRegistryFile "> $($feature.ApplyText)" $feature.RegistryKey
|
||||||
|
|
||||||
@@ -139,13 +161,36 @@ function ExecuteParameter {
|
|||||||
# Executes all selected parameters/features
|
# Executes all selected parameters/features
|
||||||
function ExecuteAllChanges {
|
function ExecuteAllChanges {
|
||||||
# Build list of actionable parameters (skip control params and data-only params)
|
# Build list of actionable parameters (skip control params and data-only params)
|
||||||
|
$undoChanges = $script:Params.ContainsKey('Undo')
|
||||||
$actionableKeys = @()
|
$actionableKeys = @()
|
||||||
|
$paramsToRemove = @()
|
||||||
foreach ($paramKey in $script:Params.Keys) {
|
foreach ($paramKey in $script:Params.Keys) {
|
||||||
if ($script:ControlParams -contains $paramKey) { continue }
|
if ($script:ControlParams -contains $paramKey) { continue }
|
||||||
if ($paramKey -eq 'Apps') { continue }
|
if ($paramKey -eq 'Apps') { continue }
|
||||||
if ($paramKey -eq 'CreateRestorePoint') { continue }
|
if ($paramKey -eq 'CreateRestorePoint') { continue }
|
||||||
|
|
||||||
|
if ($undoChanges) {
|
||||||
|
$undoFeature = GetUndoFeatureForParam -paramKey $paramKey
|
||||||
|
if (-not $undoFeature) {
|
||||||
|
$paramsToRemove += $paramKey
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$actionableKeys += $paramKey
|
$actionableKeys += $paramKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($undoChanges -and $paramsToRemove.Count -gt 0) {
|
||||||
|
foreach ($paramKey in ($paramsToRemove | Sort-Object -Unique)) {
|
||||||
|
if ($script:Params.ContainsKey($paramKey)) {
|
||||||
|
$null = $script:Params.Remove($paramKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($undoChanges -and $actionableKeys.Count -eq 0) {
|
||||||
|
throw "Undo was requested but none of the selected parameters support undo. No changes were reverted."
|
||||||
|
}
|
||||||
|
|
||||||
$totalSteps = $actionableKeys.Count
|
$totalSteps = $actionableKeys.Count
|
||||||
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
if ($script:Params.ContainsKey("CreateRestorePoint")) { $totalSteps++ }
|
||||||
@@ -174,16 +219,13 @@ function ExecuteAllChanges {
|
|||||||
$stepName = $paramKey
|
$stepName = $paramKey
|
||||||
if ($script:Features.ContainsKey($paramKey)) {
|
if ($script:Features.ContainsKey($paramKey)) {
|
||||||
$feature = $script:Features[$paramKey]
|
$feature = $script:Features[$paramKey]
|
||||||
if ($feature.ApplyText) {
|
if ($undoChanges -and $feature.UndoText) {
|
||||||
# Prefer explicit ApplyText when provided
|
$stepName = $feature.UndoText
|
||||||
|
}
|
||||||
|
elseif ($feature.ApplyText) {
|
||||||
$stepName = $feature.ApplyText
|
$stepName = $feature.ApplyText
|
||||||
} elseif ($feature.Label) {
|
} elseif ($feature.Action) {
|
||||||
# Fallback: construct a name from Action and Label, or just Label
|
$stepName = $feature.Action
|
||||||
if ($feature.Action) {
|
|
||||||
$stepName = "$($feature.Action) $($feature.Label)"
|
|
||||||
} else {
|
|
||||||
$stepName = $feature.Label
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,4 +235,4 @@ function ExecuteAllChanges {
|
|||||||
|
|
||||||
ExecuteParameter -paramKey $paramKey
|
ExecuteParameter -paramKey $paramKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ function RestartExplorer {
|
|||||||
foreach ($paramKey in $script:Params.Keys) {
|
foreach ($paramKey in $script:Params.Keys) {
|
||||||
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
|
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
|
||||||
$feature = $script:Features[$paramKey]
|
$feature = $script:Features[$paramKey]
|
||||||
Write-Host "Warning: '$($feature.Action) $($feature.Label)' requires a reboot to take full effect" -ForegroundColor Yellow
|
Write-Host "Warning: '$($feature.Action)' requires a reboot to take full effect" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ function Show-ApplyModal {
|
|||||||
foreach ($paramKey in $script:Params.Keys) {
|
foreach ($paramKey in $script:Params.Keys) {
|
||||||
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
|
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
|
||||||
$feature = $script:Features[$paramKey]
|
$feature = $script:Features[$paramKey]
|
||||||
$rebootFeatures += "$($feature.Action) $($feature.Label)"
|
$rebootFeatures += $feature.Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ function Show-ApplyModal {
|
|||||||
$applyRebootPanel.Visibility = 'Visible'
|
$applyRebootPanel.Visibility = 'Visible'
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$script:ApplyCompletionMessageEl.Text = "Your clean system is ready. Thanks for using Win11Debloat!"
|
$script:ApplyCompletionMessageEl.Text = "Your system is ready. Thanks for using Win11Debloat!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
382
Scripts/GUI/Show-ConfigWindow.ps1
Normal file
382
Scripts/GUI/Show-ConfigWindow.ps1
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
$cb.Style = $window.Resources["AppsPanelCheckBoxStyle"]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ 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
|
# Title bar event handlers
|
||||||
$titleBar.Add_MouseLeftButtonDown({
|
$titleBar.Add_MouseLeftButtonDown({
|
||||||
@@ -67,6 +69,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()
|
||||||
})
|
})
|
||||||
@@ -241,7 +259,7 @@ function Show-MainWindow {
|
|||||||
$check = ($this.IsChecked -eq $true)
|
$check = ($this.IsChecked -eq $true)
|
||||||
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
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 +272,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 +315,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 +348,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 +402,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 +409,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 +431,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 {
|
||||||
@@ -403,7 +446,7 @@ function Show-MainWindow {
|
|||||||
|
|
||||||
# Dynamically builds Tweaks UI from Features.json
|
# Dynamically builds Tweaks UI from Features.json
|
||||||
function BuildDynamicTweaks {
|
function BuildDynamicTweaks {
|
||||||
$featuresJson = LoadJsonFile -filePath $script:FeaturesFilePath -expectedVersion "1.0"
|
$featuresJson = LoadJsonFile -filePath $script:FeaturesFilePath -expectedVersion $script:FeaturesConfigVersion
|
||||||
|
|
||||||
if (-not $featuresJson) {
|
if (-not $featuresJson) {
|
||||||
Show-MessageBox -Message "Unable to load Features.json file!" -Title "Error" -Button 'OK' -Icon 'Error' | Out-Null
|
Show-MessageBox -Message "Unable to load Features.json file!" -Title "Error" -Button 'OK' -Icon 'Error' | Out-Null
|
||||||
@@ -666,7 +709,7 @@ function Show-MainWindow {
|
|||||||
if ($feature.FeatureId -match '^Disable') { $opt = 'Disable' } elseif ($feature.FeatureId -match '^Enable') { $opt = 'Enable' }
|
if ($feature.FeatureId -match '^Disable') { $opt = 'Disable' } elseif ($feature.FeatureId -match '^Enable') { $opt = 'Enable' }
|
||||||
$items = @('No Change', $opt)
|
$items = @('No Change', $opt)
|
||||||
$comboName = ("Feature_{0}_Combo" -f $feature.FeatureId) -replace '[^a-zA-Z0-9_]',''
|
$comboName = ("Feature_{0}_Combo" -f $feature.FeatureId) -replace '[^a-zA-Z0-9_]',''
|
||||||
$combo = CreateLabeledCombo -parent $panel -labelText ($feature.Action + ' ' + $feature.Label) -comboName $comboName -items $items
|
$combo = CreateLabeledCombo -parent $panel -labelText $feature.Action -comboName $comboName -items $items
|
||||||
# attach tooltip from Features.json if present
|
# attach tooltip from Features.json if present
|
||||||
if ($feature.ToolTip) {
|
if ($feature.ToolTip) {
|
||||||
$tipBlock = New-Object System.Windows.Controls.TextBlock
|
$tipBlock = New-Object System.Windows.Controls.TextBlock
|
||||||
@@ -678,7 +721,7 @@ function Show-MainWindow {
|
|||||||
try { $lblBorderObj = $window.FindName("$comboName`_LabelBorder") } catch {}
|
try { $lblBorderObj = $window.FindName("$comboName`_LabelBorder") } catch {}
|
||||||
if ($lblBorderObj) { $lblBorderObj.ToolTip = $tipBlock }
|
if ($lblBorderObj) { $lblBorderObj.ToolTip = $tipBlock }
|
||||||
}
|
}
|
||||||
$script:UiControlMappings[$comboName] = @{ Type='feature'; FeatureId = $feature.FeatureId; Action = $feature.Action; Label = $feature.Label }
|
$script:UiControlMappings[$comboName] = @{ Type='feature'; FeatureId = $feature.FeatureId; Action = $feature.Action }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -686,7 +729,7 @@ function Show-MainWindow {
|
|||||||
# Build a feature-label lookup so GenerateOverview can resolve feature IDs without reloading JSON
|
# Build a feature-label lookup so GenerateOverview can resolve feature IDs without reloading JSON
|
||||||
$script:FeatureLabelLookup = @{}
|
$script:FeatureLabelLookup = @{}
|
||||||
foreach ($f in $featuresJson.Features) {
|
foreach ($f in $featuresJson.Features) {
|
||||||
$script:FeatureLabelLookup[$f.FeatureId] = $f.Action + ' ' + $f.Label
|
$script:FeatureLabelLookup[$f.FeatureId] = $f.Action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -718,6 +761,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 +778,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 +816,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 +830,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 +856,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 +867,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()
|
||||||
@@ -891,6 +946,11 @@ function Show-MainWindow {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 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
|
||||||
@@ -1250,7 +1310,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 +1380,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 +1394,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
|
||||||
}
|
}
|
||||||
@@ -1364,13 +1430,13 @@ function Show-MainWindow {
|
|||||||
if ($userSelectionCombo.SelectedIndex -ne 2) {
|
if ($userSelectionCombo.SelectedIndex -ne 2) {
|
||||||
$appRemovalScopeCombo.IsEnabled = $true
|
$appRemovalScopeCombo.IsEnabled = $true
|
||||||
}
|
}
|
||||||
$appRemovalScopeSection.Opacity = 1.0
|
$appRemovalScopeSection.Visibility = 'Visible'
|
||||||
UpdateAppRemovalScopeDescription
|
UpdateAppRemovalScopeDescription
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Disable app removal scope selection when no apps selected
|
# Disable app removal scope selection when no apps selected
|
||||||
$appRemovalScopeCombo.IsEnabled = $false
|
$appRemovalScopeCombo.IsEnabled = $false
|
||||||
$appRemovalScopeSection.Opacity = 0.5
|
$appRemovalScopeSection.Visibility = 'Collapsed'
|
||||||
$appRemovalScopeDescription.Text = "No apps selected for removal."
|
$appRemovalScopeDescription.Text = "No apps selected for removal."
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1400,8 +1466,7 @@ function Show-MainWindow {
|
|||||||
}
|
}
|
||||||
elseif ($mapping.Type -eq 'feature') {
|
elseif ($mapping.Type -eq 'feature') {
|
||||||
$label = $script:FeatureLabelLookup[$mapping.FeatureId]
|
$label = $script:FeatureLabelLookup[$mapping.FeatureId]
|
||||||
if (-not $label) { $label = $mapping.Action + ' ' + $mapping.Label }
|
if ($label) { $changesList += $label }
|
||||||
$changesList += $label
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1445,6 +1510,44 @@ function Show-MainWindow {
|
|||||||
UpdateNavigationButtons
|
UpdateNavigationButtons
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Handle Home Revert link button
|
||||||
|
$homeRevertLinkBtn = $window.FindName('HomeRevertLinkBtn')
|
||||||
|
if ($homeRevertLinkBtn) {
|
||||||
|
if (-not (Test-Path $script:SavedSettingsFilePath)) {
|
||||||
|
$homeRevertLinkBtn.Visibility = 'Collapsed'
|
||||||
|
}
|
||||||
|
|
||||||
|
$homeRevertLinkBtn.Add_Click({
|
||||||
|
$savedSettings = LoadJsonFile -filePath $script:SavedSettingsFilePath -expectedVersion "1.0" -optionalFile
|
||||||
|
if (-not $savedSettings -or -not $savedSettings.Settings) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$revertSelection = Show-RevertSettingsModal -Owner $window -LastUsedSettings $savedSettings
|
||||||
|
$selectedFeatureIds = @($revertSelection.SelectedFeatureIds)
|
||||||
|
$shouldRestartExplorer = ($revertSelection.RestartExplorer -eq $true)
|
||||||
|
|
||||||
|
if (-not $selectedFeatureIds -or $selectedFeatureIds.Count -eq 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AddParameter 'Undo'
|
||||||
|
|
||||||
|
foreach ($featureId in $selectedFeatureIds) {
|
||||||
|
if ($script:Features.ContainsKey($featureId)) {
|
||||||
|
$feature = $script:Features[$featureId]
|
||||||
|
if ($feature.RegistryUndoKey -and $feature.UndoAction) {
|
||||||
|
AddParameter $featureId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Show-ApplyModal -Owner $window -RestartExplorer $shouldRestartExplorer
|
||||||
|
|
||||||
|
$window.Close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
# Handle Home Default Mode button - apply defaults and navigate directly to overview
|
# Handle Home Default Mode button - apply defaults and navigate directly to overview
|
||||||
$homeDefaultModeBtn = $window.FindName('HomeDefaultModeBtn')
|
$homeDefaultModeBtn = $window.FindName('HomeDefaultModeBtn')
|
||||||
$homeDefaultModeBtn.Add_Click({
|
$homeDefaultModeBtn.Add_Click({
|
||||||
@@ -1482,7 +1585,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 +1601,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
|
||||||
@@ -1715,7 +1825,7 @@ function Show-MainWindow {
|
|||||||
if ($script:UpdatingPresets) { return }
|
if ($script:UpdatingPresets) { return }
|
||||||
$check = ($this.IsChecked -eq $true)
|
$check = ($this.IsChecked -eq $true)
|
||||||
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
|
||||||
ApplyPresetToApps -MatchFilter { param($c) $script:SavedAppIds -contains $c.Tag } -Check $check
|
ApplyPresetToApps -MatchFilter { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 } -Check $check
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
198
Scripts/GUI/Show-RevertSettingsModal.ps1
Normal file
198
Scripts/GUI/Show-RevertSettingsModal.ps1
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
function Show-RevertSettingsModal {
|
||||||
|
param (
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[System.Windows.Window]$Owner = $null,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
$LastUsedSettings
|
||||||
|
)
|
||||||
|
|
||||||
|
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase | Out-Null
|
||||||
|
|
||||||
|
$usesDarkMode = GetSystemUsesDarkMode
|
||||||
|
|
||||||
|
# Determine owner window
|
||||||
|
$ownerWindow = if ($Owner) { $Owner } else { $script:GuiWindow }
|
||||||
|
|
||||||
|
# Show overlay if owner window exists
|
||||||
|
$overlay = $null
|
||||||
|
$overlayWasAlreadyVisible = $false
|
||||||
|
if ($ownerWindow) {
|
||||||
|
try {
|
||||||
|
$overlay = $ownerWindow.FindName('ModalOverlay')
|
||||||
|
if ($overlay) {
|
||||||
|
$overlayWasAlreadyVisible = ($overlay.Visibility -eq 'Visible')
|
||||||
|
if (-not $overlayWasAlreadyVisible) {
|
||||||
|
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Visible' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load XAML from file
|
||||||
|
$xaml = Get-Content -Path $script:RevertSettingsWindowSchema -Raw
|
||||||
|
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
|
||||||
|
try {
|
||||||
|
$revertWindow = [System.Windows.Markup.XamlReader]::Load($reader)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ownerWindow) {
|
||||||
|
try {
|
||||||
|
$revertWindow.Owner = $ownerWindow
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowThemeResources -window $revertWindow -usesDarkMode $usesDarkMode
|
||||||
|
|
||||||
|
$itemsPanel = $revertWindow.FindName('RevertItemsPanel')
|
||||||
|
$countText = $revertWindow.FindName('RevertSelectionCount')
|
||||||
|
$selectAllBtn = $revertWindow.FindName('RevertSelectAllBtn')
|
||||||
|
$clearBtn = $revertWindow.FindName('RevertClearBtn')
|
||||||
|
$applyBtn = $revertWindow.FindName('RevertApplyBtn')
|
||||||
|
$cancelBtn = $revertWindow.FindName('RevertCancelBtn')
|
||||||
|
$restartExplorerCheckbox = $revertWindow.FindName('RevertRestartExplorerCheckBox')
|
||||||
|
$featureCheckboxStyle = $revertWindow.FindResource('FeatureCheckboxStyle')
|
||||||
|
$restartExplorerCheckbox.Style = $featureCheckboxStyle
|
||||||
|
$entryCheckboxes = @()
|
||||||
|
|
||||||
|
foreach ($setting in $LastUsedSettings.Settings) {
|
||||||
|
if ($setting.Value -ne $true) { continue }
|
||||||
|
if ($setting.Name -eq 'Apps' -or $setting.Name -eq 'RemoveApps' -or $setting.Name -eq 'CreateRestorePoint') { continue }
|
||||||
|
|
||||||
|
$feature = $null
|
||||||
|
if ($script:Features.ContainsKey($setting.Name)) {
|
||||||
|
$feature = $script:Features[$setting.Name]
|
||||||
|
}
|
||||||
|
$undoFeature = GetUndoFeatureForParam -paramKey $setting.Name
|
||||||
|
|
||||||
|
$label = $setting.Name
|
||||||
|
if ($feature -and $feature.Action) {
|
||||||
|
$label = $feature.Action
|
||||||
|
}
|
||||||
|
|
||||||
|
$undoLabel = if ($undoFeature -and $undoFeature.UndoAction) {
|
||||||
|
$undoFeature.UndoAction
|
||||||
|
} else {
|
||||||
|
'No revert action available'
|
||||||
|
}
|
||||||
|
|
||||||
|
$canUndo = ($null -ne $undoFeature)
|
||||||
|
|
||||||
|
$itemBorder = New-Object System.Windows.Controls.Border
|
||||||
|
$itemBorder.Style = $revertWindow.FindResource('RevertItemBorderStyle')
|
||||||
|
|
||||||
|
$row = New-Object System.Windows.Controls.StackPanel
|
||||||
|
$row.Style = $revertWindow.FindResource('RevertItemRowStyle')
|
||||||
|
|
||||||
|
$checkbox = New-Object System.Windows.Controls.CheckBox
|
||||||
|
$checkbox.Content = $label
|
||||||
|
$checkbox.Tag = $setting.Name
|
||||||
|
$checkbox.Style = $featureCheckboxStyle
|
||||||
|
$checkbox.IsEnabled = $canUndo
|
||||||
|
|
||||||
|
$undoText = New-Object System.Windows.Controls.TextBlock
|
||||||
|
$undoText.Text = if ($canUndo) { "Revert to: $undoLabel" } else { 'Revert not supported for this setting' }
|
||||||
|
$undoText.Style = $revertWindow.FindResource('RevertItemUndoTextStyle')
|
||||||
|
$undoText.Opacity = if ($canUndo) { 0.75 } else { 0.4 }
|
||||||
|
|
||||||
|
$row.Children.Add($checkbox) | Out-Null
|
||||||
|
$row.Children.Add($undoText) | Out-Null
|
||||||
|
$itemBorder.Child = $row
|
||||||
|
|
||||||
|
$itemsPanel.Children.Add($itemBorder) | Out-Null
|
||||||
|
$entryCheckboxes += $checkbox
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove the divider from the last entry for cleaner list termination.
|
||||||
|
if ($itemsPanel.Children.Count -gt 0) {
|
||||||
|
$lastItem = $itemsPanel.Children[$itemsPanel.Children.Count - 1]
|
||||||
|
if ($lastItem -is [System.Windows.Controls.Border]) {
|
||||||
|
$lastItem.BorderThickness = [System.Windows.Thickness]::new(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($entryCheckboxes.Count -eq 0) {
|
||||||
|
$emptyText = New-Object System.Windows.Controls.TextBlock
|
||||||
|
$emptyText.Text = 'No previously applied tweaks can be reverted'
|
||||||
|
$emptyText.Style = $revertWindow.FindResource('RevertEmptyTextStyle')
|
||||||
|
$itemsPanel.Children.Add($emptyText) | Out-Null
|
||||||
|
$selectAllBtn.IsEnabled = $false
|
||||||
|
$clearBtn.IsEnabled = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateState = {
|
||||||
|
$selectedCount = 0
|
||||||
|
foreach ($cb in $entryCheckboxes) {
|
||||||
|
if ($cb.IsEnabled -and $cb.IsChecked -eq $true) {
|
||||||
|
$selectedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$countText.Text = "$selectedCount settings selected"
|
||||||
|
$applyBtn.IsEnabled = ($selectedCount -gt 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($cb in $entryCheckboxes) {
|
||||||
|
$cb.Add_Checked($updateState)
|
||||||
|
$cb.Add_Unchecked($updateState)
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectAllBtn.Add_Click({
|
||||||
|
foreach ($cb in $entryCheckboxes) {
|
||||||
|
if ($cb.IsEnabled) {
|
||||||
|
$cb.IsChecked = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$clearBtn.Add_Click({
|
||||||
|
foreach ($cb in $entryCheckboxes) {
|
||||||
|
if ($cb.IsEnabled) {
|
||||||
|
$cb.IsChecked = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$cancelHandler = {
|
||||||
|
$revertWindow.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
$cancelBtn.Add_Click($cancelHandler)
|
||||||
|
|
||||||
|
$applyBtn.Add_Click({
|
||||||
|
$selected = @()
|
||||||
|
foreach ($cb in $entryCheckboxes) {
|
||||||
|
if ($cb.IsEnabled -and $cb.IsChecked -eq $true -and $cb.Tag) {
|
||||||
|
$selected += $cb.Tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$revertWindow.Tag = [PSCustomObject]@{
|
||||||
|
SelectedFeatureIds = $selected
|
||||||
|
RestartExplorer = ($restartExplorerCheckbox -and $restartExplorerCheckbox.IsChecked -eq $true)
|
||||||
|
}
|
||||||
|
$revertWindow.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
$revertWindow.ShowDialog() | Out-Null
|
||||||
|
|
||||||
|
if ($overlay -and -not $overlayWasAlreadyVisible) {
|
||||||
|
try {
|
||||||
|
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($revertWindow.Tag) {
|
||||||
|
return $revertWindow.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
return [PSCustomObject]@{
|
||||||
|
SelectedFeatureIds = @()
|
||||||
|
RestartExplorer = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
param (
|
param (
|
||||||
[switch]$CLI,
|
[switch]$CLI,
|
||||||
[switch]$Silent,
|
[switch]$Silent,
|
||||||
|
[switch]$Undo,
|
||||||
[switch]$Verbose,
|
[switch]$Verbose,
|
||||||
[switch]$Sysprep,
|
[switch]$Sysprep,
|
||||||
[string]$LogPath,
|
[string]$LogPath,
|
||||||
@@ -11,6 +12,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,
|
||||||
|
|||||||
17
Scripts/Helpers/GetUndoFeatureForParam.ps1
Normal file
17
Scripts/Helpers/GetUndoFeatureForParam.ps1
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Returns the feature metadata for a parameter when it supports undo; otherwise returns $null.
|
||||||
|
function GetUndoFeatureForParam {
|
||||||
|
param (
|
||||||
|
[string]$paramKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $script:Features -or -not $script:Features.ContainsKey($paramKey)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$feature = $script:Features[$paramKey]
|
||||||
|
if (-not ($feature.RegistryUndoKey -and ($feature.UndoText -or $feature.UndoAction))) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return $feature
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
101
Win11Debloat.ps1
101
Win11Debloat.ps1
@@ -4,6 +4,7 @@
|
|||||||
param (
|
param (
|
||||||
[switch]$CLI,
|
[switch]$CLI,
|
||||||
[switch]$Silent,
|
[switch]$Silent,
|
||||||
|
[switch]$Undo,
|
||||||
[switch]$Sysprep,
|
[switch]$Sysprep,
|
||||||
[string]$LogPath,
|
[string]$LogPath,
|
||||||
[string]$User,
|
[string]$User,
|
||||||
@@ -13,6 +14,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,
|
||||||
@@ -104,6 +106,7 @@ param (
|
|||||||
|
|
||||||
# Define script-level variables & paths
|
# Define script-level variables & paths
|
||||||
$script:Version = "2026.03.15"
|
$script:Version = "2026.03.15"
|
||||||
|
$script:FeaturesConfigVersion = "2.0"
|
||||||
$script:AppsListFilePath = "$PSScriptRoot/Config/Apps.json"
|
$script:AppsListFilePath = "$PSScriptRoot/Config/Apps.json"
|
||||||
$script:DefaultSettingsFilePath = "$PSScriptRoot/Config/DefaultSettings.json"
|
$script:DefaultSettingsFilePath = "$PSScriptRoot/Config/DefaultSettings.json"
|
||||||
$script:FeaturesFilePath = "$PSScriptRoot/Config/Features.json"
|
$script:FeaturesFilePath = "$PSScriptRoot/Config/Features.json"
|
||||||
@@ -117,11 +120,13 @@ $script:MainWindowSchema = "$PSScriptRoot/Schemas/MainWindow.xaml"
|
|||||||
$script:MessageBoxSchema = "$PSScriptRoot/Schemas/MessageBoxWindow.xaml"
|
$script:MessageBoxSchema = "$PSScriptRoot/Schemas/MessageBoxWindow.xaml"
|
||||||
$script:AboutWindowSchema = "$PSScriptRoot/Schemas/AboutWindow.xaml"
|
$script:AboutWindowSchema = "$PSScriptRoot/Schemas/AboutWindow.xaml"
|
||||||
$script:ApplyChangesWindowSchema = "$PSScriptRoot/Schemas/ApplyChangesWindow.xaml"
|
$script:ApplyChangesWindowSchema = "$PSScriptRoot/Schemas/ApplyChangesWindow.xaml"
|
||||||
|
$script:RevertSettingsWindowSchema = "$PSScriptRoot/Schemas/RevertSettingsWindow.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', 'Undo', '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
|
||||||
@@ -168,9 +173,23 @@ else {
|
|||||||
Start-Transcript -Path $script:DefaultLogPath -Append -IncludeInvocationHeader -Force | Out-Null
|
Start-Transcript -Path $script:DefaultLogPath -Append -IncludeInvocationHeader -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if script has all required files
|
# Check if script has all required files/directories.
|
||||||
if (-not ((Test-Path $script:DefaultSettingsFilePath) -and (Test-Path $script:AppsListFilePath) -and (Test-Path $script:RegfilesPath) -and (Test-Path $script:AssetsPath) -and (Test-Path $script:AppSelectionSchema) -and (Test-Path $script:ApplyChangesWindowSchema) -and (Test-Path $script:SharedStylesSchema) -and (Test-Path $script:BubbleHintSchema) -and (Test-Path $script:FeaturesFilePath))) {
|
$optionalPathVariables = @('SavedSettingsFilePath', 'CustomAppsListFilePath', 'DefaultLogPath')
|
||||||
Write-Error "Win11Debloat is unable to find required files, please ensure all script files are present"
|
$requiredPathVariables = @(Get-Variable -Scope Script | Where-Object {
|
||||||
|
$_.Name -match '(FilePath|Schema|ScriptPath|RegfilesPath|AssetsPath)$' -and ($optionalPathVariables -notcontains $_.Name)
|
||||||
|
} | Select-Object -ExpandProperty Name)
|
||||||
|
|
||||||
|
$missingRequiredPaths = @()
|
||||||
|
foreach ($variableName in $requiredPathVariables) {
|
||||||
|
$pathValue = Get-Variable -Name $variableName -Scope Script -ValueOnly
|
||||||
|
|
||||||
|
if ([String]::IsNullOrWhiteSpace($pathValue) -or -not (Test-Path $pathValue)) {
|
||||||
|
$missingRequiredPaths += "$variableName => $pathValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missingRequiredPaths.Count -gt 0) {
|
||||||
|
Write-Error "Win11Debloat is unable to find required files, please ensure all script files are present. Missing: $($missingRequiredPaths -join '; ')"
|
||||||
Write-Output ""
|
Write-Output ""
|
||||||
Write-Output "Press any key to exit..."
|
Write-Output "Press any key to exit..."
|
||||||
$null = [System.Console]::ReadKey()
|
$null = [System.Console]::ReadKey()
|
||||||
@@ -181,6 +200,15 @@ if (-not ((Test-Path $script:DefaultSettingsFilePath) -and (Test-Path $script:Ap
|
|||||||
$script:Features = @{}
|
$script:Features = @{}
|
||||||
try {
|
try {
|
||||||
$featuresData = Get-Content -Path $script:FeaturesFilePath -Raw | ConvertFrom-Json
|
$featuresData = Get-Content -Path $script:FeaturesFilePath -Raw | ConvertFrom-Json
|
||||||
|
|
||||||
|
if (-not $featuresData.Version -or $featuresData.Version -ne $script:FeaturesConfigVersion) {
|
||||||
|
Write-Error "Features.json version mismatch (expected $($script:FeaturesConfigVersion), found $($featuresData.Version))"
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "Press any key to exit..."
|
||||||
|
$null = [System.Console]::ReadKey()
|
||||||
|
Exit
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($feature in $featuresData.Features) {
|
foreach ($feature in $featuresData.Features) {
|
||||||
$script:Features[$feature.FeatureId] = $feature
|
$script:Features[$feature.FeatureId] = $feature
|
||||||
}
|
}
|
||||||
@@ -249,6 +277,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,7 +292,9 @@ 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-RevertSettingsModal.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"
|
||||||
. "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1"
|
. "$PSScriptRoot/Scripts/GUI/Show-AboutDialog.ps1"
|
||||||
@@ -275,9 +306,12 @@ 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/GetUndoFeatureForParam.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"
|
||||||
@@ -315,6 +349,27 @@ foreach ($Param in $script:ControlParams) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Guard: Undo mode requires at least one actionable and cannot be combined with deployment-targeted parameters
|
||||||
|
if ($script:Params.ContainsKey('Undo')) {
|
||||||
|
$deploymentTargetParams = @('Sysprep', 'User', 'AppRemovalTarget')
|
||||||
|
$selectedDeploymentParams = @($deploymentTargetParams | Where-Object { $script:Params.ContainsKey($_) })
|
||||||
|
|
||||||
|
if ($selectedDeploymentParams.Count -gt 0) {
|
||||||
|
Write-Error "The -Undo parameter cannot be combined with deployment target parameters: -$($selectedDeploymentParams -join ', -')."
|
||||||
|
AwaitKeyToExit
|
||||||
|
}
|
||||||
|
|
||||||
|
$loadsSettingsFromPreset = $RunDefaults -or $RunDefaultsLite -or $RunSavedSettings
|
||||||
|
$undoTargets = @($script:Params.Keys | Where-Object {
|
||||||
|
($script:ControlParams -notcontains $_) -and $_ -ne 'Apps' -and $_ -ne 'CreateRestorePoint'
|
||||||
|
})
|
||||||
|
|
||||||
|
if ($undoTargets.Count -eq 0 -and -not $loadsSettingsFromPreset) {
|
||||||
|
Write-Error "The -Undo parameter requires at least one setting/feature parameter to revert."
|
||||||
|
AwaitKeyToExit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Hide progress bars for app removal, as they block Win11Debloat's output
|
# Hide progress bars for app removal, as they block Win11Debloat's output
|
||||||
if (-not ($script:Params.ContainsKey("Verbose"))) {
|
if (-not ($script:Params.ContainsKey("Verbose"))) {
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
@@ -351,6 +406,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 +428,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 +441,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 {
|
||||||
@@ -395,7 +468,7 @@ if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSa
|
|||||||
Exit
|
Exit
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Warning "Unable to load WPF GUI (not supported in this environment), falling back to CLI mode"
|
Write-Warning "Something went wrong while loading the graphical interface, falling back to CLI mode: $_"
|
||||||
if (-not $Silent) {
|
if (-not $Silent) {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Press any key to continue..."
|
Write-Host "Press any key to continue..."
|
||||||
@@ -436,9 +509,17 @@ if (($controlParamsCount -eq $script:Params.Keys.Count) -or ($script:Params.Keys
|
|||||||
AwaitKeyToExit
|
AwaitKeyToExit
|
||||||
}
|
}
|
||||||
|
|
||||||
# Execute all selected/provided parameters using the consolidated function
|
try {
|
||||||
# (This also handles restore point creation if requested)
|
# Execute all selected/provided parameters using the consolidated function
|
||||||
ExecuteAllChanges
|
# (This also handles restore point creation if requested)
|
||||||
|
ExecuteAllChanges
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "An error occurred while applying changes: $_"
|
||||||
|
|
||||||
|
AwaitKeyToExit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
RestartExplorer
|
RestartExplorer
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user