<# .SYNOPSIS Initializes the user interface for the BuildFFUVM_UI application. .DESCRIPTION This script module contains functions responsible for initializing the WPF user interface. It handles several key tasks: - Caching references to all UI controls for efficient access. - Populating UI elements like combo boxes with data (e.g., Hyper-V switches). - Setting default values for all controls based on configuration or predefined settings. - Dynamically creating and configuring complex UI components, such as sortable/selectable GridView columns and feature selection grids. This module is critical for setting up the initial state of the application window when it first loads. #> function Initialize-FluentTheme { param( [Parameter(Mandatory = $true)] [System.Windows.Window]$Window, [Parameter(Mandatory = $false)] [string]$ThemeMode = "System", [Parameter(Mandatory = $false)] [PSCustomObject]$State ) # Check if the current .NET runtime supports Window.ThemeMode (requires .NET 9+ / PowerShell 7.5+) $themeModeProperty = [System.Windows.Window].GetProperty("ThemeMode") if ($null -eq $themeModeProperty) { WriteLog "Fluent theme not available. Window.ThemeMode requires PowerShell 7.5+ (.NET 9+). Using default Aero2 theme." if ($null -ne $State) { $State.Flags.isFluentSupported = $false } # Still create tooltip styles for non-Fluent mode so Tag-to-ToolTip binding works $controlTypes = @( [System.Windows.Controls.TextBox], [System.Windows.Controls.TextBlock], [System.Windows.Controls.CheckBox] ) foreach ($controlType in $controlTypes) { $newStyle = New-Object System.Windows.Style($controlType) $toolTipBinding = New-Object System.Windows.Data.Binding("Tag") $toolTipBinding.RelativeSource = [System.Windows.Data.RelativeSource]::new([System.Windows.Data.RelativeSourceMode]::Self) $toolTipSetter = New-Object System.Windows.Setter([System.Windows.FrameworkElement]::ToolTipProperty, $toolTipBinding) $newStyle.Setters.Add($toolTipSetter) if ($Window.Resources.Contains($controlType)) { $Window.Resources.Remove($controlType) } $Window.Resources.Add($controlType, $newStyle) } WriteLog "Tooltip styles created for non-Fluent mode." return } # Mark Fluent as supported in state if ($null -ne $State) { $State.Flags.isFluentSupported = $true } # Resolve the ThemeMode enum value using reflection to avoid compile-time experimental attribute issues $themeModeType = [System.Windows.Window].GetProperty("ThemeMode").PropertyType $themeModeValue = $null switch ($ThemeMode) { "Light" { $themeModeValue = $themeModeType::Light } "Dark" { $themeModeValue = $themeModeType::Dark } "System" { $themeModeValue = $themeModeType::System } default { $themeModeValue = $themeModeType::System } } # Apply the Fluent theme mode to the window $themeModeProperty.SetValue($Window, $themeModeValue) WriteLog "Applied Fluent theme: $ThemeMode" # Re-create implicit tooltip styles with BasedOn pointing to the Fluent base style # This preserves the Tag-to-ToolTip binding while inheriting Fluent visual styling $controlTypes = @( [System.Windows.Controls.TextBox], [System.Windows.Controls.TextBlock], [System.Windows.Controls.CheckBox] ) foreach ($controlType in $controlTypes) { # Get the Fluent base style that was loaded by ThemeMode $fluentBaseStyle = $Window.TryFindResource($controlType) # Create a new implicit style with ToolTip binding $newStyle = New-Object System.Windows.Style($controlType) if ($null -ne $fluentBaseStyle) { $newStyle.BasedOn = $fluentBaseStyle } # Add the ToolTip setter that binds to the Tag property $toolTipBinding = New-Object System.Windows.Data.Binding("Tag") $toolTipBinding.RelativeSource = [System.Windows.Data.RelativeSource]::new([System.Windows.Data.RelativeSourceMode]::Self) $toolTipSetter = New-Object System.Windows.Setter([System.Windows.FrameworkElement]::ToolTipProperty, $toolTipBinding) $newStyle.Setters.Add($toolTipSetter) # Remove any existing implicit style for this type before adding the new one if ($Window.Resources.Contains($controlType)) { $Window.Resources.Remove($controlType) } $Window.Resources.Add($controlType, $newStyle) } WriteLog "Tooltip styles updated with Fluent base styles." } function Initialize-UIControls { param([PSCustomObject]$State) WriteLog "Initializing UI control references..." $window = $State.Window # Find all controls ONCE and store them in the state object $State.Controls.cmbWindowsRelease = $window.FindName('cmbWindowsRelease') $State.Controls.cmbWindowsVersion = $window.FindName('cmbWindowsVersion') $State.Controls.txtISOPath = $window.FindName('txtISOPath') $State.Controls.rbDownloadESD = $window.FindName('rbDownloadESD') $State.Controls.rbProvideISO = $window.FindName('rbProvideISO') $State.Controls.isoPathPanel = $window.FindName('isoPathPanel') $State.Controls.btnBrowseISO = $window.FindName('btnBrowseISO') $State.Controls.cmbWindowsArch = $window.FindName('cmbWindowsArch') $State.Controls.cmbWindowsLang = $window.FindName('cmbWindowsLang') $State.Controls.WindowsLangStackPanel = $window.FindName('WindowsLangStackPanel') $State.Controls.cmbWindowsSKU = $window.FindName('cmbWindowsSKU') $State.Controls.cmbMediaType = $window.FindName('cmbMediaType') $State.Controls.MediaTypeStackPanel = $window.FindName('MediaTypeStackPanel') $State.Controls.featuresPanel = $window.FindName('stackFeaturesContainer') $State.Controls.chkDownloadDrivers = $window.FindName('chkDownloadDrivers') $State.Controls.cmbMake = $window.FindName('cmbMake') $State.Controls.spMakeSection = $window.FindName('spMakeSection') $State.Controls.btnGetModels = $window.FindName('btnGetModels') $State.Controls.spModelFilterSection = $window.FindName('spModelFilterSection') $State.Controls.txtModelFilter = $window.FindName('txtModelFilter') $State.Controls.lstDriverModels = $window.FindName('lstDriverModels') $State.Controls.spDriverActionButtons = $window.FindName('spDriverActionButtons') $State.Controls.btnSaveDriversJson = $window.FindName('btnSaveDriversJson') $State.Controls.btnImportDriversJson = $window.FindName('btnImportDriversJson') $State.Controls.btnDownloadSelectedDrivers = $window.FindName('btnDownloadSelectedDrivers') $State.Controls.btnClearDriverList = $window.FindName('btnClearDriverList') $State.Controls.chkInstallOffice = $window.FindName('chkInstallOffice') $State.Controls.chkInstallApps = $window.FindName('chkInstallApps') $State.Controls.OfficePathStackPanel = $window.FindName('OfficePathStackPanel') $State.Controls.OfficePathGrid = $window.FindName('OfficePathGrid') $State.Controls.CopyOfficeConfigXMLStackPanel = $window.FindName('CopyOfficeConfigXMLStackPanel') $State.Controls.OfficeConfigurationXMLFileStackPanel = $window.FindName('OfficeConfigurationXMLFileStackPanel') $State.Controls.OfficeConfigurationXMLFileGrid = $window.FindName('OfficeConfigurationXMLFileGrid') $State.Controls.chkCopyOfficeConfigXML = $window.FindName('chkCopyOfficeConfigXML') $State.Controls.chkLatestCU = $window.FindName('chkUpdateLatestCU') $State.Controls.chkPreviewCU = $window.FindName('chkUpdatePreviewCU') $State.Controls.btnCheckUSBDrives = $window.FindName('btnCheckUSBDrives') $State.Controls.lstUSBDrives = $window.FindName('lstUSBDrives') $State.Controls.chkBuildUSBDriveEnable = $window.FindName('chkBuildUSBDriveEnable') $State.Controls.usbSection = $window.FindName('usbDriveSection') $State.Controls.chkSelectSpecificUSBDrives = $window.FindName('chkSelectSpecificUSBDrives') $State.Controls.usbSelectionPanel = $window.FindName('usbDriveSelectionPanel') $State.Controls.chkAllowExternalHardDiskMedia = $window.FindName('chkAllowExternalHardDiskMedia') $State.Controls.chkPromptExternalHardDiskMedia = $window.FindName('chkPromptExternalHardDiskMedia') $State.Controls.chkCopyAdditionalFFUFiles = $window.FindName('chkCopyAdditionalFFUFiles') $State.Controls.additionalFFUPanel = $window.FindName('additionalFFUPanel') $State.Controls.lstAdditionalFFUs = $window.FindName('lstAdditionalFFUs') $State.Controls.btnRefreshAdditionalFFUs = $window.FindName('btnRefreshAdditionalFFUs') $State.Controls.chkInstallWingetApps = $window.FindName('chkInstallWingetApps') $State.Controls.wingetPanel = $window.FindName('wingetPanel') $State.Controls.btnCheckWingetModule = $window.FindName('btnCheckWingetModule') $State.Controls.txtWingetVersion = $window.FindName('txtWingetVersion') $State.Controls.txtWingetModuleVersion = $window.FindName('txtWingetModuleVersion') $State.Controls.applicationPathPanel = $window.FindName('applicationPathPanel') $State.Controls.appListJsonPathPanel = $window.FindName('appListJsonPathPanel') $State.Controls.userAppListPathPanel = $window.FindName('userAppListPathPanel') $State.Controls.btnBrowseApplicationPath = $window.FindName('btnBrowseApplicationPath') $State.Controls.btnBrowseAppListJsonPath = $window.FindName('btnBrowseAppListJsonPath') $State.Controls.btnBrowseUserAppListPath = $window.FindName('btnBrowseUserAppListPath') $State.Controls.chkBringYourOwnApps = $window.FindName('chkBringYourOwnApps') $State.Controls.byoApplicationPanel = $window.FindName('byoApplicationPanel') $State.Controls.wingetSearchPanel = $window.FindName('wingetSearchPanel') $State.Controls.txtWingetSearch = $window.FindName('txtWingetSearch') $State.Controls.btnWingetSearch = $window.FindName('btnWingetSearch') $State.Controls.lstWingetResults = $window.FindName('lstWingetResults') $State.Controls.btnSaveWingetList = $window.FindName('btnSaveWingetList') $State.Controls.btnImportWingetList = $window.FindName('btnImportWingetList') $State.Controls.btnClearWingetList = $window.FindName('btnClearWingetList') $State.Controls.btnDownloadSelected = $window.FindName('btnDownloadSelected') $State.Controls.btnBrowseAppSource = $window.FindName('btnBrowseAppSource') $State.Controls.btnBrowseFFUDevPath = $window.FindName('btnBrowseFFUDevPath') $State.Controls.btnBrowseFFUCaptureLocation = $window.FindName('btnBrowseFFUCaptureLocation') $State.Controls.btnBrowseOfficePath = $window.FindName('btnBrowseOfficePath') $State.Controls.btnBrowseDriversFolder = $window.FindName('btnBrowseDriversFolder') $State.Controls.btnBrowsePEDriversFolder = $window.FindName('btnBrowsePEDriversFolder') $State.Controls.txtAppName = $window.FindName('txtAppName') $State.Controls.txtAppCommandLine = $window.FindName('txtAppCommandLine') $State.Controls.txtAppArguments = $window.FindName('txtAppArguments') $State.Controls.txtAppSource = $window.FindName('txtAppSource') $State.Controls.txtAppAdditionalExitCodes = $window.FindName('txtAppAdditionalExitCodes') $State.Controls.chkIgnoreExitCodes = $window.FindName('chkIgnoreExitCodes') $State.Controls.btnAddApplication = $window.FindName('btnAddApplication') $State.Controls.btnSaveBYOApplications = $window.FindName('btnSaveBYOApplications') $State.Controls.btnLoadBYOApplications = $window.FindName('btnLoadBYOApplications') $State.Controls.btnEditApplication = $window.FindName('btnEditApplication') $State.Controls.btnClearBYOApplications = $window.FindName('btnClearBYOApplications') $State.Controls.btnRemoveSelectedBYOApps = $window.FindName('btnRemoveSelectedBYOApps') $State.Controls.btnCopyBYOApps = $window.FindName('btnCopyBYOApps') $State.Controls.lstApplications = $window.FindName('lstApplications') $State.Controls.btnMoveTop = $window.FindName('btnMoveTop') $State.Controls.btnMoveUp = $window.FindName('btnMoveUp') $State.Controls.btnMoveDown = $window.FindName('btnMoveDown') $State.Controls.btnMoveBottom = $window.FindName('btnMoveBottom') $State.Controls.txtStatus = $window.FindName('txtStatus') $State.Controls.pbOverallProgress = $window.FindName('progressBar') $State.Controls.txtOverallStatus = $window.FindName('txtStatus') $State.Controls.chkEnableVMNetworking = $window.FindName('chkEnableVMNetworking') $State.Controls.spVMNetworkingSettings = $window.FindName('spVMNetworkingSettings') $State.Controls.cmbVMSwitchName = $window.FindName('cmbVMSwitchName') $State.Controls.txtCustomVMSwitchName = $window.FindName('txtCustomVMSwitchName') $State.Controls.txtFFUDevPath = $window.FindName('txtFFUDevPath') $State.Controls.txtCustomFFUNameTemplate = $window.FindName('txtCustomFFUNameTemplate') $State.Controls.txtFFUCaptureLocation = $window.FindName('txtFFUCaptureLocation') $State.Controls.txtThreads = $window.FindName('txtThreads') $State.Controls.cmbBitsPriority = $window.FindName('cmbBitsPriority') $State.Controls.txtMaxUSBDrives = $window.FindName('txtMaxUSBDrives') $State.Controls.chkCompactOS = $window.FindName('chkCompactOS') $State.Controls.chkOptimize = $window.FindName('chkOptimize') $State.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching') $State.Controls.chkCreateDeploymentMedia = $window.FindName('chkCreateDeploymentMedia') $State.Controls.chkInjectUnattend = $window.FindName('chkInjectUnattend') $State.Controls.rbDeviceNamingNone = $window.FindName('rbDeviceNamingNone') $State.Controls.rbDeviceNamingTemplate = $window.FindName('rbDeviceNamingTemplate') $State.Controls.rbDeviceNamingPrefixes = $window.FindName('rbDeviceNamingPrefixes') $State.Controls.deviceNameTemplatePanel = $window.FindName('deviceNameTemplatePanel') $State.Controls.deviceNamePrefixesPanel = $window.FindName('deviceNamePrefixesPanel') $State.Controls.txtDeviceNameTemplate = $window.FindName('txtDeviceNameTemplate') $State.Controls.txtDeviceNamePrefixesPath = $window.FindName('txtDeviceNamePrefixesPath') $State.Controls.btnBrowseDeviceNamePrefixesPath = $window.FindName('btnBrowseDeviceNamePrefixesPath') $State.Controls.txtDeviceNamePrefixes = $window.FindName('txtDeviceNamePrefixes') $State.Controls.btnSaveDeviceNamePrefixes = $window.FindName('btnSaveDeviceNamePrefixes') $State.Controls.chkVerbose = $window.FindName('chkVerbose') $State.Controls.chkCopyAutopilot = $window.FindName('chkCopyAutopilot') $State.Controls.chkCopyUnattend = $window.FindName('chkCopyUnattend') $State.Controls.chkCopyPPKG = $window.FindName('chkCopyPPKG') $State.Controls.chkCleanupAppsISO = $window.FindName('chkCleanupAppsISO') $State.Controls.chkCleanupDeployISO = $window.FindName('chkCleanupDeployISO') $State.Controls.chkCleanupDrivers = $window.FindName('chkCleanupDrivers') $State.Controls.chkRemoveFFU = $window.FindName('chkRemoveFFU') $State.Controls.chkRemoveDownloadedESD = $window.FindName('chkRemoveDownloadedESD') $State.Controls.txtDiskSize = $window.FindName('txtDiskSize') $State.Controls.txtMemory = $window.FindName('txtMemory') $State.Controls.txtProcessors = $window.FindName('txtProcessors') $State.Controls.txtVMLocation = $window.FindName('txtVMLocation') $State.Controls.txtVMNamePrefix = $window.FindName('txtVMNamePrefix') $State.Controls.cmbLogicalSectorSize = $window.FindName('cmbLogicalSectorSize') $State.Controls.txtProductKey = $window.FindName('txtProductKey') $State.Controls.txtOfficePath = $window.FindName('txtOfficePath') $State.Controls.txtOfficeConfigXMLFilePath = $window.FindName('txtOfficeConfigXMLFilePath') $State.Controls.btnBrowseOfficeConfigXMLFile = $window.FindName('btnBrowseOfficeConfigXMLFile') $State.Controls.txtDriversFolder = $window.FindName('txtDriversFolder') $State.Controls.txtPEDriversFolder = $window.FindName('txtPEDriversFolder') $State.Controls.chkCopyPEDrivers = $window.FindName('chkCopyPEDrivers') $State.Controls.chkUseDriversAsPEDrivers = $window.FindName('chkUseDriversAsPEDrivers') $State.Controls.chkUpdateLatestCU = $window.FindName('chkUpdateLatestCU') $State.Controls.chkUpdateLatestNet = $window.FindName('chkUpdateLatestNet') $State.Controls.chkUpdateLatestDefender = $window.FindName('chkUpdateLatestDefender') $State.Controls.chkUpdateEdge = $window.FindName('chkUpdateEdge') $State.Controls.chkUpdateOneDrive = $window.FindName('chkUpdateOneDrive') $State.Controls.chkUpdateLatestMSRT = $window.FindName('chkUpdateLatestMSRT') $State.Controls.chkUpdatePreviewCU = $window.FindName('chkUpdatePreviewCU') $State.Controls.txtApplicationPath = $window.FindName('txtApplicationPath') $State.Controls.txtAppListJsonPath = $window.FindName('txtAppListJsonPath') $State.Controls.txtUserAppListPath = $window.FindName('txtUserAppListPath') $State.Controls.chkInstallDrivers = $window.FindName('chkInstallDrivers') $State.Controls.chkCopyDrivers = $window.FindName('chkCopyDrivers') $State.Controls.chkCompressDriversToWIM = $window.FindName('chkCompressDriversToWIM') $State.Controls.chkRemoveApps = $window.FindName('chkRemoveApps') $State.Controls.chkRemoveUpdates = $window.FindName('chkRemoveUpdates') $State.Controls.chkUpdateLatestMicrocode = $window.FindName('chkUpdateLatestMicrocode') $State.Controls.chkDefineAppsScriptVariables = $window.FindName('chkDefineAppsScriptVariables') $State.Controls.appsScriptVariablesPanel = $window.FindName('appsScriptVariablesPanel') $State.Controls.txtAppsScriptKey = $window.FindName('txtAppsScriptKey') $State.Controls.txtAppsScriptValue = $window.FindName('txtAppsScriptValue') $State.Controls.btnAddAppsScriptVariable = $window.FindName('btnAddAppsScriptVariable') $State.Controls.lstAppsScriptVariables = $window.FindName('lstAppsScriptVariables') $State.Controls.btnRemoveSelectedAppsScriptVariables = $window.FindName('btnRemoveSelectedAppsScriptVariables') $State.Controls.btnClearAppsScriptVariables = $window.FindName('btnClearAppsScriptVariables') $State.Controls.txtDriversJsonPath = $window.FindName('txtDriversJsonPath') $State.Controls.btnBrowseDriversJsonPath = $window.FindName('btnBrowseDriversJsonPath') $State.Controls.chkUpdateADK = $window.FindName('chkUpdateADK') $State.Controls.btnLoadConfig = $window.FindName('btnLoadConfig') $State.Controls.btnRestoreDefaults = $window.FindName('btnRestoreDefaults') $State.Controls.btnBuildConfig = $window.FindName('btnBuildConfig') # Home page $State.Controls.txtHomeCurrentBuildValue = $window.FindName('txtHomeCurrentBuildValue') $State.Controls.txtHomeLatestReleaseValue = $window.FindName('txtHomeLatestReleaseValue') $State.Controls.txtHomeReleaseStatusValue = $window.FindName('txtHomeReleaseStatusValue') $State.Controls.spHomeReleaseNotesSections = $window.FindName('spHomeReleaseNotesSections') $State.Controls.ellipseHomeDiskSpaceStatus = $window.FindName('ellipseHomeDiskSpaceStatus') $State.Controls.txtHomeDiskSpaceStatusValue = $window.FindName('txtHomeDiskSpaceStatusValue') $State.Controls.ellipseHomeHyperVStatus = $window.FindName('ellipseHomeHyperVStatus') $State.Controls.txtHomeHyperVStatusValue = $window.FindName('txtHomeHyperVStatusValue') $State.Controls.txtHomeDiscussionsStatusValue = $window.FindName('txtHomeDiscussionsStatusValue') $State.Controls.tbDiscussion1 = $window.FindName('tbDiscussion1') $State.Controls.linkDiscussion1 = $window.FindName('linkDiscussion1') $State.Controls.runDiscussion1 = $window.FindName('runDiscussion1') $State.Controls.tbDiscussion2 = $window.FindName('tbDiscussion2') $State.Controls.linkDiscussion2 = $window.FindName('linkDiscussion2') $State.Controls.runDiscussion2 = $window.FindName('runDiscussion2') $State.Controls.tbDiscussion3 = $window.FindName('tbDiscussion3') $State.Controls.linkDiscussion3 = $window.FindName('linkDiscussion3') $State.Controls.runDiscussion3 = $window.FindName('runDiscussion3') $State.Controls.tbDiscussion4 = $window.FindName('tbDiscussion4') $State.Controls.linkDiscussion4 = $window.FindName('linkDiscussion4') $State.Controls.runDiscussion4 = $window.FindName('runDiscussion4') $State.Controls.tbDiscussion5 = $window.FindName('tbDiscussion5') $State.Controls.linkDiscussion5 = $window.FindName('linkDiscussion5') $State.Controls.runDiscussion5 = $window.FindName('runDiscussion5') $State.Controls.tbDiscussionsLink = $window.FindName('tbDiscussionsLink') $State.Controls.linkDiscussions = $window.FindName('linkDiscussions') # Settings page $State.Controls.cmbThemeMode = $window.FindName('cmbThemeMode') # Shared page shell $State.Controls.txtPageTitle = $window.FindName('txtPageTitle') # Navigation controls $State.Controls.lstNavigation = $window.FindName('lstNavigation') $State.Controls.lstNavSettings = $window.FindName('lstNavSettings') $State.Controls.lstLogOutput = $window.FindName('lstLogOutput') # Content pages (for navigation visibility toggling) $State.Controls.navigationPages = @( $window.FindName('pageHome'), $window.FindName('pageHyperV'), $window.FindName('pageWindows'), $window.FindName('pageUpdates'), $window.FindName('pageApplications'), $window.FindName('pageOffice'), $window.FindName('pageDrivers'), $window.FindName('pageBuild'), $window.FindName('pageMonitor') ) $State.Controls.pageSettings = $window.FindName('pageSettings') # Initialize and bind the log data collection $State.Data.logData = New-Object System.Collections.ObjectModel.ObservableCollection[string] $State.Controls.lstLogOutput.ItemsSource = $State.Data.logData } function Initialize-VMSwitchData { param([PSCustomObject]$State) WriteLog "Initializing VM Switch data..." # Hyper-V Settings: Populate VM Switch ComboBox $vmSwitchData = Get-VMSwitchData $State.Data.vmSwitchMap = $vmSwitchData.SwitchMap $State.Controls.cmbVMSwitchName.Items.Clear() foreach ($switchName in $vmSwitchData.SwitchNames) { $State.Controls.cmbVMSwitchName.Items.Add($switchName) | Out-Null } $State.Controls.cmbVMSwitchName.Items.Add('Other') | Out-Null if ($State.Controls.cmbVMSwitchName.Items.Count -gt 1) { $State.Controls.cmbVMSwitchName.SelectedIndex = 0 $State.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' } else { $State.Controls.cmbVMSwitchName.SelectedItem = 'Other' $State.Controls.txtCustomVMSwitchName.Visibility = 'Visible' } } function Initialize-UIDefaults { param([PSCustomObject]$State) WriteLog "Initializing UI defaults..." # Get default values from helper functions $State.Defaults.windowsSettingsDefaults = Get-WindowsSettingsDefaults $State.Defaults.generalDefaults = Get-GeneralDefaults -FFUDevelopmentPath $State.FFUDevelopmentPath # Build tab defaults from General Defaults $State.Controls.txtFFUDevPath.Text = $State.FFUDevelopmentPath $State.Controls.txtCustomFFUNameTemplate.Text = $State.Defaults.generalDefaults.CustomFFUNameTemplate $State.Controls.txtFFUCaptureLocation.Text = $State.Defaults.generalDefaults.FFUCaptureLocation $State.Controls.txtThreads.Text = $State.Defaults.generalDefaults.Threads $State.Controls.cmbBitsPriority.SelectedItem = $State.Defaults.generalDefaults.BitsPriority $State.Controls.txtMaxUSBDrives.Text = $State.Defaults.generalDefaults.MaxUSBDrives $State.Controls.chkBuildUSBDriveEnable.IsChecked = $State.Defaults.generalDefaults.BuildUSBDriveEnable $State.Controls.chkCompactOS.IsChecked = $State.Defaults.generalDefaults.CompactOS $State.Controls.chkUpdateADK.IsChecked = $State.Defaults.generalDefaults.UpdateADK $State.Controls.chkOptimize.IsChecked = $State.Defaults.generalDefaults.Optimize $State.Controls.chkAllowVHDXCaching.IsChecked = $State.Defaults.generalDefaults.AllowVHDXCaching $State.Controls.chkInjectUnattend.IsChecked = $State.Defaults.generalDefaults.InjectUnattend $State.Controls.chkCreateDeploymentMedia.IsChecked = $State.Defaults.generalDefaults.CreateDeploymentMedia $State.Controls.chkAllowExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.AllowExternalHardDiskMedia $State.Controls.chkPromptExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.PromptExternalHardDiskMedia $State.Controls.chkSelectSpecificUSBDrives.IsChecked = $State.Defaults.generalDefaults.SelectSpecificUSBDrives $State.Controls.chkCopyAutopilot.IsChecked = $State.Defaults.generalDefaults.CopyAutopilot $State.Controls.chkCopyUnattend.IsChecked = $State.Defaults.generalDefaults.CopyUnattend $State.Controls.chkCopyPPKG.IsChecked = $State.Defaults.generalDefaults.CopyPPKG Set-DeviceNamingMode -State $State -Mode $State.Defaults.generalDefaults.DeviceNamingMode $State.Controls.txtDeviceNameTemplate.Text = $State.Defaults.generalDefaults.DeviceNameTemplate $State.Controls.txtDeviceNamePrefixesPath.Text = $State.Defaults.generalDefaults.DeviceNamePrefixesPath $State.Controls.txtDeviceNamePrefixes.Text = ($State.Defaults.generalDefaults.DeviceNamePrefixes -join [System.Environment]::NewLine) Import-DeviceNamePrefixesFromConfiguredPath -State $State Update-DeviceNamingControls -State $State $State.Controls.chkCleanupAppsISO.IsChecked = $State.Defaults.generalDefaults.CleanupAppsISO $State.Controls.chkCleanupDeployISO.IsChecked = $State.Defaults.generalDefaults.CleanupDeployISO $State.Controls.chkCleanupDrivers.IsChecked = $State.Defaults.generalDefaults.CleanupDrivers $State.Controls.chkRemoveFFU.IsChecked = $State.Defaults.generalDefaults.RemoveFFU $State.Controls.chkRemoveApps.IsChecked = $State.Defaults.generalDefaults.RemoveApps $State.Controls.chkRemoveUpdates.IsChecked = $State.Defaults.generalDefaults.RemoveUpdates $State.Controls.chkRemoveDownloadedESD.IsChecked = $State.Defaults.generalDefaults.RemoveDownloadedESD $State.Controls.chkVerbose.IsChecked = $State.Defaults.generalDefaults.Verbose $State.Controls.usbSelectionPanel.Visibility = if ($State.Controls.chkSelectSpecificUSBDrives.IsChecked) { 'Visible' } else { 'Collapsed' } $State.Controls.chkSelectSpecificUSBDrives.IsEnabled = $State.Controls.chkBuildUSBDriveEnable.IsChecked $State.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $State.Controls.chkAllowExternalHardDiskMedia.IsChecked $State.Controls.chkCopyAdditionalFFUFiles.IsChecked = $State.Defaults.generalDefaults.CopyAdditionalFFUFiles $State.Controls.additionalFFUPanel.Visibility = if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) { 'Visible' } else { 'Collapsed' } Update-BitsPrioritySetting -State $State # Hyper-V Settings defaults from General Defaults $State.Controls.chkEnableVMNetworking.IsChecked = $State.Defaults.generalDefaults.EnableVMNetworking Initialize-VMSwitchData -State $State $State.Controls.spVMNetworkingSettings.IsEnabled = $true -eq $State.Controls.chkEnableVMNetworking.IsChecked $State.Controls.txtDiskSize.Text = $State.Defaults.generalDefaults.DiskSizeGB $State.Controls.txtMemory.Text = $State.Defaults.generalDefaults.MemoryGB $State.Controls.txtProcessors.Text = $State.Defaults.generalDefaults.Processors $State.Controls.txtVMLocation.Text = $State.Defaults.generalDefaults.VMLocation $State.Controls.txtVMNamePrefix.Text = $State.Defaults.generalDefaults.VMNamePrefix $State.Controls.cmbLogicalSectorSize.SelectedItem = ($State.Controls.cmbLogicalSectorSize.Items | Where-Object { $_.Content -eq $State.Defaults.generalDefaults.LogicalSectorSize.ToString() }) # Populate Windows Release, Version, and SKU comboboxes # Initialize Windows settings combos based on media source mode $initIsoPath = $State.Defaults.windowsSettingsDefaults.DefaultISOPath if ($null -ne $State.Controls.rbProvideISO -and -not $State.Controls.rbProvideISO.IsChecked) { $initIsoPath = '' } Get-WindowsSettingsCombos -isoPath $initIsoPath -State $State # Windows Settings tab defaults $State.Controls.cmbWindowsLang.ItemsSource = $State.Defaults.windowsSettingsDefaults.AllowedLanguages $State.Controls.cmbWindowsLang.SelectedItem = $State.Defaults.windowsSettingsDefaults.DefaultWindowsLang $State.Controls.cmbMediaType.ItemsSource = $State.Defaults.windowsSettingsDefaults.AllowedMediaTypes $State.Controls.cmbMediaType.SelectedItem = $State.Defaults.windowsSettingsDefaults.DefaultMediaType $State.Controls.txtProductKey.Text = $State.Defaults.windowsSettingsDefaults.DefaultProductKey # Updates tab defaults from General Defaults $State.Controls.chkUpdateLatestCU.IsChecked = $State.Defaults.generalDefaults.UpdateLatestCU $State.Controls.chkUpdateLatestNet.IsChecked = $State.Defaults.generalDefaults.UpdateLatestNet $State.Controls.chkUpdateLatestDefender.IsChecked = $State.Defaults.generalDefaults.UpdateLatestDefender $State.Controls.chkUpdateEdge.IsChecked = $State.Defaults.generalDefaults.UpdateEdge $State.Controls.chkUpdateOneDrive.IsChecked = $State.Defaults.generalDefaults.UpdateOneDrive $State.Controls.chkUpdateLatestMSRT.IsChecked = $State.Defaults.generalDefaults.UpdateLatestMSRT $State.Controls.chkUpdateLatestMicrocode.IsChecked = $State.Defaults.generalDefaults.UpdateLatestMicrocode $State.Controls.chkUpdatePreviewCU.IsChecked = $State.Defaults.generalDefaults.UpdatePreviewCU # Set initial state for CU checkbox interplay $State.Controls.chkPreviewCU.IsEnabled = -not $State.Controls.chkLatestCU.IsChecked $State.Controls.chkLatestCU.IsEnabled = -not $State.Controls.chkPreviewCU.IsChecked # Applications tab defaults from General Defaults $State.Controls.chkInstallApps.IsChecked = $State.Defaults.generalDefaults.InstallApps $State.Controls.txtApplicationPath.Text = $State.Defaults.generalDefaults.ApplicationPath $State.Controls.txtAppListJsonPath.Text = $State.Defaults.generalDefaults.AppListJsonPath $State.Controls.txtUserAppListPath.Text = $State.Defaults.generalDefaults.UserAppListPath $State.Controls.chkInstallWingetApps.IsChecked = $State.Defaults.generalDefaults.InstallWingetApps $State.Controls.chkBringYourOwnApps.IsChecked = $State.Defaults.generalDefaults.BringYourOwnApps # M365 Apps/Office tab defaults from General Defaults $State.Controls.chkInstallOffice.IsChecked = $State.Defaults.generalDefaults.InstallOffice $State.Controls.txtOfficePath.Text = $State.Defaults.generalDefaults.OfficePath $State.Controls.chkCopyOfficeConfigXML.IsChecked = $State.Defaults.generalDefaults.CopyOfficeConfigXML $State.Controls.txtOfficeConfigXMLFilePath.Text = $State.Defaults.generalDefaults.OfficeConfigXMLFilePath # Drivers tab defaults from General Defaults $State.Controls.txtDriversFolder.Text = $State.Defaults.generalDefaults.DriversFolder $State.Controls.txtPEDriversFolder.Text = $State.Defaults.generalDefaults.PEDriversFolder $State.Controls.txtDriversJsonPath.Text = $State.Defaults.generalDefaults.DriversJsonPath $State.Controls.chkDownloadDrivers.IsChecked = $State.Defaults.generalDefaults.DownloadDrivers $State.Controls.chkInstallDrivers.IsChecked = $State.Defaults.generalDefaults.InstallDrivers $State.Controls.chkCopyDrivers.IsChecked = $State.Defaults.generalDefaults.CopyDrivers $State.Controls.chkCopyPEDrivers.IsChecked = $State.Defaults.generalDefaults.CopyPEDrivers $State.Controls.chkUseDriversAsPEDrivers.IsChecked = $State.Defaults.generalDefaults.UseDriversAsPEDrivers $State.Controls.chkCompressDriversToWIM.IsChecked = $State.Defaults.generalDefaults.CompressDownloadedDriversToWim # Drivers tab UI logic $makeList = @('Microsoft', 'Dell', 'HP', 'Lenovo') if ($null -ne $State.Controls.cmbMake) { # Clear existing items to prevent duplication on re-initialization (e.g., after Restore Defaults) $State.Controls.cmbMake.Items.Clear() foreach ($m in $makeList) { [void]$State.Controls.cmbMake.Items.Add($m) } if ($State.Controls.cmbMake.Items.Count -gt 0) { $State.Controls.cmbMake.SelectedIndex = 0 } } Update-DriverDownloadPanelVisibility -State $State # Set initial state for driver checkbox interplay Update-DriverCheckboxStates -State $State # Set initial state for InstallApps checkbox based on updates Update-InstallAppsState -State $State # Set default theme mode and disable if Fluent is not supported if ($null -ne $State.Controls.cmbThemeMode) { $State.Controls.cmbThemeMode.SelectedItem = "System" if (-not $State.Flags.isFluentSupported) { $State.Controls.cmbThemeMode.IsEnabled = $false $State.Controls.cmbThemeMode.Tag = "Fluent theme requires PowerShell 7.5+ (.NET 9+). Best experience on PowerShell 7.6+ (.NET 10)." } } # Set default navigation selection to Home and initialize the shared page title if ($null -ne $State.Controls.lstNavigation) { $State.Controls.lstNavigation.SelectedIndex = 0 # Keep the shell header aligned with the selected navigation item on first render if ($null -ne $State.Controls.txtPageTitle) { $selectedNavigationItem = $State.Controls.lstNavigation.SelectedItem if ($null -ne $selectedNavigationItem -and -not [string]::IsNullOrWhiteSpace([string]$selectedNavigationItem.Tag)) { $State.Controls.txtPageTitle.Text = [string]$selectedNavigationItem.Tag } else { $State.Controls.txtPageTitle.Text = 'Home' } } } # Set initial state for Office panel visibility Update-OfficePanelVisibility -State $State # Set initial state for Application panel visibility Update-ApplicationPanelVisibility -State $State # Set initial state for BYO Apps copy button Update-CopyButtonState -State $State # Apply accent color to primary action button only (per Windows design guidance) if ($State.Flags.isFluentSupported) { try { $State.Controls.btnRun = $State.Window.FindName('btnRun') if ($null -ne $State.Controls.btnRun) { # Use SetResourceReference for live accent color updates when user changes Windows theme $State.Controls.btnRun.SetResourceReference( [System.Windows.Controls.Control]::BackgroundProperty, [System.Windows.SystemColors]::AccentColorBrushKey ) $State.Controls.btnRun.Foreground = [System.Windows.Media.Brushes]::White } } catch { WriteLog "Could not apply accent color to Build FFU button: $($_.Exception.Message)" } } } function Initialize-DynamicUIElements { param([PSCustomObject]$State) WriteLog "Initializing dynamic UI elements (Grids, Columns)..." # Get the Fluent base style for ListViewItem in GridView mode # Must use GridViewItemContainerStyleKey (not the generic ListViewItem type key) because the # generic Fluent ListViewItem style has a template without GridViewRowPresenter, which breaks # column-based rendering and causes items to display their ToString() representation. $listViewItemBaseStyle = $State.Window.TryFindResource([System.Windows.Controls.GridView]::GridViewItemContainerStyleKey) # Driver Models ListView setup # Set ListViewItem style to stretch content horizontally so cell templates fill the cell $itemStyleDriverModels = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleDriverModels.BasedOn = $listViewItemBaseStyle } $itemStyleDriverModels.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstDriverModels.ItemContainerStyle = $itemStyleDriverModels $driverModelsGridView = New-Object System.Windows.Controls.GridView $State.Controls.lstDriverModels.View = $driverModelsGridView # Assign GridView to ListView first # Add the selectable column and scope header select-all to visible filtered rows. Add-SelectableGridViewColumn -ListView $State.Controls.lstDriverModels -State $State -HeaderCheckBoxKeyName "chkSelectAllDriverModels" -ColumnWidth 70 -HeaderSelectionAffectsVisibleItemsOnly # Add other sortable columns with left-aligned headers Add-SortableColumn -gridView $driverModelsGridView -header "Make" -binding "Make" -width 100 -headerHorizontalAlignment Left Add-SortableColumn -gridView $driverModelsGridView -header "Model" -binding "Model" -width 200 -headerHorizontalAlignment Left Add-SortableColumn -gridView $driverModelsGridView -header "Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left $State.Controls.lstDriverModels.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { param($eventSource, $e) # $eventSource is the ListView control $header = $e.OriginalSource if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) { # Retrieve the main UI state object from the window's Tag property $listViewControl = $eventSource $window = [System.Windows.Window]::GetWindow($listViewControl) $uiStateFromWindowTag = $window.Tag Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag } } ) # Keep driver model columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstDriverModels -FixedColumnIndexes @(0) # Winget Search ListView setup $wingetGridView = New-Object System.Windows.Controls.GridView $State.Controls.lstWingetResults.View = $wingetGridView # Assign GridView to ListView first # Set ListViewItem style to stretch content horizontally so cell templates fill the cell $itemStyleWingetResults = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleWingetResults.BasedOn = $listViewItemBaseStyle } $itemStyleWingetResults.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstWingetResults.ItemContainerStyle = $itemStyleWingetResults # Add the selectable column using the new function Add-SelectableGridViewColumn -ListView $State.Controls.lstWingetResults -State $State -HeaderCheckBoxKeyName "chkSelectAllWingetResults" -ColumnWidth 60 # Add other sortable columns with left-aligned headers Add-SortableColumn -gridView $wingetGridView -header "Name" -binding "Name" -width 200 -headerHorizontalAlignment Left Add-SortableColumn -gridView $wingetGridView -header "Id" -binding "Id" -width 200 -headerHorizontalAlignment Left Add-SortableColumn -gridView $wingetGridView -header "Version" -binding "Version" -width 100 -headerHorizontalAlignment Left Add-SortableColumn -gridView $wingetGridView -header "Source" -binding "Source" -width 100 -headerHorizontalAlignment Left # --- START: Add Architecture Column --- $archColumn = New-Object System.Windows.Controls.GridViewColumn $archHeader = New-Object System.Windows.Controls.GridViewColumnHeader $archHeader.Tag = "Architecture" # For sorting $archHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left # Create header content with correct padding to match other columns $commonPaddingForHeader = New-Object System.Windows.Thickness(5, 2, 5, 2) $headerTextElementFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock]) $headerTextElementFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Architecture") $headerTextBlockPadding = New-Object System.Windows.Thickness($commonPaddingForHeader.Left, $commonPaddingForHeader.Top, $commonPaddingForHeader.Right, $commonPaddingForHeader.Bottom) $headerTextElementFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, $headerTextBlockPadding) $headerTextElementFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) $headerDataTemplate = New-Object System.Windows.DataTemplate $headerDataTemplate.VisualTree = $headerTextElementFactory $archHeader.ContentTemplate = $headerDataTemplate $archColumn.Header = $archHeader $archColumn.Width = 120 # Create the CellTemplate with a ComboBox $archCellTemplate = New-Object System.Windows.DataTemplate $comboBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.ComboBox]) # The ItemsSource for the ComboBox $availableArchitectures = @('x86', 'x64', 'arm64', 'x86 x64', 'NA') $comboBoxFactory.SetValue([System.Windows.Controls.ItemsControl]::ItemsSourceProperty, $availableArchitectures) # Bind the text property to the 'Architecture' property of the data item. # This ensures the initial value is displayed correctly. $binding = New-Object System.Windows.Data.Binding("Architecture") $binding.Mode = [System.Windows.Data.BindingMode]::TwoWay $comboBoxFactory.SetBinding([System.Windows.Controls.ComboBox]::TextProperty, $binding) # Create a style to disable the ComboBox for 'msstore' source, inheriting the Fluent base style $comboBoxStyle = New-Object System.Windows.Style $comboBoxStyle.TargetType = [System.Windows.Controls.ComboBox] $comboBoxBaseStyle = $State.Window.TryFindResource([System.Windows.Controls.ComboBox]) if ($null -ne $comboBoxBaseStyle) { $comboBoxStyle.BasedOn = $comboBoxBaseStyle } $dataTrigger = New-Object System.Windows.DataTrigger $dataTrigger.Binding = New-Object System.Windows.Data.Binding("Source") $dataTrigger.Value = "msstore" $dataTrigger.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ComboBox]::IsEnabledProperty, $false))) $comboBoxStyle.Triggers.Add($dataTrigger) $comboBoxFactory.SetValue([System.Windows.FrameworkElement]::StyleProperty, $comboBoxStyle) $archCellTemplate.VisualTree = $comboBoxFactory $archColumn.CellTemplate = $archCellTemplate $wingetGridView.Columns.Add($archColumn) # --- END: Add Architecture Column --- # --- START: Add Additional Exit Codes Column --- $exitCodesColumn = New-Object System.Windows.Controls.GridViewColumn $exitCodesHeader = New-Object System.Windows.Controls.GridViewColumnHeader $exitCodesHeader.Tag = "AdditionalExitCodes" $exitCodesHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $exitHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock]) $exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Additional Exit Codes") $exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5, 2, 5, 2))) $exitHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) $exitHeaderTemplate = New-Object System.Windows.DataTemplate $exitHeaderTemplate.VisualTree = $exitHeaderTextFactory $exitCodesHeader.ContentTemplate = $exitHeaderTemplate $exitCodesColumn.Header = $exitCodesHeader $exitCodesColumn.Width = 140 $exitCodesCellTemplate = New-Object System.Windows.DataTemplate $exitCodesTextBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBox]) $exitBinding = New-Object System.Windows.Data.Binding("AdditionalExitCodes") $exitBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay $exitCodesTextBoxFactory.SetBinding([System.Windows.Controls.TextBox]::TextProperty, $exitBinding) $exitCodesCellTemplate.VisualTree = $exitCodesTextBoxFactory $exitCodesColumn.CellTemplate = $exitCodesCellTemplate $wingetGridView.Columns.Add($exitCodesColumn) # --- END: Add Additional Exit Codes Column --- # --- START: Add Ignore Non-Zero Exit Codes Column --- $ignoreColumn = New-Object System.Windows.Controls.GridViewColumn $ignoreHeader = New-Object System.Windows.Controls.GridViewColumnHeader $ignoreHeader.Tag = "IgnoreNonZeroExitCodes" $ignoreHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $ignoreHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock]) $ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Ignore Exit Codes") $ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5, 2, 5, 2))) $ignoreHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) $ignoreHeaderTemplate = New-Object System.Windows.DataTemplate $ignoreHeaderTemplate.VisualTree = $ignoreHeaderTextFactory $ignoreHeader.ContentTemplate = $ignoreHeaderTemplate $ignoreColumn.Header = $ignoreHeader $ignoreColumn.Width = 140 $ignoreCellTemplate = New-Object System.Windows.DataTemplate # Center the checkbox in the cell $ignoreCellGridFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.Grid]) $ignoreCellGridFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch) $ignoreCellGridFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Stretch) $ignoreCheckFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox]) $ignoreCheckFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center) $ignoreCheckFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) $ignoreBinding = New-Object System.Windows.Data.Binding("IgnoreNonZeroExitCodes") $ignoreBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay $ignoreCheckFactory.SetBinding([System.Windows.Controls.Primitives.ToggleButton]::IsCheckedProperty, $ignoreBinding) # Build the visual tree: Grid -> CheckBox $ignoreCellGridFactory.AppendChild($ignoreCheckFactory) $ignoreCellTemplate.VisualTree = $ignoreCellGridFactory $ignoreColumn.CellTemplate = $ignoreCellTemplate $wingetGridView.Columns.Add($ignoreColumn) # --- END: Add Ignore Non-Zero Exit Codes Column --- Add-SortableColumn -gridView $wingetGridView -header "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left $State.Controls.lstWingetResults.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { param($eventSource, $e) # $eventSource is the ListView control $header = $e.OriginalSource if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) { # Retrieve the main UI state object from the window's Tag property $listViewControl = $eventSource $window = [System.Windows.Window]::GetWindow($listViewControl) $uiStateFromWindowTag = $window.Tag Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag } } ) # Keep Winget result columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstWingetResults -FixedColumnIndexes @(0) # BYO Applications ListView setup $byoAppsGridView = New-Object System.Windows.Controls.GridView $State.Controls.lstApplications.View = $byoAppsGridView # Set ListViewItem style to stretch content horizontally $itemStyleBYOApps = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleBYOApps.BasedOn = $listViewItemBaseStyle } $itemStyleBYOApps.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstApplications.ItemContainerStyle = $itemStyleBYOApps # Add the selectable column Add-SelectableGridViewColumn -ListView $State.Controls.lstApplications -State $State -HeaderCheckBoxKeyName "chkSelectAllBYOApps" -ColumnWidth 60 # Add other sortable columns Add-SortableColumn -gridView $byoAppsGridView -header "Priority" -binding "Priority" -width 60 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Name" -binding "Name" -width 150 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Command Line" -binding "CommandLine" -width 200 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Arguments" -binding "Arguments" -width 200 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Source" -binding "Source" -width 150 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Exit Codes" -binding "AdditionalExitCodes" -width 100 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Ignore Exit Codes" -binding "IgnoreExitCodes" -width 120 -headerHorizontalAlignment Left Add-SortableColumn -gridView $byoAppsGridView -header "Copy Status" -binding "CopyStatus" -width 150 -headerHorizontalAlignment Left # Keep BYO application columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstApplications -FixedColumnIndexes @(0) # Apps Script Variables ListView setup # Bind ItemsSource to the data list $State.Controls.lstAppsScriptVariables.ItemsSource = $State.Data.appsScriptVariablesDataList.ToArray() # Set ListViewItem style to stretch content horizontally so cell templates fill the cell $itemStyleAppsScriptVars = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleAppsScriptVars.BasedOn = $listViewItemBaseStyle } $itemStyleAppsScriptVars.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstAppsScriptVariables.ItemContainerStyle = $itemStyleAppsScriptVars # The GridView for lstAppsScriptVariables is defined in XAML. We need to get it and add the column. if ($State.Controls.lstAppsScriptVariables.View -is [System.Windows.Controls.GridView]) { Add-SelectableGridViewColumn -ListView $State.Controls.lstAppsScriptVariables -State $State -HeaderCheckBoxKeyName "chkSelectAllAppsScriptVariables" -ColumnWidth 60 # Make Key and Value columns sortable $appsScriptVarsGridView = $State.Controls.lstAppsScriptVariables.View # Key Column (should be at index 1 after selectable column is inserted at 0) if ($appsScriptVarsGridView.Columns.Count -gt 1) { $keyColumn = $appsScriptVarsGridView.Columns[1] $keyHeader = New-Object System.Windows.Controls.GridViewColumnHeader $keyHeader.Content = "Key" $keyHeader.Tag = "Key" # Property to sort by $keyHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $keyColumn.Header = $keyHeader } # Value Column (should be at index 2) if ($appsScriptVarsGridView.Columns.Count -gt 2) { $valueColumn = $appsScriptVarsGridView.Columns[2] $valueHeader = New-Object System.Windows.Controls.GridViewColumnHeader $valueHeader.Content = "Value" $valueHeader.Tag = "Value" # Property to sort by $valueHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $valueColumn.Header = $valueHeader } # Add Click event handler for sorting $State.Controls.lstAppsScriptVariables.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { param($eventSource, $e) # $eventSource is the ListView control $header = $e.OriginalSource if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) { # Retrieve the main UI state object from the window's Tag property $listViewControl = $eventSource $window = [System.Windows.Window]::GetWindow($listViewControl) $uiStateFromWindowTag = $window.Tag Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag } } ) # Keep apps script variable columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstAppsScriptVariables -FixedColumnIndexes @(0) } else { WriteLog "Warning: lstAppsScriptVariables.View is not a GridView. Selectable column not added, and sorting cannot be enabled." } # Build dynamic multi-column checkboxes for optional features if ($State.Controls.featuresPanel -and $State.Defaults.windowsSettingsDefaults) { BuildFeaturesGrid -parent $State.Controls.featuresPanel -allowedFeatures $State.Defaults.windowsSettingsDefaults.AllowedFeatures -State $State } else { WriteLog "Initialize-DynamicUIElements: Could not build features grid. Panel or defaults missing." } # USB Drives ListView setup # Set ListViewItem style to stretch content horizontally so cell templates fill the cell $itemStyleUSBDrives = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleUSBDrives.BasedOn = $listViewItemBaseStyle } $itemStyleUSBDrives.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstUSBDrives.ItemContainerStyle = $itemStyleUSBDrives if ($State.Controls.lstUSBDrives.View -is [System.Windows.Controls.GridView]) { # Add the selectable column using the shared function Add-SelectableGridViewColumn -ListView $State.Controls.lstUSBDrives -State $State -HeaderCheckBoxKeyName "chkSelectAllUSBDrivesHeader" -ColumnWidth 70 # Make other columns sortable $usbDrivesGridView = $State.Controls.lstUSBDrives.View # Model Column (index 0 in XAML, now 1) if ($usbDrivesGridView.Columns.Count -gt 1) { $modelColumn = $usbDrivesGridView.Columns[1] $modelHeader = New-Object System.Windows.Controls.GridViewColumnHeader $modelHeader.Content = "Model" $modelHeader.Tag = "Model" # Property to sort by $modelHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $modelColumn.Header = $modelHeader } # Unique ID Column (index 1 in XAML, now 2) if ($usbDrivesGridView.Columns.Count -gt 2) { $uniqueIdColumn = $usbDrivesGridView.Columns[2] $uniqueIdHeader = New-Object System.Windows.Controls.GridViewColumnHeader $uniqueIdHeader.Content = "Unique ID" $uniqueIdHeader.Tag = "UniqueId" # Property to sort by $uniqueIdHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $uniqueIdColumn.Header = $uniqueIdHeader } # Size Column (index 2 in XAML, now 3) if ($usbDrivesGridView.Columns.Count -gt 3) { $sizeColumn = $usbDrivesGridView.Columns[3] $sizeHeader = New-Object System.Windows.Controls.GridViewColumnHeader $sizeHeader.Content = "Size (GB)" $sizeHeader.Tag = "Size" # Property to sort by $sizeHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $sizeColumn.Header = $sizeHeader } # Add Click event handler for sorting $State.Controls.lstUSBDrives.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { param($eventSource, $e) # $eventSource is the ListView control $header = $e.OriginalSource if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) { # Retrieve the main UI state object from the window's Tag property $listViewControl = $eventSource $window = [System.Windows.Window]::GetWindow($listViewControl) $uiStateFromWindowTag = $window.Tag Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag } } ) # Keep USB drive columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstUSBDrives -FixedColumnIndexes @(0) } else { WriteLog "Warning: lstUSBDrives.View is not a GridView. Selectable column not added, and sorting cannot be enabled." } # Additional FFUs ListView setup $itemStyleAdditionalFFUs = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem]) if ($null -ne $listViewItemBaseStyle) { $itemStyleAdditionalFFUs.BasedOn = $listViewItemBaseStyle } $itemStyleAdditionalFFUs.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch))) $State.Controls.lstAdditionalFFUs.ItemContainerStyle = $itemStyleAdditionalFFUs if ($State.Controls.lstAdditionalFFUs.View -is [System.Windows.Controls.GridView]) { Add-SelectableGridViewColumn -ListView $State.Controls.lstAdditionalFFUs -State $State -HeaderCheckBoxKeyName "chkSelectAllAdditionalFFUs" -ColumnWidth 70 $additionalFFUsGridView = $State.Controls.lstAdditionalFFUs.View if ($additionalFFUsGridView.Columns.Count -gt 1) { $nameColumn = $additionalFFUsGridView.Columns[1] $nameHeader = New-Object System.Windows.Controls.GridViewColumnHeader $nameHeader.Content = "FFU Name" $nameHeader.Tag = "Name" $nameHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $nameColumn.Header = $nameHeader } if ($additionalFFUsGridView.Columns.Count -gt 2) { $lastModColumn = $additionalFFUsGridView.Columns[2] $lastModHeader = New-Object System.Windows.Controls.GridViewColumnHeader $lastModHeader.Content = "Last Modified" $lastModHeader.Tag = "LastModified" $lastModHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left $lastModColumn.Header = $lastModHeader } $State.Controls.lstAdditionalFFUs.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, [System.Windows.RoutedEventHandler] { param($eventSource, $e) $header = $e.OriginalSource if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) { $listViewControl = $eventSource $window = [System.Windows.Window]::GetWindow($listViewControl) $uiStateFromWindowTag = $window.Tag Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag } } ) # Keep additional FFU columns sized to the current visible content. Enable-ListViewColumnAutoResize -ListView $State.Controls.lstAdditionalFFUs -FixedColumnIndexes @(0) } else { WriteLog "Warning: lstAdditionalFFUs.View is not a GridView. Selectable column not added, and sorting cannot be enabled." } } Export-ModuleMember -Function *