Allow "imagex" to be renamed (default: wimlib-imagex)
[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 imagex=@IMAGEX_PROGNAME@
25
26 calc_columns () {
27         STAT_COL=80
28         if [[ -t 0 ]]; then
29                 # stty will fail when stdin isn't a terminal
30                 STAT_COL=$(stty size)
31                 # stty gives "rows cols"; strip the rows number, we just want columns
32                 STAT_COL=${STAT_COL##* }
33         elif tput cols &>/dev/null; then
34                 # is /usr/share/terminfo already mounted, and TERM recognized?
35                 STAT_COL=$(tput cols)
36         fi
37         if (( STAT_COL == 0 )); then
38                 # if output was 0 (serial console), set default width to 80
39                 STAT_COL=80
40         fi
41
42         # we use 13 characters for our own stuff
43         STAT_COL=$(( STAT_COL - 13 ))
44
45         if [[ -t 1 ]]; then
46                 SAVE_POSITION="\e[s"
47                 RESTORE_POSITION="\e[u"
48                 DEL_TEXT="\e[$(( STAT_COL + 4 ))G"
49         else
50                 SAVE_POSITION=""
51                 RESTORE_POSITION=""
52                 DEL_TEXT=""
53         fi
54 }
55
56
57 deltext() {
58         printf "${DEL_TEXT}"
59 }
60
61 stat_busy() {
62         printf "${PREFIX_REG} ${1} "
63         printf "${SAVE_POSITION}"
64         deltext
65         printf "   [BUSY] "
66 }
67
68 stat_done() {
69         deltext
70         printf "   [DONE] \n"
71 }
72
73 stat_fail() {
74         deltext
75         printf "   [FAIL] \n"
76         exit 1
77 }
78
79
80 cleanup() {
81         if mountpoint -q "$mnt_dir" ; then
82                 @IMAGEX_PROGNAME@ unmount "$mnt_dir"
83         fi
84         rm -rf "$tmp_dir"
85 }
86
87 usage() {
88         cat << EOF
89 Usage: $script_name [OPTIONS] IMAGE
90
91   -i, --iso                Make an ISO image instead of a disk image.
92   -o, --only-wim           Make neither a disk image nor an ISO image;
93                               instead, only make a modified boot.wim file.
94   -W, --windows-dir=DIR    Use DIR as the location of the mounted Windows 7
95                               or Windows 8 DVD.  Default is /mnt/windows,
96                               then /mnt/windows7, then /mnt/windows8.
97   -A, --waik-dir=DIR       Get the boot files and boot.wim from the ISO of the
98                               Windows Automated Installation Kit mounted on DIR
99                               instead of from the Windows 7 or Windows 8 DVD.
100   -s, --start-script=FILE  Add FILE to the root directory of Windows PE image
101                               and adjust \Windows\System32\winpeshl.ini to
102                               execute FILE when Windows PE starts up.
103   -w, --wim=WIM            Use WIM as the boot.wim file.  Defaults to
104                               sources/boot.wim in the Windows DVD directory, or
105                               F1_WINPE.WIM from the WAIK if --waik-dir is
106                               specified.
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 (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 "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 "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 "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 "Invalid option \"$1\""
215                         usage
216                         exit 1
217                         ;;
218                 esac
219                 shift
220         done
221
222         if [ $# -ne 1 ]; then
223                 echo "You must specify the name of the image file to create!"
224                 echo "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 /mnt/windows8; do
234                         if [ -d "$windows_dir"/sources ]; then
235                                 break
236                         fi
237                 done
238         fi
239         if [ ! -d "$windows_dir" ]; then
240                 if [ -z "$windows_dir_specified" ]; then
241                         cat << EOF
242 ERROR: Could not find the directory that the Windows 7 or 8 ISO image is mounted
243 on!  Please specify this directory using the --windows-dir option.
244 EOF
245                 else
246                         echo "ERROR: Could not find the directory \"$windows_dir\"!"
247                 fi
248                 exit 1
249         fi
250         if [ ! -d "$windows_dir/sources" ]; then
251                 cat << EOF
252 ERROR: The directory "$windows_dir" exists, but it seems that the Windows 7 or 8
253 ISO image is not mounted on it.  Please mount the image to continue.
254 EOF
255                 exit 1
256         fi
257 }
258
259 check_needed_programs() {
260         if [ -z "$waik_dir" -o -n "$modify_wim" ]; then
261                 if ! type -P @IMAGEX_PROGNAME@ &> /dev/null ; then
262                         cat << EOF
263 ERROR: To make a customized image of Windows PE, we need the "$imagex" program
264 from "wimlib" so that we can modify the boot.wim file.  However, "$imagex"
265 doesn't seem to be installed.  Please install "wimlib" to continue.
266 EOF
267                         exit 1
268                 fi
269         fi
270
271         if [ $make = iso ]; then
272                 if ! type -P mkisofs &> /dev/null ; then
273                         cat << EOF
274 ERROR: To make a bootable ISO image of Windows PE, we need the "mkisofs"
275 program, but it doesn't seem to be installed.  Please install the "cdrkit"
276 package to continue, or try omitting the --iso option to make a disk image
277 instead of an ISO image.
278 EOF
279                         exit 1
280                 fi
281         elif [ $make = disk ] ; then
282                 if ! type -P syslinux &> /dev/null ; then
283                         cat << EOF
284 ERROR: To make a bootable disk image of Windows PE, we need the "syslinux"
285 program, but it doesn't seem to be installed.  Please install the "syslinux"
286 package to continue, or try using the --iso option to make an ISO image instead
287 of a disk image.
288 EOF
289                         exit 1
290                 fi
291
292                 if ! type -P mformat mcopy &> /dev/null; then
293                         cat << EOF
294 ERROR: To make a bootable disk image of Windows PE, we need the "mformat" and
295 "mcopy" programs from the "mtools" package.  These programs allow us to
296 format a FAT filesystem and copy files to it without needing root privileges.
297 Please install "mtools" if you want to make a disk image of Windows PE.  Or,
298 try using the --iso option to make an ISO image instead of a disk image.
299 EOF
300                 fi
301         fi
302
303         if [ -n "$waik_dir" ]; then
304                 if ! type -P cabextract &> /dev/null ; then
305                         cat << EOF
306 ERROR: The boot files in the Windows Automated Installation Kit (WAIK) are
307 inside cabinet archives.  To extract these files, we need the "cabextract"
308 program, but it doesn't seem to be installed.  Please install "cabextract" to
309 continue.
310 EOF
311                         exit 1
312                 fi
313         fi
314
315 }
316
317 get_primary_boot_files() {
318         if [ -n "$waik_dir" ]; then
319                 # Get boot files from the WAIK.
320
321                 stat_busy "Copying primary boot files from the Windows Automated Installation Kit ($waik_dir, $arch)"
322                 if [ $make = iso ]; then
323                         cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_etfsboot.com -p \
324                                         > "$tmp_dir"/etfsboot.com || stat_fail
325                 fi
326                 cabextract "$waik_dir"/wAIK${arch}.msi -F F${arch_id}_BOOTMGR -p \
327                                 > "$tmp_dir"/bootmgr || stat_fail
328                 cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_boot.sdi -p \
329                                 > "$tmp_dir"/boot/boot.sdi || stat_fail
330                 cabextract "$waik_dir"/wAIK${arch}.msi -F F_WINPE_${arch}_bcd -p \
331                                 > "$tmp_dir"/boot/bcd || stat_fail
332                 stat_done
333         else
334                 # Get boot files from the Windows ISO
335
336                 stat_busy "Copying primary boot files from mounted Windows DVD ($windows_dir)"
337                 if [ $make = iso ]; then
338                         cp "$windows_dir"/boot/etfsboot.com "$tmp_dir" || stat_fail
339                 fi
340                 cp "$windows_dir"/bootmgr "$tmp_dir" || stat_fail
341                 cp "$windows_dir"/boot/{bcd,boot.sdi} "$tmp_dir"/boot || stat_fail
342                 stat_done
343         fi
344 }
345
346 get_boot_wim() {
347         boot_wim="$1"
348         # Copy the WIM over, or export the 2nd image in the WIM in the case of boot.wim
349         # from the Windows DVD.
350         remove_setup=
351         if [ -z "$wim" ]; then
352
353                 # WIM file unspecified- grab it from the WAIK or the Windows DVD
354                 if [ -n "$waik_dir" ]; then
355                         # WAIK
356                         stat_busy "Extracting boot.wim from \"$waik_dir/WinPE.cab\""
357                         cabextract "$waik_dir/WinPE.cab" -F F${arch_id}_WINPE.WIM -p \
358                                         > "$boot_wim" 2>/dev/null || stat_fail
359                         stat_done
360                 else
361                         # Windows DVD
362                         remove_setup=yes
363                         wim="$windows_dir/sources/boot.wim"
364                         stat_busy "Exporting image from \"$wim\""
365                         "$imagex" export "$windows_dir"/sources/boot.wim 2 \
366                                                 --boot "$boot_wim" || stat_fail
367                         stat_done
368                 fi
369         else
370                 # WIM file specified
371                 stat_busy "Copying $wim to temporary directory"
372                 cp "$wim" "$boot_wim"|| stat_fail
373                 stat_done
374         fi
375 }
376
377 modify_boot_wim() {
378         boot_wim="$1"
379         mnt_dir="$2"
380
381         # Make modifications to the WIM.
382         stat_busy "Mounting "$1" read-write"
383
384         mkdir -p "$mnt_dir" || stat_fail
385         "$imagex" mountrw "$boot_wim" "$mnt_dir"|| stat_fail
386
387         stat_done
388
389         if [ -n "$remove_setup" ]; then
390                 stat_busy "Renaming setup.exe to prevent it from bothering us"
391                 mv "$mnt_dir"/setup.exe{,.bkup} || stat_fail
392                 mv "$mnt_dir"/sources/setup.exe{,.bkup} || stat_fail
393                 stat_done
394         fi
395
396         if [ -n "$start_script" ]; then
397                 stat_busy "Setting \"$start_script\" as the script to be executed"\
398                                         "when Windows PE boots"
399                 cp "$start_script" "$mnt_dir"|| stat_fail
400                 cat > "$mnt_dir/Windows/System32/winpeshl.ini" << EOF
401 [LaunchApps]
402 %SYSTEMDRIVE%\\$start_script
403 EOF
404                 stat_done
405         fi
406
407         if [ -n "$overlay" ]; then
408                 stat_busy "Overlaying \"$overlay\" on the Windows PE filesystem"
409                 cp -r "$overlay"/* "$mnt_dir"  || stat_fail
410                 stat_done
411         fi
412
413         stat_busy "Rebuilding WIM with changes made"
414         "$imagex" unmount --commit "$mnt_dir" || stat_fail
415         stat_done
416
417         rmdir "$mnt_dir"
418 }
419
420 make_iso_img() {
421         image="$1"
422
423         # Make the ISO using the mkisofs command from cdrkit
424
425         stat_busy "Making ISO image \"$image\""
426
427         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE ($arch)"  -d -N \
428                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
429                 -hide boot.cat -quiet -o "$image" "$tmp_dir" || stat_fail
430
431         stat_done
432 }
433
434 make_disk_img() {
435         image="$1"
436
437         stat_busy "Making disk image \"$image\""
438
439         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
440         image_size=$(( image_du + 10000000 ))
441
442         mtool_conf="$(mktemp)"
443
444         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
445                         bs=4096 &> /dev/null
446
447         cat > "$mtool_conf" << EOF
448 MTOOLS_SKIP_CHECK=1
449 MTOOLS_FAT_COMPATIBILITY=1
450 drive s:
451         file="$image"
452 EOF
453
454         export MTOOLSRC="$mtool_conf"
455
456         mformat -h 255 -s 63 -T $(( image_size / 512)) s:
457         mcopy -s "$tmp_dir"/* s:
458
459         syslinux --install "$image"
460         mcopy /usr/lib/syslinux/chain.c32 s:
461         mcopy - 's:syslinux.cfg' << EOF
462         DEFAULT winpe
463         LABEL   winpe
464         COM32   chain.c32
465         APPEND  ntldr=/bootmgr
466 EOF
467         rm -f "$mtool_conf"
468         stat_done
469 }
470
471 calc_columns
472 tmp_dir="$(mktemp -d)"
473 mnt_dir="$tmp_dir"/.boot.wim.mount
474 process_command_line "$@"
475 if [ -z "$waik_dir" ]; then
476         find_windows_dir
477 fi
478 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
479         modify_wim=yes
480 fi
481 check_needed_programs
482 trap cleanup exit
483
484 if [ $make != wim ]; then
485         mkdir -p "$tmp_dir"/{boot,sources}
486         get_primary_boot_files
487 fi
488
489 if [ $make = wim ]; then
490         boot_wim="$image"
491 else
492         boot_wim="$tmp_dir"/sources/boot.wim
493 fi
494
495 get_boot_wim "$boot_wim"
496
497 if [ -n "$modify_wim" ]; then
498         modify_boot_wim "$boot_wim" "$mnt_dir"
499 fi
500
501 if [ $make = iso ]; then
502         make_iso_img "$image"
503 elif [ $make = disk ]; then
504         make_disk_img "$image"
505 fi
506
507 echo "The image ($image) is $(stat -c %s "$image") bytes."