How to create UEFI bootable USB media to install Windows Server 2016

Since Windows Server 2016 has been released, I grabbed the RTM ISO file and wanted to install a new server using a USB stick.

I configured the BIOS settings of the server to only boot UEFI and disabled the legacy boot.

Some of the key points to bear in mind:

  • A USB stick with more than 5.3GB is required
  • UEFI requires a FAT32 partition
  • FAT32 has some serious limitations and the size of the install.wim file exceeds those limits. This file requires therefore to be split into multiple more suitable parts

More on this here:

Here’s how I created my USB boot media compatible with UEFI using PowerShell:

# minimum size of USB stick 5.29GB
# Set here the path of your ISO file
$iso = 'C:\Users\localuser\Downloads\en_windows_server_2016_x64_dvd_9327751.iso'
# Clean ! will clear any plugged-in USB stick!!
Get-Disk | Where BusType -eq 'USB' |
Clear-Disk -RemoveData -Confirm:$true -PassThru
# Convert GPT
if ((Get-Disk | Where BusType -eq 'USB').PartitionStyle -eq 'RAW') {
Get-Disk | Where BusType -eq 'USB' |
Initialize-Disk -PartitionStyle GPT
} else {
Get-Disk | Where BusType -eq 'USB' |
Set-Disk -PartitionStyle GPT
# Create partition primary and format to FAT32
$volume = Get-Disk | Where BusType -eq 'USB' |
New-Partition -UseMaximumSize -AssignDriveLetter |
Format-Volume -FileSystem FAT32
if (Test-Path -Path "$($volume.DriveLetter):\") {
# Mount iso
$miso = Mount-DiskImage -ImagePath $iso -StorageType ISO -PassThru
# Driver letter
$dl = ($miso | Get-Volume).DriveLetter
if (Test-Path -Path "$($dl):\sources\install.wim") {
# Copy ISO content to USB except install.wim
& (Get-Command "$($env:systemroot)\system32\robocopy.exe") @(
# Split install.wim
& (Get-Command "$($env:systemroot)\system32\dism.exe") @(
# Eject USB
(New-Object -comObject Shell.Application).NameSpace(17).
# Dismount ISO
Dismount-DiskImage -ImagePath $iso

Inside the Nuget bootstraping process

A few days ago the PowerShell Team announced on their blog that PowerShellGet has been open-sourced. Both PowerShellGet and PackageManagement modules are now available on the PowerShell Gallery to more easily consume/update these modules and on github to contribute to these projects:

The article says

PowerShellGet has a dependency on PackageManagement.

That’s true.
I’d add that you cannot use the Find-Module cmdlet from the PowerShellGet module before the PackagementManagement module installed the Nuget provider.

Do I have Nuget listed as a provider?

# You can either do
# and see if Nuget is in the list
# or
Get-PackageProvider | Where Name -eq 'NuGet'


But, as soon as you do

Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue


…or if you use the Find-Module cmdlet, you’ll be asked to complete the Nuget provider installation.

Find-Module -Name Pester -Repository PSGallery


Did you notice that the messages don’t say exactly the same thing and that one proposes to install a minimum version and the other
The Get-PackageProvider from the PackageManagement module seems more accurate than the Find-Module cmdlet from the PowerShellGet module.
Well, because the PowerShellGet module hardcodes the version

…whereas the PackageManagement module built the version on the fly:

After opening the PSModule.psm1 file located in C:\Program Files\WindowsPowerShell\Modules\PowerShellGet\, I can notice that many functions inside that module call either:

# the BootstrapNuGetExe switch 
# used by the Publish-Module function
Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet `


Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet

The reason is explained on this page: What’s the difference between nuget-anycpu.exe, Microsoft.PackageManagement.NuGetProvider.dll and nuget.exe?

nuget.exe is used by PowerShellGet to publish packages
Microsoft.PackageManagement.NuGetProvider.dll is used by OneGet and PowerShellGet to discover and install packages.
Only the Publish-Module cmdlet of PowerShellGet will require nuget.exe.

So far, we’ve seen that there are two components that can be downloaded and installed.
According to the content of the PSModule.psm1 file, Nuget.exe is downloaded by the PowerShellGet module and uses the following URL to get it:

# go fwlink for ''
$script:NuGetClientSourceURL = 

Where does Microsoft.PackageManagement.NuGetProvider.dll come from?

Microsoft proposes in their recent blog post to do the following to install the Nuget provider:

Install-PackageProvider -Name Nuget –Force –Verbose

The same piece of information is also mentioned in the FAQ of the PackagementManagement module on github: How do I install a package provider such as NuGet provider if I do not have Internet connection on my box?

Again inside the the PSModule.psm1 file (the PowerShellGet module), its internal Install-NuGetClientBinaries function actually launches the following to install the Nuget provider:

PackageManagement\Install-PackageProvider -Name 'Nuget' `
-MinimumVersion ([Version]'')  `
-Scope $scope -Force


Instead of the above command, I propose to do directly the following and add the Debug switch to uncover what happens behind the scene:

Get-PackageProvider -Name NuGet `
-ErrorAction SilentlyContinue -Verbose -Debug

The following hardcoded URL is immediately used:


Let’s examine this URL and set the MaximumRedirection to 0 to avoid any redirection.

$HT = @{
 Uri = ''
 MaximumRedirection = 0
 ErrorAction = 'SilentlyContinue'
$req = Invoke-WebRequest @HT
# the page has moved
# to this location

We can see the page is redirected to another URL:

The file downloaded from is a swidtag file (Software Identiy Tag) written in XML:

Now, to get the latest version of the Microsoft.PackageManagement.NuGetProvider.dll file, I need to read the content of the providers.masterList.feed.swidtag XML file:

# set a variable
$MasterSwidTagURI = ''

# Get the link of the Nuget provider
(Invoke-WebRequest -Uri $MasterSwidTagURI -MaximumRedirection 0 -ErrorAction SilentlyContinue).Content
))).SoftwareIdentity.Link | Where {
    $_.rel -eq 'package' -and
    $_.latest -eq 'true' -and
    $ -eq 'nuget'
} | Select -expand href

# or more simply using the Invoke-RestMethod cmdlet
(Invoke-RestMethod $MasterSwidTagURI).SoftwareIdentity.Link | 
Where {
    $_.rel -eq 'package' -and
    $_.latest -eq 'true' -and
    $ -eq 'nuget'
} | Select -expand href


To get the latest version of the dll, there’s a specific swidtag file to read from the following URL:

Let’s examine the content of this new XML file:

$dllURI = ''
# Get the version
(Invoke-RestMethod -Uri $dllURI).SoftwareIdentity.version
# Get the source (dll download location)
(Invoke-RestMethod -Uri $dllURI).SoftwareIdentity.Link.href
# New info about the file to download
(Invoke-RestMethod -Uri $dllURI).SoftwareIdentity.Payload.File


Now, we know how and from where the file is being downloaded.

As of version, there’s a hash being used by the Install-PackageProvider cmdlet to check the integrity of the downloaded Microsoft.PackageManagement.NuGetProvider.dll file.
If the Install-PackageProvider is invoked with the Debug switch, I can see at the end of the debug stream the following line: DEBUG: 00:02:07.7019092 BoostrapRequest::ValidateFileHash

The above hash that appears inside the swidtag file doesn’t look standard.

Here’s how it can be verified:

# Download the dll
Invoke-WebRequest -Uri $((Invoke-RestMethod -Uri $dllURI).SoftwareIdentity.Link.href) -OutFile ~/downloads/$((Invoke-RestMethod -Uri $dllURI).SoftwareIdentity.Payload.File.Name) -Verbose

# Get its SHA512 hash
(Get-FileHash ~/downloads/Microsoft.PackageManagement.NuGetProvider.dll -Algorithm SHA512 | Select -Expand Hash).ToLower()

# Check it matches the one specified in the swidtag file
   (Invoke-RestMethod -Uri '').SoftwareIdentity.Payload.File.hash
 ) -replace '-',''