Win10+ Setup Disk (Works with UEFI Secure Boot / BIOS / Install.wim over 4 GB)

Discussion in 'MDL Projects and Applications' started by rpo, Mar 18, 2019.

  1. rpo

    rpo MDL Expert

    Jan 3, 2010
    1,502
    1,504
    60
  2. freddie-o

    freddie-o MDL Expert

    Jul 29, 2009
    1,603
    2,673
    60
    #442 freddie-o, Mar 23, 2025
    Last edited: Mar 23, 2025
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. Carlos Detweiller

    Carlos Detweiller Emperor of Ice-Cream
    Staff Member

    Dec 21, 2012
    7,011
    8,202
    240
    I guess one progress bar for the current file and the second one for the totals.
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  4. rpo

    rpo MDL Expert

    Jan 3, 2010
    1,502
    1,504
    60
    #444 rpo, Mar 23, 2025
    Last edited: Mar 24, 2025
    (OP)
    As @Carlos Detweiller said, the upper bar is for the global stats and the lower for the current file.
    The same form is used do display the screens.
    The -Window parameter on the powershell command line can be changed.
    The bar for the current file is not significant for small files because the bar are updated after 4Mbytes are processed.
    Here is my script :
    Code:
    <# : standard way of doing hybrid batch + powershell scripts
    @title Win10+ Setup Disk 3.4 &color 3E
    @powershell -noprofile -Window max -c "$param='%*';$ScriptPath='%~f0';iex((Get-Content('%~f0') -Raw))"&exit/b
    #>
    $OnlyUSBsticks="NO"
    #
    $host.ui.rawui.windowtitle = ($title=$host.ui.rawui.windowtitle -replace "^.*  ")
    #
    #
    #   Homepage: https://forums.mydigitallife.net/threads/win10-setup-disk-works-with-uefi-secure-boot-bios-install-wim-over-4-gb.79268/
    #   Credits: @rpo, @freddie-o, @BAU & @abbodi1406
    #
    Add-Type -AssemblyName Microsoft.VisualBasic
    Function MsgBox {
    # learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/msgbox-function
    [Microsoft.VisualBasic.Interaction]::MsgBox(
        $Args[0],"$($Args[1]),$($Args[2]),SystemModal",$title)}
    #
    #   This script must be executed with admin privilege
    #
    #   Test Administrator privileges
    If (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
    {
        $uac_error="Elevating UAC for Administrator Privileges failed"
        #   The following test is to avoid infinite looping if elevating UAC for Administrator Privileges failed
        If($param -eq $uac_error){
            $msg="$uac_error.`r`nRight click on the script and select ""Run as administrator""."
            MsgBox $msg    "OKOnly" "Critical" >$Null;exit}
    #   Restart the script to get Administrator privileges and exit
        Start-Process "$ScriptPath" $uac_error -Verb runAs; exit
    }
    #
    Add-Type -AssemblyName System.Windows.Forms # Load the class System.Windows.Forms
    #
    # Filebrowser dialog object
    $FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property @{
        Multiselect = $false                 # Only one file can be chosen
        Filter = 'ISO images (*.iso)|*.iso' # Select only iso files
    }
    #
    Function Update_bcd ($partition){
    #    Set bootmenupolicy Legacy for $partition
    bcdedit /store "$partition\boot\bcd" /set '{default}' bootmenupolicy Legacy >$Null
    bcdedit /store "$partition\EFI\Microsoft\boot\bcd" /set '{default}' bootmenupolicy Legacy >$Null
    remove-item "$partition\boot\bcd.*" -force
    remove-item "$partition\EFI\Microsoft\boot\bcd.*" -force
    }
    # **************************************************
    $msg="Would you like to show only USB sticks and hide external hard disks?"
    $filter="Status='OK' and (InterfaceType='usb'and MediaType='Removable Media')"
    # YES : show only usb sticks
    # NO  : show usb sticks and external hard disks
    $OnlyUSBsticks=MsgBox $msg "YESNO" "Question"
    If($OnlyUSBsticks -eq "NO"){$filter="$filter or MediaType='External hard disk media'"}
    # **************************************************
    $delay=15
    While(!($Disks=Get-CimInstance -ClassName Win32_DiskDrive -Filter $filter)){
        $msg="No USB sticks were detected. Plug in your USB stick first and retry after $delay seconds or Cancel to exit."
        If((MsgBox $msg    "RetryCancel" "Question") -eq "Cancel"){exit}
        Start-Sleep $delay >$Null}
    #
    #winform dimensions
    $height=270
    $width=420
    $Form=New-Object System.Windows.Forms.Form # Create the screen form (window)
    $Form.TopMost = $True
    $Form.ShowIcon=$False
    $Form.ControlBox=$False
    # Set the title and size of the window:
    $Form.Text="$Title"
    $Form.Font='Consolas,10'
    $Form.Width=$width ; $Form.Height=$height
    $Form.StartPosition = "CenterScreen"
    $Form.KeyPreview = $True
    $Form.Add_KeyDown({if ($_.KeyCode -eq "Enter"){$OKButton.PerformClick()}})         # Enter = click OK button
    $Form.Add_KeyDown({if ($_.KeyCode -eq "Escape"){$CancelButton.PerformClick()}})    # Escape = click on Cancel button
    $Form.SizeGripStyle = "Hide"
    $form.ShowInTaskbar = $False
    $form.MaximizeBox = $False
    $form.MinimizeBox = $False
    [System.Windows.Forms.Application]::EnableVisualStyles();
    
    # Add a Source Windows ISO label on the form
    $SourceISO=New-Object System.Windows.Forms.Label
    $SourceISO.Location=New-Object System.Drawing.Point(20,30)
    $SourceISO.Size=New-Object System.Drawing.Size(200,20)
    $SourceISO.text="Source Windows ISO"
    $Form.Controls.Add($SourceISO)
    #
    # Add the ISO file name on the form
    $ISOFile=New-Object System.Windows.Forms.Label
    $ISOFile.Location=New-Object System.Drawing.Point(20,50)
    $ISOFile.Text=" "
    $ISOFile.Size=New-Object System.Drawing.Size(364,24)
    $ISOFile.Backcolor = "White"
    $ISOFile.BorderStyle = "FixedSingle"
    $Form.Controls.Add($ISOFile)
    #
    # Add a Target USB Disk label on the form
    $TargetUSB=New-Object System.Windows.Forms.Label
    $TargetUSB.Location=New-Object System.Drawing.Point(20,100)
    $TargetUSB.Text="Target USB Disk"
    $TargetUSB.Size=New-Object System.Drawing.Size(200,20)
    $Form.Controls.Add($TargetUSB)
    #
    # Create a USB Disk drop-down list and fill it
    $USBDiskList=New-Object System.Windows.Forms.ComboBox
    $USBDiskList.DropDownStyle=[System.Windows.Forms.ComboBoxStyle]::DropDownList
    $USBDiskList.Location=New-Object System.Drawing.Point(20,120)
    $USBDiskList.Size=New-Object System.Drawing.Size(363,22)
    $Form.Controls.Add($SelectUSBDiskList)
    #
    # Add a Select ISO button on the form
    $SelectISOButton=New-Object System.Windows.Forms.Button
    $SelectISOButton.Location=New-Object System.Drawing.Point(20,185)
    $SelectISOButton.Text="Select ISO"
    $SelectISOButton.Size=New-Object System.Drawing.Size(110,26)
    $Form.Controls.Add($SelectISOButton)
    #
    # Add a Create Setup Disk button on the form
    $CreateDiskButton=New-Object System.Windows.Forms.Button
    $CreateDiskButton.Location=New-Object System.Drawing.Point(140,185)
    $CreateDiskButton.Text="Create Setup Disk"
    $CreateDiskButton.Size=New-Object System.Drawing.Size(155,26)
    $Form.Controls.Add($CreateDiskButton)
    $CreateDiskButton.Enabled = $false
    $CreateDiskButton.DialogResult = "OK"
    #
    # Add a Cancel button on the form
    $CancelButton=New-Object System.Windows.Forms.Button
    $CancelButton.Location=New-Object System.Drawing.Point(305,185)
    $CancelButton.Text="Cancel"
    $CancelButton.Size=New-Object System.Drawing.Size(80,26)
    $Form.Controls.Add($CancelButton)
    $CancelButton.DialogResult = "Cancel"
    #
    $SelectISOButton.Add_Click({
    If ($FileBrowser.ShowDialog() -ne "Cancel"){ # if Cancel, just ignore
        $Global:ImagePath=$FileBrowser.filename    # return the file name
        $ISOFile.Text=Split-Path -Path $ImagePath -leaf # extract filename and extension (iso)
        if(($ISOFile.Text).length -gt 44){
            $ISOFile.Text=$ImagePath.PadRight(100).substring(0,43)+"..."}
        $CreateDiskButton.Enabled = $true
        $CreateDiskButton.Focus()}})
    #
    $USBDisks=@() # array with USB disk number
    Foreach ($Disk in $Disks){
        $FriendlyName=($Disk.Caption).PadRight(40).substring(0,35)
        $USBDisks+=$Disk.Index
        $USBDiskList.Items.Add(("{0,-30}{1,10:n2} GB" -f $FriendlyName,($Disk.Size/1GB))) >$Null
    <#
        Foreach ($Partition in (Get-Partition $Disk.Index)) {
            If(!$Partition.DriveLetter){$AccessPath="  "}Else{$AccessPath=$Partition.DriveLetter+":"}
            $USBDisks+=$Disk.Index
            $USBDiskList.Items.Add((" ({0,2}){1,-30}{2,10:n2} GB" -f $AccessPath,($Partition|
                Get-Volume).FileSystemLabel.PadRight(25).substring(0,25),($Partition.Size/1GB))) >$Null
        }
    #>
    }
    $form.Controls.Add($USBDiskList)
    $USBDiskList.SelectedIndex=0
    #
    $Groupbox=New-Object System.Windows.Forms.Groupbox # Add a group box on the form
    $Groupbox.Location=New-Object System.Drawing.Point(7,0)
    $Groupbox.Text=""
    $Groupbox.Size=New-Object System.Drawing.Size(390,225)
    $Form.Controls.Add($Groupbox)
    #
    # create label
    $label1 = New-Object system.Windows.Forms.Label
    $label1.Left=5
    $label1.Top= 10
    $label1.Width= $width - 20
    #adjusted height to accommodate progress 
    $label1.Height=150
    $label1.Font= "Verdana"
    #optional to show border
    #$label1.BorderStyle=1
    $msg=@"
    1.) Click "Select ISO" to select your source Windows ISO
    
    2.) Select your "Target USB disk" from the drop-down menu
    
    3.) Click "Create Setup Disk" to start creating your Windows setup disk
    "@
    MsgBox "$msg" "OKOnly" "Information" >$Null
    #
    if($form.ShowDialog() -eq "Cancel") {exit}
    #
    $Form.Controls.Remove($SourceISO)
    $Form.Controls.Remove($ISOFile)
    $Form.Controls.Remove($TargetUSB)
    $Form.Controls.Remove($SelectUSBDiskList)
    $Form.Controls.Remove($SelectISOButton)
    $Form.Controls.Remove($CreateDiskButton)
    $Form.Controls.Remove($CancelButton)
    $Form.Controls.Remove($USBDiskList)
    $Form.Controls.Remove($Groupbox)
    #
    # At this point the mounted USB disk and the iso image file path are defined
    #
    $USB=$USBDisks[$USBDiskList.SelectedIndex]
    #
    $msg=@"
    WARNING!`r`n
    Your USB disk will be converted to MBR scheme, repartitioned and reformatted.`r`n
    All partitions and data currently stored in the USB disk will be erased.`r`n
    Are you sure you want to continue?
    "@
    If((MsgBox $msg "YESNO" "Exclamation") -eq "NO"){exit}
    Clear-Host
    
    #add the label to the form
    $form.controls.add($label1)
    #
    #"Mounting and checking to see if the ISO image was mounted"
    #   Check if iso already mounted by getting driveletter
    If($ISO=(Get-DiskImage $ImagePath|Get-Volume).DriveLetter){$Mounted=$True}Else
    #    Mount iso and get drive letter
        {$Mounted=$False;If(!($ISO=(Mount-DiskImage $ImagePath|Get-Volume).DriveLetter)){Exit}}
    $ISO=$ISO+":"
    #
    Stop-Service ShellHWDetection -ErrorAction SilentlyContinue >$Null
    $ProgressPreference="SilentlyContinue"
    $label1.Text = "Cleaning the USB disk and converting it to MBR partition scheme"
    $form.Show()
    #
    "Select disk $USB`nclean`nconvert MBR`nexit"|diskpart >$Null
    If($LASTEXITCODE -ne 0){
        $msg="Diskpart operations failed with error code $LASTEXITCODE."
        $msg;pause }
    $label1.Text = $label1.Text+"`r`n`r`nCreating the FAT32 boot partition"
    $form.Refresh()
    $usbfat32=(New-Partition -DiskNumber $usb -Size 1GB -AssignDriveLetter|
        Format-Volume -FileSystem FAT32 -NewFileSystemLabel "BOOT").DriveLetter + ":"
    $label1.Text = $label1.Text+"`r`n`r`nCreating the NTFS setup partition & marking partition as active"
    $form.Refresh()
    $usbntfs=(New-Partition -DiskNumber $usb -UseMaximumSize -AssignDriveLetter -IsActive|
        Format-Volume -FileSystem NTFS -NewFileSystemLabel "SETUP").DriveLetter + ":"
    Start-Service ShellHWDetection -erroraction silentlycontinue >$Null
    # *********************************************************************
    $form.controls.Remove($label1)
    # 
    $label1 = New-Object system.Windows.Forms.Label
    $label1.Text = "not started"
    $label1.Left=5
    $label1.Top= 10
    $label1.Width= $width - 20
    $label1.Height=65
    $label1.Font= "Verdana"
    #$label1.BorderStyle=1
    $form.controls.add($label1)
    # 
    $label2 = New-Object system.Windows.Forms.Label
    $label2.Text = "not started"
    $label2.Left=5
    $label2.Top= 130
    $label2.Width= $width - 20
    $label2.Height=65
    $label2.Font= "Verdana"
    #$label2.BorderStyle=1
    $form.controls.add($label2)
    
    $progressBar1 = New-Object System.Windows.Forms.ProgressBar
    $progressBar1.Value = 0
    $progressBar1.Style="Continuous"
    #
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = $width - 30
    $System_Drawing_Size.Height = 20
    $progressBar1.Size = $System_Drawing_Size
    $progressBar1.Left = 5
    $progressBar1.Top = 70
    $form.Controls.Add($progressBar1)
    #
    $progressBar2 = New-Object System.Windows.Forms.ProgressBar
    $progressBar2.Name = 'progressBar2'
    $progressBar2.Value = 0
    $progressBar2.Style="Continuous"
    $System_Drawing_Size = New-Object System.Drawing.Size
    $System_Drawing_Size.Width = $width - 30
    $System_Drawing_Size.Height = 20
    $progressBar2.Size = $System_Drawing_Size
    $progressBar2.Left = 5
    $progressBar2.Top = 200
    $form.Controls.Add($progressBar2)
    $form.Show()| out-null
    $form.Focus() | out-null
    #
    # *****************************************************************************
    #
    Function Copy_With_Progression {
    # suffix 1 : variables for global process
    # suffix 2 : variables for one file process
    [long]$allbytes = ($files | measure -Sum length).Sum
    [long]$total1 = 0 # bytes done
    $index = 0
    $filescount = $files.Count
    $sw1 = [Diagnostics.Stopwatch]::StartNew()
    $Buffer = New-Object Byte[] (4MB) # 4MB buffer
    ForEach ($file in $files) {
        $filefullname = $file.fullname     
        $index++
        # build destination path for this file (for example H:\sources\boot.wim)     
        $destfile = "$Dest_device$((Split-Path $filefullname -NoQualifier))" 
        $destdir= Split-Path $destfile -Parent
        # if it doesn't exist, create it
        if (!(Test-Path $destdir)){New-Item -ItemType Directory "$destdir" -Force >$Null}
        $SourceFile = [io.file]::OpenRead($filefullname)
        $DestinationFile = [io.file]::Create($destfile)
        $Filelength=$File.Length
        $sw2 = [Diagnostics.Stopwatch]::StartNew()
        [long]$total2 = [long]$count = 0
        do {
            # copy 4MB of src file to dst file
            $count = $SourceFile.Read($buffer, 0, $buffer.Length)
            $DestinationFile.Write($buffer, 0, $count)
            # this is just write-progress
            # uses stopwatch to determine how long is left
            $total2 += $count
            $total1 += $count
    #        Statistics for current file   
            if ($Filelength -gt 1){
                $pctcomp2 = $total2 * 100 / $FileLength}else{$pctcomp2 = 100}
            [int]$secselapsed2 = $sw2.Elapsed.TotalSeconds   
            if ($secselapsed2 -ne 0){
               [single]$xferrate = $total2 / $secselapsed2 / 1mb}else{[single]$xferrate = 0.0}
            if ($total2 -gt 0) {       
                [int]$secsleft2=$secselapsed2/$total2*$Filelength -$secselapsed2}else{[int]$secsleft2=0}                           
    #        Global statistics
            $pctcomp1 = $total1 * 100/ $allbytes
            [int]$secselapsed1 = $sw1.Elapsed.TotalSeconds   
            if ($total1 -gt 0) {       
                [int]$secsleft1=$secselapsed1/$total1*$Filelength -$secselapsed1}else{[int]$secsleft1=0}                   
            # Global
            $Form.Text="Copying $destfile"
            $progressBar1.Value = $pctcomp1
            $label1.text="{0,5} of $filescount files  - {1,5} left" -f $index,($filescount - $index) 
            $label1.text="$($label1.text){0,20:0} MB of {1,6:0} MB" -f ($total1/1MB),($allbytes/1MB)
            $label1.text="$($label1.text)`r`n`r`nPercent complete = {0,3:0}%      {1,4} minutes {2,2} seconds remaining" -f $pctcomp1,[math]::Truncate($secsleft1/60),($secsleft1%60)         
            # Current file
            $progressBar2.Value = $pctcomp2
            $label2.text="Copying file @ {0,6:n2} MB/s" -f $xferrate 
            $label2.text="$($label2.text){0,16:0} MB of {1,6:0} MB" -f ($total2/1MB),($filelength/1MB) 
            $label2.text="$($label2.text)`r`n`r`nPercent complete = {0,3:0}%      {1,4} minutes {2,2} seconds remaining" -f $pctcomp2,[math]::Truncate($secsleft2/60),($secsleft2%60) 
           
            $form.Refresh()
        } while ($count -gt 0)
        $sw2.Stop()
        $sw2.Reset()
        $SourceFile.Close()
        $DestinationFile.Close()
    }
    $sw1.Stop()
    $sw1.Reset()
    $Buffer=$Null
    #
    }# End Function Copy_With_Progression
    #
    #"`r`nCopying boot files to the fat32 boot partition $usbfat32"
    $files = Get-ChildItem $iso\boot, $iso\efi, 
        $iso\sources\boot.wim, $iso\bootmgr.*, $iso\bootmgr -Recurse -File -Force
    $Dest_device="$usbfat32"
    Copy_With_Progression   
    #
    Update_BCD $usbfat32
    #"`r`nRemoving the drive letter to hide the fat32 boot partition"
    Get-Volume ($usbfat32 -replace ".$")|Get-Partition|
        Remove-PartitionAccessPath -accesspath $usbfat32
    #
    #"`r`nCopying contents of the ISO to the NTFS setup partition $usbntfs"
    $files = Get-ChildItem $iso -Recurse -File -Force 
    $Dest_device="$usbntfs"
    Copy_With_Progression 
    #
    $form.Close()
    #
    Update_BCD $usbntfs
    bootsect /nt60 $usbntfs /force /mbr >$Null
    # Eject the mounted iso image if it was loaded by the script
    If(!$Mounted){DisMount-DiskImage $ImagePath >$Null}
    MsgBox "$Title was created successfully." "OKOnly" "Information" >$Null
    
     

    Update : my bad, the code is now ok
     
  5. Carlos Detweiller

    Carlos Detweiller Emperor of Ice-Cream
    Staff Member

    Dec 21, 2012
    7,011
    8,202
    240
    #445 Carlos Detweiller, Mar 28, 2025
    Last edited: Mar 29, 2025
    Temporarily closed for maintenance. Will be re-opened very soon.

    Edit: Reopened under new management. :)

    @rpo

    Edit: Oh, and while we are at it, would you like to have this topic moved to the "MDL Projects and Applications" forum? @rpo
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  6. rpo

    rpo MDL Expert

    Jan 3, 2010
    1,502
    1,504
    60
    Why not, it appears that prominent people of the community approve your suggestion.

    PS : caveat concerning the gui draft : the gui may freeze (because Powershells support for multithreading is limited).