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