Public/Functions/Copy-IsoToUsb.ps1

function Copy-IsoToUsb {
    <#
    .SYNOPSIS
    Creates a bootable USB drive from a Windows ISO.
 
    .DESCRIPTION
    Formats a selected USB disk, mounts the ISO, and copies installation files
    to the USB volume. Supports FAT32 or NTFS, optional bootsect execution, and
    optional splitting of large install.wim files.
 
    .PARAMETER ISOFile
    Full path to the ISO file to mount and copy.
 
    .PARAMETER MakeBootable
    Runs bootsect.exe against the USB drive after formatting.
 
    .PARAMETER NTFS
    Formats the USB drive as NTFS instead of FAT32.
 
    .PARAMETER SplitWim
    Forces splitting install.wim into .swm files during copy.
 
    .PARAMETER USBLabel
    File system label assigned to the USB drive.
 
    .EXAMPLE
    Copy-IsoToUsb -ISOFile 'C:\Temp\Win11.iso' -MakeBootable -USBLabel WIN11
    Creates a bootable USB and copies the ISO contents.
 
    .EXAMPLE
    Copy-IsoToUsb -ISOFile 'C:\Temp\Win11.iso' -NTFS -USBLabel WIN11NTFS
    Creates an NTFS-formatted USB and copies the ISO contents.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-11 - Updated comment-based help
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Extension -eq '.iso')})]
        [System.String]$ISOFile,
        [System.Management.Automation.SwitchParameter]$MakeBootable,
        [System.Management.Automation.SwitchParameter]$NTFS,
        [System.Management.Automation.SwitchParameter]$SplitWim,
        [System.String]$USBLabel
    )
    begin {
        #=================================================
        Write-Verbose "Validating Elevated Permissions ..."
        #=================================================
        $Elevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
        if ( -not $Elevated ) {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)]This Function requires Elevation"
        }
    }
    
    process {
        #=================================================
        Write-Verbose "Selecting USB Drive ..."
        #=================================================
        if ($NTFS) {
            $Results = Get-Disk | Where-Object {$_.Size/1GB -lt 33 -and $_.BusType -eq 'USB'} | Out-GridView -Title 'Select USB Drive to Format' -OutputMode Single | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -PassThru | New-Partition -UseMaximumSize -IsActive -AssignDriveLetter | Format-Volume -FileSystem NTFS -NewFileSystemLabel $USBLabel
        } else {
            $Results = Get-Disk | Where-Object {$_.Size/1GB -lt 33 -and $_.BusType -eq 'USB'} | Out-GridView -Title 'Select USB Drive to Format' -OutputMode Single | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -PassThru | New-Partition -UseMaximumSize -IsActive -AssignDriveLetter | Format-Volume -FileSystem FAT32 -NewFileSystemLabel $USBLabel
        }
        
        #=================================================
        Write-Verbose "Validating a USB Drive was Selected ..."
        #=================================================
        if($null -eq $Results) {
            throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)]No USB Driver was Found or Selected"
        }

        #=================================================
        Write-Verbose "Getting Volumes ..."
        #=================================================
        $Volumes = (Get-Volume).Where({$_.DriveLetter}).DriveLetter
        
        #=================================================
        Write-Verbose "Mounting the ISO ..."
        #=================================================
        Mount-DiskImage -ImagePath $ISOFile
        
        #=================================================
        Write-Verbose "Waiting 5 Seconds ..."
        #=================================================
        Start-Sleep -s 5
        
        #=================================================
        Write-Verbose "Detemrining the Drive Letter of the Mounted ISO ..."
        #=================================================
        $ISO = (Compare-Object -ReferenceObject $Volumes -DifferenceObject (Get-Volume).Where({$_.DriveLetter}).DriveLetter).InputObject
        
        #=================================================
        Write-Verbose "Making the USB Drive Botoable ..."
        #=================================================
        if ($MakeBootable.IsPresent) {
            Set-Location -Path "$($ISO):\boot"
            bootsect.exe /nt60 "$($Results.DriveLetter):"    
        }
        
        #=================================================
        Write-Verbose "Set SplitWim"
        #=================================================
        if (! ($NTFS.IsPresent)) {
            if (Test-Path "$($ISO):\sources\install.wim") {
                if ((Get-Item "$($ISO):\sources\install.wim").length -gt 4gb) {
                    Write-Verbose "Split-WindowsImage: True"
                    $SplitWim = $true
                }
            }
        }

        #=================================================
        Write-Verbose "Copying Files ..."
        #=================================================
        if ($SplitWim.IsPresent) {
            Copy-Item -Path "$($ISO):\*" -Exclude install.wim -Destination "$($Results.DriveLetter):" -Recurse -Verbose

            if (Test-Path "$($ISO):\sources\install.wim") {
                $WimTemp = "$((Get-Date).ToString('HHmmss'))"

                if (Test-Path "$env:TEMP\$WimTemp") {Remove-Item -Path "$env:TEMP\$WimTemp" -Force | Out-Null}
                New-Item -Path "$env:TEMP\$WimTemp" -ItemType Directory -Force | Out-Null

                Write-Host "Copying $($ISO):\sources\install.wim to $env:TEMP\$WimTemp\install.wim" -ForegroundColor Green
                Copy-Item -Path "$($ISO):\sources\install.wim" -Destination "$env:TEMP\$WimTemp\install.wim" -Verbose

                Set-ItemProperty -Path "$env:TEMP\$WimTemp\install.wim" -Name IsReadOnly -Value $false | Out-Null
                
                Write-Host "Splitting install.wim to $env:TEMP\$WimTemp\install*.swm" -ForegroundColor Green
                Split-WindowsImage -FileSize 500 -ImagePath "$env:TEMP\$WimTemp\install.wim" -SplitImagePath "$env:TEMP\$WimTemp\install.swm" | Out-Null
                
                Write-Host "Copying install*.swm to $($Results.DriveLetter):\sources" -ForegroundColor Green
                Copy-Item -Path "$env:TEMP\$WimTemp\*" -Exclude install.wim -Destination "$($Results.DriveLetter):\sources" -Recurse -Verbose
            }
        } else {
            Copy-Item -Path "$($ISO):\*" -Destination "$($Results.DriveLetter):" -Recurse -Verbose
        }

        #=================================================
        Write-Verbose "Dismounting Disk Image ..."
        #=================================================
        Dismount-DiskImage -ImagePath $ISOFile
    }
    end {
        #=================================================
        Write-Verbose "Complete"
        #=================================================
    }
}