by Ron Phillips, aka El Bo on Technet


If you've ever gotten a phone call at 2 am asking you to figure out why a system is down, you'll appreciate the utility of self-healing systems. With a little work with WMI we can apply this concept to BizTalk.

This article covers how to do this for BizTalk host instances, but the same techniques can be applied to receive locations and send ports.

Why do host instances go down in the first place? BizTalk sits atop SQL Server, and it's very clingy: if it loses connectivity for even a very brief interval, host instances will shut down.  Sometimes after a reboot host instances fail to come up.  Even an interruption that doesn't involve all of SQL, such as the Enterprise Single Sign-On service going offline, can bring down host instances.

And don't underestimate your fat-fingered co-workers. I once saw a week's worth of testing go down the drain because a system administrator saw this one service on his QA environment taking up a ton of CPU, so he disabled it. That service was a host instance, and so even though he wasn't a BizTalk admin he was able to halt the instance.

The key tricks at work here are all via WMI.  The complete script is at the end of the article, but let's walk through the fun parts.  All script here is good ol' fashioned vbScript, but it works the same via PowerShell, a .NET console program, or anything else that lets you access WMI.

What we're going to develop is a script that can run as a scheduled Task in Windows. When it runs, it will look at our host instances, enable ones that are disabled, and email us if it can't get the instance up and running (as well as write to the Windows application log).  For clustered environments, where the host instance on the non-active node can't be started, the script will detect this and skip that instance.

The script can be scheduled to run frequently - say, every 10 minutes - and insure that we keep our processes running. 

Building the Sample

To start, you need to be able to query the BizTalk management database. WMI gives us an easy hook for this:

Set objWMIService = GetObject("winmgmts://./root/MicrosoftBizTalkServer")

Now you can look around in the management database with SQL queries - well, really simple ones. Finding out what will and won't work requires some trial and error.  For example, our final script lets you provide a list of host names to check (rather than all of the hosts on the machine - there may be some where you want to sometimes take a host instance offline temporarily). So you would think we'd query the database for a specific host name. 

But that WHERE clause on the query just doesn't work, so we grab all of them and iterate through the list:

Set colHostInstances = objWMIService.ExecQuery("Select * from MSBTS_HostInstance Where HostType=1 And IsDisabled=False ")

That gives us a collection of host instances. In our script, we iterate through that to find a particular one.

If you're new to WMI, prepare to fall in love: you get a rich set of objects that you manipulate via straightforward properties and methods. In this case, once we've found the particular host we're interested in, we check whether it is stopped by looking at the property ServiceState.  A value of 1 indicates it has stopped, but as is good programming practice we define a constant for this so the code ends up being easy to read: 

if Ucase(HostName)=Ucase(objHostInstance.HostName ) And objHostInstance.ServiceState = HostInstServiceState_Stopped then

Next up we want to see if we're dealing with a clustered host, and if so, is the instance we're looking at on the active node or not. If you try to start a host instance on a non-active node in a cluster, it will fail, and this would cause our script to email out a false alert.

We check for whether the host is clustered and then compare whether the server the instance is on is the active node of the cluster:

