Creating a bootable Windows USB installer in Linux

Background

I recently had a case to install Windows Server 2016 on a Dell PowerEdge R630 server. Microsoft make the ISO images available for download on their website, however the Windows Server 2016 ISO clocks in at over 6GB. Actually getting the server to boot and install from this ISO image was surprisingly difficult:

  • It’s too large for a regular DVD-R disc, and I didn’t have any dual-layer discs on hand.
  • The ISO image contains a file (install.wim) that is greater than 4GB, so won’t fit on a FAT32 filesystem as created by tools such as Unetbootin.
  • There is a command in Windows (dism) to split a WIM file but hadn’t come across any Linux tools to date. Update: wimlib can do this and means you can probably do this on a legacy FAT filesystem after all!
  • It’s too large to fit into Dell vFlash which has a image size limit of 4GB (presumably as it uses FAT32 internally).
  • Tools that were known to work such as Rufus and the Microsoft USB imager only run in Windows, which I didn’t have handy.
  • The ISO mounting capability via the server iDRAC console actually worked, but was painfully slow (hours).

So I set about to find a way to make a bootable Windows USB stick when you’ve only got immediate access to a Linux box. This took a few attempts to get right but turns out to be relatively simple. I imagine this will also be perfectly doable on a Mac as well, with a few modified commands.

Process

Here’s the actual process to create a bootable Windows installer on a USB stick from a Windows installer ISO image for a UEFI system. This should work for any Windows version released over the last decade or so. Secure Boot must be disabled for this to work.

WARNING: This will erase any existing data on the USB stick!

You will need:

Plug the USB stick in and run dmesg to see what identifier it gets:

[28959.294103] usb 4-1: new SuperSpeed Gen 1 USB device number 15 using xhci_hcd
[28959.319266] usb 4-1: New USB device found, idVendor=0781, idProduct=5581, bcdDevice= 1.00
[28959.319278] usb 4-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[28959.319282] usb 4-1: Product: Ultra
[28959.319286] usb 4-1: Manufacturer: SanDisk
[28959.319290] usb 4-1: SerialNumber: 1234567890
[28959.321513] usb-storage 4-1:1.0: USB Mass Storage device detected
[28959.322014] scsi host7: usb-storage 4-1:1.0
[28960.347536] scsi 7:0:0:0: Direct-Access     SanDisk  Ultra            1.00 PQ: 0 ANSI: 6
[28960.348477] scsi 7:0:0:0: Attached scsi generic sg2 type 0
[28960.349336] sd 7:0:0:0: [sdb] 60063744 512-byte logical blocks: (30.8 GB/28.6 GiB)
[28960.350654] sd 7:0:0:0: [sdb] Write Protect is off
[28960.350664] sd 7:0:0:0: [sdb] Mode Sense: 43 00 00 00
[28960.351430] sd 7:0:0:0: [sdb] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
[28960.399678]  sdb: sdb1 sdb2
[28960.402932] sd 7:0:0:0: [sdb] Attached SCSI removable disk

In this case the disk shows up as sdb, so the device we’ll use for this exercise is /dev/sdb. Yours may vary depending on how many disks you have connected to the system so you will need to modify the remaining commands accordingly.

The first step is to create a new GPT partition table:

sudo parted /dev/sdb mklabel gpt

Create an exFAT partition for the Windows ISO contents. We’ll set the partition type as NTFS; that is correct for exFAT. For the sake of simplicity I suggest using 0% and 99% as the boundaries as the exFAT driver filesystem image is only 512k. It’s a quick hack that saves calculating partition sizes and also helps ensure that parted correctly block aligns the partitions.

We’ll also need to enable the msftdata flag on the partition and then format it with an exFAT filesystem:

sudo parted /dev/sdb mkpart winsetup ntfs 0% 99%
sudo parted /dev/sdb set 1 msftdata on
sudo mkfs.exfat -n winsetup /dev/sdb1

Mount the Windows ISO and the exFAT USB stick partition. I suggest making some temporary working directory for these:

mkdir -p /tmp/{iso,usb}
sudo mount ~/Downloads/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.iso /tmp/iso -o loop,ro
sudo mount /dev/sdb1 /tmp/usb
cp -r /tmp/iso/* /tmp/usb/

Now create a partition for the exFAT/NTFS driver, and set the msftdata flag on this partition as well:

sudo parted /dev/sdb mkpart uefi-ntfs 99% 100%
sudo parted /dev/sdb set 2 msftdata on

We don’t need to format this one, we’ll just copy the Rufus driver image directly to it:

sudo dd if=~/Downloads/uefi-ntfs.img of=/dev/sdb2 bs=512k count=1

The last step is to clean up. Unmount the partitions and remove the temporary mount point directories:

sudo umount /tmp/{iso,usb} && sudo rmdir /tmp/{iso,usb}

From here you can remove the USB stick and use it to deploy Windows!

Another reason I try to avoid Windows

I decided to replace the hard disk in my Dell Optiplex 755 with a larger model (500GB). Migrating Ubuntu was trivial; Windows Vista was not. Clonezilla did its best but I ended up running into all sorts of Windows BCD corruption issues. Most of the various repair tools on the Windows DVD failed with incredibly non-descriptive error messages.

After giving up I quickly discovered that Windows would refuse to even install, citing Windows is unable to find a system volume that meets its criteria for installing. This was even after zeroising the first few GB of the disk with dd. WTF Microsoft?

Even a BIOS update didn’t do the trick. (hint to Dell, et al.: DOS is dead. Please give us a modern, OS independent BIOS update mechanism)

It turns out that two things seemed to have an affect on this: my HP Photosmart printer with its built-in card reader and floppy drive setting in the BIOS. Disconnecting the printer and setting the floppy drive mode from USB (default if there is no internal floppy drive) to None cured the problem.

By this stage I had wasted enough hours so didn’t even bother trying to see if the clone would work this time around.

Of course all along Ubuntu behaves just perfectly.

Grep for Windows

Ever since using Ubuntu as my primary OS I’ve missed many of the command line tools when using Windows. However I just discovered Windows has it’s own version of the grep command called findstr.

I’m probably a bit behind the eight ball here but despite it’s awful name, mangled switches and  other Microsoftisms findstr seems to behave in quite a similar fashion to good old grep, enough for basic commands at least.