February 22, 2012 at 11:22 AM

Microsoft BizTalk Server 2010 ships with some assemblies that assist you with the administration and deployment of your BizTalk environment.  During the development of an automated deployment plan, we were able to perform most of the deployment tasks at a BizTalk application level, by using these assemblies:

 

Microsoft.BizTalk.ExplorerOM

 

-       Create/delete applications

-       Control receive locations

-       Control orchestrations

-       Control send ports

-       Control send port groups

-       Control application references

 

Microsoft.BizTalk.ApplicationDeployment

 

-       Add/remove resources

 

Microsoft.BizTalk.Deployment

 

-       Import/export bindings

 

Microsoft.BizTalk.Operations

 

-       Query for active service instances         

 

Remark that all of the used objects can be initialized by three parameters:

      -    SqlServerName

-    BizTalkMgmtDbName

-    ApplicationName

 

 

The object "BizTalk Group" is defined by the SqlServerName and the BizTalkMgmtDbName.  A "BizTalk Application" is defined by its BizTalk Group and the ApplicationName.  However, these assemblies have totally no notion of the object "BizTalk Server".  They only support operations at a database level (BizTalk Group), but some deployment tasks need to be executed on a BizTalk Server level:

 

- Install/uninstall assemblies to the Global Assembly Cache

- Control host instances

 

For both actions we needed to be able to dynamically determine which BizTalk Servers are part of the already defined BizTalk Group. After some research, we found two options to implement this logic:

 

Windows Management Instrumentation

 

WMI has some support for BizTalk via the WMI namespace “root\\MicrosoftBizTalkServer”.  We are able to loop though all configured host instances, by executing a query on the MSBTS_HostInstance class.  As each host instance runs on a BizTalk Server, it’s easy to implement some logic that retrieves all BizTalk Servers.

 

private List<string> GetBizTalkServers()
{
     List<string> btsServers = new List<string>();

     EnumerationOptions wmiEnumerationOptions = new EnumerationOptions { ReturnImmediately = false };
     ObjectQuery wmiQuery = new ObjectQuery("SELECT * FROM MSBTS_HostInstance");
     using (ManagementObjectSearcher wmiSearcher = new ManagementObjectSearcher("root\\MicrosoftBizTalkServer", wmiQuery.QueryString, wmiEnumerationOptions))
     {
          ManagementObjectCollection hostInstanceCollection = wmiSearcher.Get();
          foreach (ManagementObject hostInstance in hostInstanceCollection.Cast<ManagementObject>())
          {
                string btsServer = hostInstance["Name"].ToString().Split(' ').Last();
                if (btsServers.Contains(btsServer) == false)
                {
                     btsServers.Add(btsServer);
                }
          }
      } 

      return btsServers;
}

Custom SQL Server Query

 

Another option is to retrieve the information directly from the BizTalkMgmtDb.  The table adm_Server actually contains a list of all BizTalk Servers that are part of the BizTalk Group.  So this simple .NET code is able to return us the needed information:

 

 

private List<string> GetBizTalkServers()
{
     List<string> btsServers = new List<string>();

     SqlConnection sqlConnection = new SqlConnection(String.Format(CultureInfo.CurrentCulture, "Server={0};Database={1};Integrated Security=SSPI;", SqlServerName, MgmtDatabase));
     SqlCommand sqlCommand = new SqlCommand("SELECT Name FROM [dbo].[adm_Server]", sqlConnection);

     sqlConnection.Open();

     SqlDataReader sqlDataReader = sqlCommand.ExecuteReader();

     while (sqlDataReader.Read())
     {
          btsServers.Add(sqlDataReader["Name"].ToString());
     }

     sqlConnection.Close();

     return btsServers;
}

 

 

Conclusion

 

It's pretty easy to retrieve a list of all BizTalk Servers that are part of a BizTalk Group.  A limitation of the WMI approach is that this will only work when it's executed on one of the BizTalk Servers.  So I prefer the SQL approach, because this will also work for remote execution.

 

Toon Vanhoutte

Posted in: BizTalk | Cluster | Deployment | General | infrastructure

Tags:


February 16, 2012 at 7:00 PM

Recently, I had a project where full side-by-side deployment was required.  In a first test phase of the automated deployment, we used MSBuild together with the Codeplex MSBuild Extension Pack. We tried deploying assemblies side-by-side, but BizTalk threw errors when trying to deploy a new version of our Blog.Codit.BizTalk.Pipelines assembly:

 

