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