Automating VirtualBox screenshots with Python

Following on from last week's post, this post is going to look at using the VirtualBox API to take screenshots.

There are two ways the VirtualBox API can be called, either via a Component Object Model (COM), or using a web service built using SOAP, so long as the vboxwebsrv process is started. This post is going to look at using the COM interface, however the calls should be very similar to the web service interface.

Setting up Python

The Python's standard library doesn't have any way to use COM interfaces, however the pywin32 module can be used to create COM objects. This can be installed using pip:

$ python -m venv vbox_venv
$ source vbox_venv/Scripts/activate
$ pip install pywin32
Collecting pywin32
  Using cached https://files.pythonhosted.org/packages/d4/2d/b927e61c4a2b0aaaab72c8cb97cf748c319c399d804293164b0c43380d5f/pywin32-223-cp36-cp36m-win32.whl
Installing collected packages: pywin32
Successfully installed pywin32-223

Once pywin32 is installed, you should be able to import win32com.client:

$ python
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 16:07:46) [MSC v.1900 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import win32com.client
>>> win32com.client.__name__
'win32com.client'

Using the API

Once pywin32 is setup, the next thing to do is start a new session. This can be done using Dispatch:

import win32com.client
from win32com.client import constants

vbox = win32com.client.Dispatch("VirtualBox.VirtualBox")
session = win32com.client.Dispatch("VirtualBox.Session")

The findMachine function can then be used to find the virtual machine we want to screenshot:

machine = vbox.FindMachine("example_vm")

Note: you can either search for machines by name or UUID.

The VirtualBox API uses a locking mechanism to control access to virtual machines. Before calling the screenshot function, we need to create a lock using the lockMachine function:

machine.LockMachine(session, constants.LockType_Shared)

This function takes two parameters, the session to create the lock with, and the type of lock to create. The VirtualBox COM interface uses several enumerations to store constants, like the lock type, these can be accessed from win32com.client.constants.

After successfully creating a lock it should be possible to use the GetScreenResolution and TakeScreenShotToArray functions to work out the require resolution and grab a screenshot:

display = session.Console.Display
width, height, _, _, _, _= display.GetScreenResolution(0)
screenshot = display.TakeScreenShotToArray(0, width, height, constants.BitmapFormat_PNG)

At this point we can now release our machine lock:

session.UnlockMachine()

Finally the screenshot data can be written out to a file:

with open('screenshot.png', 'wb') as output_png:
    output_png.write(screenshot.tobytes())

If everything goes well, this should create a new screenshot:

$ file screenshot.png
screenshot.png: PNG image data, 720 x 400, 8-bit/color RGB, non-interlaced

Note: the lines above can obviously be put together into a short script.

COM interfaces tips

I've not previously done much with COM interfaces, below are a few points I wish I knew at the start:

  1. The VirtualBox SDK docs are very comprehensive, and cover all available functions in detail, although they can be a bit tricky to navigate.

  2. You can inspect available constants by calling win32com.client.constants.__dict__["__dicts__"][0] after you've called the Dispatch method.

  3. pywin32 comes with a simple object browser which can be used to browse available COM interface functions. You can access this by running:

    $ python vbox_venv/Lib/site-packages/win32com/client/combrowse.py