Suspended Messages

I’ve been working with a customer recently around a new BizTalk 2004 project, they raised a question around being notified when a “Message” was suspended by BizTalk 2004.

BizTalk suspends Messages if say a validation error occurs in a pipeline (schema validation failure, etc.) and for a myriad of other reasons! This is fine but how do you then find out about messages being suspended, so you can then salvage any data or work out what went wrong.

It turns out that BizTalk has a WMI event for just this event and it’s pretty simple to hook this event to get notification, e.g:

 

using System.Management;

...

      // Set up an event watcher and a handler for the MSBTS_ServiceInstanceSuspendedEvent event

      ManagementEventWatcher watcher = new ManagementEventWatcher( new ManagementScope("root\\MicrosoftBizTalkServer"),

                        new EventQuery("SELECT * FROM MSBTS_ServiceInstanceSuspendedEvent") );

      watcher.EventArrived += new EventArrivedEventHandler(MyEventHandler);

      // Start watching for MSBTS_ServiceInstanceSuspendedEvent events

      watcher.Start();

 

So you could write a NT Service that hooks this event and your away – pretty straight forward. But, how do you then get hold of the actual message data including the “context” information that BizTalk maintains? Well that’s a bit harder J

You get given the ServiceInstanceID which relates to the ServiceInstance that was suspended, you then need to get all of the Message Instances that relate to it. However each Message Instance could have a number of Message Parts so you need to cater for all (or one in most cases of these). But to get that MessagePart information you need to parse the MessageInstance context file, once you’ve got the MessagePart information you can then get hold of the bodies – but only via saving them to disk then loading them up!

This code uses the right naming convention to figure out the names of the saved files, you could write something to watch for new files but this is a lot more reliable!

Phew!

Here’s some sample code – it’s a bit rough and ready but gives you the idea. Let me know if you find any problems! J Apologies for the HTML formatting of code, drop me a mail if you want the code

 

static public void MyEventHandler(object sender, EventArrivedEventArgs e)

      {

            string TempDirectoryName = @"c:\drop\out";

            //Lookup MSBTS_ServiceInstanceSuspendedEvent in the BTS04 documentation for additional properties

            string ErrorID = e.NewEvent["ErrorID"].ToString();
            string ErrorCategory = e.NewEvent["ErrorCategory"].ToString();
            string ErrorDescription = e.NewEvent["ErrorDescription"].ToString();

            string ServiceStatus = e.NewEvent["ServiceStatus"].ToString();

            string ServiceInstanceID = e.NewEvent["InstanceID"].ToString();   

            EnumerationOptions enumOptions = new EnumerationOptions();

            enumOptions.ReturnImmediately = false;

            ManagementObjectSearcher MessageInstancesInServiceInstance = new ManagementObjectSearcher("root\\MicrosoftBizTalkServer",

                        "Select * from MSBTS_MessageInstance where ServiceInstanceID='"+ ServiceInstanceID + "'",enumOptions);

            //Enumerate through the result set and start each HostInstance if it is already stopped

            foreach(ManagementObject MessageInstance in MessageInstancesInServiceInstance.Get())

            {

                  // The only way to get at the message body is to utilise the SaveToFile

                  // method on the BTS_MessageInstance WMI Class.

                  // This saves all of the message information to files.

                  // Each MessagePart making up a Message is saved in seperate files, typically you only

                  // get a Body, but we cater for Multi-Part messages to cover all scenarios

                   

                  // As well as the MessageParts a context file is created, we need to use this to

                  // extract the MessagePartID's and MessagePartName's out so we can then work out the filenames

                  // to open!

 

                  // The context filename format is:

                  // <MessageInstanceID>_context.xml

 

                  // And then the actual message information filename format is:

 

                  // <MessageInstanceID>_<MessagePartID>[_<MessagePartName>].out

                  // MessagePartName is only required if the MessagePart has a name!

 

                  // We need to build this filename up so we can load it up - no hacking here!

 

                  // Save the files

                  MessageInstance.InvokeMethod("SaveToFile", new object[] {TempDirectoryName});

 

                  // Get the MessageInstanceID

                  string MessageInstanceID = MessageInstance["MessageInstanceID"].ToString();

                       

                  // We now need to load the context file up to get the MessagePart information

                  string ContextFileName = String.Format(@"{0}\{1}_context.xml",TempDirectoryName,MessageInstanceID);

 

                  // Load the context file up

                  XmlDocument doc = new XmlDocument();

                  doc.Load(ContextFileName);

 

                  // Use XPath to return all of the MessagePart(s) referenced in the context

                  // We can then load the file up to get the Message information

                  XmlNodeList MessageParts = doc.SelectNodes("/MessageInfo/PartInfo/MessagePart");

                  foreach (XmlNode MessagePart in MessageParts)

                  {

                        // Pull the MessagePart info out that we need

                        string MessagePartID = MessagePart.Attributes["ID"].Value;

                        string MessagePartName = MessagePart.Attributes["Name"].Value;

 

                        // If we have a MessagePartName we have to append this to the end of the filename

                        // It's optional so if we don't have it we don't worry about it

                        if (MessagePartName.Length > 0)

                        {

                              string FileName = String.Format(@"{0}\{1}_{2}_{3}.out",TempDirectoryName,MessageInstanceID,MessagePartID,MessagePartName);

                              // Do your work

                        }

                        else

                        {

                              string FileName = String.Format(@"{0}\{1}_{2}.out",TempDirectoryName,MessageInstanceID,MessagePartID);

                              // Do your work

                        }

                  }

            }

      }