public/New-OSDWorkspaceUSB.ps1
function New-OSDWorkspaceUSB { <# .SYNOPSIS Creates a new OSDWorkspace USB bootable drive with WinPE boot media. .DESCRIPTION The New-OSDWorkspaceUSB function creates a new bootable USB drive from a selected OSDWorkspace WinPE Build. This function prepares the USB drive for booting into WinPE by partitioning, formatting, and copying necessary boot files. 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. Prompts for selection of a USB drive that meets size requirements 6. Clears all data from the selected USB drive (with confirmation) 7. Initializes the disk with MBR partition style 8. Creates and formats a 4GB FAT32 boot partition (active) 9. Creates and formats an NTFS data partition using remaining space 10. Copies the selected WinPE media files to the bootable partition The function creates a dual-partition structure: - A FAT32 bootable partition (4GB) containing WinPE boot files - An NTFS data partition using the remaining space .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 New-OSDWorkspaceUSB Creates a new OSDWorkspace USB with default labels for boot and data partitions. Uses 'USB-WinPE' for the boot partition and 'USB-DATA' for the data partition. .EXAMPLE New-OSDWorkspaceUSB -BootLabel 'BOOT' -DataLabel 'OSDDATA' Creates a new OSDWorkspace USB with custom labels for boot and data partitions. Uses 'BOOT' for the boot partition and 'OSDDATA' for the data partition. .EXAMPLE New-OSDWorkspaceUSB -Verbose Creates a new OSDWorkspace USB with detailed verbose output showing each step of the process. .OUTPUTS Microsoft.Management.Infrastructure.CimInstance#root/Microsoft/Windows/Storage/MSFT_Disk Returns the configured USB disk object. .NOTES Author: David Segura Version: 1.0 Date: April 2025 Prerequisites: - PowerShell 5.0 or higher - Windows 10 or higher (Windows 11 recommended) - Windows 10 build 1703 or higher - Run as Administrator - At least one WinPE build available in the OSDWorkspace - USB drive with minimum capacity of 7GB WARNING: This function will erase ALL data on the selected USB drive. A confirmation prompt will be displayed before erasing the drive. For drives larger than 2TB, the current GPT implementation is commented out as it is not working as expected for bootable drives. #> [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 #================================================= # Select a USB Disk Write-Verbose '$SelectDisk = Invoke-SelectUSBDisk -MinimumSizeGB $MinimumSizeGB -MaximumSizeGB $MaximumSizeGB' $SelectDisk = Invoke-SelectUSBDisk -MinimumSizeGB $MinimumSizeGB -MaximumSizeGB $MaximumSizeGB if (-NOT ($SelectDisk)) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] No USB Drives that met the required criteria were detected" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] MinimumSizeGB: $MinimumSizeGB" Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] MaximumSizeGB: $MaximumSizeGB" Break } #================================================= # Get-OSDDisk -BusType USB # At this point I have the Disk object in $GetUSBDisk Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] `$GetUSBDisk = Get-OSDDisk -BusType USB -Number `$SelectDisk.Number" $GetUSBDisk = Get-OSDDisk -BusType USB -Number $SelectDisk.Number $GetUSBDisk | Select-Object -Property * -ExcludeProperty Cim*,PS*,Pass* #================================================= # Clear-Disk Prompt for Confirmation if ($GetUSBDisk.NumberOfPartitions -eq 0) { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Disk does not have any partitions. This is a good thing!" } else { Write-Verbose '$GetUSBDisk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$true' $GetUSBDisk | Clear-Disk -RemoveData -RemoveOEM -Confirm:$true -ErrorAction Stop } #================================================= # Get-OSDDisk -BusType USB Write-Verbose '$GetUSBDisk = Get-OSDDisk -BusType USB -Number $SelectDisk.Number | Where-Object {$_.NumberOfPartitions -eq 0}' $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)] Something went very very wrong in this process" Break } #================================================= # -lt 2TB #================================================= if ($GetUSBDisk.PartitionStyle -eq 'RAW') { Write-Verbose '$GetUSBDisk | Initialize-Disk -PartitionStyle MBR' $GetUSBDisk | Initialize-Disk -PartitionStyle MBR -ErrorAction Stop } if ($GetUSBDisk.PartitionStyle -eq 'GPT') { Write-Verbose '$GetUSBDisk | Set-Disk -PartitionStyle MBR' 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 # $PEBOOTA = $GetUSBDisk | New-Partition -Size 4GB -IsActive -AssignDriveLetter | Format-Volume -FileSystem FAT32 -NewFileSystemLabel 'WinPE-AMD64' -ErrorAction Stop # $PEBOOTB = $GetUSBDisk | New-Partition -Size 4GB -IsActive -AssignDriveLetter | Format-Volume -FileSystem FAT32 -NewFileSystemLabel 'WinPE-ARM64' -ErrorAction Stop $DataPartition = $GetUSBDisk | New-Partition -UseMaximumSize -AssignDriveLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel $DataLabel -ErrorAction Stop } #================================================= # -ge 2TB # This is not working as expected and will probably not be bootable # So leaving it in here for historic purposes #================================================= <# if ($GetUSBDisk.SizeGB -gt 1800) { $GetUSBDisk | Initialize-Disk -PartitionStyle GPT $DataPartition = $GetUSBDisk | New-Partition -Size ($GetUSBDisk.Size - 2GB) -AssignDriveLetter | ` Format-Volume -FileSystem NTFS -NewFileSystemLabel $DataLabel $BootPartition = $GetUSBDisk | New-Partition -GptType "{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}" -UseMaximumSize -AssignDriveLetter | ` Format-Volume -FileSystem FAT32 -NewFileSystemLabel $BootLabel } #> #================================================= # WinpeDestinationPath $WinpeDestinationPath = "$($BootPartition.DriveLetter):\" if (-NOT ($WinpeDestinationPath)) { Write-Warning "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Unable to find Destination Path at $WinpeDestinationPath" Break } #================================================= # Update WinPE Volume if ((Test-Path -Path "$($BootMediaObject.FullName)") -and (Test-Path -Path "$WinpeDestinationPath")) { Write-Host -ForegroundColor DarkGray "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Copying $($BootMediaObject.FullName) to BootPartition partition at $BootLabel" robocopy "$($BootMediaObject.FullName)" "$WinpeDestinationPath" *.* /e /ndl /njh /njs /np /r:0 /w:0 /b /zb } #================================================= # 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) #================================================= } |