Files
FFU/FFUDevelopment/BuildFFUVM_UI.ps1
T
rbalsleyMSFT fb9dc3fbc5 Refactors dynamic UI setup into core module for better organization
Moves initialization logic for several list views (driver models, Winget results, app script variables) and the features grid into a new `Initialize-DynamicUIElements` function.
This centralizes the setup of these UI components within the `FFUUI.Core.Initialize` module, improving code structure.

Additionally:
- Updates sort event handlers in the new module to correctly retrieve UI state from the window's tag.
- Enhances `Invoke-ListViewSort` by initializing sort state flags if they are not already present, improving sorting stability.
2025-06-16 18:28:04 -07:00

1591 lines
108 KiB
PowerShell

[CmdletBinding()]
[System.STAThread()]
param()
# Check PowerShell Version
if ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Error "PowerShell 7 or later is required to run this script."
exit 1
}
# --------------------------------------------------------------------------
# SECTION: Variables & Constants
# --------------------------------------------------------------------------
# $FFUDevelopmentPath = $PSScriptRoot
$FFUDevelopmentPath = 'C:\FFUDevelopment' # hard coded for testing
# --- NEW: Central State Object ---
$script:uiState = [PSCustomObject]@{
Window = $null;
Controls = @{
featureCheckBoxes = @{};
UpdateInstallAppsBasedOnUpdates = $null
};
Data = @{
allDriverModels = [System.Collections.Generic.List[PSCustomObject]]::new();
appsScriptVariablesDataList = [System.Collections.Generic.List[PSCustomObject]]::new();
versionData = $null;
vmSwitchMap = @{}
};
Flags = @{
installAppsForcedByUpdates = $false;
prevInstallAppsStateBeforeUpdates = $null;
installAppsCheckedByOffice = $false;
lastSortProperty = $null;
lastSortAscending = $true
};
Defaults = @{};
LogFilePath = "$FFUDevelopmentPath\FFUDevelopment_UI.log"
}
# Remove any existing modules to avoid conflicts
if (Get-Module -Name 'FFU.Common.Core' -ErrorAction SilentlyContinue) {
Remove-Module -Name 'FFU.Common.Core' -Force
}
if (Get-Module -Name 'FFUUI.Core' -ErrorAction SilentlyContinue) {
Remove-Module -Name 'FFUUI.Core' -Force
}
# Import the common core module first for logging
Import-Module "$PSScriptRoot\FFU.Common" -Force
# Import the Core UI Logic Module
Import-Module "$PSScriptRoot\FFUUI.Core" -Force
# Set the log path for the common logger (for UI operations)
Set-CommonCoreLogPath -Path $script:uiState.LogFilePath
# Setting long path support - this prevents issues where some applications have deep directory structures
# and driver extraction fails due to long paths.
$script:uiState.Flags.originalLongPathsValue = $null # Store original value
try {
$script:uiState.Flags.originalLongPathsValue = Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -ErrorAction SilentlyContinue
}
catch {
# Key or value might not exist, which is fine.
WriteLog "Could not read initial LongPathsEnabled value (may not exist)."
}
# Enable long paths if not already enabled
if ($script:uiState.Flags.originalLongPathsValue -ne 1) {
try {
WriteLog 'LongPathsEnabled is not set to 1. Setting it to 1 for the duration of this script.'
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1 -Force
WriteLog 'LongPathsEnabled set to 1.'
}
catch {
WriteLog "Error setting LongPathsEnabled registry key: $($_.Exception.Message). Long path issues might persist."
}
}
else {
WriteLog "LongPathsEnabled is already set to 1."
}
if (Test-Path -Path $script:uiState.LogFilePath) {
Remove-item -Path $script:uiState.LogFilePath -Force
}
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore, PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
# Load XAML
$xamlPath = Join-Path $PSScriptRoot "BuildFFUVM_UI.xaml"
if (-not (Test-Path $xamlPath)) {
Write-Error "XAML file not found: $xamlPath"
return
}
$xamlString = Get-Content $xamlPath -Raw
$reader = New-Object System.IO.StringReader($xamlString)
$xmlReader = [System.Xml.XmlReader]::Create($reader)
$window = [Windows.Markup.XamlReader]::Load($xmlReader)
# -----------------------------------------------------------------------------
# SECTION: Winget UI
# -----------------------------------------------------------------------------
# Create data context class for version binding
$script:uiState.Data.versionData = [PSCustomObject]@{
WingetVersion = "Not checked"
ModuleVersion = "Not checked"
}
# Add observable property support
$script:uiState.Data.versionData | Add-Member -MemberType ScriptMethod -Name NotifyPropertyChanged -Value {
param($PropertyName)
if ($this.PropertyChanged) {
$this.PropertyChanged.Invoke($this, [System.ComponentModel.PropertyChangedEventArgs]::new($PropertyName))
}
}
$script:uiState.Data.versionData | Add-Member -MemberType NoteProperty -Name PropertyChanged -Value $null
$script:uiState.Data.versionData | Add-Member -TypeName "System.ComponentModel.INotifyPropertyChanged"
function Update-WingetVersionFields {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$wingetText,
[Parameter(Mandatory)]
[string]$moduleText
)
# Force UI update on the UI thread
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Normal, [Action] {
$script:uiState.Controls.txtWingetVersion.Text = $wingetText
$script:uiState.Controls.txtWingetModuleVersion.Text = $moduleText
# Force immediate UI refresh
[System.Windows.Forms.Application]::DoEvents()
})
}
$window.Add_Loaded({
# Pass the state object to all initialization functions
$script:uiState.Window = $window
$window.Tag = $script:uiState
Initialize-UIControls -State $script:uiState
# Get Windows Settings defaults and lists from helper module
$script:uiState.Defaults.windowsSettingsDefaults = Get-WindowsSettingsDefaults
# Get General defaults from helper module
$script:uiState.Defaults.generalDefaults = Get-GeneralDefaults -FFUDevelopmentPath $FFUDevelopmentPath
Initialize-DynamicUIElements -State $script:uiState
# Initialize Windows Settings UI using data from helper module
Refresh-WindowsSettingsCombos -isoPath $script:uiState.Defaults.windowsSettingsDefaults.DefaultISOPath -State $script:uiState # Use combined refresh function
$script:uiState.Controls.txtISOPath.Add_TextChanged({ Refresh-WindowsSettingsCombos -isoPath $script:uiState.Controls.txtISOPath.Text -State $script:uiState })
$script:uiState.Controls.cmbWindowsRelease.Add_SelectionChanged({
$selectedReleaseValue = 11 # Default if null
if ($null -ne $script:uiState.Controls.cmbWindowsRelease.SelectedItem) {
$selectedReleaseValue = $script:uiState.Controls.cmbWindowsRelease.SelectedItem.Value
}
# Only need to update the Version combo when Release changes
Update-WindowsVersionCombo -selectedRelease $selectedReleaseValue -isoPath $script:uiState.Controls.txtISOPath.Text -State $script:uiState
# Also update the SKU combo (now derives values internally)
Update-WindowsSkuCombo -State $script:uiState
})
$script:uiState.Controls.btnBrowseISO.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "ISO files (*.iso)|*.iso"
$ofd.Title = "Select Windows ISO File"
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $script:uiState.Controls.txtISOPath.Text = $ofd.FileName }
})
# Populate static combos from defaults object
$script:uiState.Controls.cmbWindowsArch.ItemsSource = $script:uiState.Defaults.windowsSettingsDefaults.AllowedArchitectures
$script:uiState.Controls.cmbWindowsArch.SelectedItem = $script:uiState.Defaults.windowsSettingsDefaults.DefaultWindowsArch
$script:uiState.Controls.cmbWindowsLang.ItemsSource = $script:uiState.Defaults.windowsSettingsDefaults.AllowedLanguages
$script:uiState.Controls.cmbWindowsLang.SelectedItem = $script:uiState.Defaults.windowsSettingsDefaults.DefaultWindowsLang
# $script:uiState.Controls.cmbWindowsSKU.ItemsSource is now populated by Update-WindowsSkuCombo
$script:uiState.Controls.cmbWindowsSKU.SelectedItem = $script:uiState.Defaults.windowsSettingsDefaults.DefaultWindowsSKU # Attempt to set default
$script:uiState.Controls.cmbMediaType.ItemsSource = $script:uiState.Defaults.windowsSettingsDefaults.AllowedMediaTypes
$script:uiState.Controls.cmbMediaType.SelectedItem = $script:uiState.Defaults.windowsSettingsDefaults.DefaultMediaType
# Set default text values for Windows Settings
$script:uiState.Controls.txtOptionalFeatures.Text = $script:uiState.Defaults.windowsSettingsDefaults.DefaultOptionalFeatures
$window.FindName('txtProductKey').Text = $script:uiState.Defaults.windowsSettingsDefaults.DefaultProductKey
# Build tab defaults from General Defaults
$window.FindName('txtFFUDevPath').Text = $FFUDevelopmentPath # Keep this as it's the base path
$window.FindName('txtCustomFFUNameTemplate').Text = $script:uiState.Defaults.generalDefaults.CustomFFUNameTemplate
$window.FindName('txtFFUCaptureLocation').Text = $script:uiState.Defaults.generalDefaults.FFUCaptureLocation
$window.FindName('txtShareName').Text = $script:uiState.Defaults.generalDefaults.ShareName
$window.FindName('txtUsername').Text = $script:uiState.Defaults.generalDefaults.Username
$window.FindName('chkBuildUSBDriveEnable').IsChecked = $script:uiState.Defaults.generalDefaults.BuildUSBDriveEnable
$window.FindName('chkCompactOS').IsChecked = $script:uiState.Defaults.generalDefaults.CompactOS
$script:uiState.Controls.chkUpdateADK.IsChecked = $script:uiState.Defaults.generalDefaults.UpdateADK # Set default for chkUpdateADK
$window.FindName('chkOptimize').IsChecked = $script:uiState.Defaults.generalDefaults.Optimize
$window.FindName('chkAllowVHDXCaching').IsChecked = $script:uiState.Defaults.generalDefaults.AllowVHDXCaching
$window.FindName('chkCreateCaptureMedia').IsChecked = $script:uiState.Defaults.generalDefaults.CreateCaptureMedia
$window.FindName('chkCreateDeploymentMedia').IsChecked = $script:uiState.Defaults.generalDefaults.CreateDeploymentMedia
$window.FindName('chkAllowExternalHardDiskMedia').IsChecked = $script:uiState.Defaults.generalDefaults.AllowExternalHardDiskMedia
$window.FindName('chkPromptExternalHardDiskMedia').IsChecked = $script:uiState.Defaults.generalDefaults.PromptExternalHardDiskMedia
$window.FindName('chkSelectSpecificUSBDrives').IsChecked = $script:uiState.Defaults.generalDefaults.SelectSpecificUSBDrives
$window.FindName('chkCopyAutopilot').IsChecked = $script:uiState.Defaults.generalDefaults.CopyAutopilot
$window.FindName('chkCopyUnattend').IsChecked = $script:uiState.Defaults.generalDefaults.CopyUnattend
$window.FindName('chkCopyPPKG').IsChecked = $script:uiState.Defaults.generalDefaults.CopyPPKG
$window.FindName('chkCleanupAppsISO').IsChecked = $script:uiState.Defaults.generalDefaults.CleanupAppsISO
$window.FindName('chkCleanupCaptureISO').IsChecked = $script:uiState.Defaults.generalDefaults.CleanupCaptureISO
$window.FindName('chkCleanupDeployISO').IsChecked = $script:uiState.Defaults.generalDefaults.CleanupDeployISO
$window.FindName('chkCleanupDrivers').IsChecked = $script:uiState.Defaults.generalDefaults.CleanupDrivers
$window.FindName('chkRemoveFFU').IsChecked = $script:uiState.Defaults.generalDefaults.RemoveFFU
$script:uiState.Controls.chkRemoveApps.IsChecked = $script:uiState.Defaults.generalDefaults.RemoveApps
$script:uiState.Controls.chkRemoveUpdates.IsChecked = $script:uiState.Defaults.generalDefaults.RemoveUpdates
# Hyper-V Settings defaults from General Defaults
$window.FindName('txtDiskSize').Text = $script:uiState.Defaults.generalDefaults.DiskSizeGB
$window.FindName('txtMemory').Text = $script:uiState.Defaults.generalDefaults.MemoryGB
$window.FindName('txtProcessors').Text = $script:uiState.Defaults.generalDefaults.Processors
$window.FindName('txtVMLocation').Text = $script:uiState.Defaults.generalDefaults.VMLocation
$window.FindName('txtVMNamePrefix').Text = $script:uiState.Defaults.generalDefaults.VMNamePrefix
$window.FindName('cmbLogicalSectorSize').SelectedItem = ($window.FindName('cmbLogicalSectorSize').Items | Where-Object { $_.Content -eq $script:uiState.Defaults.generalDefaults.LogicalSectorSize.ToString() })
# Hyper-V Settings: Populate VM Switch ComboBox (Keep existing logic)
$vmSwitchData = Get-VMSwitchData
$script:uiState.Data.vmSwitchMap = $vmSwitchData.SwitchMap
$script:uiState.Controls.cmbVMSwitchName.Items.Clear()
foreach ($switchName in $vmSwitchData.SwitchNames) {
$script:uiState.Controls.cmbVMSwitchName.Items.Add($switchName) | Out-Null
}
$script:uiState.Controls.cmbVMSwitchName.Items.Add('Other') | Out-Null
if ($script:uiState.Controls.cmbVMSwitchName.Items.Count -gt 1) {
$script:uiState.Controls.cmbVMSwitchName.SelectedIndex = 0
$firstSwitch = $script:uiState.Controls.cmbVMSwitchName.SelectedItem
if ($script:uiState.Data.vmSwitchMap.ContainsKey($firstSwitch)) {
$script:uiState.Controls.txtVMHostIPAddress.Text = $script:uiState.Data.vmSwitchMap[$firstSwitch]
}
else {
$script:uiState.Controls.txtVMHostIPAddress.Text = $script:uiState.Defaults.generalDefaults.VMHostIPAddress # Use default if IP not found
}
$script:uiState.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed'
}
else {
$script:uiState.Controls.cmbVMSwitchName.SelectedItem = 'Other'
$script:uiState.Controls.txtCustomVMSwitchName.Visibility = 'Visible'
$script:uiState.Controls.txtVMHostIPAddress.Text = $script:uiState.Defaults.generalDefaults.VMHostIPAddress # Use default
}
$script:uiState.Controls.cmbVMSwitchName.Add_SelectionChanged({
param($eventSource, $selectionChangedEventArgs)
$selectedItem = $eventSource.SelectedItem
if ($selectedItem -eq 'Other') {
$script:uiState.Controls.txtCustomVMSwitchName.Visibility = 'Visible'
$script:uiState.Controls.txtVMHostIPAddress.Text = '' # Clear IP for custom
}
else {
$script:uiState.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed'
if ($script:uiState.Data.vmSwitchMap.ContainsKey($selectedItem)) {
$script:uiState.Controls.txtVMHostIPAddress.Text = $script:uiState.Data.vmSwitchMap[$selectedItem]
}
else {
$script:uiState.Controls.txtVMHostIPAddress.Text = '' # Clear IP if not found in map
}
}
})
# Updates tab defaults from General Defaults
$window.FindName('chkUpdateLatestCU').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateLatestCU
$window.FindName('chkUpdateLatestNet').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateLatestNet
$window.FindName('chkUpdateLatestDefender').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateLatestDefender
$window.FindName('chkUpdateEdge').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateEdge
$window.FindName('chkUpdateOneDrive').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateOneDrive
$window.FindName('chkUpdateLatestMSRT').IsChecked = $script:uiState.Defaults.generalDefaults.UpdateLatestMSRT
$script:uiState.Controls.chkUpdateLatestMicrocode.IsChecked = $script:uiState.Defaults.generalDefaults.UpdateLatestMicrocode # Added for UpdateLatestMicrocode
$window.FindName('chkUpdatePreviewCU').IsChecked = $script:uiState.Defaults.generalDefaults.UpdatePreviewCU
# Applications tab defaults from General Defaults
$window.FindName('chkInstallApps').IsChecked = $script:uiState.Defaults.generalDefaults.InstallApps
$window.FindName('txtApplicationPath').Text = $script:uiState.Defaults.generalDefaults.ApplicationPath
$window.FindName('txtAppListJsonPath').Text = $script:uiState.Defaults.generalDefaults.AppListJsonPath
$window.FindName('chkInstallWingetApps').IsChecked = $script:uiState.Defaults.generalDefaults.InstallWingetApps
$window.FindName('chkBringYourOwnApps').IsChecked = $script:uiState.Defaults.generalDefaults.BringYourOwnApps
# M365 Apps/Office tab defaults from General Defaults
$window.FindName('chkInstallOffice').IsChecked = $script:uiState.Defaults.generalDefaults.InstallOffice
$window.FindName('txtOfficePath').Text = $script:uiState.Defaults.generalDefaults.OfficePath
$window.FindName('chkCopyOfficeConfigXML').IsChecked = $script:uiState.Defaults.generalDefaults.CopyOfficeConfigXML
$window.FindName('txtOfficeConfigXMLFilePath').Text = $script:uiState.Defaults.generalDefaults.OfficeConfigXMLFilePath
# Drivers tab defaults from General Defaults
$window.FindName('txtDriversFolder').Text = $script:uiState.Defaults.generalDefaults.DriversFolder
$window.FindName('txtPEDriversFolder').Text = $script:uiState.Defaults.generalDefaults.PEDriversFolder
$script:uiState.Controls.txtDriversJsonPath.Text = $script:uiState.Defaults.generalDefaults.DriversJsonPath # Set default text
$window.FindName('chkDownloadDrivers').IsChecked = $script:uiState.Defaults.generalDefaults.DownloadDrivers
$window.FindName('chkInstallDrivers').IsChecked = $script:uiState.Defaults.generalDefaults.InstallDrivers
$window.FindName('chkCopyDrivers').IsChecked = $script:uiState.Defaults.generalDefaults.CopyDrivers
$window.FindName('chkCopyPEDrivers').IsChecked = $script:uiState.Defaults.generalDefaults.CopyPEDrivers
# Drivers tab UI logic
$makeList = @('Microsoft', 'Dell', 'HP', 'Lenovo')
foreach ($m in $makeList) { [void]$script:uiState.Controls.cmbMake.Items.Add($m) }
if ($script:uiState.Controls.cmbMake.Items.Count -gt 0) { $script:uiState.Controls.cmbMake.SelectedIndex = 0 }
$script:uiState.Controls.chkDownloadDrivers.Add_Checked({
$script:uiState.Controls.cmbMake.Visibility = 'Visible'
$script:uiState.Controls.btnGetModels.Visibility = 'Visible'
$script:uiState.Controls.spMakeSection.Visibility = 'Visible'
$script:uiState.Controls.spModelFilterSection.Visibility = 'Visible'
$script:uiState.Controls.lstDriverModels.Visibility = 'Visible'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Visible'
})
$script:uiState.Controls.chkDownloadDrivers.Add_Unchecked({
$script:uiState.Controls.cmbMake.Visibility = 'Collapsed'
$script:uiState.Controls.btnGetModels.Visibility = 'Collapsed'
$script:uiState.Controls.spMakeSection.Visibility = 'Collapsed'
$script:uiState.Controls.spModelFilterSection.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.Visibility = 'Collapsed'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.ItemsSource = $null
$script:uiState.Data.allDriverModels = @()
$script:uiState.Controls.txtModelFilter.Text = ""
})
$script:uiState.Controls.spMakeSection.Visibility = if ($script:uiState.Controls.chkDownloadDrivers.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.btnGetModels.Visibility = if ($script:uiState.Controls.chkDownloadDrivers.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.spModelFilterSection.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.Visibility = 'Collapsed'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Collapsed'
$script:uiState.Controls.btnGetModels.Add_Click({
$selectedMake = $script:uiState.Controls.cmbMake.SelectedItem
$script:uiState.Controls.txtStatus.Text = "Getting models for $selectedMake..."
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$this.IsEnabled = $false
try {
# Get previously selected models from the master list ($script:uiState.Data.allDriverModels)
# This ensures all selected items are captured, regardless of any active filter.
$previouslySelectedModels = @($script:uiState.Data.allDriverModels | Where-Object { $_.IsSelected })
# Get newly fetched models for the current make (already standardized)
$newlyFetchedStandardizedModels = Get-ModelsForMake -SelectedMake $selectedMake -State $script:uiState
$combinedModelsList = [System.Collections.Generic.List[PSCustomObject]]::new()
$modelIdentifiersInCombinedList = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
# Add previously selected models first to preserve their selection state and order (if any)
foreach ($item in $previouslySelectedModels) {
$combinedModelsList.Add($item)
# Use a composite key of Make and Model for uniqueness tracking
$modelIdentifiersInCombinedList.Add("$($item.Make)::$($item.Model)") | Out-Null
}
# Add newly fetched models if they are not already in the combined list (based on Make::Model identifier)
$addedNewCount = 0
foreach ($item in $newlyFetchedStandardizedModels) {
if (-not $modelIdentifiersInCombinedList.Contains("$($item.Make)::$($item.Model)")) {
$combinedModelsList.Add($item)
# Add to HashSet to prevent duplicates if the new list itself has them (though Get-ModelsForMake should try to avoid this)
$modelIdentifiersInCombinedList.Add("$($item.Make)::$($item.Model)") | Out-Null
$addedNewCount++
}
}
$script:uiState.Data.allDriverModels = $combinedModelsList.ToArray() | Sort-Object @{Expression = { $_.IsSelected }; Descending = $true }, Make, Model # Sort by selection status, then Make, then Model
$script:uiState.Controls.lstDriverModels.ItemsSource = $script:uiState.Data.allDriverModels
$script:uiState.Controls.txtModelFilter.Text = "" # Clear any existing filter
if ($script:uiState.Data.allDriverModels.Count -gt 0) {
$script:uiState.Controls.spModelFilterSection.Visibility = 'Visible'
$script:uiState.Controls.lstDriverModels.Visibility = 'Visible'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Visible'
$statusText = "Displaying $($script:uiState.Data.allDriverModels.Count) models."
if ($newlyFetchedStandardizedModels.Count -gt 0 -and $addedNewCount -eq 0 -and $previouslySelectedModels.Count -gt 0) {
# This case means new models were fetched, but all were already present in the selected list.
$statusText = "Fetched $($newlyFetchedStandardizedModels.Count) models for $selectedMake; all were already in the selected list. Displaying $($script:uiState.Data.allDriverModels.Count) total selected models."
}
elseif ($addedNewCount -gt 0) {
$statusText = "Added $addedNewCount new models for $selectedMake. Displaying $($script:uiState.Data.allDriverModels.Count) total models."
}
elseif ($newlyFetchedStandardizedModels.Count -eq 0 -and $selectedMake -eq 'Lenovo' ) {
# Handled Lenovo specific no new models found message inside Get-ModelsForMake or if user cancelled prompt
$statusText = if ($previouslySelectedModels.Count -gt 0) { "No new models found for $selectedMake. Displaying $($previouslySelectedModels.Count) previously selected models." } else { "No models found for $selectedMake." }
}
elseif ($newlyFetchedStandardizedModels.Count -eq 0) {
$statusText = "No new models found for $selectedMake. Displaying $($script:uiState.Data.allDriverModels.Count) previously selected models."
}
$script:uiState.Controls.txtStatus.Text = $statusText
}
else {
$script:uiState.Controls.spModelFilterSection.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.Visibility = 'Collapsed'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Collapsed'
$script:uiState.Controls.txtStatus.Text = "No models to display for $selectedMake."
}
} # End Try
catch {
$script:uiState.Controls.txtStatus.Text = "Error getting models: $($_.Exception.Message)"
[System.Windows.MessageBox]::Show("Error getting models: $($_.Exception.Message)", "Error", "OK", "Error")
# Minimal UI reset on error, keep previously selected if any
if ($null -eq $script:uiState.Data.allDriverModels -or $script:uiState.Data.allDriverModels.Count -eq 0) {
$script:uiState.Controls.spModelFilterSection.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.Visibility = 'Collapsed'
$script:uiState.Controls.spDriverActionButtons.Visibility = 'Collapsed'
$script:uiState.Controls.lstDriverModels.ItemsSource = $null
$script:uiState.Controls.txtModelFilter.Text = ""
}
} # End Catch
finally {
$window.Cursor = $null
$this.IsEnabled = $true # Re-enable the button
} # End Finally
})
$script:uiState.Controls.txtModelFilter.Add_TextChanged({
param($sourceObject, $textChangedEventArgs)
Filter-DriverModels -filterText $script:uiState.Controls.txtModelFilter.Text -State $script:uiState
})
$script:uiState.Controls.btnDownloadSelectedDrivers.Add_Click({
param($buttonSender, $clickEventArgs)
$selectedDrivers = @($script:uiState.Controls.lstDriverModels.Items | Where-Object { $_.IsSelected })
if (-not $selectedDrivers) {
[System.Windows.MessageBox]::Show("No drivers selected to download.", "Download Drivers", "OK", "Information")
return
}
$buttonSender.IsEnabled = $false
$script:uiState.Controls.pbOverallProgress.Visibility = 'Visible'
$script:uiState.Controls.pbOverallProgress.Value = 0
$script:uiState.Controls.txtStatus.Text = "Preparing driver downloads..."
# Define common necessary task-specific variables locally
# Ensure required selections are made
if ($null -eq $script:uiState.Controls.cmbWindowsRelease.SelectedItem) {
[System.Windows.MessageBox]::Show("Please select a Windows Release.", "Missing Information", "OK", "Warning")
$buttonSender.IsEnabled = $true
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$script:uiState.Controls.txtStatus.Text = "Driver download cancelled."
return
}
if ($null -eq $script:uiState.Controls.cmbWindowsArch.SelectedItem) {
[System.Windows.MessageBox]::Show("Please select a Windows Architecture.", "Missing Information", "OK", "Warning")
$buttonSender.IsEnabled = $true
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$script:uiState.Controls.txtStatus.Text = "Driver download cancelled."
return
}
if (($selectedDrivers | Where-Object { $_.Make -eq 'HP' }) -and $null -ne $script:uiState.Controls.cmbWindowsVersion -and $null -eq $script:uiState.Controls.cmbWindowsVersion.SelectedItem) {
[System.Windows.MessageBox]::Show("HP drivers are selected. Please select a Windows Version.", "Missing Information", "OK", "Warning")
$buttonSender.IsEnabled = $true
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$script:uiState.Controls.txtStatus.Text = "Driver download cancelled."
return
}
$localDriversFolder = $script:uiState.Controls.txtDriversFolder.Text
$localWindowsRelease = $script:uiState.Controls.cmbWindowsRelease.SelectedItem.Value
$localWindowsArch = $script:uiState.Controls.cmbWindowsArch.SelectedItem
$localWindowsVersion = if ($null -ne $script:uiState.Controls.cmbWindowsVersion -and $null -ne $script:uiState.Controls.cmbWindowsVersion.SelectedItem) { $script:uiState.Controls.cmbWindowsVersion.SelectedItem } else { $null }
$coreStaticVars = Get-CoreStaticVariables
$localHeaders = $coreStaticVars.Headers
$localUserAgent = $coreStaticVars.UserAgent
$compressDrivers = $script:uiState.Controls.chkCompressDriversToWIM.IsChecked
# --- Dell Catalog Handling (once, if Dell drivers are selected) ---
$dellCatalogXmlPath = $null # This will be the path passed to the background task
if ($selectedDrivers | Where-Object { $_.Make -eq 'Dell' }) {
$script:uiState.Controls.txtStatus.Text = "Checking Dell Catalog..."
WriteLog "Dell drivers selected. Preparing Dell catalog..."
$dellDriversFolderUi = Join-Path -Path $localDriversFolder -ChildPath "Dell"
$catalogBaseName = if ($localWindowsRelease -le 11) { "CatalogPC" } else { "Catalog" }
$dellCabFileUi = Join-Path -Path $dellDriversFolderUi -ChildPath "$($catalogBaseName).cab"
# This $dellCatalogXmlPath is the one we ensure exists and is up-to-date for the Save-DellDriversTask
$dellCatalogXmlPath = Join-Path -Path $dellDriversFolderUi -ChildPath "$($catalogBaseName).xml"
$catalogUrl = if ($localWindowsRelease -le 11) { "http://downloads.dell.com/catalog/CatalogPC.cab" } else { "https://downloads.dell.com/catalog/Catalog.cab" }
$downloadDellCatalog = $true
if (Test-Path -Path $dellCatalogXmlPath -PathType Leaf) {
if (((Get-Date) - (Get-Item $dellCatalogXmlPath).LastWriteTime).TotalDays -lt 7) {
WriteLog "Using existing Dell Catalog XML (less than 7 days old) for download task: $dellCatalogXmlPath"
$downloadDellCatalog = $false
$script:uiState.Controls.txtStatus.Text = "Dell Catalog ready."
}
else {
WriteLog "Existing Dell Catalog XML '$dellCatalogXmlPath' is older than 7 days."
}
}
else {
WriteLog "Dell Catalog XML '$dellCatalogXmlPath' not found."
}
if ($downloadDellCatalog) {
WriteLog "Dell Catalog XML '$dellCatalogXmlPath' needs to be downloaded/updated for driver download task."
$script:uiState.Controls.txtStatus.Text = "Downloading Dell Catalog..."
try {
# Ensure Dell drivers folder exists
if (-not (Test-Path -Path $dellDriversFolderUi -PathType Container)) {
WriteLog "Creating Dell drivers folder: $dellDriversFolderUi"
New-Item -Path $dellDriversFolderUi -ItemType Directory -Force | Out-Null
}
if (Test-Path $dellCabFileUi) { Remove-Item $dellCabFileUi -Force -ErrorAction SilentlyContinue }
if (Test-Path $dellCatalogXmlPath) { Remove-Item $dellCatalogXmlPath -Force -ErrorAction SilentlyContinue }
# Using Start-BitsTransferWithRetry and Invoke-Process (available from FFUUI.Core.psm1)
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $dellCabFileUi
WriteLog "Dell Catalog CAB downloaded to $dellCabFileUi"
Invoke-Process -FilePath "Expand.exe" -ArgumentList """$dellCabFileUi"" ""$dellCatalogXmlPath""" | Out-Null
WriteLog "Dell Catalog XML extracted to $dellCatalogXmlPath"
Remove-Item -Path $dellCabFileUi -Force -ErrorAction SilentlyContinue
WriteLog "Dell Catalog CAB file $dellCabFileUi deleted."
$script:uiState.Controls.txtStatus.Text = "Dell Catalog ready."
}
catch {
$errMsg = "Failed to download/extract Dell Catalog for driver download task: $($_.Exception.Message)"
WriteLog $errMsg; [System.Windows.MessageBox]::Show($errMsg, "Dell Catalog Error", "OK", "Error")
$dellCatalogXmlPath = $null # Ensure it's null if failed, Save-DellDriversTask will handle this
$script:uiState.Controls.txtStatus.Text = "Dell Catalog download failed. Dell drivers may not download."
}
}
# If $downloadDellCatalog was false, $dellCatalogXmlPath is already set to the existing valid XML.
}
# --- End Dell Catalog Handling ---
$script:uiState.Controls.txtStatus.Text = "Processing all selected drivers..."
WriteLog "Processing all selected drivers: $($selectedDrivers.Model -join ', ')"
$taskArguments = @{
DriversFolder = $localDriversFolder
WindowsRelease = $localWindowsRelease
WindowsArch = $localWindowsArch
WindowsVersion = $localWindowsVersion # Will be null if not applicable (e.g., not HP)
Headers = $localHeaders
UserAgent = $localUserAgent
CompressToWim = $compressDrivers
DellCatalogXmlPath = $dellCatalogXmlPath # Will be null if not Dell or if Dell catalog prep failed
}
Invoke-ParallelProcessing -ItemsToProcess $selectedDrivers `
-ListViewControl $script:uiState.Controls.lstDriverModels `
-IdentifierProperty 'Model' `
-StatusProperty 'DownloadStatus' `
-TaskType 'DownloadDriverByMake' `
-TaskArguments $taskArguments `
-CompletedStatusText 'Completed' `
-ErrorStatusPrefix 'Error: ' `
-WindowObject $window `
-MainThreadLogPath $script:uiState.LogFilePath
$overallSuccess = $true
# Check if any item has an error status after processing
# We iterate over $script:lstDriverModels.Items because their DownloadStatus property was updated by Invoke-ParallelProcessing
foreach ($item in ($script:uiState.Controls.lstDriverModels.Items | Where-Object { $_.IsSelected })) {
# Check only originally selected items
if ($item.DownloadStatus -like 'Error:*') {
$overallSuccess = $false
WriteLog "Error detected for model $($item.Model) (Make: $($item.Make)): $($item.DownloadStatus)"
# No break here, log all errors
}
}
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$buttonSender.IsEnabled = $true
if ($overallSuccess) {
$script:uiState.Controls.txtStatus.Text = "All selected driver downloads processed."
[System.Windows.MessageBox]::Show("All selected driver downloads processed. Check status column for details.", "Download Process Finished", "OK", "Information")
}
else {
$script:uiState.Controls.txtStatus.Text = "Driver downloads processed with some errors. Check status column and log."
[System.Windows.MessageBox]::Show("Driver downloads processed, but some errors occurred. Please check the status column for each driver and the log file for details.", "Download Process Finished with Errors", "OK", "Warning")
}
})
$script:uiState.Controls.btnClearDriverList.Add_Click({
$script:uiState.Controls.lstDriverModels.ItemsSource = $null
$script:uiState.Data.allDriverModels = @()
$script:uiState.Controls.txtModelFilter.Text = ""
$script:uiState.Controls.txtStatus.Text = "Driver list cleared."
})
$script:uiState.Controls.btnSaveDriversJson.Add_Click({ Save-DriversJson -State $script:uiState })
$script:uiState.Controls.btnImportDriversJson.Add_Click({ Import-DriversJson -State $script:uiState })
# Office interplay (Keep existing logic)
$script:uiState.Flags.installAppsCheckedByOffice = $false
if ($script:uiState.Controls.chkInstallOffice.IsChecked) {
$script:uiState.Controls.OfficePathStackPanel.Visibility = 'Visible'
$script:uiState.Controls.OfficePathGrid.Visibility = 'Visible'
$script:uiState.Controls.CopyOfficeConfigXMLStackPanel.Visibility = 'Visible'
# Show/hide XML file path based on checkbox state
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = if ($script:uiState.Controls.chkCopyOfficeConfigXML.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = if ($script:uiState.Controls.chkCopyOfficeConfigXML.IsChecked) { 'Visible' } else { 'Collapsed' }
}
else {
$script:uiState.Controls.OfficePathStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficePathGrid.Visibility = 'Collapsed'
$script:uiState.Controls.CopyOfficeConfigXMLStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = 'Collapsed'
}
$script:uiState.Controls.chkInstallOffice.Add_Checked({
if (-not $script:uiState.Controls.chkInstallApps.IsChecked) {
$script:uiState.Controls.chkInstallApps.IsChecked = $true
$script:uiState.Flags.installAppsCheckedByOffice = $true
}
$script:uiState.Controls.chkInstallApps.IsEnabled = $false
$script:uiState.Controls.OfficePathStackPanel.Visibility = 'Visible'
$script:uiState.Controls.OfficePathGrid.Visibility = 'Visible'
$script:uiState.Controls.CopyOfficeConfigXMLStackPanel.Visibility = 'Visible'
# Show/hide XML file path based on checkbox state
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = if ($script:uiState.Controls.chkCopyOfficeConfigXML.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = if ($script:uiState.Controls.chkCopyOfficeConfigXML.IsChecked) { 'Visible' } else { 'Collapsed' }
})
$script:uiState.Controls.chkInstallOffice.Add_Unchecked({
if ($script:uiState.Flags.installAppsCheckedByOffice) {
$script:uiState.Controls.chkInstallApps.IsChecked = $false
$script:uiState.Flags.installAppsCheckedByOffice = $false
}
# Only re-enable InstallApps if not forced by Updates
if (-not $script:uiState.Flags.installAppsForcedByUpdates) {
$script:uiState.Controls.chkInstallApps.IsEnabled = $true
}
$script:uiState.Controls.OfficePathStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficePathGrid.Visibility = 'Collapsed'
$script:uiState.Controls.CopyOfficeConfigXMLStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = 'Collapsed'
})
$script:uiState.Controls.chkCopyOfficeConfigXML.Add_Checked({
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = 'Visible'
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = 'Visible'
})
$script:uiState.Controls.chkCopyOfficeConfigXML.Add_Unchecked({
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel.Visibility = 'Collapsed'
$script:uiState.Controls.OfficeConfigurationXMLFileGrid.Visibility = 'Collapsed'
})
# Updates/InstallApps interplay (Keep existing logic)
$script:uiState.Flags.installAppsForcedByUpdates = $false
$script:uiState.Flags.prevInstallAppsStateBeforeUpdates = $null
# Define the scriptblock within the Loaded event and assign it to the state object
$script:uiState.Controls.UpdateInstallAppsBasedOnUpdates = {
param($State) # Pass state object to avoid using $script: scope inside
$anyUpdateChecked = $State.Controls.chkUpdateLatestDefender.IsChecked -or $State.Controls.chkUpdateEdge.IsChecked -or $State.Controls.chkUpdateOneDrive.IsChecked -or $State.Controls.chkUpdateLatestMSRT.IsChecked
if ($anyUpdateChecked) {
if (-not $State.Flags.installAppsForcedByUpdates) {
$State.Flags.prevInstallAppsStateBeforeUpdates = $State.Controls.chkInstallApps.IsChecked
$State.Flags.installAppsForcedByUpdates = $true
}
$State.Controls.chkInstallApps.IsChecked = $true
$State.Controls.chkInstallApps.IsEnabled = $false
}
else {
if ($State.Flags.installAppsForcedByUpdates) {
$State.Controls.chkInstallApps.IsChecked = $State.Flags.prevInstallAppsStateBeforeUpdates
$State.Flags.installAppsForcedByUpdates = $false
$State.Flags.prevInstallAppsStateBeforeUpdates = $null
}
# Only re-enable InstallApps if not forced by Office
if (-not $State.Controls.chkInstallOffice.IsChecked) {
$State.Controls.chkInstallApps.IsEnabled = $true
}
}
}
$script:uiState.Controls.chkUpdateLatestDefender.Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateLatestDefender.Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateEdge.Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateEdge.Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateOneDrive.Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateOneDrive.Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateLatestMSRT.Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
$script:uiState.Controls.chkUpdateLatestMSRT.Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
# Initial check for Updates/InstallApps state
& $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState
# CU interplay (Keep existing logic)
$script:uiState.Controls.chkLatestCU.Add_Checked({ $script:uiState.Controls.chkPreviewCU.IsEnabled = $false })
$script:uiState.Controls.chkLatestCU.Add_Unchecked({ $script:uiState.Controls.chkPreviewCU.IsEnabled = $true })
$script:uiState.Controls.chkPreviewCU.Add_Checked({ $script:uiState.Controls.chkLatestCU.IsEnabled = $false })
$script:uiState.Controls.chkPreviewCU.Add_Unchecked({ $script:uiState.Controls.chkLatestCU.IsEnabled = $true })
# Set initial state based on defaults
$script:uiState.Controls.chkPreviewCU.IsEnabled = -not $script:uiState.Controls.chkLatestCU.IsChecked
$script:uiState.Controls.chkLatestCU.IsEnabled = -not $script:uiState.Controls.chkPreviewCU.IsChecked
# USB Drive Detection/Selection logic (Keep existing logic)
$script:uiState.Controls.btnCheckUSBDrives.Add_Click({
$script:uiState.Controls.lstUSBDrives.Items.Clear()
$usbDrives = Get-USBDrives
foreach ($drive in $usbDrives) {
$script:uiState.Controls.lstUSBDrives.Items.Add([PSCustomObject]$drive)
}
if ($script:uiState.Controls.lstUSBDrives.Items.Count -gt 0) {
$script:uiState.Controls.lstUSBDrives.SelectedIndex = 0
}
})
$script:uiState.Controls.chkSelectAllUSBDrives.Add_Checked({
foreach ($item in $script:uiState.Controls.lstUSBDrives.Items) { $item.IsSelected = $true }
$script:uiState.Controls.lstUSBDrives.Items.Refresh()
})
$script:uiState.Controls.chkSelectAllUSBDrives.Add_Unchecked({
foreach ($item in $script:uiState.Controls.lstUSBDrives.Items) { $item.IsSelected = $false }
$script:uiState.Controls.lstUSBDrives.Items.Refresh()
})
$script:uiState.Controls.lstUSBDrives.Add_KeyDown({
param($eventSource, $keyEvent)
if ($keyEvent.Key -eq 'Space') {
$selectedItem = $script:uiState.Controls.lstUSBDrives.SelectedItem
if ($selectedItem) {
$selectedItem.IsSelected = !$selectedItem.IsSelected
$script:uiState.Controls.lstUSBDrives.Items.Refresh()
$allSelected = -not ($script:uiState.Controls.lstUSBDrives.Items | Where-Object { -not $_.IsSelected })
$script:uiState.Controls.chkSelectAllUSBDrives.IsChecked = $allSelected
}
}
})
$script:uiState.Controls.lstUSBDrives.Add_SelectionChanged({
param($eventSource, $selChangeEvent)
$allSelected = -not ($script:uiState.Controls.lstUSBDrives.Items | Where-Object { -not $_.IsSelected })
$script:uiState.Controls.chkSelectAllUSBDrives.IsChecked = $allSelected
})
$script:uiState.Controls.usbSection.Visibility = if ($script:uiState.Controls.chkBuildUSBDriveEnable.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.usbSelectionPanel.Visibility = if ($script:uiState.Controls.chkSelectSpecificUSBDrives.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.chkBuildUSBDriveEnable.Add_Checked({
$script:uiState.Controls.usbSection.Visibility = 'Visible'
$script:uiState.Controls.chkSelectSpecificUSBDrives.IsEnabled = $true
})
$script:uiState.Controls.chkBuildUSBDriveEnable.Add_Unchecked({
$script:uiState.Controls.usbSection.Visibility = 'Collapsed'
$script:uiState.Controls.chkSelectSpecificUSBDrives.IsEnabled = $false
$script:uiState.Controls.chkSelectSpecificUSBDrives.IsChecked = $false
$script:uiState.Controls.lstUSBDrives.Items.Clear()
$script:uiState.Controls.chkSelectAllUSBDrives.IsChecked = $false
})
$script:uiState.Controls.chkSelectSpecificUSBDrives.Add_Checked({ $script:uiState.Controls.usbSelectionPanel.Visibility = 'Visible' })
$script:uiState.Controls.chkSelectSpecificUSBDrives.Add_Unchecked({
$script:uiState.Controls.usbSelectionPanel.Visibility = 'Collapsed'
$script:uiState.Controls.lstUSBDrives.Items.Clear()
$script:uiState.Controls.chkSelectAllUSBDrives.IsChecked = $false
})
$script:uiState.Controls.chkSelectSpecificUSBDrives.IsEnabled = $script:uiState.Controls.chkBuildUSBDriveEnable.IsChecked
$script:uiState.Controls.chkAllowExternalHardDiskMedia.Add_Checked({ $script:uiState.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $true })
$script:uiState.Controls.chkAllowExternalHardDiskMedia.Add_Unchecked({
$script:uiState.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $false
$script:uiState.Controls.chkPromptExternalHardDiskMedia.IsChecked = $false
})
# Set initial state based on defaults
$script:uiState.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $script:uiState.Controls.chkAllowExternalHardDiskMedia.IsChecked
# APPLICATIONS tab UI logic (Keep existing logic)
$script:uiState.Controls.chkInstallWingetApps.Visibility = if ($script:uiState.Controls.chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.applicationPathPanel.Visibility = if ($script:uiState.Controls.chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.appListJsonPathPanel.Visibility = if ($script:uiState.Controls.chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.chkBringYourOwnApps.Visibility = if ($script:uiState.Controls.chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.byoApplicationPanel.Visibility = if ($script:uiState.Controls.chkBringYourOwnApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.wingetPanel.Visibility = if ($script:uiState.Controls.chkInstallWingetApps.IsChecked) { 'Visible' } else { 'Collapsed' }
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Collapsed' # Keep search hidden initially
$script:uiState.Controls.chkInstallApps.Add_Checked({
$script:uiState.Controls.chkInstallWingetApps.Visibility = 'Visible'
$script:uiState.Controls.applicationPathPanel.Visibility = 'Visible'
$script:uiState.Controls.appListJsonPathPanel.Visibility = 'Visible'
$script:uiState.Controls.chkBringYourOwnApps.Visibility = 'Visible'
# New logic for AppsScriptVariables
$script:uiState.Controls.chkDefineAppsScriptVariables.Visibility = 'Visible'
})
$script:uiState.Controls.chkInstallApps.Add_Unchecked({
$script:uiState.Controls.chkInstallWingetApps.IsChecked = $false # Uncheck children when parent is unchecked
$script:uiState.Controls.chkBringYourOwnApps.IsChecked = $false
$script:uiState.Controls.chkInstallWingetApps.Visibility = 'Collapsed'
$script:uiState.Controls.applicationPathPanel.Visibility = 'Collapsed'
$script:uiState.Controls.appListJsonPathPanel.Visibility = 'Collapsed'
$script:uiState.Controls.chkBringYourOwnApps.Visibility = 'Collapsed'
$script:uiState.Controls.wingetPanel.Visibility = 'Collapsed'
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Collapsed'
$script:uiState.Controls.byoApplicationPanel.Visibility = 'Collapsed'
# New logic for AppsScriptVariables
$script:uiState.Controls.chkDefineAppsScriptVariables.IsChecked = $false # Also uncheck it
$script:uiState.Controls.chkDefineAppsScriptVariables.Visibility = 'Collapsed'
$script:uiState.Controls.appsScriptVariablesPanel.Visibility = 'Collapsed' # Ensure panel is hidden
})
$script:uiState.Controls.btnBrowseApplicationPath.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select Application Path Folder"
if ($selectedPath) { $script:uiState.Controls.txtApplicationPath.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowseAppListJsonPath.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "JSON files (*.json)|*.json"
$ofd.Title = "Select AppList.json File"
$ofd.CheckFileExists = $false
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { $script:uiState.Controls.txtAppListJsonPath.Text = $ofd.FileName }
})
$script:uiState.Controls.chkBringYourOwnApps.Add_Checked({ $script:uiState.Controls.byoApplicationPanel.Visibility = 'Visible' })
$script:uiState.Controls.chkBringYourOwnApps.Add_Unchecked({
$script:uiState.Controls.byoApplicationPanel.Visibility = 'Collapsed'
# Clear fields when hiding
$script:uiState.Controls.txtAppName.Text = ''
$script:uiState.Controls.txtAppCommandLine.Text = ''
$script:uiState.Controls.txtAppArguments.Text = ''
$script:uiState.Controls.txtAppSource.Text = ''
})
$script:uiState.Controls.chkInstallWingetApps.Add_Checked({ $script:uiState.Controls.wingetPanel.Visibility = 'Visible' })
$script:uiState.Controls.chkInstallWingetApps.Add_Unchecked({
$script:uiState.Controls.wingetPanel.Visibility = 'Collapsed'
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Collapsed' # Hide search when unchecked
})
$script:uiState.Controls.btnCheckWingetModule.Add_Click({
param($buttonSender, $clickEventArgs)
$buttonSender.IsEnabled = $false
$window.Cursor = [System.Windows.Input.Cursors]::Wait
# Initial UI update before calling the core function
Update-WingetVersionFields -wingetText "Checking..." -moduleText "Checking..."
$statusResult = $null
try {
# Call the Core function to perform checks and potential install/update
# Pass the UI update function as a callback
$statusResult = Confirm-WingetInstallationUI -UiUpdateCallback {
param($wingetText, $moduleText)
Update-WingetVersionFields -wingetText $wingetText -moduleText $moduleText
}
# Display appropriate message based on the result
if ($statusResult.Success -and $statusResult.UpdateAttempted) {
# Update attempted and successful
[System.Windows.MessageBox]::Show("Winget components installed/updated successfully.", "Winget Installation Complete", "OK", "Information")
}
elseif (-not $statusResult.Success) {
# Error occurred
$errorMessage = if (-not [string]::IsNullOrWhiteSpace($statusResult.Message)) { $statusResult.Message } else { "An unknown error occurred during Winget check/install." }
[System.Windows.MessageBox]::Show($errorMessage, "Winget Error", "OK", "Error")
}
# If Winget components were already up-to-date ($statusResult.Success -eq $true -and $statusResult.UpdateAttempted -eq $false), no message box is shown.
# Show search panel only if the final status is successful and checkbox is still checked
if ($statusResult.Success -and $script:uiState.Controls.chkInstallWingetApps.IsChecked) {
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Visible'
}
else {
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Collapsed' # Hide if not successful or unchecked
}
}
catch {
# Catch errors from the Confirm-WingetInstallationUI call itself (less likely now)
Update-WingetVersionFields -wingetText "Error" -moduleText "Error"
[System.Windows.MessageBox]::Show("Unexpected error checking/installing Winget components: $($_.Exception.Message)", "Error", "OK", "Error")
$script:uiState.Controls.wingetSearchPanel.Visibility = 'Collapsed' # Ensure search is hidden on error
}
finally {
$buttonSender.IsEnabled = $true
$window.Cursor = $null
}
})
$script:uiState.Controls.btnWingetSearch.Add_Click({ Search-WingetApps -State $script:uiState })
$script:uiState.Controls.txtWingetSearch.Add_KeyDown({
param($eventSrc, $keyEvent)
if ($keyEvent.Key -eq 'Return') { Search-WingetApps -State $script:uiState; $keyEvent.Handled = $true }
})
$script:uiState.Controls.btnSaveWingetList.Add_Click({ Save-WingetList -State $script:uiState })
$script:uiState.Controls.btnImportWingetList.Add_Click({ Import-WingetList -State $script:uiState })
$script:uiState.Controls.btnClearWingetList.Add_Click({
$script:uiState.Controls.lstWingetResults.ItemsSource = @() # Set ItemsSource to an empty array
$script:uiState.Controls.txtWingetSearch.Text = ""
if ($script:uiState.Controls.txtStatus) { $script:uiState.Controls.txtStatus.Text = "Cleared all applications from the list" }
})
$script:uiState.Controls.btnDownloadSelected.Add_Click({
param($buttonSender, $clickEventArgs)
$selectedApps = $script:uiState.Controls.lstWingetResults.Items | Where-Object { $_.IsSelected }
if (-not $selectedApps) {
[System.Windows.MessageBox]::Show("No applications selected to download.", "Download Winget Apps", "OK", "Information")
return
}
$buttonSender.IsEnabled = $false
$script:uiState.Controls.pbOverallProgress.Visibility = 'Visible'
$script:uiState.Controls.pbOverallProgress.Value = 0
$script:uiState.Controls.txtStatus.Text = "Starting Winget app downloads..."
# Define necessary task-specific variables locally
$localAppsPath = $script:uiState.Controls.txtApplicationPath.Text
$localAppListJsonPath = $script:uiState.Controls.txtAppListJsonPath.Text
$localWindowsArch = $script:uiState.Controls.cmbWindowsArch.SelectedItem
$localOrchestrationPath = Join-Path -Path $script:uiState.Controls.txtApplicationPath.Text -ChildPath "Orchestration"
# Create hashtable for task-specific arguments to pass to Invoke-ParallelProcessing
$taskArguments = @{
AppsPath = $localAppsPath
AppListJsonPath = $localAppListJsonPath
WindowsArch = $localWindowsArch
OrchestrationPath = $localOrchestrationPath
}
# Select only necessary properties before passing to Invoke-ParallelProcessing
$itemsToProcess = $selectedApps | Select-Object Name, Id, Source, Version # Include Version if needed
# Invoke the centralized parallel processing function
# Pass task type and task-specific arguments
Invoke-ParallelProcessing -ItemsToProcess $itemsToProcess `
-ListViewControl $script:uiState.Controls.lstWingetResults `
-IdentifierProperty 'Id' `
-StatusProperty 'DownloadStatus' `
-TaskType 'WingetDownload' `
-TaskArguments $taskArguments `
-CompletedStatusText "Completed" `
-ErrorStatusPrefix "Error: " `
-WindowObject $window `
-MainThreadLogPath $script:uiState.LogFilePath
# Final status update (handled by Invoke-ParallelProcessing)
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$buttonSender.IsEnabled = $true
})
# BYO Apps UI logic (Keep existing logic)
$script:uiState.Controls.btnBrowseAppSource.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select Application Source Folder"
if ($selectedPath) { $script:uiState.Controls.txtAppSource.Text = $selectedPath }
})
$script:uiState.Controls.btnAddApplication.Add_Click({
$name = $script:uiState.Controls.txtAppName.Text
$commandLine = $script:uiState.Controls.txtAppCommandLine.Text
$arguments = $script:uiState.Controls.txtAppArguments.Text
$source = $script:uiState.Controls.txtAppSource.Text
if ([string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($commandLine) -or [string]::IsNullOrWhiteSpace($arguments)) {
[System.Windows.MessageBox]::Show("Please fill in all fields (Name, Command Line, and Arguments)", "Missing Information", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
return
}
$listView = $script:uiState.Controls.lstApplications
$priority = 1
if ($listView.Items.Count -gt 0) {
$priority = ($listView.Items | Measure-Object -Property Priority -Maximum).Maximum + 1
}
$application = [PSCustomObject]@{ Priority = $priority; Name = $name; CommandLine = $commandLine; Arguments = $arguments; Source = $source; CopyStatus = "" }
$listView.Items.Add($application)
$script:uiState.Controls.txtAppName.Text = ""
$script:uiState.Controls.txtAppCommandLine.Text = ""
$script:uiState.Controls.txtAppArguments.Text = ""
$script:uiState.Controls.txtAppSource.Text = ""
Update-CopyButtonState -State $script:uiState
})
$script:uiState.Controls.btnSaveBYOApplications.Add_Click({
$saveDialog = New-Object Microsoft.Win32.SaveFileDialog
$saveDialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
$saveDialog.DefaultExt = ".json"
$saveDialog.Title = "Save Application List"
$initialDir = $script:uiState.Controls.txtApplicationPath.Text
if ([string]::IsNullOrWhiteSpace($initialDir) -or -not (Test-Path $initialDir)) { $initialDir = $PSScriptRoot }
$saveDialog.InitialDirectory = $initialDir
$saveDialog.FileName = "UserAppList.json"
if ($saveDialog.ShowDialog()) { Save-BYOApplicationList -Path $saveDialog.FileName -State $script:uiState }
})
$script:uiState.Controls.btnLoadBYOApplications.Add_Click({
$openDialog = New-Object Microsoft.Win32.OpenFileDialog
$openDialog.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
$openDialog.Title = "Import Application List"
$initialDir = $script:uiState.Controls.txtApplicationPath.Text
if ([string]::IsNullOrWhiteSpace($initialDir) -or -not (Test-Path $initialDir)) { $initialDir = $PSScriptRoot }
$openDialog.InitialDirectory = $initialDir
if ($openDialog.ShowDialog()) { Import-BYOApplicationList -Path $openDialog.FileName -State $script:uiState; Update-CopyButtonState -State $script:uiState }
})
$script:uiState.Controls.btnClearBYOApplications.Add_Click({
$result = [System.Windows.MessageBox]::Show("Are you sure you want to clear all applications?", "Clear Applications", [System.Windows.MessageBoxButton]::YesNo, [System.Windows.MessageBoxImage]::Question)
if ($result -eq [System.Windows.MessageBoxResult]::Yes) { $script:uiState.Controls.lstApplications.Items.Clear(); Update-CopyButtonState -State $script:uiState }
})
$script:uiState.Controls.btnCopyBYOApps.Add_Click({
param($buttonSender, $clickEventArgs)
$appsToCopy = $script:uiState.Controls.lstApplications.Items | Where-Object { -not [string]::IsNullOrWhiteSpace($_.Source) }
if (-not $appsToCopy) {
[System.Windows.MessageBox]::Show("No applications with a source path specified.", "Copy BYO Apps", "OK", "Information")
return
}
$buttonSender.IsEnabled = $false
$script:uiState.Controls.pbOverallProgress.Visibility = 'Visible'
$script:uiState.Controls.pbOverallProgress.Value = 0
$script:uiState.Controls.txtStatus.Text = "Starting BYO app copy..."
# Define necessary task-specific variables locally
$localAppsPath = $script:uiState.Controls.txtApplicationPath.Text
# Create hashtable for task-specific arguments
$taskArguments = @{
AppsPath = $localAppsPath
}
# Select only necessary properties before passing
$itemsToProcess = $appsToCopy | Select-Object Priority, Name, CommandLine, Arguments, Source
# Invoke the centralized parallel processing function
# Pass task type and task-specific arguments
Invoke-ParallelProcessing -ItemsToProcess $itemsToProcess `
-ListViewControl $script:uiState.Controls.lstApplications `
-IdentifierProperty 'Name' `
-StatusProperty 'CopyStatus' `
-TaskType 'CopyBYO' `
-TaskArguments $taskArguments `
-CompletedStatusText "Copied" `
-ErrorStatusPrefix "Error: " `
-WindowObject $window `
-MainThreadLogPath $script:uiState.LogFilePath
# Final status update (handled by Invoke-ParallelProcessing)
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
$buttonSender.IsEnabled = $true
})
$script:uiState.Controls.btnMoveTop.Add_Click({ Move-ListViewItemTop -ListView $script:uiState.Controls.lstApplications })
$script:uiState.Controls.btnMoveUp.Add_Click({ Move-ListViewItemUp -ListView $script:uiState.Controls.lstApplications })
$script:uiState.Controls.btnMoveDown.Add_Click({ Move-ListViewItemDown -ListView $script:uiState.Controls.lstApplications })
$script:uiState.Controls.btnMoveBottom.Add_Click({ Move-ListViewItemBottom -ListView $script:uiState.Controls.lstApplications })
# BYO Apps ListView setup (Keep existing logic, ensure CopyStatus column is handled)
$byoGridView = $script:uiState.Controls.lstApplications.View
if ($byoGridView -is [System.Windows.Controls.GridView]) {
$copyStatusColumnExists = $false
foreach ($col in $byoGridView.Columns) { if ($col.Header -eq "Copy Status") { $copyStatusColumnExists = $true; break } }
if (-not $copyStatusColumnExists) {
$actionColumnIndex = -1
for ($i = 0; $i -lt $byoGridView.Columns.Count; $i++) { if ($byoGridView.Columns[$i].Header -eq "Action") { $actionColumnIndex = $i; break } }
$copyStatusColumn = New-Object System.Windows.Controls.GridViewColumn
$copyStatusColumn.Header = "Copy Status"; $copyStatusColumn.DisplayMemberBinding = New-Object System.Windows.Data.Binding("CopyStatus"); $copyStatusColumn.Width = 150
if ($actionColumnIndex -ge 0) { $byoGridView.Columns.Insert($actionColumnIndex, $copyStatusColumn) } else { $byoGridView.Columns.Add($copyStatusColumn) }
}
}
Update-CopyButtonState -State $script:uiState # Initial check
# General Browse Button Handlers (Keep existing logic)
$script:uiState.Controls.btnBrowseFFUDevPath.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select FFU Development Path"
if ($selectedPath) { $script:uiState.Controls.txtFFUDevPath.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowseFFUCaptureLocation.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select FFU Capture Location"
if ($selectedPath) { $script:uiState.Controls.txtFFUCaptureLocation.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowseOfficePath.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select Office Path"
if ($selectedPath) { $script:uiState.Controls.txtOfficePath.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowseDriversFolder.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select Drivers Folder"
if ($selectedPath) { $script:uiState.Controls.txtDriversFolder.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowsePEDriversFolder.Add_Click({
$selectedPath = Show-ModernFolderPicker -Title "Select PE Drivers Folder"
if ($selectedPath) { $script:uiState.Controls.txtPEDriversFolder.Text = $selectedPath }
})
$script:uiState.Controls.btnBrowseDriversJsonPath.Add_Click({
$sfd = New-Object System.Windows.Forms.SaveFileDialog
$sfd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
$sfd.Title = "Select or Create Drivers.json File"
$sfd.FileName = "Drivers.json"
$sfd.CheckFileExists = $false # Allow creating a new file or selecting existing
$currentDriversJsonPath = $script:uiState.Controls.txtDriversJsonPath.Text
$dialogInitialDirectory = $null # Initialize to null
if (-not [string]::IsNullOrWhiteSpace($currentDriversJsonPath)) {
WriteLog "Attempting to determine InitialDirectory for Drivers.json SaveFileDialog from txtDriversJsonPath: '$currentDriversJsonPath'"
try {
# Attempt to get the parent directory of the path in the textbox
$parentDir = Split-Path -Path $currentDriversJsonPath -Parent -ErrorAction Stop
# Check if the parent directory is not null/empty and actually exists as a directory
if (-not ([string]::IsNullOrEmpty($parentDir)) -and (Test-Path -Path $parentDir -PathType Container)) {
$dialogInitialDirectory = $parentDir
WriteLog "Set InitialDirectory for SaveFileDialog to '$parentDir' based on parent of txtDriversJsonPath."
}
else {
# Parent directory is invalid or doesn't exist
WriteLog "Parent directory '$parentDir' from txtDriversJsonPath ('$currentDriversJsonPath') is not a valid existing directory. SaveFileDialog will use default InitialDirectory."
# $dialogInitialDirectory remains $null, so dialog uses its default
}
}
catch {
# Error occurred trying to split the path (e.g., path is malformed)
WriteLog "Error splitting path from txtDriversJsonPath ('$currentDriversJsonPath'): $($_.Exception.Message). SaveFileDialog will use default InitialDirectory."
# $dialogInitialDirectory remains $null
}
}
else {
# TextBox is empty, dialog will use its default initial directory
WriteLog "txtDriversJsonPath is empty. SaveFileDialog will use default InitialDirectory."
# $dialogInitialDirectory remains $null
}
$sfd.InitialDirectory = $dialogInitialDirectory # Set to $null if no valid directory was found, dialog will use its default
if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$script:uiState.Controls.txtDriversJsonPath.Text = $sfd.FileName
WriteLog "User selected or created Drivers.json at: $($sfd.FileName)"
}
else {
WriteLog "User cancelled SaveFileDialog for Drivers.json."
}
})
# Driver Checkbox Conditional Logic
$script:uiState.Controls.chkInstallDrivers.Add_Checked({
$script:uiState.Controls.chkCopyDrivers.IsEnabled = $false
$script:uiState.Controls.chkCompressDriversToWIM.IsEnabled = $false
})
$script:uiState.Controls.chkInstallDrivers.Add_Unchecked({
# Only re-enable if the other checkboxes are not checked
if (-not $script:uiState.Controls.chkCopyDrivers.IsChecked) { $script:uiState.Controls.chkCopyDrivers.IsEnabled = $true }
if (-not $script:uiState.Controls.chkCompressDriversToWIM.IsChecked) { $script:uiState.Controls.chkCompressDriversToWIM.IsEnabled = $true }
})
$script:uiState.Controls.chkCopyDrivers.Add_Checked({
$script:uiState.Controls.chkInstallDrivers.IsEnabled = $false
})
$script:uiState.Controls.chkCopyDrivers.Add_Unchecked({
# Only re-enable if InstallDrivers is not checked
if (-not $script:uiState.Controls.chkInstallDrivers.IsChecked) { $script:uiState.Controls.chkInstallDrivers.IsEnabled = $true }
})
$script:uiState.Controls.chkCompressDriversToWIM.Add_Checked({
$script:uiState.Controls.chkInstallDrivers.IsEnabled = $false
})
$script:uiState.Controls.chkCompressDriversToWIM.Add_Unchecked({
# Only re-enable if InstallDrivers is not checked
if (-not $script:uiState.Controls.chkInstallDrivers.IsChecked) { $script:uiState.Controls.chkInstallDrivers.IsEnabled = $true }
})
# Set initial state based on defaults (assuming defaults are false)
$script:uiState.Controls.chkInstallDrivers.IsEnabled = $true
$script:uiState.Controls.chkCopyDrivers.IsEnabled = $true
$script:uiState.Controls.chkCompressDriversToWIM.IsEnabled = $true
# AppsScriptVariables Event Handlers
$script:uiState.Controls.chkDefineAppsScriptVariables.Add_Checked({
$script:uiState.Controls.appsScriptVariablesPanel.Visibility = 'Visible'
})
$script:uiState.Controls.chkDefineAppsScriptVariables.Add_Unchecked({
$script:uiState.Controls.appsScriptVariablesPanel.Visibility = 'Collapsed'
})
$script:uiState.Controls.btnAddAppsScriptVariable.Add_Click({
$key = $script:uiState.Controls.txtAppsScriptKey.Text.Trim()
$value = $script:uiState.Controls.txtAppsScriptValue.Text.Trim()
if ([string]::IsNullOrWhiteSpace($key)) {
[System.Windows.MessageBox]::Show("Apps Script Variable Key cannot be empty.", "Input Error", "OK", "Warning")
return
}
# Check for duplicate keys
$existingKey = $script:uiState.Controls.lstAppsScriptVariables.Items | Where-Object { $_.Key -eq $key }
if ($existingKey) {
[System.Windows.MessageBox]::Show("An Apps Script Variable with the key '$key' already exists.", "Duplicate Key", "OK", "Warning")
return
}
$newItem = [PSCustomObject]@{
IsSelected = $false # Add IsSelected property
Key = $key
Value = $value
}
$script:uiState.Data.appsScriptVariablesDataList.Add($newItem)
$script:uiState.Controls.lstAppsScriptVariables.ItemsSource = $script:uiState.Data.appsScriptVariablesDataList.ToArray()
$script:uiState.Controls.txtAppsScriptKey.Clear()
$script:uiState.Controls.txtAppsScriptValue.Clear()
# Update the header checkbox state
if ($null -ne $script:uiState.Controls.chkSelectAllAppsScriptVariables) {
Update-SelectAllHeaderCheckBoxState -ListView $script:uiState.Controls.lstAppsScriptVariables -HeaderCheckBox $script:uiState.Controls.chkSelectAllAppsScriptVariables
}
})
$script:uiState.Controls.btnRemoveSelectedAppsScriptVariables.Add_Click({
$itemsToRemove = @($script:uiState.Data.appsScriptVariablesDataList | Where-Object { $_.IsSelected })
if ($itemsToRemove.Count -eq 0) {
[System.Windows.MessageBox]::Show("Please select one or more Apps Script Variables to remove.", "Selection Error", "OK", "Warning")
return
}
foreach ($itemToRemove in $itemsToRemove) {
$script:uiState.Data.appsScriptVariablesDataList.Remove($itemToRemove)
}
$script:uiState.Controls.lstAppsScriptVariables.ItemsSource = $script:uiState.Data.appsScriptVariablesDataList.ToArray()
# Update the header checkbox state
if ($null -ne $script:uiState.Controls.chkSelectAllAppsScriptVariables) {
# Check if variable exists
Update-SelectAllHeaderCheckBoxState -ListView $script:uiState.Controls.lstAppsScriptVariables -HeaderCheckBox $script:uiState.Controls.chkSelectAllAppsScriptVariables
}
})
$script:uiState.Controls.btnClearAppsScriptVariables.Add_Click({
$script:uiState.Data.appsScriptVariablesDataList.Clear()
$script:uiState.Controls.lstAppsScriptVariables.ItemsSource = $script:uiState.Data.appsScriptVariablesDataList.ToArray()
# Update the header checkbox state
if ($null -ne $script:uiState.Controls.chkSelectAllAppsScriptVariables) {
Update-SelectAllHeaderCheckBoxState -ListView $script:uiState.Controls.lstAppsScriptVariables -HeaderCheckBox $script:uiState.Controls.chkSelectAllAppsScriptVariables
}
})
# Initial state for chkDefineAppsScriptVariables based on chkInstallApps
if ($script:uiState.Controls.chkInstallApps.IsChecked) {
$script:uiState.Controls.chkDefineAppsScriptVariables.Visibility = 'Visible'
}
else {
$script:uiState.Controls.chkDefineAppsScriptVariables.Visibility = 'Collapsed'
}
# Initial state for appsScriptVariablesPanel based on chkDefineAppsScriptVariables
if ($script:uiState.Controls.chkDefineAppsScriptVariables.IsChecked) {
$script:uiState.Controls.appsScriptVariablesPanel.Visibility = 'Visible'
}
else {
$script:uiState.Controls.appsScriptVariablesPanel.Visibility = 'Collapsed'
}
})
# Button: Build FFU
$btnRun = $window.FindName('btnRun')
$btnRun.Add_Click({
try {
$progressBar = $script:uiState.Controls.pbOverallProgress
$txtStatus = $script:uiState.Controls.txtStatus
$progressBar.Visibility = 'Visible'
$txtStatus.Text = "Starting FFU build..."
$config = Get-UIConfig -State $script:uiState
$configFilePath = Join-Path $config.FFUDevelopmentPath "FFUConfig.json"
$config | ConvertTo-Json -Depth 10 | Set-Content $configFilePath -Encoding UTF8
$txtStatus.Text = "Executing BuildFFUVM script with config file..."
& "$PSScriptRoot\BuildFFUVM.ps1" -ConfigFile $configFilePath
if ($config.InstallOffice -and $config.OfficeConfigXMLFile) {
Copy-Item -Path $config.OfficeConfigXMLFile -Destination $config.OfficePath -Force
$txtStatus.Text = "Office Configuration XML file copied successfully."
}
$txtStatus.Text = "FFU build completed successfully."
}
catch {
[System.Windows.MessageBox]::Show("An error occurred: $_", "Error", "OK", "Error")
$script:uiState.Controls.txtStatus.Text = "FFU build failed."
}
finally {
$script:uiState.Controls.pbOverallProgress.Visibility = 'Collapsed'
}
})
# Button: Build Config
$btnBuildConfig = $window.FindName('btnBuildConfig')
$btnBuildConfig.Add_Click({
try {
$config = Get-UIConfig -State $script:uiState
$defaultConfigPath = Join-Path $config.FFUDevelopmentPath "config"
if (-not (Test-Path $defaultConfigPath)) {
New-Item -Path $defaultConfigPath -ItemType Directory -Force | Out-Null
}
$sfd = New-Object System.Windows.Forms.SaveFileDialog
$sfd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
$sfd.Title = "Save Configuration File"
$sfd.InitialDirectory = $defaultConfigPath
$sfd.FileName = "FFUConfig.json"
if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$savePath = $sfd.FileName
$config | ConvertTo-Json -Depth 10 | Set-Content $savePath -Encoding UTF8
[System.Windows.MessageBox]::Show("Configuration file saved to:`n$savePath", "Success", "OK", "Information")
}
}
catch {
[System.Windows.MessageBox]::Show("Error saving config file:`n$_", "Error", "OK", "Error")
}
})
# Button: Load Config File
$btnLoadConfig = $window.FindName('btnLoadConfig')
$btnLoadConfig.Add_Click({
try {
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
$ofd.Title = "Load Configuration File"
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
WriteLog "Loading configuration from: $($ofd.FileName)"
$configContent = Get-Content -Path $ofd.FileName -Raw | ConvertFrom-Json
if ($null -eq $configContent) {
WriteLog "LoadConfig Error: configContent is null after parsing $($ofd.FileName). File might be empty or malformed."
[System.Windows.MessageBox]::Show("Failed to parse the configuration file. It might be empty or not valid JSON.", "Load Error", "OK", "Error")
return
}
WriteLog "LoadConfig: Successfully parsed config file. Top-level keys: $($configContent.PSObject.Properties.Name -join ', ')"
# Update Build tab values
Set-UIValue -ControlName 'txtFFUDevPath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'FFUDevelopmentPath' -State $script:uiState
Set-UIValue -ControlName 'txtCustomFFUNameTemplate' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'CustomFFUNameTemplate' -State $script:uiState
Set-UIValue -ControlName 'txtFFUCaptureLocation' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'FFUCaptureLocation' -State $script:uiState
Set-UIValue -ControlName 'txtShareName' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'ShareName' -State $script:uiState
Set-UIValue -ControlName 'txtUsername' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'Username' -State $script:uiState
Set-UIValue -ControlName 'chkBuildUSBDriveEnable' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'BuildUSBDrive' -State $script:uiState
Set-UIValue -ControlName 'chkCompactOS' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CompactOS' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateADK' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateADK' -State $script:uiState
Set-UIValue -ControlName 'chkOptimize' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'Optimize' -State $script:uiState
Set-UIValue -ControlName 'chkAllowVHDXCaching' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'AllowVHDXCaching' -State $script:uiState
Set-UIValue -ControlName 'chkAllowExternalHardDiskMedia' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'AllowExternalHardDiskMedia' -State $script:uiState
Set-UIValue -ControlName 'chkPromptExternalHardDiskMedia' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'PromptExternalHardDiskMedia' -State $script:uiState
Set-UIValue -ControlName 'chkCreateCaptureMedia' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CreateCaptureMedia' -State $script:uiState
Set-UIValue -ControlName 'chkCreateDeploymentMedia' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CreateDeploymentMedia' -State $script:uiState
# USB Drive Modification group (Build Tab)
Set-UIValue -ControlName 'chkCopyAutopilot' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyAutopilot' -State $script:uiState
Set-UIValue -ControlName 'chkCopyUnattend' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyUnattend' -State $script:uiState
Set-UIValue -ControlName 'chkCopyPPKG' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyPPKG' -State $script:uiState
# Post Build Cleanup group (Build Tab)
Set-UIValue -ControlName 'chkCleanupAppsISO' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CleanupAppsISO' -State $script:uiState
Set-UIValue -ControlName 'chkCleanupCaptureISO' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CleanupCaptureISO' -State $script:uiState
Set-UIValue -ControlName 'chkCleanupDeployISO' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CleanupDeployISO' -State $script:uiState
Set-UIValue -ControlName 'chkCleanupDrivers' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CleanupDrivers' -State $script:uiState
Set-UIValue -ControlName 'chkRemoveFFU' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'RemoveFFU' -State $script:uiState
Set-UIValue -ControlName 'chkRemoveApps' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'RemoveApps' -State $script:uiState
Set-UIValue -ControlName 'chkRemoveUpdates' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'RemoveUpdates' -State $script:uiState
# Hyper-V Settings
Set-UIValue -ControlName 'cmbVMSwitchName' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'VMSwitchName' -State $script:uiState
Set-UIValue -ControlName 'txtVMHostIPAddress' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'VMHostIPAddress' -State $script:uiState
Set-UIValue -ControlName 'txtDiskSize' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'Disksize' -TransformValue { param($val) $val / 1GB } -State $script:uiState
Set-UIValue -ControlName 'txtMemory' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'Memory' -TransformValue { param($val) $val / 1GB } -State $script:uiState
Set-UIValue -ControlName 'txtProcessors' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'Processors' -State $script:uiState
Set-UIValue -ControlName 'txtVMLocation' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'VMLocation' -State $script:uiState
Set-UIValue -ControlName 'txtVMNamePrefix' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'FFUPrefix' -State $script:uiState
Set-UIValue -ControlName 'cmbLogicalSectorSize' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'LogicalSectorSizeBytes' -TransformValue { param($val) $val.ToString() } -State $script:uiState
# Windows Settings
Set-UIValue -ControlName 'txtISOPath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'ISOPath' -State $script:uiState
Set-UIValue -ControlName 'cmbWindowsRelease' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'WindowsRelease' -State $script:uiState
Set-UIValue -ControlName 'cmbWindowsVersion' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'WindowsVersion' -State $script:uiState
Set-UIValue -ControlName 'cmbWindowsArch' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'WindowsArch' -State $script:uiState
Set-UIValue -ControlName 'cmbWindowsLang' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'WindowsLang' -State $script:uiState
Set-UIValue -ControlName 'cmbWindowsSKU' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'WindowsSKU' -State $script:uiState
Set-UIValue -ControlName 'cmbMediaType' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'MediaType' -State $script:uiState
Set-UIValue -ControlName 'txtProductKey' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'ProductKey' -State $script:uiState
Set-UIValue -ControlName 'txtOptionalFeatures' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'OptionalFeatures' -State $script:uiState
# Update Optional Features checkboxes based on the loaded text
$loadedFeaturesString = $script:uiState.Controls.txtOptionalFeatures.Text
if (-not [string]::IsNullOrWhiteSpace($loadedFeaturesString)) {
$loadedFeaturesArray = $loadedFeaturesString.Split(';')
WriteLog "LoadConfig: Updating Optional Features checkboxes. Loaded features: $($loadedFeaturesArray -join ', ')"
foreach ($featureEntry in $script:uiState.Controls.featureCheckBoxes.GetEnumerator()) {
$featureName = $featureEntry.Key
$featureCheckbox = $featureEntry.Value
if ($loadedFeaturesArray -contains $featureName) {
$featureCheckbox.IsChecked = $true
WriteLog "LoadConfig: Checked checkbox for feature '$featureName'."
}
else {
$featureCheckbox.IsChecked = $false
}
}
}
else {
# If no optional features are loaded, uncheck all
WriteLog "LoadConfig: No optional features string loaded. Unchecking all feature checkboxes."
foreach ($featureEntry in $script:uiState.Controls.featureCheckBoxes.GetEnumerator()) {
$featureEntry.Value.IsChecked = $false
}
}
# M365 Apps/Office tab
Set-UIValue -ControlName 'chkInstallOffice' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'InstallOffice' -State $script:uiState
Set-UIValue -ControlName 'txtOfficePath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'OfficePath' -State $script:uiState
Set-UIValue -ControlName 'chkCopyOfficeConfigXML' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyOfficeConfigXML' -State $script:uiState
Set-UIValue -ControlName 'txtOfficeConfigXMLFilePath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'OfficeConfigXMLFile' -State $script:uiState
# Drivers tab
Set-UIValue -ControlName 'chkInstallDrivers' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'InstallDrivers' -State $script:uiState
Set-UIValue -ControlName 'chkDownloadDrivers' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'DownloadDrivers' -State $script:uiState
Set-UIValue -ControlName 'chkCopyDrivers' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyDrivers' -State $script:uiState
Set-UIValue -ControlName 'cmbMake' -PropertyName 'SelectedItem' -ConfigObject $configContent -ConfigKey 'Make' -State $script:uiState
Set-UIValue -ControlName 'txtDriversFolder' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'DriversFolder' -State $script:uiState
Set-UIValue -ControlName 'txtPEDriversFolder' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'PEDriversFolder' -State $script:uiState
Set-UIValue -ControlName 'txtDriversJsonPath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'DriversJsonPath' -State $script:uiState
Set-UIValue -ControlName 'chkCopyPEDrivers' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CopyPEDrivers' -State $script:uiState
Set-UIValue -ControlName 'chkCompressDriversToWIM' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'CompressDownloadedDriversToWim' -State $script:uiState
# Updates tab
Set-UIValue -ControlName 'chkUpdateLatestCU' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateLatestCU' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateLatestNet' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateLatestNet' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateLatestDefender' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateLatestDefender' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateEdge' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateEdge' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateOneDrive' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateOneDrive' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateLatestMSRT' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateLatestMSRT' -State $script:uiState
Set-UIValue -ControlName 'chkUpdateLatestMicrocode' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdateLatestMicrocode' -State $script:uiState
Set-UIValue -ControlName 'chkUpdatePreviewCU' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'UpdatePreviewCU' -State $script:uiState
# Applications tab
Set-UIValue -ControlName 'chkInstallApps' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'InstallApps' -State $script:uiState
Set-UIValue -ControlName 'chkInstallWingetApps' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'InstallWingetApps' -State $script:uiState
Set-UIValue -ControlName 'chkBringYourOwnApps' -PropertyName 'IsChecked' -ConfigObject $configContent -ConfigKey 'BringYourOwnApps' -State $script:uiState
Set-UIValue -ControlName 'txtApplicationPath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'AppsPath' -State $script:uiState
Set-UIValue -ControlName 'txtAppListJsonPath' -PropertyName 'Text' -ConfigObject $configContent -ConfigKey 'AppListPath' -State $script:uiState
# Handle AppsScriptVariables
$appsScriptVarsKeyExists = $false
if ($configContent -is [System.Management.Automation.PSCustomObject] -and $null -ne $configContent.PSObject.Properties) {
try {
if (($configContent.PSObject.Properties.Match('AppsScriptVariables')).Count -gt 0) {
$appsScriptVarsKeyExists = $true
}
}
catch { WriteLog "ERROR: Exception while trying to Match key 'AppsScriptVariables'. Error: $($_.Exception.Message)" }
}
$lstAppsScriptVars = $script:uiState.Controls.lstAppsScriptVariables
$chkDefineAppsScriptVars = $script:uiState.Controls.chkDefineAppsScriptVariables
$appsScriptVarsPanel = $script:uiState.Controls.appsScriptVariablesPanel
$script:uiState.Data.appsScriptVariablesDataList.Clear()
if ($appsScriptVarsKeyExists -and $null -ne $configContent.AppsScriptVariables -and $configContent.AppsScriptVariables -is [System.Management.Automation.PSCustomObject]) {
WriteLog "LoadConfig: Processing AppsScriptVariables from config."
$loadedVars = $configContent.AppsScriptVariables
$hasVars = $false
foreach ($prop in $loadedVars.PSObject.Properties) {
$script:uiState.Data.appsScriptVariablesDataList.Add([PSCustomObject]@{ IsSelected = $false; Key = $prop.Name; Value = $prop.Value })
$hasVars = $true
}
if ($hasVars) {
$chkDefineAppsScriptVars.IsChecked = $true
$appsScriptVarsPanel.Visibility = 'Visible'
WriteLog "LoadConfig: Loaded AppsScriptVariables and checked 'Define Apps Script Variables'."
}
else {
$chkDefineAppsScriptVars.IsChecked = $false
$appsScriptVarsPanel.Visibility = 'Collapsed'
WriteLog "LoadConfig: AppsScriptVariables key was present but empty. Unchecked 'Define Apps Script Variables'."
}
}
elseif ($appsScriptVarsKeyExists -and $null -ne $configContent.AppsScriptVariables -and $configContent.AppsScriptVariables -is [hashtable]) {
# Handle if it's already a hashtable (e.g., from older config or direct creation)
WriteLog "LoadConfig: Processing AppsScriptVariables (Hashtable) from config."
$loadedVars = $configContent.AppsScriptVariables
$hasVars = $false
foreach ($keyName in $loadedVars.Keys) {
$script:uiState.Data.appsScriptVariablesDataList.Add([PSCustomObject]@{ IsSelected = $false; Key = $keyName; Value = $loadedVars[$keyName] })
$hasVars = $true
}
if ($hasVars) {
$chkDefineAppsScriptVars.IsChecked = $true
$appsScriptVarsPanel.Visibility = 'Visible'
WriteLog "LoadConfig: Loaded AppsScriptVariables (Hashtable) and checked 'Define Apps Script Variables'."
}
else {
$chkDefineAppsScriptVars.IsChecked = $false
$appsScriptVarsPanel.Visibility = 'Collapsed'
WriteLog "LoadConfig: AppsScriptVariables (Hashtable) key was present but empty. Unchecked 'Define Apps Script Variables'."
}
}
else {
$chkDefineAppsScriptVars.IsChecked = $false
$appsScriptVarsPanel.Visibility = 'Collapsed'
WriteLog "LoadConfig Info: Key 'AppsScriptVariables' not found, is null, or not a PSCustomObject/Hashtable. Unchecked 'Define Apps Script Variables'."
}
# Update the ListView's ItemsSource after populating the data list
$lstAppsScriptVars.ItemsSource = $script:uiState.Data.appsScriptVariablesDataList.ToArray()
# Update the header checkbox state
if ($null -ne (Get-Variable -Name 'chkSelectAllAppsScriptVariables' -Scope Script -ErrorAction SilentlyContinue)) {
Update-SelectAllHeaderCheckBoxState -ListView $lstAppsScriptVars -HeaderCheckBox $script:chkSelectAllAppsScriptVariables
}
# Update USB Drive selection if present in config
$usbDriveListKeyExists = $false
if ($configContent -is [System.Management.Automation.PSCustomObject] -and $null -ne $configContent.PSObject.Properties) {
try {
if (($configContent.PSObject.Properties.Match('USBDriveList')).Count -gt 0) {
$usbDriveListKeyExists = $true
}
}
catch {
WriteLog "ERROR: Exception while trying to Match key 'USBDriveList' on configContent.PSObject.Properties. Error: $($_.Exception.Message)"
}
}
if ($usbDriveListKeyExists -and $null -ne $configContent.USBDriveList) {
WriteLog "LoadConfig: Processing USBDriveList from config."
# First click the Check USB Drives button to populate the list
$script:uiState.Controls.btnCheckUSBDrives.RaiseEvent(
[System.Windows.RoutedEventArgs]::new(
[System.Windows.Controls.Button]::ClickEvent
)
)
# Then select the drives that match the saved configuration
foreach ($item in $script:uiState.Controls.lstUSBDrives.Items) {
$propertyName = $item.Model
$propertyExists = $false
$propertyValue = $null
# Ensure USBDriveList is a PSCustomObject before trying to access its properties dynamically
if ($null -ne $configContent.USBDriveList -and $configContent.USBDriveList -is [System.Management.Automation.PSCustomObject]) {
# Check if the property exists on the USBDriveList object
if ($configContent.USBDriveList.PSObject.Properties.Match($propertyName).Count -gt 0) {
$propertyExists = $true
# Access the value dynamically
$propertyValue = $configContent.USBDriveList.$($propertyName)
}
}
if ($propertyExists -and ($propertyValue -eq $item.SerialNumber)) {
WriteLog "LoadConfig: Selecting USB Drive Model '$($item.Model)' with Serial '$($item.SerialNumber)'."
$item.IsSelected = $true
}
else {
if (-not $propertyExists -and ($null -ne $configContent.USBDriveList)) {
WriteLog "LoadConfig: Property '$($propertyName)' not found on USBDriveList for item Model '$($item.Model)'."
}
$item.IsSelected = $false # Ensure others are deselected if not in config or value mismatch
}
}
$script:uiState.Controls.lstUSBDrives.Items.Refresh()
# Update the Select All checkbox state
$allSelected = $script:uiState.Controls.lstUSBDrives.Items.Count -gt 0 -and -not ($script:uiState.Controls.lstUSBDrives.Items | Where-Object { -not $_.IsSelected })
$script:uiState.Controls.chkSelectAllUSBDrives.IsChecked = $allSelected
WriteLog "LoadConfig: USBDriveList processing complete."
}
else {
WriteLog "LoadConfig Info: Key 'USBDriveList' not found or is null in configuration file. Skipping USB drive selection."
}
# If BuildUSBDrive is enabled and USBDriveList was present and not empty in the config,
# ensure "Select Specific USB Drives" is checked to show the list.
$shouldAutoCheckSpecificDrives = $false
if ($window.FindName('chkBuildUSBDriveEnable').IsChecked -and $usbDriveListKeyExists -and ($null -ne $configContent.USBDriveList)) {
if ($configContent.USBDriveList -is [System.Management.Automation.PSCustomObject]) {
if ($configContent.USBDriveList.PSObject.Properties.Count -gt 0) {
$shouldAutoCheckSpecificDrives = $true
}
}
elseif ($configContent.USBDriveList -is [hashtable]) {
# Fallback for older configs
if ($configContent.USBDriveList.Keys.Count -gt 0) {
$shouldAutoCheckSpecificDrives = $true
}
}
}
if ($shouldAutoCheckSpecificDrives) {
WriteLog "LoadConfig: Auto-checking 'Select Specific USB Drives' due to pre-selected USB drives in config."
$script:uiState.Controls.chkSelectSpecificUSBDrives.IsChecked = $true
}
else {
WriteLog "LoadConfig: Condition to auto-check 'Select Specific USB Drives' was NOT met."
}
WriteLog "LoadConfig: Configuration loading process finished."
}
}
catch {
WriteLog "LoadConfig FATAL Error: $($_.Exception.ToString())" # Log full exception details
[System.Windows.MessageBox]::Show("Error loading config file:`n$($_.Exception.Message)", "Error", "OK", "Error")
}
})
# Add handler for Remove button clicks
$window.Add_SourceInitialized({
$listView = $window.FindName('lstApplications')
$listView.AddHandler(
[System.Windows.Controls.Button]::ClickEvent,
[System.Windows.RoutedEventHandler] {
param($buttonSender, $clickEventArgs)
if ($clickEventArgs.OriginalSource -is [System.Windows.Controls.Button] -and $clickEventArgs.OriginalSource.Content -eq "Remove") {
Remove-Application -priority $clickEventArgs.OriginalSource.Tag -State $script:uiState
}
}
)
})
# Register cleanup to reclaim memory and revert LongPathsEnabled setting when the UI window closes
$window.Add_Closed({
# Revert LongPathsEnabled registry setting if it was changed by this script
if ($script:uiState.Flags.originalLongPathsValue -ne 1) {
# Only revert if we changed it from something other than 1
try {
$currentValue = Get-ItemPropertyValue -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -ErrorAction SilentlyContinue
if ($currentValue -eq 1) {
# Double-check it's still 1 before reverting
$revertValue = if ($null -eq $script:uiState.Flags.originalLongPathsValue) { 0 } else { $script:uiState.Flags.originalLongPathsValue } # Revert to original or 0 if it didn't exist
WriteLog "Reverting LongPathsEnabled registry key back to original value ($revertValue)."
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value $revertValue -Force
WriteLog "LongPathsEnabled reverted."
}
}
catch {
WriteLog "Error reverting LongPathsEnabled registry key: $($_.Exception.Message)."
}
}
# Garbage collection
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
})
[void]$window.ShowDialog()