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