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