Last active
September 13, 2024 20:51
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 $? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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