I know often people run into the need to extract a message from the BizTalk tracking database. I thought I’d write a post on the 3 methods I’ve found so far over time.
As some of you know the body and context of messages in BizTalk are compressed, and rightfully so. The actual compression and decompression code is hidden inside a dll called BTSDBAccessor.dll. This DLL, the “BizTalk Database Acessor”, is unmanaged and does a lot of work for BizTalk including accessing the BizTalk databases to send and receive messages.
Based on blog sites and forums I’ve run into over time I know of three ways we can programmatically get a message body out of the tracking database: Operations DLL, SQL, and WMI. I’ve created a little test C# Windows application that uses all three.
The application has only one form (print-screen below), and expects the following parameters:
– The message guid of the message you want to extract.
– The extraction type (Use Operations DLL, Use SQL, Use WMI)
– Tracking DB server (the BizTalk server name)
– Tracking DB name (the BizTalk Tracking database name)
Here is the code for the “Get Message” button:
private void btnGetMessage_Click(object sender, EventArgs e)
{
txtMessage.Clear();
txtMessage.Refresh();
GuidmessageGuid;
try
{
messageGuid = new Guid(txtGuid.Text);
}
catch (ExceptionexGuid)
{
txtMessage.Text = "Please enter a valid Guid. Error: "+ exGuid.Message;
return;
}
switch(cboGetType.SelectedIndex)
{
case 0:
txtMessage.Text = GetMessageWithOperations(messageGuid);
break;
case 1:
txtMessage.Text = GetMessageWithSQL(messageGuid);
break;
case 2:
txtMessage.Text = GetMessageWithWMI(messageGuid);
break;
default:
break;
}
}
Now, let’s finally see the three ways of getting to the message body, shall we?
1. My favourite, use the Microsoft.BizTalk.Operations dll. This is pretty straight forward, you add a reference to Microsoft.BizTalk.Operations.dll and use the GetTrackedMessage of the BizTalkOperations class. You can also get to the message context using this method. This is only for BizTalk 2006 and later. Here is the code:
//Retrieves the message using the operations DLL - Add Microsofr.BizTalk.Operations.dll to references
public string GetMessageWithOperations(GuidMessageInstanceId)
{
try
{
TrackingDatabasedta = new TrackingDatabase(txtTrackingDBServer.Text, txtTrackingDBName.Text);
BizTalkOperations operations = new BizTalkOperations();
IBaseMessagemessage = operations.GetTrackedMessage(MessageInstanceId, dta);
string body = string.Empty;
using (StreamReaderstreamReader = new StreamReader(message.BodyPart.Data))
{
body = streamReader.ReadToEnd();
}
return body;
}
catch (ExceptionexOp)
{
return "Failed to get message with id "+ MessageInstanceId.ToString() + " from tracking database: "+ exOp.Message;
}
}
Here’s the great post mentioning this method by Richard Hallgren:
http://www.richardhallgren.com/reading-the-message-body-and-context-from-the-biztalkdtadb-using-operations-library-in-biztalk-2006/
2. Use the WMI MSBTS_TrackedMessageInstance.SaveToFile method to save the instance to disk. This was the popular method in BizTalk 2004 since there was no operations dll then. Here is the code:
//Uses WMI to save the tracked message out to a file folder using MSBTS_TrackedMessageInstance class
public stringGetMessageWithWMI(GuidMessageInstanceId)
{
try
{
// Construct full WMI path to the MSBTS_TrackedMessageInstance using the message guid (NOTE: MessageInstanceID string value must be enclosed in {} curly brackets)
string strInstanceFullPath = "\\\\.\\root\\MicrosoftBizTalkServer:MSBTS_TrackedMessageInstance.MessageInstanceID='{"+ MessageInstanceId.ToString() + "}'";
// Load the MSBTS_TrackedMessageInstance
ManagementObject objTrackedSvcInst = new ManagementObject(strInstanceFullPath);
// Invoke "SaveToFile" method to save the message out into the specified folder
objTrackedSvcInst.InvokeMethod("SaveToFile", new object[] {Application.StartupPath});
//Get all files in the directory starting with this messageid
string[] files = Directory.GetFiles(Application.StartupPath, "{"+ MessageInstanceId.ToString() + "*.*");
string message = "";
foreach (string file in files)
{
if(file.EndsWith(".out"))
{
using (StreamReadersr = new StreamReader(file))
{
message = sr.ReadToEnd();
}
}
}
foreach (string file in files)
{
System.IO.File.Delete(file);
}
if (files.Length == 0)
{
throw new Exception("No files found on folder that match the GUID");
}
return message;
}
catch (ExceptionexWMI)
{
return "Failed to save tracked message with id "+ MessageInstanceId.ToString() + " into folder " + Application.StartupPath + ": "+ exWMI.Message;
}
}
http://kentweare.blogspot.com/2007/05/biztalk-retrieving-tracked-messages.html
3. The bts_GetTrackedMessageParts stored procedure inside the tracking database expects the message GUID and will return the compressed message data back. We can then use reflection to invoke the Decompress method of the Microsoft.BizTalk.Message.Interop.CompressionStreams class inside Microsoft.BizTalk.Pipeline.dll to decompress the data returned from SQL. Here is the code:
//Calls BizTalk stored procedure to retrieve compressed message and decompresses it
public stringGetMessageWithSQL(GuidMessageInstanceId)
{
try
{
//Connection to DTA database on localhost
SqlConnection con = new SqlConnection("Data Source=" + txtTrackingDBServer.Text + ";Initial Catalog=" + txtTrackingDBName.Text + ";Integrated Security=True");
string message = "";
try
{
SqlCommandcmd = new SqlCommand();
SqlDataReader reader;
//Build execution of stored procedure bts_GetTrackedMessageParts
cmd.CommandText = "bts_GetTrackedMessageParts";
cmd.CommandType = CommandType.StoredProcedure;
SqlParameterguidParameter = new SqlParameter("@uidMsgID", SqlDbType.UniqueIdentifier);
guidParameter.Value = MessageInstanceId;
cmd.Parameters.Add(guidParameter);
cmd.Connection = con;
con.Open();
reader = cmd.ExecuteReader();
//Get the reader to retrieve the data
while (reader.Read())
{
//Use memory stream and reflection to get the data
SqlBinarybinData = new SqlBinary((byte[])reader["imgPart"]);
MemoryStream stream = new MemoryStream(binData.Value);
AssemblypipelineAssembly = Assembly.LoadFrom(string.Concat(@"C:\Program Files\Microsoft BizTalk Server 2006", @"\Microsoft.BizTalk.Pipeline.dll"));
TypecompressionStreamsType = pipelineAssembly.GetType("Microsoft.BizTalk.Message.Interop.CompressionStreams", true);
StreamReader st = new StreamReader((Stream)compressionStreamsType.InvokeMember("Decompress", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Static, null, null, new object[] { (object)stream }));
message = st.ReadToEnd();
}
}
finally
{
con.Close();
}
return message;
}
catch (ExceptionexSQL)
{
return "Failed to get message with id "+ MessageInstanceId.ToString() + " from tracking database: "+ exSQL.Message;
}
}
This is the post that I ran into that helped me with this method:
http://www.tech-archive.net/Archive/BizTalk/microsoft.public.biztalk.general/2007-05/msg00124.html
Hope this helps someone! Post a comment (and fill out the email address field) or use the “Contact” page if you want the source code, but it’s pretty much all here in this post.

