Initial commit (current version is wimlib 0.6.2)
[wimlib] / programs / mkwinpeimg
1 #!/bin/bash
2
3 # This script can make a customized bootable image of Windows PE.
4 #
5 # Copyright (C) 2012 Eric Biggers
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11 #
12
13 script_name="$(basename $0)"
14 PREFIX_REG="::"
15
16 calc_columns () {
17         STAT_COL=80
18         if [[ -t 0 ]]; then
19                 # stty will fail when stdin isn't a terminal
20                 STAT_COL=$(stty size)
21                 # stty gives "rows cols"; strip the rows number, we just want columns
22                 STAT_COL=${STAT_COL##* }
23         elif tput cols &>/dev/null; then
24                 # is /usr/share/terminfo already mounted, and TERM recognized?
25                 STAT_COL=$(tput cols)
26         fi
27         if (( STAT_COL == 0 )); then
28                 # if output was 0 (serial console), set default width to 80
29                 STAT_COL=80
30         fi
31
32         # we use 13 characters for our own stuff
33         STAT_COL=$(( STAT_COL - 13 ))
34
35         if [[ -t 1 ]]; then
36                 SAVE_POSITION="\e[s"
37                 RESTORE_POSITION="\e[u"
38                 DEL_TEXT="\e[$(( STAT_COL + 4 ))G"
39         else
40                 SAVE_POSITION=""
41                 RESTORE_POSITION=""
42                 DEL_TEXT=""
43         fi
44 }
45
46
47 deltext() {
48         printf "${DEL_TEXT}"
49 }
50
51 stat_busy() {
52         printf "${PREFIX_REG} ${1} "
53         printf "${SAVE_POSITION}"
54         deltext
55         printf "   [BUSY] "
56 }
57
58 stat_done() {
59         deltext
60         printf "   [DONE] \n"
61 }
62
63 stat_fail() {
64         deltext
65         printf "   [FAIL] \n"
66         exit 1
67 }
68
69
70 cleanup() {
71         if mountpoint -q "$mnt_dir" ; then
72                 imagex unmount "$mnt_dir"
73         fi
74         rm -rf "$tmp_dir"
75 }
76
77 usage() {
78         cat << EOF
79 Usage: $script_name [OPTIONS] IMAGE
80
81   -i, --iso                Make an ISO image instead of a disk image.
82   -o, --only-wim           Make neither a disk image nor an ISO image; 
83                               instead, only make a modified boot.wim file.
84   -W, --windows-dir=DIR    Use DIR as the location of the mounted Windows 7
85                               or Windows 8 DVD.  Default is /mnt/windows,
86                               then /mnt/windows7, then /mnt/windows8.
87   -A, --waik-dir=DIR       Get the boot files and boot.wim from the ISO of the 
88                               Windows Automated Installation Kit mounted on DIR
89                               instead of from the Windows 7 or Windows 8 DVD.
90   -s, --start-script=FILE  Add FILE to the root directory of Windows PE image 
91                               and adjust \Windows\System32\winpeshl.ini to 
92                               execute FILE when Windows PE starts up.
93   -w, --wim=WIM            Use WIM as the boot.wim file.  Defaults to 
94                               sources/boot.wim in the Windows DVD directory, or
95                               F1_WINPE.WIM from the WAIK if --waik-dir is
96                               specified.
97   -O, --overlay=DIR        Adds all the files in DIR to the Windows PE image.
98   -t, --tmp-dir=DIR        Use DIR as the temporary base of the ISO filesystem.  
99                               Defaults to making one using \"mktemp -d\".
100   -h, --help               Display this information.
101   -v, --version            Show version information.
102
103   See \`man mkwinpeimg' for more information.
104 EOF
105 }
106
107 version() {
108         echo "$script_name (wimlib 0.4.8)"
109         exit 0
110 }
111
112 make=disk
113
114 process_command_line() {
115
116         if ! options=$(getopt -o oiw:W:s:O:t:A:hv -l \
117                 only-wim,iso,wim:,windows-dir:,start-script:,overlay:,tmp-dir:,waik-dir:,help,version \
118                                 -- "$@" ); then
119                 usage
120                 exit 1
121         fi
122         eval set -- "$options"
123         while [ $# -gt 0 ]; do
124                 case "$1" in
125                 -i|--iso)
126                         make=iso
127                         ;;
128                 -o|--only-wim)
129                         make=wim
130                         ;;
131                 -W|--windows-dir)
132                         windows_dir="$2"
133                         windows_dir_specified=yes
134                         if [ -n "$waik_dir" ]; then
135                                 echo "ERROR: Cannot specify both --windows-dir and --waik-dir!"
136                                 exit 1
137                         fi
138                         shift
139                         ;;
140                 -A|--waik-dir)
141                         waik_dir="$2"
142                         if [ -n "$windows_dir" ]; then
143                                 echo "ERROR: Cannot specify both --windows-dir and --waik-dir!"
144                                 exit 1
145                         fi
146                         shift
147                         ;;
148                 -w|--wim)
149                         wim="$2"
150                         shift
151                         ;;
152                 -s|--start-script)
153                         start_script="$2"
154                         shift
155                         ;;
156                 -O|--overlay)
157                         overlay="$2"
158                         shift
159                         ;;
160                 -t|--tmp-dir)
161                         rmdir "$tmp_dir"
162                         tmp_dir="$2"
163                         shift
164                         ;;
165                 -h|--help)
166                         usage
167                         exit 0
168                         ;;
169                 -v|--version)
170                         version
171                         ;;
172                 --)
173                         shift
174                         break
175                         ;;
176                 *)
177                         echo "Invalid option \"$1\""
178                         usage
179                         exit 1
180                         ;;
181                 esac
182                 shift
183         done
184         if [ $# -ne 1 ]; then
185                 echo "You must specify the name of the image file to create!"
186                 echo "Run \"$script_name -h\" to see usage information."
187                 exit 1
188         else
189                 image="$1"
190         fi
191 }
192
193
194 find_windows_dir() {
195         if [ -z "$windows_dir_specified" ]; then
196                 for windows_dir in /mnt/windows /mnt/windows7 /mnt/windows8; do
197                         if [ -d "$windows_dir"/sources ]; then
198                                 break
199                         fi
200                 done
201         fi
202         if [ ! -d "$windows_dir" ]; then
203                 if [ -z "$windows_dir_specified" ]; then
204                         cat << EOF
205 ERROR: Could not find the directory that the Windows 7 or 8 ISO image is mounted
206 on!  Please specify this directory using the --windows-dir option.
207 EOF
208                 else
209                         echo "ERROR: Could not find the directory \"$windows_dir\"!"
210                 fi
211                 exit 1
212         fi
213         if [ ! -d "$windows_dir/sources" ]; then
214                 cat << EOF
215 ERROR: The directory "$windows_dir" exists, but it seems that the Windows 7 or 8
216 ISO image is not mounted on it.  Please mount the image to continue.
217 EOF
218                 exit 1
219         fi
220 }
221
222 check_needed_programs() {
223         if [ -z "$waik_dir" -o -n "$modify_wim" ]; then
224                 if ! type -P imagex &> /dev/null ; then
225                         cat << EOF
226 ERROR: To make a customized image of Windows PE, we need the \"imagex\" program
227 from WIMLIB so that we can modify the boot.wim file.  However, \"imagex\"
228 doesn't seem to be installed.  Please install WIMLIB to continue.
229 EOF
230                         exit 1
231                 fi
232         fi
233
234         if [ $make = iso ]; then
235                 if ! type -P mkisofs &> /dev/null ; then
236                         cat << EOF 
237 ERROR: To make a bootable ISO image of Windows PE, we need the \"mkisofs\"
238 program, but it doesn't seem to be installed.  Please install the \"cdrkit\"
239 package to continue, or try omitting the --iso option to make a disk image
240 instead of an ISO image.
241 EOF
242                         exit 1
243                 fi
244         elif [ $make = disk ] ; then
245                 if ! type -P syslinux &> /dev/null ; then
246                         cat << EOF
247 ERROR: To make a bootable disk image of Windows PE, we need the \"syslinux\"
248 program, but it doesn't seem to be installed.  Please install the \"syslinux\"
249 package to continue, or try using the --iso option to make an ISO image instead
250 of a disk image.
251 EOF
252                         exit 1
253                 fi
254
255                 if ! type -P mformat mcopy &> /dev/null; then
256                         cat << EOF
257 ERROR: To make a bootable disk image of Windows PE, we need the \"mformat\" and
258 \"mcopy\" programs from the \"mtools\" package.  These programs allow us to
259 format a FAT filesystem and copy files to it without needing root privileges.
260 Please install \"mtools\" if you want to make a disk image of Windows PE.  Or,
261 try using the --iso option to make an ISO image instead of a disk image.
262 EOF
263                 fi
264         fi
265
266         if [ -n "$waik_dir" ]; then
267                 if ! type -P cabextract &> /dev/null ; then
268                         cat << EOF
269 ERROR: The boot files in the Windows Automated Installation Kit (WAIK) are
270 inside cabinet archives.  To extract these files, we need the \"cabextract\"
271 program, but it doesn't seem to be installed.  Please install \"cabextract\" to
272 continue.
273 EOF
274                         exit 1
275                 fi
276         fi
277
278 }
279
280 get_primary_boot_files() {
281
282         if [ -n "$waik_dir" ]; then
283                 # Get boot files from the WAIK.
284
285                 stat_busy "Copying primary boot files from the Windows Automated Installation Kit ($waik_dir)"
286                 if [ $make = iso ]; then
287                         cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_etfsboot.com -p \
288                                         > "$tmp_dir"/etfsboot.com || stat_fail
289                 fi
290                 cabextract "$waik_dir"/wAIKX86.msi -F F1_BOOTMGR -p \
291                                 > "$tmp_dir"/bootmgr || stat_fail
292                 cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_boot.sdi -p \
293                                 > "$tmp_dir"/boot/boot.sdi || stat_fail
294                 cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_bcd -p \
295                                 > "$tmp_dir"/boot/bcd || stat_fail
296                 stat_done
297         else
298                 # Get boot files from the Windows ISO
299
300                 stat_busy "Copying primary boot files from mounted Windows DVD ($windows_dir)"
301                 if [ $make = iso ]; then
302                         cp "$windows_dir"/boot/etfsboot.com "$tmp_dir" || stat_fail
303                 fi
304                 cp "$windows_dir"/bootmgr "$tmp_dir" || stat_fail
305                 cp "$windows_dir"/boot/{bcd,boot.sdi} "$tmp_dir"/boot || stat_fail
306                 stat_done
307         fi
308 }
309
310 get_boot_wim() {
311         boot_wim="$1"
312         # Copy the WIM over, or export the 2nd image in the WIM in the case of boot.wim
313         # from the Windows DVD.
314         remove_setup=
315         if [ -z "$wim" ]; then
316
317                 # WIM file unspecified- grab it from the WAIK or the Windows DVD
318                 if [ -n "$waik_dir" ]; then
319                         # WAIK
320                         stat_busy "Extracting boot.wim from \"$waik_dir/WinPE.cab\""
321                         cabextract "$waik_dir/WinPE.cab" -F F1_WINPE.WIM -p \
322                                         > "$boot_wim" 2>/dev/null || stat_fail
323                         stat_done
324                 else
325                         # Windows DVD
326                         remove_setup=yes
327                         wim="$windows_dir/sources/boot.wim"
328                         stat_busy "Exporting image from \"$wim\""
329                         imagex export "$windows_dir"/sources/boot.wim 2 \
330                                                 --compress --boot "$boot_wim" || stat_fail
331                         stat_done
332                 fi
333         else
334                 # WIM file specified
335                 stat_busy "Copying $wim to temporary directory"
336                 cp "$wim" "$boot_wim"|| stat_fail
337                 stat_done
338         fi
339 }
340
341 modify_boot_wim() {
342
343         boot_wim="$1"
344         mnt_dir="$2"
345
346         # Make modifications to the WIM. 
347         stat_busy "Mounting "$1" read-write"
348
349         mkdir -p "$mnt_dir" || stat_fail
350         imagex mountrw "$boot_wim" "$mnt_dir"|| stat_fail
351
352         stat_done
353
354         if [ -n "$remove_setup" ]; then
355                 stat_busy "Renaming setup.exe to prevent it from bothering us"
356                 mv "$mnt_dir"/setup.exe{,.bkup} || stat_fail
357                 mv "$mnt_dir"/sources/setup.exe{,.bkup} || stat_fail
358                 stat_done
359         fi
360
361         if [ -n "$start_script" ]; then
362                 stat_busy "Setting \"$start_script\" as the script to be executed"\
363                                         "when Windows PE boots"
364                 cp "$start_script" "$mnt_dir"|| stat_fail
365                 cat > "$mnt_dir/Windows/System32/winpeshl.ini" << EOF
366 [LaunchApps]
367 %SYSTEMDRIVE%\\$start_script
368 EOF
369                 stat_done
370         fi
371
372         if [ -n "$overlay" ]; then
373                 stat_busy "Overlaying \"$overlay\" on the Windows PE filesystem"
374                 cp -r "$overlay"/* "$mnt_dir"  || stat_fail
375                 stat_done
376         fi
377
378         stat_busy "Rebuilding WIM with changes made"
379         imagex unmount --commit "$mnt_dir" || stat_fail
380         stat_done
381
382         rmdir "$mnt_dir"
383 }
384
385 make_iso_img() {
386
387         image="$1"
388
389         # Make the ISO using the mkisofs command from cdrkit
390
391         stat_busy "Making ISO image \"$image\""
392
393         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE (x86)"  -d -N \
394                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
395                 -hide boot.cat -quiet -o "$image" "$tmp_dir" || stat_fail
396
397         stat_done
398
399 }
400
401 make_disk_img() {
402
403         image="$1"
404
405         stat_busy "Making disk image \"$image\""
406
407         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
408         image_size=$(( image_du + 10000000 ))
409
410         mtool_conf="$(mktemp)"
411
412         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
413                         bs=4096 &> /dev/null
414
415         cat > "$mtool_conf" << EOF
416 MTOOLS_SKIP_CHECK=1
417 MTOOLS_FAT_COMPATIBILITY=1
418 drive s:
419         file="$image"
420 EOF
421
422         export MTOOLSRC="$mtool_conf"
423
424         mformat -h 255 -s 63 -T $(( image_size / 512)) s:
425         mcopy -s "$tmp_dir"/* s:
426
427         syslinux --install "$image"
428         mcopy /usr/lib/syslinux/chain.c32 s:
429         mcopy - 's:syslinux.cfg' << EOF
430         DEFAULT winpe
431         LABEL   winpe
432         COM32   chain.c32
433         APPEND  ntldr=/bootmgr
434 EOF
435         rm -f "$mtool_conf"
436         stat_done
437 }
438
439
440 calc_columns
441 tmp_dir="$(mktemp -d)"
442 mnt_dir="$tmp_dir"/.boot.wim.mount
443 process_command_line "$@"
444 if [ -z "$waik_dir" ]; then
445         find_windows_dir
446 fi
447 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
448         modify_wim=yes
449 fi
450 check_needed_programs
451 trap cleanup exit
452
453 if [ $make != wim ]; then
454         mkdir -p "$tmp_dir"/{boot,sources}
455         get_primary_boot_files
456 fi
457
458 if [ $make = wim ]; then
459         boot_wim="$image"
460 else
461         boot_wim="$tmp_dir"/sources/boot.wim
462 fi
463
464 get_boot_wim "$boot_wim"
465
466 if [ -n "$modify_wim" ]; then
467         modify_boot_wim "$boot_wim" "$mnt_dir"
468 fi
469
470 if [ $make = iso ]; then
471         make_iso_img "$image"
472 elif [ $make = disk ]; then
473         make_disk_img "$image"
474 fi
475
476 echo "The image ($image) is $(stat -c %s "$image") bytes."