Skip to content

Instantly share code, notes, and snippets.

@n1kk
Last active April 13, 2023 00:41
Show Gist options
  • Save n1kk/ff66da77acdc0191370d3b85e87a5592 to your computer and use it in GitHub Desktop.
Save n1kk/ff66da77acdc0191370d3b85e87a5592 to your computer and use it in GitHub Desktop.
Script to create .vmdk file for a raw hard drive for Virtualbox in macOS

A script to ease the process of creation and registering of raw disk .vmdk files that are used by Virtualbox to launch os from that hard drive in virtual environment.

Prerequisites

You need to ensure following

  • Virtualbox is installed.
  • Virtual machine for guest os is created. Name can be set in script.
  • VM has an empty IDE controller. It does not work with SATA for some reason.
    • Go to VM Settings > Storage
    • Click 'Add new storage controller' below the 'Storage Devices' list
    • Select 'Add IDE controller'
    • Save
  • System Integrity Protection needs to be disabled for Virtualbox to be able to mount drives.
    • Restart…
    • Hold down Command-R to reboot into Recovery Mode.
    • launch Utilities > Terminal.
    • run csrutil disable
    • Restart…

Reference

  • vboxmanage reference page
  • diskutil reference. Official apple page was taken down, or moved :/

Usage

This script can optionally attach/clear vmdk file to existing VM. Since to use drives directly Virtualbox requires owner permissions and drives in /dev/ are recreated every boot, this script needs to be rerun before starting VM.

bash ./create_vmdk.sh "VOLUME_NAME_1,VOLUME_NAME_2" --post 0 --device 0 --vm-name "VM_NAME" --vbox-man "PATH_TO_VBOXMANAGE" --attach --clear --efi
arguments description
$1 Name of the volume to make .vmdk of. Accept comma separated list of volume names. All volumes must be located on one physical drive.
--port Port of the IDE controller, default 0.
--device Device on the port of the IDE controller, default 0.
--vm-name Name of the virtual machine as it is registered in Virtualbox. You can check it by running vboxmanage list vms. Default Bootcamp.
--vbox-man Alternative path to vboxmanage tool. Full path is usually in Virtualbox.app: /Applications/VirtualBox.app/Contents/MacOS/VBoxManage. Default vboxmanage.
--attach Attaches created .vmdk to existing virtualbox. --vm-name should be specified.
--clear Clears .vmdk with same parameters from existing vm. Since this script has to be run every time or after every boot, you will need this if you use --attach.
--efi Tries to find EFI partition on the same disk and add it to the list of partitions for .vmdk file.

Examples

To creates vmdk file for windows installed on Bootcamp volume and include EFI partition in it:

bash ./create_vmdk.sh "BOOTCAMP" --efi

Same as above but also register vmdk with VM and clear existing one if previously registered:

bash ./create_vmdk.sh "BOOTCAMP" --efi --attach --clear --vm-name "Bootcamp"

To create vmdk with vboxmanage from custom path:

bash ./create_vmdk.sh "BOOTCAMP" --efi --vbox-man "/Applications/VMTools/VirtualBox.app/Contents/MacOS/VBoxManage"

Heres an example of a script to automate creation, removal, registration of vmdk, launch of VM, and mounting back volumes after VM closes and Virtualbox quits.

run.command

#!/bin/bash
source ./create_vmdk.sh

cd "$(dirname "$0")"
echo "working in $PWD"

vm_name="Windows10"

create_vmdk "BOOTCAMP" --device 0 --vm-name $vm_name --attach --clear --efi
create_vmdk "STORAGE_1,STORAGE_2" --device 1 --vm-name $vm_name --attach --clear

# this will wait for VirtualBox.app to quit, not just VM to close
open -g -W ./${vm_name}.vbox -a "/Applications/VirtualBox.app"

# after vbox quit it will mount back volumes
mount_volume "BOOTCAMP"
mount_volume "STORAGE_1,STORAGE_2"

Dont forget to make it executable sudo chmod +x ./run.command

