mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 10:19:36 -06:00
Refactor USB creation to use parallel processing
Improves the performance of creating multiple deployment USB drives by refactoring the process to run in parallel using `ForEach-Object -Parallel`. Key changes: - Mounts the deployment ISO once before processing begins, rather than for each individual drive. - Partitions, formats, and copies files to multiple USB drives concurrently, significantly reducing the total time required. - Simplifies and cleans up the FFU selection logic. - Standardizes on `robocopy` for all large file transfer operations to improve performance and logging.
This commit is contained in:
+83
-149
@@ -3212,212 +3212,146 @@ Function New-DeploymentUSB {
|
|||||||
|
|
||||||
$SelectedFFUFile = $null
|
$SelectedFFUFile = $null
|
||||||
|
|
||||||
# 1. Get FFU File(s)
|
# 1. Get FFU File(s) - This happens once before parallel processing
|
||||||
# Check if the CopyFFU switch is present
|
|
||||||
if ($CopyFFU.IsPresent) {
|
if ($CopyFFU.IsPresent) {
|
||||||
# Get all FFU files in the specified directory
|
|
||||||
$FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu"
|
$FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu"
|
||||||
$FFUCount = $FFUFiles.count
|
$FFUCount = $FFUFiles.count
|
||||||
|
|
||||||
# If there is exactly one FFU file, select it
|
|
||||||
if ($FFUCount -eq 1) {
|
if ($FFUCount -eq 1) {
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
$SelectedFFUFile = $FFUFiles.FullName
|
||||||
|
WriteLog "One FFU file found, will use: $SelectedFFUFile"
|
||||||
}
|
}
|
||||||
# If there are multiple FFU files, prompt the user to select one
|
|
||||||
elseif ($FFUCount -gt 1) {
|
elseif ($FFUCount -gt 1) {
|
||||||
WriteLog "Found $FFUCount FFU files"
|
WriteLog "Found $FFUCount FFU files"
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
if ($VerbosePreference -ne 'Continue') {
|
||||||
Write-Host "Found $FFUCount FFU files"
|
Write-Host "Found $FFUCount FFU files"
|
||||||
}
|
}
|
||||||
$output = @()
|
$output = @()
|
||||||
# Create a table of FFU files with their index, name, and last modified date
|
|
||||||
for ($i = 0; $i -lt $FFUCount; $i++) {
|
for ($i = 0; $i -lt $FFUCount; $i++) {
|
||||||
$index = $i + 1
|
$output += [PSCustomObject]@{
|
||||||
$name = $FFUFiles[$i].Name
|
'FFU Number' = $i + 1
|
||||||
$modified = $FFUFiles[$i].LastWriteTime
|
'FFU Name' = $FFUFiles[$i].Name
|
||||||
$Properties = [ordered]@{
|
'Last Modified' = $FFUFiles[$i].LastWriteTime
|
||||||
'FFU Number' = $index
|
|
||||||
'FFU Name' = $name
|
|
||||||
'Last Modified' = $modified
|
|
||||||
}
|
}
|
||||||
$output += New-Object PSObject -Property $Properties
|
|
||||||
}
|
}
|
||||||
$output | Format-Table -AutoSize -Property 'FFU Number', 'FFU Name', 'Last Modified' | Out-String | Write-Host
|
$output | Format-Table -AutoSize | Out-String | Write-Host
|
||||||
|
|
||||||
# Loop until a valid FFU file is selected
|
|
||||||
do {
|
do {
|
||||||
$inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files"
|
$inputChoice = Read-Host "Enter the number for the FFU to copy, or 'A' for all"
|
||||||
# Check if the input is a valid number or 'A'
|
|
||||||
if ($inputChoice -match '^\d+$' -or $inputChoice -eq 'A') {
|
|
||||||
if ($inputChoice -eq 'A') {
|
if ($inputChoice -eq 'A') {
|
||||||
# Select all FFU files
|
|
||||||
$SelectedFFUFile = $FFUFiles.FullName
|
$SelectedFFUFile = $FFUFiles.FullName
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host 'Will copy all FFU files'
|
|
||||||
}
|
|
||||||
WriteLog 'Will copy all FFU Files'
|
WriteLog 'Will copy all FFU Files'
|
||||||
}
|
}
|
||||||
else {
|
elseif ($inputChoice -match '^\d+$' -and [int]$inputChoice -ge 1 -and [int]$inputChoice -le $FFUCount) {
|
||||||
# Convert input to integer and validate the selection
|
$SelectedFFUFile = $FFUFiles[[int]$inputChoice - 1].FullName
|
||||||
$inputChoice = [int]$inputChoice
|
|
||||||
if ($inputChoice -ge 1 -and $inputChoice -le $FFUCount) {
|
|
||||||
$selectedIndex = $inputChoice - 1
|
|
||||||
$SelectedFFUFile = $FFUFiles[$selectedIndex].FullName
|
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host "$SelectedFFUFile was selected"
|
|
||||||
}
|
|
||||||
WriteLog "$SelectedFFUFile was selected"
|
WriteLog "$SelectedFFUFile was selected"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Handle invalid selection
|
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host "Invalid selection. Please try again."
|
Write-Host "Invalid selection. Please try again."
|
||||||
}
|
}
|
||||||
WriteLog "Invalid selection. Please try again."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Handle invalid input
|
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host "Invalid selection. Please try again."
|
|
||||||
}
|
|
||||||
WriteLog "Invalid selection. Please try again."
|
|
||||||
}
|
|
||||||
} while ($null -eq $SelectedFFUFile)
|
} while ($null -eq $SelectedFFUFile)
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Handle case where no FFU files are found
|
Write-Error "No FFU files found in $BuildUSBPath\FFU. Cannot copy FFU to USB drive."
|
||||||
WriteLog "No FFU files found in the current directory."
|
|
||||||
Write-Error "No FFU files found in the current directory."
|
|
||||||
Return
|
Return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# 2. Partition and format USB drives
|
|
||||||
$counter = 0
|
|
||||||
|
|
||||||
foreach ($USBDrive in $USBDrives) {
|
# Mount ISO once before the loop
|
||||||
$Counter++
|
WriteLog "Mounting deployment ISO: $DeployISO"
|
||||||
WriteLog "Formatting USB drive $Counter out of $USBDrivesCount"
|
$ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\"
|
||||||
WriteLog "USB Drive Model: $($USBDrive.Model)"
|
WriteLog "ISO mounted at $ISOMountPoint"
|
||||||
WriteLog "USB Drive Serial Number: $($USBDrive.SerialNumber)"
|
|
||||||
|
# 2. Partition and format USB drives in parallel
|
||||||
|
WriteLog "Starting parallel creation for $USBDrivesCount USB drive(s)."
|
||||||
|
|
||||||
|
$USBDrives | ForEach-Object -Parallel {
|
||||||
|
$USBDrive = $_
|
||||||
|
|
||||||
|
# Import common module for logging in this thread
|
||||||
|
Import-Module "$($using:PSScriptRoot)\FFU.Common" -Force
|
||||||
|
Set-CommonCoreLogPath -Path $using:LogFile
|
||||||
|
|
||||||
$DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "")
|
$DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "")
|
||||||
WriteLog "Physical Disk number is $DiskNumber for USB drive $Counter out of $USBDrivesCount"
|
WriteLog "Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId) processing DiskNumber $DiskNumber ($($USBDrive.Model))"
|
||||||
|
|
||||||
$ScriptBlock = {
|
# Partitioning
|
||||||
param($DiskNumber)
|
|
||||||
$Disk = Get-Disk -Number $DiskNumber
|
$Disk = Get-Disk -Number $DiskNumber
|
||||||
# Clear-Disk -Number $DiskNumber -RemoveData -RemoveOEM -Confirm:$false
|
|
||||||
# Clear-disk has an unusual behavior where it sets external hard disk media as RAW, however removable media is set as MBR.
|
|
||||||
if ($Disk.PartitionStyle -ne "RAW") {
|
if ($Disk.PartitionStyle -ne "RAW") {
|
||||||
$Disk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false
|
$Disk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false
|
||||||
$Disk = Get-Disk -Number $DiskNumber
|
$Disk = Get-Disk -Number $DiskNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($Disk.PartitionStyle -eq "RAW") {
|
if ($Disk.PartitionStyle -eq "RAW") {
|
||||||
$Disk | Initialize-Disk -PartitionStyle MBR -Confirm:$false
|
$Disk | Initialize-Disk -PartitionStyle MBR -Confirm:$false
|
||||||
}
|
}
|
||||||
elseif ($Disk.PartitionStyle -ne "RAW") {
|
else {
|
||||||
$Disk | Get-Partition | Remove-Partition -Confirm:$false
|
$Disk | Get-Partition | Remove-Partition -Confirm:$false
|
||||||
$Disk | Set-Disk -PartitionStyle MBR
|
$Disk | Set-Disk -PartitionStyle MBR
|
||||||
}
|
}
|
||||||
# Get-Disk $DiskNumber | Get-Partition | Remove-Partition
|
|
||||||
$BootPartition = $Disk | New-Partition -Size 2GB -IsActive -AssignDriveLetter
|
$BootPartition = $Disk | New-Partition -Size 2GB -IsActive -AssignDriveLetter
|
||||||
$DeployPartition = $Disk | New-Partition -UseMaximumSize -AssignDriveLetter
|
$DeployPartition = $Disk | New-Partition -UseMaximumSize -AssignDriveLetter
|
||||||
Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "TempBoot" -Confirm:$false
|
Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "TempBoot" -Confirm:$false
|
||||||
Format-Volume -Partition $DeployPartition -FileSystem NTFS -NewFileSystemLabel "TempDeploy" -Confirm:$false
|
Format-Volume -Partition $DeployPartition -FileSystem NTFS -NewFileSystemLabel "TempDeploy" -Confirm:$false
|
||||||
|
|
||||||
|
$BootPartitionDriveLetter = "$($BootPartition.DriveLetter):\"
|
||||||
|
$DeployPartitionDriveLetter = "$($DeployPartition.DriveLetter):\"
|
||||||
|
WriteLog "Disk $DiskNumber partitioned. Boot: $BootPartitionDriveLetter, Deploy: $DeployPartitionDriveLetter"
|
||||||
|
|
||||||
|
# Copy WinPE files
|
||||||
|
WriteLog "Copying WinPE files from $($using:ISOMountPoint) to $BootPartitionDriveLetter"
|
||||||
|
robocopy $using:ISOMountPoint $BootPartitionDriveLetter /E /COPYALL /R:5 /W:5 /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
|
||||||
|
# Copy other files
|
||||||
|
if ($using:CopyFFU.IsPresent -and $null -ne $using:SelectedFFUFile) {
|
||||||
|
if ($using:SelectedFFUFile -is [array]) {
|
||||||
|
WriteLog "Copying multiple FFU files to $DeployPartitionDriveLetter"
|
||||||
|
foreach ($FFUFile in $using:SelectedFFUFile) {
|
||||||
|
robocopy (Split-Path $FFUFile -Parent) $DeployPartitionDriveLetter (Split-Path $FFUFile -Leaf) /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "Copying $($using:SelectedFFUFile) to $DeployPartitionDriveLetter"
|
||||||
|
robocopy (Split-Path $using:SelectedFFUFile -Parent) $DeployPartitionDriveLetter (Split-Path $using:SelectedFFUFile -Leaf) /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLog 'Partitioning USB Drive'
|
if ($using:CopyDrivers) {
|
||||||
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $DiskNumber | Out-null
|
$DriversPathOnUSB = Join-Path $DeployPartitionDriveLetter "Drivers"
|
||||||
WriteLog 'Done'
|
WriteLog "Copying drivers to $DriversPathOnUSB"
|
||||||
|
robocopy $using:DriversFolder $DriversPathOnUSB /E /COPYALL /R:5 /W:5 /J /XF .gitkeep /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
# 3. Copy WinPE files to USB drive boot partition
|
if ($using:CopyPPKG) {
|
||||||
|
$PPKGPathOnUSB = Join-Path $DeployPartitionDriveLetter "PPKG"
|
||||||
|
WriteLog "Copying PPKGs to $PPKGPathOnUSB"
|
||||||
|
robocopy $using:PPKGFolder $PPKGPathOnUSB /E /COPYALL /R:5 /W:5 /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
# $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
if ($using:CopyUnattend) {
|
||||||
$BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveLetter IS NOT NULL").Name
|
$UnattendPathOnUSB = Join-Path $DeployPartitionDriveLetter "Unattend"
|
||||||
$ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\"
|
WriteLog "Copying Unattend files to $UnattendPathOnUSB"
|
||||||
WriteLog "Copying WinPE files to $BootPartitionDriveLetter"
|
robocopy $using:UnattendFolder $UnattendPathOnUSB /E /COPYALL /R:5 /W:5 /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
robocopy "$ISOMountPoint" "$BootPartitionDriveLetter" /E /COPYALL /R:5 /W:5 /J
|
}
|
||||||
|
|
||||||
|
if ($using:CopyAutopilot) {
|
||||||
|
$AutopilotPathOnUSB = Join-Path $DeployPartitionDriveLetter "Autopilot"
|
||||||
|
WriteLog "Copying Autopilot files to $AutopilotPathOnUSB"
|
||||||
|
robocopy $using:AutopilotFolder $AutopilotPathOnUSB /E /COPYALL /R:5 /W:5 /J /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rename volumes
|
||||||
|
WriteLog "Renaming volumes for disk $DiskNumber"
|
||||||
|
Set-Volume -DriveLetter $BootPartition.DriveLetter -NewFileSystemLabel "Boot"
|
||||||
|
Set-Volume -DriveLetter $DeployPartition.DriveLetter -NewFileSystemLabel "Deploy"
|
||||||
|
WriteLog "Finished processing disk $DiskNumber"
|
||||||
|
|
||||||
|
} -ThrottleLimit 2
|
||||||
|
|
||||||
|
# Dismount ISO after all parallel jobs are complete
|
||||||
|
WriteLog "Dismounting deployment ISO."
|
||||||
Dismount-DiskImage -ImagePath $DeployISO | Out-Null
|
Dismount-DiskImage -ImagePath $DeployISO | Out-Null
|
||||||
|
|
||||||
#4. Copy FFU file(s), drivers, PPKG, Autopilot, unattend to USB drive deploy partition
|
|
||||||
|
|
||||||
if ($CopyFFU.IsPresent) {
|
|
||||||
if ($null -ne $SelectedFFUFile) {
|
|
||||||
# $DeployPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempDeploy' AND DriveType=2 AND DriveLetter IS NOT NULL").Name
|
|
||||||
$DeployPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempDeploy' AND DriveLetter IS NOT NULL").Name
|
|
||||||
if ($SelectedFFUFile -is [array]) {
|
|
||||||
WriteLog "Copying multiple FFU files to $DeployPartitionDriveLetter. This could take a few minutes."
|
|
||||||
foreach ($FFUFile in $SelectedFFUFile) {
|
|
||||||
robocopy $(Split-Path $FFUFile -Parent) $DeployPartitionDriveLetter $(Split-Path $FFUFile -Leaf) /COPYALL /R:5 /W:5 /J
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog ("Copying " + $SelectedFFUFile + " to $DeployPartitionDriveLetter. This could take a few minutes.")
|
|
||||||
robocopy $(Split-Path $SelectedFFUFile -Parent) $DeployPartitionDriveLetter $(Split-Path $SelectedFFUFile -Leaf) /COPYALL /R:5 /W:5 /J
|
|
||||||
}
|
|
||||||
#Copy drivers using robocopy due to potential size
|
|
||||||
if ($CopyDrivers) {
|
|
||||||
WriteLog "Copying drivers to $DeployPartitionDriveLetter\Drivers"
|
|
||||||
if ($Make) {
|
|
||||||
WriteLog "Copying drivers for make: $Make"
|
|
||||||
robocopy "$DriversFolder\$Make" "$DeployPartitionDriveLetter\Drivers" /E /R:5 /W:5 /J
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "No make specified, copying all drivers"
|
|
||||||
robocopy "$DriversFolder" "$DeployPartitionDriveLetter\Drivers" /E /R:5 /W:5 /J
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
#Copy Unattend file to the USB drive.
|
|
||||||
if ($CopyUnattend) {
|
|
||||||
# WriteLog "Copying Unattend folder to $DeployPartitionDriveLetter"
|
|
||||||
# Copy-Item -Path "$FFUDevelopmentPath\Unattend" -Destination $DeployPartitionDriveLetter -Recurse -Force
|
|
||||||
$DeployUnattendPath = "$DeployPartitionDriveLetter\unattend"
|
|
||||||
WriteLog "Copying unattend file to $DeployUnattendPath"
|
|
||||||
New-Item -Path $DeployUnattendPath -ItemType Directory | Out-Null
|
|
||||||
if ($WindowsArch -eq 'x64') {
|
|
||||||
Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_x64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null
|
|
||||||
}
|
|
||||||
if ($WindowsArch -eq 'arm64') {
|
|
||||||
Copy-Item -Path "$FFUDevelopmentPath\unattend\unattend_arm64.xml" -Destination "$DeployUnattendPath\Unattend.xml" -Force | Out-Null
|
|
||||||
}
|
|
||||||
#Check for prefixes.txt file and copy it to the USB drive
|
|
||||||
if (Test-Path "$FFUDevelopmentPath\unattend\prefixes.txt") {
|
|
||||||
WriteLog "Copying prefixes.txt file to $DeployUnattendPath"
|
|
||||||
Copy-Item -Path "$FFUDevelopmentPath\unattend\prefixes.txt" -Destination "$DeployUnattendPath\prefixes.txt" -Force | Out-Null
|
|
||||||
}
|
|
||||||
WriteLog 'Copy completed'
|
|
||||||
}
|
|
||||||
#Copy PPKG folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder
|
|
||||||
if ($CopyPPKG) {
|
|
||||||
WriteLog "Copying PPKG folder to $DeployPartitionDriveLetter"
|
|
||||||
Copy-Item -Path "$FFUDevelopmentPath\PPKG" -Destination $DeployPartitionDriveLetter -Recurse -Force
|
|
||||||
}
|
|
||||||
#Copy Autopilot folder in the FFU folder to the USB drive. Can use copy-item as it's a small folder
|
|
||||||
if ($CopyAutopilot) {
|
|
||||||
WriteLog "Copying Autopilot folder to $DeployPartitionDriveLetter"
|
|
||||||
Copy-Item -Path "$FFUDevelopmentPath\Autopilot" -Destination $DeployPartitionDriveLetter -Recurse -Force
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "No FFU file selected. Skipping copy."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Volume -FileSystemLabel "TempBoot" -NewFileSystemLabel "Boot"
|
|
||||||
Set-Volume -FileSystemLabel "TempDeploy" -NewFileSystemLabel "Deploy"
|
|
||||||
|
|
||||||
if ($USBDrivesCount -gt 1) {
|
|
||||||
& mountvol $BootPartitionDriveLetter /D
|
|
||||||
& mountvol $DeployPartitionDriveLetter /D
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Drive $counter completed"
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "USB Drives completed"
|
WriteLog "USB Drives completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user