Skip to content

Instantly share code, notes, and snippets.

@PhrozenByte
Last active September 13, 2024 20:51
Show Gist options
  • Save PhrozenByte/429f7a0cb626ef6c73bca43f22e9dcc4 to your computer and use it in GitHub Desktop.
Save PhrozenByte/429f7a0cb626ef6c73bca43f22e9dcc4 to your computer and use it in GitHub Desktop.
Runs applications inside a VirtualBox VM (VBoxGuestRun.bat) and toggles a VirtualBox VM on/off (VBoxToggle.bat)
@echo off & setlocal
REM Runs applications inside a VirtualBox VM
REM Version 1.1 (build 20160904)
REM
REM Copyright (C) 2016 Daniel Rudolf <www.daniel-rudolf.de>
REM
REM This program is free software: you can redistribute it and/or modify
REM it under the terms of the GNU General Public License as published by
REM the Free Software Foundation, version 3 of the License only.
REM
REM This program is distributed in the hope that it will be useful,
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
REM GNU General Public License for more details.
REM
REM See <http://www.gnu.org/licenses/> to receive a full-text-copy of
REM the GNU General Public License.
if "%~1" == "" (
set ERROR=You must pass a VirtualBox VM name or ID as first argument
goto :error
)
if "%~2" == "" (
set ERROR=You must pass a path to an executable on the guest as second argument
goto :error
)
cd "%~dp0"
set VBOX_FOUND=true
if not exist VirtualBox.exe set VBOX_FOUND=false
if not exist VBoxManage.exe set VBOX_FOUND=false
if not %VBOX_FOUND% == true (
cd "%PROGRAMFILES%\Oracle\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES(X86)%\Oracle\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES%\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES(X86)%\VirtualBox\" 2>NUL
if not exist VirtualBox.exe (
set ERROR=VirtualBox.exe not found
goto :error
)
if not exist VBoxManage.exe (
set ERROR=VBoxManage.exe not found
goto :error
)
)
VBoxManage.exe list vms | findstr /C:"%~1" >NUL
if errorlevel 1 (
set ERROR=Unable to start VirtualBox VM "%~1": Invalid VM name or ID given
goto :error
)
VBoxManage.exe list runningvms | findstr /C:"%~1" >NUL
if not errorlevel 1 goto :vm_running
start VirtualBox.exe --startvm "%~1" --seamless
<NUL set /p =Starting VM
set WAIT=30
:until_vm_running
if %WAIT% == 0 (
echo Failure!
set ERROR=Unable to start VirtualBox VM "%~1": Timeout while waiting for startup
goto :error
)
VBoxManage.exe list runningvms | findstr /C:"%~1" >NUL
if errorlevel 1 (
<NUL set /p =.
set /a WAIT-=1
timeout /T 1 /NOBREAK >NUL
goto :until_vm_running
)
<NUL set /p =.
timeout /T 1 /NOBREAK >NUL
echo Success!
:vm_running
set CMD=VBoxManage.exe guestproperty get "%~1" "/VirtualBox/GuestInfo/OS/LoggedInUsers"
for /f "usebackq tokens=*" %%o in (`%CMD%`) do set LOGGEDIN=%%o
if not "%LOGGEDIN%" == "No value set!" if not "%LOGGEDIN:Value: =%" == "0" goto :logged_in
<NUL set /p =Awaiting login
set WAIT=120
:until_logged_in
if %WAIT% == 0 (
echo Failure!
set ERROR=Unable to start VirtualBox VM "%~1": Timeout while waiting for login
goto :error
)
VBoxManage.exe list runningvms | findstr /C:"%~1" >NUL
if errorlevel 1 (
echo Failure!
set ERROR=Unable to start VirtualBox VM "%~1": VM suddenly stopped running
goto :error
)
for /f "usebackq tokens=*" %%o in (`%CMD%`) do set LOGGEDIN=%%o
if "%LOGGEDIN%" == "No value set!" (
<NUL set /p =.
set /a WAIT-=1
timeout /T 1 /NOBREAK >NUL
goto :until_logged_in
) else (
if "%LOGGEDIN:Value: =%" == "0" (
<NUL set /p =.
set /a WAIT-=1
timeout /T 1 /NOBREAK >NUL
goto :until_logged_in
)
)
echo Success!
:logged_in
echo Running executable...
VBoxManage.exe guestcontrol "%~1" run --exe "%~2" --username "VirtualBox"
exit /B 0
:error
echo ERROR: %ERROR%
start cmd /c "echo VirtualBox: %ERROR% && pause"
exit /B 1
#!/bin/bash
# Runs applications inside a VirtualBox VM
# Version 2.1 (build 20240913)
#
# Copyright (C) 2016-2024 Daniel Rudolf <www.daniel-rudolf.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# See <http://www.gnu.org/licenses/> to receive a full-text-copy of
# the GNU General Public License.
set -eu -o pipefail
APP_NAME="VBoxGuestRun"
APP_ICON="virtualbox"
print_usage() {
echo "Usage:"
echo " $APP_NAME [--quiet] [--user USERNAME] VM_NAME EXEC [ARG]..."
}
gui_error() {
echo "$APP_NAME: $1" >&2
if [ -x "$(which notify-send)" ]; then
notify-send --urgency=critical --app-name="$APP_NAME" --icon="$APP_ICON" "$APP_NAME" "$1" > /dev/null 2>&1
elif [ -x "$(which zenity)" ]; then
zenity --error --ellipsize --icon-name="$APP_ICON" --title="$APP_NAME" --text="$1" > /dev/null 2>&1
fi
}
vbox() {
VBoxManage --nologo "$@"
}
start_vm() {
echo "${1:-Starting VM}..." >&3
if ! flock -xn 9; then
await_lock "Awaiting lock"
return 1
fi
case "${2:-start}" in
"start"|"restore")
if ! vbox startvm "{$VM}" >&3; then
gui_error "Unable to start VirtualBox VM '$VM': \`VBoxManage startvm\` failed"
exit 1
fi
;;
"resume")
if ! vbox controlvm "{$VM}" resume >&3; then
gui_error "Unable to resume VirtualBox VM '$VM': \`VBoxManage controlvm\` failed"
exit 1
fi
;;
esac
flock -u 9
return 0
}
await_lock() {
local TIMEOUT="${2:-60}"
local DELAY="${3:-6}"
echo -n "${1:-Awaiting lock}" >&3
local WAIT_TIMEOUT=0
local WAIT_DELAY=0
while true; do
if [ $WAIT_DELAY -eq 0 ] && [ $WAIT_TIMEOUT -ge $TIMEOUT ]; then
echo " Failed!" >&3
gui_error "Unable to start VirtualBox VM '$VM': Timeout while waiting for lock"
exit 1
fi
if flock -xn 9; then
if [ $WAIT_DELAY -ge $DELAY ]; then
echo " Success!" >&3
break
fi
((++WAIT_DELAY))
else
WAIT_DELAY=0
((++WAIT_TIMEOUT))
fi
echo -n "." >&3
sleep 0.5
done
}
is_vm_running() {
local VM_STATE="${1:-$(get_vm_state)}"
if [ "$VM_STATE" != "running" ] && [ "$VM_STATE" != "livesnapshotting" ] && [ "$VM_STATE" != "deletingsnapshotlive" ]; then
return 1
fi
}
is_vm_paused() {
local VM_STATE="${1:-$(get_vm_state)}"
if [ "$VM_STATE" != "paused" ] && [ "$VM_STATE" != "deletingsnapshotlivepaused" ]; then
return 1
fi
}
is_user_logged_in() {
# `query session console` outputs the user(s) logged in using the 'console' session, the output matches: >console $VM_USER ...
# '>' indicates an active session (' ' if inactive), 'console' indicates the terminal session, and '$VM_USER' the client's local username
local VM_SESSION="$(vbox guestcontrol "{$VM}" run --username "$VM_USER" --timeout 3000 \
"C:\\Windows\\System32\\query.exe" session console 2> /dev/null)"
if [ -z "$(sed -ne "1d;/>console *$(sed -e 's/[]\/$*.^[]/\\&/g' <<< "$VM_USER") */p" <<< "$VM_SESSION")" ]; then
return 1
fi
}
get_vm_state() {
local VM_STATE_RAW="$(vbox showvminfo "{$VM}" --machinereadable 2> /dev/null | grep 'VMState=')"
if [ -z "$VM_STATE_RAW" ] || ! [[ "$VM_STATE_RAW" =~ ^VMState=\"(.+)\"$ ]]; then
return 1
fi
echo "${BASH_REMATCH[1]}"
}
await_vm_state() {
local INITIAL_STATE="${2:-starting}"
local CURRENT_STATE=""
local TARGET_STATE="${3:-running}"
local TIMEOUT="${4:-60}"
local DELAY="${5:-4}"
local PAUSED_GRACE="${6:-20}"
echo -n "${1:-Starting VM}" >&3
local WAIT_TIMEOUT=0
local WAIT_DELAY=0
local WAIT_PAUSED_GRACE=0
while true; do
if [ $WAIT_DELAY -eq 0 ] && [ $WAIT_TIMEOUT -ge $TIMEOUT ]; then
echo " Failed!" >&3
gui_error "Unable to start VirtualBox VM '$VM': Timeout while waiting for startup"
exit 1
fi
CURRENT_STATE="$(get_vm_state)"
if [ "$CURRENT_STATE" == "$TARGET_STATE" ] || { [ "$TARGET_STATE" == "*" ] && [ "$CURRENT_STATE" != "$INITIAL_STATE" ]; }; then
if [ $WAIT_DELAY -ge $DELAY ]; then
echo " Success!" >&3
break
fi
((++WAIT_DELAY))
echo -n "·" >&3
elif [ "$CURRENT_STATE" == "$INITIAL_STATE" ]; then
WAIT_DELAY=0
((++WAIT_TIMEOUT))
echo -n "." >&3
elif is_vm_paused "$CURRENT_STATE" && [ $WAIT_PAUSED_GRACE -lt $PAUSED_GRACE ]; then
WAIT_DELAY=0
((++WAIT_PAUSED_GRACE))
echo -n "." >&3
else
echo " Failed!" >&3
gui_error "Unable to start VirtualBox VM '$VM': VM unexpectedly switched to '$CURRENT_STATE' state"
exit 1
fi
sleep 0.5
done
}
handle_vm_state() {
local VM_STATE="$(get_vm_state)"
if is_vm_running "$VM_STATE"; then
return 0
fi
# see https://www.virtualbox.org/sdkref/_virtual_box_8idl.html#a80b08f71210afe16038e904a656ed9eb
case "$VM_STATE" in
"poweroff"|"aborted")
if ! start_vm "Starting VM" "start"; then
return 1
fi
await_vm_state "Awaiting startup"
;;
"saved"|"aborted-saved")
if ! start_vm "Restoring VM" "restore"; then
return 1
fi
await_vm_state "Awaiting restoration" "restoring"
;;
"paused")
if ! start_vm "Resuming VM" "resume"; then
return 1
fi
await_vm_state "Awaiting resume"
;;
"starting")
await_vm_state "Awaiting startup"
;;
"restoring"|"restoringsnapshot")
await_vm_state "Awaiting restoration" "$VM_STATE"
;;
"onlinesnapshotting")
await_vm_state "Awaiting snapshotting to finish" "onlinesnapshotting"
;;
"saving")
await_vm_state "Awaiting saving to finish" "saving" "saved"
if ! start_vm "Restoring VM" "restore"; then
return 1
fi
await_vm_state "Awaiting restoration" "restoring"
;;
"stopping")
await_vm_state "Awaiting power off" "stopping" "*"
handle_vm_state
;;
"settingup"|"snapshotting"|"deletingsnapshot")
await_vm_state "Awaiting VM management state to change" "$VM_STATE" "*"
handle_vm_state
;;
"teleporting"|"teleportingpausedvm"|"teleportingin"|"teleported")
gui_error "Unable to start VirtualBox VM '$VM': VM is being or was teleported, please reset VM"
exit 1
;;
"gurumeditation")
gui_error "Unable to start VirtualBox VM '$VM': VM is stuck and in guru meditation, please reset VM"
exit 1
;;
*)
gui_error "Unable to start VirtualBox VM '$VM': Unknown VM state '$VM_STATE'"
exit 1
;;
esac
}
await_user_login() {
local TIMEOUT="${2:-240}"
local DELAY="${3:-6}"
local PAUSED_GRACE="${4:-20}"
echo -n "${1:-Awaiting login}" >&3
local WAIT_TIMEOUT=0
local WAIT_DELAY=0
local WAIT_PAUSED_GRACE=0
while true; do
if [ $WAIT_DELAY -eq 0 ] && [ $WAIT_TIMEOUT -ge $TIMEOUT ]; then
echo " Failed!" >&3
gui_error "Unable to start VirtualBox VM '$VM': Timeout while waiting for login"
exit 1
fi
if ! is_vm_running; then
if ! is_vm_paused || [ $WAIT_PAUSED_GRACE -ge $PAUSED_GRACE ]; then
echo " Failed!" >&3
gui_error "Unable to start VirtualBox VM '$VM': VM suddenly stopped running"
exit 1
fi
WAIT_DELAY=0
((++WAIT_PAUSED_GRACE))
echo -n "." >&3
elif is_user_logged_in; then
if [ $WAIT_DELAY -ge $DELAY ]; then
echo " Success!" >&3
break
fi
((++WAIT_DELAY))
echo -n "·" >&3
else
WAIT_DELAY=0
((++WAIT_TIMEOUT))
echo -n "." >&3
fi
sleep 0.5
done
}
handle_user_login() {
if is_user_logged_in; then
return 0
fi
await_user_login "Awaiting login"
if ! is_vm_running; then
gui_error "Unable to start VirtualBox VM '$VM': VM suddenly stopped running"
exit 1
fi
if ! is_user_logged_in; then
gui_error "Unable to start VirtualBox VM '$VM': VM user suddenly logged out"
exit 1
fi
}
# check dependencies
if ! [ -x "$(which VBoxManage)" ]; then
echo "$APP_NAME: VBoxManage executable not found" >&2
exit 1
fi
# parse args
VM_USER="VirtualBox"
QUIET="no"
VM=""
EXEC=""
ARGS=()
while [ $# -gt 0 ]; do
if [ -z "$VM" ]; then
if [ "$1" == "--quiet" ]; then
QUIET="yes"
elif [ "$1" == "--user" ]; then
if [ -z "${2:-}" ]; then
echo "$APP_NAME: Invalid VirtualBox VM guest username '$2'" >&2
exit 1
fi
VM_USER="$2"
shift
else
VM="$1"
fi
elif [ -z "$EXEC" ]; then
EXEC="$1"
else
ARGS+=( "$1" )
fi
shift
done
# check args
if [ -z "$VM" ]; then
echo "$APP_NAME: You must pass a VirtualBox VM name or ID as first argument" >&2
print_usage >&2
exit 1
elif ! [[ "$(vbox list vms 2> /dev/null | grep "$VM")" =~ ^\"(.+)\"\ \{(.+)\}$ ]]; then
echo "$APP_NAME: Unable to start VirtualBox VM '$VM': Invalid VM name or UUID given" >&2
exit 1
fi
VM="${BASH_REMATCH[2]}"
if [ -z "$EXEC" ]; then
echo "$APP_NAME: You must pass a path to an executable on the guest as second argument" >&2
print_usage >&2
exit 1
fi
# setup
exec 9> "/var/lock/${APP_NAME}_$VM"
[ "$QUIET" == "yes" ] \
&& exec 3> /dev/null \
|| exec 3>&1
# await VM startup
if ! handle_vm_state; then
gui_error "Unable to start VirtualBox VM '$VM': Giving up..."
exit 1
elif ! is_vm_running; then
gui_error "Unable to start VirtualBox VM '$VM': VM suddenly stopped running"
exit 1
fi
# await user login
handle_user_login
# run command
echo "Executing \`${EXEC@Q} ${ARGS[@]@Q}\`..." >&3
vbox guestcontrol "{$VM}" run --username "$VM_USER" --exe "$EXEC" "${ARGS[@]}"
exit $?
@echo off & setlocal
REM Toggles a VirtualBox VM on/off
REM Version 1.1 (build 20160904)
REM
REM Copyright (C) 2016 Daniel Rudolf <www.daniel-rudolf.de>
REM
REM This program is free software: you can redistribute it and/or modify
REM it under the terms of the GNU General Public License as published by
REM the Free Software Foundation, version 3 of the License only.
REM
REM This program is distributed in the hope that it will be useful,
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
REM GNU General Public License for more details.
REM
REM See <http://www.gnu.org/licenses/> to receive a full-text-copy of
REM the GNU General Public License.
if "%~1" == "" (
set ERROR=You must pass a VirtualBox VM name or ID as first argument
goto :error
)
cd "%~dp0"
set VBOX_FOUND=true
if not exist VirtualBox.exe set VBOX_FOUND=false
if not exist VBoxManage.exe set VBOX_FOUND=false
if not %VBOX_FOUND% == true (
cd "%PROGRAMFILES%\Oracle\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES(X86)%\Oracle\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES%\VirtualBox\" 2>NUL
if errorlevel 1 cd "%PROGRAMFILES(X86)%\VirtualBox\" 2>NUL
if not exist VirtualBox.exe (
set ERROR=VirtualBox.exe not found
goto :error
)
if not exist VBoxManage.exe (
set ERROR=VBoxManage.exe not found
goto :error
)
)
VBoxManage.exe list vms | findstr /C:"%~1" >NUL
if errorlevel 1 (
set ERROR=Unable to start VirtualBox VM "%~1": Invalid VM name or ID given
goto :error
)
VBoxManage.exe list runningvms | findstr /C:"%~1" >NUL
if not errorlevel 1 (
echo Stopping VM...
VBoxManage.exe controlvm "%~1" savestate
exit /B 0
)
:loop_args
if not "%~1" == "" (
set ARGS=%ARGS% %1
shift
goto :loop_args
)
echo Starting VM...
start VirtualBox.exe --startvm %ARGS%
exit /B 0
:error
echo ERROR: %ERROR%
start cmd /c "echo VirtualBox: %ERROR% && pause"
exit /B 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment