From f20945be9f251f5ceb676a7cdd17986728942612 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:18:32 -0800 Subject: [PATCH] Commit - Added new parameter `$USBDriveList` - A hashtable containing USB drives from win32_diskdrive where: - Key: USB drive model name (partial match supported) - Value: USB drive serial number (trailing partial match supported due to some serial numbers ending with blank spaces) Example: @{ "SanDisk Ultra" = "1234567890"; "Kingston DataTraveler" = "0987654321" } This is primarily designed for the UI so when a user selects a specific drive, we can make sure we're targeting the correct drive. We don't rely on the physical disk number since a user can save the USB drive information to a config file and re-use that at a later date and the physical disk number could be different, or used on another machine. - Added support for the $USBDriveList parameter to handle copying to specified USB drives. There is no change to the prior USB drive copy process. --- FFUDevelopment/BuildFFUVM.ps1 | 260 +++++++++++++++++++++++++++++++++- 1 file changed, 255 insertions(+), 5 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index dd41ba8..46f1aa5 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -168,6 +168,13 @@ When set to $true, will download and install the latest OneDrive for Windows 10/ .PARAMETER UpdatePreviewCU When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false. +.PARAMETER USBDriveList +A hashtable containing USB drives from win32_diskdrive where: +- Key: USB drive model name (partial match supported) +- Value: USB drive serial number (trailing partial match supported due to some serial numbers ending with blank spaces) + +Example: @{ "SanDisk Ultra" = "1234567890"; "Kingston DataTraveler" = "0987654321" } + .PARAMETER UserAgent User agent string to use when downloading files. @@ -315,6 +322,7 @@ param( [string]$OptionalFeatures, [string]$ProductKey, [bool]$BuildUSBDrive, + [hashtable]$USBDriveList, [Parameter(Mandatory = $false)] [ValidateSet(10, 11, 2016, 2019, 2021, 2022, 2024, 2025)] [int]$WindowsRelease = 11, @@ -417,7 +425,7 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) { } # If this is the Headers parameter, convert PSCustomObject to hashtable - if ((($key -eq 'Headers') -or ($key -eq 'AppsScriptVariables')) -and ($value -is [System.Management.Automation.PSCustomObject])) { + if ((($key -eq 'Headers') -or ($key -eq 'AppsScriptVariables') -or ($key -eq 'USBDriveList')) -and ($value -is [System.Management.Automation.PSCustomObject])) { $hashtableValue = [hashtable]::new() foreach ($prop in $value.psobject.Properties) { $hashtableValue[$prop.Name] = $prop.Value @@ -3621,8 +3629,8 @@ Function Get-USBDrive { # Log the start of the USB drive check WriteLog 'Checking for USB drives' - # Check if external hard disk media is allowed - If ($AllowExternalHardDiskMedia) { + # Check if external hard disk media is allowed and user has not specified USB drives + If ($AllowExternalHardDiskMedia -and (-not($USBDriveList))) { # Get all removable and external hard disk media drives [array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media' OR MediaType='External hard disk media'") [array]$ExternalHardDiskDrives = $USBDrives | Where-Object { $_.MediaType -eq 'External hard disk media' } @@ -3726,6 +3734,27 @@ Function Get-USBDrive { } } } + elseif ($USBDriveList) { + # Log the count of specified USB drives + $USBDriveListCount = $USBDriveList.Count + WriteLog "Looking for $USBDriveListCount USB drives from USB Drive List" + # Get only the specified USB drives based on both model and serial number + $USBDrives = @() + foreach ($model in $USBDriveList.Keys) { + $serialNumber = $USBDriveList[$model] + Writelog "Looking for USB drive model $model with serial number $serialNumber" + $USBDrive = Get-CimInstance -ClassName Win32_DiskDrive -Filter "Model LIKE '%$model%' AND SerialNumber LIKE '$serialNumber%' AND (MediaType='Removable Media' OR MediaType='External hard disk media')" + if ($USBDrive) { + WriteLog "Found USB drive model $($USBDrive.model) with serial number $($USBDrive.serialNumber)" + $USBDrives += $USBDrive + } + else { + WriteLog "USB drive model $model with serial number $serialNumber not found" + } + } + $USBDrivesCount = $USBDrives.Count + WriteLog "Found $USBDrivesCount of $USBDriveListCount USB drives from USB Drive List" + } else { # Get only removable media drives [array]$USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'") @@ -3735,14 +3764,227 @@ Function Get-USBDrive { # Check if any USB drives were found if ($null -eq $USBDrives) { - WriteLog "No removable USB drive found. Exiting" - Write-Error "No removable USB drive found. Exiting" + WriteLog "No USB drive found. Exiting" + Write-Error "No USB drive found. Exiting" exit 1 } # Return the found USB drives and their count return $USBDrives, $USBDrivesCount } +# Function New-DeploymentUSB { +# param( +# [switch]$CopyFFU +# ) +# WriteLog "CopyFFU is set to $CopyFFU" +# $BuildUSBPath = $PSScriptRoot +# WriteLog "BuildUSBPath is $BuildUSBPath" + +# $SelectedFFUFile = $null + +# # 1. Get FFU File(s) +# # Check if the CopyFFU switch is present +# if ($CopyFFU.IsPresent) { +# # Get all FFU files in the specified directory +# $FFUFiles = Get-ChildItem -Path "$BuildUSBPath\FFU" -Filter "*.ffu" +# $FFUCount = $FFUFiles.count + +# # If there is exactly one FFU file, select it +# if ($FFUCount -eq 1) { +# $SelectedFFUFile = $FFUFiles.FullName +# } +# # If there are multiple FFU files, prompt the user to select one +# elseif ($FFUCount -gt 1) { +# WriteLog "Found $FFUCount FFU files" +# if($VerbosePreference -ne 'Continue'){ +# Write-Host "Found $FFUCount FFU files" +# } +# $output = @() +# # Create a table of FFU files with their index, name, and last modified date +# for ($i = 0; $i -lt $FFUCount; $i++) { +# $index = $i + 1 +# $name = $FFUFiles[$i].Name +# $modified = $FFUFiles[$i].LastWriteTime +# $Properties = [ordered]@{ +# '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' + +# # Loop until a valid FFU file is selected +# do { +# $inputChoice = Read-Host "Enter the number corresponding to the FFU file you want to copy or 'A' to copy all FFU files" +# # Check if the input is a valid number or 'A' +# if ($inputChoice -match '^\d+$' -or $inputChoice -eq 'A') { +# if ($inputChoice -eq 'A') { +# # Select all FFU files +# $SelectedFFUFile = $FFUFiles.FullName +# if ($VerbosePreference -ne 'Continue') { +# Write-Host 'Will copy all FFU files' +# } +# WriteLog 'Will copy all FFU Files' +# } +# else { +# # Convert input to integer and validate the selection +# $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" +# } +# else { +# # Handle invalid selection +# if ($VerbosePreference -ne 'Continue') { +# 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) + +# } +# else { +# # Handle case where no FFU files are found +# WriteLog "No FFU files found in the current directory." +# Write-Error "No FFU files found in the current directory." +# Return +# } +# } +# # 2. Partition and format USB drives +# $counter = 0 + +# foreach ($USBDrive in $USBDrives) { +# $Counter++ +# WriteLog "Formatting USB drive $Counter out of $USBDrivesCount" +# $DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") +# WriteLog "Physical Disk number is $DiskNumber for USB drive $Counter out of $USBDrivesCount" + +# $ScriptBlock = { +# param($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") { +# $Disk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false +# $Disk = Get-Disk -Number $DiskNumber +# } + +# if($Disk.PartitionStyle -eq "RAW") { +# $Disk | Initialize-Disk -PartitionStyle MBR -Confirm:$false +# } +# elseif($Disk.PartitionStyle -ne "RAW"){ +# $Disk | Get-Partition | Remove-Partition -Confirm:$false +# $Disk | Set-Disk -PartitionStyle MBR +# } +# # Get-Disk $DiskNumber | Get-Partition | Remove-Partition +# $BootPartition = $Disk | New-Partition -Size 2GB -IsActive -AssignDriveLetter +# $DeployPartition = $Disk | New-Partition -UseMaximumSize -AssignDriveLetter +# Format-Volume -Partition $BootPartition -FileSystem FAT32 -NewFileSystemLabel "TempBoot" -Confirm:$false +# Format-Volume -Partition $DeployPartition -FileSystem NTFS -NewFileSystemLabel "TempDeploy" -Confirm:$false +# } + +# WriteLog 'Partitioning USB Drive' +# Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $DiskNumber | Out-null +# WriteLog 'Done' + +# # 3. Copy WinPE files to USB drive boot partition + +# # $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveType=2 AND DriveLetter IS NOT NULL").Name +# $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveLetter IS NOT NULL").Name +# $ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\" +# WriteLog "Copying WinPE files to $BootPartitionDriveLetter" +# robocopy "$ISOMountPoint" "$BootPartitionDriveLetter" /E /COPYALL /R:5 /W:5 /J +# 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){ +# robocopy "$DriversFolder\$Make" "$DeployPartitionDriveLetter\Drivers" /E /R:5 /W:5 /J +# }else{ +# 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" +# } Function New-DeploymentUSB { param( [switch]$CopyFFU @@ -3753,6 +3995,7 @@ Function New-DeploymentUSB { $SelectedFFUFile = $null + # 1. Get FFU File(s) # Check if the CopyFFU switch is present if ($CopyFFU.IsPresent) { # Get all FFU files in the specified directory @@ -3834,11 +4077,14 @@ Function New-DeploymentUSB { Return } } + # 2. Partition and format USB drives $counter = 0 foreach ($USBDrive in $USBDrives) { $Counter++ WriteLog "Formatting USB drive $Counter out of $USBDrivesCount" + WriteLog "USB Drive Model: $($USBDrive.Model)" + WriteLog "USB Drive Serial Number: $($USBDrive.SerialNumber)" $DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") WriteLog "Physical Disk number is $DiskNumber for USB drive $Counter out of $USBDrivesCount" @@ -3870,6 +4116,8 @@ Function New-DeploymentUSB { Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $DiskNumber | Out-null WriteLog 'Done' + # 3. Copy WinPE files to USB drive boot partition + # $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveType=2 AND DriveLetter IS NOT NULL").Name $BootPartitionDriveLetter = (Get-WmiObject -Class win32_volume -Filter "Label='TempBoot' AND DriveLetter IS NOT NULL").Name $ISOMountPoint = (Mount-DiskImage -ImagePath $DeployISO -PassThru | Get-Volume).DriveLetter + ":\" @@ -3877,6 +4125,8 @@ Function New-DeploymentUSB { robocopy "$ISOMountPoint" "$BootPartitionDriveLetter" /E /COPYALL /R:5 /W:5 /J 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