Add BITS transfer priority configuration support

Introduces a new parameter to control BITS download priority across the build system and UI, allowing users to optimize transfer speeds when needed.

The feature adds a priority selector to the UI with four options (Foreground, High, Normal, Low) and propagates the selection through the build script and common modules. Priority can be set via UI, command-line parameter, or environment variable, with Normal as the default.

Updates the BITS transfer retry logic to respect the configured priority instead of hardcoding Normal priority, and fixes minor code formatting inconsistencies.
This commit is contained in:
rbalsleyMSFT
2025-11-21 14:04:52 -08:00
parent e67590d0a1
commit 8229aa73fe
9 changed files with 121 additions and 23 deletions
+3
View File
@@ -354,6 +354,8 @@ param(
[bool]$BuildUSBDrive, [bool]$BuildUSBDrive,
[hashtable]$USBDriveList, [hashtable]$USBDriveList,
[int]$MaxUSBDrives = 5, [int]$MaxUSBDrives = 5,
[ValidateSet('Foreground', 'High', 'Normal', 'Low')]
[string]$BitsPriority = 'Normal',
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[ValidateSet(10, 11, 2016, 2019, 2021, 2022, 2024, 2025)] [ValidateSet(10, 11, 2016, 2019, 2021, 2022, 2024, 2025)]
[int]$WindowsRelease = 11, [int]$WindowsRelease = 11,
@@ -659,6 +661,7 @@ if ($WindowsSKU -like "*LTS*") {
# Set the log path for the common logger # Set the log path for the common logger
Set-CommonCoreLogPath -Path $LogFile Set-CommonCoreLogPath -Path $LogFile
Set-BitsTransferPriority -Priority $BitsPriority
#FUNCTIONS #FUNCTIONS
+29 -13
View File
@@ -641,7 +641,7 @@
<TabItem Header="Build" Padding="20"> <TabItem Header="Build" Padding="20">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Margin="10"> <Grid Margin="10">
<!-- Define 10 rows for the Build tab --> <!-- Define 12 rows for the Build tab -->
<Grid.RowDefinitions> <Grid.RowDefinitions>
<!-- Row 0: Header --> <!-- Row 0: Header -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@@ -657,13 +657,15 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 6: Threads --> <!-- Row 6: Threads -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 7: General Build Options Header --> <!-- Row 7: BITS Priority -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 8: General Build Options Checkboxes --> <!-- Row 8: General Build Options Header -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 9: Build USB Drive Section --> <!-- Row 9: General Build Options Checkboxes -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 10: Post-Build Cleanup --> <!-- Row 10: Build USB Drive Section -->
<RowDefinition Height="Auto"/>
<!-- Row 11: Post-Build Cleanup -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -729,11 +731,25 @@
<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"/> <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"/> <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> </Grid>
<!-- Row 7: General Build Options Header --> <!-- Row 7: BITS Priority -->
<TextBlock Grid.Row="7" Text="General Build Options" FontWeight="Bold" FontSize="16" Margin="0,10,0,5"/> <Grid Grid.Row="7" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="BITS Priority" VerticalAlignment="Center" ToolTip="Controls the BITS download priority used by the UI and BuildFFUVM.ps1. Switch to Foreground to maximize download speed if needed."/>
<ComboBox x:Name="cmbBitsPriority" Grid.Column="1" Margin="5" VerticalAlignment="Center" Width="150" HorizontalAlignment="Left" ToolTip="Controls the BITS download priority used by the UI and BuildFFUVM.ps1. Switch to Foreground to maximize download speed if needed.">
<sys:String>Foreground</sys:String>
<sys:String>High</sys:String>
<sys:String>Normal</sys:String>
<sys:String>Low</sys:String>
</ComboBox>
</Grid>
<!-- Row 8: General Build Options Header -->
<TextBlock Grid.Row="8" Text="General Build Options" FontWeight="Bold" FontSize="16" Margin="0,10,0,5"/>
<!-- Row 8: General Build Options Checkboxes --> <!-- Row 9: General Build Options Checkboxes -->
<WrapPanel Grid.Row="8" Margin="0,5"> <WrapPanel Grid.Row="9" 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="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="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."/> <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."/>
@@ -745,8 +761,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."/> <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> </WrapPanel>
<!-- Row 9: Build USB Drive Section --> <!-- Row 10: Build USB Drive Section -->
<StackPanel Grid.Row="9" Margin="0,10,0,5" x:Name="usbDriveSection" Visibility="Collapsed"> <StackPanel Grid.Row="10" 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"/> <TextBlock Text="Build USB Drive Settings" FontWeight="Bold" FontSize="16" Margin="0,0,0,10"/>
<StackPanel Margin="5,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."/> <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."/>
@@ -811,8 +827,8 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<!-- Row 10: Post-Build Cleanup --> <!-- Row 11: Post-Build Cleanup -->
<StackPanel Grid.Row="10" Margin="0,10,0,5"> <StackPanel Grid.Row="11" Margin="0,10,0,5">
<TextBlock Text="Post-Build Cleanup" FontWeight="Bold" FontSize="16" Margin="0,0,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="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."/> <CheckBox x:Name="chkCleanupCaptureISO" Content="Cleanup Capture ISO" Margin="5" VerticalAlignment="Center" Tag="Remove WinPE capture ISO after FFU capture."/>
+38 -3
View File
@@ -12,6 +12,10 @@ $script:CommonCoreLogFilePath = $null
# Mutex for log file access # Mutex for log file access
$script:commonCoreLogMutexName = "Global\FFUCommonCoreLogMutex" # Unique name $script:commonCoreLogMutexName = "Global\FFUCommonCoreLogMutex" # Unique name
$script:commonCoreLogMutex = New-Object System.Threading.Mutex($false, $script:commonCoreLogMutexName) $script:commonCoreLogMutex = New-Object System.Threading.Mutex($false, $script:commonCoreLogMutexName)
$script:BitsTransferPriority = 'Normal'
if (-not [string]::IsNullOrWhiteSpace($env:FFU_BITS_PRIORITY)) {
$script:BitsTransferPriority = $env:FFU_BITS_PRIORITY
}
# Function to set the log file path for this module # Function to set the log file path for this module
function Set-CommonCoreLogPath { function Set-CommonCoreLogPath {
@@ -31,6 +35,23 @@ function Set-CommonCoreLogPath {
} }
} }
function Set-BitsTransferPriority {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateSet('Foreground', 'High', 'Normal', 'Low')]
[string]$Priority
)
$script:BitsTransferPriority = $Priority
try {
Set-Item -Path Env:FFU_BITS_PRIORITY -Value $Priority -ErrorAction Stop
}
catch {
WriteLog "Failed to set FFU_BITS_PRIORITY environment variable: $($_.Exception.Message)"
}
WriteLog "BITS transfer priority set to $Priority."
}
# Centralized WriteLog function # Centralized WriteLog function
function WriteLog { function WriteLog {
[CmdletBinding()] [CmdletBinding()]
@@ -143,9 +164,23 @@ function Start-BitsTransferWithRetry {
[string]$Source, [string]$Source,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$Destination, [string]$Destination,
[int]$Retries = 3 [int]$Retries = 3,
[ValidateSet('Foreground','High','Normal','Low')]
[string]$Priority
) )
if ([string]::IsNullOrWhiteSpace($Priority)) {
if (-not [string]::IsNullOrWhiteSpace($env:FFU_BITS_PRIORITY)) {
$Priority = $env:FFU_BITS_PRIORITY
}
elseif (-not [string]::IsNullOrWhiteSpace($script:BitsTransferPriority)) {
$Priority = $script:BitsTransferPriority
}
else {
$Priority = 'Normal'
}
}
$attempt = 0 $attempt = 0
$lastError = $null $lastError = $null
$notLoggedOnHResult = [int]0x800704dd $notLoggedOnHResult = [int]0x800704dd
@@ -158,7 +193,7 @@ function Start-BitsTransferWithRetry {
$VerbosePreference = 'SilentlyContinue' $VerbosePreference = 'SilentlyContinue'
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
Start-BitsTransfer -Source $Source -Destination $Destination -Priority Normal -ErrorAction Stop Start-BitsTransfer -Source $Source -Destination $Destination -Priority $Priority -ErrorAction Stop
$ProgressPreference = $OriginalProgressPreference $ProgressPreference = $OriginalProgressPreference
$VerbosePreference = $OriginalVerbosePreference $VerbosePreference = $OriginalVerbosePreference
@@ -259,7 +294,7 @@ function ConvertTo-SafeName {
# Collapse multiple consecutive dashes # Collapse multiple consecutive dashes
$sanitized = $sanitized -replace '-{2,}', '-' $sanitized = $sanitized -replace '-{2,}', '-'
# Trim leading/trailing spaces, periods, and dashes # Trim leading/trailing spaces, periods, and dashes
$sanitized = $sanitized.Trim(' ','.','-') $sanitized = $sanitized.Trim(' ', '.', '-')
if ([string]::IsNullOrWhiteSpace($sanitized)) { if ([string]::IsNullOrWhiteSpace($sanitized)) {
$sanitized = 'Unnamed' $sanitized = 'Unnamed'
} }
@@ -96,6 +96,7 @@ function Get-UIConfig {
USBDriveList = @{} USBDriveList = @{}
Username = $State.Controls.txtUsername.Text Username = $State.Controls.txtUsername.Text
Threads = [int]$State.Controls.txtThreads.Text Threads = [int]$State.Controls.txtThreads.Text
BitsPriority = $State.Controls.cmbBitsPriority.SelectedItem
MaxUSBDrives = [int]$State.Controls.txtMaxUSBDrives.Text MaxUSBDrives = [int]$State.Controls.txtMaxUSBDrives.Text
Verbose = $State.Controls.chkVerbose.IsChecked Verbose = $State.Controls.chkVerbose.IsChecked
VMHostIPAddress = $State.Controls.txtVMHostIPAddress.Text VMHostIPAddress = $State.Controls.txtVMHostIPAddress.Text
@@ -412,6 +413,7 @@ function Update-UIFromConfig {
Set-UIValue -ControlName 'txtShareName' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'ShareName' -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 'txtUsername' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Username' -State $State
Set-UIValue -ControlName 'txtThreads' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Threads' -State $State Set-UIValue -ControlName 'txtThreads' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Threads' -State $State
Set-UIValue -ControlName 'cmbBitsPriority' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'BitsPriority' -State $State
Set-UIValue -ControlName 'txtMaxUSBDrives' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'MaxUSBDrives' -State $State Set-UIValue -ControlName 'txtMaxUSBDrives' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'MaxUSBDrives' -State $State
Set-UIValue -ControlName 'chkBuildUSBDriveEnable' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'BuildUSBDrive' -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 'chkCompactOS' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CompactOS' -State $State
@@ -748,13 +750,14 @@ function Update-UIFromConfig {
else { else {
$State.Controls.additionalFFUPanel.Visibility = 'Collapsed' $State.Controls.additionalFFUPanel.Visibility = 'Collapsed'
} }
} }
catch { catch {
WriteLog "LoadConfig: Error applying Additional FFU selections: $($_.Exception.Message)" WriteLog "LoadConfig: Error applying Additional FFU selections: $($_.Exception.Message)"
} }
WriteLog "LoadConfig: Configuration loading process finished." Update-BitsPrioritySetting -State $State
} WriteLog "LoadConfig: Configuration loading process finished."
}
function Invoke-SaveConfiguration { function Invoke-SaveConfiguration {
param( param(
@@ -152,6 +152,17 @@ function Register-EventHandlers {
$localState.Controls.chkPromptExternalHardDiskMedia.IsChecked = $false $localState.Controls.chkPromptExternalHardDiskMedia.IsChecked = $false
}) })
if ($null -ne $State.Controls.cmbBitsPriority) {
$State.Controls.cmbBitsPriority.Add_SelectionChanged({
param($eventSource, $selectionChangedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
if ($null -eq $window -or $null -eq $window.Tag) {
return
}
Update-BitsPrioritySetting -State $window.Tag
})
}
# Additional FFU Files events # Additional FFU Files events
$State.Controls.chkCopyAdditionalFFUFiles.Add_Checked({ $State.Controls.chkCopyAdditionalFFUFiles.Add_Checked({
param($eventSource, $routedEventArgs) param($eventSource, $routedEventArgs)
@@ -118,6 +118,7 @@ function Initialize-UIControls {
$State.Controls.txtShareName = $window.FindName('txtShareName') $State.Controls.txtShareName = $window.FindName('txtShareName')
$State.Controls.txtUsername = $window.FindName('txtUsername') $State.Controls.txtUsername = $window.FindName('txtUsername')
$State.Controls.txtThreads = $window.FindName('txtThreads') $State.Controls.txtThreads = $window.FindName('txtThreads')
$State.Controls.cmbBitsPriority = $window.FindName('cmbBitsPriority')
$State.Controls.txtMaxUSBDrives = $window.FindName('txtMaxUSBDrives') $State.Controls.txtMaxUSBDrives = $window.FindName('txtMaxUSBDrives')
$State.Controls.chkCompactOS = $window.FindName('chkCompactOS') $State.Controls.chkCompactOS = $window.FindName('chkCompactOS')
$State.Controls.chkOptimize = $window.FindName('chkOptimize') $State.Controls.chkOptimize = $window.FindName('chkOptimize')
@@ -234,6 +235,7 @@ function Initialize-UIDefaults {
$State.Controls.txtShareName.Text = $State.Defaults.generalDefaults.ShareName $State.Controls.txtShareName.Text = $State.Defaults.generalDefaults.ShareName
$State.Controls.txtUsername.Text = $State.Defaults.generalDefaults.Username $State.Controls.txtUsername.Text = $State.Defaults.generalDefaults.Username
$State.Controls.txtThreads.Text = $State.Defaults.generalDefaults.Threads $State.Controls.txtThreads.Text = $State.Defaults.generalDefaults.Threads
$State.Controls.cmbBitsPriority.SelectedItem = $State.Defaults.generalDefaults.BitsPriority
$State.Controls.txtMaxUSBDrives.Text = $State.Defaults.generalDefaults.MaxUSBDrives $State.Controls.txtMaxUSBDrives.Text = $State.Defaults.generalDefaults.MaxUSBDrives
$State.Controls.chkBuildUSBDriveEnable.IsChecked = $State.Defaults.generalDefaults.BuildUSBDriveEnable $State.Controls.chkBuildUSBDriveEnable.IsChecked = $State.Defaults.generalDefaults.BuildUSBDriveEnable
$State.Controls.chkCompactOS.IsChecked = $State.Defaults.generalDefaults.CompactOS $State.Controls.chkCompactOS.IsChecked = $State.Defaults.generalDefaults.CompactOS
@@ -263,6 +265,7 @@ function Initialize-UIDefaults {
$State.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $State.Controls.chkAllowExternalHardDiskMedia.IsChecked $State.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $State.Controls.chkAllowExternalHardDiskMedia.IsChecked
$State.Controls.chkCopyAdditionalFFUFiles.IsChecked = $State.Defaults.generalDefaults.CopyAdditionalFFUFiles $State.Controls.chkCopyAdditionalFFUFiles.IsChecked = $State.Defaults.generalDefaults.CopyAdditionalFFUFiles
$State.Controls.additionalFFUPanel.Visibility = if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) { 'Visible' } else { 'Collapsed' } $State.Controls.additionalFFUPanel.Visibility = if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) { 'Visible' } else { 'Collapsed' }
Update-BitsPrioritySetting -State $State
# Hyper-V Settings defaults from General Defaults # Hyper-V Settings defaults from General Defaults
Initialize-VMSwitchData -State $State Initialize-VMSwitchData -State $State
@@ -220,6 +220,32 @@ function Invoke-ProgressUpdate {
$ProgressQueue.Enqueue(@{ Identifier = $Identifier; Status = $Status }) $ProgressQueue.Enqueue(@{ Identifier = $Identifier; Status = $Status })
} }
function Update-BitsPrioritySetting {
param(
[Parameter(Mandatory)]
[pscustomobject]$State
)
$combo = $State.Controls.cmbBitsPriority
if ($null -eq $combo) {
WriteLog "BITS priority control not available; skipping priority update."
return
}
$selectedPriority = $combo.SelectedItem
if ([string]::IsNullOrWhiteSpace($selectedPriority)) {
$selectedPriority = 'Normal'
}
try {
Set-BitsTransferPriority -Priority $selectedPriority
WriteLog "BITS transfer priority set to $selectedPriority."
}
catch {
WriteLog "Failed to set BITS transfer priority: $($_.Exception.Message)"
}
}
# Add a function to create a sortable list view # Add a function to create a sortable list view
function Add-SortableColumn { function Add-SortableColumn {
param( param(
@@ -117,6 +117,7 @@ function Get-GeneralDefaults {
ShareName = "FFUCaptureShare" ShareName = "FFUCaptureShare"
Username = "ffu_user" Username = "ffu_user"
Threads = 5 Threads = 5
BitsPriority = 'Normal'
MaxUSBDrives = 5 MaxUSBDrives = 5
BuildUSBDriveEnable = $false BuildUSBDriveEnable = $false
CompactOS = $true CompactOS = $true
Binary file not shown.