if (objHostInstance.ClusterInstanceType  = HostIsClustered) then
  ' is it on the active node?
  if (Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
  iStartThis = 1
  end if
 end if

And for our final magic trick, let's look at that function we popped in there, GetActiveClusterNode(). It's a pretty simple vbScript function that performs a little more WMI magic to get the active cluster node. We start by getting the name of the computer we're running on:

Set wshShell = WScript.CreateObject( "WScript.Shell" )
 strComputerName = wshShell.ExpandEnvironmentStrings( "%COMPUTERNAME%" )

We use this to get information about the cluster the machine is part of:

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\MSCluster")
 Set colItems = objWMIService.ExecQuery("SELECT * FROM MSCluster_NodeToActiveGroup", "WQL", _
                                          wbemFlagReturnImmediately + wbemFlagForwardOnly)

And then we iterate the list to find the active node (using a little knowledge of how the data from that query is formatted - it's not really something that can be explained better than just looking at the code).

The whole script is below. It looks for a file named HostInstancesList.txt to read from (one host name per line), but you can substitute WScript.Arguments.Item(0) if you'd prefer to pass it as a command line parameter.

To run it from the Windows Task scheduler, set the task action to: cscript startuphosts.vbs    Remember to set the "Startup in" folder to the folder containing the script, otherwise the script won't find the file containing a list of hosts to check. You'll also need to put in values for the variables strSMTPServer and strDestinationAddress to enable email notifications. For these types of things, I always recommend setting up an email distribution list, so all of your admin scripts can use the same hard-coded address and you use the distrib list to manage who gets the notifications.

' StartUpHosts.vbs Start up host instances
' by Ron Phillips
' Original: 8/31/2012
' Revisions:
' Usage: Cscript StartUpHosts.vbs
' Configuration: Looks for file HostInstancesList.txt with one host instance per line
Option Explicit
CONST ForReading = 1
' Host Instance status number
CONST HostInstServiceState_Stopped = 1
' is the host clustered?
CONST HostIsClustered = 1
' Windows App Log status codes
CONST Log_Code_Error = 1
CONST Log_Code_Success = 0
CONST Log_Code_Warning = 2
CONST Log_Code_Information=4
' This next item is to let the email include a note as to which environment its from,
' so typical values might be "QA" "DEV" "UAT" etc.
CONST What_Environment="PROD"
Dim oFS        : Set oFS = CreateObject( "Scripting.FileSystemObject" )
Dim thewholelist
Dim arrReplayList
Dim arrListRow
Dim sRow
Dim strGlobalhost
' read in the list
' now we're going to do this in an inefficient way - for each record in the list,
' we query the management database and iterate through the whole list
' we could keep the collections around and just query once
' but this is a read-only operation and very low resource impact, so it's not a big deal
for each sRow in arrReplayList
 if len(sRow)>0 then
  Call HostInstanceStart(sRow)
  ' pause a second between each one
 end if
WScript.Echo("Finished starting up host instances")
WScript.Quit 0
' Support functions, if yo are adding/modifying please keep these generic/re-usable.
Sub CheckForFile(thefile)
   Dim ocheckFS        : Set ocheckFS = CreateObject( "Scripting.FileSystemObject" )
   If (Not ocheckFs.FileExists(thefile)) Then
      WScript.Echo thefile + " is missing"
      WScript.Quit 1
   End If
End Sub
Sub HostInstanceStart(HostName)
 Dim objWMIService
 Dim colHostInstances
 Dim objHostInstance
 Dim strActiveClusterNode
 Dim iStartThis
 Dim iFoundName
 '   get a WMI object to hook in to the management database
    Set objWMIService = GetObject("winmgmts://./root/MicrosoftBizTalkServer")
 '   query BizTalk host instances that are of type In-Process (within BizTalk Server installation) and enabled
    Set colHostInstances = objWMIService.ExecQuery("Select * from MSBTS_HostInstance Where HostType=1 And IsDisabled=False ")
 '   If any host instance is found check name
    If (colHostInstances.Count > 0) Then
        Wscript.Echo("Checking status of " + HostName)
        For Each objHostInstance in colHostInstances
   ' check the name
   ' To list the host name (space) server name :WScript.Echo("On " + objHostInstance.Name)
   if Ucase(HostName)=Ucase(objHostInstance.HostName ) then
    iFoundName = 1
   end if
   if Ucase(HostName)=Ucase(objHostInstance.HostName ) And objHostInstance.ServiceState = HostInstServiceState_Stopped then
    ' is it clustered?
    if (objHostInstance.ClusterInstanceType  = HostIsClustered) then
     ' is it on the active node?
     if (Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
     iStartThis = 1
     end if
    end if
    if iStartThis=1 then
     Wscript.Echo "  Starting """ & objHostInstance.Name & """..."
     Call WriteWinAppLog("BizTalk StartUpHosts.vbs script Attempting to start Host Instance " + objHostInstance.Name,Log_Code_Information)
    end if
   end if
  if iFoundName=0 Then
          Wscript.Echo "Cannot find sny enabled hosts instance for host " + HostName + ". Check to see if host instance is disabled or name is spelled wrong."
    Call WriteWinAppLog("BizTalk StartupHosts.vbs script error: Unable to find enabled host instance " + HostName,Log_Code_Error)
    Call SendErrMail("Unable to find host instance " + HostName )
  end if
        Wscript.Echo "Cannot find sny enabled hosts on this system"
  Call WriteWinAppLog("BizTalk StartupHosts.vbs script error: Unable to find any enabled host instances on this system",Log_Code_Error)
  Call SendErrMail("Unable to find any enabled host instances on this system "  )
 End If
End Sub
Function GetActiveClusterNode()
 On Error Resume Next
 Dim strnodename
 Dim strnodename1
 Dim strComputer
 Dim wshShell
 Dim computername
 Dim arrComputers
 Dim objWMIService
 Dim Count, count1
 Dim colItems,objItem
 Dim strcomputername
 Set wshShell = WScript.CreateObject( "WScript.Shell" )
 strComputerName = wshShell.ExpandEnvironmentStrings( "%COMPUTERNAME%" )
 computername = Trim(strcomputername)
 count = count + 1
 Const wbemFlagReturnImmediately = &h10
 Const wbemFlagForwardOnly = &h20
 arrComputers = Array("localhost")
 For Each strComputer In arrComputers
  Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\MSCluster")
  Set colItems = objWMIService.ExecQuery("SELECT * FROM MSCluster_NodeToActiveGroup", "WQL", _
                                          wbemFlagReturnImmediately + wbemFlagForwardOnly)
  For Each objItem In colItems
   strnodename = right(objItem.GroupComponent,count)
   strnodename1 = left(strnodename,count1)
   if len(strnodename1)>0 then
    'WScript.Echo "Active Cluster node is " & strnodename1
    GetActiveClusterNode= strnodename1
   end if
 WScript.Echo "Active Cluster node is " + strnodename1
 Set objWMIService = Nothing
 Set wshShell = Nothing
End Function
Sub   CheckWMIError()
   if Err <> 0   Then
      On Error Resume   Next
      Dim strErrDesc: strErrDesc = Err.Description
      Dim ErrNum: ErrNum = Err.Number
      Dim WMIError : Set WMIError = CreateObject("WbemScripting.SwbemLastError")
   Dim FinalMessage
      if ( TypeName(WMIError) = "Empty" ) then
         FinalMessage strErrDesc & " (HRESULT: "   & Hex(ErrNum) & ")."
         FinalMessage= WMIError.Description & "(HRESULT: " & Hex(ErrNum) & ")."
         Set WMIError = nothing
      end if
      Call WriteWinAppLog(FinalMessage,Log_Code_Error)
   Call SendErrMail("Error starting " & strGlobalHost & " " & FinalMessage)
      ' if you want to quit on err: wscript.quit 0
   end if
End Sub
Sub WriteWinAppLog(Message,Log_Code)
Dim objShell
 Set objShell = Wscript.CreateObject("Wscript.Shell")
 objShell.LogEvent Log_Code,Message
 Set objShell = Nothing
End Sub
' This subroutine sends an email out for the error. email address and smtp server are hard coded
Sub SendErrMail(strMessage)
Dim strSMTPServer,strDestinationAddress
Dim objEmail
 ' set an email address to send errors to. This could also be taken from a command line parameter
 Set objEmail = CreateObject("CDO.Message")
 objEmail.From = strDestinationAddress
 objEmail.To = strDestinationAddress
 objEmail.Subject = "BizTalk " + What_Environment  + ": Unable to start host instance"
 objEmail.Textbody = strMessage
 objEmail.Configuration.Fields.Item _
 objEmail.Configuration.Fields.Item _
 objEmail.Configuration.Fields.Item _
End Sub

See Also

Another important place to find a huge amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.