Update add somme function and GUI tools#10
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds a new GUI-based GPO Policy Manager and updates existing functions in the PSGPOTools module. While it introduces significant new functionality, there are several critical bugs and maintainability issues that should be addressed before merging.
Key Changes:
- Added a Scope parameter to
Get-PSGPOPolicyfunction to filter policies by User or Machine scope - Enhanced
Initialize-PSGPOAdmxwith improved formatting and comments - Introduced a new
Show-GPOManagerfunction providing a WPF-based graphical interface for browsing and managing GPO policies
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $scopeItem.Tag = $scope | ||
|
|
||
| $policies = Get-PSGPOPolicy -Scope $scope | ||
| if ($Filter -and $Filter -ne '' -and $Filter -ne 'Rechercher une GPO...') { |
There was a problem hiding this comment.
In the Populate-TreeView function, the filter condition checks three different conditions for the same value which is redundant. The condition "$Filter -and $Filter -ne '' -and $Filter -ne 'Rechercher une GPO...'" can be simplified since the placeholder check already handles the empty string case. Consider simplifying to just check if the filter is meaningful.
| if ($Filter -and $Filter -ne '' -and $Filter -ne 'Rechercher une GPO...') { | |
| if (-not [string]::IsNullOrWhiteSpace($Filter) -and $Filter -ne 'Rechercher une GPO...') { |
| function Set-Theme { | ||
| param([bool]$dark) | ||
|
|
||
| $resources = $window.Resources | ||
|
|
||
| if ($dark) { | ||
| # Dark Theme - Improved colors | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3399FF') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0066CC') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00D4FF') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3F3F46') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#CCCCCC') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFD700') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#64B5F6') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#81C784') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $themeToggleButton.Content = '☀️ Mode Clair' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| } else { | ||
| # Light Theme | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#106EBE') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#005A9E') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00BCF2') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#E1E1E1') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#605E5C') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FDB900') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#107C10') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $themeToggleButton.Content = '🌙 Mode Sombre' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The nested Set-Theme function is defined inside Show-GPOManager but lacks any documentation. The function takes a boolean parameter but doesn't document what it does or what the parameter means. Adding inline documentation would improve code maintainability.
| function Set-Theme { | ||
| param([bool]$dark) | ||
|
|
||
| $resources = $window.Resources | ||
|
|
||
| if ($dark) { | ||
| # Dark Theme - Improved colors | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3399FF') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0066CC') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00D4FF') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3F3F46') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#CCCCCC') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFD700') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#64B5F6') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#81C784') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $themeToggleButton.Content = '☀️ Mode Clair' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| } else { | ||
| # Light Theme | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#106EBE') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#005A9E') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00BCF2') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#E1E1E1') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#605E5C') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FDB900') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#107C10') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $themeToggleButton.Content = '🌙 Mode Sombre' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| $themeToggleButton.Add_Click({ | ||
| $script:isDarkTheme = -not $script:isDarkTheme | ||
| Set-Theme -dark $script:isDarkTheme | ||
| }) | ||
|
|
||
| # Get context menu items from XAML | ||
| $enableMenuItem = $window.FindName("enableMenuItem") | ||
| $disableMenuItem = $window.FindName("disableMenuItem") | ||
| $createGpoMenuItem = $window.FindName("createGpoMenuItem") | ||
|
|
||
| # Add click handlers to menu items | ||
| if ($enableMenuItem) { | ||
| $enableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Enable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($disableMenuItem) { | ||
| $disableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Disable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($createGpoMenuItem) { | ||
| $createGpoMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Create GPO for: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| $displayNameTextBox = $window.FindName("displayNameTextBox") | ||
| $descriptionTextBox = $window.FindName("descriptionTextBox") | ||
| $scopeTextBox = $window.FindName("scopeTextBox") | ||
| $registryPathTextBox = $window.FindName("registryPathTextBox") | ||
| $registryKeyTextBox = $window.FindName("registryKeyTextBox") | ||
| $registryValuesGrid = $window.FindName("registryValuesGrid") | ||
| $searchTextBox = $window.FindName("searchTextBox") | ||
| $searchButton = $window.FindName("searchButton") | ||
| $breadcrumbTextBlock = $window.FindName("breadcrumbTextBlock") | ||
|
|
||
| $treeView.add_SelectedItemChanged({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -is [Windows.Controls.TreeViewItem]) { | ||
| $policy = $selectedItem.Tag | ||
|
|
||
| # Build breadcrumb path | ||
| $breadcrumbPath = "" | ||
| $currentItem = $selectedItem | ||
| $pathParts = @() | ||
|
|
||
| while ($currentItem -ne $null) { | ||
| if ($currentItem.Tag -is [GPOToolsPolicy]) { | ||
| $pathParts = @($currentItem.Tag.DisplayName) + $pathParts | ||
| } elseif ($currentItem.Tag -is [GPOToolsCategory]) { | ||
| $pathParts = @($currentItem.Tag.DisplayName) + $pathParts | ||
| } elseif ($currentItem.Tag -is [ScopePolicy]) { | ||
| $pathParts = @($currentItem.Tag.ToString()) + $pathParts | ||
| } | ||
| $currentItem = $currentItem.Parent | ||
| if ($currentItem -isnot [Windows.Controls.TreeViewItem]) { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| $breadcrumbPath = $pathParts -join ' > ' | ||
| if ($breadcrumbPath) { | ||
| $breadcrumbTextBlock.Text = "📍 $breadcrumbPath" | ||
| } else { | ||
| $breadcrumbTextBlock.Text = "Select a policy to see its path" | ||
| } | ||
|
|
||
| if ($policy -is [GPOToolsPolicy]) { | ||
| $displayNameTextBox.Text = $policy.DisplayName | ||
| $descriptionTextBox.Text = if ($policy.Description) { $policy.Description } else { "(Aucune description disponible)" } | ||
| $scopeTextBox.Text = $policy.Scope.ToString() | ||
|
|
||
| # Handle registry information with null checks | ||
| if ($policy.Registry) { | ||
| $registryPathTextBox.Text = if ($policy.Registry.Path) { $policy.Registry.Path } else { "(Aucun chemin de registre défini)" } | ||
| $registryKeyTextBox.Text = if ($policy.Registry.Key) { $policy.Registry.Key } else { "(Aucune clé de registre définie)" } | ||
|
|
||
| # Populate registry values in the DataGrid | ||
| if ($policy.Registry.Value -and $policy.Registry.Value.Keys.Count -gt 0) { | ||
| $registryValuesGrid.ItemsSource = @( | ||
| foreach ($key in $policy.Registry.Value.Keys) { | ||
| [PSCustomObject]@{ | ||
| Key = $key | ||
| Value = if ($policy.Registry.Value[$key]) { $policy.Registry.Value[$key] } else { "(Valeur non définie)" } | ||
| } | ||
| } | ||
| ) | ||
| } else { | ||
| $registryValuesGrid.ItemsSource = @( | ||
| [PSCustomObject]@{ | ||
| Key = "Information" | ||
| Value = "(Aucune valeur de registre disponible pour cette GPO)" | ||
| } | ||
| ) | ||
| } | ||
| } else { | ||
| $registryPathTextBox.Text = "(Cette GPO ne nécessite pas de configuration de registre)" | ||
| $registryKeyTextBox.Text = "(Aucune clé de registre)" | ||
| $registryValuesGrid.ItemsSource = @( | ||
| [PSCustomObject]@{ | ||
| Key = "Information" | ||
| Value = "(Cette policy ne stocke pas de données dans le registre)" | ||
| } | ||
| ) | ||
| } | ||
| } else { | ||
| $displayNameTextBox.Text = "" | ||
| $descriptionTextBox.Text = "" | ||
| $scopeTextBox.Text = "" | ||
| $registryPathTextBox.Text = "" | ||
| $registryKeyTextBox.Text = "" | ||
| $registryValuesGrid.ItemsSource = $null | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| function Populate-TreeView { | ||
| param( | ||
| [string]$Filter = '' | ||
| ) | ||
| $treeView.Items.Clear() | ||
| $scopes = [Enum]::GetValues([ScopePolicy]) | ||
| foreach ($scope in $scopes) { | ||
| $scopeItem = New-Object Windows.Controls.TreeViewItem | ||
|
|
||
| # Create StackPanel for icon + text | ||
| $scopePanel = New-Object Windows.Controls.StackPanel | ||
| $scopePanel.Orientation = 'Horizontal' | ||
|
|
||
| $scopeIcon = New-Object Windows.Controls.TextBlock | ||
| $scopeIcon.Text = '📋 ' | ||
| $scopeIcon.FontSize = 14 | ||
| $scopeIcon.Foreground = $window.Resources['IconScopeBrush'] | ||
|
|
||
| $scopeText = New-Object Windows.Controls.TextBlock | ||
| $scopeText.Text = $scope.ToString() | ||
| $scopeText.FontWeight = 'SemiBold' | ||
|
|
||
| $scopePanel.Children.Add($scopeIcon) | Out-Null | ||
| $scopePanel.Children.Add($scopeText) | Out-Null | ||
| $scopeItem.Header = $scopePanel | ||
| $scopeItem.Tag = $scope | ||
|
|
||
| $policies = Get-PSGPOPolicy -Scope $scope | ||
| if ($Filter -and $Filter -ne '' -and $Filter -ne 'Rechercher une GPO...') { | ||
| $policies = $policies | Where-Object { $_.DisplayName -like "*$Filter*" } | ||
| } | ||
|
|
||
| foreach ($policy in $policies) { | ||
| $category = $policy.Category | ||
| $parentCategory = $category.ParentCategory | ||
| $currentItem = $scopeItem | ||
|
|
||
| while ($null -ne $parentCategory) { | ||
| # Check if category already exists | ||
| $existingItem = $null | ||
| foreach ($item in $currentItem.Items) { | ||
| if ($item.Tag -and $item.Tag.DisplayName -eq $parentCategory.DisplayName) { | ||
| $existingItem = $item | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if (-not $existingItem) { | ||
| $newItem = New-Object Windows.Controls.TreeViewItem | ||
|
|
||
| # Create StackPanel for folder icon + text | ||
| $folderPanel = New-Object Windows.Controls.StackPanel | ||
| $folderPanel.Orientation = 'Horizontal' | ||
|
|
||
| $folderIcon = New-Object Windows.Controls.TextBlock | ||
| $folderIcon.Text = '📂 ' | ||
| $folderIcon.FontSize = 13 | ||
| $folderIcon.Foreground = $window.Resources['IconFolderBrush'] | ||
|
|
||
| $folderText = New-Object Windows.Controls.TextBlock | ||
| $folderText.Text = $parentCategory.DisplayName | ||
|
|
||
| $folderPanel.Children.Add($folderIcon) | Out-Null | ||
| $folderPanel.Children.Add($folderText) | Out-Null | ||
| $newItem.Header = $folderPanel | ||
| $newItem.Tag = $parentCategory | ||
|
|
||
| $currentItem.Items.Add($newItem) | Out-Null | ||
| $currentItem = $newItem | ||
| } else { | ||
| $currentItem = $existingItem | ||
| } | ||
| $parentCategory = $parentCategory.ParentCategory | ||
| } | ||
|
|
||
| $policyItem = New-Object Windows.Controls.TreeViewItem | ||
|
|
||
| # Create StackPanel for file icon + text | ||
| $policyPanel = New-Object Windows.Controls.StackPanel | ||
| $policyPanel.Orientation = 'Horizontal' | ||
|
|
||
| $policyIcon = New-Object Windows.Controls.TextBlock | ||
| $policyIcon.Text = '📄 ' | ||
| $policyIcon.FontSize = 13 | ||
| $policyIcon.Foreground = $window.Resources['IconFileBrush'] | ||
|
|
||
| $policyText = New-Object Windows.Controls.TextBlock | ||
| $policyText.Text = $policy.DisplayName | ||
|
|
||
| $policyPanel.Children.Add($policyIcon) | Out-Null | ||
| $policyPanel.Children.Add($policyText) | Out-Null | ||
| $policyItem.Header = $policyPanel | ||
| $policyItem.Tag = $policy | ||
|
|
||
| $currentItem.Items.Add($policyItem) | Out-Null | ||
| } | ||
|
|
||
| if ($scopeItem.Items.Count -gt 0) { | ||
| $treeView.Items.Add($scopeItem) | Out-Null | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The function Populate-TreeView and the theme toggle functionality access and modify variables from the outer scope without proper scoping. The click handlers and nested functions rely on closure over variables like $treeView, $window, etc. While PowerShell supports this, explicitly documenting or structuring this dependency would improve maintainability. Consider using a more structured approach with a class or explicitly passing required references.
| # Sequential processing to load ADMX and ADML files | ||
| [GPOToolsUtility]::InitiateAdmxAdml($Item, $UICulture) | ||
| } | ||
|
|
There was a problem hiding this comment.
The new Show-GPOManager function lacks any documentation. There is no comment-based help, no description of what the function does, its purpose, or usage examples. This is especially important for a complex GUI function that introduces significant new functionality. Consider adding proper PowerShell comment-based help with .SYNOPSIS, .DESCRIPTION, .EXAMPLE, etc.
| <# | |
| .SYNOPSIS | |
| Displays the PSGPOTools GPO Manager graphical user interface. | |
| .DESCRIPTION | |
| Show-GPOManager opens a WPF-based graphical user interface that allows you to | |
| browse, search, and inspect Group Policy settings and related information | |
| loaded by Initialize-PSGPOAdmx. The GUI uses the ADMX/ADML data parsed and | |
| cached by GPOToolsUtility and provides an interactive way to explore policies, | |
| categories, and supported platforms. | |
| This command requires a Windows environment with GUI capabilities because it | |
| relies on WPF assemblies (PresentationFramework, PresentationCore, WindowsBase). | |
| For accurate data, run Initialize-PSGPOAdmx prior to launching the manager so | |
| that ADMX/ADML definitions are loaded. | |
| .EXAMPLE | |
| PS C:\> Show-GPOManager | |
| Launches the GPO Manager UI using the ADMX/ADML data previously initialized | |
| with Initialize-PSGPOAdmx. | |
| .EXAMPLE | |
| PS C:\> Initialize-PSGPOAdmx -Path "C:\Windows\PolicyDefinitions" | |
| PS C:\> Show-GPOManager | |
| Initializes the ADMX/ADML definitions from the specified PolicyDefinitions | |
| directory and then opens the GPO Manager GUI to browse those settings. | |
| .NOTES | |
| The window is created using Windows Presentation Foundation (WPF). Ensure that | |
| you are running in a PowerShell session that supports WPF (e.g. PowerShell | |
| running on Windows with access to a desktop session). | |
| #> |
| function Populate-TreeView { | ||
| param( | ||
| [string]$Filter = '' | ||
| ) |
There was a problem hiding this comment.
The Populate-TreeView function lacks documentation explaining its purpose, the Filter parameter, and its behavior. This is a complex function with significant logic that would benefit from clear documentation.
| Content='🌙 Mode Sombre' | ||
| Height='36' | ||
| Width='140' | ||
| Style='{StaticResource ModernButton}' | ||
| Margin='0,0,12,0'/> | ||
| <TextBox Name='searchTextBox' | ||
| Width='320' | ||
| Height='36' | ||
| Style='{StaticResource ModernTextBox}' | ||
| VerticalContentAlignment='Center' | ||
| Margin='0,0,12,0' | ||
| Text='Rechercher une GPO...' | ||
| Foreground='#999999'/> | ||
| <Button Name='searchButton' | ||
| Content='🔍 Rechercher' | ||
| Height='36' | ||
| Width='130' | ||
| Style='{StaticResource ModernButton}'/> | ||
| </StackPanel> | ||
| </Grid> | ||
| </Border> | ||
|
|
||
| <!-- Main Content Area with Splitter --> | ||
| <Grid Grid.Row='1' Margin='0'> | ||
| <Grid.ColumnDefinitions> | ||
| <ColumnDefinition Width='2*' MinWidth='250'/> | ||
| <ColumnDefinition Width='Auto'/> | ||
| <ColumnDefinition Width='3*' MinWidth='300'/> | ||
| </Grid.ColumnDefinitions> | ||
|
|
||
| <!-- TreeView Panel --> | ||
| <Border Grid.Column='0' | ||
| Background='{DynamicResource BackgroundBrush}' | ||
| Margin='16,16,8,16' | ||
| CornerRadius='8' | ||
| BorderThickness='1' | ||
| BorderBrush='{DynamicResource BorderBrush}'> | ||
| <Border.Effect> | ||
| <DropShadowEffect BlurRadius='10' ShadowDepth='2' Opacity='0.1' Color='Black'/> | ||
| </Border.Effect> | ||
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height='Auto'/> | ||
| <RowDefinition Height='*'/> | ||
| </Grid.RowDefinitions> | ||
|
|
||
| <Border Grid.Row='0' | ||
| Name='treeHeaderBorder' | ||
| Background='{DynamicResource HeaderBackgroundBrush}' | ||
| Padding='16,12' | ||
| CornerRadius='8,8,0,0'> | ||
| <StackPanel> | ||
| <TextBlock Name='treeTitleTextBlock' | ||
| Text='📁 Policy Structure' | ||
| FontSize='15' | ||
| FontWeight='SemiBold' | ||
| Foreground='{DynamicResource HeaderTextBrush}'/> | ||
| <TextBlock Name='breadcrumbTextBlock' | ||
| Text='Select a policy to see its path' | ||
| FontSize='11' | ||
| Foreground='{DynamicResource SecondaryTextBrush}' | ||
| Margin='0,4,0,0' | ||
| TextTrimming='CharacterEllipsis'/> | ||
| </StackPanel> | ||
| </Border> | ||
|
|
||
| <TreeView Name='treeView' Grid.Row='1' Margin='8'> | ||
| <TreeView.ContextMenu> | ||
| <ContextMenu Name='treeViewContextMenu'> | ||
| <MenuItem Name='enableMenuItem' Header='✅ Enable this parameter'/> | ||
| <MenuItem Name='disableMenuItem' Header='❌ Disable this parameter'/> | ||
| <Separator/> | ||
| <MenuItem Name='createGpoMenuItem' Header='➕ Create a GPO'/> | ||
| </ContextMenu> | ||
| </TreeView.ContextMenu> | ||
| </TreeView> | ||
| </Grid> | ||
| </Border> | ||
|
|
||
| <!-- Splitter --> | ||
| <GridSplitter Grid.Column='1' | ||
| Width='6' | ||
| HorizontalAlignment='Center' | ||
| VerticalAlignment='Stretch' | ||
| Background='Transparent' | ||
| ShowsPreview='True'/> | ||
|
|
||
| <!-- Details Panel --> | ||
| <Border Grid.Column='2' | ||
| Background='{DynamicResource BackgroundBrush}' | ||
| Margin='8,16,16,16' | ||
| CornerRadius='8' | ||
| BorderThickness='1' | ||
| BorderBrush='{DynamicResource BorderBrush}'> | ||
| <Border.Effect> | ||
| <DropShadowEffect BlurRadius='10' ShadowDepth='2' Opacity='0.1' Color='Black'/> | ||
| </Border.Effect> | ||
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height='Auto'/> | ||
| <RowDefinition Height='*'/> | ||
| </Grid.RowDefinitions> | ||
|
|
||
| <Border Grid.Row='0' | ||
| Name='detailsHeaderBorder' | ||
| Background='{DynamicResource HeaderBackgroundBrush}' | ||
| Padding='16,12' | ||
| CornerRadius='8,8,0,0'> | ||
| <TextBlock Name='detailsTitleTextBlock' | ||
| Text='ℹ️ Policy Details' | ||
| FontSize='15' | ||
| FontWeight='SemiBold' | ||
| Foreground='{DynamicResource HeaderTextBrush}'/> | ||
| </Border> | ||
|
|
||
| <ScrollViewer Grid.Row='1' | ||
| VerticalScrollBarVisibility='Auto' | ||
| Padding='16'> | ||
| <StackPanel Name='detailsPanel'> | ||
| <TextBlock Text='Display Name' Style='{StaticResource LabelStyle}'/> | ||
| <TextBox Name='displayNameTextBox' Style='{StaticResource ReadOnlyTextBox}'/> | ||
|
|
||
| <TextBlock Text='Description' Style='{StaticResource LabelStyle}' Margin='0,16,0,4'/> | ||
| <TextBox Name='descriptionTextBox' | ||
| Style='{StaticResource ReadOnlyTextBox}' | ||
| MinHeight='60' | ||
| MaxHeight='120' | ||
| VerticalScrollBarVisibility='Auto'/> | ||
|
|
||
| <TextBlock Text='Scope' Style='{StaticResource LabelStyle}' Margin='0,16,0,4'/> | ||
| <TextBox Name='scopeTextBox' Style='{StaticResource ReadOnlyTextBox}'/> | ||
|
|
||
| <TextBlock Text='Registry Path' Style='{StaticResource LabelStyle}' Margin='0,16,0,4'/> | ||
| <TextBox Name='registryPathTextBox' Style='{StaticResource ReadOnlyTextBox}'/> | ||
|
|
||
| <TextBlock Text='Registry Key' Style='{StaticResource LabelStyle}' Margin='0,16,0,4'/> | ||
| <TextBox Name='registryKeyTextBox' Style='{StaticResource ReadOnlyTextBox}'/> | ||
|
|
||
| <TextBlock Text='Registry Values' Style='{StaticResource LabelStyle}' Margin='0,16,0,4'/> | ||
| <DataGrid Name='registryValuesGrid' | ||
| AutoGenerateColumns='False' | ||
| IsReadOnly='True' | ||
| MaxHeight='200' | ||
| Margin='0,0,0,16'> | ||
| <DataGrid.Columns> | ||
| <DataGridTextColumn Header='Name' Binding='{Binding Key}' Width='*'/> | ||
| <DataGridTextColumn Header='Value' Binding='{Binding Value}' Width='2*'/> | ||
| </DataGrid.Columns> | ||
| </DataGrid> | ||
| </StackPanel> | ||
| </ScrollViewer> | ||
| </Grid> | ||
| </Border> | ||
| </Grid> | ||
| </Grid> | ||
| </Window> | ||
| "@ | ||
|
|
||
| $xmlDocument = New-Object System.Xml.XmlDocument | ||
| $xmlDocument.LoadXml($xaml) | ||
| $reader = New-Object System.Xml.XmlNodeReader $xmlDocument | ||
| $window = [Windows.Markup.XamlReader]::Load($reader) | ||
|
|
||
| $treeView = $window.FindName("treeView") | ||
| $themeToggleButton = $window.FindName("themeToggleButton") | ||
| $treeTitleTextBlock = $window.FindName("treeTitleTextBlock") | ||
| $detailsTitleTextBlock = $window.FindName("detailsTitleTextBlock") | ||
|
|
||
| # Theme management | ||
| $isDarkTheme = $false | ||
|
|
||
| function Set-Theme { | ||
| param([bool]$dark) | ||
|
|
||
| $resources = $window.Resources | ||
|
|
||
| if ($dark) { | ||
| # Dark Theme - Improved colors | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3399FF') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0066CC') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00D4FF') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#3F3F46') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#CCCCCC') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0086F0') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#2D2D30') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFD700') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#64B5F6') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#81C784') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1E1E1E') | ||
| $themeToggleButton.Content = '☀️ Mode Clair' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#252526') | ||
| } | ||
| } else { | ||
| # Light Theme | ||
| $resources['PrimaryBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['PrimaryHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#106EBE') | ||
| $resources['PrimaryPressedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#005A9E') | ||
| $resources['AccentBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#00BCF2') | ||
| $resources['BackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['SecondaryBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $resources['BorderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#E1E1E1') | ||
| $resources['TextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
| $resources['SecondaryTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#605E5C') | ||
| $resources['TreeViewSelectedBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['TreeViewSelectedTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FFFFFF') | ||
| $resources['TreeViewHoverBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['IconFolderBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#FDB900') | ||
| $resources['IconFileBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#0078D4') | ||
| $resources['IconScopeBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#107C10') | ||
| $resources['HeaderBackgroundBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| $resources['HeaderTextBrush'] = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#1F1F1F') | ||
|
|
||
| $window.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F5F5F5') | ||
| $themeToggleButton.Content = '🌙 Mode Sombre' | ||
|
|
||
| # Update header backgrounds | ||
| $treeHeaderBorder = $window.FindName("treeHeaderBorder") | ||
| $detailsHeaderBorder = $window.FindName("detailsHeaderBorder") | ||
| if ($treeHeaderBorder) { | ||
| $treeHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| if ($detailsHeaderBorder) { | ||
| $detailsHeaderBorder.Background = [System.Windows.Media.BrushConverter]::new().ConvertFrom('#F3F2F1') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| $themeToggleButton.Add_Click({ | ||
| $script:isDarkTheme = -not $script:isDarkTheme | ||
| Set-Theme -dark $script:isDarkTheme | ||
| }) | ||
|
|
||
| # Get context menu items from XAML | ||
| $enableMenuItem = $window.FindName("enableMenuItem") | ||
| $disableMenuItem = $window.FindName("disableMenuItem") | ||
| $createGpoMenuItem = $window.FindName("createGpoMenuItem") | ||
|
|
||
| # Add click handlers to menu items | ||
| if ($enableMenuItem) { | ||
| $enableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Enable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($disableMenuItem) { | ||
| $disableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Disable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($createGpoMenuItem) { | ||
| $createGpoMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Create GPO for: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| $displayNameTextBox = $window.FindName("displayNameTextBox") | ||
| $descriptionTextBox = $window.FindName("descriptionTextBox") | ||
| $scopeTextBox = $window.FindName("scopeTextBox") | ||
| $registryPathTextBox = $window.FindName("registryPathTextBox") | ||
| $registryKeyTextBox = $window.FindName("registryKeyTextBox") | ||
| $registryValuesGrid = $window.FindName("registryValuesGrid") | ||
| $searchTextBox = $window.FindName("searchTextBox") | ||
| $searchButton = $window.FindName("searchButton") | ||
| $breadcrumbTextBlock = $window.FindName("breadcrumbTextBlock") | ||
|
|
||
| $treeView.add_SelectedItemChanged({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -is [Windows.Controls.TreeViewItem]) { | ||
| $policy = $selectedItem.Tag | ||
|
|
||
| # Build breadcrumb path | ||
| $breadcrumbPath = "" | ||
| $currentItem = $selectedItem | ||
| $pathParts = @() | ||
|
|
||
| while ($currentItem -ne $null) { | ||
| if ($currentItem.Tag -is [GPOToolsPolicy]) { | ||
| $pathParts = @($currentItem.Tag.DisplayName) + $pathParts | ||
| } elseif ($currentItem.Tag -is [GPOToolsCategory]) { | ||
| $pathParts = @($currentItem.Tag.DisplayName) + $pathParts | ||
| } elseif ($currentItem.Tag -is [ScopePolicy]) { | ||
| $pathParts = @($currentItem.Tag.ToString()) + $pathParts | ||
| } | ||
| $currentItem = $currentItem.Parent | ||
| if ($currentItem -isnot [Windows.Controls.TreeViewItem]) { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| $breadcrumbPath = $pathParts -join ' > ' | ||
| if ($breadcrumbPath) { | ||
| $breadcrumbTextBlock.Text = "📍 $breadcrumbPath" | ||
| } else { | ||
| $breadcrumbTextBlock.Text = "Select a policy to see its path" | ||
| } | ||
|
|
||
| if ($policy -is [GPOToolsPolicy]) { | ||
| $displayNameTextBox.Text = $policy.DisplayName | ||
| $descriptionTextBox.Text = if ($policy.Description) { $policy.Description } else { "(Aucune description disponible)" } | ||
| $scopeTextBox.Text = $policy.Scope.ToString() | ||
|
|
||
| # Handle registry information with null checks | ||
| if ($policy.Registry) { | ||
| $registryPathTextBox.Text = if ($policy.Registry.Path) { $policy.Registry.Path } else { "(Aucun chemin de registre défini)" } | ||
| $registryKeyTextBox.Text = if ($policy.Registry.Key) { $policy.Registry.Key } else { "(Aucune clé de registre définie)" } | ||
|
|
||
| # Populate registry values in the DataGrid | ||
| if ($policy.Registry.Value -and $policy.Registry.Value.Keys.Count -gt 0) { | ||
| $registryValuesGrid.ItemsSource = @( | ||
| foreach ($key in $policy.Registry.Value.Keys) { | ||
| [PSCustomObject]@{ | ||
| Key = $key | ||
| Value = if ($policy.Registry.Value[$key]) { $policy.Registry.Value[$key] } else { "(Valeur non définie)" } | ||
| } | ||
| } | ||
| ) | ||
| } else { | ||
| $registryValuesGrid.ItemsSource = @( | ||
| [PSCustomObject]@{ | ||
| Key = "Information" | ||
| Value = "(Aucune valeur de registre disponible pour cette GPO)" | ||
| } | ||
| ) | ||
| } | ||
| } else { | ||
| $registryPathTextBox.Text = "(Cette GPO ne nécessite pas de configuration de registre)" | ||
| $registryKeyTextBox.Text = "(Aucune clé de registre)" | ||
| $registryValuesGrid.ItemsSource = @( | ||
| [PSCustomObject]@{ | ||
| Key = "Information" | ||
| Value = "(Cette policy ne stocke pas de données dans le registre)" |
There was a problem hiding this comment.
The comment contains French text mixed with English. Text strings in lines 927, 938, 941, and throughout the GUI use French ("Mode Sombre", "Mode Clair", "Rechercher une GPO...", etc.), including error messages and placeholders (lines 1251, 1256-1258, 1265, 1273, 1278-1283). For consistency and maintainability, the codebase should use a single language consistently, or implement proper localization/internationalization support.
| while ($null -ne $parentCategory) { | ||
| # Check if category already exists | ||
| $existingItem = $null | ||
| foreach ($item in $currentItem.Items) { | ||
| if ($item.Tag -and $item.Tag.DisplayName -eq $parentCategory.DisplayName) { | ||
| $existingItem = $item | ||
| break | ||
| } | ||
| } | ||
|
|
||
| if (-not $existingItem) { | ||
| $newItem = New-Object Windows.Controls.TreeViewItem | ||
|
|
||
| # Create StackPanel for folder icon + text | ||
| $folderPanel = New-Object Windows.Controls.StackPanel | ||
| $folderPanel.Orientation = 'Horizontal' | ||
|
|
||
| $folderIcon = New-Object Windows.Controls.TextBlock | ||
| $folderIcon.Text = '📂 ' | ||
| $folderIcon.FontSize = 13 | ||
| $folderIcon.Foreground = $window.Resources['IconFolderBrush'] | ||
|
|
||
| $folderText = New-Object Windows.Controls.TextBlock | ||
| $folderText.Text = $parentCategory.DisplayName | ||
|
|
||
| $folderPanel.Children.Add($folderIcon) | Out-Null | ||
| $folderPanel.Children.Add($folderText) | Out-Null | ||
| $newItem.Header = $folderPanel | ||
| $newItem.Tag = $parentCategory | ||
|
|
||
| $currentItem.Items.Add($newItem) | Out-Null | ||
| $currentItem = $newItem | ||
| } else { | ||
| $currentItem = $existingItem | ||
| } | ||
| $parentCategory = $parentCategory.ParentCategory | ||
| } |
There was a problem hiding this comment.
The nested loops iterate through parent categories and tree items to build hierarchical structure. This could result in poor performance with a large number of policies or deep category hierarchies (O(n*m) complexity). Consider using a dictionary/hashtable to cache category items by their DisplayName for faster lookups instead of iterating through all items each time.
| if ($enableMenuItem) { | ||
| $enableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Enable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($disableMenuItem) { | ||
| $disableMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Disable: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| if ($createGpoMenuItem) { | ||
| $createGpoMenuItem.Add_Click({ | ||
| $selectedItem = $treeView.SelectedItem | ||
| if ($selectedItem -and $selectedItem.Tag -is [GPOToolsPolicy]) { | ||
| Write-Host "Create GPO for: $($selectedItem.Tag.DisplayName)" | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
The menu item click handlers only output to Write-Host without implementing any actual functionality. These handlers appear to be placeholder implementations that don't perform the actions their names suggest (enabling/disabling parameters, creating GPOs). This could confuse users who expect these menu items to work.
| try { | ||
| $window.Icon = [System.Windows.Media.Imaging.BitmapImage]::new([Uri]::new("pack://application:,,,/icon.ico")) | ||
| } catch { | ||
| # Icon not found, continue without it |
There was a problem hiding this comment.
The hardcoded icon path "pack://application:,,,/icon.ico" will likely fail in most installations since no icon.ico file is included in the module. The try-catch silently ignores the error, which is good for graceful degradation, but this hardcoded path should either be removed, made configurable, or point to an actual bundled resource.
| try { | |
| $window.Icon = [System.Windows.Media.Imaging.BitmapImage]::new([Uri]::new("pack://application:,,,/icon.ico")) | |
| } catch { | |
| # Icon not found, continue without it | |
| $iconPath = Join-Path -Path $PSScriptRoot -ChildPath 'icon.ico' | |
| if (Test-Path -LiteralPath $iconPath) { | |
| try { | |
| $iconUri = [System.Uri]::new((Resolve-Path -LiteralPath $iconPath)) | |
| $window.Icon = [System.Windows.Media.Imaging.BitmapImage]::new($iconUri) | |
| } catch { | |
| # Icon failed to load, continue without it | |
| } |
| $detailsTitleTextBlock = $window.FindName("detailsTitleTextBlock") | ||
|
|
||
| # Theme management | ||
| $isDarkTheme = $false |
There was a problem hiding this comment.
The function uses a script-scoped variable $script:isDarkTheme but initializes a local variable $isDarkTheme on line 1096. This inconsistency could cause issues - the Add_Click handler references $script:isDarkTheme while the initial assignment is to a local variable. The initial assignment should be $script:isDarkTheme = $false to match the reference in the click handler.
| $isDarkTheme = $false | |
| $script:isDarkTheme = $false |
Requirements
Description of the Change
[ We must be able to understand the design of your change from this description. If we cannot get a good idea of what the code will be doing from the description here, the pull request may be closed at the maintainers' discretion. Keep in mind that the maintainer reviewing this PR may not be familiar with or have worked with the code here recently, so please walk us through the concepts. ]
Testing
[ Please describe the testing you have performed ]
Associated/Resolved Issues
[ Enter any applicable issues here ]