HOWTO: Monitor Virtual Server Events

I think Virtual Server Events and Asynchronous Tasks are two of the least utilized features of the Virtual Server Administration API.

Contrary to the often-asked task of "find the VM, turn it off, manipulate its VHD, then turn it back on", which shows the synchronous, task-driven side of the VS Admin model, Events and Asynchronous Tasks show off a nice event-based, asynchronous model suitable for passive monitoring and response. What good is this?

Well... that's the motivation behind this blog entry's sample code, which illustrates a simple "Virtual Server Monitor Agent" which does a few basic things:

  • By default, it simply monitors all Virtual Server events and reports them to the console window. Assuming you named the JScript code VSMonitor.js, just launch it from the commandline with:
    START CSCRIPT VSMonitor.js
  • Thereafter, whenever you start a Virtual Machine, a new child monitor script will pop up and monitor just that Virtual Machine
  • If that Virtual Machine has a Virtual DVD drive, they will also be monitored for media insertion/ejection and type (ISO or drive letter path)
  • When you turn off a Virtual machine, its associated child monitor script will also terminate (but it will first popup a message telling you this, in case you missed it).
  • It also notices when you fail to startup a Virtual Machine due to Out-of-Memory and report that via a popup

The sample basically illustrates how to:

  • Sync on Virtual Server wide events
  • Act on a Virtual Server wide event
  • Sync on Virtual Machine specific events
  • Act on a Virtual Machine specific event
  • Sync on Virtual DVD specific events
  • Report on Virtual DVD events

And it should provide enough information for you to play around, note when events happen, and note available data for the event... and implement other interesting ideas supported by the API. I don't have any in mind, but if you do... feel free to share... because then I may update this sample to do something more interesting. :-)

Enjoy.

//David

 var strVMName = "sample VM Name";
var CRLF = "\r\n";
var NO_WAIT = false;
var VISIBLE = 1;

var ERROR_VM_NOT_FOUND = -2146828283;
var ERROR_VM_OUT_OF_MEMORY = 3238134821;

var fSignaled = false;
var fPopup = true;
var cPeriod = 10000;

var VM_STATE_STRING = {
    0 : "Invalid",
    1 : "Off",
    2 : "Saved",
    3 : "Turning on",
    4 : "Restoring",
    5 : "Running",
    6 : "Paused",
    7 : "Saving",
    8 : "Turning off",
    9 : "Merging drives",
    10 : "Deleting VM"
};
var VM_STATE_OFF = 1;
var VM_STATE_RUNNING = 5;

var objVS = null;
var objVM = null;
var objDVD = null;
var objShell = null;
var enumObjs;
var enumObj;

if ( WScript.Arguments.Length > 0 )
{
    strVMName = WScript.Arguments.Item( 0 );
}

try
{
    objShell = new ActiveXObject( "WScript.Shell" );
    objVS = new ActiveXObject( "VirtualServer.Application" );

    try
    {
        //
        // Try to locate the specified VM and listen for its events
        // If fail to locate VM, just listen for VS-wide events
        //
        objVM = objVS.FindVirtualMachine( strVMName );
        WScript.ConnectObject( objVM, "VirtualMachineEvent_" );

        //
        // If the VM has any DVD drives, listen for their changes, too
        //
        enumObjs = new Enumerator( objVM.DVDROMDrives );

        for ( ; !enumObjs.atEnd(); enumObjs.moveNext() )
        {
            objDVD = enumObjs.item();
            WScript.ConnectObject( objDVD, "VirtualDVDEvent_" );
        }
    }
    catch ( e )
    {
        if ( e.number == ERROR_VM_NOT_FOUND )
        {
            LogEcho( "Failed to find VM " + strVMName + "." );
        }
        else
        {
            LogEcho( formatErrorString( e ) );
        }

        objVM = null;
        WScript.ConnectObject( objVS, "VirtualServerEvent_" );
    }

    LogEcho( "Waiting for Events from: " + CRLF +
             ( objVM  != null ? "- VM " + strVMName + CRLF : "- VS Wide" ) +
             ( objDVD != null ? "- DVD Drive(s)" + CRLF : "" ) +
             "" );

    //
    // Poll every cPeriod time period to determine if should exit
    //
    while ( !fSignaled )
    {
        WScript.Sleep( cPeriod );
    }
}
catch ( e )
{
    LogEcho(
        formatErrorString( e ) + CRLF +
        ( objShell == null ?
            "Failed to create WScript.Shell - check:" + CRLF +
            "- %windir%\System32\WSHOM.OCX is ACLed for user" + CRLF +
            "- Scripts are allowed by Personal Security products" + CRLF :
            "" ) +
        ( objVS == null ?
            "Failed to create Virtual Server COM object - check:" + CRLF +
            "- Virtual Server is installed" + CRLF +
            "- User has Control/Execute Access in VS Security" + CRLF :
            "" )
            );
    LogEcho( "Waiting a few seconds for you to read this..." );
    WScript.Sleep( cPeriod );
}

