BizTalk Server: Monitoring and Automatically Starting Host Instances Via A Scheduled Task
by Ron Phillips, aka El Bo on Technet
Introduction
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?
strActiveClusterNode=GetActiveClusterNode()
if (Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
iStartThis = 1
end if
else
iStartThis=1
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
CheckForFile("HostInstancesList.txt")
' read in the list
thewholelist=oFS.OpenTextFile("HostInstancesList.txt",ForReading).ReadAll
arrReplayList=Split(thewholelist,vbCrLf)
' 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
strGlobalhost=sRow
Call HostInstanceStart(sRow)
' pause a second between each one
WScript.Sleep(1000)
end if
next
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
iFoundName=0
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
iStartThis=0
' is it clustered?
if (objHostInstance.ClusterInstanceType = HostIsClustered) then
' is it on the active node?
strActiveClusterNode=GetActiveClusterNode()
if (Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
iStartThis = 1
end if
else
iStartThis=1
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)
objHostInstance.Start
CheckWMIError
end if
end if
Next
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
Else
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=Len(strcomputername)
Count1=Len(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
Next
Next
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) & ")."
else
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
strSMTPServer="yoursmtp.yourcompany.com"
' set an email address to send errors to. This could also be taken from a command line parameter
'strDestinationAddress="biztalkadmin@yourcompany.com"
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 _
("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
objEmail.Configuration.Fields.Item _
("http://schemas.microsoft.com/cdo/configuration/smtpserver") = _
strSMTPServer
objEmail.Configuration.Fields.Item _
("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objEmail.Configuration.Fields.Update
objEmail.Send
End Sub
See Also
Another important place to find an extensive amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.