mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Feat: Add configurable thread limit for parallel operations
Adds a "Threads" setting to the UI, allowing users to control the throttle limit for parallel tasks like driver and application processing. This introduces a new textbox in the build options and updates the parallel processing function to use this configurable value instead of a hardcoded one. Input validation is also added to ensure the threads value is a valid integer and is at least 1. The new setting is integrated into the configuration save/load functionality.
This commit is contained in:
Binary file not shown.
@@ -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;
|
||||
|
||||
@@ -665,13 +665,15 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 5: Username -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 6: General Build Options Header -->
|
||||
<!-- Row 6: Threads -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 7: General Build Options Checkboxes -->
|
||||
<!-- Row 7: General Build Options Header -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 8: Build USB Drive Header -->
|
||||
<!-- Row 8: General Build Options Checkboxes -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 9: Build USB Drive Options Grid -->
|
||||
<!-- Row 9: Build USB Drive Section -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 10: Post-Build Cleanup -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -728,11 +730,20 @@
|
||||
<TextBlock Grid.Column="0" Text="Username" VerticalAlignment="Center" ToolTip="Username for accessing the shared folder. The default is ffu_user. The script will auto-create the account and password. When finished, it will remove the account."/>
|
||||
<TextBox x:Name="txtUsername" Grid.Column="1" Margin="5" VerticalAlignment="Center" ToolTip="Username for accessing the shared folder. The default is ffu_user. The script will auto-create the account and password. When finished, it will remove the account."/>
|
||||
</Grid>
|
||||
<!-- Row 6: General Build Options Header -->
|
||||
<TextBlock Grid.Row="6" Text="General Build Options" FontWeight="Bold" FontSize="16" Margin="0,10,0,5"/>
|
||||
<!-- Row 6: Threads -->
|
||||
<Grid Grid.Row="6" Margin="0,5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Threads" VerticalAlignment="Center" ToolTip="Controls the number of parallel threads used by ForEach-Object -Parallel and sets the value of the -ThrottleLimit parameter. Default is 5. Used in Winget, Application Copy, and driver downloads"/>
|
||||
<TextBox x:Name="txtThreads" Grid.Column="1" Margin="5" VerticalAlignment="Center" Width="50" HorizontalAlignment="Left" Text="5" ToolTip="Controls the number of parallel threads used by ForEach-Object -Parallel and sets the value of the -ThrottleLimit parameter. Default is 5. Used in Winget, Application Copy, and driver downloads"/>
|
||||
</Grid>
|
||||
<!-- Row 7: General Build Options Header -->
|
||||
<TextBlock Grid.Row="7" Text="General Build Options" FontWeight="Bold" FontSize="16" Margin="0,10,0,5"/>
|
||||
|
||||
<!-- Row 7: General Build Options Checkboxes -->
|
||||
<WrapPanel Grid.Row="7" Margin="0,5">
|
||||
<!-- Row 8: General Build Options Checkboxes -->
|
||||
<WrapPanel Grid.Row="8" Margin="0,5">
|
||||
<CheckBox x:Name="chkBuildUSBDriveEnable" Content="Build USB Drive" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will partition and format a USB drive and copy the captured FFU to the drive."/>
|
||||
<CheckBox x:Name="chkCompactOS" Content="Compact OS" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will compact the OS when building the FFU."/>
|
||||
<CheckBox x:Name="chkUpdateADK" Content="Update ADK" Margin="5" VerticalAlignment="Center" Tag="When set to $true, the script will check for and install/update to the latest Windows ADK and WinPE add-on."/>
|
||||
@@ -743,8 +754,8 @@
|
||||
<CheckBox x:Name="chkVerbose" Content="Verbose" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will enable write-verbose output to the console for the build script."/>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- Row 8: Build USB Drive Section -->
|
||||
<StackPanel Grid.Row="8" Margin="0,10,0,5" x:Name="usbDriveSection" Visibility="Collapsed">
|
||||
<!-- Row 9: Build USB Drive Section -->
|
||||
<StackPanel Grid.Row="9" Margin="0,10,0,5" x:Name="usbDriveSection" Visibility="Collapsed">
|
||||
<TextBlock Text="Build USB Drive Settings" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
|
||||
<StackPanel Margin="5,0,0,10">
|
||||
<CheckBox x:Name="chkAllowExternalHardDiskMedia" Content="Allow External Hard Disk Media" Margin="5" VerticalAlignment="Center" Tag="When set to $true, will allow the use of external hard disk media."/>
|
||||
@@ -780,8 +791,8 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Row 9: Post-Build Cleanup -->
|
||||
<StackPanel Grid.Row="9" Margin="0,10,0,5">
|
||||
<!-- Row 10: Post-Build Cleanup -->
|
||||
<StackPanel Grid.Row="10" Margin="0,10,0,5">
|
||||
<TextBlock Text="Post-Build Cleanup" FontWeight="Bold" FontSize="16" Margin="0,0,0,5"/>
|
||||
<CheckBox x:Name="chkCleanupAppsISO" Content="Cleanup Apps ISO" Margin="5" VerticalAlignment="Center" Tag="Remove Apps ISO after FFU capture."/>
|
||||
<CheckBox x:Name="chkCleanupCaptureISO" Content="Cleanup Capture ISO" Margin="5" VerticalAlignment="Center" Tag="Remove WinPE capture ISO after FFU capture."/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -112,6 +112,7 @@ function Get-GeneralDefaults {
|
||||
FFUCaptureLocation = $ffuCapturePath
|
||||
ShareName = "FFUCaptureShare"
|
||||
Username = "ffu_user"
|
||||
Threads = 5
|
||||
BuildUSBDriveEnable = $false
|
||||
CompactOS = $true
|
||||
Optimize = $true
|
||||
|
||||
Reference in New Issue
Block a user