all Technical posts

Build and Deploy with the MSBuild Extension Pack

In this post I try to explain how to automate your deployment using the MSBuild Extension Pack. First I work through a general scenario, deploying your assemblies on your Development machine, extracting an MSI and deploying that to Test. At the end I included some extra scenarios.

Problem

Everybody probably acknowledges that building and deploying a BizTalk solution without any help can be a serious pain. It’s even more of a hassle when your development BizTalk Server and Visual Studio are on separate machines.

An additional problem that we encountered at the customer was that the test-environment consists of 2 nodes, so our project had to be deployed on both, since no access to our personal development machines is wanted or allowed.

Because we didn’t feel like building the project, adding the built files as resources and extracting an MSI-file every time we needed to deploy on test, we searched for an easy solution to this problem. We had a few options: Use custom build scripts, use the built-in capabilities of Visual Studio or use the BizTalk Deployment Framework. The decision was made to go for custom build scripts, since they were the most versatile, didn’t require a lot of additional setup, and were very simple, so usable by everyone.

In the following paragraphs, I will try and teach you how to use simple MSBuild scripts to build and deploy your solutions, allowing you to simplify your deployment lifecycle.

These scripts will allow you to do the following:

  • Get the latest sources
  • Build the project
  • Prepare our bindings with a custom tool for development / test / acceptance / production
  • Add the resources to the development machine
  • Import bindings
  • Restart development host instances
  • Extract an MSI
  • Import MSI in the Test environment
  • Install MSI on the nodes in the environment
  • Import test bindings
  • Restart test host instances
  • Prepare packages for validation and production releases
  • Create / update databases
  • Many, many more…

Just a little teaser, doing the above (except for the prepping and database takes only 10 seconds for a small project):

I’m going to start with a general deployment, and look into a few sidetracks afterwards.

Solution

Prerequisites

Script skeleton

<Project xmlns=”http://schemas.microsoft.com/developer/msbuild/2003″>
<Import Project=”$(MSBuildExtensionsPath)ExtensionPack4.0MSBuild.ExtensionPack.tasks” />
<PropertyGroup>
<Application>Codit.LFT</Application>
<Version>1.0.0.0</Version>
<ReleasePath>Z:_Releases$(Application)$(Version)</ReleasePath>
<DevMachine>$(COMPUTERNAME)</DevMachine>
<TestMachine>$(COMPUTERNAME)</TestMachine>
<TestDatabaseServer>$(COMPUTERNAME)</TestDatabaseServer>
</PropertyGroup>
<Target Name=”Default”>
<CallTarget Targets=”Development;Test ” />
</Target>
<Target Name=”Development”>
<Message Text=”1. Cleanup” Importance=”High” />
<Message Text=”2. Building source” Importance=”High” />
<Message Text=”3. Deploying Application” Importance=”High” />
<Message Text=”3.1. Stopping host instances” Importance=”High” />
<Message Text=”3.2. Creating application” Importance=”High” />
<Message Text=”3.3. Adding resources” Importance=”High” />
<Message Text=”3.4. Importing bindings” Importance=”High” />
<Message Text=”3.5. Starting host instances” Importance=”High” />
<Message Text=”3.6. Starting application” Importance=”High” />
<Message Text=”4. Create Release Package” Importance=”High” />
<Message Text=”4.1. Exporting MSI” Importance=”High” />
<Message Text=”4.2. Copying bindings” Importance=”High” />
</Target>
<Target Name=”Test”>
<Message Text=”1. Cleanup” Importance=”High” />
<Message Text=”2. Create Release package” Importance=”High” />
<Message Text=”3. Install on test” Importance=”High” />
<Message Text=”3.1. Create application” Importance=”High” />
<Message Text=”3.2. Importing MSI” Importance=”High” />
<Message Text=”3.3. Installing MSI” Importance=”High” />
<Message Text=”3.4. Importing bindings” Importance=”High” />
<Message Text=”3.5. Restarting Host Instances” Importance=”High” />
<Message Text=”3.6. Starting application” Importance=”High” />
</Target>
</Project>

General properties

Property Name Description
Application The name of the application (in my case also the name of the solution file)
Version Version number used in ReleasePath, to keep an archive of different MSI’s and releases
DevMachine Computer name of your BizTalk development machine (If it’s the same one you’re running the script on, you can just use $(COMPUTERNAME))
TestMachine Similar to DevMachine property, but for the TEST environment
TestDatabaseServer Name of the database server for your Test environment. (If it’s the same as the TestMachine, you can just use $(TestMachine))

Development Section

Cleanup

This is a pretty straightforward section, only contains 2 commands to clean 2 folders
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction=”RemoveContent” Path=”$(APPDATA)MicrosoftBizTalk ServerDeployment” />
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction=”RemoveContent” Path=”$(ReleasePath)” Condition=”Exists($(ReleasePath))” />

Since I was having issues with binding files being cached, I opt to clean the cache folder before the build. The ReleasePath folder is being cleaned as well, just so you can verify that your installer was created (if you were to automate the deployment). The Condition first checks if the folder exists, since it would be of no use to clean a non-existent folder.

