public/Update-OSDWorkspaceUSB.ps1

function Update-OSDWorkspaceUSB {
    <#
    .SYNOPSIS
        Updates an existing OSDWorkspace USB drive with new WinPE boot media files.
 
    .DESCRIPTION
        The Update-OSDWorkspaceUSB function refreshes an existing OSDWorkspace bootable USB drive
        with updated WinPE boot media files from a selected OSDWorkspace WinPE Build.
         
        This function performs the following operations:
        1. Validates administrator privileges
        2. Prompts for selection of a WinPE Build using Select-OSDWSWinPEBuild
        3. Prompts for selection of a Media type (WinPE-Media or WinPE-MediaEX)
        4. Disables Autorun for the USB drive
        5. Locates existing USB volumes labeled 'USB-WinPE'
        6. Copies the selected WinPE media files to the bootable partition
        7. Creates a BootMedia.json file with build information
         
        No partitioning or formatting is performed, as this function is designed to update
        an existing USB drive that was previously created with New-OSDWorkspaceUSB.
 
    .PARAMETER BootLabel
        Specifies the volume label for the boot partition.
        Default value is 'USB-WinPE'.
        Maximum length is 11 characters due to FAT32 filesystem limitations.
 
    .PARAMETER DataLabel
        Specifies the volume label for the data partition.
        Default value is 'USB-DATA'.
        Maximum length is 32 characters due to NTFS filesystem limitations.
 
    .EXAMPLE
        Update-OSDWorkspaceUSB
         
        Updates an existing OSDWorkspace USB drive with the selected OSDWorkspace WinPE Build.
        Uses the default labels 'USB-WinPE' for boot partition and 'USB-DATA' for data partition.
 
    .EXAMPLE
        Update-OSDWorkspaceUSB -BootLabel 'BOOT' -DataLabel 'DATA'
         
        Updates an existing OSDWorkspace USB drive with the selected OSDWorkspace WinPE Build.
        Uses the custom labels 'BOOT' for boot partition and 'DATA' for data partition.
 
    .EXAMPLE
        Update-OSDWorkspaceUSB -Verbose
         
        Updates an existing OSDWorkspace USB drive with detailed verbose output showing each step of the process.
 
    .OUTPUTS
        Microsoft.Management.Infrastructure.CimInstance#root/Microsoft/Windows/Storage/MSFT_Disk
        Returns the updated USB disk object.
 
    .NOTES
        Author: David Segura
        Version: 1.0
        Date: April 2025
         
        Prerequisites:
            - PowerShell 5.0 or higher
            - Windows 10 or higher
            - Run as Administrator
            - An existing OSDWorkspace USB drive created with New-OSDWorkspaceUSB
            - At least one WinPE build available in the OSDWorkspace
         
        The function searches for USB volumes labeled 'USB-WinPE' to update. If no matching
        volumes are found, the function will display a warning and exit.
    #>


    
    [CmdletBinding()]
    param (
        # Label for the boot partition. Default is 'USB-WinPE'.
        [ValidateLength(0,11)]
        [string]
        $BootLabel = 'USB-WinPE',

        # Label for the data partition. Default is 'USB-DATA'.
        [ValidateLength(0,32)]
        [string]
        $DataLabel = 'USB-DATA'
    )
    #=================================================
    $Error.Clear()
    Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start"
    Initialize-OSDWorkspace
    #=================================================
    # 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'
    $MinimumSizeGB = 7
    $MaximumSizeGB = 2000
    #=================================================
    # Block
    Block-StandardUser
    Block-WindowsVersionNe10
    Block-PowerShellVersionLt5
    Block-WindowsReleaseIdLt1703
    #=================================================
    # Do we have a Boot Media?
    $SelectWinPEMedia = Select-OSDWSWinPEBuild

    if ($null -eq $SelectWinPEMedia) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No OSDWorkspace WinPE Build was found or selected"
        return
    }
    #=================================================
    # Select a BootMedia Media folder
    Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Select an OSDWorkspace WinPE Build to use with this USB (Cancel to exit)"
    $BootMediaObject = Get-ChildItem $($SelectWinPEMedia.Path) -Directory | Where-Object { ($_.Name -eq 'WinPE-Media') -or ($_.Name -eq 'WinPE-MediaEX') } | Sort-Object Name, FullName | Select-Object Name, FullName | Out-GridView -Title 'Select an OSDWorkspace WinPE Build to use with this USB (Cancel to exit)' -OutputMode Single
    if ($null -eq $BootMediaObject) {
        Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No WinPE-Media or WinPE-MediaEX subfolders were found"
        return
    }
    $BootMediaArch = $SelectWinPEMedia.Architecture.ToUpper()
    #$BootLabel = "WinPE-$($BootMediaArch)"
    #=================================================
    # Disable Autorun
    Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' -Name NoDriveTypeAutorun -Type DWord -Value 0xFF -ErrorAction SilentlyContinue
    #=================================================
    # Update WinPE Volume
    if (Test-Path -Path $BootMediaObject.FullName) {
        $WinpeVolumes = Get-USBVolume | Where-Object { $_.FileSystemLabel -eq 'USB-WinPE' }
        if ($WinpeVolumes) {
            Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Copying $($BootMediaObject.FullName) to USB-WinPE partitions"
            foreach ($volume in $WinpeVolumes) {
                if (Test-Path -Path "$($volume.DriveLetter):\") {
                    robocopy "$($BootMediaObject.FullName)" "$($volume.DriveLetter):\" *.* /e /ndl /r:0 /w:0 /xd '$RECYCLE.BIN' 'System Volume Information' /xj
                }
                $SelectWinPEMedia | ConvertTo-Json -Depth 5 | Out-File -FilePath "$($volume.DriveLetter):\BootMedia.json" -Force
            }
        }
        else {
            Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Unable to find a USB Partition labeled USB-WinPE to update"
        }
    }
    #=================================================
    # Remove Read-Only Attribute
    <#
    Get-ChildItem -Path $WinpeDestinationPath -File -Recurse -Force | ForEach-Object {
        Set-ItemProperty -Path $_.FullName -Name IsReadOnly -Value $false -Force -ErrorAction Ignore
    }
    #>

    #=================================================
    return (Get-OSDDisk -BusType USB -Number $SelectDisk.Number)
    #=================================================
}