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
 
    .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
    #=================================================
    # Block
    Block-StandardUser
    Block-WindowsVersionNe10
    Block-PowerShellVersionLt5
    Block-WindowsReleaseIdLt1703
    #=================================================
    # 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
    }
    #=================================================
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End"
    return (Get-OSDDisk -BusType USB -Number $SelectDisk.Number)
    #=================================================
}