Building source

Building the source isn’t a hard task. Just point to your solution file and MSBuild takes care of the rest. (Don’t forget to keep your built assemblies, since you’ll be needing them later)
<MSBuild Projects=”$(Application).sln”>
<Output TaskParameter=”TargetOutputs” ItemName=”Assemblies”/>
</MSBuild>

Deploy the application

Stopping host instances

For development I always like to stop the host instances before deploying. So first you have to get a list of all host instances:
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Get”>
<Output TaskParameter=”HostInstances” ItemName=”His” />
</MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance>

After which you can stop them:
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Stop” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″ />
Note that I use a Condition here to check the “ServiceState”, so I only stop the “running” host instances.

Creating application

Now that the host instances have been stopped, you can create the application, before we can do so, we have to check if it already exists, and if it does, we just stop it completely:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”CheckExists” Application=”$(Application)” MachineName=”$(DevMachine)”>
<Output TaskParameter=”Exists” PropertyName=”ApplicationExists” />
</MSBuild.ExtensionPack.BizTalk.BizTalkApplication>
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”StopAll” Applications=”$(Application)” MachineName=”$(DevMachine)” Condition=”$(ApplicationExists)” />
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”Create” Applications=”$(Application)” MachineName=”$(DevMachine)” Condition=”!$(ApplicationExists)” />

Adding resources

Next up is adding your assemblies to your newly created application:
<MSBuild.ExtensionPack.BizTalk.BizTalkAssembly TaskAction=”Add” Application=”$(Application)” Assemblies=”@(Assemblies)” MachineName=”$(DevMachine)” GacOnMsiFileImport=”false” Force=”true” GacOnMsiFileInstall=”true” />
By default the Assemblies are set to be added to the GAC on MSI file Import, but since I want them to GAC on install, I swap the parameters. The Force option here is to overwrite existing assemblies.

Importing bindings

Following that, the bindings should be imported, I keep my bindings separated per environment in a subdirectory of the solution, so your BindingFile location may vary, nothing very special about this:
<MSBuild.ExtensionPack.Biztalk.BizTalkApplication TaskAction=”ImportBindings” Application=”$(Application)” MachineName=”$(DevMachine)” BindingFile=”.BindingsDEV$(Application).xml” />

Starting host instances

Once the import has succeeded, restart the Host Instances, note that the same condition applies here, only the Host Instances that were running are started again, this is to avoid starting ones that shouldn’t be:
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Start” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″ />

Start application

Last thing to do is start your application again:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”StartAll” Applications=”$(Application)” MachineName=”$(DevMachine)” />

Create release package

Exporting MSI

Now that the deployment has (hopefully) succeeded, we need to export the MSI package for deployment to other environments.

The export needs the fully qualified name of the assemblies if you want to exclude the binding and party information from the MSI, and we only have the path, so we first need to get extended information about every assembly:
<MSBuild.ExtensionPack.Framework.Assembly TaskAction=”GetInfo” NetAssembly=”%(Assemblies.Identity)”>
<Output TaskParameter=”OutputItems” ItemName=”AssembliesToExport” />
</MSBuild.ExtensionPack.Framework.Assembly>

Now that you have it, you can pass this information on to the ExportToMsi, which will export the MSI in the root directory of your ReleasePath (because 1 MSI per version should be installed on all your environments):
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”ExportToMsi” Application=”$(Application)” Resources=”@(AssembliesToExport->’%(FullName)’)” MsiPath=”$(ReleasePath)$(Application).msi” MachineName=”$(DevMachine)” />

Copying bindings

For the sake of completeness, I also copy the binding file to the ReleasePath directory (the “/z” option is to allow restartable transfers to network drives if your connection would fail):
<MSBuild.ExtensionPack.FileSystem.RoboCopy Source=”.BindingsDEV” Destination=”$(ReleasePath)DEV” Files=”*.xml” Options=”/z” />

Test section

Since I don’t have a test machine for these purposes, I assume my development machine is also my test machine.

Cleanup

This is almost the same as the cleanup in the Development Section (2.3.1 Cleanup), with the difference that instead of clearing the whole ReleasePath directory, I only clear the TEST subdirectory:
<MSBuild.ExtensionPack.FileSystem.Folder TaskAction=”RemoveContent” Path=”$(APPDATA)MicrosoftBizTalk ServerDeployment” />
<MSBuild.ExtensionPack.FileSystem.Folder TaskACtion=”RemoveContent” Path=”$(ReleasePath)TEST” Condition=”Exists(‘$(ReleasePath)TEST’)” />

Create release package

Since my release package only needs the binding file, all I need to do is copy my binding file from my solution directory to my release path directory:
<MSBuild.ExtensionPack.FileSystem.RoboCopy Source=”.BindingsTEST” Destination=”$(ReleasePath)TEST” Files=”*.xml” Options=”/z” />

Install on test

Create application

