/ BLOG / Automated compacting of VHD's under Microsoft Virtual Server 2005

At work we use Microsoft Virtual Server 2005 a fair bit. In some circumstances, particularly when running a Windows guest, the virtual hard disks can grow exceedingly quickly. Thankfully, there is a provided set of tools to help with this problem. A precompactor and a compactor. You run the precompactor to zero out the free diskspace, you then pause or shutdown the virtual machine and run the compactor.

This is all well and good, unless the virtual machines are running in a production environment and you need to do this at 2am in the morning. Like hell do I want to stay up for that, especially as each stage can take some time, depending on the load on the virtual server host.

Also thankfully it’s possible to automate this behaviour (thanks to David Wang, this script is heavily based on his existing work)!

  1. Copy or make the precompactor available to each virtual guest that you wish to compact
  2. Schedule the precompact to run using Scheduled Tasks, within each virtual server guest. Allow for at least 2 hours, depending on the size of the disk(s). “precompact.exe -Silent -SetDisks:cde” would automatically, and silently, compact drives C, D and E. Ammend or script as appropriate.
  3. In the virtual server host you then setup a Scheduled Task to run the script below. “cscript path\to\server\filename.js”, ensuring that you edit strServer and arrVMNames as appropriate, and that you leave enough time for all the precompacting to complete
Ensure that the script is run by a user account with the correct privileges.

// Automated VHD compacting
// Heavily based on a script by David Wang, http://blogs.msdn.com/david.wang/archive/2006/04/17/HOWTO-Perform-VHD-Maintenance-Automatically.aspx

// Usage:
// Edit the strServer and arrVMNames variables below, as appropriate
// Schedule "precompact.exe -Silent -SetDisks:cde", where "cde" are the drives to run the precompact against,
// to run a few hours before this script, inside the virtual machines
// Then on the host machine, schedule this script to run (command below), ensuring that there's enough 
// time for the precompact to have finished
// "cscript automated-vhd-compact-custom-multiple.js"

// definitions

// amount of time, in milliseconds, that the script should sleep
var GUEST_OS_SLEEP_RESOLUTION = 250;

var ERROR_FILE_NOT_FOUND = 2;

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 VM_STATE_OFF = 1;
var VM_STATE_SAVED = 2;
var VM_STATE_RUNNING = 5;
var VM_STATE_PAUSED = 6;

// Config

// server that Microsoft Virtual Server 2005 is running on
var strServer = "localhost";

// array of names - each virtual machine you wish to compact
var arrVMNames = ["Standalone - Xenon - XP", "Webserver - Tungsten - 2003 Std"];

var objVS = new ActiveXObject("VirtualServer.Application", strServer);

for (var i = 0; i < arrVMNames.length; i++)
{
   var objVM = objVS.FindVirtualMachine(arrVMNames[i]);
   var task;

   if (objVM == null)
   {
       LogEcho("Virtual Machine " + arrVMNames[i] + " was not found on server " + strServer);
       Quit(ERROR_FILE_NOT_FOUND);
   }

   LogEcho("Selected Virtual Machine " + arrVMNames[i] + " on server " + strServer);

   if (objVM.State != VM_STATE_RUNNING)
   {
       // if the VM wasn't running, then the precompact didn't run,
        // therefore there is no point in even running the compact
        LogEcho(arrVMNames[i] + " is not running");
        continue;
    }

    LogEcho("Saving VM...");
    task = objVM.Save();
    WaitForTask(task);

    LogEcho("Compacting VHDs");
    var enumHardDiskConnection = new Enumerator(objVM.HardDiskConnections);
    var objHardDiskConnection;
    var objHardDisk;
    
    while (!enumHardDiskConnection.atEnd())
    {
        try
        {
            objHardDiskConnection = enumHardDiskConnection.item();
            objHardDisk = objHardDiskConnection.HardDisk;

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

        enumHardDiskConnection.moveNext();
    }

    LogEcho("Compact done!");
    LogEcho("Starting up" + arrVMNames[i]);
    task = objVM.StartUp();
    WaitForTask(task);
    LogEcho("Startup done!");
}

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 + "%   ";
        // this should not exceed CLEAR_LINE
        cchLine = strLine.length; 

        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);
    }
}