Appendix B, Script for Virtual Server Host Clustering

The following Visual Basic script ensures that in a Virtual Server host cluster, the guest functions correctly when a failover or other cluster-related process occurs. The script also triggers restart of the guest if the guest stops running. The script is configured as a Generic Script resource in the cluster.

To use the script, carefully copy only the script text (not including "Havm.vbs") into a text editor such as Notepad and then save the script, using Havm.vbs as the filename. When saving the file, be sure to save it in a way that does not add linebreaks.

Havm.vbs

'*******************************************************************************************************************************************************
'Global variables
'*******************************************************************************************************************************************************
'Script Version
ScriptVersion = "1.0"

'Flagged TRUE if the virtual machine has ever responded to an additions heartbeat.
VirtualMachineHasHeartbeat = FALSE

'Used by Terminate() to make failure recovery decisions.
VirtualMachineFailedOnline = FALSE

'Time in miliseconds to wait to save/restore state.
Timeout = 300000

'Stores the percentage of received heartbeats over 1 time block at which the guest is considered dead.
AdditionsIsAliveHeartbeatThreshold = 0

'*******************************************************************************************************************************************************
'Open()
'
'Check to see if "Virtual Server" service is running. If not attempt to start it.
'
'Check to see if the following private properties exist. If not, create/set them:
'   VirtualMachineName - Stores the name of the virtual machine that is made highly available by this resource. (User has to set it explicitly)
'   UseAdditionsIsAlive - 1 to use the additions heartbeat code path in IsAlive(), 0 to not use that code path. (Default is 1)
'   AdditionsIsAliveFailureRecoveryAction - 1 to turn off vm, 0 to save state. (Default is 1)
'*******************************************************************************************************************************************************
Function Open()
    
    On Error Resume Next
    Resource.LogInformation("Entering Open() for Virtual Server Host Clustering Generic Script Version " & ScriptVersion)
    Set wmiProvider = GetObject("winmgmts:/root/cimv2")
    Set vsService = wmiProvider.Get("win32_service='Virtual Server'")
    vsServiceState = vsService.State

    If uCase(vsServiceState) <> "RUNNING" Then
        returnValue = vsService.StartService()
        'Check if the call to start the service succeeded or not. 0 or 10 means it did.
        If (returnValue <> 0) and (returnValue <> 10) Then      
            Resource.LogInformation("Attempt to start 'Virtual Server' service on this machine failed with error " & returnValue)
            Open = returnValue
            Exit Function
        End If
    End If

    If Resource.PropertyExists("VirtualMachineName") = FALSE Then
        Resource.AddProperty("VirtualMachineName")
    End If

    If Resource.PropertyExists("UseAdditionsIsAlive") = FALSE Then
        Resource.AddProperty("UseAdditionsIsAlive")
        Resource.UseAdditionsIsAlive = 1
    End If

    If Resource.PropertyExists("AdditionsIsAliveFailureRecoveryAction") = FALSE Then
        Resource.AddProperty("AdditionsIsAliveFailureRecoveryAction")
        Resource.AdditionsIsAliveFailureRecoveryAction = 1
    End If

End Function


