public/Update-OSDeployBootUSB.ps1

function Update-OSDeployBootUSB {
    <#
    .SYNOPSIS
        Updates an existing OSDeploy USB drive with new BootMedia from an OSDeployCore BootImage build.
 
    .DESCRIPTION
        Refreshes the BootMedia files on one or more existing OSDeploy USB drives. The function:
        - Prompts for selection of a completed BootImage from %ProgramData%\OSDeployCore\boot
        - Prompts for the specific bootmedia folder (bootmedia or bootmedia_ca2023)
        - Locates all connected USB volumes whose label matches $BootLabel (default 'OSDEPLOY')
        - Copies the selected media to each matching USB volume using robocopy
        - Writes a BootMedia.json file to each updated volume
 
        No partitioning or formatting is performed. Use New-OSDeployBootUSB to create a new drive.
 
    .PARAMETER BootLabel
        Volume label used to identify USB boot partitions to update. Maximum 11 characters.
        Default is 'OSDEPLOY'.
 
    .PARAMETER DataLabel
        Volume label used to identify the USB data partition when copying ESD files.
        Maximum 32 characters. Default is 'OSDCloud'.
 
    .EXAMPLE
        Update-OSDeployBootUSB
        Updates all connected USB volumes labeled 'OSDEPLOY' with the selected BootImage build.
 
    .EXAMPLE
        Update-OSDeployBootUSB -BootLabel 'WinPE'
        Updates all connected USB volumes labeled 'WinPE'.
 
    .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
            - One or more USB drives previously created with New-OSDeployBootUSB
 
 
    Dependencies:
      Module Functions: Get-OSDeployVolumeUSB, New-OSDeployBootUSB, Select-OSDeployCoreBootImage, Test-IsAdministrator, Update-OSDeployCoreESD, Write-OSDeployBanner
      Executables: robocopy.exe
      Windows Features: Windows ADK WinPE Addon
    .LINK
        https://github.com/OSDeploy/OSDeploy
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param (
        # Label used to identify USB boot partitions. Default is 'OSDEPLOY'.
        [ValidateLength(0, 11)]
        [string]
        $BootLabel = 'OSDEPLOY',

        # Label used to identify the USB data partition for ESD file copy. 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'
    #=================================================
    # 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
    #=================================================
    # Update matching USB volumes
    if (Test-Path -Path $BootMediaObject.FullName) {
        $WinpeVolumes = Get-OSDeployVolumeUSB -FileSystemLabel $BootLabel

        if ($WinpeVolumes) {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Copying $($BootMediaObject.FullName) to $BootLabel partitions"
            foreach ($volume in $WinpeVolumes) {
                if (Test-Path -Path "$($volume.DriveLetter):\") {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Updating $($volume.DriveLetter):\"
                    robocopy.exe "$($BootMediaObject.FullName)" "$($volume.DriveLetter):\" *.* /e /njh /ndl /r:0 /w:0 /xd '$RECYCLE.BIN' 'System Volume Information' /xj
                    $SelectBootImage | ConvertTo-Json -Depth 5 | Out-File -FilePath "$($volume.DriveLetter):\BootMedia.json" -Force
                }
            }
        }
        else {
            Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No USB partitions labeled '$BootLabel' were found"
        }
    }
    else {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] BootMedia path not found: $($BootMediaObject.FullName)"
    }
    #=================================================
    # 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 {
        $DataVolumes = Get-OSDeployVolumeUSB -FileSystemLabel $DataLabel

        if (-not $DataVolumes) {
            Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No USB partitions labeled '$DataLabel' were found — skipping OSDCloud copy"
        }
        else {
            foreach ($DataVolume in $DataVolumes) {
                $DataDrive        = $DataVolume.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"
                    continue
                }

                if ($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"
                    continue
                }

                $copyCaption = "Copy OSDCloud – $DataDrive`:\"
                $copyMessage = "Source : $OSDCloudSourcePath`nDest : $OSDCloudDestPath`nFiles : $($SourceFiles.Count) file(s)`nSize : $([Math]::Round($RequiredBytes / 1GB, 2)) GB`nFree : $([Math]::Round($FreeBytes / 1GB, 2)) GB`n`nCopy OSDCloud content to the USB data partition?"

                if ($PSCmdlet.ShouldContinue($copyMessage, $copyCaption)) {
                    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
                }
                else {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] OSDCloud copy declined by user for $DataDrive`:\"
                }
            }
        }
    }
    #=================================================
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End"
    #=================================================
}