Clean up file headers
[wimlib] / programs / mkwinpeimg
1 #!/bin/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
24 calc_columns () {
25         STAT_COL=80
26         if [[ -t 0 ]]; then
27                 # stty will fail when stdin isn't a terminal
28                 STAT_COL=$(stty size)
29                 # stty gives "rows cols"; strip the rows number, we just want columns
30                 STAT_COL=${STAT_COL##* }
31         elif tput cols &>/dev/null; then
32                 # is /usr/share/terminfo already mounted, and TERM recognized?
33                 STAT_COL=$(tput cols)
34         fi
35         if (( STAT_COL == 0 )); then
36                 # if output was 0 (serial console), set default width to 80
37                 STAT_COL=80
38         fi
39
40         # we use 13 characters for our own stuff
41         STAT_COL=$(( STAT_COL - 13 ))
42
43         if [[ -t 1 ]]; then
44                 SAVE_POSITION="\e[s"
45                 RESTORE_POSITION="\e[u"
46                 DEL_TEXT="\e[$(( STAT_COL + 4 ))G"
47         else
48                 SAVE_POSITION=""
49                 RESTORE_POSITION=""
50                 DEL_TEXT=""
51         fi
52 }
53
54
55 deltext() {
56         printf "${DEL_TEXT}"
57 }
58
59 stat_busy() {
60         printf "${PREFIX_REG} ${1} "
61         printf "${SAVE_POSITION}"
62         deltext
63         printf "   [BUSY] "
64 }
65
66 stat_done() {
67         deltext
68         printf "   [DONE] \n"
69 }
70
71 stat_fail() {
72         deltext
73         printf "   [FAIL] \n"
74         exit 1
75 }
76
77
78 cleanup() {
79         if mountpoint -q "$mnt_dir" ; then
80                 imagex unmount "$mnt_dir"
81         fi
82         rm -rf "$tmp_dir"
83 }
84
85 usage() {
86         cat << EOF
87 Usage: $script_name [OPTIONS] IMAGE
88
89   -i, --iso                Make an ISO image instead of a disk image.
90   -o, --only-wim           Make neither a disk image nor an ISO image; 
91                               instead, only make a modified boot.wim file.
92   -W, --windows-dir=DIR    Use DIR as the location of the mounted Windows 7
93                               or Windows 8 DVD.  Default is /mnt/windows,
94                               then /mnt/windows7, then /mnt/windows8.
95   -A, --waik-dir=DIR       Get the boot files and boot.wim from the ISO of the 
96                               Windows Automated Installation Kit mounted on DIR
97                               instead of from the Windows 7 or Windows 8 DVD.
98   -s, --start-script=FILE  Add FILE to the root directory of Windows PE image 
99                               and adjust \Windows\System32\winpeshl.ini to 
100                               execute FILE when Windows PE starts up.
101   -w, --wim=WIM            Use WIM as the boot.wim file.  Defaults to 
102                               sources/boot.wim in the Windows DVD directory, or
103                               F1_WINPE.WIM from the WAIK if --waik-dir is
104                               specified.
105   -O, --overlay=DIR        Adds all the files in DIR to the Windows PE image.
106   -t, --tmp-dir=DIR        Use DIR as the temporary base of the ISO filesystem.  
107                               Defaults to making one using \"mktemp -d\".
108   -h, --help               Display this information.
109   -v, --version            Show version information.
110
111   See \`man mkwinpeimg' for more information.
112 EOF
113 }
114
115 version() {
116         echo "$script_name (wimlib 0.4.8)"
117         exit 0
118 }
119
120 make=disk
121
122 process_command_line() {
123
124         if ! options=$(getopt -o oiw:W:s:O:t:A:hv -l \
125                 only-wim,iso,wim:,windows-dir:,start-script:,overlay:,tmp-dir:,waik-dir:,help,version \
126                                 -- "$@" ); then
127                 usage
128                 exit 1
129         fi
130         eval set -- "$options"
131         while [ $# -gt 0 ]; do
132                 case "$1" in
133                 -i|--iso)
134                         make=iso
135                         ;;
136                 -o|--only-wim)
137                         make=wim
138                         ;;
139                 -W|--windows-dir)
140                         windows_dir="$2"
141                         windows_dir_specified=yes
142                         if [ -n "$waik_dir" ]; then
143                                 echo "ERROR: Cannot specify both --windows-dir and --waik-dir!"
144                                 exit 1
145                         fi
146                         shift
147                         ;;
148                 -A|--waik-dir)
149                         waik_dir="$2"
150                         if [ -n "$windows_dir" ]; then
151                                 echo "ERROR: Cannot specify both --windows-dir and --waik-dir!"
152                                 exit 1
153                         fi
154                         shift
155                         ;;
156                 -w|--wim)
157                         wim="$2"
158                         shift
159                         ;;
160                 -s|--start-script)
161                         start_script="$2"
162                         shift
163                         ;;
164                 -O|--overlay)
165                         overlay="$2"
166                         shift
167                         ;;
168                 -t|--tmp-dir)
169                         rmdir "$tmp_dir"
170                         tmp_dir="$2"
171                         shift
172                         ;;
173                 -h|--help)
174                         usage
175                         exit 0
176                         ;;
177                 -v|--version)
178                         version
179                         ;;
180                 --)
181                         shift
182                         break
183                         ;;
184                 *)
185                         echo "Invalid option \"$1\""
186                         usage
187                         exit 1
188                         ;;
189                 esac
190                 shift
191         done
192         if [ $# -ne 1 ]; then
193                 echo "You must specify the name of the image file to create!"
194                 echo "Run \"$script_name -h\" to see usage information."
195                 exit 1
196         else
197                 image="$1"
198         fi
199 }
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
290         if [ -n "$waik_dir" ]; then
291                 # Get boot files from the WAIK.
292
293                 stat_busy "Copying primary boot files from the Windows Automated Installation Kit ($waik_dir)"
294                 if [ $make = iso ]; then
295                         cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_etfsboot.com -p \
296                                         > "$tmp_dir"/etfsboot.com || stat_fail
297                 fi
298                 cabextract "$waik_dir"/wAIKX86.msi -F F1_BOOTMGR -p \
299                                 > "$tmp_dir"/bootmgr || stat_fail
300                 cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_boot.sdi -p \
301                                 > "$tmp_dir"/boot/boot.sdi || stat_fail
302                 cabextract "$waik_dir"/wAIKX86.msi -F F_WINPE_X86_bcd -p \
303                                 > "$tmp_dir"/boot/bcd || stat_fail
304                 stat_done
305         else
306                 # Get boot files from the Windows ISO
307
308                 stat_busy "Copying primary boot files from mounted Windows DVD ($windows_dir)"
309                 if [ $make = iso ]; then
310                         cp "$windows_dir"/boot/etfsboot.com "$tmp_dir" || stat_fail
311                 fi
312                 cp "$windows_dir"/bootmgr "$tmp_dir" || stat_fail
313                 cp "$windows_dir"/boot/{bcd,boot.sdi} "$tmp_dir"/boot || stat_fail
314                 stat_done
315         fi
316 }
317
318 get_boot_wim() {
319         boot_wim="$1"
320         # Copy the WIM over, or export the 2nd image in the WIM in the case of boot.wim
321         # from the Windows DVD.
322         remove_setup=
323         if [ -z "$wim" ]; then
324
325                 # WIM file unspecified- grab it from the WAIK or the Windows DVD
326                 if [ -n "$waik_dir" ]; then
327                         # WAIK
328                         stat_busy "Extracting boot.wim from \"$waik_dir/WinPE.cab\""
329                         cabextract "$waik_dir/WinPE.cab" -F F1_WINPE.WIM -p \
330                                         > "$boot_wim" 2>/dev/null || stat_fail
331                         stat_done
332                 else
333                         # Windows DVD
334                         remove_setup=yes
335                         wim="$windows_dir/sources/boot.wim"
336                         stat_busy "Exporting image from \"$wim\""
337                         imagex export "$windows_dir"/sources/boot.wim 2 \
338                                                 --compress --boot "$boot_wim" || stat_fail
339                         stat_done
340                 fi
341         else
342                 # WIM file specified
343                 stat_busy "Copying $wim to temporary directory"
344                 cp "$wim" "$boot_wim"|| stat_fail
345                 stat_done
346         fi
347 }
348
349 modify_boot_wim() {
350
351         boot_wim="$1"
352         mnt_dir="$2"
353
354         # Make modifications to the WIM. 
355         stat_busy "Mounting "$1" read-write"
356
357         mkdir -p "$mnt_dir" || stat_fail
358         imagex mountrw "$boot_wim" "$mnt_dir"|| stat_fail
359
360         stat_done
361
362         if [ -n "$remove_setup" ]; then
363                 stat_busy "Renaming setup.exe to prevent it from bothering us"
364                 mv "$mnt_dir"/setup.exe{,.bkup} || stat_fail
365                 mv "$mnt_dir"/sources/setup.exe{,.bkup} || stat_fail
366                 stat_done
367         fi
368
369         if [ -n "$start_script" ]; then
370                 stat_busy "Setting \"$start_script\" as the script to be executed"\
371                                         "when Windows PE boots"
372                 cp "$start_script" "$mnt_dir"|| stat_fail
373                 cat > "$mnt_dir/Windows/System32/winpeshl.ini" << EOF
374 [LaunchApps]
375 %SYSTEMDRIVE%\\$start_script
376 EOF
377                 stat_done
378         fi
379
380         if [ -n "$overlay" ]; then
381                 stat_busy "Overlaying \"$overlay\" on the Windows PE filesystem"
382                 cp -r "$overlay"/* "$mnt_dir"  || stat_fail
383                 stat_done
384         fi
385
386         stat_busy "Rebuilding WIM with changes made"
387         imagex unmount --commit "$mnt_dir" || stat_fail
388         stat_done
389
390         rmdir "$mnt_dir"
391 }
392
393 make_iso_img() {
394
395         image="$1"
396
397         # Make the ISO using the mkisofs command from cdrkit
398
399         stat_busy "Making ISO image \"$image\""
400
401         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE (x86)"  -d -N \
402                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
403                 -hide boot.cat -quiet -o "$image" "$tmp_dir" || stat_fail
404
405         stat_done
406
407 }
408
409 make_disk_img() {
410
411         image="$1"
412
413         stat_busy "Making disk image \"$image\""
414
415         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
416         image_size=$(( image_du + 10000000 ))
417
418         mtool_conf="$(mktemp)"
419
420         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
421                         bs=4096 &> /dev/null
422
423         cat > "$mtool_conf" << EOF
424 MTOOLS_SKIP_CHECK=1
425 MTOOLS_FAT_COMPATIBILITY=1
426 drive s:
427         file="$image"
428 EOF
429
430         export MTOOLSRC="$mtool_conf"
431
432         mformat -h 255 -s 63 -T $(( image_size / 512)) s:
433         mcopy -s "$tmp_dir"/* s:
434
435         syslinux --install "$image"
436         mcopy /usr/lib/syslinux/chain.c32 s:
437         mcopy - 's:syslinux.cfg' << EOF
438         DEFAULT winpe
439         LABEL   winpe
440         COM32   chain.c32
441         APPEND  ntldr=/bootmgr
442 EOF
443         rm -f "$mtool_conf"
444         stat_done
445 }
446
447
448 calc_columns
449 tmp_dir="$(mktemp -d)"
450 mnt_dir="$tmp_dir"/.boot.wim.mount
451 process_command_line "$@"
452 if [ -z "$waik_dir" ]; then
453         find_windows_dir
454 fi
455 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
456         modify_wim=yes
457 fi
458 check_needed_programs
459 trap cleanup exit
460
461 if [ $make != wim ]; then
462         mkdir -p "$tmp_dir"/{boot,sources}
463         get_primary_boot_files
464 fi
465
466 if [ $make = wim ]; then
467         boot_wim="$image"
468 else
469         boot_wim="$tmp_dir"/sources/boot.wim
470 fi
471
472 get_boot_wim "$boot_wim"
473
474 if [ -n "$modify_wim" ]; then
475         modify_boot_wim "$boot_wim" "$mnt_dir"
476 fi
477
478 if [ $make = iso ]; then
479         make_iso_img "$image"
480 elif [ $make = disk ]; then
481         make_disk_img "$image"
482 fi
483
484 echo "The image ($image) is $(stat -c %s "$image") bytes."