This is identical to the part in the development section (2.3.3.2 Creating application), except that it’s on another server this time:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”CheckExists” Application=”$(Application)” MachineName=”$(TestMachine)” DatabaseServer=”$(TestDatabaseServer)”>
<Output TaskParameter=”Exists” PropertyName=”ApplicationExists” />
</MSBuild.ExtensionPack.BizTalk.BizTalkApplication>
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”StopAll” Applications=”$(Application)” MachineName=”$(TestMachine)” Condition=”$(ApplicationExists)” DatabaseServer=”$(TestDatabaseServer)” />
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”Create” Applications=”$(Application)” MachineName=”$(TestMachine)” Condition=”!$(ApplicationExists)” DatabaseServer=”$(TestDatabaseServer)” />

Importing MSI

Once your release package has been prepared, you can import the MSI file into BizTalk, yet again the Overwrite switch enables you to overwrite existing assemblies, or else the import fails:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”ImportFromMsi” Application=”$(Application)” MachineName=”$(TestMachine)” MsiPath=”$(ReleasePath)$(Application).msi” Overwrite=”True”DatabaseServer=”$(TestDatabaseServer)” />

Installing MSI

Using psexec, we can remotely install the MSI file on the server:
<Exec Command=”psexec \$(TestMachine) msiexec /i $(ReleasePath)$(Application).msi /quiet” />

Importing bindings

Since no binding files are included in the MSI, we have to import our own from the ReleasePath directory, similar to what we did before (2.3.3.4 Importing bindings):
<MSBuild.ExtensionPack.Biztalk.BizTalkApplication TaskAction=”ImportBindings” Application=”$(Application)” MachineName=”$(TestMachine)” BindingFile=”$(ReleasePath)TEST$(Application).xml”DatabaseServer=”$(TestDatabaseServer)” />

Restarting host instances

This part is similar to the stopping (2.3.3.1 Stopping host instances) and starting (2.3.3.5 Starting host instances) of the host instances in the development part. This just does it after import of the MSI and bindings, so it causes the least amount of downtime possible:
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Get”>
<Output TaskParameter=”HostInstances” ItemName=”His” />
</MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance>
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Stop” MachineName=”$(TestMachine)” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″/>
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Start” MachineName=”$(TestMachine)” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″ />

Starting the application

After the deploy has succeeded, start the application:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”StartAll” Applications=”$(Application)” MachineName=”$(TestMachine)” DatabaseServer=”$(TestDatabaseServer)” />

Special scenarios

Validation and Production

As you might have noticed, there are no sections for Validation and Production in this script. I’ve left them out due to the fact that there is no support to check for running service instances.

What you could do is first disable the receive locations using the following command:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”DisableAllReceiveLocations” Applications=”$(Application)” MachineName=”$(TestMachine)” DatabaseServer=”$(TestDatabaseServer)” />
Afterwards you can force a wait, so you have time to check for running instances (and stop the build if you have to do manual interactions):
<MSBuild.ExtensionPack.UI.Dialog TaskAction=”Show” Text=”Please confirm all running instances have completed. (No will stop build)” Button1Text=”Yes” Button2Text=”No”>
<Output TaskParameter=”ButtonClickedText” PropertyName=”Clicked” />
</MSBuild.ExtensionPack.UI.Dialog>
<Error Text=”User stopped build.” Condition=”$(Clicked) != ‘Yes'” />

The steps to take from now on should be similar to Test (at least in my case). However, this may vary, so I’m not responsible for lost production messages ( I should add a disclaimer, shouldn’t I? 🙂 ).

Multiple nodes in Test

If you have multiple nodes in your Test Environment you have to do a couple of changes, including installing the MSI on all nodes. This can be easily done by just editing the psexec command to run on multiple servers:
<Exec Command=”psexec \$(TestMachine),$(TestMachine2) msiexec /i $(ReleasePath)$(Application).msi /quiet” />
You’ll also have to restart the Host instances on the second node, by repeating the restarting lines:
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Get” MachineName=”$(TestMachine2)”>
<Output TaskParameter=”HostInstances” ItemName=”His” />
</MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance>
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Stop” MachineName=”$(TestMachine2)” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″ />
<MSBuild.ExtensionPack.BizTalk.BizTalkHostInstance TaskAction=”Start” MachineName=”$(TestMachine2)” HostName=”%(His.Identity)” Condition=”%(His.ServiceState) == 4″ />

I want to delete the application for every deploy

If you want to delete the application before you deploy, you can specify the Force parameter in the Create action:
<MSBuild.ExtensionPack.BizTalk.BizTalkApplication TaskAction=”Create” Applications=”$(Application)”Force=”True”MachineName=”$(DevMachine)” Condition=”!$(ApplicationExists)” />

Endnotes

I hope this post gave you an idea on how using simple scripts can really simplify your life. If you have any questions, feel free to ask them.

I’ve included a quick sample solution, that shows how it can be done. The solution can be found here: https://github.com/CoditCompany/MSBuildExtensionPack

Subscribe to our RSS feed

Thanks, we've sent the link to your inbox

Invalid email address

Submit

Your download should start shortly!

Stay in Touch - Subscribe to Our Newsletter

Keep up to date with industry trends, events and the latest customer stories

Invalid email address

Submit

Great you’re on the list!