RSS

[...] extracting messages from the BizTalk tracking database. One of which is this excellent article by Thiago Almeida. Then it occured to me that the WMI script Terminate.vbs has the option to save the messages before [...]
Pingback by Saving suspended messages in BizTalk « Vincent Choo’s blog — October 1, 2008 @ 3:55 am
Hello Thiago!
I tried to use your code from the first example and I had an error: System.ArgumentNullException was unhandled by user code
Message=”Value cannot be null.\r\nParameter name: dbName”
Source=”Microsoft.BizTalk.Operations”
ParamName=”dbName”
StackTrace:
at Microsoft.BizTalk.Operations.BizTalkDatabase.CheckInputParams(String dbServer, String dbName)
…
Would you comment it?
Comment by Fat64 — October 2, 2008 @ 2:17 am
Hi,
You have to make sure you type the correct Tracking DB server and Tracking DB name in the appropriate boxes. Are you using BizTalk 2004? In 2006 R2 it shows an “invalid input parameter Parameter name: dbName” error.
Comment by Thiago Almeida — October 2, 2008 @ 7:15 am
I’ve found some problems using two of the methods:
1. Using the operation dll:s, sql connections used are not closed when message has been received. If you for instance invokes the gettrackedmessage 1000 times in a row, you will have 1000 open sql connections until the application is closed.
2. Using WMI to retrieve the message, you will have problem in a clustered environment if you try to receive messages sometimes. Out of 1000 messages, only a few percent is retrieved correctly.
Any solutions to the two problems?
Comment by Daniel Jansson — October 2, 2008 @ 8:01 pm
Another question
Does anyone know how to retrieve the message property bag / context using the last example?
The message body works fine, and I assume the context is in the imgPropBag field.
But I get exceptions when trying to decompress that.
Comment by Daniel Jansson — October 2, 2008 @ 9:25 pm
You are partially right. I had Biztalk Server 2006, not R2. With R2 it works fine. Thank you!
Comment by Fat64 — October 2, 2008 @ 9:48 pm
Daniel,
I’m not sure you will be able to get the message context using the third method. See the “What used to be the problem?” section of Richard’s post here: http://www.richardhallgren.com/reading-the-message-body-and-context-from-the-biztalkdtadb-using-operations-library-in-biztalk-2006/
I suppose to release the connection to the tracking database you would have to dispose of the BizTalkOperations or the TrackingDatabase object – I will investigate some more tomorrow. This issue has been found before with not many comments http://forums.microsoft.com/TechNet/ShowPost.aspx?PostID=3896987&SiteID=17 so it might be worth spending some time on it!
Regards, Thiago
Comment by Thiago Almeida — October 2, 2008 @ 10:12 pm
Hi Daniel,
I’ve investigated this further (and noticed your post on the forum!). It indeed doesn’t release the connections straight away. I does release all of them when you close the application though.
It eventually releases them however: I tested calling it 226 times to save all the tracked messages I had on my deve machine. It took about 11 minutes to release all the connections if I left the application open.
Calling dta.Dispose() for the TrackingDatabase passed to the GetTrackedMessage method does not help, and even refactoring BizTalkOperations to get access to the OperationsGroup object exposed by the private _biztalkGroup property then calling it’s dispose method does not release them.
You might have to either rely on the connections getting slowly released or the “create an app domain” method you described on the forums post.
Comment by Thiago Almeida — October 3, 2008 @ 12:40 pm
Regarding the message context, it is actually possible to get this from the database. I’ve never found it documented anywhere, but by working back through the BizTalk code using Reflector I was able to figure it out:
First add references to Microsoft.BizTalk.Interop.Agent.dll and Microsoft.BizTalk.Pipeline.dll and use the following namespaces:
using Microsoft.BizTalk.Agent.Interop;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
Then call the stored proc ops_LoadTrackedMessageContext and then get the result into a MemoryStream as in the code above.
You can then walk through the context as follows:
MemoryStream stream = … // result of calling ops_LoadTrackedMessageContext
IBaseMessageContext context = ((IBTMessageAgentFactory)((IBTMessageAgent)new BTMessageAgent())).CreateMessageContext();
((IPersistStream)context).Load(stream);
for (int i = 0; i < context.CountProperties; ++i)
{
string propName;
string propNamespace;
object propValue = context.ReadAt(i, out propName, out propNamespace);
System.Console.Out.WriteLine(propNamespace + “, ” + propName + “: ” + propValue.ToString());
}
Comment by Alister — October 28, 2008 @ 2:38 am
Nice Alister, great work on the Reflector, thanks for sharing!
Comment by Thiago Almeida — October 28, 2008 @ 4:27 pm
Thank you Alister, great example. Works like a charm.
However, the loadtrackedmessagecontext procedure requires the spool as input, is there
a good way to retrieve this?
Comment by Daniel Jansson — December 10, 2008 @ 3:08 am
There’s probably a better way of finding out which tracking spool table holds your message. But one way is to call loadtrackedmessagecontext twice, first passing 1 as the spool id and then 2 as the spool id. Of course you only need to call the second one if the first one didn’t find it.
Comment by Thiago Almeida — December 10, 2008 @ 8:05 am
[...] qui [...]
Pingback by 3 ways of programmatically extracting a message body from the BizTalk tracking database - Claudio Santorum Blog — May 15, 2009 @ 1:16 am