Originally posted . and last modified at .

Every now and then I have $reasons to install Windows 10 to one or more workstations, quite often reason is that many games just aren’t available for Linux. This was one of such days. As I only have Linux workstations (without DVD drives) I had to create installer USB from ISO image provided by Microsoft.

I tried couple methods:

  • a) dd if=windows.iso of=/dev/myusbstick and
  • b) creating FAT32 partition & copying disk contents there. (FAT32 because it’s the requirement for UEFI booting.)

Both of these failed to produce working USB installer, and after some debugging I found out, that sources/install.wim is more than 4 gigabytes in size, and maximum filesize limitation on FAT32 filesystems happens to be 4 gigabytes.

So I had to do some research (read: googling), and then I was able to find blog post by Microsoft describing various methods for creating UEFI USB installer using Windows. But, as mentioned before, I didn’t have working Windows installation available … thus methods described in the blog post weren’t helping me. But this seemed to be something that could work, only if I was able to reproduce the results on Linux.

Some more research (googling), and I found some Linux tools for handling .wim files. And those were just apt-get install away, pure win:

apt-get install wimtools

Especially interesting was tool called wimlib-imagex, which is able to do same tricks as dism in Windows. And I already had that Microsoft’s blog post describing how to split files larger than 4GB. I decided to proceed my test, and split sources/install.wim to parts with maximum size of 250 megabytes. (This was just arbitrary size I picked, eg. 1024 (ie. 1GB) would have worked as well.)

wimlib-imagex split install.wim install.swm 250

And to make life easier for everyone, including future me, here’s full script to unpack ISO image, split that one huge file, and then format USB device and finally copy files to get working Windows 10 installer.

Script to create Windows 10 installer on Linux

# Path to source ISO image.
# <https://www.microsoft.com/software-download/windows10ISO>
export ISOFILE="Win10_1809Oct_EnglishInternational_x64.iso"

# Path to the device that points to your USB stick
# Your modified installer will be written here, "target".
export USBDEVICE="/dev/sdX"

# Temporary directory we create for manipulating the image contents
export TEMPDIR="$(mktemp -d winimage-XXXXXX)"

# Our mountpoint for the ISO image
export SOURCE="source"

# Our mountpoint for the target device
export TARGET="target"

# Install necessary tools
# - parted: for creating disk partitions
# - rsync: for copying files around efficiently
# - wimtools: for manipulating Windows installer contents
apt-get install parted rsync wimtools

# Directory for mounting the ISO image
mkdir "${SOURCE}"

# Directory for mounting the USB stick
mkdir "${TARGET}"

# Create GPT partition table, one FAT32 partition that uses 100% of available space
# first create msdos table to ensure that existing table is purged
parted --script "${USBDEVICE}" mklabel msdos
parted --script "${USBDEVICE}" mklabel gpt
parted --script "${USBDEVICE}" mkpart primary fat32 1 100%
parted --script "${USBDEVICE}" set 1 msftdata on

# Just to make sure that partition is correctly formatted as FAT32
mkfs.vfat "${USBDEVICE}1"

# Mount Windows ISO image for file copying
mount -oloop "${ISOFILE}" "${SOURCE}"

# Mount USB stick, partition 1
mount "${USBDEVICE}1" "${TARGET}"

# Copy all files from ISO image to temporary directory
rsync -avh --no-o --no-g "${SOURCE}/" "${TEMPDIR}/"

# Split the install.wim file to smaller parts (max 250MB), to temporary directory
wimlib-imagex split \
    "${TEMPDIR}/sources/install.wim" \
    "${TEMPDIR}/sources/install.swm" \

# Finally, copy resulting data structure, without large file (install.wim) to
# the USB stick
rsync -avh --no-o --no-g --exclude="install.wim" "${TEMPDIR}/" "${TARGET}/"

# Ensure that everything has been written to disk

# Unmount, your stick is ready now
umount "${SOURCE}"
umount "${TARGET}"
rm -rf "${TEMPDIR}"

Revision history

  • 2020-11-06 post was completely rewritten to use better language

Feedback / comments?

Ping me (@ypcs) on Twitter