Update version-info
[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         wimlib-imagex update "$boot_wim" --rebuild \
441                 < "$tmp_dir/__mkwinpeimg.update.cmds" > /dev/null || stat_fail
442         stat_done
443 }
444
445 make_iso_img() {
446         image="$1"
447
448         # Make the ISO using the mkisofs command from cdrkit
449
450         stat_busy "Making ISO image \"$image\""
451
452         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE ($arch)"  -d -N \
453                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
454                 -hide boot.cat -quiet -o "$image" "$tmp_dir" 1>&4 || stat_fail
455
456         stat_done
457 }
458
459 make_disk_img() {
460         image="$1"
461
462         stat_busy "Making disk image \"$image\""
463
464         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
465         image_size=$(( image_du + 10000000 ))
466
467         mtool_conf="$(mktemp)"
468
469         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
470                         bs=4096 &> /dev/null
471
472         cat > "$mtool_conf" <<- EOF
473                 MTOOLS_SKIP_CHECK=1
474                 MTOOLS_FAT_COMPATIBILITY=1
475                 drive s:
476                         file="$image"
477         EOF
478
479         export MTOOLSRC="$mtool_conf"
480
481         mformat -h 255 -s 63 -T $(( image_size / 512)) s: || stat_fail
482         mcopy -s "$tmp_dir"/* s: || stat_fail
483
484         syslinux --install "$image"
485
486         syslinuxdir="/usr/lib/syslinux"
487
488         if [ -d "$syslinuxdir/bios" ]; then
489                 biosdir="$syslinuxdir/bios"
490         else
491                 biosdir="$syslinuxdir"
492         fi
493
494         mcopy "$biosdir/chain.c32" s: || stat_fail
495         if [ -e "$biosdir/libcom32.c32" ]; then
496                 mcopy "$biosdir/libcom32.c32" s:
497         fi
498         if [ -e "$biosdir/libutil.c32" ]; then
499                 mcopy "$biosdir/libutil.c32" s:
500         fi
501         mcopy - 's:syslinux.cfg' <<- EOF
502                 DEFAULT winpe
503                 LABEL   winpe
504                 COM32   chain.c32
505                 APPEND  ntldr=/bootmgr
506         EOF
507         rm -f "$mtool_conf"
508         stat_done
509 }
510
511 calc_columns
512 tmp_dir="$(mktemp -d)"
513 process_command_line "$@"
514
515 if [ "$image" = "-" ] ; then
516         # Writing image to standard output
517         if [ "$make" != iso ]; then
518                 echo 1>&2 "ERROR: Writing image to standard output is only supported in --iso mode!"
519                 exit 1
520         fi
521         # We can't print anything to standard output except the ISO image
522         # itself.  Play with the file descriptors.
523
524         exec 4>&1  # 4 is now the original standard output.
525         exec 1>&2  # Anything that goes to standard output now, by default,
526                    # actually goes to standard error.
527 else
528         exec 4>&1  # 4 is now a copy of standard output
529 fi
530 if [ -z "$waik_dir" ]; then
531         find_windows_dir
532 fi
533 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
534         modify_wim=yes
535 fi
536 check_needed_programs
537 trap cleanup EXIT
538
539 if [ $make != wim ]; then
540         mkdir -p "$tmp_dir"/{boot,sources}
541         get_primary_boot_files
542 fi
543
544 if [ $make = wim ]; then
545         boot_wim="$image"
546 else
547         boot_wim="$tmp_dir"/sources/boot.wim
548 fi
549
550 get_boot_wim "$boot_wim"
551
552 if [ -n "$modify_wim" ]; then
553         modify_boot_wim "$boot_wim" "$tmp_dir"
554 fi
555
556 if [ $make = iso ]; then
557         make_iso_img "$image"
558 elif [ $make = disk ]; then
559         make_disk_img "$image"
560 fi
561
562 if [ "$image" != "-" ]; then
563         echo "The image ($image) is $(stat -c %s "$image") bytes."
564 fi