mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
c5730230f6
Refactor functions to use a centralized state object Modifies various functions to accept and utilize a `$State` parameter, replacing direct access to `$script:uiState` and module-scoped variables. This change centralizes state management, improving code consistency and maintainability by passing necessary UI elements, data, and default configurations explicitly.
3423 lines
199 KiB
PowerShell
3423 lines
199 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
|
|
$AppsPath = "$FFUDevelopmentPath\Apps"
|
|
$AppListJsonPath = "$AppsPath\AppList.json"
|
|
$UserAppListJsonPath = "$AppsPath\UserAppList.json" # Define path for UserAppList.json
|
|
#Microsoft sites will intermittently fail on downloads. These headers are to help with that.
|
|
$Headers = @{
|
|
"Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
|
|
"Accept-Encoding" = "gzip, deflate, br, zstd"
|
|
"Accept-Language" = "en-US,en;q=0.9"
|
|
"Priority" = "u=0, i"
|
|
"Sec-Ch-Ua" = "`"Microsoft Edge`";v=`"125`", `"Chromium`";v=`"125`", `"Not.A/Brand`";v=`"24`""
|
|
"Sec-Ch-Ua-Mobile" = "?0"
|
|
"Sec-Ch-Ua-Platform" = "`"Windows`""
|
|
"Sec-Fetch-Dest" = "document"
|
|
"Sec-Fetch-Mode" = "navigate"
|
|
"Sec-Fetch-Site" = "none"
|
|
"Sec-Fetch-User" = "?1"
|
|
"Upgrade-Insecure-Requests" = "1"
|
|
}
|
|
$UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0'
|
|
|
|
# --- NEW: Central State Object ---
|
|
$script:uiState = [PSCustomObject]@{
|
|
Window = $null;
|
|
Controls = @{
|
|
featureCheckBoxes = @{}; # Moved from script scope
|
|
UpdateInstallAppsBasedOnUpdates = $null # Placeholder for the scriptblock
|
|
};
|
|
Data = @{
|
|
allDriverModels = [System.Collections.Generic.List[PSCustomObject]]::new();
|
|
appsScriptVariablesDataList = [System.Collections.Generic.List[PSCustomObject]]::new();
|
|
versionData = $null; # Will be initialized later
|
|
vmSwitchMap = @{} # To store Hyper-V switch to IP mapping
|
|
};
|
|
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\common\FFU.Common.Core.psm1"
|
|
# Import the Core UI Logic Module
|
|
Import-Module "$PSScriptRoot\FFUUI.Core\FFUUI.Core.psm1"
|
|
|
|
# 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."
|
|
# Optionally show a warning to the user if this fails?
|
|
# [System.Windows.MessageBox]::Show("Could not enable long path support. Some operations might fail.", "Warning", "OK", "Warning")
|
|
}
|
|
}
|
|
else {
|
|
WriteLog "LongPathsEnabled is already set to 1."
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# SECTION: LOAD UI
|
|
# ----------------------------------------------------------------------------
|
|
|
|
# Helper function to safely set UI properties from config and log the process
|
|
function Set-UIValue {
|
|
param(
|
|
[string]$ControlName,
|
|
[string]$PropertyName,
|
|
[object]$ConfigObject,
|
|
[string]$ConfigKey,
|
|
[scriptblock]$TransformValue = $null, # Optional scriptblock to transform the value from config
|
|
[psobject]$State # Pass the $State object
|
|
)
|
|
|
|
$control = $State.Controls[$ControlName]
|
|
if ($null -eq $control) {
|
|
WriteLog "LoadConfig Error: Control '$ControlName' not found in the state object."
|
|
return
|
|
}
|
|
|
|
# Robust check for property existence.
|
|
$keyExists = $false
|
|
if ($ConfigObject -is [System.Management.Automation.PSCustomObject] -and $null -ne $ConfigObject.PSObject.Properties) {
|
|
# Use the Match() method, which returns a collection of matching properties.
|
|
# If the count is greater than 0, the key exists.
|
|
try {
|
|
if (($ConfigObject.PSObject.Properties.Match($ConfigKey)).Count -gt 0) {
|
|
$keyExists = $true
|
|
}
|
|
}
|
|
catch {
|
|
WriteLog "ERROR: Exception while trying to Match key '$ConfigKey' on ConfigObject.PSObject.Properties. Error: $($_.Exception.Message)"
|
|
# $keyExists remains false
|
|
}
|
|
}
|
|
|
|
if (-not $keyExists) {
|
|
WriteLog "LoadConfig Info: Key '$ConfigKey' not found in configuration object. Skipping '$ControlName.$PropertyName'."
|
|
return
|
|
}
|
|
|
|
$valueFromConfig = $ConfigObject.$ConfigKey
|
|
WriteLog "LoadConfig: Preparing to set '$ControlName.$PropertyName'. Config key: '$ConfigKey', Raw value: '$valueFromConfig'."
|
|
|
|
$finalValue = $valueFromConfig
|
|
if ($null -ne $TransformValue) {
|
|
try {
|
|
$finalValue = Invoke-Command -ScriptBlock $TransformValue -ArgumentList $valueFromConfig
|
|
WriteLog "LoadConfig: Transformed value for '$ControlName.$PropertyName' (from key '$ConfigKey') is: '$finalValue'."
|
|
}
|
|
catch {
|
|
WriteLog "LoadConfig Error: Failed to transform value for '$ControlName.$PropertyName' from key '$ConfigKey'. Error: $($_.Exception.Message)"
|
|
return
|
|
}
|
|
}
|
|
|
|
try {
|
|
# Handle ComboBox SelectedItem specifically
|
|
if ($control -is [System.Windows.Controls.ComboBox] -and $PropertyName -eq 'SelectedItem') {
|
|
$itemToSelect = $null
|
|
# Iterate through the Items collection of the ComboBox
|
|
foreach ($item in $control.Items) {
|
|
$itemValue = $null
|
|
if ($item -is [System.Windows.Controls.ComboBoxItem]) {
|
|
$itemValue = $item.Content
|
|
}
|
|
elseif ($item -is [pscustomobject] -and $item.PSObject.Properties['Value']) {
|
|
$itemValue = $item.Value
|
|
}
|
|
elseif ($item -is [pscustomobject] -and $item.PSObject.Properties['Display']) {
|
|
# Assuming 'Display' might be used if 'Value' isn't
|
|
$itemValue = $item.Display
|
|
}
|
|
else {
|
|
$itemValue = $item # For simple string items or direct object comparison
|
|
}
|
|
|
|
# Compare, ensuring types are compatible or converting $finalValue if necessary
|
|
if (($null -ne $itemValue -and $itemValue.ToString() -eq $finalValue.ToString()) -or ($item -eq $finalValue)) {
|
|
$itemToSelect = $item
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($null -ne $itemToSelect) {
|
|
$control.SelectedItem = $itemToSelect
|
|
WriteLog "LoadConfig: Successfully set '$ControlName.SelectedItem' by finding matching item for value '$finalValue'."
|
|
}
|
|
elseif ($control.IsEditable -and ($finalValue -is [string] -or $finalValue -is [int] -or $finalValue -is [long])) {
|
|
$control.Text = $finalValue.ToString()
|
|
WriteLog "LoadConfig: Set '$ControlName.Text' to '$($finalValue.ToString())' as SelectedItem match failed (editable ComboBox)."
|
|
}
|
|
else {
|
|
$itemsString = ""
|
|
try {
|
|
# Safer way to get item strings
|
|
$itemStrings = @()
|
|
foreach ($cbItem in $control.Items) {
|
|
if ($null -ne $cbItem) { $itemStrings += $cbItem.ToString() } else { $itemStrings += "[NULL_ITEM]" }
|
|
}
|
|
$itemsString = $itemStrings -join "; "
|
|
}
|
|
catch { $itemsString = "Error retrieving item strings." }
|
|
WriteLog "LoadConfig Warning: Could not find or set item matching value '$finalValue' for '$ControlName.SelectedItem'. Current items: [$itemsString]"
|
|
}
|
|
}
|
|
else {
|
|
# For other properties or controls
|
|
$control.$PropertyName = $finalValue
|
|
WriteLog "LoadConfig: Successfully set '$ControlName.$PropertyName' to '$finalValue'."
|
|
}
|
|
}
|
|
catch {
|
|
WriteLog "LoadConfig Error: Failed to set '$ControlName.$PropertyName' to '$finalValue'. Error: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# --------------------------------------------------------------------------
|
|
# SECTION: Driver Download Functions
|
|
# --------------------------------------------------------------------------
|
|
|
|
# Helper function to convert raw driver objects to a standardized format
|
|
function ConvertTo-StandardizedDriverModel {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[PSCustomObject]$RawDriverObject,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Make,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
|
|
$modelDisplay = $RawDriverObject.Model # Default
|
|
$id = $RawDriverObject.Model # Default
|
|
$link = $null
|
|
$productName = $null
|
|
$machineType = $null
|
|
|
|
if ($RawDriverObject.PSObject.Properties['Link']) {
|
|
$link = $RawDriverObject.Link
|
|
}
|
|
|
|
# Lenovo specific handling
|
|
if ($Make -eq 'Lenovo') {
|
|
# RawDriverObject.Model is "ProductName (MachineType)" from Get-LenovoDriversModelList
|
|
# RawDriverObject.ProductName is "ProductName"
|
|
# RawDriverObject.MachineType is "MachineType"
|
|
$modelDisplay = $RawDriverObject.Model # This is already "ProductName (MachineType)"
|
|
$productName = $RawDriverObject.ProductName
|
|
$machineType = $RawDriverObject.MachineType
|
|
$id = $RawDriverObject.MachineType # Use MachineType as a more specific ID for Lenovo backend operations if needed
|
|
}
|
|
|
|
return [PSCustomObject]@{
|
|
IsSelected = $false
|
|
Make = $Make
|
|
Model = $modelDisplay # Primary display string, used as identifier in ListView
|
|
Link = $link
|
|
Id = $id # Technical/unique identifier (e.g., MachineType for Lenovo)
|
|
ProductName = $productName # Specific for Lenovo
|
|
MachineType = $machineType # Specific for Lenovo
|
|
Version = "" # Placeholder
|
|
Type = "" # Placeholder
|
|
Size = "" # Placeholder
|
|
Arch = "" # Placeholder
|
|
DownloadStatus = "" # Initial download status
|
|
}
|
|
}
|
|
|
|
# Helper function to get models for a selected Make and standardize them
|
|
function Get-ModelsForMake {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$SelectedMake,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
|
|
$standardizedModels = [System.Collections.Generic.List[PSCustomObject]]::new()
|
|
$rawModels = @()
|
|
|
|
# Get necessary values from UI or script scope
|
|
$localDriversFolder = $State.Controls.txtDriversFolder.Text
|
|
$localWindowsRelease = $null
|
|
if ($null -ne $State.Controls.cmbWindowsRelease.SelectedItem) {
|
|
$localWindowsRelease = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
|
}
|
|
|
|
# $Headers and $UserAgent are available from script scope
|
|
|
|
if (-not $localWindowsRelease -and ($SelectedMake -eq 'Dell' -or $SelectedMake -eq 'Lenovo')) {
|
|
[System.Windows.MessageBox]::Show("Please select a Windows Release first for $SelectedMake.", "Missing Information", "OK", "Warning")
|
|
throw "Windows Release not selected for $SelectedMake."
|
|
}
|
|
|
|
switch ($SelectedMake) {
|
|
'Microsoft' {
|
|
$rawModels = Get-MicrosoftDriversModelList -Headers $Headers -UserAgent $UserAgent
|
|
}
|
|
'Dell' {
|
|
$rawModels = Get-DellDriversModelList -WindowsRelease $localWindowsRelease -DriversFolder $localDriversFolder -Make $SelectedMake
|
|
}
|
|
'HP' {
|
|
$rawModels = Get-HPDriversModelList -DriversFolder $localDriversFolder -Make $SelectedMake
|
|
}
|
|
'Lenovo' {
|
|
$modelSearchTerm = [Microsoft.VisualBasic.Interaction]::InputBox("Enter Lenovo Model Name or Machine Type (e.g., T480 or 20L5):", "Lenovo Model Search", "")
|
|
if ([string]::IsNullOrWhiteSpace($modelSearchTerm)) {
|
|
# User cancelled or entered nothing
|
|
return @()
|
|
}
|
|
$State.Controls.txtStatus.Text = "Searching Lenovo models for '$modelSearchTerm'..."
|
|
$rawModels = Get-LenovoDriversModelList -ModelSearchTerm $modelSearchTerm -Headers $Headers -UserAgent $UserAgent
|
|
}
|
|
default {
|
|
[System.Windows.MessageBox]::Show("Selected Make '$SelectedMake' is not supported for automatic model retrieval.", "Unsupported Make", "OK", "Warning")
|
|
return @()
|
|
}
|
|
}
|
|
|
|
if ($null -ne $rawModels) {
|
|
foreach ($rawModel in $rawModels) {
|
|
# Filter out Chromebooks for Lenovo before standardization
|
|
if ($SelectedMake -eq 'Lenovo' -and $rawModel.Model -match 'Chromebook') {
|
|
WriteLog "Get-ModelsForMake: Skipping Chromebook model: $($rawModel.Model)"
|
|
continue
|
|
}
|
|
$standardizedModels.Add((ConvertTo-StandardizedDriverModel -RawDriverObject $rawModel -Make $SelectedMake -State $State))
|
|
}
|
|
}
|
|
|
|
return $standardizedModels.ToArray()
|
|
}
|
|
|
|
|
|
|
|
# Function to filter the driver model list based on text input
|
|
function Filter-DriverModels {
|
|
param(
|
|
[string]$filterText,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
# Check if UI elements and the full list are available
|
|
if ($null -eq $State.Controls.lstDriverModels -or $null -eq $State.Data.allDriverModels) {
|
|
WriteLog "Filter-DriverModels: ListView or full model list not available."
|
|
return
|
|
}
|
|
|
|
WriteLog "Filtering models with text: '$filterText'"
|
|
|
|
# Filter the full list based on the Model property (case-insensitive)
|
|
# Use -match for potentially better performance or stick with -like
|
|
# Ensure the result is always an array, even if only one item matches
|
|
$filteredModels = @($State.Data.allDriverModels | Where-Object { $_.Model -like "*$filterText*" })
|
|
|
|
# Update the ListView's ItemsSource with the filtered list
|
|
# Setting ItemsSource directly should work for simple scenarios
|
|
$State.Controls.lstDriverModels.ItemsSource = $filteredModels
|
|
|
|
# Explicitly refresh the ListView's view to reflect the changes in the bound source
|
|
if ($null -ne $State.Controls.lstDriverModels.ItemsSource -and $State.Controls.lstDriverModels.Items -is [System.ComponentModel.ICollectionView]) {
|
|
$State.Controls.lstDriverModels.Items.Refresh()
|
|
}
|
|
elseif ($null -ne $State.Controls.lstDriverModels.ItemsSource) {
|
|
# Fallback refresh if not using ICollectionView (less common for direct ItemsSource binding)
|
|
$State.Controls.lstDriverModels.Items.Refresh()
|
|
}
|
|
|
|
|
|
WriteLog "Filtered list contains $($filteredModels.Count) models."
|
|
}
|
|
|
|
# Function to save selected driver models to a JSON file
|
|
function Save-DriversJson {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
WriteLog "Save-DriversJson function called."
|
|
$selectedDrivers = @($State.Controls.lstDriverModels.Items | Where-Object { $_.IsSelected })
|
|
|
|
if (-not $selectedDrivers) {
|
|
[System.Windows.MessageBox]::Show("No drivers selected to save.", "Save Drivers", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
WriteLog "No drivers selected to save."
|
|
return
|
|
}
|
|
|
|
$outputJson = @{} # Use a Hashtable for the desired structure
|
|
|
|
$selectedDrivers | Group-Object -Property Make | ForEach-Object {
|
|
$makeName = $_.Name
|
|
$modelsForThisMake = @() # Initialize an array to hold model objects
|
|
|
|
foreach ($driverItem in $_.Group) {
|
|
$modelObject = $null
|
|
switch ($makeName) {
|
|
'Microsoft' {
|
|
$modelObject = @{
|
|
Name = $driverItem.Model # Model is the display name
|
|
Link = $driverItem.Link
|
|
}
|
|
}
|
|
'Dell' {
|
|
$modelObject = @{
|
|
Name = $driverItem.Model
|
|
}
|
|
}
|
|
'HP' {
|
|
$modelObject = @{
|
|
Name = $driverItem.Model
|
|
}
|
|
}
|
|
'Lenovo' {
|
|
$modelObject = @{
|
|
Name = $driverItem.Model # This is "ProductName (MachineType)"
|
|
ProductName = $driverItem.ProductName # This is "ProductName"
|
|
MachineType = $driverItem.MachineType # This is "MachineType"
|
|
}
|
|
}
|
|
default {
|
|
WriteLog "Save-DriversJson: Unknown Make '$makeName' encountered for model '$($driverItem.Model)'. Skipping."
|
|
}
|
|
}
|
|
if ($null -ne $modelObject) {
|
|
$modelsForThisMake += $modelObject
|
|
}
|
|
}
|
|
|
|
if ($modelsForThisMake.Count -gt 0) {
|
|
# Store the array of model objects under a "Models" key
|
|
$outputJson[$makeName] = @{
|
|
"Models" = $modelsForThisMake
|
|
}
|
|
}
|
|
}
|
|
|
|
$sfd = New-Object System.Windows.Forms.SaveFileDialog
|
|
$sfd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
|
|
$sfd.Title = "Save Selected Drivers"
|
|
$sfd.FileName = "Drivers.json"
|
|
$sfd.InitialDirectory = $FFUDevelopmentPath
|
|
|
|
if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
try {
|
|
$outputJson | ConvertTo-Json -Depth 5 | Set-Content -Path $sfd.FileName -Encoding UTF8
|
|
[System.Windows.MessageBox]::Show("Selected drivers saved to $($sfd.FileName)", "Save Successful", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
WriteLog "Selected drivers saved to $($sfd.FileName)"
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Error saving drivers file: $($_.Exception.Message)", "Save Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
|
WriteLog "Error saving drivers file to $($sfd.FileName): $($_.Exception.Message)"
|
|
}
|
|
}
|
|
else {
|
|
WriteLog "Save drivers operation cancelled by user."
|
|
}
|
|
}
|
|
|
|
# Function to import driver models from a JSON file
|
|
function Import-DriversJson {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
WriteLog "Import-DriversJson function called."
|
|
$ofd = New-Object System.Windows.Forms.OpenFileDialog
|
|
$ofd.Filter = "JSON files (*.json)|*.json|All files (*.*)|*.*"
|
|
$ofd.Title = "Import Drivers"
|
|
$ofd.InitialDirectory = $FFUDevelopmentPath
|
|
|
|
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
try {
|
|
$importedData = Get-Content -Path $ofd.FileName -Raw | ConvertFrom-Json
|
|
if ($null -eq $importedData -or $importedData -isnot [System.Management.Automation.PSCustomObject]) {
|
|
[System.Windows.MessageBox]::Show("Invalid JSON file format. Expected a JSON object with Makes as keys.", "Import Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
|
WriteLog "Import-DriversJson: Invalid JSON format in $($ofd.FileName). Expected an object."
|
|
return
|
|
}
|
|
|
|
$newModelsAdded = 0
|
|
$existingModelsUpdated = 0
|
|
|
|
if ($null -eq $State.Data.allDriverModels) {
|
|
$State.Data.allDriverModels = @()
|
|
}
|
|
|
|
$importedData.PSObject.Properties | ForEach-Object {
|
|
$makeName = $_.Name
|
|
$makeData = $_.Value # This is the object containing "Models" array
|
|
|
|
# Check if $makeData is null, not a PSCustomObject, or does not have a 'Models' property
|
|
if ($null -eq $makeData -or $makeData -isnot [System.Management.Automation.PSCustomObject] -or -not ($makeData.PSObject.Properties | Where-Object { $_.Name -eq 'Models' })) {
|
|
WriteLog "Import-DriversJson: Skipping Make '$makeName' due to invalid structure or missing 'Models' key."
|
|
return # Corresponds to 'continue' in ForEach-Object script block
|
|
}
|
|
|
|
$modelObjectArray = $makeData.Models # This is now an array of objects
|
|
if ($null -eq $modelObjectArray -or $modelObjectArray -isnot [array]) {
|
|
WriteLog "Import-DriversJson: Skipping Make '$makeName' because 'Models' value is not an array."
|
|
return
|
|
}
|
|
|
|
foreach ($importedModelObject in $modelObjectArray) {
|
|
if ($null -eq $importedModelObject -or -not $importedModelObject.PSObject.Properties['Name']) {
|
|
WriteLog "Import-DriversJson: Skipping model for Make '$makeName' due to missing 'Name' property or null object."
|
|
continue
|
|
}
|
|
$importedModelNameFromObject = $importedModelObject.Name
|
|
if ([string]::IsNullOrWhiteSpace($importedModelNameFromObject)) {
|
|
WriteLog "Import-DriversJson: Skipping empty model name for Make '$makeName'."
|
|
continue
|
|
}
|
|
|
|
$existingModel = $State.Data.allDriverModels | Where-Object { $_.Make -eq $makeName -and $_.Model -eq $importedModelNameFromObject } | Select-Object -First 1
|
|
|
|
if ($null -ne $existingModel) {
|
|
$existingModel.IsSelected = $true
|
|
$existingModel.DownloadStatus = "Imported"
|
|
|
|
if ($makeName -eq 'Microsoft' -and $importedModelObject.PSObject.Properties['Link']) {
|
|
if ($existingModel.Link -ne $importedModelObject.Link) {
|
|
$existingModel.Link = $importedModelObject.Link
|
|
WriteLog "Import-DriversJson: Updated Link for existing Microsoft model '$($existingModel.Model)'."
|
|
}
|
|
}
|
|
elseif ($makeName -eq 'Lenovo') {
|
|
$updateExistingLenovo = $false
|
|
if ($importedModelObject.PSObject.Properties['ProductName'] -and $existingModel.PSObject.Properties['ProductName'] -and $existingModel.ProductName -ne $importedModelObject.ProductName) {
|
|
$existingModel.ProductName = $importedModelObject.ProductName
|
|
$updateExistingLenovo = $true
|
|
}
|
|
if ($importedModelObject.PSObject.Properties['MachineType'] -and $existingModel.PSObject.Properties['MachineType'] -and $existingModel.MachineType -ne $importedModelObject.MachineType) {
|
|
$existingModel.MachineType = $importedModelObject.MachineType
|
|
$existingModel.Id = $importedModelObject.MachineType # Update Id as well
|
|
$updateExistingLenovo = $true
|
|
}
|
|
if ($updateExistingLenovo) {
|
|
WriteLog "Import-DriversJson: Updated ProductName/MachineType/Id for existing Lenovo model '$($existingModel.Model)'."
|
|
}
|
|
}
|
|
$existingModelsUpdated++
|
|
WriteLog "Import-DriversJson: Marked existing model '$($existingModel.Make) - $($existingModel.Model)' as imported."
|
|
}
|
|
else {
|
|
# Model does not exist, create a new one
|
|
$importedLink = if ($makeName -eq 'Microsoft' -and $importedModelObject.PSObject.Properties['Link']) { $importedModelObject.Link } else { $null }
|
|
$importedId = $importedModelNameFromObject # Default Id
|
|
$importedProductName = $null
|
|
$importedMachineType = $null
|
|
|
|
if ($makeName -eq 'Lenovo') {
|
|
$importedProductName = if ($importedModelObject.PSObject.Properties['ProductName']) { $importedModelObject.ProductName } else { $null }
|
|
$importedMachineType = if ($importedModelObject.PSObject.Properties['MachineType']) { $importedModelObject.MachineType } else { $null }
|
|
|
|
if ($null -ne $importedMachineType) {
|
|
$importedId = $importedMachineType # Override Id for Lenovo
|
|
}
|
|
|
|
# Fallback parsing if ProductName/MachineType are missing from JSON but Name has the pattern
|
|
if (($null -eq $importedProductName -or $null -eq $importedMachineType) -and $importedModelNameFromObject -match '(.+?)\s*\((.+?)\)$') {
|
|
WriteLog "Import-DriversJson: Lenovo model '$importedModelNameFromObject' missing ProductName or MachineType in JSON. Attempting to parse from Name."
|
|
if ($null -eq $importedProductName) { $importedProductName = $matches[1].Trim() }
|
|
if ($null -eq $importedMachineType) {
|
|
$importedMachineType = $matches[2].Trim()
|
|
$importedId = $importedMachineType # Update Id if MachineType was parsed here
|
|
}
|
|
}
|
|
|
|
if ($null -eq $importedProductName -or $null -eq $importedMachineType) {
|
|
WriteLog "Import-DriversJson: Warning - Lenovo model '$importedModelNameFromObject' is missing ProductName or MachineType after parsing. ID might be based on full name."
|
|
}
|
|
}
|
|
|
|
$newDriverModel = [PSCustomObject]@{
|
|
IsSelected = $true
|
|
Make = $makeName
|
|
Model = $importedModelNameFromObject # Full display name
|
|
Link = $importedLink
|
|
Id = $importedId
|
|
ProductName = $importedProductName
|
|
MachineType = $importedMachineType
|
|
Version = ""
|
|
Type = ""
|
|
Size = ""
|
|
Arch = ""
|
|
DownloadStatus = "Imported"
|
|
}
|
|
$State.Data.allDriverModels += $newDriverModel
|
|
$newModelsAdded++
|
|
WriteLog "Import-DriversJson: Added new model '$($newDriverModel.Make) - $($newDriverModel.Model)' from import. ID: $($newDriverModel.Id), Link: $($newDriverModel.Link)"
|
|
}
|
|
}
|
|
}
|
|
|
|
$State.Data.allDriverModels = $State.Data.allDriverModels | Sort-Object @{Expression = { $_.IsSelected }; Descending = $true }, Make, Model
|
|
|
|
Filter-DriverModels -filterText $State.Controls.txtModelFilter.Text -State $State
|
|
|
|
$message = "Driver import complete.`nNew models added: $newModelsAdded`nExisting models updated: $existingModelsUpdated"
|
|
[System.Windows.MessageBox]::Show($message, "Import Successful", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
WriteLog $message
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Error importing drivers file: $($_.Exception.Message)", "Import Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
|
WriteLog "Error importing drivers file from $($ofd.FileName): $($_.Exception.Message)"
|
|
}
|
|
}
|
|
else {
|
|
WriteLog "Import drivers operation cancelled by user."
|
|
}
|
|
}
|
|
|
|
# Some default values
|
|
$defaultFFUPrefix = "_FFU"
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
#Remove old log file if found
|
|
if (Test-Path -Path $script:uiState.LogFilePath) {
|
|
Remove-item -Path $script:uiState.LogFilePath -Force
|
|
}
|
|
|
|
# Function to refresh the Windows Release ComboBox based on ISO path
|
|
function Update-WindowsReleaseCombo {
|
|
param(
|
|
[string]$isoPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
|
|
if (-not $State.Controls.cmbWindowsRelease) { return } # Ensure combo exists
|
|
|
|
$oldSelectedItemValue = $null
|
|
if ($null -ne $State.Controls.cmbWindowsRelease.SelectedItem) {
|
|
$oldSelectedItemValue = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
|
}
|
|
|
|
# Get the appropriate list of releases from the helper module
|
|
$availableReleases = Get-AvailableWindowsReleases -IsoPath $isoPath -State $State
|
|
|
|
# Update the ComboBox ItemsSource
|
|
$State.Controls.cmbWindowsRelease.ItemsSource = $availableReleases
|
|
$State.Controls.cmbWindowsRelease.DisplayMemberPath = 'Display'
|
|
$State.Controls.cmbWindowsRelease.SelectedValuePath = 'Value'
|
|
|
|
# Try to re-select the previously selected item, or default
|
|
$itemToSelect = $availableReleases | Where-Object { $_.Value -eq $oldSelectedItemValue } | Select-Object -First 1
|
|
if ($null -ne $itemToSelect) {
|
|
$State.Controls.cmbWindowsRelease.SelectedItem = $itemToSelect
|
|
}
|
|
elseif ($availableReleases.Count -gt 0) {
|
|
# Default to Windows 11 if available, otherwise the first item
|
|
$defaultItem = $availableReleases | Where-Object { $_.Value -eq 11 } | Select-Object -First 1
|
|
if ($null -eq $defaultItem) {
|
|
$defaultItem = $availableReleases[0]
|
|
}
|
|
$State.Controls.cmbWindowsRelease.SelectedItem = $defaultItem
|
|
}
|
|
else {
|
|
# No items available (should not happen with current logic)
|
|
$State.Controls.cmbWindowsRelease.SelectedIndex = -1
|
|
}
|
|
}
|
|
|
|
# Function to refresh the Windows Version ComboBox based on selected release and ISO path
|
|
function Update-WindowsVersionCombo {
|
|
param(
|
|
[int]$selectedRelease,
|
|
[string]$isoPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
|
|
$combo = $State.Controls.cmbWindowsVersion # Use script-scoped variable
|
|
if (-not $combo) { return } # Ensure combo exists
|
|
|
|
# Get available versions and default from the helper module
|
|
$versionData = Get-AvailableWindowsVersions -SelectedRelease $selectedRelease -IsoPath $isoPath -State $State
|
|
|
|
# Update the ComboBox ItemsSource and IsEnabled state
|
|
$combo.ItemsSource = $versionData.Versions
|
|
$combo.IsEnabled = $versionData.IsEnabled
|
|
|
|
# Set the selected item
|
|
if ($null -ne $versionData.DefaultVersion -and $versionData.Versions -contains $versionData.DefaultVersion) {
|
|
$combo.SelectedItem = $versionData.DefaultVersion
|
|
}
|
|
elseif ($versionData.Versions.Count -gt 0) {
|
|
$combo.SelectedIndex = 0 # Fallback to first item if default isn't valid
|
|
}
|
|
else {
|
|
$combo.SelectedIndex = -1 # No items available
|
|
}
|
|
}
|
|
|
|
# Function to refresh the Windows SKU ComboBox based on selected release
|
|
function Update-WindowsSkuCombo {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
# This function no longer takes parameters.
|
|
# It derives the selected release value and display name from the cmbWindowsRelease ComboBox.
|
|
|
|
$skuCombo = $State.Controls.cmbWindowsSKU
|
|
if (-not $skuCombo) {
|
|
WriteLog "Update-WindowsSkuCombo: SKU ComboBox not found."
|
|
return
|
|
}
|
|
|
|
$releaseCombo = $script:uiState.Controls.cmbWindowsRelease
|
|
if (-not $releaseCombo -or $null -eq $releaseCombo.SelectedItem) {
|
|
WriteLog "Update-WindowsSkuCombo: Windows Release ComboBox not found or no item selected. Cannot update SKUs."
|
|
$skuCombo.ItemsSource = @() # Clear SKUs
|
|
$skuCombo.SelectedIndex = -1
|
|
return
|
|
}
|
|
|
|
$selectedReleaseItem = $releaseCombo.SelectedItem
|
|
$selectedReleaseValue = $selectedReleaseItem.Value
|
|
$selectedReleaseDisplayName = $selectedReleaseItem.Display
|
|
|
|
$previousSelectedSku = $null
|
|
if ($null -ne $skuCombo.SelectedItem) {
|
|
$previousSelectedSku = $skuCombo.SelectedItem
|
|
}
|
|
|
|
WriteLog "Update-WindowsSkuCombo: Updating SKUs for Release Value '$selectedReleaseValue' (Display: '$selectedReleaseDisplayName')."
|
|
# Call Get-AvailableSkusForRelease with both Value and DisplayName
|
|
$availableSkus = Get-AvailableSkusForRelease -SelectedReleaseValue $selectedReleaseValue -SelectedReleaseDisplayName $selectedReleaseDisplayName -State $State
|
|
|
|
$skuCombo.ItemsSource = $availableSkus
|
|
WriteLog "Update-WindowsSkuCombo: Set ItemsSource with $($availableSkus.Count) SKUs."
|
|
|
|
# Attempt to re-select the previous SKU, or "Pro", or the first available
|
|
if ($null -ne $previousSelectedSku -and $availableSkus -contains $previousSelectedSku) {
|
|
$skuCombo.SelectedItem = $previousSelectedSku
|
|
WriteLog "Update-WindowsSkuCombo: Re-selected previous SKU '$previousSelectedSku'."
|
|
}
|
|
elseif ($availableSkus -contains "Pro") {
|
|
$skuCombo.SelectedItem = "Pro"
|
|
WriteLog "Update-WindowsSkuCombo: Selected default SKU 'Pro'."
|
|
}
|
|
elseif ($availableSkus.Count -gt 0) {
|
|
$skuCombo.SelectedIndex = 0
|
|
WriteLog "Update-WindowsSkuCombo: Selected first available SKU '$($skuCombo.SelectedItem)'."
|
|
}
|
|
else {
|
|
$skuCombo.SelectedIndex = -1 # No SKUs available
|
|
WriteLog "Update-WindowsSkuCombo: No SKUs available for Release '$selectedReleaseValue' (Display: '$selectedReleaseDisplayName')."
|
|
}
|
|
}
|
|
|
|
# Combined function to refresh both Release and Version combos
|
|
function Refresh-WindowsSettingsCombos {
|
|
param(
|
|
[string]$isoPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
|
|
# Update Release combo first
|
|
Update-WindowsReleaseCombo -isoPath $isoPath -State $State
|
|
|
|
# Get the newly selected release value
|
|
$selectedReleaseValue = 11 # Default to 11 if selection is null
|
|
if ($null -ne $State.Controls.cmbWindowsRelease.SelectedItem) {
|
|
$selectedReleaseValue = $State.Controls.cmbWindowsRelease.SelectedItem.Value
|
|
}
|
|
|
|
# Update Version combo based on the selected release
|
|
Update-WindowsVersionCombo -selectedRelease $selectedReleaseValue -isoPath $isoPath -State $State
|
|
|
|
# Update SKU combo based on the selected release (now derives values internally)
|
|
Update-WindowsSkuCombo -State $State
|
|
}
|
|
|
|
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)
|
|
|
|
# Dynamic checkboxes for optional features in Windows Settings tab
|
|
function UpdateOptionalFeaturesString {
|
|
param(
|
|
[psobject]$State
|
|
)
|
|
$checkedFeatures = @()
|
|
foreach ($entry in $State.Controls.featureCheckBoxes.GetEnumerator()) {
|
|
if ($entry.Value.IsChecked) { $checkedFeatures += $entry.Key }
|
|
}
|
|
$State.Controls.txtOptionalFeatures.Text = $checkedFeatures -join ";"
|
|
}
|
|
function BuildFeaturesGrid {
|
|
param (
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.FrameworkElement]$parent,
|
|
[Parameter(Mandatory)]
|
|
[array]$allowedFeatures # Pass the list of features explicitly
|
|
)
|
|
$parent.Children.Clear()
|
|
$script:uiState.Controls.featureCheckBoxes.Clear() # Clear the tracking hashtable
|
|
|
|
$sortedFeatures = $allowedFeatures | Sort-Object
|
|
$rows = 10 # Define number of rows for layout
|
|
$columns = [math]::Ceiling($sortedFeatures.Count / $rows)
|
|
|
|
$featuresGrid = New-Object System.Windows.Controls.Grid
|
|
$featuresGrid.Margin = "0,5,0,5"
|
|
$featuresGrid.ShowGridLines = $false
|
|
|
|
# Define grid rows
|
|
for ($r = 0; $r -lt $rows; $r++) {
|
|
$rowDef = New-Object System.Windows.Controls.RowDefinition
|
|
$rowDef.Height = [System.Windows.GridLength]::Auto
|
|
$featuresGrid.RowDefinitions.Add($rowDef) | Out-Null
|
|
}
|
|
# Define grid columns
|
|
for ($c = 0; $c -lt $columns; $c++) {
|
|
$colDef = New-Object System.Windows.Controls.ColumnDefinition
|
|
$colDef.Width = [System.Windows.GridLength]::Auto
|
|
$featuresGrid.ColumnDefinitions.Add($colDef) | Out-Null
|
|
}
|
|
|
|
# Populate grid with checkboxes
|
|
for ($i = 0; $i -lt $sortedFeatures.Count; $i++) {
|
|
$featureName = $sortedFeatures[$i]
|
|
$colIndex = [int]([math]::Floor($i / $rows))
|
|
$rowIndex = $i % $rows
|
|
|
|
$chk = New-Object System.Windows.Controls.CheckBox
|
|
$chk.Content = $featureName
|
|
$chk.Margin = "5"
|
|
$chk.Add_Checked({ UpdateOptionalFeaturesString -State $script:uiState })
|
|
$chk.Add_Unchecked({ UpdateOptionalFeaturesString -State $script:uiState })
|
|
|
|
$script:uiState.Controls.featureCheckBoxes[$featureName] = $chk # Track the checkbox
|
|
|
|
[System.Windows.Controls.Grid]::SetRow($chk, $rowIndex)
|
|
[System.Windows.Controls.Grid]::SetColumn($chk, $colIndex)
|
|
$featuresGrid.Children.Add($chk) | Out-Null
|
|
}
|
|
$parent.Children.Add($featuresGrid) | Out-Null
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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()
|
|
})
|
|
}
|
|
|
|
|
|
# Add a function to create a sortable list view for Winget search results
|
|
function Add-SortableColumn {
|
|
param(
|
|
[System.Windows.Controls.GridView]$gridView,
|
|
[string]$header,
|
|
[string]$binding,
|
|
[int]$width = 'Auto',
|
|
[bool]$isCheckbox = $false,
|
|
[System.Windows.HorizontalAlignment]$headerHorizontalAlignment = [System.Windows.HorizontalAlignment]::Stretch
|
|
)
|
|
|
|
$column = New-Object System.Windows.Controls.GridViewColumn
|
|
$commonPadding = New-Object System.Windows.Thickness(5, 2, 5, 2)
|
|
|
|
$headerControl = New-Object System.Windows.Controls.GridViewColumnHeader
|
|
$headerControl.Tag = $binding # Used for sorting
|
|
|
|
if ($isCheckbox) {
|
|
# Cell template for a column of checkboxes
|
|
$cellTemplate = New-Object System.Windows.DataTemplate
|
|
$gridFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.Grid])
|
|
|
|
$checkBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox])
|
|
$checkBoxFactory.SetBinding([System.Windows.Controls.CheckBox]::IsCheckedProperty, (New-Object System.Windows.Data.Binding("IsSelected")))
|
|
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center)
|
|
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
|
|
|
$checkBoxFactory.AddHandler([System.Windows.Controls.CheckBox]::ClickEvent, [System.Windows.RoutedEventHandler] {
|
|
param($eventSourceLocal, $eventArgsLocal)
|
|
# Sync logic would be needed here if this column had a header checkbox
|
|
})
|
|
$gridFactory.AppendChild($checkBoxFactory)
|
|
$cellTemplate.VisualTree = $gridFactory
|
|
$column.CellTemplate = $cellTemplate
|
|
# $column.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Center # REMOVED
|
|
}
|
|
else {
|
|
# For regular text columns
|
|
$headerControl.HorizontalContentAlignment = $headerHorizontalAlignment
|
|
$headerControl.Content = $header
|
|
|
|
$headerTextElementFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock])
|
|
$headerTextElementFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, $header)
|
|
$headerTextBlockPadding = New-Object System.Windows.Thickness($commonPadding.Left, $commonPadding.Top, $commonPadding.Right, $commonPadding.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
|
|
$headerControl.ContentTemplate = $headerDataTemplate
|
|
|
|
$cellTemplate = New-Object System.Windows.DataTemplate
|
|
$textBlockFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock])
|
|
$textBlockFactory.SetBinding([System.Windows.Controls.TextBlock]::TextProperty, (New-Object System.Windows.Data.Binding($binding)))
|
|
# Adjust left padding to 0 for cell text to align with header text
|
|
$cellTextBlockPadding = New-Object System.Windows.Thickness(0, $commonPadding.Top, $commonPadding.Right, $commonPadding.Bottom)
|
|
$textBlockFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, $cellTextBlockPadding)
|
|
$textBlockFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Left)
|
|
$textBlockFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
|
|
|
$cellTemplate.VisualTree = $textBlockFactory
|
|
$column.CellTemplate = $cellTemplate
|
|
# $column.HorizontalContentAlignment = $headerHorizontalAlignment # REMOVED
|
|
}
|
|
|
|
$column.Header = $headerControl
|
|
|
|
if ($width -ne 'Auto') {
|
|
$column.Width = $width
|
|
}
|
|
|
|
$gridView.Columns.Add($column)
|
|
}
|
|
|
|
# Function to sort ListView items
|
|
function Invoke-ListViewSort {
|
|
param(
|
|
[System.Windows.Controls.ListView]$listView,
|
|
[string]$property
|
|
)
|
|
|
|
# Toggle sort direction if clicking the same column
|
|
if ($script:uiState.Flags.lastSortProperty -eq $property) {
|
|
$script:uiState.Flags.lastSortAscending = -not $script:uiState.Flags.lastSortAscending
|
|
}
|
|
else {
|
|
$script:uiState.Flags.lastSortAscending = $true
|
|
}
|
|
$script:uiState.Flags.lastSortProperty = $property
|
|
|
|
# Get items from ItemsSource or Items collection
|
|
$currentItemsSource = $listView.ItemsSource
|
|
$itemsToSort = @()
|
|
if ($null -ne $currentItemsSource) {
|
|
$itemsToSort = @($currentItemsSource)
|
|
}
|
|
else {
|
|
$itemsToSort = @($listView.Items)
|
|
}
|
|
|
|
if ($itemsToSort.Count -eq 0) {
|
|
return
|
|
}
|
|
|
|
$selectedItems = @($itemsToSort | Where-Object { $_.IsSelected })
|
|
$unselectedItems = @($itemsToSort | Where-Object { -not $_.IsSelected })
|
|
|
|
# Define the primary sort criterion
|
|
$primarySortDefinition = @{
|
|
Expression = {
|
|
$val = $_.$property
|
|
if ($null -eq $val) { '' } else { $val }
|
|
}
|
|
Ascending = $script:uiState.Flags.lastSortAscending
|
|
}
|
|
|
|
$sortCriteria = [System.Collections.Generic.List[hashtable]]::new()
|
|
$sortCriteria.Add($primarySortDefinition)
|
|
|
|
# Determine secondary sort property based on the ListView
|
|
$secondarySortPropertyName = $null
|
|
if ($listView.Name -eq 'lstDriverModels') {
|
|
$secondarySortPropertyName = "Model"
|
|
}
|
|
elseif ($listView.Name -eq 'lstWingetResults') {
|
|
$secondarySortPropertyName = "Name"
|
|
}
|
|
elseif ($listView.Name -eq 'lstAppsScriptVariables') {
|
|
if ($property -eq "Key") {
|
|
$secondarySortPropertyName = "Value"
|
|
}
|
|
elseif ($property -eq "Value") {
|
|
$secondarySortPropertyName = "Key"
|
|
}
|
|
else {
|
|
# Default secondary sort for IsSelected or other properties
|
|
$secondarySortPropertyName = "Key"
|
|
}
|
|
}
|
|
|
|
if ($null -ne $secondarySortPropertyName -and $property -ne $secondarySortPropertyName) {
|
|
$itemsHaveSecondaryProperty = $false
|
|
if ($unselectedItems.Count -gt 0) {
|
|
if ($null -ne $unselectedItems[0].PSObject.Properties[$secondarySortPropertyName]) {
|
|
$itemsHaveSecondaryProperty = $true
|
|
}
|
|
}
|
|
elseif ($selectedItems.Count -gt 0) {
|
|
if ($null -ne $selectedItems[0].PSObject.Properties[$secondarySortPropertyName]) {
|
|
$itemsHaveSecondaryProperty = $true
|
|
}
|
|
}
|
|
|
|
if ($itemsHaveSecondaryProperty) {
|
|
# Create a scriptblock for the secondary sort expression dynamically
|
|
$expressionScriptBlock = [scriptblock]::Create("`$_.$secondarySortPropertyName")
|
|
|
|
$secondarySortDefinition = @{
|
|
Expression = {
|
|
$val = Invoke-Command -ScriptBlock $expressionScriptBlock -ArgumentList $_
|
|
if ($null -eq $val) { '' } else { $val }
|
|
}
|
|
Ascending = $true # Secondary sort always ascending
|
|
}
|
|
$sortCriteria.Add($secondarySortDefinition)
|
|
}
|
|
}
|
|
|
|
$sortedUnselected = $unselectedItems | Sort-Object -Property $sortCriteria.ToArray()
|
|
# Ensure $sortedUnselected is not null before attempting to add its range
|
|
if ($null -eq $sortedUnselected) {
|
|
$sortedUnselected = @()
|
|
}
|
|
|
|
# Combine sorted items: selected items first, then sorted unselected items
|
|
$newSortedList = [System.Collections.Generic.List[object]]::new()
|
|
$newSortedList.AddRange($selectedItems)
|
|
$newSortedList.AddRange($sortedUnselected)
|
|
|
|
# Set the new sorted list as the ItemsSource
|
|
# Try nulling out ItemsSource first to force a more complete refresh
|
|
$listView.ItemsSource = $null
|
|
$listView.ItemsSource = $newSortedList.ToArray()
|
|
}
|
|
|
|
# Function to add a selectable GridViewColumn with a "Select All" header CheckBox
|
|
function Add-SelectableGridViewColumn {
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView,
|
|
[Parameter(Mandatory)]
|
|
[string]$HeaderCheckBoxScriptVariableName,
|
|
[Parameter(Mandatory)]
|
|
[double]$ColumnWidth,
|
|
[string]$IsSelectedPropertyName = "IsSelected"
|
|
)
|
|
|
|
# Ensure the ListView has a GridView
|
|
if ($null -eq $ListView.View -or -not ($ListView.View -is [System.Windows.Controls.GridView])) {
|
|
WriteLog "Add-SelectableGridViewColumn: ListView '$($ListView.Name)' does not have a GridView or View is null. Cannot add column."
|
|
# Optionally, create a new GridView if one doesn't exist, though XAML usually defines it.
|
|
# $ListView.View = New-Object System.Windows.Controls.GridView
|
|
return
|
|
}
|
|
$gridView = $ListView.View
|
|
|
|
# Create the "Select All" CheckBox for the header
|
|
$headerCheckBox = New-Object System.Windows.Controls.CheckBox
|
|
$headerCheckBox.HorizontalAlignment = [System.Windows.HorizontalAlignment]::Center
|
|
# Store an object containing the IsSelectedPropertyName and the ListView's Name in the Tag
|
|
$headerTagObject = [PSCustomObject]@{
|
|
PropertyName = $IsSelectedPropertyName
|
|
ListViewName = $ListView.Name
|
|
}
|
|
$headerCheckBox.Tag = $headerTagObject
|
|
# Removed debug WriteLog for storing tag data
|
|
|
|
$headerCheckBox.Add_Checked({
|
|
param($senderCheckBoxLocal, $eventArgsCheckedLocal)
|
|
|
|
$tagData = $senderCheckBoxLocal.Tag
|
|
if ($null -eq $tagData -or -not $tagData.PSObject.Properties['PropertyName'] -or -not $tagData.PSObject.Properties['ListViewName']) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - Tag data on header checkbox is missing, null, or malformed. Aborting HeaderChecked event."
|
|
return
|
|
}
|
|
|
|
$localPropertyName = $tagData.PropertyName
|
|
$localListViewName = $tagData.ListViewName
|
|
# Removed debug WriteLog for HeaderChecked event fired
|
|
|
|
if ([string]::IsNullOrEmpty($localPropertyName)) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - PropertyName from Tag is null or empty in HeaderChecked event for ListView '$localListViewName'. Aborting."
|
|
return
|
|
}
|
|
if ([string]::IsNullOrEmpty($localListViewName)) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - ListViewName from Tag is null or empty in HeaderChecked event. Aborting."
|
|
return
|
|
}
|
|
|
|
$actualListView = $script:uiState.Controls[$localListViewName]
|
|
if ($null -eq $actualListView) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - ListView control '$localListViewName' not found in window during HeaderChecked event. Aborting."
|
|
return
|
|
}
|
|
# Removed debug WriteLog for successfully finding ListView in HeaderChecked
|
|
|
|
$collectionToUpdate = $null
|
|
if ($null -ne $actualListView.ItemsSource) {
|
|
$collectionToUpdate = $actualListView.ItemsSource
|
|
}
|
|
elseif ($actualListView.HasItems) {
|
|
$collectionToUpdate = $actualListView.Items
|
|
}
|
|
|
|
if ($null -ne $collectionToUpdate) {
|
|
foreach ($item in $collectionToUpdate) {
|
|
try {
|
|
$item.($localPropertyName) = $true
|
|
}
|
|
catch {
|
|
WriteLog "Error setting '$localPropertyName' to true for item in $($actualListView.Name): $($_.Exception.Message)"
|
|
}
|
|
}
|
|
$actualListView.Items.Refresh()
|
|
WriteLog "Header checkbox for $($actualListView.Name) checked. All items' '$localPropertyName' set to true."
|
|
}
|
|
})
|
|
$headerCheckBox.Add_Unchecked({
|
|
param($senderCheckBoxLocal, $eventArgsUncheckedLocal)
|
|
|
|
$tagData = $senderCheckBoxLocal.Tag
|
|
if ($null -eq $tagData -or -not $tagData.PSObject.Properties['PropertyName'] -or -not $tagData.PSObject.Properties['ListViewName']) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - Tag data on header checkbox is missing, null, or malformed. Aborting HeaderUnchecked event."
|
|
return
|
|
}
|
|
|
|
$localPropertyName = $tagData.PropertyName
|
|
$localListViewName = $tagData.ListViewName
|
|
# Removed debug WriteLog for HeaderUnchecked event fired
|
|
|
|
if ([string]::IsNullOrEmpty($localPropertyName)) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - PropertyName from Tag is null or empty in HeaderUnchecked event for ListView '$localListViewName'. Aborting."
|
|
return
|
|
}
|
|
if ([string]::IsNullOrEmpty($localListViewName)) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - ListViewName from Tag is null or empty in HeaderUnchecked event. Aborting."
|
|
return
|
|
}
|
|
|
|
$actualListView = $script:uiState.Controls[$localListViewName]
|
|
if ($null -eq $actualListView) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - ListView control '$localListViewName' not found in window during HeaderUnchecked event. Aborting."
|
|
return
|
|
}
|
|
# Removed debug WriteLog for successfully finding ListView in HeaderUnchecked
|
|
|
|
# Only proceed if the uncheck was initiated by the user (IsChecked is explicitly false)
|
|
if ($senderCheckBoxLocal.IsChecked -eq $false) {
|
|
$collectionToUpdate = $null
|
|
if ($null -ne $actualListView.ItemsSource) {
|
|
$collectionToUpdate = $actualListView.ItemsSource
|
|
}
|
|
elseif ($actualListView.HasItems) {
|
|
$collectionToUpdate = $actualListView.Items
|
|
}
|
|
|
|
if ($null -ne $collectionToUpdate) {
|
|
foreach ($item in $collectionToUpdate) {
|
|
try {
|
|
$item.($localPropertyName) = $false
|
|
}
|
|
catch {
|
|
WriteLog "Error setting '$localPropertyName' to false for item in $($actualListView.Name): $($_.Exception.Message)"
|
|
}
|
|
}
|
|
$actualListView.Items.Refresh()
|
|
WriteLog "Header checkbox for $($actualListView.Name) unchecked by user. All items' '$localPropertyName' set to false."
|
|
}
|
|
}
|
|
})
|
|
|
|
# Store the header checkbox in a script-scoped variable
|
|
Set-Variable -Name $HeaderCheckBoxScriptVariableName -Value $headerCheckBox -Scope Script -Force
|
|
WriteLog "Add-SelectableGridViewColumn: Stored header checkbox in script variable '$HeaderCheckBoxScriptVariableName'."
|
|
|
|
# Create the GridViewColumn
|
|
$selectableColumn = New-Object System.Windows.Controls.GridViewColumn
|
|
$selectableColumn.Header = $headerCheckBox
|
|
$selectableColumn.Width = $ColumnWidth
|
|
|
|
# Create the CellTemplate for item CheckBoxes
|
|
$cellTemplate = New-Object System.Windows.DataTemplate
|
|
|
|
# Use a Border to ensure CheckBox centers and stretches
|
|
$borderFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.Border])
|
|
$borderFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)
|
|
$borderFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Stretch)
|
|
|
|
$checkBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox])
|
|
$checkBoxFactory.SetBinding([System.Windows.Controls.CheckBox]::IsCheckedProperty, (New-Object System.Windows.Data.Binding($IsSelectedPropertyName)))
|
|
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center)
|
|
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
|
|
|
# Create an object to store both the header checkbox name and the ListView name
|
|
$tagObject = [PSCustomObject]@{
|
|
HeaderCheckboxName = $HeaderCheckBoxScriptVariableName
|
|
ListViewName = $ListView.Name # Store the name of the ListView
|
|
}
|
|
# Store this object in the Tag of each item checkbox
|
|
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::TagProperty, $tagObject)
|
|
|
|
# Add handler to update the header checkbox state when an item checkbox is clicked
|
|
$checkBoxFactory.AddHandler([System.Windows.Controls.CheckBox]::ClickEvent, [System.Windows.RoutedEventHandler] {
|
|
param($eventSourceLocal, $eventArgsLocal)
|
|
|
|
$itemCheckBox = $eventSourceLocal -as [System.Windows.Controls.CheckBox]
|
|
if ($null -eq $itemCheckBox) {
|
|
WriteLog "Add-SelectableGridViewColumn: CRITICAL - Event source in item checkbox click handler is not a CheckBox."
|
|
return
|
|
}
|
|
|
|
$tagData = $itemCheckBox.Tag
|
|
if ($null -eq $tagData -or -not $tagData.PSObject.Properties['HeaderCheckboxName'] -or -not $tagData.PSObject.Properties['ListViewName']) {
|
|
WriteLog "Add-SelectableGridViewColumn: Error - Tag data on itemCheckBox is missing or malformed."
|
|
return
|
|
}
|
|
|
|
$headerCheckboxNameFromTag = $tagData.HeaderCheckboxName
|
|
$listViewNameFromTag = $tagData.ListViewName
|
|
|
|
WriteLog "Add-SelectableGridViewColumn: Item Click. ListView: '$listViewNameFromTag', HeaderChkName: '$headerCheckboxNameFromTag'"
|
|
|
|
if ([string]::IsNullOrEmpty($headerCheckboxNameFromTag)) {
|
|
WriteLog "Add-SelectableGridViewColumn: Error - Header checkbox name from Tag is null or empty for ListView '$listViewNameFromTag'."
|
|
return
|
|
}
|
|
if ([string]::IsNullOrEmpty($listViewNameFromTag)) {
|
|
WriteLog "Add-SelectableGridViewColumn: Error - ListView name from Tag is null or empty."
|
|
return
|
|
}
|
|
|
|
# Retrieve the actual ListView control using its name stored in the Tag
|
|
$targetListView = $script:uiState.Controls[$listViewNameFromTag]
|
|
if ($null -eq $targetListView) {
|
|
WriteLog "Add-SelectableGridViewColumn: Error - Could not find ListView control named '$listViewNameFromTag'."
|
|
return
|
|
}
|
|
|
|
$headerChk = Get-Variable -Name $headerCheckboxNameFromTag -Scope Script -ValueOnly -ErrorAction SilentlyContinue
|
|
if ($null -ne $headerChk) {
|
|
Update-SelectAllHeaderCheckBoxState -ListView $targetListView -HeaderCheckBox $headerChk
|
|
}
|
|
else {
|
|
WriteLog "Add-SelectableGridViewColumn: Error - Could not retrieve script variable for header checkbox named '$headerCheckboxNameFromTag' for ListView '$listViewNameFromTag'."
|
|
}
|
|
})
|
|
|
|
$borderFactory.AppendChild($checkBoxFactory)
|
|
$cellTemplate.VisualTree = $borderFactory
|
|
$selectableColumn.CellTemplate = $cellTemplate
|
|
|
|
# Insert the new column at the beginning of the GridView
|
|
$gridView.Columns.Insert(0, $selectableColumn)
|
|
WriteLog "Add-SelectableGridViewColumn: Successfully added selectable column to '$($ListView.Name)'."
|
|
}
|
|
|
|
# Function to update the IsChecked state of a "Select All" header CheckBox
|
|
function Update-SelectAllHeaderCheckBoxState {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView,
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.CheckBox]$HeaderCheckBox
|
|
)
|
|
|
|
$collectionToInspect = $null
|
|
if ($null -ne $ListView.ItemsSource) {
|
|
$collectionToInspect = @($ListView.ItemsSource)
|
|
}
|
|
elseif ($ListView.HasItems) {
|
|
# Check if Items collection has items and ItemsSource is null
|
|
$collectionToInspect = @($ListView.Items)
|
|
}
|
|
|
|
# If no items to inspect (either ItemsSource was null and Items was empty, or ItemsSource was empty)
|
|
if ($null -eq $collectionToInspect -or $collectionToInspect.Count -eq 0) {
|
|
$HeaderCheckBox.IsChecked = $false
|
|
return
|
|
}
|
|
|
|
$selectedCount = ($collectionToInspect | Where-Object { $_.IsSelected }).Count
|
|
$totalItemCount = $collectionToInspect.Count # Get the total count from the collection being inspected
|
|
|
|
if ($totalItemCount -eq 0) {
|
|
# Handle empty list case specifically
|
|
$HeaderCheckBox.IsChecked = $false
|
|
}
|
|
elseif ($selectedCount -eq $totalItemCount) {
|
|
$HeaderCheckBox.IsChecked = $true
|
|
}
|
|
elseif ($selectedCount -eq 0) {
|
|
$HeaderCheckBox.IsChecked = $false
|
|
}
|
|
else {
|
|
# Indeterminate state
|
|
$HeaderCheckBox.IsChecked = $null
|
|
}
|
|
}
|
|
|
|
# Function to update priorities sequentially in a ListView
|
|
function Update-ListViewPriorities {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView
|
|
)
|
|
|
|
$currentPriority = 1
|
|
foreach ($item in $ListView.Items) {
|
|
if ($null -ne $item -and $item.PSObject.Properties['Priority']) {
|
|
$item.Priority = $currentPriority
|
|
$currentPriority++
|
|
}
|
|
}
|
|
$ListView.Items.Refresh()
|
|
}
|
|
|
|
# Function to move selected item to the top
|
|
function Move-ListViewItemTop {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView
|
|
)
|
|
|
|
$selectedItem = $ListView.SelectedItem
|
|
if ($null -eq $selectedItem) { return }
|
|
|
|
$currentIndex = $ListView.Items.IndexOf($selectedItem)
|
|
if ($currentIndex -gt 0) {
|
|
$ListView.Items.RemoveAt($currentIndex)
|
|
$ListView.Items.Insert(0, $selectedItem)
|
|
$ListView.SelectedItem = $selectedItem
|
|
Update-ListViewPriorities -ListView $ListView
|
|
}
|
|
}
|
|
|
|
# Function to move selected item up one position
|
|
function Move-ListViewItemUp {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView
|
|
)
|
|
|
|
$selectedItem = $ListView.SelectedItem
|
|
if ($null -eq $selectedItem) { return }
|
|
|
|
$currentIndex = $ListView.Items.IndexOf($selectedItem)
|
|
if ($currentIndex -gt 0) {
|
|
$ListView.Items.RemoveAt($currentIndex)
|
|
$ListView.Items.Insert($currentIndex - 1, $selectedItem)
|
|
$ListView.SelectedItem = $selectedItem
|
|
Update-ListViewPriorities -ListView $ListView
|
|
}
|
|
}
|
|
|
|
# Function to move selected item down one position
|
|
function Move-ListViewItemDown {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView
|
|
)
|
|
|
|
$selectedItem = $ListView.SelectedItem
|
|
if ($null -eq $selectedItem) { return }
|
|
|
|
$currentIndex = $ListView.Items.IndexOf($selectedItem)
|
|
if ($currentIndex -lt ($ListView.Items.Count - 1)) {
|
|
$ListView.Items.RemoveAt($currentIndex)
|
|
$ListView.Items.Insert($currentIndex + 1, $selectedItem)
|
|
$ListView.SelectedItem = $selectedItem
|
|
Update-ListViewPriorities -ListView $ListView
|
|
}
|
|
}
|
|
|
|
# Function to move selected item to the bottom
|
|
function Move-ListViewItemBottom {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[System.Windows.Controls.ListView]$ListView
|
|
)
|
|
|
|
$selectedItem = $ListView.SelectedItem
|
|
if ($null -eq $selectedItem) { return }
|
|
|
|
$currentIndex = $ListView.Items.IndexOf($selectedItem)
|
|
if ($currentIndex -lt ($ListView.Items.Count - 1)) {
|
|
$ListView.Items.RemoveAt($currentIndex)
|
|
$ListView.Items.Add($selectedItem)
|
|
$ListView.SelectedItem = $selectedItem
|
|
Update-ListViewPriorities -ListView $ListView
|
|
}
|
|
}
|
|
|
|
# Function to update the enabled state of the Copy Apps button
|
|
function Update-CopyButtonState {
|
|
param(
|
|
[psobject]$State
|
|
)
|
|
$listView = $State.Controls.lstApplications
|
|
$copyButton = $State.Controls.btnCopyBYOApps
|
|
if ($listView -and $copyButton) {
|
|
$hasSource = $false
|
|
foreach ($item in $listView.Items) {
|
|
if ($null -ne $item -and $item.PSObject.Properties['Source'] -and -not [string]::IsNullOrWhiteSpace($item.Source)) {
|
|
$hasSource = $true
|
|
break
|
|
}
|
|
}
|
|
$copyButton.IsEnabled = $hasSource
|
|
}
|
|
}
|
|
|
|
# --------------------------------------------------------------------------
|
|
# SECTION: Parallel Processing
|
|
# --------------------------------------------------------------------------
|
|
|
|
# --------------------------------------------------------------------------
|
|
|
|
$window.Add_Loaded({
|
|
# Assign UI elements to script variables
|
|
$script:uiState.Window = $window
|
|
$script:uiState.Controls.cmbWindowsRelease = $window.FindName('cmbWindowsRelease')
|
|
$script:uiState.Controls.cmbWindowsVersion = $window.FindName('cmbWindowsVersion')
|
|
$script:uiState.Controls.txtISOPath = $window.FindName('txtISOPath')
|
|
$script:uiState.Controls.btnBrowseISO = $window.FindName('btnBrowseISO')
|
|
$script:uiState.Controls.cmbWindowsArch = $window.FindName('cmbWindowsArch')
|
|
$script:uiState.Controls.cmbWindowsLang = $window.FindName('cmbWindowsLang')
|
|
$script:uiState.Controls.cmbWindowsSKU = $window.FindName('cmbWindowsSKU')
|
|
$script:uiState.Controls.cmbMediaType = $window.FindName('cmbMediaType')
|
|
$script:uiState.Controls.txtOptionalFeatures = $window.FindName('txtOptionalFeatures')
|
|
$script:uiState.Controls.featuresPanel = $window.FindName('stackFeaturesContainer')
|
|
$script:uiState.Controls.chkDownloadDrivers = $window.FindName('chkDownloadDrivers')
|
|
$script:uiState.Controls.cmbMake = $window.FindName('cmbMake')
|
|
# $script:uiState.Controls.cmbModel = $window.FindName('cmbModel') # cmbModel TextBox removed from XAML
|
|
$script:uiState.Controls.spMakeSection = $window.FindName('spMakeSection') # Updated StackPanel name
|
|
$script:uiState.Controls.btnGetModels = $window.FindName('btnGetModels')
|
|
$script:uiState.Controls.spModelFilterSection = $window.FindName('spModelFilterSection') # New StackPanel for filter
|
|
$script:uiState.Controls.txtModelFilter = $window.FindName('txtModelFilter') # New TextBox for filter
|
|
$script:uiState.Controls.lstDriverModels = $window.FindName('lstDriverModels')
|
|
# Set ListViewItem style to stretch content horizontally so cell templates fill the cell
|
|
$itemStyleDriverModels = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem])
|
|
$itemStyleDriverModels.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)))
|
|
$script:uiState.Controls.lstDriverModels.ItemContainerStyle = $itemStyleDriverModels
|
|
|
|
# Driver Models ListView setup
|
|
$driverModelsGridView = New-Object System.Windows.Controls.GridView
|
|
$script:uiState.Controls.lstDriverModels.View = $driverModelsGridView # Assign GridView to ListView first
|
|
|
|
# Add the selectable column using the new function
|
|
Add-SelectableGridViewColumn -ListView $script:uiState.Controls.lstDriverModels -HeaderCheckBoxScriptVariableName "chkSelectAllDriverModels" -ColumnWidth 70
|
|
|
|
# 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 "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left
|
|
$script:uiState.Controls.lstDriverModels.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) {
|
|
Invoke-ListViewSort -listView $script:uiState.Controls.lstDriverModels -property $header.Tag
|
|
}
|
|
}
|
|
)
|
|
$script:uiState.Controls.spDriverActionButtons = $window.FindName('spDriverActionButtons')
|
|
$script:uiState.Controls.btnSaveDriversJson = $window.FindName('btnSaveDriversJson')
|
|
$script:uiState.Controls.btnImportDriversJson = $window.FindName('btnImportDriversJson')
|
|
$script:uiState.Controls.btnDownloadSelectedDrivers = $window.FindName('btnDownloadSelectedDrivers')
|
|
$script:uiState.Controls.btnClearDriverList = $window.FindName('btnClearDriverList')
|
|
# New button
|
|
$script:uiState.Controls.chkInstallOffice = $window.FindName('chkInstallOffice')
|
|
$script:uiState.Controls.chkInstallApps = $window.FindName('chkInstallApps')
|
|
$script:uiState.Controls.OfficePathStackPanel = $window.FindName('OfficePathStackPanel')
|
|
$script:uiState.Controls.OfficePathGrid = $window.FindName('OfficePathGrid')
|
|
$script:uiState.Controls.CopyOfficeConfigXMLStackPanel = $window.FindName('CopyOfficeConfigXMLStackPanel')
|
|
$script:uiState.Controls.OfficeConfigurationXMLFileStackPanel = $window.FindName('OfficeConfigurationXMLFileStackPanel')
|
|
$script:uiState.Controls.OfficeConfigurationXMLFileGrid = $window.FindName('OfficeConfigurationXMLFileGrid')
|
|
$script:uiState.Controls.chkCopyOfficeConfigXML = $window.FindName('chkCopyOfficeConfigXML')
|
|
$script:uiState.Controls.chkLatestCU = $window.FindName('chkUpdateLatestCU')
|
|
$script:uiState.Controls.chkPreviewCU = $window.FindName('chkUpdatePreviewCU')
|
|
$script:uiState.Controls.btnCheckUSBDrives = $window.FindName('btnCheckUSBDrives')
|
|
$script:uiState.Controls.lstUSBDrives = $window.FindName('lstUSBDrives')
|
|
$script:uiState.Controls.chkSelectAllUSBDrives = $window.FindName('chkSelectAllUSBDrives')
|
|
$script:uiState.Controls.chkBuildUSBDriveEnable = $window.FindName('chkBuildUSBDriveEnable')
|
|
$script:uiState.Controls.usbSection = $window.FindName('usbDriveSection')
|
|
$script:uiState.Controls.chkSelectSpecificUSBDrives = $window.FindName('chkSelectSpecificUSBDrives')
|
|
$script:uiState.Controls.usbSelectionPanel = $window.FindName('usbDriveSelectionPanel')
|
|
$script:uiState.Controls.chkAllowExternalHardDiskMedia = $window.FindName('chkAllowExternalHardDiskMedia')
|
|
$script:uiState.Controls.chkPromptExternalHardDiskMedia = $window.FindName('chkPromptExternalHardDiskMedia')
|
|
$script:uiState.Controls.chkInstallWingetApps = $window.FindName('chkInstallWingetApps')
|
|
$script:uiState.Controls.wingetPanel = $window.FindName('wingetPanel')
|
|
$script:uiState.Controls.btnCheckWingetModule = $window.FindName('btnCheckWingetModule')
|
|
$script:uiState.Controls.txtWingetVersion = $window.FindName('txtWingetVersion')
|
|
$script:uiState.Controls.txtWingetModuleVersion = $window.FindName('txtWingetModuleVersion')
|
|
$script:uiState.Controls.applicationPathPanel = $window.FindName('applicationPathPanel')
|
|
$script:uiState.Controls.appListJsonPathPanel = $window.FindName('appListJsonPathPanel')
|
|
$script:uiState.Controls.btnBrowseApplicationPath = $window.FindName('btnBrowseApplicationPath')
|
|
$script:uiState.Controls.btnBrowseAppListJsonPath = $window.FindName('btnBrowseAppListJsonPath')
|
|
$script:uiState.Controls.chkBringYourOwnApps = $window.FindName('chkBringYourOwnApps')
|
|
$script:uiState.Controls.byoApplicationPanel = $window.FindName('byoApplicationPanel')
|
|
$script:uiState.Controls.wingetSearchPanel = $window.FindName('wingetSearchPanel')
|
|
$script:uiState.Controls.txtWingetSearch = $window.FindName('txtWingetSearch')
|
|
$script:uiState.Controls.btnWingetSearch = $window.FindName('btnWingetSearch')
|
|
$script:uiState.Controls.lstWingetResults = $window.FindName('lstWingetResults')
|
|
# Set ListViewItem style to stretch content horizontally so cell templates fill the cell
|
|
$itemStyleWingetResults = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem])
|
|
$itemStyleWingetResults.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)))
|
|
$script:uiState.Controls.lstWingetResults.ItemContainerStyle = $itemStyleWingetResults
|
|
$script:uiState.Controls.btnSaveWingetList = $window.FindName('btnSaveWingetList')
|
|
$script:uiState.Controls.btnImportWingetList = $window.FindName('btnImportWingetList')
|
|
$script:uiState.Controls.btnClearWingetList = $window.FindName('btnClearWingetList')
|
|
$script:uiState.Controls.btnDownloadSelected = $window.FindName('btnDownloadSelected')
|
|
$script:uiState.Controls.btnBrowseAppSource = $window.FindName('btnBrowseAppSource')
|
|
$script:uiState.Controls.btnBrowseFFUDevPath = $window.FindName('btnBrowseFFUDevPath')
|
|
$script:uiState.Controls.btnBrowseFFUCaptureLocation = $window.FindName('btnBrowseFFUCaptureLocation')
|
|
$script:uiState.Controls.btnBrowseOfficePath = $window.FindName('btnBrowseOfficePath')
|
|
$script:uiState.Controls.btnBrowseDriversFolder = $window.FindName('btnBrowseDriversFolder')
|
|
$script:uiState.Controls.btnBrowsePEDriversFolder = $window.FindName('btnBrowsePEDriversFolder')
|
|
$script:uiState.Controls.btnAddApplication = $window.FindName('btnAddApplication')
|
|
$script:uiState.Controls.btnSaveBYOApplications = $window.FindName('btnSaveBYOApplications')
|
|
$script:uiState.Controls.btnLoadBYOApplications = $window.FindName('btnLoadBYOApplications')
|
|
$script:uiState.Controls.btnClearBYOApplications = $window.FindName('btnClearBYOApplications')
|
|
$script:uiState.Controls.btnCopyBYOApps = $window.FindName('btnCopyBYOApps')
|
|
$script:uiState.Controls.lstApplications = $window.FindName('lstApplications')
|
|
$script:uiState.Controls.btnMoveTop = $window.FindName('btnMoveTop')
|
|
$script:uiState.Controls.btnMoveUp = $window.FindName('btnMoveUp')
|
|
$script:uiState.Controls.btnMoveDown = $window.FindName('btnMoveDown')
|
|
$script:uiState.Controls.btnMoveBottom = $window.FindName('btnMoveBottom')
|
|
$script:uiState.Controls.txtStatus = $window.FindName('txtStatus') # Assign txtStatus control
|
|
# Assign Progress Bar and Overall Status Text controls to script variables
|
|
$script:uiState.Controls.pbOverallProgress = $window.FindName('progressBar') # Use the correct x:Name from XAML
|
|
$script:uiState.Controls.txtOverallStatus = $window.FindName('txtStatus') # Use the correct x:Name from XAML (assuming it's txtStatus)
|
|
$script:uiState.Controls.cmbVMSwitchName = $window.FindName('cmbVMSwitchName')
|
|
$script:uiState.Controls.txtVMHostIPAddress = $window.FindName('txtVMHostIPAddress')
|
|
$script:uiState.Controls.txtCustomVMSwitchName = $window.FindName('txtCustomVMSwitchName')
|
|
$script:uiState.Controls.txtFFUDevPath = $window.FindName('txtFFUDevPath')
|
|
$script:uiState.Controls.txtCustomFFUNameTemplate = $window.FindName('txtCustomFFUNameTemplate')
|
|
$script:uiState.Controls.txtFFUCaptureLocation = $window.FindName('txtFFUCaptureLocation')
|
|
$script:uiState.Controls.txtShareName = $window.FindName('txtShareName')
|
|
$script:uiState.Controls.txtUsername = $window.FindName('txtUsername')
|
|
$script:uiState.Controls.chkCompactOS = $window.FindName('chkCompactOS')
|
|
$script:uiState.Controls.chkOptimize = $window.FindName('chkOptimize')
|
|
$script:uiState.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching')
|
|
$script:uiState.Controls.chkCreateCaptureMedia = $window.FindName('chkCreateCaptureMedia')
|
|
$script:uiState.Controls.chkCreateDeploymentMedia = $window.FindName('chkCreateDeploymentMedia')
|
|
$script:uiState.Controls.chkCopyAutopilot = $window.FindName('chkCopyAutopilot')
|
|
$script:uiState.Controls.chkCopyUnattend = $window.FindName('chkCopyUnattend')
|
|
$script:uiState.Controls.chkCopyPPKG = $window.FindName('chkCopyPPKG')
|
|
$script:uiState.Controls.chkCleanupAppsISO = $window.FindName('chkCleanupAppsISO')
|
|
$script:uiState.Controls.chkCleanupCaptureISO = $window.FindName('chkCleanupCaptureISO')
|
|
$script:uiState.Controls.chkCleanupDeployISO = $window.FindName('chkCleanupDeployISO')
|
|
$script:uiState.Controls.chkCleanupDrivers = $window.FindName('chkCleanupDrivers')
|
|
$script:uiState.Controls.chkRemoveFFU = $window.FindName('chkRemoveFFU')
|
|
$script:uiState.Controls.txtDiskSize = $window.FindName('txtDiskSize')
|
|
$script:uiState.Controls.txtMemory = $window.FindName('txtMemory')
|
|
$script:uiState.Controls.txtProcessors = $window.FindName('txtProcessors')
|
|
$script:uiState.Controls.txtVMLocation = $window.FindName('txtVMLocation')
|
|
$script:uiState.Controls.txtVMNamePrefix = $window.FindName('txtVMNamePrefix')
|
|
$script:uiState.Controls.cmbLogicalSectorSize = $window.FindName('cmbLogicalSectorSize')
|
|
$script:uiState.Controls.txtProductKey = $window.FindName('txtProductKey')
|
|
$script:uiState.Controls.txtOfficePath = $window.FindName('txtOfficePath')
|
|
$script:uiState.Controls.txtOfficeConfigXMLFilePath = $window.FindName('txtOfficeConfigXMLFilePath')
|
|
$script:uiState.Controls.txtDriversFolder = $window.FindName('txtDriversFolder')
|
|
$script:uiState.Controls.txtPEDriversFolder = $window.FindName('txtPEDriversFolder')
|
|
$script:uiState.Controls.chkCopyPEDrivers = $window.FindName('chkCopyPEDrivers')
|
|
$script:uiState.Controls.chkUpdateLatestCU = $window.FindName('chkUpdateLatestCU')
|
|
$script:uiState.Controls.chkUpdateLatestNet = $window.FindName('chkUpdateLatestNet')
|
|
$script:uiState.Controls.chkUpdateLatestDefender = $window.FindName('chkUpdateLatestDefender')
|
|
$script:uiState.Controls.chkUpdateEdge = $window.FindName('chkUpdateEdge')
|
|
$script:uiState.Controls.chkUpdateOneDrive = $window.FindName('chkUpdateOneDrive')
|
|
$script:uiState.Controls.chkUpdateLatestMSRT = $window.FindName('chkUpdateLatestMSRT')
|
|
$script:uiState.Controls.chkUpdatePreviewCU = $window.FindName('chkUpdatePreviewCU')
|
|
$script:uiState.Controls.txtApplicationPath = $window.FindName('txtApplicationPath')
|
|
$script:uiState.Controls.txtAppListJsonPath = $window.FindName('txtAppListJsonPath')
|
|
|
|
# Assign Driver Checkboxes
|
|
$script:uiState.Controls.chkInstallDrivers = $window.FindName('chkInstallDrivers')
|
|
$script:uiState.Controls.chkCopyDrivers = $window.FindName('chkCopyDrivers')
|
|
$script:uiState.Controls.chkCompressDriversToWIM = $window.FindName('chkCompressDriversToWIM')
|
|
$script:uiState.Controls.chkRemoveApps = $window.FindName('chkRemoveApps')
|
|
$script:uiState.Controls.chkRemoveUpdates = $window.FindName('chkRemoveUpdates')
|
|
$script:uiState.Controls.chkUpdateLatestMicrocode = $window.FindName('chkUpdateLatestMicrocode')
|
|
|
|
# AppsScriptVariables Controls
|
|
$script:uiState.Controls.chkDefineAppsScriptVariables = $window.FindName('chkDefineAppsScriptVariables')
|
|
$script:uiState.Controls.appsScriptVariablesPanel = $window.FindName('appsScriptVariablesPanel')
|
|
$script:uiState.Controls.txtAppsScriptKey = $window.FindName('txtAppsScriptKey')
|
|
$script:uiState.Controls.txtAppsScriptValue = $window.FindName('txtAppsScriptValue')
|
|
$script:uiState.Controls.btnAddAppsScriptVariable = $window.FindName('btnAddAppsScriptVariable')
|
|
$script:uiState.Controls.lstAppsScriptVariables = $window.FindName('lstAppsScriptVariables')
|
|
# Bind ItemsSource to the data list
|
|
$script:uiState.Controls.lstAppsScriptVariables.ItemsSource = $script:uiState.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])
|
|
$itemStyleAppsScriptVars.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)))
|
|
$script:uiState.Controls.lstAppsScriptVariables.ItemContainerStyle = $itemStyleAppsScriptVars
|
|
|
|
# The GridView for lstAppsScriptVariables is defined in XAML. We need to get it and add the column.
|
|
if ($script:uiState.Controls.lstAppsScriptVariables.View -is [System.Windows.Controls.GridView]) {
|
|
Add-SelectableGridViewColumn -ListView $script:uiState.Controls.lstAppsScriptVariables -HeaderCheckBoxScriptVariableName "chkSelectAllAppsScriptVariables" -ColumnWidth 60
|
|
|
|
# Make Key and Value columns sortable
|
|
$appsScriptVarsGridView = $script:uiState.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
|
|
$script:uiState.Controls.lstAppsScriptVariables.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) {
|
|
Invoke-ListViewSort -listView $script:uiState.Controls.lstAppsScriptVariables -property $header.Tag
|
|
}
|
|
}
|
|
)
|
|
}
|
|
else {
|
|
WriteLog "Warning: lstAppsScriptVariables.View is not a GridView. Selectable column not added, and sorting cannot be enabled."
|
|
}
|
|
|
|
$script:uiState.Controls.btnRemoveSelectedAppsScriptVariables = $window.FindName('btnRemoveSelectedAppsScriptVariables') # Updated variable name
|
|
$script:uiState.Controls.btnClearAppsScriptVariables = $window.FindName('btnClearAppsScriptVariables')
|
|
|
|
# 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 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 = $window.FindName('chkUpdateADK') # Assign chkUpdateADK
|
|
$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
|
|
WriteLog "DEBUG: Before cmbLogicalSectorSize assignment."
|
|
WriteLog "DEBUG: \$script:uiState.Defaults.generalDefaults is $($script:uiState.Defaults.generalDefaults -ne $null)."
|
|
if ($script:uiState.Defaults.generalDefaults -ne $null) {
|
|
WriteLog "DEBUG: \$script:uiState.Defaults.generalDefaults.LogicalSectorSize is $($script:uiState.Defaults.generalDefaults.LogicalSectorSize)."
|
|
}
|
|
$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 = $window.FindName('txtDriversJsonPath') # Assign new TextBox
|
|
$script:uiState.Controls.txtDriversJsonPath.Text = $script:uiState.Defaults.generalDefaults.DriversJsonPath # Set default text
|
|
$script:uiState.Controls.btnBrowseDriversJsonPath = $window.FindName('btnBrowseDriversJsonPath') # Assign new Button
|
|
$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 (Keep existing logic)
|
|
$makeList = @('Microsoft', 'Dell', 'HP', 'Lenovo') # Added 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'
|
|
# Make the model filter, list, and action buttons visible immediately
|
|
# This allows users to import a Drivers.json without first clicking "Get Models"
|
|
$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 # Disable the button
|
|
|
|
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
|
|
})
|
|
$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
|
|
$localDriversFolder = $window.FindName('txtDriversFolder').Text
|
|
$localWindowsRelease = $window.FindName('cmbWindowsRelease').SelectedItem.Value
|
|
$localWindowsArch = $window.FindName('cmbWindowsArch').SelectedItem
|
|
$localHeaders = $Headers # Use script-level variable
|
|
$localUserAgent = $UserAgent # Use script-level variable
|
|
$compressDrivers = $script:uiState.Controls.chkCompressDriversToWIM.IsChecked
|
|
|
|
# Define common necessary task-specific variables locally
|
|
# Ensure required selections are made
|
|
if ($null -eq $window.FindName('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 $window.FindName('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 -eq $window.FindName('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 = $window.FindName('txtDriversFolder').Text
|
|
$localWindowsRelease = $window.FindName('cmbWindowsRelease').SelectedItem.Value
|
|
$localWindowsArch = $window.FindName('cmbWindowsArch').SelectedItem
|
|
$localWindowsVersion = if ($null -ne $window.FindName('cmbWindowsVersion').SelectedItem) { $window.FindName('cmbWindowsVersion').SelectedItem } else { $null }
|
|
$localHeaders = $Headers # Use script-level variable
|
|
$localUserAgent = $UserAgent # Use script-level variable
|
|
$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 })
|
|
$script:uiState.Controls.btnImportDriversJson.Add_Click({ Import-DriversJson })
|
|
|
|
# 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'
|
|
})
|
|
|
|
# Build dynamic multi-column checkboxes for optional features (Keep existing logic)
|
|
if ($script:featuresPanel) { BuildFeaturesGrid -parent $script:featuresPanel -allowedFeatures $script:windowsSettingsDefaults.AllowedFeatures }
|
|
|
|
# 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
|
|
}
|
|
}
|
|
}
|
|
$window.FindName('chkUpdateLatestDefender').Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateLatestDefender').Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateEdge').Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateEdge').Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateOneDrive').Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateOneDrive').Add_Unchecked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('chkUpdateLatestMSRT').Add_Checked({ & $script:uiState.Controls.UpdateInstallAppsBasedOnUpdates -State $script:uiState })
|
|
$window.FindName('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) { $window.FindName('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) { $window.FindName('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
|
|
$window.FindName('txtAppName').Text = ''
|
|
$window.FindName('txtAppCommandLine').Text = ''
|
|
$window.FindName('txtAppArguments').Text = ''
|
|
$window.FindName('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
|
|
}
|
|
})
|
|
|
|
# Winget Search ListView setup
|
|
$wingetGridView = New-Object System.Windows.Controls.GridView
|
|
$script:uiState.Controls.lstWingetResults.View = $wingetGridView # Assign GridView to ListView first
|
|
|
|
# Add the selectable column using the new function
|
|
Add-SelectableGridViewColumn -ListView $script:uiState.Controls.lstWingetResults -HeaderCheckBoxScriptVariableName "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
|
|
Add-SortableColumn -gridView $wingetGridView -header "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left
|
|
$script:uiState.Controls.lstWingetResults.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) {
|
|
Invoke-ListViewSort -listView $script:uiState.Controls.lstWingetResults -property $header.Tag
|
|
}
|
|
}
|
|
)
|
|
$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" }
|
|
})
|
|
# --------------------------------------------------------------------------
|
|
# SECTION: Background Task Management (Using ForEach-Object -Parallel)
|
|
# --------------------------------------------------------------------------
|
|
# Modules (UI_Helpers, BackgroundTasks) and Scripts (WingetFunctions) are imported/dot-sourced
|
|
# directly into the main script scope. ForEach-Object -Parallel automatically handles
|
|
# module/variable availability in the parallel threads.
|
|
# UI updates are handled by calling helper functions directly on the main UI thread
|
|
# after the parallel processing completes.
|
|
# --------------------------------------------------------------------------
|
|
|
|
$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 = $window.FindName('txtApplicationPath').Text
|
|
$localAppListJsonPath = $window.FindName('txtAppListJsonPath').Text
|
|
$localWindowsArch = $window.FindName('cmbWindowsArch').SelectedItem
|
|
$localOrchestrationPath = Join-Path -Path $window.FindName('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) { $window.FindName('txtAppSource').Text = $selectedPath }
|
|
})
|
|
$script:uiState.Controls.btnAddApplication.Add_Click({
|
|
$name = $window.FindName('txtAppName').Text
|
|
$commandLine = $window.FindName('txtAppCommandLine').Text
|
|
$arguments = $window.FindName('txtAppArguments').Text
|
|
$source = $window.FindName('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 = $window.FindName('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)
|
|
$window.FindName('txtAppName').Text = ""
|
|
$window.FindName('txtAppCommandLine').Text = ""
|
|
$window.FindName('txtAppArguments').Text = ""
|
|
$window.FindName('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 = $window.FindName('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 }
|
|
})
|
|
$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 = $window.FindName('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) { $window.FindName('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 = $window.FindName('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) { $window.FindName('txtFFUDevPath').Text = $selectedPath }
|
|
})
|
|
$script:uiState.Controls.btnBrowseFFUCaptureLocation.Add_Click({
|
|
$selectedPath = Show-ModernFolderPicker -Title "Select FFU Capture Location"
|
|
if ($selectedPath) { $window.FindName('txtFFUCaptureLocation').Text = $selectedPath }
|
|
})
|
|
$script:uiState.Controls.btnBrowseOfficePath.Add_Click({
|
|
$selectedPath = Show-ModernFolderPicker -Title "Select Office Path"
|
|
if ($selectedPath) { $window.FindName('txtOfficePath').Text = $selectedPath }
|
|
})
|
|
$script:uiState.Controls.btnBrowseDriversFolder.Add_Click({
|
|
$selectedPath = Show-ModernFolderPicker -Title "Select Drivers Folder"
|
|
if ($selectedPath) { $window.FindName('txtDriversFolder').Text = $selectedPath }
|
|
})
|
|
$script:uiState.Controls.btnBrowsePEDriversFolder.Add_Click({
|
|
$selectedPath = Show-ModernFolderPicker -Title "Select PE Drivers Folder"
|
|
if ($selectedPath) { $window.FindName('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'
|
|
}
|
|
|
|
})
|
|
|
|
# Function to search for Winget apps
|
|
function Search-WingetApps {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
try {
|
|
$searchQuery = $State.Controls.txtWingetSearch.Text
|
|
if ([string]::IsNullOrWhiteSpace($searchQuery)) { return }
|
|
|
|
# Get current items from the ListView
|
|
$currentItemsInListView = @()
|
|
if ($null -ne $State.Controls.lstWingetResults.ItemsSource) {
|
|
$currentItemsInListView = @($State.Controls.lstWingetResults.ItemsSource)
|
|
}
|
|
elseif ($State.Controls.lstWingetResults.HasItems) {
|
|
$currentItemsInListView = @($State.Controls.lstWingetResults.Items)
|
|
}
|
|
|
|
# Store selected apps from the current view
|
|
$selectedAppsFromView = @($currentItemsInListView | Where-Object { $_.IsSelected })
|
|
|
|
# Search for new apps
|
|
$searchedAppResults = Search-WingetPackagesPublic -Query $searchQuery | ForEach-Object {
|
|
[PSCustomObject]@{
|
|
IsSelected = $false # New items are not selected by default
|
|
Name = $_.Name
|
|
Id = $_.Id
|
|
Version = $_.Version
|
|
Source = $_.Source
|
|
DownloadStatus = ""
|
|
}
|
|
}
|
|
|
|
$finalAppList = [System.Collections.Generic.List[object]]::new()
|
|
$addedAppIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
|
|
|
# Add previously selected apps first
|
|
foreach ($app in $selectedAppsFromView) {
|
|
$finalAppList.Add($app)
|
|
$addedAppIds.Add($app.Id) | Out-Null
|
|
}
|
|
|
|
# Add new search results, avoiding duplicates of already added (selected) apps
|
|
foreach ($result in $searchedAppResults) {
|
|
if (-not $addedAppIds.Contains($result.Id)) {
|
|
$finalAppList.Add($result)
|
|
$addedAppIds.Add($result.Id) | Out-Null # Track added IDs to prevent duplicates from search results themselves
|
|
}
|
|
}
|
|
|
|
# Update the ListView's ItemsSource
|
|
$script:uiState.Controls.lstWingetResults.ItemsSource = $finalAppList.ToArray()
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Error searching for apps: $_", "Error", "OK", "Error")
|
|
}
|
|
}
|
|
|
|
# Function to save selected apps to JSON
|
|
function Save-WingetList {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
try {
|
|
$selectedApps = $State.Controls.lstWingetResults.Items | Where-Object { $_.IsSelected }
|
|
if (-not $selectedApps) {
|
|
[System.Windows.MessageBox]::Show("No apps selected to save.", "Warning", "OK", "Warning")
|
|
return
|
|
}
|
|
|
|
$appList = @{
|
|
apps = @($selectedApps | ForEach-Object {
|
|
[ordered]@{
|
|
name = $_.Name
|
|
id = $_.Id
|
|
source = $_.Source.ToLower()
|
|
}
|
|
})
|
|
}
|
|
|
|
$sfd = New-Object System.Windows.Forms.SaveFileDialog
|
|
$sfd.Filter = "JSON files (*.json)|*.json"
|
|
$sfd.Title = "Save App List"
|
|
$sfd.InitialDirectory = $AppsPath
|
|
$sfd.FileName = "AppList.json"
|
|
|
|
if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
$appList | ConvertTo-Json -Depth 10 | Set-Content $sfd.FileName -Encoding UTF8
|
|
[System.Windows.MessageBox]::Show("App list saved successfully.", "Success", "OK", "Information")
|
|
}
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Error saving app list: $_", "Error", "OK", "Error")
|
|
}
|
|
}
|
|
|
|
# Function to import app list from JSON
|
|
function Import-WingetList {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[psobject]$State
|
|
)
|
|
try {
|
|
$ofd = New-Object System.Windows.Forms.OpenFileDialog
|
|
$ofd.Filter = "JSON files (*.json)|*.json"
|
|
$ofd.Title = "Import App List"
|
|
$ofd.InitialDirectory = $AppsPath
|
|
|
|
if ($ofd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|
$importedAppsData = Get-Content $ofd.FileName -Raw | ConvertFrom-Json
|
|
|
|
$newAppListForItemsSource = [System.Collections.Generic.List[object]]::new()
|
|
|
|
if ($null -ne $importedAppsData.apps) {
|
|
foreach ($appInfo in $importedAppsData.apps) {
|
|
$newAppListForItemsSource.Add([PSCustomObject]@{
|
|
IsSelected = $true # Imported apps are marked as selected
|
|
Name = $appInfo.name
|
|
Id = $appInfo.id
|
|
Version = "" # Will be populated when searching or if data exists
|
|
Source = $appInfo.source
|
|
DownloadStatus = ""
|
|
})
|
|
}
|
|
}
|
|
|
|
$State.Controls.lstWingetResults.ItemsSource = $newAppListForItemsSource.ToArray()
|
|
|
|
[System.Windows.MessageBox]::Show("App list imported successfully.", "Success", "OK", "Information")
|
|
}
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Error importing app list: $_", "Error", "OK", "Error")
|
|
}
|
|
}
|
|
|
|
# Function to remove application and reorder priorities
|
|
function Remove-Application {
|
|
param(
|
|
$priority,
|
|
[psobject]$State
|
|
)
|
|
|
|
$listView = $State.Controls.lstApplications
|
|
|
|
# Remove the item with the specified priority
|
|
$itemToRemove = $listView.Items | Where-Object { $_.Priority -eq $priority } | Select-Object -First 1
|
|
if ($itemToRemove) {
|
|
$listView.Items.Remove($itemToRemove)
|
|
# Reorder priorities for remaining items
|
|
Update-ListViewPriorities -ListView $listView
|
|
# Update the Copy Apps button state
|
|
Update-CopyButtonState -State $State
|
|
}
|
|
}
|
|
|
|
# Function to save BYO applications to JSON
|
|
function Save-BYOApplicationList {
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$Path,
|
|
[Parameter(Mandatory)]
|
|
[psobject]$State
|
|
)
|
|
|
|
$listView = $State.Controls.lstApplications
|
|
if (-not $listView -or $listView.Items.Count -eq 0) {
|
|
[System.Windows.MessageBox]::Show("No applications to save.", "Save Applications", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
return
|
|
}
|
|
|
|
try {
|
|
# Ensure items are sorted by current priority before saving
|
|
# Exclude CopyStatus when saving
|
|
$applications = $listView.Items | Sort-Object Priority | Select-Object Priority, Name, CommandLine, Arguments, Source
|
|
$applications | ConvertTo-Json -Depth 5 | Set-Content -Path $Path -Force -Encoding UTF8
|
|
[System.Windows.MessageBox]::Show("Applications saved successfully to `"$Path`".", "Save Applications", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Failed to save applications: $_", "Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
|
}
|
|
}
|
|
|
|
# Function to load BYO applications from JSON
|
|
function Import-BYOApplicationList {
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$Path,
|
|
[Parameter(Mandatory)]
|
|
[psobject]$State
|
|
)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
[System.Windows.MessageBox]::Show("Application list file not found at `"$Path`".", "Import Applications", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
|
|
return
|
|
}
|
|
|
|
try {
|
|
$applications = Get-Content -Path $Path -Raw | ConvertFrom-Json
|
|
$listView = $window.FindName('lstApplications')
|
|
$listView.Items.Clear()
|
|
|
|
# Add items and sort by priority from the file
|
|
$sortedApps = $applications | Sort-Object Priority
|
|
foreach ($app in $sortedApps) {
|
|
# Ensure all properties exist, add CopyStatus
|
|
$appObject = [PSCustomObject]@{
|
|
Priority = $app.Priority # Keep original priority for now
|
|
Name = $app.Name
|
|
CommandLine = $app.CommandLine
|
|
Arguments = if ($app.PSObject.Properties['Arguments']) { $app.Arguments } else { "" } # Handle missing Arguments
|
|
Source = $app.Source
|
|
CopyStatus = "" # Initialize CopyStatus
|
|
}
|
|
$listView.Items.Add($appObject)
|
|
}
|
|
|
|
# Reorder priorities sequentially after loading
|
|
Update-ListViewPriorities -ListView $listView
|
|
# Update the Copy Apps button state
|
|
Update-CopyButtonState -State $State
|
|
|
|
[System.Windows.MessageBox]::Show("Applications imported successfully from `"$Path`".", "Import Applications", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
|
|
}
|
|
catch {
|
|
[System.Windows.MessageBox]::Show("Failed to import applications: $_", "Error", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Error)
|
|
}
|
|
}
|
|
|
|
# Button: Build FFU
|
|
$btnRun = $window.FindName('btnRun')
|
|
$btnRun.Add_Click({
|
|
try {
|
|
$progressBar = $window.FindName('progressBar')
|
|
$txtStatus = $window.FindName('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")
|
|
$window.FindName('txtStatus').Text = "FFU build failed."
|
|
}
|
|
finally {
|
|
$window.FindName('progressBar').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() # Clear the backing data list
|
|
|
|
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."
|
|
$window.FindName('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()
|