mkwinpeimg: use case insensitive mode when updating boot.wim
[wimlib] / programs / mkwinpeimg.in
1 #!/usr/bin/env bash
2 #
3 # This script can make a customized bootable image of Windows PE.
4 #
5
6 # Copyright (C) 2012, 2013 Eric Biggers
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 script_name="$(basename "$0")"
22 PREFIX_REG="::"
23 WIMLIB_VERSION=@VERSION@
24
25 calc_columns () {
26         STAT_COL=80
27         if [[ -t 0 ]]; then
28                 # stty will fail when stdin isn't a terminal
29                 STAT_COL=$(stty size)
30                 # stty gives "rows cols"; strip the rows number, we just want columns
31                 STAT_COL=${STAT_COL##* }
32         elif tput cols &>/dev/null; then
33                 # is /usr/share/terminfo already mounted, and TERM recognized?
34                 STAT_COL=$(tput cols)
35         fi
36         if (( STAT_COL == 0 )); then
37                 # if output was 0 (serial console), set default width to 80
38                 STAT_COL=80
39         fi
40
41         # we use 13 characters for our own stuff
42         STAT_COL=$(( STAT_COL - 13 ))
43
44         if [[ -t 1 ]]; then
45                 SAVE_POSITION="\e[s"
46                 RESTORE_POSITION="\e[u"
47                 DEL_TEXT="\e[$(( STAT_COL + 4 ))G"
48         else
49                 SAVE_POSITION=""
50                 RESTORE_POSITION=""
51                 DEL_TEXT=""
52         fi
53 }
54
55
56 deltext() {
57         printf "${DEL_TEXT}"
58 }
59
60 stat_busy() {
61         printf "${PREFIX_REG} ${1} "
62         printf "${SAVE_POSITION}"
63         deltext
64         printf "   [BUSY] "
65 }
66
67 stat_done() {
68         deltext
69         printf "   [DONE] \n"
70 }
71
72 stat_fail() {
73         deltext
74         printf "   [FAIL] \n"
75         exit 1
76 }
77
78
79 cleanup() {
80         rm -rf "$tmp_dir"
81 }
82
83 usage() {
84         cat << EOF
85 Usage: $script_name [OPTIONS] IMAGE
86
87   -i, --iso                Make an ISO image instead of a disk image.
88   -o, --only-wim           Make neither a disk image nor an ISO image;
89                               instead, only make a modified boot.wim file.
90   -W, --windows-dir=DIR    Use DIR as the location of the mounted Windows
91                               installation ISO image.  If not specified, then
92                               the script tries the following locations:
93                               /mnt/windows, /mnt/windows7, /mnt/windows8,
94                               /mnt/windows10.
95   -A, --waik-dir=DIR       Get the boot files and boot.wim from the ISO image
96                               of the Windows Automated Installation Kit (WAIK)
97                               mounted on DIR instead of from a Windows
98                               installation ISO.  This also works if the mounted
99                               ISO is for the WAIK supplement rather than for the
100                               WAIK itself.
101   -s, --start-script=FILE  Add FILE to the root directory of Windows PE image
102                               and adjust \Windows\System32\winpeshl.ini to
103                               execute FILE when Windows PE starts up.
104   -w, --wim=WIM            Use WIM as the boot.wim file.  This defaults to the
105                               appropriate WIM file from the Windows or WAIK
106                               directory.
107   -O, --overlay=DIR        Adds all the files in DIR to the Windows PE image.
108   -t, --tmp-dir=DIR        Use DIR as the temporary base of the ISO filesystem.
109                               Defaults to making one using "mktemp -d".
110   -a, --arch=ARCH          Use the Windows PE version from the WAIK that has
111                               the CPU architecture ARCH.  Possible values:
112                               "x86" or "amd64".  Default is "x86".
113   -h, --help               Display this information.
114   -v, --version            Show version information.
115
116   See \`man mkwinpeimg' for more information.
117 EOF
118 }
119
120 version() {
121         echo "$script_name (distributed with wimlib $WIMLIB_VERSION)"
122         exit 0
123 }
124
125 make=disk
126
127 process_command_line() {
128
129         if ! options=$(getopt -o oiw:W:s:O:t:A:a:hv -l \
130                 only-wim,iso,wim:,windows-dir:,start-script:,overlay:,tmp-dir:,waik-dir:,arch:,help,version \
131                                 -- "$@" ); then
132                 usage
133                 exit 1
134         fi
135
136         # default arch value
137         arch="X86"
138         arch_id="1"
139
140         eval set -- "$options"
141         while [ $# -gt 0 ]; do
142                 case "$1" in
143                 -i|--iso)
144                         make=iso
145                         ;;
146                 -o|--only-wim)
147                         make=wim
148                         ;;
149                 -W|--windows-dir)
150                         windows_dir="$2"
151                         windows_dir_specified=yes
152                         if [ -n "$waik_dir" ]; then
153                                 echo 1>&2 "ERROR: Cannot specify both --windows-dir and --waik-dir!"
154                                 exit 1
155                         fi
156                         shift
157                         ;;
158                 -A|--waik-dir)
159                         waik_dir="$2"
160                         if [ -n "$windows_dir" ]; then
161                                 echo 1>&2 "ERROR: Cannot specify both --windows-dir and --waik-dir!"
162                                 exit 1
163                         fi
164                         shift
165                         ;;
166                 -w|--wim)
167                         wim="$2"
168                         shift
169                         ;;
170                 -s|--start-script)
171                         start_script="$2"
172                         shift
173                         ;;
174                 -O|--overlay)
175                         overlay="$2"
176                         shift
177                         ;;
178                 -t|--tmp-dir)
179                         rmdir "$tmp_dir"
180                         tmp_dir="$2"
181                         shift
182                         ;;
183                 -a|--arch)
184                         if [ "$2" == "x86" ]; then
185                                 arch="X86"
186                                 arch_id="1"
187                         # Need to test Itanium images before making it an
188                         # option.  Note: syslinux is x86 only so can't be used
189                         # for the Itanium disk image.
190                         #elif [ "$2" == "ia64" ]; then
191                                 #arch="IA64"
192                                 #arch_id="2"
193                         elif [ "$2" == "amd64" ]; then
194                                 arch="AMD64"
195                                 arch_id="3"
196                         else
197                                 echo 1>&2 "ERROR: $2 is not a valid arch (x86/amd64)"
198                                 exit 1
199                         fi
200                         shift
201                         ;;
202                 -h|--help)
203                         usage
204                         exit 0
205                         ;;
206                 -v|--version)
207                         version
208                         ;;
209                 --)
210                         shift
211                         break
212                         ;;
213                 *)
214                         echo 1>&2 "Invalid option \"$1\""
215                         usage
216                         exit 1
217                         ;;
218                 esac
219                 shift
220         done
221
222         if [ $# -ne 1 ]; then
223                 echo 1>&2 "You must specify the name of the image file to create!"
224                 echo 1>&2 "Run \"$script_name -h\" to see usage information."
225                 exit 1
226         else
227                 image="$1"
228         fi
229 }
230
231 find_windows_dir() {
232         if [ -z "$windows_dir_specified" ]; then
233                 for windows_dir in /mnt/windows /mnt/windows7           \
234                                    /mnt/windows8 /mnt/windows10;        \
235                 do
236                         if [ -d "$windows_dir"/sources ]; then
237                                 break
238                         fi
239                 done
240         fi
241         if [ ! -d "$windows_dir" ]; then
242                 if [ -z "$windows_dir_specified" ]; then
243                         cat 1>&2 << EOF
244 ERROR: Could not find the directory that the Windows (Vista or later) ISO image
245 is mounted on!  Please specify this directory using the --windows-dir option.
246 EOF
247                 else
248                         echo 1>&2 "ERROR: Could not find the directory \"$windows_dir\"!"
249                 fi
250                 exit 1
251         fi
252         if [ ! -d "$windows_dir/sources" ]; then
253                 cat 1>&2 << EOF
254 ERROR: The directory "$windows_dir" exists, but it seems that a Windows
255 (Vista or later) installation ISO image is not mounted on it.  Please mount
256 the image to continue.
257 EOF
258                 exit 1
259         fi
260 }
261
262 check_needed_programs() {
263         if [ -z "$waik_dir" -o -n "$modify_wim" ]; then
264                 if ! type -P wimlib-imagex &> /dev/null ; then
265                         cat 1>&2 << EOF
266 ERROR: To make a customized image of Windows PE, we need the wimlib-imagex program
267 from "wimlib" so that we can modify the boot.wim file.  However, wimlib-imagex
268 doesn't seem to be installed.  Please install "wimlib" to continue.
269 EOF
270                         exit 1
271                 fi
272         fi
273
274         if [ $make = iso ]; then
275                 if ! type -P mkisofs &> /dev/null ; then
276                         cat 1>&2 << EOF
277 ERROR: To make a bootable ISO image of Windows PE, we need the "mkisofs"
278 program, but it doesn't seem to be installed.  Please install the "cdrkit"
279 package to continue, or try omitting the --iso option to make a disk image
280 instead of an ISO image.
281 EOF
282                         exit 1
283                 fi
284         elif [ $make = disk ] ; then
285                 if ! type -P syslinux &> /dev/null ; then
286                         cat 1>&2 << EOF
287 ERROR: To make a bootable disk image of Windows PE, we need the "syslinux"
288 program, but it doesn't seem to be installed.  Please install the "syslinux"
289 package to continue, or try using the --iso option to make an ISO image instead
290 of a disk image.
291 EOF
292                         exit 1
293                 fi
294
295                 if ! type -P mformat mcopy &> /dev/null; then
296                         cat 1>&2 << EOF
297 ERROR: To make a bootable disk image of Windows PE, we need the "mformat" and
298 "mcopy" programs from the "mtools" package.  These programs allow us to
299 format a FAT filesystem and copy files to it without needing root privileges.
300 Please install "mtools" if you want to make a disk image of Windows PE.  Or,
301 try using the --iso option to make an ISO image instead of a disk image.
302 EOF
303                 fi
304         fi
305
306         if [ -n "$waik_dir" ] &&  [ -f "$waik_dir"/wAIK${arch}.msi ]; then
307                 if ! type -P cabextract &> /dev/null ; then
308                         cat 1>&2 << EOF
309 ERROR: The boot files in the Windows Automated Installation Kit (WAIK) are
310 inside cabinet archives.  To extract these files, we need the "cabextract"
311 program, but it doesn't seem to be installed.  Please install "cabextract" to
312 continue.
313 EOF
314                         exit 1
315                 fi
316         fi
317
318 }
319
320 get_primary_boot_files() {
321         if [ -n "$waik_dir" ]; then
322                 # Get boot files from the WAIK.
323                 stat_busy "Copying primary boot files from the Windows Automated Installation Kit ($waik_dir, $arch)"
324
325                 if [ -f "$waik_dir"/wAIK${arch}.msi ]; then
326                         if [ $make = iso ]; then
327                                 cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_etfsboot.com -p \
328                                                 > "$tmp_dir"/etfsboot.com || stat_fail
329                         fi
330                         cabextract "$waik_dir"/wAIK${arch}.msi -F F${arch_id}_BOOTMGR -p \
331                                         > "$tmp_dir"/bootmgr || stat_fail
332                         cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_boot.sdi -p \
333                                         > "$tmp_dir"/boot/boot.sdi || stat_fail
334                         cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_bcd -p \
335                                         > "$tmp_dir"/boot/bcd || stat_fail
336                 # The WAIK supplement disc has a different structure
337                 else
338                         # Note: fuseiso, mount default to map=normal i.e. lowercase
339                         if [ $make = iso ]; then
340                                 cp "$waik_dir"/${arch,,}/boot/etfsboot.com $tmp_dir/etfsboot.com || stat_fail
341                         fi
342                         cp "$waik_dir"/${arch,,}/bootmgr $tmp_dir/bootmgr || stat_fail
343                         cp "$waik_dir"/${arch,,}/boot/boot.sdi $tmp_dir/boot/boot.sdi || stat_fail
344                         cp "$waik_dir"/${arch,,}/boot/bcd $tmp_dir/boot/bcd || stat_fail
345                 fi
346                 stat_done
347         else
348                 # Get boot files from the Windows ISO
349
350                 stat_busy "Copying primary boot files from mounted Windows ISO ($windows_dir)"
351                 if [ $make = iso ]; then
352                         cp "$windows_dir"/boot/etfsboot.com "$tmp_dir" || stat_fail
353                 fi
354                 cp "$windows_dir"/bootmgr "$tmp_dir" || stat_fail
355                 cp "$windows_dir"/boot/{bcd,boot.sdi} "$tmp_dir"/boot || stat_fail
356                 stat_done
357         fi
358 }
359
360 get_boot_wim() {
361         boot_wim="$1"
362         # Copy the WIM over, or export the 2nd image in the WIM in the case of boot.wim
363         # from the Windows ISO.
364         remove_setup=
365         if [ -z "$wim" ]; then
366
367                 # WIM file unspecified- grab it from the WAIK or the Windows ISO
368                 if [ -n "$waik_dir" ]; then
369                         # WAIK
370                         if [ -f "$waik_dir/WinPE.cab" ]; then
371                                 stat_busy "Extracting boot.wim from \"$waik_dir/WinPE.cab\""
372                                 cabextract "$waik_dir/WinPE.cab" -F F${arch_id}_WINPE.WIM -p \
373                                                 > "$boot_wim" 2>/dev/null || stat_fail
374                         # WAIK supplement has different layout
375                         else
376                                 stat_busy "Copying boot.wim from \"${waik_dir}/${arch,,}/winpe.wim\""
377                                 cp "$waik_dir"/${arch,,}/winpe.wim "$boot_wim" || stat_fail
378                                 chmod +w "$boot_wim"
379                         fi
380                         stat_done
381                 else
382                         # Windows ISO
383                         remove_setup=yes
384                         wim="$windows_dir/sources/boot.wim"
385                         stat_busy "Exporting image from \"$wim\""
386                         wimlib-imagex export "$windows_dir"/sources/boot.wim 2 \
387                                                 --boot "$boot_wim" || stat_fail
388                         stat_done
389                 fi
390         else
391                 # WIM file specified
392                 stat_busy "Copying $wim to temporary directory"
393                 cp "$wim" "$boot_wim"|| stat_fail
394                 stat_done
395         fi
396 }
397
398 # Make modifications to the WIM.
399 modify_boot_wim() {
400         boot_wim="$1"
401         tmp_dir="$2"
402
403         exec 3>"$tmp_dir/__mkwinpeimg.update.cmds"
404
405         if [ -n "$remove_setup" ]; then
406                 stat_busy "Renaming setup.exe to prevent it from bothering us"
407                 cat 1>&3 <<- EOF
408                         rename /setup.exe /setup.exe.orig
409                         rename /sources/setup.exe /sources/setup.exe.orig
410                 EOF
411                 stat_done
412         fi
413
414         if [ -n "$start_script" ]; then
415                 stat_busy "Setting \"$start_script\" as the script to be executed when Windows PE boots"
416                 start_script_base="$(basename "$start_script")"
417                 cat > "$tmp_dir/__mkwinpeimg.winpeshl.ini" <<- EOF
418                         [LaunchApps]
419                         %SYSTEMDRIVE%\\$start_script_base
420                 EOF
421                 cat 1>&3 <<- EOF
422                         add '$start_script' '/$start_script_base'
423                         delete --force /Windows/System32/winpeshl.ini
424                         add '$tmp_dir/__mkwinpeimg.winpeshl.ini' /Windows/System32/winpeshl.ini
425                 EOF
426                 stat_done
427         fi
428
429         if [ -n "$overlay" ]; then
430                 stat_busy "Overlaying \"$overlay\" on the Windows PE filesystem"
431                 cat 1>&3 <<- EOF
432                         add '$overlay' /
433                 EOF
434                 stat_done
435         fi
436
437         exec 3>&-
438
439         stat_busy "Rebuilding WIM with changes made"
440         # Use case-insensitive mode; some Windows PE images contain a "windows"
441         # directory instead of a "Windows" directory...
442         WIMLIB_IMAGEX_IGNORE_CASE=1 wimlib-imagex update "$boot_wim" --rebuild \
443                 < "$tmp_dir/__mkwinpeimg.update.cmds" > /dev/null || stat_fail
444         stat_done
445 }
446
447 make_iso_img() {
448         image="$1"
449
450         # Make the ISO using the mkisofs command from cdrkit
451
452         stat_busy "Making ISO image \"$image\""
453
454         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE ($arch)"  -d -N \
455                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
456                 -hide boot.cat -quiet -o "$image" "$tmp_dir" 1>&4 || stat_fail
457
458         stat_done
459 }
460
461 make_disk_img() {
462         image="$1"
463
464         stat_busy "Making disk image \"$image\""
465
466         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
467         image_size=$(( image_du + 10000000 ))
468
469         mtool_conf="$(mktemp)"
470
471         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
472                         bs=4096 &> /dev/null
473
474         cat > "$mtool_conf" <<- EOF
475                 MTOOLS_SKIP_CHECK=1
476                 MTOOLS_FAT_COMPATIBILITY=1
477                 drive s:
478                         file="$image"
479         EOF
480
481         export MTOOLSRC="$mtool_conf"
482
483         mformat -h 255 -s 63 -T $(( image_size / 512)) s: || stat_fail
484         mcopy -s "$tmp_dir"/* s: || stat_fail
485
486         syslinux --install "$image"
487
488         syslinuxdir="/usr/lib/syslinux"
489
490         if [ -d "$syslinuxdir/bios" ]; then
491                 biosdir="$syslinuxdir/bios"
492         else
493                 biosdir="$syslinuxdir"
494         fi
495
496         mcopy "$biosdir/chain.c32" s: || stat_fail
497         if [ -e "$biosdir/libcom32.c32" ]; then
498                 mcopy "$biosdir/libcom32.c32" s:
499         fi
500         if [ -e "$biosdir/libutil.c32" ]; then
501                 mcopy "$biosdir/libutil.c32" s:
502         fi
503         mcopy - 's:syslinux.cfg' <<- EOF
504                 DEFAULT winpe
505                 LABEL   winpe
506                 COM32   chain.c32
507                 APPEND  ntldr=/bootmgr
508         EOF
509         rm -f "$mtool_conf"
510         stat_done
511 }
512
513 calc_columns
514 tmp_dir="$(mktemp -d)"
515 process_command_line "$@"
516
517 if [ "$image" = "-" ] ; then
518         # Writing image to standard output
519         if [ "$make" != iso ]; then
520                 echo 1>&2 "ERROR: Writing image to standard output is only supported in --iso mode!"
521                 exit 1
522         fi
523         # We can't print anything to standard output except the ISO image
524         # itself.  Play with the file descriptors.
525
526         exec 4>&1  # 4 is now the original standard output.
527         exec 1>&2  # Anything that goes to standard output now, by default,
528                    # actually goes to standard error.
529 else
530         exec 4>&1  # 4 is now a copy of standard output
531 fi
532 if [ -z "$waik_dir" ]; then
533         find_windows_dir
534 fi
535 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
536         modify_wim=yes
537 fi
538 check_needed_programs
539 trap cleanup EXIT
540
541 if [ $make != wim ]; then
542         mkdir -p "$tmp_dir"/{boot,sources}
543         get_primary_boot_files
544 fi
545
546 if [ $make = wim ]; then
547         boot_wim="$image"
548 else
549         boot_wim="$tmp_dir"/sources/boot.wim
550 fi
551
552 get_boot_wim "$boot_wim"
553
554 if [ -n "$modify_wim" ]; then
555         modify_boot_wim "$boot_wim" "$tmp_dir"
556 fi
557
558 if [ $make = iso ]; then
559         make_iso_img "$image"
560 elif [ $make = disk ]; then
561         make_disk_img "$image"
562 fi
563
564 if [ "$image" != "-" ]; then
565         echo "The image ($image) is $(stat -c %s "$image") bytes."
566 fi