Docs: Add PowerShell comment-based help to all script modules

Adds standard PowerShell comment-based help blocks (synopsis and description) to all UI and common library script modules (`.psm1`) and the main UI entry point script (`.ps1`).

This improves maintainability and discoverability by documenting the purpose of each script file. Also removes various redundant or commented-out code blocks.
This commit is contained in:
rbalsleyMSFT
2025-07-18 14:52:03 -07:00
parent 7cc7919da4
commit 6df7b16cdf
19 changed files with 234 additions and 146 deletions
+25 -10
View File
@@ -39,6 +39,9 @@ When set to $true, will remove the drivers folder after the FFU has been capture
.PARAMETER CompactOS .PARAMETER CompactOS
When set to $true, will compact the OS when building the FFU. Default is $true. When set to $true, will compact the OS when building the FFU. Default is $true.
.PARAMETER CompressDownloadedDriversToWim
When set to $true, compresses downloaded drivers into a WIM file. Default is $false.
.PARAMETER ConfigFile .PARAMETER ConfigFile
Path to a JSON file containing parameters to use for the script. Default is $null. Path to a JSON file containing parameters to use for the script. Default is $null.
@@ -72,6 +75,9 @@ Size of the virtual hard disk for the virtual machine. Default is a 30GB dynamic
.PARAMETER DriversFolder .PARAMETER DriversFolder
Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers. Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers.
.PARAMETER DriversJsonPath
Path to a JSON file that specifies which drivers to download.
.PARAMETER ExportConfigFile .PARAMETER ExportConfigFile
Path to a JSON file to export the parameters used for the script. Path to a JSON file to export the parameters used for the script.
@@ -114,12 +120,18 @@ Amount of memory to allocate for the virtual machine. Recommended to use 8GB if
.PARAMETER Model .PARAMETER Model
Model of the device to download drivers. This is required if Make is set. Model of the device to download drivers. This is required if Make is set.
.PARAMETER OfficeConfigXMLFile
Path to a custom Office configuration XML file to use for installation.
.PARAMETER Optimize .PARAMETER Optimize
When set to $true, will optimize the FFU file. Default is $true. When set to $true, will optimize the FFU file. Default is $true.
.PARAMETER OptionalFeatures .PARAMETER OptionalFeatures
Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP). Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP).
.PARAMETER orchestrationPath
Path to the orchestration folder containing scripts that run inside the VM. Default is $FFUDevelopmentPath\Apps\Orchestration.
.PARAMETER PEDriversFolder .PARAMETER PEDriversFolder
Path to the folder containing drivers to be injected into the WinPE deployment media. Default is $FFUDevelopmentPath\PEDrivers. Path to the folder containing drivers to be injected into the WinPE deployment media. Default is $FFUDevelopmentPath\PEDrivers.
@@ -132,12 +144,12 @@ Product key for the Windows edition specified in WindowsSKU. This will overwrite
.PARAMETER PromptExternalHardDiskMedia .PARAMETER PromptExternalHardDiskMedia
When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true. When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true.
.PARAMETER RemoveFFU
When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false.
.PARAMETER RemoveApps .PARAMETER RemoveApps
When set to $true, will remove the application content in the Apps folder after the FFU has been captured. Default is $true. When set to $true, will remove the application content in the Apps folder after the FFU has been captured. Default is $true.
.PARAMETER RemoveFFU
When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false.
.PARAMETER RemoveUpdates .PARAMETER RemoveUpdates
When set to $true, will remove the downloaded CU, MSRT, Defender, Edge, OneDrive, and .NET files downloaded. Default is $true. When set to $true, will remove the downloaded CU, MSRT, Defender, Edge, OneDrive, and .NET files downloaded. Default is $true.
@@ -148,10 +160,10 @@ Name of the shared folder for FFU capture. The default is FFUCaptureShare. This
When set to $true, the script will check for and install the latest Windows ADK and WinPE add-on if they are not already installed or up-to-date. Default is $true. When set to $true, the script will check for and install the latest Windows ADK and WinPE add-on if they are not already installed or up-to-date. Default is $true.
.PARAMETER UpdateEdge .PARAMETER UpdateEdge
When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false. When set to $true, will download and install the latest Microsoft Edge. Default is $false.
.PARAMETER UpdateLatestCU .PARAMETER UpdateLatestCU
When set to $true, will download and install the latest cumulative update for Windows 10/11. Default is $false. When set to $true, will download and install the latest cumulative update. Default is $false.
.PARAMETER UpdateLatestDefender .PARAMETER UpdateLatestDefender
When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false. When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false.
@@ -163,13 +175,16 @@ When set to $true, will download and install the latest microcode updates for ap
When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false. When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false.
.PARAMETER UpdateLatestNet .PARAMETER UpdateLatestNet
When set to $true, will download and install the latest .NET Framework for Windows 10/11. Default is $false. When set to $true, will download and install the latest .NET Framework. Default is $false.
.PARAMETER UpdateOneDrive .PARAMETER UpdateOneDrive
When set to $true, will download and install the latest OneDrive for Windows 10/11 and install it as a per-machine installation instead of per-user. Default is $false. When set to $true, will download and install the latest OneDrive and install it as a per-machine installation instead of per-user. Default is $false.
.PARAMETER UpdatePreviewCU .PARAMETER UpdatePreviewCU
When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false. When set to $true, will download and install the latest Preview cumulative update. Default is $false.
.PARAMETER UserAppListPath
Path to a JSON file containing a list of user-defined applications to install. Default is $FFUDevelopmentPath\Apps\UserAppList.json.
.PARAMETER USBDriveList .PARAMETER USBDriveList
A hashtable containing USB drives from win32_diskdrive where: A hashtable containing USB drives from win32_diskdrive where:
@@ -185,7 +200,7 @@ User agent string to use when downloading files.
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. 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.
.PARAMETER VMHostIPAddress .PARAMETER VMHostIPAddress
IP address of the Hyper-V host for FFU capture. If $InstallApps is set to $true, this parameter must be configured. You must manually configure this. The script will not auto-detect your IP (depending on your network adapters, it may not find the correct IP). IP address of the Hyper-V host for FFU capture. If $InstallApps is set to $true, this parameter must be configured. You must manually configure this, or use the UI to auto-detect.
.PARAMETER VMLocation .PARAMETER VMLocation
Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to. Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to.
@@ -405,7 +420,7 @@ param(
[bool]$UpdateADK = $true [bool]$UpdateADK = $true
) )
$ProgressPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue'
$version = '2505.1' $version = '2507.1'
# Remove any existing modules to avoid conflicts # Remove any existing modules to avoid conflicts
if (Get-Module -Name 'FFU.Common.Core' -ErrorAction SilentlyContinue) { if (Get-Module -Name 'FFU.Common.Core' -ErrorAction SilentlyContinue) {
+18
View File
@@ -1,3 +1,21 @@
<#
.SYNOPSIS
Launches the FFU Development UI, a WPF application for configuring and running the FFU build process.
.DESCRIPTION
The BuildFFUVM_UI.ps1 script is the main entry point for the FFU Development user interface. It initializes and displays a WPF-based graphical interface defined in BuildFFUVM_UI.xaml.
The script is responsible for:
- Initializing a global state object to manage UI controls, data, and application flags.
- Importing the required FFU.Common and FFUUI.Core modules which contain the business logic.
- Ensuring system prerequisites, such as PowerShell 7 and Long Path Support, are met.
- Loading the XAML window, initializing UI controls with default values, and registering all event handlers.
- Launching the core build script (BuildFFUVM.ps1) in a background job when the user initiates a build.
- Providing real-time feedback by monitoring the build log file and updating the UI's progress bar and log viewer.
- Handling cleanup operations, such as reverting system settings, when the application is closed.
This script acts as the primary host for the UI, connecting the user interface with the underlying build and logic modules.
#>
[CmdletBinding()] [CmdletBinding()]
[System.STAThread()] [System.STAThread()]
param() param()
+9 -75
View File
@@ -1,6 +1,12 @@
# FFU.Common.Core.psm1 <#
# Contains common core functions like logging and process invocation. .SYNOPSIS
Provides core, shared functions for logging, process execution, and resilient file transfers used across the FFU project.
.DESCRIPTION
This module is a central component of the FFU project, offering a set of robust, reusable functions.
It includes a centralized logging mechanism (WriteLog), a wrapper for running external processes with error handling (Invoke-Process),
a retry-aware BITS transfer function for reliable downloads (Start-BitsTransferWithRetry), and a progress reporting helper.
This module is designed to be imported by other scripts and modules within the project to ensure consistent behavior for common tasks.
#>
# Script-scoped variable for the log file path # Script-scoped variable for the log file path
$script:CommonCoreLogFilePath = $null $script:CommonCoreLogFilePath = $null
# Mutex for log file access # Mutex for log file access
@@ -66,75 +72,6 @@ function WriteLog {
} }
} }
# Function to invoke external process
# function Invoke-Process {
# [CmdletBinding(SupportsShouldProcess)]
# param(
# [Parameter(Mandatory)]
# [ValidateNotNullOrEmpty()]
# [string]$FilePath,
# [Parameter()]
# [ValidateNotNullOrEmpty()]
# [string[]]$ArgumentList,
# [Parameter()]
# [ValidateNotNullOrEmpty()]
# [bool]$Wait = $true
# )
# $ErrorActionPreference = 'Stop' # Keep this local to the function
# try {
# $stdOutTempFile = "$env:TEMP\$((New-Guid).Guid)"
# $stdErrTempFile = "$env:TEMP\$((New-Guid).Guid)"
# $startProcessParams = @{
# FilePath = $FilePath
# ArgumentList = $ArgumentList
# RedirectStandardError = $stdErrTempFile
# RedirectStandardOutput = $stdOutTempFile
# Wait = $Wait
# PassThru = $true
# NoNewWindow = $true
# }
# # DEBUG
# # WriteLog "Running Command: $($startProcessParams.FilePath) $($startProcessParams.ArgumentList -join ' ')"
# if ($PSCmdlet.ShouldProcess("Process [$($FilePath)]", "Run with args: [$($ArgumentList)]")) {
# $cmd = Start-Process @startProcessParams
# $cmdOutput = Get-Content -Path $stdOutTempFile -Raw -ErrorAction SilentlyContinue
# $cmdError = Get-Content -Path $stdErrTempFile -Raw -ErrorAction SilentlyContinue
# if (-not [string]::IsNullOrWhiteSpace($cmdOutput)) {
# WriteLog "STDOUT from '$FilePath': $cmdOutput"
# }
# if (-not [string]::IsNullOrWhiteSpace($cmdError)) {
# WriteLog "STDERR from '$FilePath': $cmdError"
# }
# if ($cmd.ExitCode -ne 0 -and $Wait) {
# $errorMessage = "Process '$FilePath' exited with code $($cmd.ExitCode)."
# if (-not [string]::IsNullOrWhiteSpace($cmdError)) {
# $errorMessage += " Error: $cmdError"
# }
# elseif (-not [string]::IsNullOrWhiteSpace($cmdOutput)) {
# $errorMessage += " Output: $cmdOutput"
# }
# throw $errorMessage.Trim()
# }
# }
# }
# catch {
# WriteLog "Error in Invoke-Process for '$FilePath': $($_.Exception.Message)"
# throw
# }
# finally {
# if (Test-Path $stdOutTempFile) { Remove-Item -Path $stdOutTempFile -Force -ErrorAction Ignore }
# if (Test-Path $stdErrTempFile) { Remove-Item -Path $stdErrTempFile -Force -ErrorAction Ignore }
# }
# return $cmd
# }
function Invoke-Process { function Invoke-Process {
[CmdletBinding(SupportsShouldProcess)] [CmdletBinding(SupportsShouldProcess)]
param param
@@ -257,7 +194,4 @@ function Set-Progress {
WriteLog "[PROGRESS] $Percentage | $Message" WriteLog "[PROGRESS] $Percentage | $Message"
} }
# Suppress the default progress bar for a cleaner console output for any script importing this module.
# $ProgressPreference = 'SilentlyContinue'
Export-ModuleMember -Function * Export-ModuleMember -Function *
@@ -1,10 +1,13 @@
# FFU Common Drivers Module <#
# Contains shared functions related to driver handling. .SYNOPSIS
Provides common functions for driver management, including compression, mapping, and existence checks.
# --------------------------------------------------------------------------
# SECTION: Driver Compression Function
# --------------------------------------------------------------------------
.DESCRIPTION
The FFU.Common.Drivers module contains a set of shared functions used across the FFU project for handling driver packages.
This includes compressing driver folders into WIM files for efficient storage and deployment, maintaining a JSON-based mapping
of downloaded drivers to their respective makes and models, and checking for the pre-existence of driver packages to avoid
redundant downloads.
#>
function Compress-DriverFolderToWim { function Compress-DriverFolderToWim {
[CmdletBinding(SupportsShouldProcess)] [CmdletBinding(SupportsShouldProcess)]
param( param(
@@ -1,10 +1,57 @@
<#
.SYNOPSIS
Manages and executes multiple background tasks in parallel, with support for updating a WPF UI with progress.
.DESCRIPTION
This function provides a generic framework for running tasks in parallel using PowerShell's ForEach-Object -Parallel.
It is designed to process an array of items, executing a specific task for each one. It can operate in two modes: UI mode and non-UI mode.
In UI mode, it updates a specified ListView control in a WPF window with the status of each item as it's being processed
(e.g., Queued, Downloading, Completed, Error). It uses a dispatcher to ensure UI updates are thread-safe.
In non-UI mode, it runs the tasks and logs the status to the FFUDevelopment.log file.
The function determines the task to run via the -TaskType parameter and passes necessary arguments using -TaskArguments.
It handles module imports and log file setup within each parallel runspace to ensure tasks have the necessary dependencies and logging capabilities.
.PARAMETER ItemsToProcess
An array of objects, where each object represents an item to be processed by a parallel task. This is a mandatory parameter.
.PARAMETER ListViewControl
(UI Mode) The WPF ListView control that the function will update with the status of each item. Defaults to $null.
.PARAMETER IdentifierProperty
The name of the property on the item objects that serves as a unique identifier (e.g., 'Name', 'Id').
This is used to find and update the correct row in the ListView.
.PARAMETER StatusProperty
The name of the property on the item objects that holds the status string. This property will be updated with progress messages.
.PARAMETER TaskType
A string specifying which task to execute for each item. This is mandatory.
Valid values are:
- 'WingetDownload': Downloads a Winget application.
- 'CopyBYO': Copies a user-provided application.
- 'DownloadDriverByMake': Downloads drivers for a specific manufacturer.
.PARAMETER TaskArguments
A hashtable containing arguments required by the specific task being run (e.g., paths, API keys, configuration settings).
.PARAMETER CompletedStatusText
The status text to display when an item is processed successfully.
.PARAMETER ErrorStatusPrefix
A prefix for status messages when an error occurs.
.PARAMETER WindowObject
(UI Mode) The main WPF Window object, used to access the UI dispatcher for safe UI updates from background threads.
.PARAMETER MainThreadLogPath
The file path for the log file that should be used by all parallel threads. This ensures consistent logging.
.PARAMETER ThrottleLimit
The maximum number of parallel jobs to run concurrently. The default is 5.
.NOTES
This function relies on ForEach-Object -Parallel, which was introduced in PowerShell 7.
When running in UI mode, both -WindowObject and -ListViewControl must be provided.
The function dynamically imports required modules ('FFU.Common' and 'FFUUI.Core') into each parallel runspace.
It uses a concurrent queue to manage intermediate progress updates from threads to the main UI thread, preventing UI blocking and providing more granular feedback.
#>
function Invoke-ParallelProcessing { function Invoke-ParallelProcessing {
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[array]$ItemsToProcess, [array]$ItemsToProcess,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[object]$ListViewControl = $null, # Changed type to [object] [object]$ListViewControl = $null,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[string]$IdentifierProperty = 'Identifier', [string]$IdentifierProperty = 'Identifier',
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
@@ -19,9 +66,9 @@ function Invoke-ParallelProcessing {
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[string]$ErrorStatusPrefix = "Error: ", [string]$ErrorStatusPrefix = "Error: ",
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[object]$WindowObject = $null, # Changed type to [object] [object]$WindowObject = $null,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[string]$MainThreadLogPath = $null, # New parameter for the log path [string]$MainThreadLogPath = $null,
[Parameter(Mandatory = $false)] [Parameter(Mandatory = $false)]
[int]$ThrottleLimit = 5 [int]$ThrottleLimit = 5
) )
@@ -60,11 +107,9 @@ function Invoke-ParallelProcessing {
$jobScopeVariables = $TaskArguments.Clone() $jobScopeVariables = $TaskArguments.Clone()
$jobScopeVariables['_commonModulePath'] = $commonModulePathForJob $jobScopeVariables['_commonModulePath'] = $commonModulePathForJob
$jobScopeVariables['_uiCoreModulePath'] = $uiCoreModulePathForJob $jobScopeVariables['_uiCoreModulePath'] = $uiCoreModulePathForJob
$jobScopeVariables['_currentLogFilePathForJob'] = $currentLogFilePathForJob # Pass the determined log path $jobScopeVariables['_currentLogFilePathForJob'] = $currentLogFilePathForJob
$jobScopeVariables['_progressQueue'] = $progressQueue $jobScopeVariables['_progressQueue'] = $progressQueue
# The $TaskScriptBlock parameter is already a local variable in this scope
# Initial UI update needs to happen *before* starting the jobs # Initial UI update needs to happen *before* starting the jobs
# Update all items to a static "Processing..." status # Update all items to a static "Processing..." status
if ($isUiMode) { if ($isUiMode) {
@@ -89,13 +134,9 @@ function Invoke-ParallelProcessing {
# $jobScopeVariables and $TaskType are local here # $jobScopeVariables and $TaskType are local here
# Inside the -Parallel scriptblock, we access them with $using: # Inside the -Parallel scriptblock, we access them with $using:
$jobs = $ItemsToProcess | ForEach-Object -Parallel { $jobs = $ItemsToProcess | ForEach-Object -Parallel {
# Access the current item via pipeline variable $_
$currentItem = $_ $currentItem = $_
# Access the combined arguments hashtable from the calling scope using $using:
$localJobArgs = $using:jobScopeVariables $localJobArgs = $using:jobScopeVariables
# Access the task type string from the calling scope using $using:
$localTaskType = $using:TaskType $localTaskType = $using:TaskType
# Access the progress queue using $using:
$localProgressQueue = $localJobArgs['_progressQueue'] $localProgressQueue = $localJobArgs['_progressQueue']
# Initialize result hashtable # Initialize result hashtable
@@ -242,9 +283,7 @@ function Invoke-ParallelProcessing {
WriteLog $nullTaskResultMessage WriteLog $nullTaskResultMessage
$resultStatus = $nullTaskResultMessage $resultStatus = $nullTaskResultMessage
$resultCode = 1 $resultCode = 1
# $resultIdentifier is already set
} }
# If it was an unsupported Make, $resultStatus and $resultCode are already set from the 'default' case.
} }
Default { Default {
# This handles unknown $localTaskType values # This handles unknown $localTaskType values
@@ -261,7 +300,6 @@ function Invoke-ParallelProcessing {
} }
} }
catch { catch {
# Catch errors within the parallel task execution
$resultStatus = "Error: $($_.Exception.Message)" $resultStatus = "Error: $($_.Exception.Message)"
$resultCode = 1 $resultCode = 1
# Try to get an identifier # Try to get an identifier
@@ -272,7 +310,6 @@ function Invoke-ParallelProcessing {
$resultIdentifier = "UnknownItemOnError" $resultIdentifier = "UnknownItemOnError"
} }
WriteLog "Exception during parallel task '$localTaskType' for item '$resultIdentifier': $($_.Exception.ToString())" WriteLog "Exception during parallel task '$localTaskType' for item '$resultIdentifier': $($_.Exception.ToString())"
# Enqueue the error status from the catch block
$localProgressQueue.Enqueue(@{ Identifier = $resultIdentifier; Status = $resultStatus }) $localProgressQueue.Enqueue(@{ Identifier = $resultIdentifier; Status = $resultStatus })
} }
@@ -284,7 +321,7 @@ function Invoke-ParallelProcessing {
# Return a consistent hashtable structure (final result) # Return a consistent hashtable structure (final result)
return @{ return @{
Identifier = $resultIdentifier Identifier = $resultIdentifier
Status = $resultStatus # Return the final status Status = $resultStatus
ResultCode = $resultCode ResultCode = $resultCode
DriverPath = $driverPathValue DriverPath = $driverPathValue
} }
@@ -292,7 +329,6 @@ function Invoke-ParallelProcessing {
} -ThrottleLimit $ThrottleLimit -AsJob } -ThrottleLimit $ThrottleLimit -AsJob
} }
catch { catch {
# Catch errors during the *creation* of the parallel jobs (e.g., module loading in main thread failed)
WriteLog "Error initiating ForEach-Object -Parallel: $($_.Exception.Message)" WriteLog "Error initiating ForEach-Object -Parallel: $($_.Exception.Message)"
# Update all items to show a general startup error # Update all items to show a general startup error
$errorStatus = "$ErrorStatusPrefix Failed to start processing" $errorStatus = "$ErrorStatusPrefix Failed to start processing"
@@ -302,7 +338,6 @@ function Invoke-ParallelProcessing {
Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $identifier -StatusProperty $StatusProperty -StatusValue $errorStatus # Pass $WindowObject Update-ListViewItemStatus -WindowObject $WindowObject -ListView $ListViewControl -IdentifierProperty $IdentifierProperty -IdentifierValue $identifier -StatusProperty $StatusProperty -StatusValue $errorStatus # Pass $WindowObject
}) })
} }
# Exit the function as processing cannot proceed
return return
} }
@@ -334,7 +369,6 @@ function Invoke-ParallelProcessing {
} }
$intermediateStatus = $statusUpdate.Status $intermediateStatus = $statusUpdate.Status
if ($isUiMode) { if ($isUiMode) {
# Use the new $isUiMode flag
# Update the UI with the intermediate status # Update the UI with the intermediate status
try { try {
WriteLog "Dispatching INTERMEDIATE status for '$intermediateIdentifier': '$intermediateStatus'" WriteLog "Dispatching INTERMEDIATE status for '$intermediateIdentifier': '$intermediateStatus'"
@@ -361,7 +395,7 @@ function Invoke-ParallelProcessing {
$jobHandled = $false $jobHandled = $false
if ($completedJob.State -eq 'Failed') { if ($completedJob.State -eq 'Failed') {
$jobHandled = $true $jobHandled = $true
$finalIdentifier = "UnknownJob" # Placeholder $finalIdentifier = "UnknownJob"
WriteLog "Job $($completedJob.Id) failed: $($completedJob.Error)" WriteLog "Job $($completedJob.Id) failed: $($completedJob.Error)"
$finalStatus = "$ErrorStatusPrefix Job Failed" $finalStatus = "$ErrorStatusPrefix Job Failed"
$finalResultCode = 1 $finalResultCode = 1
@@ -438,12 +472,11 @@ function Invoke-ParallelProcessing {
# Remove the completed/failed job from the list and clean it up # Remove the completed/failed job from the list and clean it up
$jobs = $jobs | Where-Object { $_.Id -ne $completedJob.Id } $jobs = $jobs | Where-Object { $_.Id -ne $completedJob.Id }
Remove-Job -Job $completedJob -Force -ErrorAction SilentlyContinue Remove-Job -Job $completedJob -Force -ErrorAction SilentlyContinue
} # End foreach completedJob }
} # End if ($completedJobs) }
# 3. Allow UI events to process and sleep briefly # 3. Allow UI events to process and sleep briefly
if ($isUiMode) { if ($isUiMode) {
# Use the new $isUiMode flag
# Only sleep if jobs are still running AND the queue is empty (to avoid delaying UI updates) # Only sleep if jobs are still running AND the queue is empty (to avoid delaying UI updates)
if ($jobs.Count -gt 0 -and $progressQueue.IsEmpty) { if ($jobs.Count -gt 0 -and $progressQueue.IsEmpty) {
$WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action] { }) | Out-Null $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action] { }) | Out-Null
@@ -460,9 +493,8 @@ function Invoke-ParallelProcessing {
Start-Sleep -Milliseconds 100 Start-Sleep -Milliseconds 100
} }
} }
# If jobs are done AND queue is empty, the loop condition will terminate
} # End while ($jobs.Count -gt 0 -or -not $progressQueue.IsEmpty) }
# Final cleanup of any remaining jobs (shouldn't be necessary with this loop logic, but good practice) # Final cleanup of any remaining jobs (shouldn't be necessary with this loop logic, but good practice)
if ($jobs.Count -gt 0) { if ($jobs.Count -gt 0) {
@@ -471,7 +503,6 @@ function Invoke-ParallelProcessing {
} }
if ($isUiMode) { if ($isUiMode) {
# Use the new $isUiMode flag
WriteLog "Invoke-ParallelProcessing finished for ListView '$($ListViewControl.Name)'." WriteLog "Invoke-ParallelProcessing finished for ListView '$($ListViewControl.Name)'."
# Final overall progress update # Final overall progress update
$WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] { $WindowObject.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] {
@@ -1,6 +1,12 @@
# # Import the common core module for logging <#
# Import-Module "$PSScriptRoot\FFU.Common.Core.psm1" .SYNOPSIS
Provides functions for interacting with WinGet and the Microsoft Store to find, download, and configure applications.
.DESCRIPTION
This module contains a set of functions designed to automate application management using the WinGet package manager and the Microsoft Store.
It supports checking for and installing WinGet, downloading applications, handling different application types (Win32 and UWP), and generating silent installation commands for Win32 applications.
This module is used by both the build script (BuildFFUVM.ps1) and the UI (BuildFFUVM_UI.ps1) to manage application downloads and configuration.
#>
function Get-Application { function Get-Application {
[CmdletBinding()] [CmdletBinding()]
param ( param (
@@ -1,5 +1,9 @@
# FFU UI Core Applications Module <#
# Contains UI-layer logic for the "Bring Your Own Apps" and related features. .SYNOPSIS
Manages the UI business logic for the "Applications" tab, including "Bring Your Own (BYO) Applications" and "Apps Script Variables".
.DESCRIPTION
This module contains all the functions that power the "Applications" tab in the BuildFFUVM_UI. It handles user interactions for managing custom application lists (BYO Apps), such as adding, removing, reordering, and saving/loading the list from a JSON file (UserAppList.json). It also includes the logic for copying the application source files to the designated staging directory in parallel. Additionally, it manages the UI for creating and removing key-value pairs for the AppsScriptVariables.json file, which allows for custom parameterization of user-provided scripts.
#>
# Function to update the enabled state of the Copy Apps button # Function to update the enabled state of the Copy Apps button
function Update-CopyButtonState { function Update-CopyButtonState {
@@ -1,6 +1,9 @@
# FFU UI Core Configuration Module <#
# Contains functions for loading and saving UI configuration. .SYNOPSIS
Contains functions for loading and saving UI configuration.
.DESCRIPTION
This module provides the core logic for loading and saving the UI configuration. It includes functions to gather settings from the various UI controls, save them to a JSON file, and load settings from a JSON file to populate the UI. This allows users to persist their build configurations and easily switch between different setups.
#>
function Get-UIConfig { function Get-UIConfig {
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Provides functions for discovering, downloading, and processing Dell device drivers.
.DESCRIPTION
This module contains the logic specific to handling Dell drivers for the FFU Builder UI. It includes functions to parse Dell's large XML driver catalog to retrieve a list of supported models (Get-DellDriversModelList). It also provides a parallel-capable task function (Save-DellDriversTask) that finds, downloads, extracts, and optionally compresses all the latest driver packages for a specified Dell model and operating system.
#>
# Function to get the list of Dell models from the catalog using XML streaming # Function to get the list of Dell models from the catalog using XML streaming
function Get-DellDriversModelList { function Get-DellDriversModelList {
[CmdletBinding()] [CmdletBinding()]
@@ -1,3 +1,15 @@
<#
.SYNOPSIS
Provides functions to retrieve HP model lists and download corresponding driver packs.
.DESCRIPTION
This module contains the logic specific to handling HP drivers for the FFU Builder UI. It includes functions to:
- Download and parse the HP PlatformList.xml to generate a list of supported HP computer models.
- For a selected model, find the most appropriate driver pack based on the specified Windows release and version, with intelligent fallback logic.
- Download the driver pack, extract all individual driver installers, and then extract the driver files from each installer.
- Optionally, compress the final extracted drivers into a single WIM file for easier deployment.
These functions are designed to be called by the main UI logic, often in parallel, to efficiently manage driver acquisition.
#>
# Function to get the list of HP models from the PlatformList.xml # Function to get the list of HP models from the PlatformList.xml
function Get-HPDriversModelList { function Get-HPDriversModelList {
[CmdletBinding()] [CmdletBinding()]
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Provides functions for discovering, downloading, and processing Lenovo drivers.
.DESCRIPTION
This module contains the logic specific to handling Lenovo drivers for the FFU Builder UI. It includes functions to query the Lenovo PSREF (Product Specification Reference) API to find and list available system models based on user search terms. It also provides the core background task for downloading all relevant driver packages for a selected model and Windows release. The download process involves parsing XML catalogs, downloading individual driver executables, silently extracting their contents, and organizing them into a structured folder. The module includes robust error handling, long path mitigation by using temporary extraction locations, and an option to compress the final driver set into a WIM archive.
#>
# Function to get the list of Lenovo models using the PSREF API # Function to get the list of Lenovo models using the PSREF API
function Get-LenovoDriversModelList { function Get-LenovoDriversModelList {
[CmdletBinding()] [CmdletBinding()]
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Provides functions for discovering, downloading, and processing Microsoft Surface device drivers.
.DESCRIPTION
This module contains the logic specific to handling Microsoft Surface drivers for the FFU UI. It includes a function to scrape the official Microsoft support website to build a list of available Surface models and their driver download pages. It also provides a robust, parallel-capable function to download the correct driver package (MSI or ZIP) based on the selected Windows release, extract its contents, and optionally compress them into a WIM archive. The download process includes logic to handle MSI installer mutexes to prevent conflicts during parallel execution.
#>
# Function to get the list of Microsoft Surface models # Function to get the list of Microsoft Surface models
function Get-MicrosoftDriversModelList { function Get-MicrosoftDriversModelList {
[CmdletBinding()] [CmdletBinding()]
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Provides functions for managing and downloading hardware drivers in the FFU Builder UI.
.DESCRIPTION
This module contains all the business logic for the 'Drivers' tab in the FFU Builder UI. It handles fetching driver model lists from various manufacturers (Microsoft, Dell, HP, Lenovo), displaying and filtering them in the UI, and managing the selection state. It also includes functions to import and export driver selections to a JSON file (Drivers.json) and to orchestrate the parallel download of selected driver packages using the common parallel processing module.
#>
# Helper function to get models for a selected Make and standardize them # Helper function to get models for a selected Make and standardize them
function Get-ModelsForMake { function Get-ModelsForMake {
param( param(
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Contains the function for registering all WPF UI event handlers for the FFU Builder application.
.DESCRIPTION
This module is dedicated to managing user interactions within the FFU Builder UI. It contains the Register-EventHandlers function, which connects UI controls defined in the XAML to their corresponding actions in the PowerShell backend. This includes handling button clicks, text input validation, checkbox state changes, and list view interactions across all tabs, effectively wiring up the application's front-end to its core logic.
#>
function Register-EventHandlers { function Register-EventHandlers {
param([PSCustomObject]$State) param([PSCustomObject]$State)
WriteLog "Registering UI event handlers..." WriteLog "Registering UI event handlers..."
@@ -1,3 +1,18 @@
<#
.SYNOPSIS
Initializes the user interface for the BuildFFUVM_UI application.
.DESCRIPTION
This script module contains functions responsible for initializing the WPF user interface.
It handles several key tasks:
- Caching references to all UI controls for efficient access.
- Populating UI elements like combo boxes with data (e.g., Hyper-V switches).
- Setting default values for all controls based on configuration or predefined settings.
- Dynamically creating and configuring complex UI components, such as sortable/selectable GridView columns and feature selection grids.
This module is critical for setting up the initial state of the application window when it first loads.
#>
function Initialize-UIControls { function Initialize-UIControls {
param([PSCustomObject]$State) param([PSCustomObject]$State)
WriteLog "Initializing UI control references..." WriteLog "Initializing UI control references..."
@@ -1,3 +1,10 @@
<#
.SYNOPSIS
Provides a collection of shared helper functions for manipulating WPF UI controls, handling asynchronous updates, and managing common UI interactions.
.DESCRIPTION
This module contains a variety of reusable functions designed to support the FFU Builder UI. It includes utilities for managing ListView controls, such as sorting, reordering items, and handling 'Select All' functionality. It also provides thread-safe mechanisms for updating the UI from background tasks, wrappers for modern and classic file/folder dialogs, and generic functions for clearing UI content. These shared functions help to reduce code duplication and ensure consistent behavior across different parts of the application.
#>
# Function to update priorities sequentially in a ListView # Function to update priorities sequentially in a ListView
function Update-ListViewPriorities { function Update-ListViewPriorities {
param( param(
@@ -96,9 +103,9 @@ function Update-ListViewItemStatus {
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[object]$WindowObject, # Changed type to [object] [object]$WindowObject,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[object]$ListView, # Changed type to [object] [object]$ListView,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$IdentifierProperty, [string]$IdentifierProperty,
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -135,7 +142,7 @@ function Update-ListViewItemStatus {
catch { catch {
WriteLog "Update-ListViewItemStatus: Error updating ListView: $($_.Exception.Message)" WriteLog "Update-ListViewItemStatus: Error updating ListView: $($_.Exception.Message)"
} }
} # End of if ($WindowObject -is [System.Windows.Window]...) }
else { else {
# Log if called in non-UI mode or with incorrect types (should not happen if Invoke-ParallelProcessing $isUiMode is correct) # Log if called in non-UI mode or with incorrect types (should not happen if Invoke-ParallelProcessing $isUiMode is correct)
WriteLog "Update-ListViewItemStatus: Skipped UI update for $IdentifierValue due to non-UI mode or incorrect object types." WriteLog "Update-ListViewItemStatus: Skipped UI update for $IdentifierValue due to non-UI mode or incorrect object types."
@@ -147,7 +154,7 @@ function Update-OverallProgress {
[CmdletBinding()] [CmdletBinding()]
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
[object]$WindowObject, # Changed type to [object] [object]$WindowObject,
[Parameter(Mandatory)] [Parameter(Mandatory)]
[int]$CompletedCount, [int]$CompletedCount,
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -193,7 +200,7 @@ function Update-OverallProgress {
catch { catch {
WriteLog "Update-OverallProgress: Error updating progress: $($_.Exception.Message)" WriteLog "Update-OverallProgress: Error updating progress: $($_.Exception.Message)"
} }
} # End of if ($WindowObject -is [System.Windows.Window]) }
else { else {
# Log if called in non-UI mode or with incorrect types # Log if called in non-UI mode or with incorrect types
WriteLog "Update-OverallProgress: Skipped UI update ($StatusText) due to non-UI mode or incorrect WindowObject type." WriteLog "Update-OverallProgress: Skipped UI update ($StatusText) due to non-UI mode or incorrect WindowObject type."
@@ -242,7 +249,6 @@ function Add-SortableColumn {
$checkBoxFactory.AddHandler([System.Windows.Controls.CheckBox]::ClickEvent, [System.Windows.RoutedEventHandler] { $checkBoxFactory.AddHandler([System.Windows.Controls.CheckBox]::ClickEvent, [System.Windows.RoutedEventHandler] {
param($eventSourceLocal, $eventArgsLocal) param($eventSourceLocal, $eventArgsLocal)
# Sync logic would be needed here if this column had a header checkbox
}) })
$gridFactory.AppendChild($checkBoxFactory) $gridFactory.AppendChild($checkBoxFactory)
$cellTemplate.VisualTree = $gridFactory $cellTemplate.VisualTree = $gridFactory
@@ -314,7 +320,7 @@ function Add-SelectableGridViewColumn {
# MODIFICATION: Store the actual ListView object in the header's Tag # MODIFICATION: Store the actual ListView object in the header's Tag
$headerTagObject = [PSCustomObject]@{ $headerTagObject = [PSCustomObject]@{
PropertyName = $IsSelectedPropertyName PropertyName = $IsSelectedPropertyName
ListViewControl = $ListView # Store the object itself ListViewControl = $ListView
} }
$headerCheckBox.Tag = $headerTagObject $headerCheckBox.Tag = $headerTagObject
@@ -322,7 +328,7 @@ function Add-SelectableGridViewColumn {
param($senderCheckBoxLocal, $eventArgsCheckedLocal) param($senderCheckBoxLocal, $eventArgsCheckedLocal)
$tagData = $senderCheckBoxLocal.Tag $tagData = $senderCheckBoxLocal.Tag
$localPropertyName = $tagData.PropertyName $localPropertyName = $tagData.PropertyName
$actualListView = $tagData.ListViewControl # Get the control directly from the tag $actualListView = $tagData.ListViewControl
$collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items } $collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items }
if ($null -ne $collectionToUpdate) { if ($null -ne $collectionToUpdate) {
@@ -336,7 +342,7 @@ function Add-SelectableGridViewColumn {
if ($senderCheckBoxLocal.IsChecked -eq $false) { if ($senderCheckBoxLocal.IsChecked -eq $false) {
$tagData = $senderCheckBoxLocal.Tag $tagData = $senderCheckBoxLocal.Tag
$localPropertyName = $tagData.PropertyName $localPropertyName = $tagData.PropertyName
$actualListView = $tagData.ListViewControl # Get the control directly from the tag $actualListView = $tagData.ListViewControl
$collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items } $collectionToUpdate = if ($null -ne $actualListView.ItemsSource) { $actualListView.ItemsSource } else { $actualListView.Items }
if ($null -ne $collectionToUpdate) { if ($null -ne $collectionToUpdate) {
@@ -366,7 +372,7 @@ function Add-SelectableGridViewColumn {
# MODIFICATION: Store the actual ListView object in the item checkbox's Tag # MODIFICATION: Store the actual ListView object in the item checkbox's Tag
$tagObject = [PSCustomObject]@{ $tagObject = [PSCustomObject]@{
HeaderCheckboxKeyName = $HeaderCheckBoxKeyName HeaderCheckboxKeyName = $HeaderCheckBoxKeyName
ListViewControl = $ListView # Store the object itself ListViewControl = $ListView
} }
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::TagProperty, $tagObject) $checkBoxFactory.SetValue([System.Windows.FrameworkElement]::TagProperty, $tagObject)
@@ -376,7 +382,7 @@ function Add-SelectableGridViewColumn {
$tagData = $itemCheckBox.Tag $tagData = $itemCheckBox.Tag
$headerCheckboxKeyFromTag = $tagData.HeaderCheckboxKeyName $headerCheckboxKeyFromTag = $tagData.HeaderCheckboxKeyName
$targetListView = $tagData.ListViewControl # Get the control directly from the tag $targetListView = $tagData.ListViewControl
# Get the state from the window tag # Get the state from the window tag
$window = [System.Windows.Window]::GetWindow($targetListView) $window = [System.Windows.Window]::GetWindow($targetListView)
@@ -1,5 +1,9 @@
# FFU UI Core Windows Settings Logic Module <#
# Contains UI helper functions, data retrieval, and core processing logic for the Windows Settings tab. .SYNOPSIS
Manages the business logic for the 'Windows Settings' tab in the FFU Builder UI.
.DESCRIPTION
This module contains all the functions and data required to populate and manage the controls on the 'Windows Settings' tab. It handles dynamic updates for Windows Release, Version, SKU, and Architecture ComboBoxes based on user input, such as specifying an ISO path. It also includes logic for populating available languages, media types, and the grid of optional Windows features.
#>
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# SECTION: Module Variables (Static Data) # SECTION: Module Variables (Static Data)
@@ -170,8 +174,6 @@ $script:ltscGenericSKUs = @( # For LTSC 2019, 2021, 2024
$script:iotLtscSKUs = @( $script:iotLtscSKUs = @(
'IoT Enterprise LTSC', 'IoT Enterprise LTSC',
'IoT Enterprise N LTSC' 'IoT Enterprise N LTSC'
# Note: IoT SKUs are often specialized and might have different edition IDs.
# This list is a general representation. Actual ISOs might be needed for specific IoT LTSC editions.
) )
# Map Windows Release Values to their corresponding SKU lists # Map Windows Release Values to their corresponding SKU lists
@@ -1,9 +1,9 @@
# FFU UI Core Logic Module - Winget Functionality <#
# Contains UI-layer logic for the Winget functionality in the "Applications" tab. .SYNOPSIS
Manages all Winget-related functionality for the 'Applications' tab in the FFU Builder UI.
# ----------------------------------------------------------------------------- .DESCRIPTION
# SECTION: Winget UI Functions (Moved from BuildFFUVM_UI.ps1) This module provides the business logic for interacting with Winget from the FFU Builder UI. It includes functions for searching for packages, importing and exporting application lists, checking for and installing necessary Winget components (CLI and PowerShell module), and managing the parallel download of selected applications. It works in conjunction with FFU.Common.Winget for lower-level operations and FFU.Common.Parallel for managing concurrent downloads.
# ----------------------------------------------------------------------------- #>
# Function to search for Winget apps # Function to search for Winget apps
function Search-WingetApps { function Search-WingetApps {
+6 -2
View File
@@ -1,5 +1,9 @@
# FFU UI Core Logic Module <#
# Contains non-UI specific helper functions, data retrieval, and core processing logic. .SYNOPSIS
Core logic module for the FFU Builder UI, providing helper functions, data retrieval, and UI state management.
.DESCRIPTION
This module serves as the central logic hub for the FFU Builder UI. It contains functions for retrieving system information (like Hyper-V switches and USB drives), providing default application settings, and dynamically managing the visibility and state of various UI controls across different tabs based on user selections. It orchestrates the interactions between different parts of the UI to ensure a consistent and logical user experience.
#>
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# SECTION: Module Variables (Static Data & State) # SECTION: Module Variables (Static Data & State)