#! /bin/bash
# ----- UTILS ------
# get device id by it's name 'SomeDiskName' -> '/dev/disk3'
get_device() {
local target=$1
[[ -z $target ]] && { return; }
local line=$(diskutil list | grep $target)
local disk=$(echo "$line" | sed 's/.*\(disk[0-9]*\).*/\1/' | sed -n 1p)
[[ -z $disk ]] || { echo "$disk"; }
}
# get partition id by disk name name 'SomeDiskName' -> 'disk3s1'
get_partition() {
local target=$1
local disk=${2}
[[ -z $target ]] && { return; }
if [ -z ${disk} ]; then
local line=$(diskutil list | grep $target)
else
disk=${disk//\/dev\/r/}
disk=${disk//\/dev\//}
local line=$(diskutil list | grep $target | grep $disk)
fi
local part=$(echo "$line" | sed 's/.*\(disk[0-9]*s[0-9]*\).*/\1/' | sed -n 1p)
echo "$part"
}
# get partition id by disk name name 'SomeDiskName disk3' -> '2'
get_partition_number() {
local partition=`get_partition $@`
[[ -z $partition ]] && { return; }
local i=$((${#partition}-1))
echo "${partition:$i:1}"
}
# mount partition by volume name 'SomeDiskName' -> Volume SomeDiskName on disk1s3 mounted
mount_volume() {
for i in ${1//,/ }
do
local part_id=`get_partition $i $2`
diskutil mount $part_id
done
}
# create vmdk file for volume, remove existing one and potentially register/unregister it with virtual machine
create_vmdk() {
local target_disk_name=$1
local controller=IDE
local port=0
local device=0
local disk_id=""
local efi=false
local attach=false
local clear=false
local vm_name="Bootcamp"
# local vbox_man="/Applications/VirtualBox.app/Contents/MacOS/VBoxManage"
local vbox_man="vboxmanage"
while [ ! $# -eq 0 ]
do
case "$1" in
--controller) controller=$2; shift ;;
--port) port=$2; shift ;;
--device) device=$2; shift ;;
--disk) disk_id=$2; shift ;;
--efi) efi=true ;;
--attach) attach=true ;;
--clear) clear=true ;;
--vm-name) vm_name=$2; shift ;;
--vbox-man) vbox_man=$2; shift ;;
esac
shift
done
if [ -z $target_disk_name ]; then
echo "No volume name passed. Aborting."
return;
fi
local disk_file_name="raw_disk_${target_disk_name}"
local disk_file_name_full="raw_disk_${target_disk_name}.vmdk"
if [ "$efi" = true ] ; then
target_disk_name="$target_disk_name,EFI"
fi
local username=`id -un`
local partitions=""
for i in ${target_disk_name//,/ }
do
# if disk id is not set yet find it
if [ -z $disk_id ]; then
local disk_id=/dev/`get_device $i`
[[ -z $disk_id ]] && { echo "Can't find disk id of volume $i"; return; }
fi
local current_partition=/dev/`get_partition $i $disk_id`
[[ -z $current_partition ]] && { echo "Can't find partition id of volume $i on disk $disk_id"; return; }
local current_partition_number=`get_partition_number $i $disk_id`
[[ -z $partitions ]] && { partitions="$current_partition_number"; } || { partitions="$partitions,$current_partition_number"; }
echo "target_disk_name $i disk $disk_id partition $current_partition"
# unmount partition
diskutil unmount ${current_partition}
sudo chmod 777 ${current_partition}
done
echo "Creating disk - $disk_id --> $disk_file_name_full"
if [ -f "$disk_file_name_full" ]; then
if [ "$clear" = true ] ; then
$vbox_man storageattach $vm_name --storagectl $controller --port $port --device $device --medium none
$vbox_man closemedium disk $disk_file_name_full
fi
sudo -S rm ./${disk_file_name}*
fi
sudo chown $username ${disk_id}
sudo chmod +rx ${disk_id}
sudo chmod 777 ${disk_id}
sudo $vbox_man internalcommands createrawvmdk -filename $disk_file_name_full -rawdisk ${disk_id} -partitions $partitions
sudo chown $username ./${disk_file_name}*
sudo chmod +rwx ./${disk_file_name}*
if [ "$attach" = true ] ; then
$vbox_man storageattach $vm_name --storagectl $controller --port $port --device $device --medium $disk_file_name_full --type hdd
fi
}
# if script is being called directly pass all the arguments to create_vmdk routine
if [ "$0" = "$BASH_SOURCE" ]; then
create_vmdk $@
fi
@AbeKh
Copy link

AbeKh commented Apr 13, 2023

efficient and seamless.. thanks for sharing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment