Create a bootable USB drive

Discussion in 'Scripting' started by GodHand, Oct 2, 2018.

  1. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    378
    410
    10
    #1 GodHand, Oct 2, 2018
    Last edited: Oct 8, 2018
    A fairly simple function that will create both GPT/MBR/UEFI/BIOS bootable USB drives for OS installations or booting anything from Partition Magic to Linux and relies entirely on a disk object to eliminate user error.

    If it detects the USB drive exceeds 32GB, it will allocate only that amount of space.

    After the function clears all data from the USB disk it will create a partition structure based on the parameters passed (whether it's for booting in UEFI/GPT or BIOS/MBR, etc.). It then formats the partitions accordingly. After the USB disk is set up, it will automatically mount the ISO and then measure the length of the install.wim to ensure it does not exceed the FAT32 filesystem's maximum 4GB per-file limit. If the WIM file does exceed this limit, the function will automatically split the WIM into SWM files, create the file and directory structure on the USB disk and copy the OS installation media contents to the USB disk and make the it bootable. It will then automatically dismount the ISO and return a custom object displaying the name of the USB, its access path (assigned drive letter), the filesystem used to format the disk volumes and return a boolean value to verify that the disk is indeed bootable.

    Though I have another function that performs a USB creation like Rufus does (creating two partitions - the "tiny" one retaining only the EFI/boot files and the NTFS partition retaining all the OS files), I use this function more often and I have never had any issues booting multiple USB disks across all types of device firmwares.

    To create a disk object, with your USB disk inserted in the device, you type Get-Disk and locate the USB disk and take note of the number assigned to it. Then you can assign that to a variable - $USB = Get-Disk -Number 6, for example.

    Then to run the function, simply enter New-WinBootUSB -Disk $USB -MediaPath "E:\Windows 10 Pro.iso" -DiskLayout "UEFI" and an optional disk label if you want something recognizable when you boot up with it.

    Updated 10/08/18 per the request of a user:

    - Made the script to also work with a directory of an already extracted Windows installation ISO.
    - The -ISOPath parameter has been changed to -MediaPath and can accept either an ISO, or the full path to a directory of an ISO that has already been extracted.

    [​IMG]

    [​IMG]

    [​IMG]

    Code:
    <#  
        .NOTES
        ===========================================================================
         Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.150
         Created on:       8/20/2018 4:22 AM
         Created by:       BenTheGreat
         Organization:     Omnic.Tech
         Filename:         New-WinBootUSB.ps1
        ===========================================================================
        .DESCRIPTION
            Create a bootable Windows USB.
    #>
    
    Function New-WinBootUSB
    {
    <#
        .SYNOPSIS
            Create a bootable USB drive.
       
        .DESCRIPTION
            Create a bootable USB drive using using a disk object.
       
            Create a Windows installation file structure and copy the full installation media directly to the disk making it bootable on both GPT/UEFI and MBR/BIOS systems
       
        .PARAMETER Disk
            A disk object that identifies the USB drive.
       
            Use the Get-Disk cmdlet to return a disk object.
       
            Example: $USB = Get-Disk -Number 6
       
        .PARAMETER MediaPath
            The full path to a Windows installation media.  This can either be an ISO or directory.
       
        .PARAMETER DiskLayout
            The disk firmware layout of the device being booted by the USB.
       
        .PARAMETER DiskLabel
            An optional volume label to easily identify the USB drive.
       
        .EXAMPLE
            PS C:\> New-WinBootUSB -Disk $USB -MediaPath "E:\Windows 10\WIN_10_ENTERPRISE_LTSC.iso" -DiskLayout UEFI
            PS C:\> New-WinBootUSB -Disk $USB -MediaPath "E:\Windows 10\WIN_10_ENTERPRISE_LTSC" -DiskLayout UEFI -DiskLabel "WIN10LTSC"
    #>
        [CmdletBinding(ConfirmImpact = 'High',
            SupportsShouldProcess = $true)]
        [OutputType([PSCustomObject])]
        Param
        (
            [Parameter(Mandatory = $true,
                HelpMessage = 'A disk object that identifies the USB drive.')]
            [ValidateNotNullOrEmpty()]
            [object]$Disk,
            [Parameter(Mandatory = $true,
                HelpMessage = 'The full path to a Windows installation media.  This can either be an ISO or directory.')]
            [ValidateScript( { Test-Path $(Resolve-Path -Path $_) })]
            [string]$MediaPath,
            [Parameter(Mandatory = $true,
                HelpMessage = 'The disk firmware layout of the device being booted by the USB.')]
            [ValidateSet('UEFI', 'BIOS')]
            [ValidateNotNullOrEmpty()]
            [string]$DiskLayout,
            [Parameter(HelpMessage = 'An optional volume label to easily identify the USB drive.')]
            [string]$DiskLabel
        )
       
        Begin
        {
            $Error.Clear()
        }
        Process
        {
            If ($PSCmdlet.ShouldProcess("$($Disk.FriendlyName), All data will be lost"))
            {
                If (($Disk | Get-Disk).IsReadOnly -eq $true)
                {
                    $Disk | Set-Disk -IsReadOnly $false
                    $Disk = Get-Disk -Number $Disk.Number
                }
                $Disk | Get-Partition | Remove-Partition -Confirm:$false -ErrorAction SilentlyContinue
                $Disk | Clear-Disk -RemoveData -Confirm:$false -ErrorAction SilentlyContinue
                If ($DiskLayout.Equals('UEFI'))
                {
                    $Disk | Set-Disk -PartitionStyle GPT
                    If ($Disk.Size -gt 32GB)
                    {
                        $Partition = $Disk | New-Partition -Size 32GB
                    }
                    Else
                    {
                        $Partition = $Disk | New-Partition -UseMaximumSize
                    }
                }
                ElseIf ($DiskLayout.Equals('BIOS'))
                {
                    $Disk | Set-Disk -PartitionStyle MBR
                    If ($Disk.Size -gt 32GB)
                    {
                        $Partition = $Disk | New-Partition -Size 32GB -IsActive
                    }
                    Else
                    {
                        $Partition = $Disk | New-Partition -UseMaximumSize -IsActive
                    }
                }
                If ($DiskLabel)
                {
                    [void](Format-Volume -Partition $Partition -FileSystem FAT32 -NewFileSystemLabel $DiskLabel -Force -Confirm:$false)
                }
                Else
                {
                    [void](Format-Volume -Partition $Partition -FileSystem FAT32 -Force -Confirm:$false)
                }
                $Partition | Add-PartitionAccessPath -AssignDriveLetter
                $DiskPath = ($Disk | Get-Partition).AccessPaths[0]
                $Disk = Get-Disk -Number $Disk.Number
                $Volume = $Disk | Get-Partition | Get-Volume
                $USBDrive = $DiskPath.Substring(0, 2)
                If (([IO.FileInfo]$MediaPath).Extension -eq ".ISO")
                {
                    $SourcePath = ([System.IO.Path]::ChangeExtension($MediaPath, ([System.IO.Path]::GetExtension($MediaPath)).ToString().ToLower()))
                    $SourcePath = (Resolve-Path -Path $SourcePath -ErrorAction Stop).ProviderPath
                    Mount-DiskImage -ImagePath $SourcePath -StorageType ISO
                    $SourcePath = ((Get-DiskImage -ImagePath $SourcePath | Get-Volume).DriveLetter) + ':'
                    If (Test-Path -Path "$($SourcePath)\sources\install.wim")
                    {
                        $WimFile = Get-Item -Path "$($SourcePath)\sources\install.wim"
                    }
                    $SourceIsMounted = $true
                }
                Else
                {
                    $SourcePath = (Resolve-Path -Path $MediaPath -ErrorAction Stop).ProviderPath
                    $SourcePath = Get-Item -Path $SourcePath -Force
                    If (Test-Path -Path "$($SourcePath)\sources\install.wim")
                    {
                        $WimFile = Get-Item -Path "$($SourcePath)\sources\install.wim"
                    }
                }
                Push-Location
                Set-Location -Path "$($SourcePath)\boot"
                [void](Invoke-Expression -Command ("bootsect.exe /NT60 $USBDrive"))
                If ($?) { $IsBootable = $true }
                Else { $IsBootable = $false }
                Pop-Location
                If ($WimFile.Length -gt 4GB)
                {
                    Write-Host "Splitting WIM to SWM will take some time. Please be patient..." -NoNewline -ForegroundColor Cyan
                    [void](New-Item -Path $USBDrive -Name "sources" -ItemType Directory -Force)
                    Start-Process -FilePath Dism -ArgumentList "/Split-Image /ImageFile:`"${WimFile}`" /SWMFile:`"${USBDrive}\sources\install.swm`" /FileSize:4096" -NoNewWindow -Wait
                    Write-Host "[Completed]" -ForegroundColor Cyan
                    $FileList = Get-ChildItem -Path $SourcePath -Recurse -Exclude install.wim -Force
                }
                Else
                {
                    $FileList = Get-ChildItem -Path $SourcePath -Recurse -Force
                }
                $FileCount = $FileList.Count
                $Progress = 0
                ForEach ($File In $FileList)
                {
                    $RelativePath = $File.FullName.Replace($SourcePath, $null)
                    $DestinationPath = $USBDrive + $RelativePath
                    Switch ($File.Length)
                    {
                        { $_ -lt 1MB } { $FileSize = ($_ / 1KB).ToString("0.00 KB"); Break }
                        { $_ -lt 1GB } { $FileSize = ($_ / 1MB).ToString("0.00 MB"); Break }
                        { $_ -lt 1TB } { $FileSize = ($_ / 1GB).ToString("0.00 GB"); Break }
                    }
                    Write-Progress -Activity "Copying media from $(Split-Path -Path $MediaPath -Leaf)" -Status "Copying File: $($File.FullName), Size: $FileSize" -PercentComplete (($Progress / $FileCount) * 100)
                    Copy-Item $File.FullName -Destination $DestinationPath -Force
                    $Progress++
                }
                If ($SourceIsMounted -eq $true)
                {
                    Dismount-DiskImage -ImagePath $MediaPath
                }
            }
        }
        End
        {
            If ($Error.Count.Equals(0))
            {
                [PSCustomObject]@{
                    'USB Name'    = $Disk.FriendlyName
                    'Access Path' = $DiskPath
                    'File System' = $Volume.FileSystem
                    'Is Bootable' = $IsBootable.ToString()
                } | Format-List
            }
        }
    }
     
  2. TheStutch

    TheStutch MDL Novice

    Apr 14, 2018
    24
    59
    0
    Can you please design this script to also ingest already unpacked image folders like the unpacked directory produced by your Optimize-Offline tool?
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    378
    410
    10
    #3 GodHand, Oct 7, 2018
    Last edited: Oct 8, 2018
    (OP)
    Yeah I can add that option and post it.

    *EDIT*

    Done.