public/Update-OSDeployUSB.ps1

function Update-OSDeployUSB {
    <#
    .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-media
        - Prompts for the specific bootmedia folder (bootmedia or bootmedia_ca2023)
        - Locates all connected USB volumes whose label matches $BootLabel (default 'USB-WinPE')
        - 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-OSDeployUSB to create a new drive.
 
    .PARAMETER BootLabel
        Volume label used to identify USB boot partitions to update. Maximum 11 characters.
        Default is 'USB-WinPE'.
 
    .EXAMPLE
        Update-OSDeployUSB
        Updates all connected USB volumes labeled 'USB-WinPE' with the selected BootImage build.
 
    .EXAMPLE
        Update-OSDeployUSB -BootLabel 'OSDEPLOY'
        Updates all connected USB volumes labeled 'OSDEPLOY'.
 
    .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-media
            - One or more USB drives previously created with New-OSDeployUSB
 
    .LINK
        https://github.com/OSDeploy/OSDeploy
    #>


    [CmdletBinding()]
    param (
        # Label used to identify USB boot partitions. Default is 'USB-WinPE'.
        [ValidateLength(0, 11)]
        [string]
        $BootLabel = 'USB-WinPE'
    )
    #=================================================
    Write-OSDeployBanner
    $Error.Clear()
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start"
    #=================================================
    # Requires Run as Administrator
    $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    if (-not $IsAdmin) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator"
        return
    }
    #=================================================
    # Set Variables
    $ErrorActionPreference = 'Stop'
    #=================================================
    # 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)] [$($MyInvocation.MyCommand.Name)] Selected BootImage: $($SelectBootImage.Name)"
    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] BootImage path: $($SelectBootImage.Path)"
    #=================================================
    # Select a bootmedia folder
    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] 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-USBVolume | Where-Object { $_.FileSystemLabel -eq $BootLabel }

        if ($WinpeVolumes) {
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Copying $($BootMediaObject.FullName) to $BootLabel partitions"
            foreach ($volume in $WinpeVolumes) {
                if (Test-Path -Path "$($volume.DriveLetter):\") {
                    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Updating $($volume.DriveLetter):\"
                    robocopy.exe "$($BootMediaObject.FullName)" "$($volume.DriveLetter):\" *.* /e /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)"
    }
    #=================================================
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End"
    #=================================================
}