HOWTO: Perform VHD Maintenance Automatically

Question:

I would like to have a night script to run on guest machines and perform the following tasks:

1. start / restore from saved state if machine is not running (preferably disconnected from network in order to preserve resources)
2. run on the guest the VHD maintenance utility (manually done by mounting the ISO from the virtual server directory and pressing next on the guest)
3. save state / ( shut down only if necessary for next stage)
4. compact the VHD

can these tasks be done without human intervention?
has anybody done something like that before?

TIA

Answer:

I presume that you know how to schedule scripts with the proper credentials to run at the desired time, so all you need is the Virtual Server automation script which does the logical task of performing VHD maintenance.

Here is a quick and dirty script that illustrates how to do this task. It does 95% of the necessary work - what it does not do (and it is clearly marked) are:

  1. Wait for a good time to insert the ISO into the Guest
  2. Wait for when the ISO finishes running

These two tasks are basic interactivity with the Guest that is highly specific to your situation. There are a couple of approaches that I know of, but you get the joy to figure out which applies best to your situation.

Sample Output:

 Selected Virtual Machine "test" on server localhost
VM is NOT already running.
Starting VM...
Choosing Virtual DVD drive to insert precompact ISO.
Ejecting existing virtual media (Drive) Q:
****** TODO - Code to wait to start task in VM ******
Attaching to ISO image file D:\Program Files\Microsoft Virtual Server\Virtual M
chine Additions\Precompact.iso
****** TODO - Code to wait for task to finish in VM ******
Reattaching Q
Save VM State prior to Compacting.
Saving VM...
Compacting VHDs.
Compacting D:\Images\TestHD0.vhd
Compacting D:\Images\TestHD1.vhd
Done!

Enjoy.

//David

 var ERROR_FILE_NOT_FOUND    = 2;
var ERROR_INVALID_PARAMETER = 87;

var GUEST_OS_SLEEP_RESOLUTION = 250;
var CLEAR_LINE = String.fromCharCode( 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8 );
var CRLF = "\r\n";

var VM_STATE_OFF            = 1;
var VM_STATE_SAVED          = 2;
var VM_STATE_RUNNING        = 5;
var VM_STATE_PAUSED         = 6;

var VMDVDDrive_None         = 0;
var VMDVDDrive_Image        = 1;
var VMDVDDrive_HostDrive    = 2;

var strServer               = "localhost";
var strVMName               = "test";
var strISO                  = "D:\\Program Files\\Microsoft Virtual Server\\" +
                              "Virtual Machine Additions\\Precompact.iso";

var Fso     = new ActiveXObject( "Scripting.FileSystemObject" );
var objVS   = new ActiveXObject( "VirtualServer.Application", strServer );
var objVM   = objVS.FindVirtualMachine( strVMName );
var task;

//
// Locate the named Virtual Machine
// If it is not found, bail.
//
if ( null == objVM )
{
    LogEcho( "Virtual Machine " + "\"" + strVMName + "\"" +
             " was not found on server " + strServer );
    Quit( ERROR_FILE_NOT_FOUND );
}

LogEcho( "Selected Virtual Machine " + "\"" + strVMName + "\"" +
         " on server " + strServer );

//
// Virtual Machine is not running. Start/resume it
//
if ( objVM.State != VM_STATE_RUNNING )
{
    LogEcho( "VM is NOT already running." );

    if ( objVM.State == VM_STATE_OFF ||
         objVM.State == VM_STATE_SAVED )
    {
        LogEcho( "Starting VM..." );
        task = objVM.Startup();
        WaitForTask( task );
    }
    else if ( objVM.State == VM_STATE_PAUSED )
    {
        LogEcho( "Resuming VM..." );
        task = objVM.Resume();
    }
    else
    {
        LogEcho( "Unexpected VM State: " + objVM.State );
        Quit( ERROR_INVALID_PARAMETER );
    }
}
else
{
    LogEcho( "VM is already running." );
}

//
// Mount the ISO and assume it auto-runs
// TODO: You need to determine when the process starts and completes
//
var enumDVDDrives = new Enumerator( objVM.DVDROMDrives );
var objDVDDrive;
var strOriginalISO;
var strOriginalDrive;