Failed to add resource(s). Change requests failed for some resources. BizTalkAssemblyResourceManager failed to complete end type change request. SQL exception: "Violation of UNIQUE KEY constraint 'UQ__bts_pipe__DD425CAE7908F585'. Cannot insert duplicate key in object 'dbo.bts_pipeline'. The duplicate key value is (D6C53CD9-8466-4520-A85B-7BD887A57455)." 

 

I received this error when trying to deploy from Visual Studio, from MSBuild (using BTSTask.exe) and also when trying to deploy it manually via the Admin Console (Add Resource).  So the error is actually independent from the deployment source.  As BizTalk returned a SQL exception, I decided to run SQL Profiler and detected this call:

 

exec btm_CreatePipeline 
   @PipelineID='D6C53CD9-8466-4520-A85B-7BD887A57455',
   @Category=1,
   @Name=N'Blog.Codit.BizTalk.Pipelines.rcvSalesOrder',
   @FullyQualifiedName=N'Blog.Codit.BizTalk.Pipelines.rcvSalesOrder, Blog.Codit.BizTalk.Pipelines, Version=1.2.0.0, Culture=neutral, PublicKeyToken=xxx',
   @IsStreaming=0,
   @AssemblyID=265,
   @Release=2,
   @ID=@p8output

 

So the deployment logic tries to create a new entry in the bts_pipeline table, but it uses exactly the same (unique) ID of the pipeline of the older version.  This entry was already present in the bts_pipeline table: 

D6C53CD9-8466-4520-A85B-7BD887A57455 Blog.Codit.BizTalk.Pipelines.rcvSalesOrder, Blog.Codit.BizTalk.Pipelines, Version=1.1.0.0.  

 

We opened a case at Microsoft Support for this issue.  They forwarded the issue to the BizTalk Product Team and we received this answer:

 

The pipeline GUID is stored in the rcvSalesOrder.btp.cs  file. The GUID gets regenerated when either of the following occurs:
 
- The pipeline is saved from within Visual Studio.
- When REBUILD the BizTalk project.

 

As we executed a BUILD, instead of a REBUILD, a small change to our MSBuild script solved the issue:

<MSBuild Projects="@(ProjectsToBuild)" Targets="Rebuild" />

 

This behavior is rather inconsistent, because it only occurs when your assembly contains a pipeline object. 

 

Big thanks to Microsoft for the fast and accurate support.

Toon Vanhoutte


January 23, 2012 at 4:42 PM

Recently a customer of ours asked us if it was possible to include versioning information into the title of the installed MSI.

These MSI files can be generated by right-clicking a BizTalk application and selecting "Export" and then "MSI file":

This generates the MSI file on disk.

Problem however is that the MSI file which gets generated always has version 1.0.0.0 assigned to it.
We can always use Microsoft's Orca tool to manually update the properties of the file, but this is very cumbersome to say the least.

I went on to search if there was an option to pass some parameters to BTSTask.exe.
This tool allows you to perform the "Export MSI" via the command line, allowing you to script it.
Unfortunately this tool does not allow you to set the version information at all. 

Luckily, BizTalk comes with a .NET API that allows you to update the properties of the MSI installer package.
For BizTalk 2010 this is the Microsoft.BizTalk.MSIManager assembly.

using Microsoft.BizTalk.ApplicationDeployment.MSIManager.WindowsInstaller;

This assembly allows you to query/update the MSI database structure contained within the MSI file.

Just provide the following code to update the name of the application for example:
msiPath = full path to your MSI
applicationName = your custom application name that will appear in your "Programs and Features" list under Windows.

static public void UpdateMSIApplicationName(string msiPath, string applicationName)
        {
            Database db = Installer.OpenDatabase(msiPath, DatabaseOpenMode.Transact);
            string query = @"UPDATE Property SET Property.Value = '" + applicationName + "' WHERE Property.Property = 'ProductName'";
            Microsoft.BizTalk.ApplicationDeployment.MSIManager.WindowsInstaller.View vw = db.OpenView(query);

            vw.Execute();
            vw.Close();

            db.Commit();
            db.Close();
            db.Dispose();
        }

You can also update your Manufacturer information to customize the directory where the MSI gets installed. By default this is "Generated by Biztalk".
Use this query to perform the update then:

string query = @"UPDATE Property SET Property.Value = '" + manufacturer + "' WHERE Property.Property = 'Manufacturer'";

I also tried updating the version information, but for some reason this will insert some minor errors during installation having to do with the registry.
I believe this has to do with the registry keys that are installed by default within the BizTalk MSI version. 

Incorporating all of this into a properly automated build/deployment scenario will allow you to publish your MSI files with customized versioning.

Good luck.

 

Posted in: BizTalk | Deployment

Tags: