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