if ( enumDVDDrives.atEnd() )
{
    LogEcho( "VM does not have Virtual DVD drive." );
    LogEcho( "Saving VM..." );
    task = objVM.Save();
    WaitForTask( task );
    Quit( ERROR_INVALID_PARAMETER );
}

for ( ; !enumDVDDrives.atEnd(); enumDVDDrives.moveNext() )
{
    LogEcho( "Choosing Virtual DVD drive to insert precompact ISO." );
    objDVDDrive = enumDVDDrives.item();

    //
    // Get ready to attach the ISO. But if the Guest
    // already has an ISO attached, or if the Guest
    // is capturing a Host drive letter, remember
    // that so we can restore it on completion
    //
    try
    {
        switch ( objDVDDrive.Attachment )
        {
            case VMDVDDrive_None:
                break;
            case VMDVDDrive_Image:
                strOriginalISO = objDVDDrive.ImageFile;
                LogEcho( "Ejecting existing virtual media (ISO image) " +
                         strOriginalISO );
                objDVDDrive.ReleaseImage();
                break;
            case VMDVDDrive_HostDrive:
                strOriginalDrive = objDVDDrive.HostDriveLetter;
                LogEcho( "Ejecting existing virtual media (Drive) " +
                         strOriginalDrive + ":" );
                objDVDDrive.ReleaseHostDrive();
            break;
        }

        //
        // TODO: Figure out a good time to start the task in the VM
        //
        LogEcho( "***** TODO - Code to wait to start task in VM *****" );

        LogEcho( "Attaching to ISO image file " + strISO );
        objDVDDrive.AttachImage( Fso.GetAbsolutePathName( strISO ) );

        //
        // TODO: Wait for the task to finish inside the VM
        //
        LogEcho( "***** TODO - Code to wait for task to finish in VM *****" );
    }
    catch ( e )
    {
        LogEcho( FormatErrorString( e ) );
    }

    try
    {
        if ( strOriginalISO != null )
        {
            LogEcho( "Reattaching " + strOriginalISO );
            objDVDDrive.ReleaseImage();
            objDVDDrive.AttachImage( Fso.GetAbsolutePathName( strOriginalISO ) );
        }
        else if ( strOriginalDrive != null )
        {
            LogEcho( "Reattaching " + strOriginalDrive );
            objDVDDrive.ReleaseImage();
            objDVDDrive.AttachHostDrive( strOriginalDrive );
        }
        else
        {
            LogEcho( "Releasing attached ISO image" );
            objDVDDrive.ReleaseImage();
        }
    }
    catch ( e )
    {
        LogEcho( FormatErrorString( e ) );
    }

    break;
}

//
// Save State prior to compacting
//
LogEcho( "Save VM State prior to Compacting." );
LogEcho( "Saving VM..." );
task = objVM.Save();
WaitForTask( task );

//
// Compact all VHDs
//
LogEcho( "Compacting VHDs." );
var enumHardDiskConnection = new Enumerator( objVM.HardDiskConnections );
var objHardDiskConnection;
var objHardDisk;
for ( ; !enumHardDiskConnection.atEnd(); enumHardDiskConnection.moveNext() )
{
    try
    {
        objHardDiskConnection = enumHardDiskConnection.item();
        objHardDisk = objHardDiskConnection.HardDisk;

        LogEcho( "Compacting " + objHardDisk.File );
        task = objHardDisk.Compact();
        WaitForTask( task );
    }
    catch ( e )
    {
        LogEcho( FormatErrorString( e ) );
    }
}

LogEcho( "Done!" );


function Quit( errorNumber )
{
    WScript.Quit( errorNumber );
}

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

function FormatErrorString( e )
{
    return e.number + ": " + e.description;
}

function WaitForTask( task )
{
    var complete;
    var strLine = "";
    var cchLine = 0;

    while ( (complete = task.PercentCompleted) <= 100 )
    {
        strLine = CLEAR_LINE.substring( 0, cchLine ) + complete + "%   ";
        cchLine = strLine.length;     //this should not exceed CLEAR_LINE

        WScript.Stdout.Write( strLine );

        if ( complete >= 100 )
        {
            // Delete the % display so that next line is clean.
            WScript.Stdout.Write( CLEAR_LINE );
            break;
        }

        WScript.Sleep( GUEST_OS_SLEEP_RESOLUTION );
    }
}