windows2usb/windows2usb

387 lines
12 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e # errexit
set -f # noglob
set +o braceexpand # disable braceexpand
shopt -s nocasematch # for "if labeltype"
export LANG=C.utf8
header='
| __ _____ _ _ ____ _ _ ____ ____ |
| \ \ / /_ _| \ | | |___ \ | | | / ___|| __ ) |
| \ \ /\ / / | || \| | __) | | | | \___ \| _ \ |
| \ V V / | || |\ | / __/ | |_| |___) | |_) | |
| \_/\_/ |___|_| \_(_) |_____| \___/|____/|____/ |
========================================================
. Windows 7+ ISO to Flash Drive burning utility .'
bold="$(tput bold)"
normal="$(tput sgr0)"
blink="$(tput blink)"
red="$(tput setaf 1)"
smso="$(tput smso)"
rmso="$(tput rmso)"
scriptpath="$(readlink -f -- "$0")"
dirpath="$(dirname "$scriptpath")"
function check_requirements() {
local reqs=(awk lsblk 7z mkfs.vfat mkfs.ntfs sfdisk ms-sys mktemp wimsplit tr)
for req in ${reqs[*]}; do
if ! command -v "$req" > /dev/null;
then
echo "${bold} == ERROR: $req is required but not found ==${normal}"
exit 104
fi
done
}
function check_iso_and_device() {
if [ ! -f "$1" ]
then
echo "${bold} == ERROR: ISO file not found! ==${normal}"
exit 106
fi
if [ ! -b "$2" ]
then
echo "${bold} == ERROR: Device file not found! ==${normal}"
exit 107
fi
}
function list_removable_drives() {
lsblk=$(lsblk -d -I 8 -o RM,NAME,SIZE,MODEL | \
awk '($1 == 1) {print "/dev/" substr($0, index($0, $2))}')
if [[ "$lsblk" ]]; then
echo "$lsblk"
else
echo "No removable storage found!"
fi
}
function format_drive() {
if [[ "$1" == "dos" ]] || [[ "$1" == "gpt" ]];
then
echo "label: $1" | sfdisk "$2"
sleep 3
else
echo "${bold} == format_drive: INTERNAL ERROR ==${normal}"
exit 101
fi
}
function get_iso_name() {
7z l "$1" | awk '/Comment = / {print $3; exit 0}'
}
function sanitize_iso_name() {
# FAT32 does not allow certain characters in names, see
# https://github.com/dosfstools/dosfstools/blob/1da41f0e70dcbfb874e8191c639b91fa58205ea4/src/fatlabel.c#L96
echo "$1" | tr -d '*?.,;:/\\|+=<>[]"'
}
function check_installwim_gt4gib() {
7z l "$1" | awk 'BEGIN {doend=1} ($6 ~ /install.wim/) {if ($4 > 4294967296) {doend=0; exit 0}; doend=0; exit 1} END {if (doend) {exit 1}}'
}
function create_partitions() {
local mbrscript="- - 7 *"
local gptscript="- - EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 *"
local gptuefintfsscript="- 1MiB U *\n- - EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 *"
if [[ "$1" == "dos" ]] || [[ "$1" == "gpt" ]] || [[ "$1" == "gptntfs" ]] \
|| [[ "$1" == "gpt+uefintfs" ]];
then
if [[ "$1" == "dos" ]];
then
echo -e "$mbrscript" | sfdisk "$2"
elif [[ "$1" == "gpt" ]];
then
echo -e "$gptscript" | sfdisk "$2"
elif [[ "$1" == "gptntfs" ]];
then
echo -e "$gptscript" | sfdisk "$2"
elif [[ "$1" == "gpt+uefintfs" ]];
then
echo -e "$gptuefintfsscript" | sfdisk "$2"
fi
else
echo "${bold} == create_partitions: INTERNAL ERROR ==${normal}"
exit 102
fi
# Wait 1 second to settle new partition table
# See bug #24
sleep 1
}
function get_dev_partition_num() {
# $1 - device
# $2 - partition number
if [ -b "${1}${2}" ];
then
echo "${1}${2}"
return
fi
if [ -b "${1}p${2}" ];
then
echo "${1}p${2}"
return
fi
echo "${bold} == get_dev_partition_num: INTERNAL ERROR ==${normal}"
exit 105
}
function write_uefintfs() {
if [ -e "$dirpath/uefi-ntfs.img" ]; then
cat "$dirpath/uefi-ntfs.img" > "$1"
elif [ -e "/usr/share/windows2usb/uefi-ntfs.img" ]; then
cat "/usr/share/windows2usb/uefi-ntfs.img" > "$1"
fi
}
function extract_iso() {
# $1 - isopath
# $2 - destdir
# $3 - exclude install.wim extraction flag
if [[ "$3" == "skipinstallwim" ]]; then
7z x "$1" -o"$2" '-x!sources/install.wim'
else
7z x "$1" -o"$2"
fi
}
function split_wim() {
# $1 - isopath
# $2 - isomountpath
# $3 - destdir
mount -o ro "$1" "$2"
wimsplit "$2/sources/install.wim" "$3/sources/install.swm" 3800
umount "$2"
}
function extract_bootmgfw_from_installwim() {
# $1 - isopath
# $2 - isomountpath
# $3 - destdir
mount -o ro "$1" "$2"
local fpath="$2/sources/install.wim"
[ ! -e "$fpath" ] && fpath="$2/sources/install.esd"
7z e "$fpath" -aoa -o"$3/efi/boot/" 'Windows/Boot/EFI/bootmgfw.efi' '?/Windows/Boot/EFI/bootmgfw.efi'
umount "$2"
}
function umount_rm_path() {
if [ -d "$1" ]; then
umount "$1" || true
rm -r "$1"
fi
if [ -d "$2" ]; then
umount "$2" 2>/dev/null || true
rm -r "$2"
fi
}
function sigint_handler() {
umount_rm_path "$partpath" "$isomountpath"
}
if [[ ! "$2" ]];
then
echo "${bold}${smso}${header}${rmso}"
echo "${blink}${red}WARNING: this program will delete all existing data on your drive!${normal}"
echo
echo "$(basename "$0")" "<device> <windows iso> [mbr/gpt/gptntfs/gpt+uefintfs]"
echo
echo "mbr mode: the most universal, RECOMMENDED and DEFAULT method."
echo " This mode creates MBR partition table with FAT32 partition,"
echo " installs BIOS and UEFI bootloaders, supports Secure Boot."
echo " install.wim file larger than 4 GiB will be split."
echo " Suitable for all computers (UEFI/CSM/BIOS)."
echo
echo "gpt mode: less universal mode, for modern (UEFI) computers."
echo " GPT+FAT32, UEFI only, supports Secure Boot."
echo
echo "gptntfs mode: all the same as 'gpt' but NTFS is used."
echo " GPT+NTFS, UEFI only, supports Secure Boot."
echo " Large install.wim file will not be split."
echo " NOTE: not all UEFI are compatible with this mode,"
echo " NTFS driver should be present on the motherboard."
echo
echo "gpt+uefintfs mode: alternative hacky installation method, not recommended."
echo " This mode uses NTFS partition and third-party 'uefintfs' bootloader."
echo " GPT+NTFS(data)+FAT32(efi), UEFI only, supports Secure Boot"
echo " (since uefintfs Oct 23, 2021 release)."
echo " Large install.wim file will not be split."
echo
echo "${bold} == Removable storage list ==${normal}"
list_removable_drives
exit
fi
if [[ "$EUID" == "0" ]];
then
dev="$1"
isopath="$2"
isolabel=""
labeltype="$3"
[[ ! "$labeltype" ]] && labeltype="mbr"
mountntfs_cmd="mount.ntfs-3g"
if ! command -v "$mountntfs_cmd" > /dev/null;
then
mountntfs_cmd="mount"
fi
check_requirements
check_iso_and_device "$isopath" "$dev"
isolabel="$(get_iso_name "$isopath")"
if [ $? -ne 0 ]
then
isolabel=""
fi
echo "${bold} == Working with ISO $isolabel ==${normal}"
skipinstallwim=""
splitinstallwim=1
partpath="$(mktemp -d /run/windows2usb.XXXXXXXXXX)"
isomountpath="$(mktemp -d /run/windows2usb-mount.XXXXXXXXXX)"
trap sigint_handler INT EXIT
# MBR FAT32
if [[ "$labeltype" == "mbr" ]];
then
isolabel="$(sanitize_iso_name "$isolabel")"
echo "${bold} == Creating new MBR-formatted partition table ==${normal}"
format_drive "dos" "$dev"
echo "${bold} == Creating FAT partition ==${normal}"
create_partitions "dos" "$dev"
mkfs.vfat -n "${isolabel:0:11}" "$(get_dev_partition_num "${dev}" "1")"
echo "${bold} == Writing bootloader ==${normal}"
ms-sys -7 -f "${dev}"
ms-sys -e "$(get_dev_partition_num "${dev}" "1")"
echo "${bold} == Mounting data partition ==${normal}"
mount -o utf8=true "$(get_dev_partition_num "${dev}" "1")" "$partpath"
# GPT FAT32
elif [[ "$labeltype" == "gpt" ]];
then
isolabel="$(sanitize_iso_name "$isolabel")"
echo "${bold} == Creating new GPT-formatted partition table ==${normal}"
format_drive "gpt" "$dev"
echo "${bold} == Creating FAT partition ==${normal}"
create_partitions "gpt" "$dev"
mkfs.vfat -n "${isolabel:0:11}" "$(get_dev_partition_num "${dev}" "1")"
echo "${bold} == Mounting data partition ==${normal}"
mount -o utf8=true "$(get_dev_partition_num "${dev}" "1")" "$partpath"
# GPT NTFS
elif [[ "$labeltype" == "gptntfs" ]];
then
splitinstallwim="" # do NOT split install.wim in this mode
echo "${bold} == Creating new GPT-formatted partition table ==${normal}"
format_drive "gpt" "$dev"
echo "${bold} == Creating Microsoft NTFS partition ==${normal}"
create_partitions "gptntfs" "$dev"
mkfs.ntfs -L "$isolabel" -f "$(get_dev_partition_num "${dev}" "1")"
echo "${bold} == Mounting data partition ==${normal}"
"$mountntfs_cmd" "$(get_dev_partition_num "${dev}" "1")" "$partpath"
# GPT FAT32 (UEFINTFS) + NTFS
elif [[ "$labeltype" == "gpt+uefintfs" ]];
then
splitinstallwim="" # do NOT split install.wim in this mode
echo "${bold} == Creating new GPT-formatted partition table ==${normal}"
format_drive "gpt" "$dev"
echo "${bold} == Creating UEFI FAT and Microsoft NTFS partitions ==${normal}"
create_partitions "gpt+uefintfs" "$dev"
write_uefintfs "$(get_dev_partition_num "${dev}" "1")"
mkfs.ntfs -L "$isolabel" -f "$(get_dev_partition_num "${dev}" "2")"
echo "${bold} == Mounting data partition ==${normal}"
"$mountntfs_cmd" "$(get_dev_partition_num "${dev}" "2")" "$partpath"
fi
if [[ "$splitinstallwim" ]] && check_installwim_gt4gib "$isopath";
then
echo "${bold} == NOTE: install.wim is greater than 4 GiB and will be split to fit FAT32 limit ==${normal}"
skipinstallwim="skipinstallwim"
fi
echo "${bold} == Extracting files from ISO to the partition ==${normal}"
extract_iso "$isopath" "$partpath" "$skipinstallwim"
if [[ "$skipinstallwim" ]]; then
split_wim "$isopath" "$isomountpath" "$partpath"
fi
# If there's no .efi bootloader files inside the iso,
# extract them from install.wim
# This is true for older Windows 7 ISO files.
if [ ! -f "$partpath/efi/boot/bootx64.efi" ] && \
[ ! -f "$partpath/efi/boot/bootia32.efi" ];
then
echo "${bold} == Extracting UEFI bootloader from install.wim ==${normal}"
mkdir -p "$partpath/efi/boot/" || true
extract_bootmgfw_from_installwim "$isopath" "$isomountpath" "$partpath"
if [ -e "$partpath/efi/boot/bootmgfw.efi" ]; then
mv "$partpath/efi/boot/bootmgfw.efi" "$partpath/efi/boot/bootx64.efi"
cp "$partpath/efi/boot/bootx64.efi" "$partpath/efi/boot/bootia32.efi"
else
echo "NOTE: your ISO file does not have UEFI bootloader, UEFI boot would be unavailable!"
fi
fi
echo "${bold} == Unmounting partition ==${normal}"
echo "NOTE: If this process takes very long to complete, your system is misconfigured!"
echo "More info: https://github.com/ValdikSS/windows2usb/issues/3#issuecomment-771534058"
umount_rm_path "$partpath" "$isomountpath"
echo "${bold} == All done! ==${normal}"
else
if [[ "$APPIMAGE" ]];
then
scriptpath="$APPIMAGE"
fi
[ -f "$1" ] && PARAM1="$(readlink -f -- "$1")" || PARAM1="$1"
[ -f "$2" ] && PARAM2="$(readlink -f -- "$2")" || PARAM2="$2"
shift; shift;
privescs=(pkexec sudo)
for privesc in ${privescs[*]}; do
if command -v "$privesc" > /dev/null; then
"$privesc" "$scriptpath" "$PARAM1" "$PARAM2" "$@"
exit
fi
done
echo "${bold} == ERROR: no pkexec or sudo found. ==${normal}"
echo "${bold} == ERROR: please run this script as root manually. ==${normal}"
fi