One of the interesting things being an ISV (Independent software vendor) building
BizTalk360 focused on BizTalk server is, the API’s to access BizTalk Server resources are always limited and you need to go under the cover. In the majority of the cases we will find answer either via ExplorerOM, WMI, direct BizTalk database queries and in some instance we need to dive into undocumented system dll’s, this is one of those cases. For a long time we didn’t have the “Resources” section in BizTalk Application in BizTalk360 and we decided to bring it into the application.
Within few minutes of starting the task I realised ExplorerOM and WMI do not have any classes to access the BizTalk application resources and my hope was that data should be present somewhere in the BizTalk Management database (BizTalkMgmtDb). After spending couple of hours, came to the conclusion, the resources information is not fully present in management database either. As next step I started with some reflection and searches and finally managed to find the solution. It’s not as straight forward as calling few methods in the unknown dll’s, it was quite intense to get to that information. This blog article will serve as both as documentation to our team and hopefully will help someone who really wants to access this in the future.
How can I access BizTalk Resources programmatically?
Let’s take a look into the code first and explain it
internal BizTalkApplicationResources GetBizTalkApplicationResources()
{
List<string> systemAssemblies = new List<string>
{
"Microsoft.BizTalk.TestTools",
"Microsoft.XLANGs.BizTalk.Engine",
"Microsoft.XLANGs.BaseTypes",
"Microsoft.XLANGs.Engine",
"Microsoft.XLANGs.RuntimeTypes",
"System.Web.Services",
"System",
"mscorlib",
};
BizTalkApplicationResources applicationResources =
new BizTalkApplicationResources();
Microsoft.BizTalk.ApplicationDeployment.Group lDepGroup =
new Microsoft.BizTalk.ApplicationDeployment.Group
{
DBName = this.setting.mgmtDbName,
DBServer = this.setting.mgmtDbSqlInstanceName
};
Microsoft.BizTalk.ApplicationDeployment.Application app =
lDepGroup.Applications[this.name];
foreach (Microsoft.BizTalk.ApplicationDeployment.Resource res
in app.ResourceCollection)
{
string[] props = res.Luid.Split(',');
string displayName = props[0];
string luid = res.Luid;
//For resource type file, luid is prefeixed with 151:
//so removing it
displayName = displayName.Replace("151:", "");
luid = luid.Replace("151:", "");
BizTalkApplicationResource resource = new BizTalkApplicationResource
{
name = displayName,
fileType = res.ResourceType,
luid = luid,
properties = new NameValueList()
};
foreach (var prop in res.Properties)
{
resource.properties.Add(
new NameValue {PropKey = prop.Key, PropValue = Convert.ToString(prop.Value)}
);
if (prop.Key == "SourceLocation")
resource.sourceLocation = Convert.ToString(prop.Value);
if (prop.Key == "DestinationLocation")
resource.destinationLocation = Convert.ToString(prop.Value);
if (prop.Key == "TargetEnvironment")
resource.destinationLocation =
"ENV:" + Convert.ToString(prop.Value);
}
//Add Dependencies (if any)
if (res.ResourceType.Equals("System.BizTalk:BizTalkAssembly") ||
res.ResourceType.Equals("System.BizTalk:Assembly"))
{
string path = Microsoft.BizTalk.Gac.Gac.GetAssemblyPath(displayName);
if (string.IsNullOrEmpty(path))
resource.warning
= "Assembly not found in GAC. Dependencies cannot be verified";
else
{
Microsoft.BizTalk.Deployment.Assembly.BtsAssemblyManager manager
= new Microsoft.BizTalk.Deployment.Assembly.BtsAssemblyManager(path, null);
Array references = manager.AssemblyBase.References as Array;
if (references != null)
{
resource.dependencies = new StringList();
foreach (string refName in references)
{
//Avoid adding dependency references to .NET/BizTalk Assemblies
string[] p = refName.Split(',');
string n = p[0];
if (!systemAssemblies.Contains(n,
StringComparer.OrdinalIgnoreCase))
resource.dependencies.Add(refName);
}
}
}
}
//Add resource to collection (there is resource
//with "Application/Sample.BTS" in the collection, ignoring it)
if (res.Luid.ToLower() !=
string.Format("Application/{0}", app.Name).ToLower())
applicationResources.Add(resource);
}
return applicationResources;
}
Above is the single function taken directly from our code base, just you need to ignore few things BizTalkApplicationResource, BizTalkApplicationResources which are our internal model classes, the management database setting and application name are coming from our class directly. The rest of the code is pretty generic.
First in order for you to execute this code, you need to reference the following 3 system dll’s
- Microsoft.BizTalk.ApplicationDeployment.Engine
- Microsoft.BizTalk.Admin
- Microsoft.BizTalk.Deployment
Adding reference to the first dll is fairly straight forward, it’s just present in the BizTalk Installation folder, but the other two are only available in GAC. In order for you to reference an assembly in GAC, you should follow
one of these techniques to get to the physical dll stored in GAC. On the development machine, I just followed option #1.
It’s fairly easy to understand the code, all the configuration you make in the BizTalk Admin Console Resources windows are accessible via the “Properties” property of resource as key/value pair. The only hard bit is how to get to the dependencies of our custom BizTalk assemblies and .NET assemblies as shown here
These dependencies are actually determined using Reflection of the actual BizTalk/.NET dll’s. For this we need to reflect on the actual BizTalk /.NET assembly. The first step is identify and loading the BizTalk/.NET dll, and one of the safest assumption to make here is those assemblies are present in GAC (if not we send a warning back). We use one of the helper functions available to get the path of the assembly in GAC
string path = Microsoft.BizTalk.Gac.Gac.GetAssemblyPath(displayName);
once the path is identified we load the assembly using BtsAssemblyManager (found inside Microsoft.BizTalk.Deployment.Assembly class) and detect all the references.
Hacks in the approach
There are few hacks in the above solution to make it UI friendly
1. Removing referenced .NET/BizTalk assemblies
Since we are using reflection to determine the dependencies, it will also have reference to bunch of .NET system assemblies and BizTalk assemblies, which we do not want to show in the UI, so there is small logic to eliminate them.
2. Removing “Application/your application name” resource
When we are iterating through the resources look, there is an additional resource in the collection with the name “Application/your application name” (ex: Application/Sample.BTS), which we do not want to show in the UI, hence there is a small logic to remove it.
3. Removing “151:” prefix of certain resource types
For some of the resource types are like files, pre, post scripts the name of the resource is appended with “151:”, no idea why it’s there or of course there is no documentation for this. We quietly remove it in our code to make it UI friendly.
Important Settings
Set platform build target to x86
We used .NET 4.0 for building BizTalk360, If you leave your assembly executing the above code in the default setting of “Any CPU”, and when you try to run the method, you’ll receive the following error.
Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG))
The solution to the problem is by setting platform build target to x86, this is due to some legacy reference underneath. This may be the reason why ExplorerOM is still 32 bit.
Set startup mode to useLegacyV2RuntimeActivationPolicy=”true”
Another important thing you need to make sure is to set the startup mode like this
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
else when you try to access the resource collection, you’ll receive the following error
Type ‘Microsoft.BizTalk.ApplicationDeployment.ResourceCollection’ in Assembly ‘Microsoft.BizTalk.ApplicationDeployment.Engine, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35’ is not marked as serializable.
This is because of the support for side-by-side runtimes, .NET 4.0 has changed the way that it binds to older mixed-mode assemblies. These assemblies are, for example, those that are compiled from C++\CLI. Mixed mode assembly is built against version ‘v1.1.4322’ of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.
IIS Application Pool Enable 32-bit Application
If you are running the code as web application (like BizTalk360), then you need to set the IIS application pool “Enable 32-bit Application” property to true.