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