'*******************************************************************************************************************************************************
'Online()
'
'Issue a start control to the virtual machine.
'
'If the virtual machine is already in "Running" state, this can either mean the virtual machine has very recently failed, or it is really up and running.  
'Handle the worst case by setting VirtualMachineFailedOnline = TRUE, which will cause Terminate() to attempt to save the state. 
'*******************************************************************************************************************************************************
Function Online( )

    On Error Resume Next
    If Resource.VirtualMachineName = "" Then
        Resource.LogInformation("The VirtualMachineName private property is blank. Please run the following command on any one node of the cluster to correct the problem: cluster.exe res """ & Resource.Name & """ /priv VirtualMachineName=" & """name of virtual machine""" )
        Online = 13 '13 - The data is invalid.
        Exit Function
    Else
        Resource.LogInformation("Entering Online() for " & Resource.VirtualMachineName)
    End If

    Set virtualServer = CreateObject("VirtualServer.Application")
    Set vm = virtualserver.FindVirtualMachine(Resource.VirtualMachineName)
    Set state = vm.Startup() 

    Select Case err.number <> 0 
        Case err.number = "-1610349312"
            'Handle the case where the virtual machine is already in "Running" state at the time the resource is brought online.  
            If vm.state = 5 Then
                Resource.LogInformation("Startup() was called for virtual machine " & Resource.VirtualMachineName & ", however it was already in the started state. Setting VirtualMachineFailedOnline = TRUE")
                VirtualMachineFailedOnline = TRUE
                Online = 1
                Exit Function  
            End If
    Case err.number = "2147614729"
            If vm.state = 5 Then
                Resource.LogInformation("Startup() was called for virtual machine " & Resource.VirtualMachineName & ", however it was already in the started state. Setting VirtualMachineFailedOnline = TRUE")
                VirtualMachineFailedOnline = TRUE
                Online = 1
                Exit Function  
            End If
    Case err.number = "2684617984"
            If vm.state = 5 Then
                Resource.LogInformation("Startup() was called for virtual machine " & Resource.VirtualMachineName & ", however it was already in the started state. Setting VirtualMachineFailedOnline = TRUE")
                VirtualMachineFailedOnline = TRUE
                Online = 1
                Exit Function  
            End If
        Case Else
            Resource.LogInformation("Startup() for virtual machine " & Resource.VirtualMachineName & " failed with error " & err.number)
            Online = err.number
            Exit Function
    End Select

    Call state.WaitForCompletion(Timeout)
    If state.IsComplete = TRUE Then
        Online = 0
    End If

End Function


'*******************************************************************************************************************************************************
'LooksAlive()
'
'Return success
'*******************************************************************************************************************************************************
Function LooksAlive()
  LooksAlive = TRUE
End Function


'*******************************************************************************************************************************************************
'IsAlive()
'
'If UseAdditionsIsAlive = 1 and if VirtualMachineHasHeartbeat = TRUE and if vm.GuestOS.HeartbeatPercentage =< AdditionsIsAliveHeartbeatThreshold set IsAlive to FALSE and exit function.
'If UseAdditionsIsAlive = 1 and if VirtualMachineHasHeartbeat = TRUE and if vm.GuestOS.heartbeatpercentage > AdditionsIsAliveHeartbeatThreshold set IsAlive to TRUE and exit function.
'If UseAdditionsIsAlive <> 1 or if VirtualMachineHasHeartbeat <> TRUE, then attempt to set VirtualMachineHasHeartbeat and do basic IsAlive.
'*******************************************************************************************************************************************************
Function IsAlive()
    
    On Error Resume Next
    IsAlive = FALSE

    Set virtualServer = CreateObject("VirtualServer.Application")
    Set vm = virtualserver.FindVirtualMachine(Resource.VirtualMachineName)

    If (Resource.UseAdditionsIsAlive = 1) Then
        If VirtualMachineHasHeartbeat = TRUE Then
            If vm.GuestOS.HeartbeatPercentage <= AdditionsIsAliveHeartbeatThreshold Then
                IsAlive = FALSE
                Exit Function
            Else
                IsAlive = TRUE
                Exit Function                
            End If
        End If
    End If

    'Set VirtualMachineHasHeartbeat
    VirtualMachineHasHeartbeat = vm.GuestOS.IsHeartbeating
    
    'Do basic IsAlive check
    If vm.state = 3 or vm.state = 4 or vm.state = 5 Then
        IsAlive = TRUE
    End If

End Function


'*******************************************************************************************************************************************************
'Offline()
' 
'Issue a save state control to the virtual machine.  
'
'If the virtual machine is not already in the "Running" state, assume success.
'*******************************************************************************************************************************************************
Function Offline()

    On Error Resume Next
    Resource.LogInformation("Entering Offline() for " & Resource.VirtualMachineName)

    Set virtualServer = CreateObject("VirtualServer.Application")
    Set vm = VirtualServer.FindVirtualMachine(Resource.VirtualMachineName)
    Set state = vm.Save() 

    Select Case err.number <> 0
        'Handle the case where the virtual machine is not already in the "Running" state at the time the resource is taken offline.  
        Case err.number = "2684617222"
            Resource.LogInformation("Save() was called for virtual machine " & Resource.VirtualMachineName & ", however it was not in the started state.")
        'Handle another case where the virtual machine is not already in the "Running" state at the time the resource is taken offline.  
        Case err.number = "-1610350074"
            Resource.LogInformation("Save() was called for virtual machine " & Resource.VirtualMachineName & ", however it was not in the started state.")
        Case Else
            Resource.LogInformation("Save() for virtual machine " & Resource.VirtualMachineName & " failed with error " & err.number)
            Offline = err.number
            Exit Function
    End Select

    Call state.WaitForCompletion(Timeout)
    If state.IsComplete = TRUE Then
        Offline = 0
    End If

End Function


'*******************************************************************************************************************************************************
'Terminate()
'
'If Online() failed because the virtual machine was already "Running" (VirtualMachineFailedOnline = FALSE), then Terminate() needs to attempt to save state.
'If UseAdditionsIsAlive = 1 and VirtualMachineHasHeartbeat = TRUE, then take failure action specified by AdditionsIsAliveFailureRecoveryAction.
'*******************************************************************************************************************************************************
Function Terminate()

    On Error Resume Next   
    Set virtualServer = CreateObject("VirtualServer.Application")
    Set vm = virtualserver.FindVirtualMachine(Resource.VirtualMachineName) 

    If VirtualMachineFailedOnline = TRUE Then
        Set state = vm.Save()
        Call state.WaitForCompletion(Timeout)
        Exit Function
    End If

    If Resource.UseAdditionsIsAlive = 1 AND virtualMachineHasHeartbeat = TRUE Then
        If Resource.AdditionsIsAliveFailureRecoveryAction = 0 Then
            Set state = vm.Save()
            Call state.WaitForCompletion(Timeout)
            Exit Function
        Else
            Set state = vm.TurnOff()
            Call state.WaitForCompletion(Timeout)
            Exit Function
        End If
    End If

End Function

'*******************************************************************************************************************************************************
'Close()
'
'Return success
'*******************************************************************************************************************************************************
Function Close()
    Close = 0
End Function