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.
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:
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()
(Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
iStartThis = 1
end
else
iStartThis=1
...
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:
wshShell = WScript.CreateObject(
"WScript.Shell"
strComputerName = wshShell.ExpandEnvironmentStrings(
"%COMPUTERNAME%"
We use this to get information about the cluster the machine is part of:
"winmgmts:\\" & strComputer & "
\root\MSCluster")
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 :
oFS = CreateObject(
"Scripting.FileSystemObject"
thewholelist
arrReplayList
arrListRow
sRow
strGlobalhost
CheckForFile(
"HostInstancesList.txt"
' read in the list
thewholelist=oFS.OpenTextFile(
,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)
ocheckFS :
ocheckFS = CreateObject(
If
(
Not
ocheckFs.FileExists(thefile))
Then
WScript.Echo thefile +
" is missing"
WScript.Quit 1
End
HostInstanceStart(HostName)
objWMIService
colHostInstances
objHostInstance
strActiveClusterNode
iStartThis
iFoundName
' get a WMI object to hook in to the management database
' query BizTalk host instances that are of type In-Process (within BizTalk Server installation) and enabled
iFoundName=0
' If any host instance is found check name
(colHostInstances.Count > 0)
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
iStartThis=0
' is it clustered?
if (objHostInstance.ClusterInstanceType = HostIsClustered) then
if (Ucase(objHostInstance.RunningServer) = Ucase(strActiveClusterNode)) then
if iStartThis=1 then
Wscript.Echo
" Starting "
""
& objHostInstance.Name &
"..."
WriteWinAppLog(
"BizTalk StartUpHosts.vbs script Attempting to start Host Instance "
+ objHostInstance.Name,Log_Code_Information)
objHostInstance.Start
CheckWMIError
Next
if iFoundName=0
"Cannot find sny enabled hosts instance for host "
+ HostName +
". Check to see if host instance is disabled or name is spelled wrong."
"BizTalk StartupHosts.vbs script error: Unable to find enabled host instance "
+ HostName,Log_Code_Error)
SendErrMail(
"Unable to find host instance "
+ HostName )
Else
"Cannot find sny enabled hosts on this system"
"BizTalk StartupHosts.vbs script error: Unable to find any enabled host instances on this system"
,Log_Code_Error)
"Unable to find any enabled host instances on this system "
Function
GetActiveClusterNode()
On
Error
Resume
strnodename
strnodename1
strComputer
wshShell
computername
arrComputers
Count, count1
colItems,objItem
strcomputername
computername = Trim(strcomputername)
Count=Len(strcomputername)
Count1=Len(strcomputername)
count = count + 1
Const
wbemFlagReturnImmediately = &h10
wbemFlagForwardOnly = &h20
arrComputers = Array(
"localhost"
In
objItem
colItems
strnodename = right(objItem.GroupComponent,count)
strnodename1 = left(strnodename,count1)
if len(strnodename1)>0 then
'WScript.Echo "Active Cluster node is " & strnodename1
GetActiveClusterNode= strnodename1
WScript.Echo
"Active Cluster node is "
+ strnodename1
objWMIService =
Nothing
wshShell =
CheckWMIError()
if Err <> 0
strErrDesc: strErrDesc = Err.Description
ErrNum: ErrNum = Err.Number
WMIError :
WMIError = CreateObject(
"WbemScripting.SwbemLastError"
FinalMessage
if ( TypeName(WMIError) =
"Empty"
) then
FinalMessage strErrDesc &
" (HRESULT: "
& Hex(ErrNum) &
")."
FinalMessage= WMIError.Description &
"(HRESULT: "
WMIError = nothing
WriteWinAppLog(FinalMessage,Log_Code_Error)
"Error starting "
& strGlobalHost &
" "
& FinalMessage)
' if you want to quit on err: wscript.quit 0
WriteWinAppLog(Message,Log_Code)
objShell
objShell = Wscript.CreateObject(
"Wscript.Shell"
objShell.LogEvent Log_Code,Message
objShell =
' This subroutine sends an email out for the error. email address and smtp server are hard coded
SendErrMail(strMessage)
strSMTPServer,strDestinationAddress
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"
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
"http://schemas.microsoft.com/cdo/configuration/smtpserver"
) = _
strSMTPServer
"http://schemas.microsoft.com/cdo/configuration/smtpserverport"
) = 25
objEmail.Configuration.Fields.Update
objEmail.Send
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.