42b4a1c5c409e3d6885bb5f176e56d8ce2a862c0
[wimlib] / programs / mkwinpeimg.in
1 #!/usr/bin/env bash
2 #
3 # This script can make a customized bootable image of Windows PE.
4 #
5
6 # Copyright (C) 2012, 2013 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=@VERSION@
24 imagex=@IMAGEX_PROGNAME@
25
26 calc_columns () {
27         STAT_COL=80
28         if [[ -t 0 ]]; then
29                 # stty will fail when stdin isn't a terminal
30                 STAT_COL=$(stty size)
31                 # stty gives "rows cols"; strip the rows number, we just want columns
32                 STAT_COL=${STAT_COL##* }
33         elif tput cols &>/dev/null; then
34                 # is /usr/share/terminfo already mounted, and TERM recognized?
35                 STAT_COL=$(tput cols)
36         fi
37         if (( STAT_COL == 0 )); then
38                 # if output was 0 (serial console), set default width to 80
39                 STAT_COL=80
40         fi
41
42         # we use 13 characters for our own stuff
43         STAT_COL=$(( STAT_COL - 13 ))
44
45         if [[ -t 1 ]]; then
46                 SAVE_POSITION="\e[s"
47                 RESTORE_POSITION="\e[u"
48                 DEL_TEXT="\e[$(( STAT_COL + 4 ))G"
49         else
50                 SAVE_POSITION=""
51                 RESTORE_POSITION=""
52                 DEL_TEXT=""
53         fi
54 }
55
56
57 deltext() {
58         printf "${DEL_TEXT}"
59 }
60
61 stat_busy() {
62         printf "${PREFIX_REG} ${1} "
63         printf "${SAVE_POSITION}"
64         deltext
65         printf "   [BUSY] "
66 }
67
68 stat_done() {
69         deltext
70         printf "   [DONE] \n"
71 }
72
73 stat_fail() {
74         deltext
75         printf "   [FAIL] \n"
76         exit 1
77 }
78
79
80 cleanup() {
81         rm -rf "$tmp_dir"
82 }
83
84 usage() {
85         cat << EOF
86 Usage: $script_name [OPTIONS] IMAGE
87
88   -i, --iso                Make an ISO image instead of a disk image.
89   -o, --only-wim           Make neither a disk image nor an ISO image;
90                               instead, only make a modified boot.wim file.
91   -W, --windows-dir=DIR    Use DIR as the location of the mounted Windows 7
92                               or Windows 8 DVD.  Default is /mnt/windows,
93                               then /mnt/windows7, then /mnt/windows8.
94   -A, --waik-dir=DIR       Get the boot files and boot.wim from the ISO of the
95                               Windows Automated Installation Kit mounted on DIR
96                               instead of from the Windows 7 or Windows 8 DVD.
97                               This also works if the mounted ISO is for the
98                               WAIK supplement rather than the WAIK itself.
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.  This defaults to the
103                               appropriate WIM file from the Windows DVD, WAIK,
104                               or WAIK supplement.
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   -a, --arch=ARCH          Use the Windows PE version from the WAIK that has
109                               the CPU architecture ARCH.  Possible values:
110                               "x86" or "amd64".  Default is "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                         # Need to test Itanium images before making it an
186                         # option.  Note: syslinux is x86 only so can't be used
187                         # for the Itanium disk image.
188                         #elif [ "$2" == "ia64" ]; then
189                                 #arch="IA64"
190                                 #arch_id="2"
191                         elif [ "$2" == "amd64" ]; then
192                                 arch="AMD64"
193                                 arch_id="3"
194                         else
195                                 echo "ERROR: $2 is not a valid arch (x86/amd64)"
196                                 exit 1
197                         fi
198                         shift
199                         ;;
200                 -h|--help)
201                         usage
202                         exit 0
203                         ;;
204                 -v|--version)
205                         version
206                         ;;
207                 --)
208                         shift
209                         break
210                         ;;
211                 *)
212                         echo "Invalid option \"$1\""
213                         usage
214                         exit 1
215                         ;;
216                 esac
217                 shift
218         done
219
220         if [ $# -ne 1 ]; then
221                 echo "You must specify the name of the image file to create!"
222                 echo "Run \"$script_name -h\" to see usage information."
223                 exit 1
224         else
225                 image="$1"
226         fi
227 }
228
229 find_windows_dir() {
230         if [ -z "$windows_dir_specified" ]; then
231                 for windows_dir in /mnt/windows /mnt/windows7 /mnt/windows8; do
232                         if [ -d "$windows_dir"/sources ]; then
233                                 break
234                         fi
235                 done
236         fi
237         if [ ! -d "$windows_dir" ]; then
238                 if [ -z "$windows_dir_specified" ]; then
239                         cat << EOF
240 ERROR: Could not find the directory that the Windows 7 or 8 ISO image is mounted
241 on!  Please specify this directory using the --windows-dir option.
242 EOF
243                 else
244                         echo "ERROR: Could not find the directory \"$windows_dir\"!"
245                 fi
246                 exit 1
247         fi
248         if [ ! -d "$windows_dir/sources" ]; then
249                 cat << EOF
250 ERROR: The directory "$windows_dir" exists, but it seems that the Windows 7 or 8
251 ISO image is not mounted on it.  Please mount the image to continue.
252 EOF
253                 exit 1
254         fi
255 }
256
257 check_needed_programs() {
258         if [ -z "$waik_dir" -o -n "$modify_wim" ]; then
259                 if ! type -P @IMAGEX_PROGNAME@ &> /dev/null ; then
260                         cat << EOF
261 ERROR: To make a customized image of Windows PE, we need the "$imagex" program
262 from "wimlib" so that we can modify the boot.wim file.  However, "$imagex"
263 doesn't seem to be installed.  Please install "wimlib" to continue.
264 EOF
265                         exit 1
266                 fi
267         fi
268
269         if [ $make = iso ]; then
270                 if ! type -P mkisofs &> /dev/null ; then
271                         cat << EOF
272 ERROR: To make a bootable ISO image of Windows PE, we need the "mkisofs"
273 program, but it doesn't seem to be installed.  Please install the "cdrkit"
274 package to continue, or try omitting the --iso option to make a disk image
275 instead of an ISO image.
276 EOF
277                         exit 1
278                 fi
279         elif [ $make = disk ] ; then
280                 if ! type -P syslinux &> /dev/null ; then
281                         cat << EOF
282 ERROR: To make a bootable disk image of Windows PE, we need the "syslinux"
283 program, but it doesn't seem to be installed.  Please install the "syslinux"
284 package to continue, or try using the --iso option to make an ISO image instead
285 of a disk image.
286 EOF
287                         exit 1
288                 fi
289
290                 if ! type -P mformat mcopy &> /dev/null; then
291                         cat << EOF
292 ERROR: To make a bootable disk image of Windows PE, we need the "mformat" and
293 "mcopy" programs from the "mtools" package.  These programs allow us to
294 format a FAT filesystem and copy files to it without needing root privileges.
295 Please install "mtools" if you want to make a disk image of Windows PE.  Or,
296 try using the --iso option to make an ISO image instead of a disk image.
297 EOF
298                 fi
299         fi
300
301         if [ -n "$waik_dir" ] &&  [ -f "$waik_dir"/wAIK${arch}.msi ]; then
302                 if ! type -P cabextract &> /dev/null ; then
303                         cat << EOF
304 ERROR: The boot files in the Windows Automated Installation Kit (WAIK) are
305 inside cabinet archives.  To extract these files, we need the "cabextract"
306 program, but it doesn't seem to be installed.  Please install "cabextract" to
307 continue.
308 EOF
309                         exit 1
310                 fi
311         fi
312
313 }
314
315 get_primary_boot_files() {
316         if [ -n "$waik_dir" ]; then
317                 # Get boot files from the WAIK.
318                 stat_busy "Copying primary boot files from the Windows Automated Installation Kit ($waik_dir, $arch)"
319
320                 if [ -f "$waik_dir"/wAIK${arch}.msi ]; then
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                 # The WAIK supplement disc has a different structure
332                 else
333                         # Note: fuseiso, mount default to map=normal i.e. lowercase
334                         if [ $make = iso ]; then
335                                 cp "$waik_dir"/${arch,,}/boot/etfsboot.com $tmp_dir/etfsboot.com || stat_fail
336                         fi
337                         cp "$waik_dir"/${arch,,}/bootmgr $tmp_dir/bootmgr || stat_fail
338                         cp "$waik_dir"/${arch,,}/boot/boot.sdi $tmp_dir/boot/boot.sdi || stat_fail
339                         cp "$waik_dir"/${arch,,}/boot/bcd $tmp_dir/boot/bcd || stat_fail
340                 fi
341                 stat_done
342         else
343                 # Get boot files from the Windows ISO
344
345                 stat_busy "Copying primary boot files from mounted Windows DVD ($windows_dir)"
346                 if [ $make = iso ]; then
347                         cp "$windows_dir"/boot/etfsboot.com "$tmp_dir" || stat_fail
348                 fi
349                 cp "$windows_dir"/bootmgr "$tmp_dir" || stat_fail
350                 cp "$windows_dir"/boot/{bcd,boot.sdi} "$tmp_dir"/boot || stat_fail
351                 stat_done
352         fi
353 }
354
355 get_boot_wim() {
356         boot_wim="$1"
357         # Copy the WIM over, or export the 2nd image in the WIM in the case of boot.wim
358         # from the Windows DVD.
359         remove_setup=
360         if [ -z "$wim" ]; then
361
362                 # WIM file unspecified- grab it from the WAIK or the Windows DVD
363                 if [ -n "$waik_dir" ]; then
364                         # WAIK
365                         if [ -f "$waik_dir/WinPE.cab" ]; then
366                                 stat_busy "Extracting boot.wim from \"$waik_dir/WinPE.cab\""
367                                 cabextract "$waik_dir/WinPE.cab" -F F${arch_id}_WINPE.WIM -p \
368                                                 > "$boot_wim" 2>/dev/null || stat_fail
369                         # WAIK supplement has different layout
370                         else
371                                 stat_busy "Copying boot.wim from \"${waik_dir}/${arch,,}/winpe.wim\""
372                                 cp "$waik_dir"/${arch,,}/winpe.wim "$boot_wim" || stat_fail
373                                 chmod +w "$boot_wim"
374                         fi
375                         stat_done
376                 else
377                         # Windows DVD
378                         remove_setup=yes
379                         wim="$windows_dir/sources/boot.wim"
380                         stat_busy "Exporting image from \"$wim\""
381                         "$imagex" export "$windows_dir"/sources/boot.wim 2 \
382                                                 --boot "$boot_wim" || stat_fail
383                         stat_done
384                 fi
385         else
386                 # WIM file specified
387                 stat_busy "Copying $wim to temporary directory"
388                 cp "$wim" "$boot_wim"|| stat_fail
389                 stat_done
390         fi
391 }
392
393 # Make modifications to the WIM.
394 modify_boot_wim() {
395         boot_wim="$1"
396         tmp_dir="$2"
397
398         exec 3>"$tmp_dir/__mkwinpeimg.update.cmds"
399
400         if [ -n "$remove_setup" ]; then
401                 stat_busy "Renaming setup.exe to prevent it from bothering us"
402                 cat 1>&3 <<- EOF
403                         rename /setup.exe /setup.exe.orig
404                         rename /sources/setup.exe /sources/setup.exe.orig
405                 EOF
406                 stat_done
407         fi
408
409         if [ -n "$start_script" ]; then
410                 stat_busy "Setting \"$start_script\" as the script to be executed when Windows PE boots"
411                 start_script_base="$(basename "$start_script")"
412                 cat > "$tmp_dir/__mkwinpeimg.winpeshl.ini" <<- EOF
413                         [LaunchApps]
414                         %SYSTEMDRIVE%\\$start_script_base
415                 EOF
416                 cat 1>&3 <<- EOF
417                         add '$start_script' '/$start_script_base'
418                         delete --force /Windows/System32/winpeshl.ini
419                         add '$tmp_dir/__mkwinpeimg.winpeshl.ini' /Windows/System32/winpeshl.ini
420                 EOF
421                 stat_done
422         fi
423
424         if [ -n "$overlay" ]; then
425                 stat_busy "Overlaying \"$overlay\" on the Windows PE filesystem"
426                 cat 1>&3 <<- EOF
427                         add '$overlay' /
428                 EOF
429                 stat_done
430         fi
431
432         exec 3>&-
433
434         stat_busy "Rebuilding WIM with changes made"
435         "$imagex" update "$boot_wim" --rebuild \
436                 < "$tmp_dir/__mkwinpeimg.update.cmds" > /dev/null || stat_fail
437         stat_done
438 }
439
440 make_iso_img() {
441         image="$1"
442
443         # Make the ISO using the mkisofs command from cdrkit
444
445         stat_busy "Making ISO image \"$image\""
446
447         mkisofs -sysid ""  -A ""  -V "Microsoft Windows PE ($arch)"  -d -N \
448                 -b etfsboot.com  -no-emul-boot   -c boot.cat  -hide etfsboot.com  \
449                 -hide boot.cat -quiet -o "$image" "$tmp_dir" || stat_fail
450
451         stat_done
452 }
453
454 make_disk_img() {
455         image="$1"
456
457         stat_busy "Making disk image \"$image\""
458
459         image_du=$(du -s -b "$tmp_dir" | cut -f 1)
460         image_size=$(( image_du + 10000000 ))
461
462         mtool_conf="$(mktemp)"
463
464         dd if=/dev/zero of="$image" count=$(( (image_size + 4095) / 4096)) \
465                         bs=4096 &> /dev/null
466
467         cat > "$mtool_conf" <<- EOF
468                 MTOOLS_SKIP_CHECK=1
469                 MTOOLS_FAT_COMPATIBILITY=1
470                 drive s:
471                         file="$image"
472         EOF
473
474         export MTOOLSRC="$mtool_conf"
475
476         mformat -h 255 -s 63 -T $(( image_size / 512)) s: || stat_fail
477         mcopy -s "$tmp_dir"/* s: || stat_fail
478
479         syslinux --install "$image"
480
481         syslinuxdir="/usr/lib/syslinux"
482
483         if [ -d "$syslinuxdir/bios" ]; then
484                 biosdir="$syslinuxdir/bios"
485         else
486                 biosdir="$syslinuxdir"
487         fi
488
489         mcopy "$biosdir/chain.c32" s: || stat_fail
490         if [ -e "$biosdir/libcom32.c32" ]; then
491                 mcopy "$biosdir/libcom32.c32" s:
492         fi
493         if [ -e "$biosdir/libutil.c32" ]; then
494                 mcopy "$biosdir/libutil.c32" s:
495         fi
496         mcopy - 's:syslinux.cfg' <<- EOF
497                 DEFAULT winpe
498                 LABEL   winpe
499                 COM32   chain.c32
500                 APPEND  ntldr=/bootmgr
501         EOF
502         rm -f "$mtool_conf"
503         stat_done
504 }
505
506 calc_columns
507 tmp_dir="$(mktemp -d)"
508 process_command_line "$@"
509 if [ -z "$waik_dir" ]; then
510         find_windows_dir
511 fi
512 if [ -n "$start_script" -o -n "$overlay" -o -n "$remove_setup" ]; then
513         modify_wim=yes
514 fi
515 check_needed_programs
516 trap cleanup exit
517
518 if [ $make != wim ]; then
519         mkdir -p "$tmp_dir"/{boot,sources}
520         get_primary_boot_files
521 fi
522
523 if [ $make = wim ]; then
524         boot_wim="$image"
525 else
526         boot_wim="$tmp_dir"/sources/boot.wim
527 fi
528
529 get_boot_wim "$boot_wim"
530
531 if [ -n "$modify_wim" ]; then
532         modify_boot_wim "$boot_wim" "$tmp_dir"
533 fi
534
535 if [ $make = iso ]; then
536         make_iso_img "$image"
537 elif [ $make = disk ]; then
538         make_disk_img "$image"
539 fi
540
541 echo "The image ($image) is $(stat -c %s "$image") bytes."