mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Adds UI/CLI to copy additional FFUs to USB build
- Enables selecting multiple existing FFU images to include on the deployment USB for easier distribution and testing. - Adds a UI option with selectable, sortable list from the capture folder, refresh support, and persisted selections. - Validates that selections exist when the option is enabled to prevent empty runs. - Supports unattended/CLI flows by prompting early or accepting a preselected list for USB creation; deduplicates and logs chosen files. - Always includes the just-built (or latest available) FFU as a base. - Improves no-FFU handling and streamlines multi-FFU selection workflow.
This commit is contained in:
@@ -37,6 +37,7 @@ function Get-UIConfig {
|
||||
UseDriversAsPEDrivers = $State.Controls.chkUseDriversAsPEDrivers.IsChecked
|
||||
CopyPPKG = $State.Controls.chkCopyPPKG.IsChecked
|
||||
CopyUnattend = $State.Controls.chkCopyUnattend.IsChecked
|
||||
CopyAdditionalFFUFiles = $State.Controls.chkCopyAdditionalFFUFiles.IsChecked
|
||||
CreateCaptureMedia = $State.Controls.chkCreateCaptureMedia.IsChecked
|
||||
CreateDeploymentMedia = $State.Controls.chkCreateDeploymentMedia.IsChecked
|
||||
InjectUnattend = $State.Controls.chkInjectUnattend.IsChecked
|
||||
@@ -115,6 +116,16 @@ function Get-UIConfig {
|
||||
$State.Controls.lstUSBDrives.Items | Where-Object { $_.IsSelected } | ForEach-Object {
|
||||
$config.USBDriveList[$_.Model] = $_.SerialNumber
|
||||
}
|
||||
|
||||
# Additional FFU file selections
|
||||
$config.AdditionalFFUFiles = @()
|
||||
if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) {
|
||||
$config.AdditionalFFUFiles = @(
|
||||
$State.Controls.lstAdditionalFFUs.Items |
|
||||
Where-Object { $_.IsSelected } |
|
||||
ForEach-Object { $_.FullName }
|
||||
)
|
||||
}
|
||||
|
||||
return $config
|
||||
}
|
||||
@@ -360,6 +371,7 @@ function Update-UIFromConfig {
|
||||
Set-UIValue -ControlName 'chkAllowVHDXCaching' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'AllowVHDXCaching' -State $State
|
||||
Set-UIValue -ControlName 'chkAllowExternalHardDiskMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'AllowExternalHardDiskMedia' -State $State
|
||||
Set-UIValue -ControlName 'chkPromptExternalHardDiskMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'PromptExternalHardDiskMedia' -State $State
|
||||
Set-UIValue -ControlName 'chkCopyAdditionalFFUFiles' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAdditionalFFUFiles' -State $State
|
||||
Set-UIValue -ControlName 'chkCreateCaptureMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CreateCaptureMedia' -State $State
|
||||
Set-UIValue -ControlName 'chkCreateDeploymentMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CreateDeploymentMedia' -State $State
|
||||
Set-UIValue -ControlName 'chkInjectUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'InjectUnattend' -State $State
|
||||
@@ -369,7 +381,7 @@ function Update-UIFromConfig {
|
||||
Set-UIValue -ControlName 'chkCopyAutopilot' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAutopilot' -State $State
|
||||
Set-UIValue -ControlName 'chkCopyUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyUnattend' -State $State
|
||||
Set-UIValue -ControlName 'chkCopyPPKG' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyPPKG' -State $State
|
||||
|
||||
|
||||
# Post Build Cleanup group (Build Tab)
|
||||
Set-UIValue -ControlName 'chkCleanupAppsISO' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CleanupAppsISO' -State $State
|
||||
Set-UIValue -ControlName 'chkCleanupCaptureISO' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CleanupCaptureISO' -State $State
|
||||
@@ -654,8 +666,46 @@ function Update-UIFromConfig {
|
||||
else {
|
||||
WriteLog "LoadConfig: Condition to auto-check 'Select Specific USB Drives' was NOT met."
|
||||
}
|
||||
WriteLog "LoadConfig: Configuration loading process finished."
|
||||
}
|
||||
# Populate additional FFU list and apply selections
|
||||
try {
|
||||
if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) {
|
||||
$State.Controls.additionalFFUPanel.Visibility = 'Visible'
|
||||
if ($State.Controls.btnRefreshAdditionalFFUs) {
|
||||
$State.Controls.btnRefreshAdditionalFFUs.RaiseEvent([System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent))
|
||||
}
|
||||
$selectedFiles = @()
|
||||
$addFFUKeyExists = $false
|
||||
if ($ConfigContent -is [System.Management.Automation.PSCustomObject] -and $null -ne $ConfigContent.PSObject.Properties) {
|
||||
if (($ConfigContent.PSObject.Properties.Match('AdditionalFFUFiles')).Count -gt 0) {
|
||||
$addFFUKeyExists = $true
|
||||
}
|
||||
}
|
||||
if ($addFFUKeyExists -and $null -ne $ConfigContent.AdditionalFFUFiles) {
|
||||
$selectedFiles = @($ConfigContent.AdditionalFFUFiles)
|
||||
}
|
||||
if ($selectedFiles.Count -gt 0) {
|
||||
foreach ($item in $State.Controls.lstAdditionalFFUs.Items) {
|
||||
if ($selectedFiles -contains $item.FullName) {
|
||||
$item.IsSelected = $true
|
||||
}
|
||||
}
|
||||
$State.Controls.lstAdditionalFFUs.Items.Refresh()
|
||||
$headerChk = $State.Controls.chkSelectAllAdditionalFFUs
|
||||
if ($null -ne $headerChk) {
|
||||
Update-SelectAllHeaderCheckBoxState -ListView $State.Controls.lstAdditionalFFUs -HeaderCheckBox $headerChk
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$State.Controls.additionalFFUPanel.Visibility = 'Collapsed'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
WriteLog "LoadConfig: Error applying Additional FFU selections: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
WriteLog "LoadConfig: Configuration loading process finished."
|
||||
}
|
||||
|
||||
function Invoke-SaveConfiguration {
|
||||
param(
|
||||
|
||||
@@ -152,6 +152,50 @@ function Register-EventHandlers {
|
||||
$localState.Controls.chkPromptExternalHardDiskMedia.IsChecked = $false
|
||||
})
|
||||
|
||||
# Additional FFU Files events
|
||||
$State.Controls.chkCopyAdditionalFFUFiles.Add_Checked({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
$localState.Controls.additionalFFUPanel.Visibility = 'Visible'
|
||||
Update-AdditionalFFUList -State $localState
|
||||
})
|
||||
$State.Controls.chkCopyAdditionalFFUFiles.Add_Unchecked({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
$localState.Controls.additionalFFUPanel.Visibility = 'Collapsed'
|
||||
$localState.Controls.lstAdditionalFFUs.Items.Clear()
|
||||
$headerChk = $localState.Controls.chkSelectAllAdditionalFFUs
|
||||
if ($null -ne $headerChk) {
|
||||
Update-SelectAllHeaderCheckBoxState -ListView $localState.Controls.lstAdditionalFFUs -HeaderCheckBox $headerChk
|
||||
}
|
||||
})
|
||||
$State.Controls.btnRefreshAdditionalFFUs.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Update-AdditionalFFUList -State $localState
|
||||
})
|
||||
$State.Controls.lstAdditionalFFUs.Add_PreviewKeyDown({
|
||||
param($eventSource, $keyEvent)
|
||||
if ($keyEvent.Key -eq 'Space') {
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Invoke-ListViewItemToggle -ListView $eventSource -State $localState -HeaderCheckBoxKeyName 'chkSelectAllAdditionalFFUs'
|
||||
$keyEvent.Handled = $true
|
||||
}
|
||||
})
|
||||
$State.Controls.lstAdditionalFFUs.Add_SelectionChanged({
|
||||
param($eventSource, $selChangeEvent)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
$headerChk = $localState.Controls.chkSelectAllAdditionalFFUs
|
||||
if ($null -ne $headerChk) {
|
||||
Update-SelectAllHeaderCheckBoxState -ListView $localState.Controls.lstAdditionalFFUs -HeaderCheckBox $headerChk
|
||||
}
|
||||
})
|
||||
|
||||
$State.Controls.btnCheckUSBDrives.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
|
||||
@@ -59,6 +59,10 @@ function Initialize-UIControls {
|
||||
$State.Controls.usbSelectionPanel = $window.FindName('usbDriveSelectionPanel')
|
||||
$State.Controls.chkAllowExternalHardDiskMedia = $window.FindName('chkAllowExternalHardDiskMedia')
|
||||
$State.Controls.chkPromptExternalHardDiskMedia = $window.FindName('chkPromptExternalHardDiskMedia')
|
||||
$State.Controls.chkCopyAdditionalFFUFiles = $window.FindName('chkCopyAdditionalFFUFiles')
|
||||
$State.Controls.additionalFFUPanel = $window.FindName('additionalFFUPanel')
|
||||
$State.Controls.lstAdditionalFFUs = $window.FindName('lstAdditionalFFUs')
|
||||
$State.Controls.btnRefreshAdditionalFFUs = $window.FindName('btnRefreshAdditionalFFUs')
|
||||
$State.Controls.chkInstallWingetApps = $window.FindName('chkInstallWingetApps')
|
||||
$State.Controls.wingetPanel = $window.FindName('wingetPanel')
|
||||
$State.Controls.btnCheckWingetModule = $window.FindName('btnCheckWingetModule')
|
||||
@@ -257,6 +261,8 @@ function Initialize-UIDefaults {
|
||||
$State.Controls.usbSelectionPanel.Visibility = if ($State.Controls.chkSelectSpecificUSBDrives.IsChecked) { 'Visible' } else { 'Collapsed' }
|
||||
$State.Controls.chkSelectSpecificUSBDrives.IsEnabled = $State.Controls.chkBuildUSBDriveEnable.IsChecked
|
||||
$State.Controls.chkPromptExternalHardDiskMedia.IsEnabled = $State.Controls.chkAllowExternalHardDiskMedia.IsChecked
|
||||
$State.Controls.chkCopyAdditionalFFUFiles.IsChecked = $State.Defaults.generalDefaults.CopyAdditionalFFUFiles
|
||||
$State.Controls.additionalFFUPanel.Visibility = if ($State.Controls.chkCopyAdditionalFFUFiles.IsChecked) { 'Visible' } else { 'Collapsed' }
|
||||
|
||||
# Hyper-V Settings defaults from General Defaults
|
||||
Initialize-VMSwitchData -State $State
|
||||
@@ -454,7 +460,7 @@ function Initialize-DynamicUIElements {
|
||||
|
||||
$exitHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock])
|
||||
$exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Additional Exit Codes")
|
||||
$exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5,2,5,2)))
|
||||
$exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5, 2, 5, 2)))
|
||||
$exitHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
||||
|
||||
$exitHeaderTemplate = New-Object System.Windows.DataTemplate
|
||||
@@ -482,7 +488,7 @@ function Initialize-DynamicUIElements {
|
||||
|
||||
$ignoreHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock])
|
||||
$ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Ignore Exit Codes")
|
||||
$ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5,2,5,2)))
|
||||
$ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5, 2, 5, 2)))
|
||||
$ignoreHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
||||
|
||||
$ignoreHeaderTemplate = New-Object System.Windows.DataTemplate
|
||||
@@ -682,6 +688,51 @@ function Initialize-DynamicUIElements {
|
||||
else {
|
||||
WriteLog "Warning: lstUSBDrives.View is not a GridView. Selectable column not added, and sorting cannot be enabled."
|
||||
}
|
||||
|
||||
# Additional FFUs ListView setup
|
||||
$itemStyleAdditionalFFUs = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem])
|
||||
$itemStyleAdditionalFFUs.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)))
|
||||
$State.Controls.lstAdditionalFFUs.ItemContainerStyle = $itemStyleAdditionalFFUs
|
||||
|
||||
if ($State.Controls.lstAdditionalFFUs.View -is [System.Windows.Controls.GridView]) {
|
||||
Add-SelectableGridViewColumn -ListView $State.Controls.lstAdditionalFFUs -State $State -HeaderCheckBoxKeyName "chkSelectAllAdditionalFFUs" -ColumnWidth 70
|
||||
|
||||
$additionalFFUsGridView = $State.Controls.lstAdditionalFFUs.View
|
||||
|
||||
if ($additionalFFUsGridView.Columns.Count -gt 1) {
|
||||
$nameColumn = $additionalFFUsGridView.Columns[1]
|
||||
$nameHeader = New-Object System.Windows.Controls.GridViewColumnHeader
|
||||
$nameHeader.Content = "FFU Name"
|
||||
$nameHeader.Tag = "Name"
|
||||
$nameHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left
|
||||
$nameColumn.Header = $nameHeader
|
||||
}
|
||||
if ($additionalFFUsGridView.Columns.Count -gt 2) {
|
||||
$lastModColumn = $additionalFFUsGridView.Columns[2]
|
||||
$lastModHeader = New-Object System.Windows.Controls.GridViewColumnHeader
|
||||
$lastModHeader.Content = "Last Modified"
|
||||
$lastModHeader.Tag = "LastModified"
|
||||
$lastModHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left
|
||||
$lastModColumn.Header = $lastModHeader
|
||||
}
|
||||
|
||||
$State.Controls.lstAdditionalFFUs.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) {
|
||||
$listViewControl = $eventSource
|
||||
$window = [System.Windows.Window]::GetWindow($listViewControl)
|
||||
$uiStateFromWindowTag = $window.Tag
|
||||
Invoke-ListViewSort -listView $eventSource -property $header.Tag -State $uiStateFromWindowTag
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
WriteLog "Warning: lstAdditionalFFUs.View is not a GridView. Selectable column not added, and sorting cannot be enabled."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ function Get-GeneralDefaults {
|
||||
AllowExternalHardDiskMedia = $false
|
||||
PromptExternalHardDiskMedia = $true
|
||||
SelectSpecificUSBDrives = $false
|
||||
CopyAdditionalFFUFiles = $false
|
||||
CopyAutopilot = $false
|
||||
CopyUnattend = $false
|
||||
CopyPPKG = $false
|
||||
@@ -198,6 +199,65 @@ function Get-USBDrives {
|
||||
}
|
||||
}
|
||||
|
||||
# Returns a list of FFU files from the provided folder with selection metadata
|
||||
function Get-FFUFiles {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
if (-not (Test-Path -Path $Path)) {
|
||||
return @()
|
||||
}
|
||||
Get-ChildItem -Path $Path -Filter '*.ffu' -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
IsSelected = $false
|
||||
Name = $_.Name
|
||||
LastModified = $_.LastWriteTime
|
||||
FullName = $_.FullName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Helper: Populate Additional FFU List from the capture folder
|
||||
function Update-AdditionalFFUList {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PSCustomObject]$State
|
||||
)
|
||||
try {
|
||||
$ffuFolder = $State.Controls.txtFFUCaptureLocation.Text
|
||||
$listView = $State.Controls.lstAdditionalFFUs
|
||||
if ($null -eq $listView) { return }
|
||||
$listView.Items.Clear()
|
||||
if ([string]::IsNullOrWhiteSpace($ffuFolder) -or -not (Test-Path -Path $ffuFolder)) {
|
||||
WriteLog "Additional FFUs: Capture folder not set or not found: $ffuFolder"
|
||||
}
|
||||
else {
|
||||
$items = Get-ChildItem -Path $ffuFolder -Filter '*.ffu' -File -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
IsSelected = $false
|
||||
Name = $_.Name
|
||||
LastModified = $_.LastWriteTime
|
||||
FullName = $_.FullName
|
||||
}
|
||||
}
|
||||
foreach ($it in $items) { $listView.Items.Add($it) | Out-Null }
|
||||
WriteLog "Additional FFUs: Found $($listView.Items.Count) FFU files in $ffuFolder."
|
||||
}
|
||||
$headerChk = $State.Controls.chkSelectAllAdditionalFFUs
|
||||
if ($null -ne $headerChk) {
|
||||
Update-SelectAllHeaderCheckBoxState -ListView $listView -HeaderCheckBox $headerChk
|
||||
}
|
||||
}
|
||||
catch {
|
||||
WriteLog "Update-AdditionalFFUList error: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# Function to manage the visibility of the application UI panels
|
||||
function Update-ApplicationPanelVisibility {
|
||||
param(
|
||||
|
||||
Reference in New Issue
Block a user