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