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! 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


                        }


                  }


            }


      }


 

Comments (12)

  1. BizTalk tips from Darren Jefford (ms)

  2. C. Grossmeier says:

    Bingo! I’ve been thinking about this for a littlewhile now. Thanks for the example! I’ll let you know how we use it and how well it works in the near future…

  3. Marco De Rossi says:

    I have tried your code. I always get an error

    when I try to execute this code MessageInstance.InvokeMethod("SaveToFile", new object[] {TempDirectoryName});

    here is the stacktrace (the exception message says "The specified message is an internal message and cannot be saved."

    ex.StackTrace " at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo)rn at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)rn at System.Management.ManagementObject.InvokeMethod(String methodName, ManagementBaseObject inParameters, InvokeMethodOptions options)rn at System.Management.ManagementObject.InvokeMethod(String methodName, Object[] args)rn at BizTalkHook.BizTalkErrorHook.HandleBizTalkErrors(Object sender, EventArrivedEventArgs e) in c:\documents and settings\derossi\documenti\visual studio projects\biztalkhook\biztalkerrorhook.cs:line 160" string

  4. I am new to BizTalk 2004, I am finding it difficult to understand the concepts as the documentation is not up to the mark.

    Can you please send me some links/samples etc. which will help me get on fast track with BTS 2K4.

    I am very much interested in Adapter development and other concepts.

    Thanks in advance.

    kaushik.ruparel@lntinfotech.com

  5. Well, a client just asked on how they can get an email notification when a Biztalk message is not processed….

  6. rsquarev says:

    Darren,

    I have been using the vbscript similar to your code snippet. It works fine. The problem is when a ServiceInstance has more than 1 MessageInstances, I am able to save the FIRST message that associated with the ServiceInstance.

    Suppose one SI (ServiceInstance) has 8000 MIs (MessageInstances). When I run this, I could save only the FIRST MI of 8000 MIs. I even tried the "wbemtest" utility to cross-verify my WQL query. wbemtest also giving one MI as results. The WQL query I wrote looks like SELECT * FROM MSBTS_MessageInstance WHERE HOSTNAME="HostName" and ServiceInstanceId="{GUID}"

    What is the problem here?

    Is there any way we can save the MIs right away from SQL Server? I tried this way too. Somehow I could save the messages out, but the messages were encrypted.  The content of these message files is not readable. If I can save the MIs from SQL Server, I can delete the MIs from WMI.

    Any thoughts??

  7. rajatpk says:

    Hi  Marco De Rossi,

    I’m having the same problem when I tried to store the messages using WQL query.

    Do you have the solutions for the below error which you came across.

    “The specified message is an internal message and cannot be saved."

    Please let me know.

    Hi Darren,

    I’m having more than 500 message instances with 1 service instance for suspended non resumable messages.

    But when I run the below WQL query am getting only one message instance.

    SELECT * FROM MSBTS_MessageInstance WHERE HOSTNAME="HostName" and ServiceInstanceId="{GUID}"

    Did you solve this problem?

    Please help me.

    Thanks,

    Raja Kumaravel