public/New-OSDeployBootUSB.ps1
|
function New-OSDeployBootUSB { <# .SYNOPSIS Creates a new OSDeploy USB bootable drive from an OSDeployCore BootImage build. .DESCRIPTION Prepares a USB drive for use as an OSDeploy bootable device. The function: - Prompts for selection of a completed BootImage from %ProgramData%\OSDeployCore\boot - Prompts for the specific bootmedia folder (bootmedia or bootmedia_ca2023) - Selects a suitable USB disk (7 GB - 2 TB) - Clears, partitions (MBR), and formats the disk: - FAT32 4 GB active boot partition (labeled $BootLabel) - NTFS remaining-space data partition (labeled $DataLabel) - Copies the selected BootMedia to the FAT32 partition .PARAMETER BootLabel Volume label for the FAT32 boot partition. Maximum 11 characters (FAT32 limit). Default is 'OSDEPLOY'. .PARAMETER DataLabel Volume label for the NTFS data partition. Maximum 32 characters. Default is 'OSDCloud'. .EXAMPLE New-OSDeployBootUSB Creates a new OSDeploy USB using default labels 'OSDEPLOY' and 'OSDCloud'. .EXAMPLE New-OSDeployBootUSB -BootLabel 'WinPE' -DataLabel 'WinPE-Data' Creates a new OSDeploy USB using custom partition labels. .OUTPUTS Microsoft.Management.Infrastructure.CimInstance#root/Microsoft/Windows/Storage/MSFT_Disk Returns the prepared USB disk object. .NOTES Author: David Segura Company: Recast Software Version: 0.1.0 Date: April 2026 Prerequisites: - PowerShell 5.0 or higher - Windows 10 or higher - Run as Administrator - At least one completed BootImage build in %ProgramData%\OSDeployCore\boot - A USB drive of at least 7 GB Dependencies: Module Functions: Get-OSDDisk, Invoke-SelectUSBDisk, Select-OSDeployCoreBootImage, Test-IsAdministrator, Write-OSDeployBanner Executables: robocopy.exe Windows Features: Windows ADK WinPE Addon .LINK https://github.com/OSDeploy/OSDeploy #> [CmdletBinding()] param ( # Label for the FAT32 boot partition. Default is 'OSDEPLOY'. [ValidateLength(0, 11)] [string] $BootLabel = 'OSDEPLOY', # Label for the NTFS data partition. Default is 'OSDCloud'. [ValidateLength(0, 32)] [string] $DataLabel = 'OSDCloud' ) #================================================= Write-OSDeployBanner $Error.Clear() Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start" #================================================= # Requires Run as Administrator if (-not (Test-IsAdministrator)) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator" return } #================================================= # Set Variables $ErrorActionPreference = 'Stop' $MinimumSizeGB = 7 $MaximumSizeGB = 2000 #================================================= # Select a BootImage build $SelectBootImage = Select-OSDeployCoreBootImage if ($null -eq $SelectBootImage) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No OSDeployCore BootImage build was found or selected" return } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Selected BootImage: $($SelectBootImage.Name)" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] BootImage path: $($SelectBootImage.Path)" #================================================= # Select a bootmedia folder Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Select a bootmedia folder to copy to the USB (Cancel to exit)" $BootMediaObject = Get-ChildItem $SelectBootImage.Path -Directory | Where-Object { ($_.Name -eq 'bootmedia') -or ($_.Name -eq 'bootmedia-ca2023') } | Sort-Object Name, FullName | Select-Object Name, FullName | Out-GridView -Title 'Select a bootmedia folder to copy to the USB (Cancel to exit)' -OutputMode Single if ($null -eq $BootMediaObject) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No bootmedia folder was found or selected" return } #================================================= # Disable Autorun Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' -Name NoDriveTypeAutorun -Type DWord -Value 0xFF -ErrorAction SilentlyContinue #================================================= # Select USB disk $SelectDisk = Invoke-SelectUSBDisk -MinimumSizeGB $MinimumSizeGB -MaximumSizeGB $MaximumSizeGB if (-not $SelectDisk) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No USB drive found meeting the size requirements ($MinimumSizeGB GB - $MaximumSizeGB GB)" break } $GetUSBDisk = Get-OSDDisk -BusType USB -Number $SelectDisk.Number $GetUSBDisk | Select-Object -Property * -ExcludeProperty Cim*, PS*, Pass* #================================================= # Clear the disk if ($GetUSBDisk.NumberOfPartitions -ne 0) { $GetUSBDisk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$true -ErrorAction Stop } $GetUSBDisk = Get-OSDDisk -BusType USB -Number $SelectDisk.Number | Where-Object { $_.NumberOfPartitions -eq 0 } if (-not $GetUSBDisk) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to verify the USB disk was cleared successfully" break } #================================================= # Initialize and partition (MBR) if ($GetUSBDisk.PartitionStyle -eq 'RAW') { $GetUSBDisk | Initialize-Disk -PartitionStyle MBR -ErrorAction Stop } if ($GetUSBDisk.PartitionStyle -eq 'GPT') { Set-Disk -Number $GetUSBDisk.Number -PartitionStyle MBR -ErrorAction Stop } if ($GetUSBDisk.SizeGB -le 2000) { $BootPartition = $GetUSBDisk | New-Partition -Size 4GB -IsActive -AssignDriveLetter | Format-Volume -FileSystem FAT32 -NewFileSystemLabel $BootLabel -ErrorAction Stop $DataPartition = $GetUSBDisk | New-Partition -UseMaximumSize -AssignDriveLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel $DataLabel -ErrorAction Stop } #================================================= # Copy BootMedia to the FAT32 partition $WinpeDestinationPath = "$($BootPartition.DriveLetter):\" if (-not $WinpeDestinationPath) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to determine the destination drive letter" break } if ((Test-Path $BootMediaObject.FullName) -and (Test-Path $WinpeDestinationPath)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Copying BootMedia to $WinpeDestinationPath" robocopy.exe "$($BootMediaObject.FullName)" "$WinpeDestinationPath" *.* /e /ndl /njh /njs /np /r:0 /w:0 /b /zb } #================================================= # Copy OSDCloud content to USB data partition $OSDCloudSourcePath = Join-Path $script:OSDeployCorePath 'OSDCloud' if (-not (Test-Path -Path $OSDCloudSourcePath)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] OSDCloud source path not found, skipping data partition copy: $OSDCloudSourcePath" } else { $DataDrive = $DataPartition.DriveLetter $OSDCloudDestPath = "$DataDrive`:\OSDCloud" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Checking OSDCloud content on $DataDrive`:\" # Calculate total source size for free space check $SourceFiles = Get-ChildItem -Path $OSDCloudSourcePath -File -Recurse $RequiredBytes = ($SourceFiles | Measure-Object -Property Length -Sum).Sum $DataVolumeInfo = Get-Volume -DriveLetter $DataDrive -ErrorAction SilentlyContinue $FreeBytes = $DataVolumeInfo.SizeRemaining if ($null -eq $FreeBytes) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Could not determine free space on $DataDrive`:\ — skipping OSDCloud copy" } elseif ($RequiredBytes -gt $FreeBytes) { $RequiredGB = [Math]::Round($RequiredBytes / 1GB, 2) $FreeGB = [Math]::Round($FreeBytes / 1GB, 2) Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Insufficient space on $DataDrive`:\ — need $RequiredGB GB, $FreeGB GB free — skipping OSDCloud copy" } else { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Copying OSDCloud from $OSDCloudSourcePath to $OSDCloudDestPath" robocopy.exe "$OSDCloudSourcePath" "$OSDCloudDestPath" *.* /e /njh /ndl /r:0 /w:0 /xd '$RECYCLE.BIN' 'System Volume Information' /xj } } #================================================= Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End" return (Get-OSDDisk -BusType USB -Number $SelectDisk.Number) #================================================= } |