function LogEcho( str )
{
    WScript.Echo( str );
}

function LogPopup( str )
{
    if ( fPopup )
    {
        objShell.Popup( str );
    }
    else
    {
        LogEcho( str );
    }
}

function formatErrorString( objError )
{
    return "(" + Int32ToHRESULT( objError.number ) + ")" + ": " +
           objError.description;
}

function Int32ToHRESULT( num )
{
    if ( num < 0 )
    {
        return "0x" + new Number( 0x100000000 + num ).toString( 16 );
    }
    else
    {
        return "0x" + num.toString( 16 );
    }
}

//
// Virtual Server Events
//
function VirtualServerEvent_OnEventLogged( e )
{
    LogEcho( "VirtualServer_OnEventLogged: (Event ID) " + e );

    if ( e == ERROR_VM_OUT_OF_MEMORY )
    {
        LogPopup( "Cannot start VM due to out of memory" );
    }
}
function VirtualServerEvent_OnHeartbeatStopped( e )
{
    LogEcho( "VirtualServer_OnHeartbeatStopped: (VMName) " + e );
}
function VirtualServerEvent_OnServiceEvent( e )
{
    LogEcho( "VirtualServer_OnServiceEvent: (Event ID) " + e );
}
function VirtualServerEvent_OnVMStateChange( e, s )
{
    var retVal;
    LogEcho( "VirtualServer_OnVMStateChange: VM " + e + " " +
             "changed to state " + s + "(" + VM_STATE_STRING[ s ] + ")" );
    switch ( s )
    {
        case VM_STATE_RUNNING:
            LogEcho( "Running " + WScript.ScriptFullName +
                     " to monitor VM " + e );
            retVal = objShell.Run(
                        "CSCRIPT.EXE" + " " +
                        "\"" + WScript.ScriptFullName + "\"" + " " +
                        "\"" + e + "\"",
                        VISIBLE,
                        NO_WAIT );
            break;
    }
}

function VirtualMachineEvent_OnConfigurationChanged( e, s )
{
    LogEcho( "VirtualMachine_OnConfigurationChanged: " + strVMName + " " +
             "config " + e + " = " + "\"" + s + "\"" );
}
function VirtualMachineEvent_OnHeartbeatStopped()
{
    LogEcho( "VirtualMachine_OnHeartbeatStopped: " + strVMName );
}
function VirtualMachineEvent_OnRequestShutdown()
{
    LogEcho( "VirtualMachine_OnRequestShutdown: " + strVMName );
    //
    // return true/false to reboot
    //
    return false;
}
function VirtualMachineEvent_OnReset()
{
    LogEcho( "VirtualMachine_OnReset: " + strVMName );
}
function VirtualMachineEvent_OnStateChange( e )
{
    LogEcho( "VirtualMachine_OnStateChange: " + strVMName + " " +
             "changed to state " + e + "(" + VM_STATE_STRING[ e ] + ")" );
    switch ( e )
    {
        case VM_STATE_OFF:
            LogPopup( "Signaled to stop monitoring VM " + strVMName );
            fSignaled = true;

            break;
    }
}
function VirtualMachineEvent_OnTripleFault()
{
    LogEcho( "VirtualMachine_OnTripleFault: " + strVMName );
}

function VirtualDVDEvent_OnMediaInsert( e )
{
    LogEcho( "VirtualDVDEvent_OnMediaInsert: " + strVMName + " " +
             "DVD is bound to " + e );
}
function VirtualDVDEvent_OnMediaEject( e )
{
    LogEcho( "VirtualDVDEvent_OnMediaEject: " + strVMName + " " +
             "DVD is not bound to " + e );
}