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 .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-USBVolume | Where-Object { $_.FileSystemLabel -eq $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 /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)" } #================================================= # Verify ESD files are present on USB data partition $ESDSourcePath = Join-Path $script:OSDeployCorePath 'OSDCloud' 'OS' if (-not (Test-Path -Path $ESDSourcePath)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] ESD source path not found, skipping data partition sync: $ESDSourcePath" } else { $SourceFiles = Get-ChildItem -Path $ESDSourcePath -File -Recurse if (-not $SourceFiles) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] No ESD files found in $ESDSourcePath, skipping data partition sync" } else { $DataVolumes = Get-USBVolume | Where-Object { $_.FileSystemLabel -eq $DataLabel } if (-not $DataVolumes) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No USB partitions labeled '$DataLabel' were found — skipping ESD copy" } else { foreach ($DataVolume in $DataVolumes) { $DataDrive = $DataVolume.DriveLetter $ESDDestPath = "$DataDrive`:\OSDCloud\OS" Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Checking ESD files on $DataDrive`:\" # Determine which source files are missing from the destination $MissingFiles = $SourceFiles | Where-Object { $RelativePath = $_.FullName.Substring($ESDSourcePath.Length).TrimStart('\') -not (Test-Path -Path (Join-Path $ESDDestPath $RelativePath)) } if (-not $MissingFiles) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] ESD files already present on $DataDrive`:\" continue } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] $($MissingFiles.Count) ESD file(s) missing from $DataDrive`:\" # Verify sufficient free space before prompting $RequiredBytes = ($MissingFiles | 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 ESD 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 ESD copy" continue } # Prompt for confirmation before copying (style matches Update-OSDeployCoreESD) $copyCaption = "Copy ESD Files – $DataDrive`:\" $copyMessage = "Source : $ESDSourcePath`nDest : $ESDDestPath`nFiles : $($MissingFiles.Count) file(s) missing`nSize : $([Math]::Round($RequiredBytes / 1GB, 2)) GB`nFree : $([Math]::Round($FreeBytes / 1GB, 2)) GB`n`nCopy missing ESD files to the USB data partition?" if ($PSCmdlet.ShouldContinue($copyMessage, $copyCaption)) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] Copying ESD files from $ESDSourcePath to $ESDDestPath" robocopy.exe "$ESDSourcePath" "$ESDDestPath" *.* /e /ndl /r:0 /w:0 /xd '$RECYCLE.BIN' 'System Volume Information' /xj } else { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format G)] ESD copy declined by user for $DataDrive`:\" } } } } } #================================================= Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End" #================================================= } |