Create a bootable USB drive

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

  1. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    400
    444
    10
    #1 GodHand, Oct 2, 2018
    Last edited: Nov 13, 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:
    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
    41
    68
    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
    400
    444
    10
    #3 GodHand, Oct 7, 2018
    Last edited: Oct 8, 2018
    (OP)
    Yeah I can add that option and post it.

    *EDIT*

    Done.
     
  4. TheStutch

    TheStutch MDL Novice

    Apr 14, 2018
    41
    68
    0
    #4 TheStutch, Nov 13, 2018
    Last edited: Nov 13, 2018
    I haven't had any success using this tool at all. I know that doesn't provide you with a deep degree of troubleshooting insight, but basically it just doesn't work.
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  5. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    400
    444
    10
    Right :rolleyes:
     
  6. TheStutch

    TheStutch MDL Novice

    Apr 14, 2018
    41
    68
    0
    Thanks, I'm not the only one having issues, but whatever ...
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  7. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    400
    444
    10
    [​IMG]

    [​IMG]

    Works fine with both ISO files and expanded ISO directories. The problem on your end is, as usual, between the keyboard and seat; not with the function.
     
  8. TheStutch

    TheStutch MDL Novice

    Apr 14, 2018
    41
    68
    0
    #8 TheStutch, Nov 13, 2018
    Last edited: Nov 13, 2018
    Thanks for posting the clarifying photos. That does actually help!
    Your absolutely correct about where the problem lies. I wasn't getting the results you've shown and gave up.
    In any event my, my 'whining' was rewarded here https://forums.mydigitallife.net/th...rprise-n-ltsc-2019.76325/page-98#post-1481070 with a PowerShell GUI script that freed me from having to use the confusing keyboard and let me do nearly everything I wanted by pushing buttons on the less confusing mouse.
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  9. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    400
    444
    10
    #9 GodHand, Nov 13, 2018
    Last edited: Nov 13, 2018
    (OP)
    What does creating an ISO have to do with this function whatsoever?

    As a sidenote, that ISO Creator has no boot image stream, so any ISO file made with it will not be bootable.
     
  10. TheStutch

    TheStutch MDL Novice

    Apr 14, 2018
    41
    68
    0
    #10 TheStutch, Nov 14, 2018
    Last edited: Nov 14, 2018
    I figured out my mistake and got your script to work. Thanks for sharing it and for improving it to work with fully expanded ISO media! Create a bootable USB drive.jpg
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  11. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    400
    444
    10
    Always best to consider a parameter or value is incorrect before dismissing an entire function/project/whatever and telling others it does not work when it does. I do not write, and especially would not post, functions, scripts, projects, or the like that I am not complete certain are functional unless it's for a debugging reason (which I would not do on this forum) or to theorycraft with others.