Empowering BizTalk in HotRod Magazine

24 December 2009

Yesterday the new issue of BizTalk HotRod Magazine was published. Besides other nice content it contains an article on our PowerShell Provider for BizTalk.


Copying your host configuration

22 December 2009

Moving your host configuration from one server to another can be daunting and time consuming task. This usually needs to be done when you hand over stuff from development to test, or from test to production. On those environments you mostly want to have the exact same host configuration as you have on your development box.

Another option is that a new development team member has just started on a fresh development box and you want him to use the same host configuration that you have. There are a lot of other scenarios where you want to copy the configuration of your hosts and host instances to another machine.

A lot of great BizTalk people have already showed how this can be done in an automated way. Some samples are:

- VBScript and WMI
- Custom tool 1
- Custom tool 2

In this post I want to show you how this can be done using PowerShell and the PowerShell provider for BizTalk. So this is actually the next post in my series on more advanced things you can do with the provider.

I use a slightly different approach compared to the existing tools. I use a PowerShell script that can be executed on the source server. This is the server that already has the host configuration set up, for example a development server. The output of running this script is actually another PowerShell script that you can run on the destination server. This output scripts contains all necessary statements to create the hosts, corresponding host instances and settings.

This is how it works:

- execute the script
- the script prompts for the name and path of the output file:

image

- after a short while the script is finished and a PowerShell script is created. You can run this script on the destination server or send it to an administrator who will run it on another server.

This is the created script:


#Determine BizTalk root and switch to host container.
$BizTalkRoot = (get-psdrive -PsProvider BizTalk).Root
Join-Path $BizTalkRoot 'Platform Settings\Hosts' | Set-Location

$RunningServer = $Env:ComputerName
#Create hosts
#Create host 'ReceiveHost' and set properties.
New-Item -Path 'ReceiveHost' -HostType 'InProcess' -NtGroupName 'BizTalk Application Users' -AuthTrusted:$False
Set-ItemProperty -Path 'ReceiveHost' -Name HostTracking -Value False
Set-ItemProperty -Path 'ReceiveHost' -Name Is32BitOnly -Value True

#Create host 'ProcessingHost' and set properties.
New-Item -Path 'ProcessingHost' -HostType 'InProcess' -NtGroupName 'BizTalk Application Users' -AuthTrusted:$False
Set-ItemProperty -Path 'ProcessingHost' -Name HostTracking -Value False
Set-ItemProperty -Path 'ProcessingHost' -Name Is32BitOnly -Value True

#Create host 'SendHost' and set properties.
New-Item -Path 'SendHost' -HostType 'InProcess' -NtGroupName 'BizTalk Application Users' -AuthTrusted:$False
Set-ItemProperty -Path 'SendHost' -Name HostTracking -Value False
Set-ItemProperty -Path 'SendHost' -Name Is32BitOnly -Value False

#Create host 'TrackingHost' and set properties.
New-Item -Path 'TrackingHost' -HostType 'InProcess' -NtGroupName 'BizTalk Application Users' -AuthTrusted:$False
Set-ItemProperty -Path 'TrackingHost' -Name HostTracking -Value True
Set-ItemProperty -Path 'TrackingHost' -Name Is32BitOnly -Value False

#Switch to host instances container.
Join-Path $BizTalkRoot 'Platform Settings\Host Instances' | Set-Location

#Create host instances
#Create host instance 'Microsoft BizTalk Server ReceiveHost BTS2K9-DEV' and set properties.
$Credential = $Host.UI.PromptForCredential('Host Instance user credentials', 'Please enter credentials for host instance ''ReceiveHost''', '', '')
New-Item -Path 'hostinstance' -HostName 'ReceiveHost' -RunningServer $RunningServer -Credentials $Credential
Set-ItemProperty -Path 'Microsoft BizTalk Server ReceiveHost BTS2K9-DEV' -Name IsDisabled -Value False

#Create host instance 'Microsoft BizTalk Server ProcessingHost BTS2K9-DEV' and set properties.
$Credential = $Host.UI.PromptForCredential('Host Instance user credentials', 'Please enter credentials for host instance ''ProcessingHost''', '', '')
New-Item -Path 'hostinstance' -HostName 'ProcessingHost' -RunningServer $RunningServer -Credentials $Credential
Set-ItemProperty -Path 'Microsoft BizTalk Server ProcessingHost BTS2K9-DEV' -Name IsDisabled -Value False

#Create host instance 'Microsoft BizTalk Server SendHost BTS2K9-DEV' and set properties.
$Credential = $Host.UI.PromptForCredential('Host Instance user credentials', 'Please enter credentials for host instance ''SendHost''', '', '')
New-Item -Path 'hostinstance' -HostName 'SendHost' -RunningServer $RunningServer -Credentials $Credential
Set-ItemProperty -Path 'Microsoft BizTalk Server SendHost BTS2K9-DEV' -Name IsDisabled -Value False

