diff --git a/FFUDevelopment.zip b/FFUDevelopment.zip new file mode 100644 index 0000000..70cea0d Binary files /dev/null and b/FFUDevelopment.zip differ diff --git a/FFUDevelopment/BuildFFUVM_UI.ps1 b/FFUDevelopment/BuildFFUVM_UI.ps1 index dfa01b4..4247de1 100644 --- a/FFUDevelopment/BuildFFUVM_UI.ps1 +++ b/FFUDevelopment/BuildFFUVM_UI.ps1 @@ -9,7 +9,7 @@ if ($PSVersionTable.PSVersion.Major -lt 7) { } # Creating custom state object to hold UI state and data -$FFUDevelopmentPath = 'C:\FFUDevelopment' # hard coded for testing +$FFUDevelopmentPath = $PSScriptRoot $script:uiState = [PSCustomObject]@{ FFUDevelopmentPath = $FFUDevelopmentPath; diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml index fecf2b0..d485da0 100644 --- a/FFUDevelopment/BuildFFUVM_UI.xaml +++ b/FFUDevelopment/BuildFFUVM_UI.xaml @@ -665,13 +665,15 @@ - + - + - + - + + + @@ -728,11 +730,20 @@ - - + + + + + + + + + + + - - + + @@ -743,8 +754,8 @@ - - + + @@ -780,8 +791,8 @@ - - + + diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 index bb76e1b..bf892b5 100644 --- a/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 +++ b/FFUDevelopment/FFU.Common/FFU.Common.Parallel.psm1 @@ -21,7 +21,9 @@ function Invoke-ParallelProcessing { [Parameter(Mandatory = $false)] [object]$WindowObject = $null, # Changed type to [object] [Parameter(Mandatory = $false)] - [string]$MainThreadLogPath = $null # New parameter for the log path + [string]$MainThreadLogPath = $null, # New parameter for the log path + [Parameter(Mandatory = $false)] + [int]$ThrottleLimit = 5 ) # Check if running in UI mode by verifying the types of the passed objects $isUiMode = ($null -ne $WindowObject -and $WindowObject -is [System.Windows.Window] -and $null -ne $ListViewControl -and $ListViewControl -is [System.Windows.Controls.ListView]) @@ -287,7 +289,7 @@ function Invoke-ParallelProcessing { DriverPath = $driverPathValue } - } -ThrottleLimit 5 -AsJob + } -ThrottleLimit $ThrottleLimit -AsJob } catch { # Catch errors during the *creation* of the parallel jobs (e.g., module loading in main thread failed) @@ -412,7 +414,8 @@ function Invoke-ParallelProcessing { } } - if (-not $jobHandled) { # Catches 'Completed' with no data + if (-not $jobHandled) { + # Catches 'Completed' with no data $finalIdentifier = "UnknownJob" WriteLog "Job $($completedJob.Id) completed with state '$($completedJob.State)' but had no data." $finalStatus = "$ErrorStatusPrefix No Result Data" diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 index d833f70..21393d5 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Applications.psm1 @@ -300,7 +300,8 @@ function Invoke-CopyBYOApps { -CompletedStatusText "Copied" ` -ErrorStatusPrefix "Error: " ` -WindowObject $State.Window ` - -MainThreadLogPath $State.LogFilePath + -MainThreadLogPath $State.LogFilePath ` + -ThrottleLimit $State.Controls.txtThreads.Text # Final status update (handled by Invoke-ParallelProcessing) $State.Controls.pbOverallProgress.Visibility = 'Collapsed' diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 index 3b40fbb..b12a99e 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 @@ -89,6 +89,7 @@ function Get-UIConfig { UserAppListPath = "$($State.Controls.txtApplicationPath.Text)\UserAppList.json" USBDriveList = @{} Username = $State.Controls.txtUsername.Text + Threads = [int]$State.Controls.txtThreads.Text Verbose = $State.Controls.chkVerbose.IsChecked VMHostIPAddress = $State.Controls.txtVMHostIPAddress.Text VMLocation = $State.Controls.txtVMLocation.Text @@ -272,6 +273,7 @@ function Update-UIFromConfig { Set-UIValue -ControlName 'txtFFUCaptureLocation' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'FFUCaptureLocation' -State $State Set-UIValue -ControlName 'txtShareName' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'ShareName' -State $State Set-UIValue -ControlName 'txtUsername' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Username' -State $State + Set-UIValue -ControlName 'txtThreads' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Threads' -State $State Set-UIValue -ControlName 'chkBuildUSBDriveEnable' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'BuildUSBDrive' -State $State Set-UIValue -ControlName 'chkCompactOS' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CompactOS' -State $State Set-UIValue -ControlName 'chkUpdateADK' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'UpdateADK' -State $State diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 index cd00641..74f17b1 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 @@ -633,7 +633,8 @@ function Invoke-DownloadSelectedDrivers { -CompletedStatusText 'Completed' ` -ErrorStatusPrefix 'Error: ' ` -WindowObject $State.Window ` - -MainThreadLogPath $State.LogFilePath + -MainThreadLogPath $State.LogFilePath ` + -ThrottleLimit $State.Controls.txtThreads.Text $overallSuccess = $true $successfullyDownloaded = [System.Collections.Generic.List[PSCustomObject]]::new() @@ -670,10 +671,10 @@ function Invoke-DownloadSelectedDrivers { $make = $makeLookup[$modelName] if ($make) { $successfullyDownloaded.Add([PSCustomObject]@{ - Make = $make - Model = $modelName - DriverPath = $driverPath - }) + Make = $make + Model = $modelName + DriverPath = $driverPath + }) } else { WriteLog "Warning: Could not find 'Make' for successful download of model '$modelName'. Skipping from DriverMapping.json." @@ -722,7 +723,7 @@ function Invoke-DownloadSelectedDrivers { } 'HP' { $modelObject = @{ - Name = $driverItem.Model + Name = $driverItem.Model } } 'Lenovo' { diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 index 3e74e2e..a25683a 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 @@ -2,6 +2,69 @@ function Register-EventHandlers { param([PSCustomObject]$State) WriteLog "Registering UI event handlers..." + # -------------------------------------------------------------------------- + # SECTION: Shared Input Validation Handlers + # -------------------------------------------------------------------------- + # Define a shared event handler for TextBoxes that should only accept integer input + $integerPreviewTextInputHandler = { + param($eventSource, $textCompositionEventArgs) + # Use a regex to check if the input text is NOT a digit. \D matches any non-digit character. + if ($textCompositionEventArgs.Text -match '\D') { + # If the input is not a digit, mark the event as handled to prevent the character from being entered. + $textCompositionEventArgs.Handled = $true + } + } + + # Define a handler to validate pasted text, ensuring it's only integers + $integerPastingHandler = { + param($sender, $pastingEventArgs) + if ($pastingEventArgs.DataObject.GetDataPresent([string])) { + $pastedText = $pastingEventArgs.DataObject.GetData([string]) + # Check if the pasted text consists ONLY of one or more digits. + if ($pastedText -notmatch '^\d+$') { + # If not, cancel the paste operation. + $pastingEventArgs.CancelCommand() + } + } + else { + # If the pasted data is not in a string format, cancel it. + $pastingEventArgs.CancelCommand() + } + } + + # List of TextBox controls that require integer-only input + $integerOnlyTextBoxes = @( + $State.Controls.txtDiskSize, + $State.Controls.txtMemory, + $State.Controls.txtProcessors, + $State.Controls.txtThreads + ) + + # Attach the handlers to each relevant textbox + foreach ($textBox in $integerOnlyTextBoxes) { + if ($null -ne $textBox) { + $textBox.Add_PreviewTextInput($integerPreviewTextInputHandler) + [System.Windows.DataObject]::AddPastingHandler($textBox, $integerPastingHandler) + } + } + + # Add specific validation for the Threads textbox to ensure it's not empty and is at least 1 + if ($null -ne $State.Controls.txtThreads) { + $State.Controls.txtThreads.Add_LostFocus({ + param($eventSource, $routedEventArgs) + $textBox = $eventSource + $currentValue = 0 + # Try to parse the current text as an integer + $isValidInteger = [int]::TryParse($textBox.Text, [ref]$currentValue) + + # If the text is not a valid integer OR the value is less than 1, reset it to the default value '1' + if (-not $isValidInteger -or $currentValue -lt 1) { + $textBox.Text = '1' + WriteLog "Threads value was invalid or less than 1. Reset to 1." + } + }) + } + # Build Tab Event Handlers $State.Controls.btnBrowseFFUDevPath.Add_Click({ param($eventSource, $routedEventArgs) diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 index 02f5365..a63a1a3 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 @@ -95,6 +95,7 @@ function Initialize-UIControls { $State.Controls.txtFFUCaptureLocation = $window.FindName('txtFFUCaptureLocation') $State.Controls.txtShareName = $window.FindName('txtShareName') $State.Controls.txtUsername = $window.FindName('txtUsername') + $State.Controls.txtThreads = $window.FindName('txtThreads') $State.Controls.chkCompactOS = $window.FindName('chkCompactOS') $State.Controls.chkOptimize = $window.FindName('chkOptimize') $State.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching') @@ -206,6 +207,7 @@ function Initialize-UIDefaults { $State.Controls.txtFFUCaptureLocation.Text = $State.Defaults.generalDefaults.FFUCaptureLocation $State.Controls.txtShareName.Text = $State.Defaults.generalDefaults.ShareName $State.Controls.txtUsername.Text = $State.Defaults.generalDefaults.Username + $State.Controls.txtThreads.Text = $State.Defaults.generalDefaults.Threads $State.Controls.chkBuildUSBDriveEnable.IsChecked = $State.Defaults.generalDefaults.BuildUSBDriveEnable $State.Controls.chkCompactOS.IsChecked = $State.Defaults.generalDefaults.CompactOS $State.Controls.chkUpdateADK.IsChecked = $State.Defaults.generalDefaults.UpdateADK diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 index 57d8648..c5449c9 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 @@ -155,12 +155,12 @@ function Search-WingetPackagesPublic { # for large datasets as it avoids holding complex objects in memory and bypasses the # expensive formatting system for the raw results. Find-WinGetPackage -Query $Query -ErrorAction Stop | - Select-Object -Property @{Name = 'IsSelected'; Expression = { $false } }, - Name, - Id, - Version, - Source, - @{Name = 'DownloadStatus'; Expression = { '' } } + Select-Object -Property @{Name = 'IsSelected'; Expression = { $false } }, + Name, + Id, + Version, + Source, + @{Name = 'DownloadStatus'; Expression = { '' } } } catch { WriteLog "Error during Winget search: $($_.Exception.Message)" @@ -731,7 +731,8 @@ function Invoke-WingetDownload { -CompletedStatusText "Completed" ` -ErrorStatusPrefix "Error: " ` -WindowObject $State.Window ` - -MainThreadLogPath $State.LogFilePath + -MainThreadLogPath $State.LogFilePath ` + -ThrottleLimit $State.Controls.txtThreads.Text # Final status update is handled by Invoke-ParallelProcessing, but we need to re-enable the button $State.Controls.pbOverallProgress.Visibility = 'Collapsed' @@ -754,10 +755,10 @@ function Update-WingetVersionFields { [string]$moduleText ) $State.Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Normal, [Action] { - $State.Controls.txtWingetVersion.Text = $wingetText - $State.Controls.txtWingetModuleVersion.Text = $moduleText - [System.Windows.Forms.Application]::DoEvents() - }) + $State.Controls.txtWingetVersion.Text = $wingetText + $State.Controls.txtWingetModuleVersion.Text = $moduleText + [System.Windows.Forms.Application]::DoEvents() + }) } Export-ModuleMember -Function * \ No newline at end of file diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 index 48eef85..d74a39c 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 @@ -112,6 +112,7 @@ function Get-GeneralDefaults { FFUCaptureLocation = $ffuCapturePath ShareName = "FFUCaptureShare" Username = "ffu_user" + Threads = 5 BuildUSBDriveEnable = $false CompactOS = $true Optimize = $true