diff --git a/.gitignore b/.gitignore index bbed26a..2602432 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .rvmrc .vagrant +/ievms-control* \ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 93b944e..0000000 --- a/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# coding: utf-8 - -source :rubygems - -gem "vagrant" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 43f551c..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,29 +0,0 @@ -GEM - remote: http://rubygems.org/ - specs: - archive-tar-minitar (0.5.2) - childprocess (0.3.7) - ffi (~> 1.0, >= 1.0.6) - erubis (2.7.0) - ffi (1.3.1) - i18n (0.6.1) - json (1.5.4) - log4r (1.1.10) - net-scp (1.0.4) - net-ssh (>= 1.99.1) - net-ssh (2.2.2) - vagrant (1.0.6) - archive-tar-minitar (= 0.5.2) - childprocess (~> 0.3.1) - erubis (~> 2.7.0) - i18n (~> 0.6.0) - json (~> 1.5.1) - log4r (~> 1.1.9) - net-scp (~> 1.0.4) - net-ssh (~> 2.2.2) - -PLATFORMS - ruby - -DEPENDENCIES - vagrant diff --git a/README.md b/README.md index 1a0a155..f5b54e8 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,8 @@ Overview Microsoft provides virtual machine disk images to facilitate website testing in multiple versions of IE, regardless of the host operating system. -~~Unfortunately, setting these virtual machines up without Microsoft's VirtualPC -can be extremely difficult. The ievms scripts aim to facilitate that process using -VirtualBox on Linux or OS X.~~ With a single command, you can have IE6, IE7, IE8, -IE9 and IE10 running in separate virtual machines. - -**NOTE:** As of Feb. 1st, 2013, the [MS images](http://www.modern.ie/virtualization-tools) -are fully compatible with Virtualbox, thanks to the [modern.IE](http://modern.IE) -project. +With a single command, you can have IE6, IE7, IE8, +IE9, IE10, IE11 and MSEdge running in separate virtual machines. [![Click here to lend your support to ievms and make a donation at pledgie.com!](http://pledgie.com/campaigns/15995.png?skin_name=chrome)](http://pledgie.com/campaigns/15995) @@ -18,88 +12,40 @@ project. Quickstart ========== -Just paste this into a terminal: `curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | bash` +Just paste this into a terminal: + + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | bash Requirements ============ -* VirtualBox (http://virtualbox.org) +* VirtualBox > 5.0 (http://virtualbox.org), select 'command line utilities' during installation * Curl (Ubuntu: `sudo apt-get install curl`) * Linux Only: unar (Ubuntu: `sudo apt-get install unar`) * Patience - -Disk requirements ------------------ - -A full ievms install will require approximately 37G: - - Servo:.ievms xdissent$ du -ch * - 5.7G IE10 - Win8-disk1.vmdk - 2.6G IE10 - Win8.ova - 2.5G IE10_Win8.zip - 1.5G IE6 - WinXP-disk1.vmdk - 724M IE6 - WinXP.ova - 717M IE6_WinXP.zip - 1.6G IE7 - WinXP-disk1.vmdk - 15M IE7-WindowsXP-x86-enu.exe - 1.6G IE8 - WinXP-disk1.vmdk - 16M IE8-WindowsXP-x86-ENU.exe - 10G IE9 - Win7-disk1.vmdk - 4.7G IE9 - Win7.ova - 4.7G IE9_Win7.zip - 3.4M ievms-control.iso - 4.6M lsar - 4.5M unar - 4.1M unar1.5.zip - 37G total - -You may remove all files except `*.vmdk` after installation and they will be -re-downloaded if ievms is run again in the future: - - $ find ~/.ievms -type f ! -name "*.vmdk" -exec rm {} \; - -If all installation related files are removed, around 21G is required: - - Servo:.ievms xdissent$ du -ch * - 5.7G IE10 - Win8-disk1.vmdk - 1.5G IE6 - WinXP-disk1.vmdk - 1.6G IE7 - WinXP-disk1.vmdk - 1.6G IE8 - WinXP-disk1.vmdk - 10G IE9 - Win7-disk1.vmdk - 21G total - - -Bandwidth requirements ----------------------- - -A full installation will download roughly 7.5G of data. - -**NOTE:** Reusing the XP VM for IE7 and IE8 (the default) saves an incredible -amount of space and bandwidth. If it is disabled (`REUSE_XP=no`) the disk space -required climbs to 74G (39G if cleaned post-install) and around 17G will be -downloaded. +**NOTE** Use [ievms version 0.2.1](https://github.com/xdissent/ievms/raw/v0.2.1/ievms.sh) for VirtualBox < 5.0. Installation ============ -1. Install VirtualBox (make sure command line utilities are selected and installed). +**1.)** Install [VirtualBox](http://virtualbox.org) and check the [Requirements](#requirements) -2. Download and unpack ievms: +**2.)** Download and unpack ievms: - * Install IE versions 6, 7, 8, 9 and 10. + * To install IE versions 6, 7, 8, 9, 10, 11 and EDGE use: - curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | bash + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | bash - * Install specific IE versions (IE7 and IE9 only for example): + * To install specific IE versions (IE7, IE9 and EDGE only for example) use: - curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | IEVMS_VERSIONS="7 9" bash + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | env IEVMS_VERSIONS="7 9 EDGE" bash -3. Launch Virtual Box. +**3.)** Launch Virtual Box. -4. Choose ievms image from Virtual Box. +**4.)** Choose ievms image from Virtual Box. The OVA images are massive and can take hours or tens of minutes to download, depending on the speed of your internet connection. You might want @@ -121,7 +67,7 @@ Specifying the install path To specify where the VMs are installed, use the `INSTALL_PATH` variable: - curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | INSTALL_PATH="/Path/to/.ievms" bash + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | env INSTALL_PATH="/Path/to/.ievms" bash Passing additional options to curl @@ -130,12 +76,73 @@ Passing additional options to curl The `curl` command is passed any options present in the `CURL_OPTS` environment variable. For example, you can set a download speed limit: - curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | CURL_OPTS="--limit-rate 50k" bash + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | env CURL_OPTS="--limit-rate 50k" bash + + +Disk requirements +----------------- + +A full ievms install will require approximately 69G: + + Servo:.ievms xdissent$ du -ch * + 11G IE10 - Win7-disk1.vmdk + 22M IE10-Windows6.1-x86-en-us.exe + 11G IE11 - Win7-disk1.vmdk + 28M IE11-Windows6.1-x86-en-us.exe + 1.5G IE6 - WinXP-disk1.vmdk + 724M IE6 - WinXP.ova + 717M IE6_WinXP.zip + 1.6G IE7 - WinXP-disk1.vmdk + 15M IE7-WindowsXP-x86-enu.exe + 1.6G IE8 - WinXP-disk1.vmdk + 16M IE8-WindowsXP-x86-ENU.exe + 11G IE9 - Win7-disk1.vmdk + 4.7G IE9 - Win7.ova + 4.7G IE9_Win7.zip + 10G MSEdge - Win10-disk1.vmdk + 5.1G MSEdge - Win10.ova + 5.0G MSEdge_Win10.zip + 3.4M ievms-control-0.3.0.iso + 4.6M lsar + 4.5M unar + 4.1M unar1.5.zip + 69G total + +You may remove all files except `*.vmdk` after installation and they will be +re-downloaded if ievms is run again in the future: + + $ find ~/.ievms -type f ! -name "*.vmdk" -exec rm {} \; + +If all installation related files are removed, around 47G is required: + + Servo:.ievms xdissent$ du -ch * + 11G IE10 - Win7-disk1.vmdk + 11G IE11 - Win7-disk1.vmdk + 1.5G IE6 - WinXP-disk1.vmdk + 1.6G IE7 - WinXP-disk1.vmdk + 1.6G IE8 - WinXP-disk1.vmdk + 11G IE9 - Win7-disk1.vmdk + 10G MSEdge - Win10-disk1.vmdk + 47G total + + +Bandwidth requirements +---------------------- + +A full installation will download roughly 12.5G of data. + +**NOTE:** Reusing the XP VM for IE7 and IE8 (the default) saves an incredible +amount of space and bandwidth. If it is disabled (`REUSE_XP=no`) the disk space +required climbs to 95G (49G if cleaned post-install) and around 22G will be +downloaded. Reusing the Win7 VM on the other hand (also the default), saves +tons of bandwidth but pretty much breaks even on disk space. Disable it with +`REUSE_WIN7=no`. Features ======== + Clean Snapshot -------------- @@ -146,6 +153,14 @@ you can simply revert to the `clean` snapshot to reset your VM to the initial state. +Guest Control +------------- + +VirtualBox guest additions are installed after each virtual machine is created +(and before the clean snapshot) and the appropriate steps are taken to enable +guest control from the host machine. + + Resuming Downloads ------------------ @@ -159,7 +174,7 @@ Reusing XP VMs IE7 and IE8 ship from MS on Vista and Win7 respectively. Both of these images are far larger than the IE6 XP image, which also technically supports -IE7 and IE8. To save bandwidth, space and time, ievms will will reuse +IE7 and IE8. To save bandwidth, space and time, ievms will reuse (duplicate) the IE6 XP VM image for both. Virtualbox guest control is used to run the appropriate IE installer within the VM. The `clean` snapshot includes the updated browser version. @@ -167,7 +182,27 @@ includes the updated browser version. **NOTE:** If you'd like to disable XP VM reuse for IE7 and IE8, set the environment variable `REUSE_XP` to anything other than `yes`: - curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | REUSE_XP="no" bash + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | env REUSE_XP="no" bash + + +Reusing Win7 VMs +---------------- + +Currently there exists a [bug](https://www.virtualbox.org/ticket/11134) in +VirtualBox (or possibly elsewhere) that disables guest control after a Windows 8 +virtual machine's state is saved. To better support guest control and to +eliminate yet another image download, ievms will re-use the IE9 Win7 image for +IE10 and IE11 by default. In addition, the Win7 VMs are the only ones which can +be successfully "rearmed" to extend the activation period. + +**NOTE:** If you'd like to disable Win7 VM reuse for IE10, set the environment +variable `REUSE_WIN7` to anything other than `yes`: + + curl -s https://raw.githubusercontent.com/xdissent/ievms/master/ievms.sh | REUSE_WIN7="no" bash + + +**NOTE:** It is currently impossible to install IE11 **without** reusing the +Win7 virtual machine. Control ISO diff --git a/control/build.sh b/control/build.sh index bdeae5e..c7be53b 100755 --- a/control/build.sh +++ b/control/build.sh @@ -21,7 +21,7 @@ install_packages() { } download_cross_compiler() { - url="http://landley.net/aboriginal/downloads/binaries/cross-compiler-i686.tar.bz2" + url="http://landley.net/aboriginal/downloads/binaries/cross-compiler-i686.tar.gz" archive=`basename "${url}"` log "Downloading cross compiler archive from ${url} to ${ievms_home}/${archive}" if [[ ! -e "${archive}" ]] && ! curl -L "${url}" -o "${archive}" @@ -31,9 +31,9 @@ download_cross_compiler() { } extract_cross_compiler() { - cross_compiler=`basename "${archive}" .tar.bz2` + cross_compiler=`basename "${archive}" .tar.gz` log "Extracting cross compiler archive from ${archive} to ${ievms_home}/${cross_compiler}" - if [[ ! -e "${cross_compiler}" ]] && ! tar -jxf "${archive}" + if [[ ! -e "${cross_compiler}" ]] && ! tar -zxf "${archive}" then fail "Failed to extract ${archive} to ${ievms_home}/${cross_compiler} using 'tar', error code ($?)" fi @@ -46,7 +46,7 @@ download_kernel() { if [[ ! -e "${archive}" ]] && ! curl -L "${url}" -o "${archive}" then fail "Failed to download ${url} to ${ievms_home}/${archive} using 'curl', error code ($?)" - fi + fi } extract_kernel() { @@ -55,7 +55,7 @@ extract_kernel() { if [[ ! -e "${kernel_src}" ]] && ! tar -jxf "${archive}" then fail "Failed to extract ${archive} to ${ievms_home}/${kernel_src} using 'tar', error code ($?)" - fi + fi } configure_kernel() { @@ -82,7 +82,7 @@ download_iso() { if [[ ! -e "${archive}" ]] && ! curl -L "${url}" -o "${archive}" then fail "Failed to download ${url} to ${ievms_home}/${archive} using 'curl', error code ($?)" - fi + fi } extract_iso() { @@ -91,7 +91,7 @@ extract_iso() { if [[ ! -e "${iso}" ]] && ! unzip "${archive}" then fail "Failed to extract ${archive} to ${ievms_home}/${iso} using 'unzip', error code ($?)" - fi + fi } unpack_iso() { @@ -118,7 +118,15 @@ extract_initrd() { copy_scripts() { log "Copying scripts" cp "/vagrant/control/stage2" "${initrd}/scripts/" - cp "/vagrant/control/ievms.reg" "${initrd}/scripts/" + cp "/vagrant/control/xpsw.reg" "${initrd}/scripts/" + cp "/vagrant/control/xpusr.reg" "${initrd}/scripts/" + cp "/vagrant/control/xpvboxga.bat" "${initrd}/scripts/" + cp "/vagrant/control/deuac.reg" "${initrd}/scripts/" + cp "/vagrant/control/reuac.reg" "${initrd}/scripts/" + cp "/vagrant/control/vboxga.bat" "${initrd}/scripts/" + cp "/vagrant/control/vsint.cer" "${initrd}/scripts/" + cp "/vagrant/control/ievms.xml" "${initrd}/scripts/" + cp "/vagrant/control/ievms.bat" "${initrd}/scripts/" cp "/vagrant/control/isolinux.cfg" "${remaster_iso}/isolinux.cfg" cp "/vagrant/control/isolinux.cfg" "${remaster_iso}/syslinux.cfg" cp "${kernel}" "${remaster_iso}/" @@ -138,7 +146,7 @@ compress_initrd() { pack_iso() { iso_out="/vagrant/ievms-control.iso" log "Packing ievms ISO from ${remaster_iso} to ${iso_out}" - if ! genisoimage -o "${iso_out}" -b isolinux.bin -c boot.cat -p "ievms" -no-emul-boot -boot-load-size 4 -boot-info-table -V "IEVMS" -cache-inodes -r -J -l -joliet-long "${remaster_iso}" + if ! genisoimage -o "${iso_out}" -b isolinux.bin -c boot.cat -p "ievms" -no-emul-boot -boot-load-size 4 -boot-info-table -V "IEVMS" -cache-inodes -r -J -l -joliet-long "${remaster_iso}" then fail "Failed to pack ${remaster_iso} to ${iso_out} using 'genisoimage', error code ($?)" fi @@ -159,4 +167,4 @@ unpack_iso extract_initrd copy_scripts compress_initrd -pack_iso \ No newline at end of file +pack_iso diff --git a/control/deuac.reg b/control/deuac.reg new file mode 100755 index 0000000..482afc8 --- /dev/null +++ b/control/deuac.reg @@ -0,0 +1,5 @@ +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] +"EnableLUA"=dword:00000000 +"ConsentPromptBehaviorAdmin"=dword:00000000 diff --git a/control/ievms.bat b/control/ievms.bat new file mode 100644 index 0000000..73369ee --- /dev/null +++ b/control/ievms.bat @@ -0,0 +1 @@ +if exist C:\Users\IEUser\ievms.bat C:\Users\IEUser\ievms.bat \ No newline at end of file diff --git a/control/ievms.reg b/control/ievms.reg deleted file mode 100644 index b02d8c0..0000000 Binary files a/control/ievms.reg and /dev/null differ diff --git a/control/ievms.xml b/control/ievms.xml new file mode 100644 index 0000000..c184f58 --- /dev/null +++ b/control/ievms.xml @@ -0,0 +1,39 @@ + + + + 2013-06-20T12:19:16.22375 + IE9Win7\IEUser + + + + + IE9Win7\IEUser + Password + HighestAvailable + + + + IgnoreNew + false + false + false + false + false + + true + false + + true + true + false + false + false + PT0S + 7 + + + + C:\ievms.bat + + + \ No newline at end of file diff --git a/control/reuac.reg b/control/reuac.reg new file mode 100755 index 0000000..5458616 Binary files /dev/null and b/control/reuac.reg differ diff --git a/control/stage2 b/control/stage2 index ccd3ecd..3b00413 100755 --- a/control/stage2 +++ b/control/stage2 @@ -4,7 +4,33 @@ mkdir /tmp /disk chmod 777 /tmp sh /scripts/prepdriver.sh mdev -s -ntfs-3g /dev/sda1 /disk -o rw,noatime -reged -IC /disk/WINDOWS/system32/config/system HKEY_LOCAL_MACHINE\\SYSTEM /scripts/ievms.reg + +if [ -b /dev/sda2 ] +then + ntfs-3g /dev/sda2 /disk -o rw,noatime +else + ntfs-3g /dev/sda1 /disk -o rw,noatime +fi + +if [ -d /disk/WINDOWS ] +then + reged -IC /disk/WINDOWS/system32/config/software HKEY_LOCAL_MACHINE\\SOFTWARE /scripts/xpsw.reg + reged -IC /disk/Documents\ and\ Settings/IEUser/NTUSER.DAT HKEY_USERS\\S-1-5-21-776561741-308236825-1417001333-1003 /scripts/xpusr.reg + cp /scripts/xpvboxga.bat /disk/Documents\ and\ Settings/All\ Users/Start\ Menu/Programs/Startup/ +else + reged -IC /disk/Windows/System32/config/SOFTWARE HKEY_LOCAL_MACHINE\\SOFTWARE /scripts/deuac.reg + + if [ -d /disk/ProgramData/Microsoft/Windows/Start\ Menu/Programs/Startup ] + then + cp /scripts/vboxga.bat /disk/ProgramData/Microsoft/Windows/Start\ Menu/Programs/Startup/ + else + cp /scripts/vboxga.bat /disk/ProgramData/Microsoft/Windows/Start\ Menu/Programs/StartUp/ + fi + + cp /scripts/vsint.cer /disk/vsint.cer + cp /scripts/reuac.reg /disk/reuac.reg + cp /scripts/ievms.xml /disk/ievms.xml + cp /scripts/ievms.bat /disk/ievms.bat +fi umount /disk 2>/dev/null -poweroff \ No newline at end of file +poweroff diff --git a/control/vboxga.bat b/control/vboxga.bat new file mode 100644 index 0000000..9a5b831 --- /dev/null +++ b/control/vboxga.bat @@ -0,0 +1,16 @@ +cd /d D:\ +cd \cert +VBoxCertUtil.exe add-trusted-publisher vbox-sha1.cer --root C:\vsint.cer +del C:\vsint.cer +cd \ +VBoxWindowsAdditions.exe /S +regedit.exe /S C:\reuac.reg +del C:\reuac.reg +FOR /F "usebackq" %%i IN (`hostname`) DO SET HOST=%%i +schtasks.exe /create /s %HOST% /ru %HOST%\IEUser /rp Passw0rd! /tn ievms /xml C:\ievms.xml +echo slmgr.vbs /ato >C:\Users\IEUser\ievms.bat +schtasks.exe /run /tn ievms +timeout /t 30 +del C:\Users\IEUser\ievms.bat +shutdown.exe /s /t 00 +del %0 diff --git a/control/vsint.cer b/control/vsint.cer new file mode 100644 index 0000000..dc95f4e Binary files /dev/null and b/control/vsint.cer differ diff --git a/control/xpsw.reg b/control/xpsw.reg new file mode 100644 index 0000000..4dd43bb Binary files /dev/null and b/control/xpsw.reg differ diff --git a/control/xpusr.reg b/control/xpusr.reg new file mode 100644 index 0000000..64f9eb4 Binary files /dev/null and b/control/xpusr.reg differ diff --git a/control/xpvboxga.bat b/control/xpvboxga.bat new file mode 100644 index 0000000..1b9411b --- /dev/null +++ b/control/xpvboxga.bat @@ -0,0 +1,4 @@ +cd /d D:\ +VBoxWindowsAdditions.exe /S +shutdown.exe /s /t 00 +del %0 diff --git a/ievms.sh b/ievms.sh index 9441f92..521b17f 100755 --- a/ievms.sh +++ b/ievms.sh @@ -1,31 +1,110 @@ #!/usr/bin/env bash -# Caution is a virtue +# Caution is a virtue. set -o nounset set -o errtrace set -o errexit set -o pipefail +# ## Global Variables + +# The ievms version. +ievms_version="0.3.3" + +# Options passed to each `curl` command. curl_opts=${CURL_OPTS:-""} + +# Reuse XP virtual machines for IE versions that are supported. reuse_xp=${REUSE_XP:-"yes"} -log() { printf "$*\n" ; return $? ; } +# Reuse Win7 virtual machines for IE versions that are supported. +reuse_win7=${REUSE_WIN7:-"yes"} + +# Timeout interval to wait between checks for various states. +sleep_wait="5" + +# Store the original `cwd`. +orig_cwd=`pwd` + +# The VM user to use for guest control. +guest_user="IEUser" + +# The VM user password to use for guest control. +guest_pass="Passw0rd!" + +# ## Utilities +# Print a message to the console. +log() { printf '%s\n' "$*" ; return $? ; } + +# Print an error message to the console and bail out of the script. fail() { log "\nERROR: $*\n" ; exit 1 ; } +check_md5() { + local md5 + + case $kernel in + Darwin) md5=`md5 "${1}" | rev | cut -c-32 | rev` ;; + Linux) md5=`md5sum "${1}" | cut -c-32` ;; + esac + + if [ "${md5}" != "${2}" ] + then + log "MD5 check failed for ${1} (wanted ${2}, got ${md5})" + return 1 + fi + + log "MD5 check succeeded for ${1}" +} + +# Download a URL to a local file. Accepts a name, URL and file. +download() { # name url path md5 + local attempt=${5:-"0"} + local max=${6:-"3"} + + let attempt+=1 + + if [[ -f "${3}" ]] + then + log "Found ${1} at ${3} - skipping download" + check_md5 "${3}" "${4}" && return 0 + log "Check failed - redownloading ${1}" + rm -f "${3}" + fi + + log "Downloading ${1} from ${2} to ${3} (attempt ${attempt} of ${max})" + curl ${curl_opts} -L "${2}" -o "${3}" || fail "Failed to download ${2} to ${ievms_home}/${3} using 'curl', error code ($?)" + check_md5 "${3}" "${4}" && return 0 + + if [ "${attempt}" == "${max}" ] + then + echo "Failed to download ${2} to ${ievms_home}/${3} (attempt ${attempt} of ${max})" + return 1 + fi + + log "Redownloading ${1}" + download "${1}" "${2}" "${3}" "${4}" "${attempt}" "${max}" +} + +# ## General Setup + +# Create the ievms home folder and `cd` into it. The `INSTALL_PATH` env variable +# is used to determine the full path. The home folder is then added to `PATH`. create_home() { - def_ievms_home="${HOME}/.ievms" + local def_ievms_home="${HOME}/.ievms" ievms_home=${INSTALL_PATH:-$def_ievms_home} mkdir -p "${ievms_home}" cd "${ievms_home}" - # Move old ovas and zips into place: + PATH="${PATH}:${ievms_home}" + + # Move ovas and zips from a very old installation into place. mv -f ./ova/IE*/IE*.{ova,zip} "${ievms_home}/" 2>/dev/null || true } +# Check for a supported host system (Linux/OS X). check_system() { - # Check for supported system kernel=`uname -s` case $kernel in Darwin|Linux) ;; @@ -33,16 +112,19 @@ check_system() { esac } +# Ensure VirtualBox is installed and `VBoxManage` is on the `PATH`. check_virtualbox() { log "Checking for VirtualBox" - hash VBoxManage 2>&- || fail "VirtualBox command line utilities are not installed, please reinstall! (http://virtualbox.org)" + hash VBoxManage 2>&- || fail "VirtualBox command line utilities are not installed, please (re)install! (http://virtualbox.org)" } +# Determine the VirtualBox version details, querying the download page to ensure +# validity. check_version() { - version=`VBoxManage -v` + local version=`VBoxManage -v` major_minor_release="${version%%[-_r]*}" - major_minor="${version%.*}" - dl_page=`curl ${curl_opts} -L "http://download.virtualbox.org/virtualbox/" 2>/dev/null` + local major_minor="${version%.*}" + local dl_page=`curl ${curl_opts} -L "http://download.virtualbox.org/virtualbox/" 2>/dev/null` if [[ "$version" == *"kernel module is not loaded"* ]]; then fail "$version" @@ -56,73 +138,220 @@ check_version() { log "Virtualbox version ${major_minor_release} found." break else - log "Virtualbox version ${major_minor_release} not found - skipping." + log "Virtualbox version ${major_minor_release} not found, skipping." fi done } +# Check for the VirtualBox Extension Pack and install if not found. check_ext_pack() { log "Checking for Oracle VM VirtualBox Extension Pack" if ! VBoxManage list extpacks | grep "Oracle VM VirtualBox Extension Pack" then check_version - archive="Oracle_VM_VirtualBox_Extension_Pack-${major_minor_release}.vbox-extpack" - url="http://download.virtualbox.org/virtualbox/${major_minor_release}/${archive}" + local archive="Oracle_VM_VirtualBox_Extension_Pack-${major_minor_release}.vbox-extpack" + local url="http://download.virtualbox.org/virtualbox/${major_minor_release}/${archive}" + local md5s="https://www.virtualbox.org/download/hashes/${major_minor_release}/MD5SUMS" + local md5=`curl ${curl_opts} -L "${md5s}" | grep "${archive}" | cut -c-32` - if [[ ! -f "${archive}" ]] - then - log "Downloading Oracle VM VirtualBox Extension Pack from ${url} to ${ievms_home}/${archive}" - if ! curl ${curl_opts} -L "${url}" -o "${archive}" - then - fail "Failed to download ${url} to ${ievms_home}/${archive} using 'curl', error code ($?)" - fi - fi + download "Oracle VM VirtualBox Extension Pack" "${url}" "${archive}" "${md5}" log "Installing Oracle VM VirtualBox Extension Pack from ${ievms_home}/${archive}" - if ! VBoxManage extpack install "${archive}" - then - fail "Failed to install Oracle VM VirtualBox Extension Pack from ${ievms_home}/${archive}, error code ($?)" - fi + VBoxManage extpack install "${archive}" || fail "Failed to install Oracle VM VirtualBox Extension Pack from ${ievms_home}/${archive}, error code ($?)" fi } -download_unar() { - unar_url="http://theunarchiver.googlecode.com/files/unar1.5.zip" - unar_archive=`basename "${unar_url}"` +# Download and install `unar` from Google Code. +install_unar() { + local url="http://unarchiver.c3.cx/downloads/unar1.10.1.zip" + local archive=`basename "${url}"` - log "Downloading unar from ${unar_url} to ${ievms_home}/${unar_archive}" - if [[ ! -f "${unar_archive}" ]] && ! curl ${curl_opts} -L "${unar_url}" -o "${unar_archive}" - then - fail "Failed to download ${unar_url} to ${ievms_home}/${unar_archive} using 'curl', error code ($?)" - fi + download "unar" "${url}" "${archive}" "d548661e4b6c33512074df81e39ed874" - if ! unzip "${unar_archive}" - then - fail "Failed to extract ${ievms_home}/${unar_archive} to ${ievms_home}/," \ - "unzip command returned error code $?" - fi + unzip "${archive}" || fail "Failed to extract ${ievms_home}/${archive} to ${ievms_home}/, unzip command returned error code $?" hash unar 2>&- || fail "Could not find unar in ${ievms_home}" } +# Check for the `unar` command, downloading and installing it if not found. check_unar() { if [ "${kernel}" == "Darwin" ] then - PATH="${PATH}:${ievms_home}" - hash unar 2>&- || download_unar + hash unar 2>&- || install_unar else hash unar 2>&- || fail "Linux support requires unar (sudo apt-get install for Ubuntu/Debian)" fi } +# Pause execution until the virtual machine with a given name shuts down. +wait_for_shutdown() { + while true ; do + log "Waiting for ${1} to shutdown..." + sleep "${sleep_wait}" + VBoxManage showvminfo "${1}" | grep "State:" | grep -q "powered off" && sleep "${sleep_wait}" && return 0 || true + done +} + +# Pause execution until guest control is available for a virtual machine. +wait_for_guestcontrol() { + while true ; do + log "Waiting for ${1} to be available for guestcontrol..." + sleep "${sleep_wait}" + VBoxManage showvminfo "${1}" | grep 'Additions run level:' | grep -q "3" && return 0 || true + done +} + +# Find or download the ievms control ISO. +find_iso() { + local url="https://github.com/xdissent/ievms/releases/download/v${ievms_version}/ievms-control.iso" + local dev_iso="${orig_cwd}/ievms-control.iso" # Use local iso if in ievms dev root + if [[ -f "${dev_iso}" ]] + then + iso=$dev_iso + else + iso="${ievms_home}/ievms-control-${ievms_version}.iso" + download "ievms control ISO" "${url}" "${iso}" "1fe3f95e0731bbcba949564cf9bbe28a" + fi +} + +# Attach a dvd image to the virtual machine. +attach() { + log "Attaching ${3}" + VBoxManage storageattach "${1}" --storagectl "IDE Controller" --port 1 \ + --device 0 --type dvddrive --medium "${2}" +} + +# Eject the dvd image from the virtual machine. +eject() { + log "Ejecting ${2}" + VBoxManage modifyvm "${1}" --dvd none +} + +# Boot the virtual machine with the control ISO in the dvd drive then wait for +# it to do its magic and shut down. For XP images, the "magic" is simply +# enabling guest control without a password. For other images, it installs +# a batch file that runs on first boot to install guest additions and activate +# the OS if possible. +boot_ievms() { + find_iso + attach "${1}" "${iso}" "ievms control ISO" + start_vm "${1}" + wait_for_shutdown "${1}" + eject "${1}" "ievms control ISO" +} + +# Boot the virtual machine with guest additions in the dvd drive. After running +# `boot_ievms`, the next boot will attempt automatically install guest additions +# if present in the drive. It will shut itself down after installation. +boot_auto_ga() { + boot_ievms "${1}" + attach "${1}" "additions" "Guest Additions" + start_vm "${1}" + wait_for_shutdown "${1}" + eject "${1}" "Guest Additions" +} + +# Start a virtual machine in headless mode. +start_vm() { + log "Starting VM ${1}" + VBoxManage startvm "${1}" --type headless +} + +# Copy a file to the virtual machine from the ievms home folder. +copy_to_vm() { + log "Copying ${2} to ${3}" + guest_control_exec "${1}" cmd.exe /c copy "E:\\${2}" "${3}" +} + +# Execute a command with arguments on a virtual machine. +guest_control_exec() { + local vm="${1}" + local image="${2}" + shift + VBoxManage guestcontrol "${vm}" run \ + --username "${guest_user}" --password "${guest_pass}" \ + --exe "${image}" -- "$@" +} + +# Start an XP virtual machine and set the password for the guest user. +set_xp_password() { + start_vm "${1}" + wait_for_guestcontrol "${1}" + + log "Setting ${guest_user} password" + VBoxManage guestcontrol "${1}" run --username Administrator \ + --password "${guest_pass}" --exe "net.exe" -- \ + net.exe user "${guest_user}" "${guest_pass}" + + log "Setting auto logon password" + VBoxManage guestcontrol "${1}" run --username Administrator \ + --password "${guest_pass}" --exe "reg.exe" -- reg.exe add \ + "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" \ + /f /v DefaultPassword /t REG_SZ /d "${guest_pass}" + + log "Enabling auto admin logon" + VBoxManage guestcontrol "${1}" run --username Administrator \ + --password "${guest_pass}" --exe "reg.exe" -- reg.exe add \ + "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon" \ + /f /v AutoAdminLogon /t REG_SZ /d 1 +} + +# Shutdown an XP virtual machine and wait for it to power off. +shutdown_xp() { + log "Shutting down ${1}" + guest_control_exec "${1}" "shutdown.exe" /s /f /t 0 + wait_for_shutdown "${1}" +} + +# Install an alternative version of IE in an XP virtual machine. Downloads the +# installer, copies it to the vm, then runs it before shutting down. +install_ie_xp() { # vm url md5 + local src=`basename "${2}"` + local dest="C:\\Documents and Settings\\${guest_user}\\Desktop\\${src}" + + download "${src}" "${2}" "${src}" "${3}" + copy_to_vm "${1}" "${src}" "${dest}" + + log "Installing IE" # Always "fails" + guest_control_exec "${1}" "${dest}" /passive /norestart || true + + shutdown_xp "${1}" +} + +# Install an alternative version of IE in a Win7 virtual machine. Downloads the +# installer, copies it to the vm, then runs it before shutting down. +install_ie_win7() { # vm url md5 + local src=`basename "${2}"` + local dest="C:\\Users\\${guest_user}\\Desktop\\${src}" + + download "${src}" "${2}" "${src}" "${3}" + start_vm "${1}" + wait_for_guestcontrol "${1}" + copy_to_vm "${1}" "${src}" "${dest}" + + log "Installing IE" + guest_control_exec "${1}" "cmd.exe" /c \ + "echo ${dest} /passive /norestart >C:\\Users\\${guest_user}\\ievms.bat" + guest_control_exec "${1}" "cmd.exe" /c \ + "echo shutdown.exe /s /f /t 0 >>C:\\Users\\${guest_user}\\ievms.bat" + guest_control_exec "${1}" "schtasks.exe" /run /tn ievms + + wait_for_shutdown "${1}" +} + +# Build an ievms virtual machine given the IE version desired. build_ievm() { unset archive unset unit + local prefix="IE" + local suffix="" + local version="${1}" case $1 in 6|7|8) os="WinXP" if [ "${reuse_xp}" != "yes" ] then + if [ "$1" == "6" ]; then unit="10"; fi if [ "$1" == "7" ]; then os="Vista"; fi if [ "$1" == "8" ]; then os="Win7"; fi else @@ -131,141 +360,157 @@ build_ievm() { fi ;; 9) os="Win7" ;; - 10) os="Win8" ;; + 10|11) + if [ "${reuse_win7}" != "yes" ] + then + if [ "$1" == "11" ]; then fail "IE11 is only available if REUSE_WIN7 is set"; fi + os="Win8" + else + os="Win7" + archive="IE9_Win7.zip" + fi + ;; + EDGE) + prefix="MS" + suffix="_preview" + version="Edge" + os="Win10" + unit="8" + ;; *) fail "Invalid IE version: ${1}" ;; esac - vm="IE${1} - ${os}" - def_archive="${vm/ - /_}.zip" + local vm="${prefix}${version} - ${os}" + local def_archive="${vm/ - /_}.zip" archive=${archive:-$def_archive} unit=${unit:-"11"} - ova=`basename "${archive/_/ - }" .zip`.ova - url="http://virtualization.modern.ie/vhd/IEKitV1_Final/VirtualBox/OSX/${archive}" - + local ova="`basename "${archive/_/ - }" .zip`${suffix}.ova" + + local url + if [ "${os}" == "Win10" ] + then + url="https://az792536.vo.msecnd.net/vms/VMBuild_20160802/VirtualBox/MSEdge/MSEdge.Win10_RS1.VirtualBox.zip" + else + url="https://az412801.vo.msecnd.net/vhd/IEKitV1_Final/VirtualBox/OSX/${archive}" + fi + + local md5 + case $archive in + IE6_WinXP.zip) md5="3d5b7d980296d048de008d28305ca224" ;; + IE7_Vista.zip) md5="d5269b2220f5c7fb9786dad513f2c05a" ;; + IE8_Win7.zip) md5="21b0aad3d66dac7f88635aa2318a3a55" ;; + IE9_Win7.zip) md5="58d201fe7dc7e890ad645412264f2a2c" ;; + IE10_Win8.zip) md5="cc4e2f4b195e1b1e24e2ce6c7a6f149c" ;; + MSEdge_Win10.zip) md5="467d8286cb8cbed90f0761c3566abdda" ;; + esac + log "Checking for existing OVA at ${ievms_home}/${ova}" if [[ ! -f "${ova}" ]] then - log "Downloading OVA ZIP from ${url} to ${ievms_home}/${archive}" - if [[ ! -f "${archive}" ]] && ! curl ${curl_opts} -L -O "${url}" - then - fail "Failed to download ${url} to ${ievms_home}/ using 'curl', error code ($?)" - fi + download "OVA ZIP" "${url}" "${archive}" "${md5}" log "Extracting OVA from ${ievms_home}/${archive}" - if ! unar "${archive}" - then - fail "Failed to extract ${archive} to ${ievms_home}/${ova}," \ - "unar command returned error code $?" - fi + unar "${archive}" || fail "Failed to extract ${archive} to ${ievms_home}/${ova}, unar command returned error code $?" fi log "Checking for existing ${vm} VM" if ! VBoxManage showvminfo "${vm}" >/dev/null 2>/dev/null then - disk_path="${ievms_home}/${vm}-disk1.vmdk" + local disk_path="${ievms_home}/${vm}-disk1.vmdk" log "Creating ${vm} VM (disk: ${disk_path})" VBoxManage import "${ova}" --vsys 0 --vmname "${vm}" --unit "${unit}" --disk "${disk_path}" - if ! egrep -q '(vmx|svm)' /proc/cpuinfo ; then - log "Disable VT-x/AMT-V on ${vm} (not available on your CPU)" - VBoxManage modifyvm "${vm}" --hwvirtex off --hwvirtexexcl off --vtxvpid off --nestedpaging off - # FIXME: still complains about VT-x if CPU count is not set to 1 (don't know why...) - VBoxManage modifyvm "${vm}" --cpus 1 - fi + if ! egrep -q '(vmx|svm)' /proc/cpuinfo ; then + log "Disable VT-x/AMT-V on ${vm} (not available on your CPU)" + VBoxManage modifyvm "${vm}" --hwvirtex off --hwvirtexexcl off --vtxvpid off --nestedpaging off + # FIXME: still complains about VT-x if CPU count is not set to 1 (don't know why...) + VBoxManage modifyvm "${vm}" --cpus 1 + fi + + log "Adding shared folder" + VBoxManage sharedfolder add "${vm}" --automount --name ievms \ + --hostpath "${ievms_home}" log "Building ${vm} VM" declare -F "build_ievm_ie${1}" && "build_ievm_ie${1}" - + + log "Tagging VM with ievms version" + VBoxManage setextradata "${vm}" "ievms" "{\"version\":\"${ievms_version}\"}" + log "Creating clean snapshot" VBoxManage snapshot "${vm}" take clean --description "The initial VM state" fi } -build_ievm_xp() { - sleep_wait="10" +# Build the IE6 virtual machine. +build_ievm_ie6() { + boot_auto_ga "IE6 - WinXP" + set_xp_password "IE6 - WinXP" + shutdown_xp "IE6 - WinXP" +} - installer=`basename "${2}"` - installer_host="${ievms_home}/${installer}" - installer_guest="/Documents and Settings/IEUser/Desktop/Install IE${1}.exe" - log "Downloading IE${1} installer from ${2}" - if [[ ! -f "${installer}" ]] && ! curl ${curl_opts} -L "${2}" -o "${installer}" +# Build the IE7 virtual machine, reusing the XP VM if requested (the default). +build_ievm_ie7() { + if [ "${reuse_xp}" != "yes" ] then - fail "Failed to download ${url} to ${ievms_home}/${installer} using 'curl', error code ($?)" + boot_auto_ga "IE7 - Vista" + else + boot_auto_ga "IE7 - WinXP" + set_xp_password "IE7 - WinXP" + install_ie_xp "IE7 - WinXP" "http://download.microsoft.com/download/3/8/8/38889dc1-848c-4bf2-8335-86c573ad86d9/IE7-WindowsXP-x86-enu.exe" "ea16789f6fc1d2523f704e8f9afbe906" fi +} - iso_url="https://dl.dropbox.com/u/463624/ievms-control.iso" - dev_iso=`pwd`/ievms-control.iso # Use local iso if in ievms dev root - if [[ -f "${dev_iso}" ]]; then iso=$dev_iso; else iso="${ievms_home}/ievms-control.iso"; fi - log "Downloading ievms ISO from ${iso_url}" - if [[ ! -f "${iso}" ]] && ! curl ${curl_opts} -L "${iso_url}" -o "${iso}" +# Build the IE8 virtual machine, reusing the XP VM if requested (the default). +build_ievm_ie8() { + if [ "${reuse_xp}" != "yes" ] then - fail "Failed to download ${iso_url} to ${ievms_home}/${iso} using 'curl', error code ($?)" + boot_auto_ga "IE8 - Win7" + else + boot_auto_ga "IE8 - WinXP" + set_xp_password "IE8 - WinXP" + install_ie_xp "IE8 - WinXP" "http://download.microsoft.com/download/C/C/0/CC0BD555-33DD-411E-936B-73AC6F95AE11/IE8-WindowsXP-x86-ENU.exe" "616c2e8b12aaa349cd3acb38bf581700" fi +} - log "Attaching ievms.iso" - VBoxManage storageattach "${vm}" --storagectl "IDE Controller" --port 1 --device 0 --type dvddrive --medium "${iso}" - - log "Starting VM ${vm}" - VBoxManage startvm "${vm}" --type headless - - log "Waiting for ${vm} to shutdown..." - x="0" ; until [ "${x}" != "0" ]; do - sleep "${sleep_wait}" - VBoxManage list runningvms | grep "${vm}" >/dev/null && x=$? || x=$? - done - - log "Ejecting ievms.iso" - VBoxManage modifyvm "${vm}" --dvd none - - log "Starting VM ${vm}" - VBoxManage startvm "${vm}" --type headless - - log "Waiting for ${vm} to be available for guestcontrol..." - x="1" ; until [ "${x}" == "0" ]; do - sleep "${sleep_wait}" - VBoxManage guestcontrol "${vm}" cp "${installer_host}" "${installer_guest}" --username IEUser --dryrun && x=$? || x=$? - done - - sleep "${sleep_wait}" # Extra sleep for good measure. - log "Copying IE${1} installer to Desktop" - VBoxManage guestcontrol "${vm}" cp "${installer_host}" "${installer_guest}" --username IEUser - - log "Installing IE${1}" # Always "fails" - VBoxManage guestcontrol "${vm}" exec --image "${installer_guest}" --username IEUser --wait-exit -- /passive /norestart || true - - log "Shutting down VM ${vm}" - VBoxManage guestcontrol "${vm}" exec --image "/WINDOWS/system32/shutdown.exe" --username IEUser --wait-exit -- -s -f -t 0 - - x="0" ; until [ "${x}" != "0" ]; do - sleep "${sleep_wait}" - log "Waiting for ${vm} to shutdown..." - VBoxManage list runningvms | grep "${vm}" >/dev/null && x=$? || x=$? - done - - sleep "${sleep_wait}" # Extra sleep for good measure. +# Build the IE9 virtual machine. +build_ievm_ie9() { + boot_auto_ga "IE9 - Win7" } -build_ievm_ie7() { - if [ "${reuse_xp}" != "yes" ]; then return; fi - build_ievm_xp 7 "http://download.microsoft.com/download/3/8/8/38889dc1-848c-4bf2-8335-86c573ad86d9/IE7-WindowsXP-x86-enu.exe" +# Build the IE10 virtual machine, reusing the Win7 VM if requested (the default). +build_ievm_ie10() { + if [ "${reuse_win7}" != "yes" ] + then + boot_auto_ga "IE10 - Win8" + else + boot_auto_ga "IE10 - Win7" + install_ie_win7 "IE10 - Win7" "https://raw.githubusercontent.com/kbandla/installers/master/MSIE/IE10-Windows6.1-x86-en-us.exe" "0f14b2de0b3cef611b9c1424049e996b" + fi } -build_ievm_ie8() { - if [ "${reuse_xp}" != "yes" ]; then return; fi - build_ievm_xp 8 "http://download.microsoft.com/download/C/C/0/CC0BD555-33DD-411E-936B-73AC6F95AE11/IE8-WindowsXP-x86-ENU.exe" +# Build the IE11 virtual machine, reusing the Win7 VM always. +build_ievm_ie11() { + boot_auto_ga "IE11 - Win7" + install_ie_win7 "IE11 - Win7" "http://download.microsoft.com/download/9/2/F/92FC119C-3BCD-476C-B425-038A39625558/IE11-Windows6.1-x86-en-us.exe" "7d3479b9007f3c0670940c1b10a3615f" } +# ## Main Entry Point + +# Run through all checks to get the host ready for installation. check_system create_home check_virtualbox check_ext_pack check_unar -all_versions="6 7 8 9 10" +# Install each requested virtual machine sequentially. +all_versions="6 7 8 9 10 11 EDGE" for ver in ${IEVMS_VERSIONS:-$all_versions} do - log "Building IE${ver} VM" + log "Building IE ${ver} VM" build_ievm $ver done +# We made it! log "Done!"