#Create host instance 'Microsoft BizTalk Server TrackingHost BTS2K9-DEV' and set properties.
$Credential = $Host.UI.PromptForCredential('Host Instance user credentials', 'Please enter credentials for host instance ''TrackingHost''', '', '')
New-Item -Path 'hostinstance' -HostName 'TrackingHost' -RunningServer $RunningServer -Credentials $Credential
Set-ItemProperty -Path 'Microsoft BizTalk Server TrackingHost BTS2K9-DEV' -Name IsDisabled -Value False

There are a couple of things to mention about the generated script:

- This is by no means a script applicable for all situations and environments. The purpose is just to show the output script as a result of running the source script. The source script will probably need some modification to make it apply to your environment. For example the script does not take into account multi server configurations, etc.
- The output script will prompt for credentials for each host instance it will create. There are also ways to script this from source to destination. See for example here. In most cases hosts in different environments will run under different accounts which is why I choose to show a prompt each time.
- Also in this script the windows group for the host is kept the same for the destination environment. This might not be the case in your particular situation.

After running this script on the destination server I have a exact copy of my host environment on the source server.

The source script looks like this:

$ScriptFile = Read-Host 'Enter full path of script file:'</pre>
'#Determine BizTalk root and switch to host container.' | Out-File $ScriptFile
'$BizTalkRoot = (get-psdrive -PsProvider BizTalk).Root' | Out-File $ScriptFile -Append
'Join-Path $BizTalkRoot ''Platform Settings\Hosts'' | Set-Location' | Out-File $ScriptFile -Append
'' | Out-File $ScriptFile -Append

'$RunningServer = $Env:ComputerName' | Out-File $ScriptFile -Append

$BizTalkRoot = (get-psdrive -PsProvider BizTalk).Root
Join-Path $BizTalkRoot 'Platform Settings\Hosts' | Set-Location

'#Create hosts' | Out-File $ScriptFile -Append
$HostArray = @()
Get-ChildItem -Path . |  Foreach-Object {
    if (!$_.IsDefault -and $_.HostType -ne 'Isolated') {
   
        $HostArray += $_.Name
   
        "#Create host '$($_.Name)' and set properties." | Out-File $ScriptFile -Append
        "New-Item -Path '$($_.Name)' -HostType '$($_.HostType)' -NtGroupName '$($_.NtGroupName)' -AuthTrusted:`$$($_.AuthTrusted)" | Out-File $ScriptFile -Append
        "Set-ItemProperty -Path '$($_.Name)' -Name HostTracking -Value $($_.HostTracking)" | Out-File $ScriptFile -Append
        "Set-ItemProperty -Path '$($_.Name)' -Name Is32BitOnly -Value $($_.Is32BitOnly)" | Out-File $ScriptFile -Append
        '' | Out-File $ScriptFile -Append
                   
    }
}

'#Switch to host instances container.' | Out-File $ScriptFile -Append
'Join-Path $BizTalkRoot ''Platform Settings\Host Instances'' | Set-Location' | Out-File $ScriptFile -Append
'' | Out-File $ScriptFile -Append

Join-Path $BizTalkRoot 'Platform Settings\Host Instances' | Set-Location

'#Create host instances' | Out-File $ScriptFile -Append
Get-ChildItem -Path . |  Foreach-Object {
    if ($HostArray -contains $_.HostName) {
      
        "#Create host instance '$($_.Name)' and set properties." | Out-File $ScriptFile -Append
        "`$Credential = `$Host.UI.PromptForCredential('Host Instance user credentials', 'Please enter credentials for host instance ''$($_.HostName)''', '', '')" | Out-File $ScriptFile -Append
        "New-Item -Path 'hostinstance' -HostName '$($_.HostName)' -RunningServer `$RunningServer -Credentials `$Credential" | Out-File $ScriptFile -Append
        "Set-ItemProperty -Path '$($_.Name)' -Name IsDisabled -Value $($_.IsDisabled)" | Out-File $ScriptFile -Append
         '' | Out-File $ScriptFile -Append
       
    }
}

In the script I skip the default and isolated host. As with the generated output script this is just a sample and might not apply to your specific situation. The source script should be modified to confirm to your requirements and environment.

You can download the source script from here.


Untyped messages and Business Rules Engine

22 December 2009

This blogpost has been superseded by a new version. See: http://biztalkmessages.vansplunteren.net/2010/03/04/untyped-messages-and-business-rules-engine-part-2/

The other day one of my BizTalk buddies asked me if I knew a way to process different messages by the same policy in the Business Rules Engine (BRE). In other words is it possible to create some sort of generic policy that can process different types of messages?

In his scenario the schemas had some fields in common and he needed those fields to be evaluated and optionally set in a single BRE policy.

Of course you can make a policy that depends on different schemas and add rules for each schema. The bad thing about that is that you will get multiple copies of almost the same rule which might lead to a maintenance nightmare.

I created a small sample to show how this can be achieved in a single policy and single rule. Below are three very basic schemas I used in this sample:


<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:b="<a href="http://schemas.microsoft.com/BizTalk/2003">http://schemas.microsoft.com/BizTalk/2003</a>" xmlns="<a href="http://UntypedBRE.FirstSchema">http://UntypedBRE.FirstSchema</a>" targetNamespace="<a href="http://UntypedBRE.FirstSchema">http://UntypedBRE.FirstSchema</a>" xmlns:xs="<a href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</a>">
  <xs:element name="FirstSchema">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string" />
        <xs:element name="IsJohn" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

 

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:b="<a href="http://schemas.microsoft.com/BizTalk/2003">http://schemas.microsoft.com/BizTalk/2003</a>" xmlns="<a href="http://UntypedBRE.SecondSchema">http://UntypedBRE.SecondSchema</a>" targetNamespace="<a href="http://UntypedBRE.SecondSchema">http://UntypedBRE.SecondSchema</a>" xmlns:xs="<a href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</a>">
  <xs:element name="SecondSchema">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string" />
        <xs:element name="IsJohn" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

 

<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns:b="<a href="http://schemas.microsoft.com/BizTalk/2003">http://schemas.microsoft.com/BizTalk/2003</a>" xmlns="<a href="http://UntypedBRE.ThirdSchema">http://UntypedBRE.ThirdSchema</a>" targetNamespace="<a href="http://UntypedBRE.ThirdSchema">http://UntypedBRE.ThirdSchema</a>" xmlns:xs="<a href="http://www.w3.org/2001/XMLSchema">http://www.w3.org/2001/XMLSchema</a>">
  <xs:element name="ThirdSchema">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="FirstName" type="xs:string" />
        <xs:element name="LastName" type="xs:string" />
        <xs:element name="IsJohn" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

I my sample policy I want to check the ‘FirstName’ element. If the value is equal to ‘John’ I want to fill the ‘IsJohn’ element with value ‘yes’. The policy (and single rule) should work for all of the above schemas.

The problem is that mentioned schemas belong to a different namespace and have a different root node, hence in BizTalk terms have a different message type. Because XML schema facts in the BRE are by default tightly coupled to a specific schema the consequence is that those facts can only operate on single type of message.

In order to make this generic you have to do the following:

1. Add one of the schemas to the Facts Explorer in the BRE.

image

2. Make the schema ‘general’

As you can see the Document Type resembles the type of the schema I added. To make this generic I change this value to ‘System.Xml.XmlDocument’. This will make sure that if I use a fact from this schema in a rule it will not be typed to this schema but will be generic.

image

3. Create the rule

In this step I create rule. The facts will be filled in later.

image

4. Modify the XML facts.

In the rule I need to evaluate the ‘FirstName’ fact and optionally set the ‘IsJohn’ fact. Because I want this to work on all schemas I need to define the facts in a generic way. If I click on the ‘FirstName’ fact I can see the xpath statements that point to this fact in the property pane:

image

The ‘Xpath Field’ and ‘Xpath Selector’ properties are directly referring to ‘FirstSchema’ root node and namespace. I change the values to make them generic also:

image

Note that I’m using ‘self::node()’ here. I described this trick before here.

Now the XML fact is no longer pointing to a specific namespace or root node. It just points to a ‘FirstName’ node somewhere in a Xml document.

There are of course other possible values for ‘XPath Selector’ and ‘XPath Field’ to solve this. It all depends on the schemas. If for example the facts you need all have the same parent node you can make the ‘XPath selector’ select the parent node and the ‘XPath Field’ select the ‘FirstName’ element.

I do the same for the fact I want to update in the action of the rule:

image

5. Complete the rule by adding the facts.

Finally I can drag the XML facts from the Facts Explorer to my rule to complete the condition and create a new action. Like this:

image

You can see that the both the condition and the action are not referring (anymore) to any specific schema  but instead to any schema that has ‘FirstName’  and ‘IsJohn’  elements.

Testing the rule with instances from two different schemas shows that this works:

image

image

One thing to note about this is that the way I changed the xpath statements for the Xml facts comes with a performance penalty. Using things like ‘//*…….’.  will make the engine go through the whole xml tree which is less efficient then using the original full xpath statement. So if performance is a strict requirement be careful using techniques like these.

Another thing is that I do not check for the existence of the nodes first. The policy will crash when an message is processed that does not contain on of the nodes used in the rule.