Removing Non-Printable Characters

Hi Folks,

I notice BizTalk does not like certain Non-Printable characters when dealing with XML data. I use a pipeline component to strip them out of the stream.

Here is the sample code that you can use in a PipeLine component:

using System;
using System.Collections.Generic;
using System.Text;

namespace FileManagement.BOL.Helper
{
       public  class NonPrintableCharacters
    {
        public static List<char> charList = new List<char>();

        public NonPrintableCharacters ()
        {
            //Refer to http://nemesis.lonestar.org/reference/telecom/codes/ascii.html
            charList.Add((char)Convert.ToInt16("0x01", 16)); //1 Start Of Heading
            charList.Add((char)Convert.ToInt16("0x02", 16)); //2 Start Of Text
            charList.Add((char)Convert.ToInt16("0x03", 16)); //3 End Of Text
            charList.Add((char)Convert.ToInt16("0x04", 16)); //4 End Of Transmission
            charList.Add((char)Convert.ToInt16("0x05", 16)); //5 Enquiry, Also known as WRU (Who aRe You), HERE IS, and Answerback
            charList.Add((char)Convert.ToInt16("0x06", 16)); //6 Acknowledge
            charList.Add((char)Convert.ToInt16("0x07", 16)); //7 Bell
            charList.Add((char)Convert.ToInt16("0x08", 16)); //8 Backspace
            //Line Feed 0x0A and Horizontal Tab 0x09 is allowed
            charList.Add((char)Convert.ToInt16("0x0B", 16)); //11 Vertical Tabulation
            charList.Add((char)Convert.ToInt16("0x0C", 16)); //12 Form Feed,
            //Carriage Return 0x0D is allowed
            charList.Add((char)Convert.ToInt16("0x0E", 16)); //14 Shift Out
            charList.Add((char)Convert.ToInt16("0x0F", 16)); //15 Shift In
            charList.Add((char)Convert.ToInt16("0x10", 16)); //15 Shift In
            charList.Add((char)Convert.ToInt16("0x11", 16)); //17 Device Control 1,Also known as X-ON
            charList.Add((char)Convert.ToInt16("0x12", 16)); //18 Device Control 2
            charList.Add((char)Convert.ToInt16("0x13", 16)); //19 Device Control 3,Also known as X-OFF
            charList.Add((char)Convert.ToInt16("0x14", 16)); //20 Device Control 4
            charList.Add((char)Convert.ToInt16("0x15", 16)); //21 Negative Acknowledge
            charList.Add((char)Convert.ToInt16("0x16", 16)); //22 Sychronous Idle
            charList.Add((char)Convert.ToInt16("0x17", 16)); //23 End of Transmission Block
            charList.Add((char)Convert.ToInt16("0x18", 16)); //24 Cancel
            charList.Add((char)Convert.ToInt16("0x19", 16)); //25 End of Medium
            charList.Add((char)Convert.ToInt16("0x1A", 16)); //26 Substitute
            charList.Add((char)Convert.ToInt16("0x1B", 16)); //27 Escape
            charList.Add((char)Convert.ToInt16("0x1C", 16)); //28 File Separator
            charList.Add((char)Convert.ToInt16("0x1D", 16)); //29 Group Separator
            charList.Add((char)Convert.ToInt16("0x1E", 16)); //30 Record Separator
            charList.Add((char)Convert.ToInt16("0x1F", 16)); //31 Unit Separator
            charList.Add((char)Convert.ToInt16("0x7F", 16)); //127 Delete, Also known as RUB OUT
        }

        public string ReplaceInvalidChars(string mystring, char newChar)
        {
            foreach (char c in charList)
                mystring = mystring.Replace(c, newChar);
            return mystring;
        }

    }
}

You can call this class from lets say when looping through the stream:

StreamReader reader = new StreamReader(stream, myEncoding)

while ((RecordLine = reader.ReadLine()) != null)
           {
               RecordLine = cleanChars.ReplaceInvalidChars(RecordLine, ‘ ‘); 

}

The above will replace the Non-printable characters with a space.

Reason why I use this, is that the XML libraries in .NET support certain Non-Printable characters than BizTalk cannot tolerate.

Hope this is useful. You can do this is a cleaver way with loops on the decimals if you like, made it like this for simplicity.

ASP.NET MVC – Project Type not supported

Hi Folks,

A quick post, when upgrading from MVC 1 to MVC 2 you may get this error in Visual Studio 2008, or when using MVC 2 on a different dev box.

Project Type Not Supported, when trying to open the project file (*.csproj)

very simple to fix.

Open the project file it notepad (or notepad++), and change the guid

<ProjectTypeGuids>{603c0e0b-db56-11dc-be95-000d561079b0};

to

<ProjectTypeGuids>{F85E285D-A4E0-4152-9332-AB1D724D3325}

Then update ALL web.config files, changing the Version to 2.0.0.0, the public key guid stays the same 🙂

<add assembly="System.Web.Mvc, Version=1.0.0.0

To:

<add assembly="System.Web.Mvc, Version=2.0.0.0

Run all Unit Tests

Hope this helps

BizTalk 2006: The Naughty Aggregator Pattern and Microsoft’s absence from making it behave

Aggregator Pattern Pitfalls

One of the features you would expect BizTalk to accomplish is in built BATCHING, where you need to send messages to an external system in Batch Mode, as usual a batching system depends on two variables:

  • Maximum Batch Size

OR

  • Batching Interval (Time Frame)

Microsoft was kind enough to provide a sample solution for batching. The solution is found here, after BizTalk is installed:

C:\Program Files (x86)\Microsoft BizTalk Server 2006\SDK\Samples\Pipelines\Aggregator

Or

C:\Program Files\Microsoft BizTalk Server 2006\SDK\Samples\Pipelines\Aggregator

Just run the setup.bat file to get it installed on your BizTalk instance. They are even kind enough to provide 4 instance documents to drop in the receive location and then what will happen is the final send location will have them batched into one message, nice hey!

How it works is an envelope is used to batch messages within a pipeline. I highly recommend you play with this SDK, even though it is bugged, you gain good understanding of envelopes in BizTalk.

BUT NOT SO FAST!

What Microsoft fails to tell you is that if you need to batch more than 4 messages you will get ZOMBIES in BizTalk, yes that is right, install the SDK, and create 8 or more instance1.txt files and drop them all at the same time in the receive location, let’s say we drop 80, we should get one message out, instead, you get failed instances in BizTalk, as Zombies are created, how it happens is due to the Orchestration design problem. The orchestration is configured to listen for messages, the problem is when it is finalizing the final batch message it is possible that a new message from the BizTalk message box can sneak into the Orchestration receive handler, but the final message is already created, so this new message that crept into the process becomes a zombie and you get stuck messages in the BizTalk message box!

Many people on the net use a listen shape, and use a timer to manage batching, Microsoft’s SDK approach is a loop and use MAX BATCH SIZE, and either method will cause zombie records. Look at the orchestration below:

 

When the loop ends, and processing continues, it is possible that more messages from the message box arrive! This is what causes stuck messages in the BizTalk Message box. Errors will be like this:

0xC0C01B4C The instance completed without consuming all of its messages. The instance and its unconsumed messages have been suspended.

So as far as I am concerned Microsoft have some serious work to do on BizTalk to address this problem, I feel that Batching is core requirement for any workflow system, and they should address this, if you read their articles they mention the problem, BUT DO NOT PROVIDE A SOLUTION.

There are solutions, we use a MSMQ or SQL table to manage batching, but I feel this is not a nice and elegant approach.

Summary

Microsoft need to address this problem in BizTalk or provide the community with a Batching Adapter, it is about time they get this done, this problem has been around now for far too long!

http://msdn.microsoft.com/en-us/library/bb203853.aspx

http://msdn.microsoft.com/en-us/library/aa561361.aspx

http://msdn.microsoft.com/en-us/library/ms942189.aspx

Thanks Microsoft for providing us with a flawed SDK sample that only works when you batch small message numbers (Less than 8)! Please take this opportunity to please the BizTalk community and provide us with a professional batching adapter.

What interested me the most or amuses me is the various authors on BizTalk books that have discussed this pattern, and not once mentioned the bug, and reviewing their solution has shown to also have the bug, but then again, most of the examples for BizTalk on educational resources are always with small batch sizes like a flight agency/hotel booking company, where you have a convoy of max 2 messages, not one of them have discussed enterprise batching solutions.

Hopefully we see some improvements in this regard soon.

Custom Split Function, allows Text Qualifiers

Hi Folks,

The built in Split function in C# is cool to split up a delimited string, or record when reading text data from files etc, but what happens when you need to split a string with text qualifiers in it like this:

Romiko, Derbynew, 29, "52 SurfSide Street, Durban, South Africa"

You going to run into issues when using the C# built in string split function.

I checked out a number of places on the net and many of the samples are SLOW. So I thought it would be best to create a low level version, that is extremely fast.

Here it is:

public string[] Split

(

string expression,

char delimiter,

char qualifier,

bool ignoreCase

)

{

if (ignoreCase)

{

expression = expression.ToLower();

delimiter = char.ToLower(delimiter);

qualifier = char.ToLower(qualifier);

}

int len = expression.Length;

char symbol;

 

List<string> list = new List<string>();

string newField = null;

 

for (int begin = 0; begin < len; ++begin)

{

symbol = expression[begin];

 

if (symbol == delimiter || symbol == ‘\n’)

{

list.Add(string.Empty);

}

else

{

newField = null;

int end = begin;

for (end = begin; end < len; ++end)

{

symbol = expression[end];

if (symbol == qualifier)

{

// bypass the unsplitable block of text

bool foundClosingSymbol = false;

for (end = end + 1; end < len; ++end)

{

symbol = expression[end];

if (symbol == qualifier) { foundClosingSymbol = true; break; }

}

if (false == foundClosingSymbol)

{

throw new ArgumentException

("expression contains an unclosed qualifier symbol" );

}

continue;

}

if (symbol == delimiter || symbol == ‘\n’)

{

newField = expression.Substring(begin, end – begin);

begin = end;

break;

}

 

}

if (newField == null)

{

newField = expression.Substring(begin);

begin = end;

}

list.Add(newField);

}

}

return list.ToArray();

}

 

I ran the above with 10 000 or so records and it was completed within 2 seconds or so. Some slow versions on the net that I found took over 6-7 minutes:

Here are some slow ones that you may run into on the net.

 

using System.Text.RegularExpressions;

 

public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)

{

string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))",

Regex.Escape(delimiter), Regex.Escape(qualifier));

 

RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;

if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;

 

Regex _Expression = New Regex(_Statement, _Options);

return _Expression.Split(expression);

}

 

public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)

{

bool _QualifierState = false;

int _StartIndex = 0;

System.Collections.ArrayList _Values = new System.Collections.ArrayList();

 

for (int _CharIndex=0; _CharIndex<expression.Length-1; _CharIndex++)

{

if ((qualifier!=null)

& (string.Compare(expression.Substring(_CharIndex, qualifier.Length), qualifier, ignoreCase)==0))

{

_QualifierState = !(_QualifierState);

}

else if (!(_QualifierState) & (delimiter!=null)

& (string.Compare(expression.Substring(_CharIndex, delimiter.Length), delimiter, ignoreCase)==0))

{

_Values.Add(expression.Substring(_StartIndex, _CharIndex – _StartIndex));

_StartIndex = _CharIndex + 1;

}

}

 

if (_StartIndex<expression.Length)

_Values.Add(expression.Substring(_StartIndex, expression.Length – _StartIndex));

 

string[] _returnValues = new string[_Values.Count];

_Values.CopyTo(_returnValues);

return _returnValues;

}

 

So I hope you have fun with split functions, and if you in the mood to make other custom functions, I am keen to see them!

MSBUILD: Creating Professional Deployment Scripts – Part 1

 

Introduction

I recently published a blog in regards to developing a custom application that will configure IIS using Directory Services. The tool is capable of:

  • Creating Application Pools
  • Creating Virtual Directories
  • Creating Web Sites
  • Configuring settings for the above (Application Pool Identity, Anonymous User, Virtual Directory Application Name)

To be honest, I was not entirely happy with this tool, since it would not work on Vista or later versions of IIS. Another problem, was that the XML file holding the configuration was my own ‘Language’ of expressing how to configure IIS and is not a standard, so could be hard for administrators to learn if I depart from the company, so we need something that a community supports.

A colleague of mine (Chris Hagens) introduced me to MSBuild, and I was blown away by this tool and how easy it is to use. This article will introduce a simple build script that will get you going so that you can blow the mind of your administrator when the next deployment schedule is nigh.

Before we continue, please take a moment to read this article, I certainly found it helpful in fast tracking my learning curve, I enjoy to understand processes instead of trying to hack. So put a sign on the door "Do not disturb" and grab a cup of tea and lets get down and dirty!

Patrick Smacchia: http://www.codeproject.com/KB/books/msbuild.aspx

Internet Information Server

Scenario

We are implementing a SOA (Service Orientated Architecture) solution which requires various web services to be deployed on various servers and different environments (Pre-Production, Production, Test and Development).

NTFS permissions will also be created and active directory account will be added to a group.

Solution

We going to geek it to the max and create a simple MSBuild script that will run at configure the web services automatically for us. There are many ways to deploy zipped applications, so in this solution, I want to keep it simple and assume you have a separate script to unzip the web service web files e.g asmx, web.config and dll’s to the file system.

The solution consists of two files:

  • WebServers.cmd
  • WebServices.build

There is also assemblies and schemas used:

  • Microsoft.Sdc.Common.tasks
  • MSBuild.Community.Tasks.Targets
  • Microsoft.Sdc.Tasks.BizTalk.dll
  • MSBuild.Community.Tasks.dll

I have created a zip file that you can download.

Download

http://grounding.co.za/files/folders/documents/entry1841.aspx

Prerequisite

.Net Framework

The prerequisite for this to work is the .NET Framework version 2 is installed, just the runtime

image

Here is a link for version 2 on a x86 platform

 http://www.microsoft.com/downloads/details.aspx?familyid=0856eacb-4362-4b0d-8edd-aab15c5e04f5&displaylang=en

if you on 64-bit windows, then download

http://www.microsoft.com/downloads/details.aspx?familyid=B44A0000-ACF8-4FA1-AFFB-40E78D788B00&displaylang=en

Libraries, XSD and import files

  • Microsoft.Sdc.Common.tasks
  • MSBuild.Community.Tasks.Targets
  • Microsoft.Sdc.Tasks.dll
  • MSBuild.Community.Tasks.dll

image

image

Note: If you run this on XP you may not get Application Pools! IIS 6.0 and higher uses Application Pools.

Analysis

Main Batch File

Here is the main batch file, it’s sole purpose is to pass the correct environment variable to the MSBuild tool, so it knows which environment it is building:

WebServices.cmd

———————————————————————————————-

@echo off
set /P ENVIRONMENT="Choose environment (DEV, TEST, PREP, PROD) : "
IF %ENVIRONMENT% == DEV GOTO deploy
IF %ENVIRONMENT% == TEST GOTO deploy
IF %ENVIRONMENT% == PREP GOTO deploy
IF %ENVIRONMENT% == PROD GOTO deploy
ECHO Unknown environment
GOTO end

:deploy
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe WebServices.build /property:ENVIRONMENT=%ENVIRONMENT%

:end
pause

———————————————————————————————-

The first thing we need to do is store the answer to the  question into an environment variable, in this case the question or prompt is going to be "Choose environment…."

The set /P switch allows you to set the value of a variable to a line of input entered by the user.  Displays the specified promptString before reading the line of input.  The promptString can be empty.

Once we have successfully we can now call the msbuild utility and parse it the correct argument for the environment.

"msbuild.exe WebServices.build /property:ENVIRONMENT=%ENVIRONMENT%"

WebServices.Build

———————————————————————————————-

<Project DefaultTargets="All"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
    <Import Project="C:\MSBUILD\Tools\Microsoft.Sdc.Common.tasks" />
    <Import Project="C:\MSBUILD\Tools\MSBuild.Community.Tasks.Targets"/>

    <!– Set the application name as a property –>
    <PropertyGroup Condition="$(ENVIRONMENT)==’DEV’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>Administrator</GatewayIdentityUsername>
        <GatewayIdentityDomain>guru-f09deb3e</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>password</GatewayIdentityPassword>
        <GatewayBackupFolder>C:\MSBuild\Example\Gateway\Backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\MSBuild\Example\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’TEST’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService</GatewayIdentityUsername>
        <GatewayIdentityDomain>TEST</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)
</GatewayIdentityFQUser>
        <GatewayIdentityPassword>PassworD</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’PREPROD’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService_PP</GatewayIdentityUsername>
        <GatewayIdentityDomain>TEST</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>pASSWORd</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>
    <PropertyGroup Condition="$(ENVIRONMENT)==’PROD’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>_SA_WebService</GatewayIdentityUsername>
        <GatewayIdentityDomain>PROD</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword></GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Gateway\WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>Gateway.WebServices</GatewayVirtualDirName>
    </PropertyGroup>

   <Target Name="AppPool" >
        <Prompt Condition="$(ENVIRONMENT) == ‘PROD’" Text="Enter the application pool password">
            <Output TaskParameter="UserInput" PropertyName="GatewayIdentityPassword"/>
        </Prompt>
        <Message Text="Creating Gateway Application Pool" />
        <Web.AppPool.Create ContinueOnError="true" AppPoolName="$(GatewayAppPool)" Identity="$(GatewayIdentityFQUser)" Password="$(GatewayIdentityPassword)" IdentityType="3" PeriodicRestartTime="12"/>
    </Target>

    <Target Name="VirtualDirectory">
        <Message Text="Creating Gateway Virtual Directory" />
        <Web.WebSite.DeleteVirtualDirectory VirtualDirectoryName="MMIT.Gateway.WebServices" />
        <Web.WebSite.CreateVirtualDirectory AppPoolId="$(GatewayAppPool)" AppCreate="true" WebAppName="$(GatewayVirtualDirName)" Path="$(GatewayVirtualPath)" VirtualDirectoryName="$(GatewayVirtualDirName)" />
    </Target>

    <Target Name="FolderPermissionsBackup">
        <MakeDir Directories="$(GatewayBackupFolder)"/>
        <Exec Command="cacls $(GatewayBackupFolder)  /G $(GatewayIdentityFQUser):F /T /E" />
        <Exec Command="cacls c:\windows\temp  /G $(GatewayIdentityFQUser):F /T /E" />
    </Target>

    <Target Name="ASPWorkerProcess">
        <ActiveDirectory.Group.AddUser ContinueOnError="true" GroupName="IIS_WPG" GroupMachine ="$(ServerName)" UserName="$(GatewayIdentityUsername)" UserDomain ="$(GatewayIdentityDomain)" />
    </Target>
    <Target Name="All">
        <CallTarget Targets="AppPool" />
        <CallTarget Targets="VirtualDirectory" />
        <CallTarget Targets="FolderPermissionsBackup" />
        <CallTarget Targets="ASPWorkerProcess" />
    </Target>
</Project>

———————————————————————————————-

XML Declarations and Imports

<Project DefaultTargets="All"  xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
    <Import Project="C:\MSBuild\Tools\Microsoft.Sdc.Common.tasks" />
    <Import Project="C:\MSBuild\Tools\MSBuild.Community.Tasks.Targets"/>

    <!– Set the application name as a property –>

This is an advanced XML file that actually conforms to an XSD, which is referenced in this namespace:

"http://schemas.microsoft.com/developer/msbuild/2003"

I recommend you reference it, it will allow you to use type safety when writing the XML configuration file, assuming you using an XML editor (notepad is not recommended, your eyes will go dizzy). Notice my files are located in my C:\MSBuild\Tools\ folder, you can modify this and choose your path.

Property Group

This section in the XML is very powerful, here you can define variables, like when writing a class, and then reference them, what is really nice you can use conditional statements to initialise the properties depending on a state of a variable e.g.

Condition="$(ENVIRONMENT)==’DEV’

In my example, I have properties to store for Username, Password, Domain and also the fully qualified username e.g. Domain\UserName

<PropertyGroup Condition="$(ENVIRONMENT)==’DEV’">
        <ServerName>.</ServerName>
        <!– Gateway settings –>
        <GatewayAppPool>MMITGatewayPool</GatewayAppPool>
        <GatewayIdentityUsername>Gateway</GatewayIdentityUsername>
        <GatewayIdentityDomain>DEV</GatewayIdentityDomain>
        <GatewayIdentityFQUser>$(GatewayIdentityDomain)\$(GatewayIdentityUsername)</GatewayIdentityFQUser>
        <GatewayIdentityPassword>mmitdev</GatewayIdentityPassword>
        <GatewayBackupFolder>c:\backup</GatewayBackupFolder>
        <GatewayVirtualPath>C:\Code\MMIT.Gateway\MMIT.Gateway.WebServices</GatewayVirtualPath>
        <GatewayVirtualDirName>MMIT.Gateway.WebServices</GatewayVirtualDirName>
</PropertyGroup>

Target Group

In this section you put the implementation of your scripts, like running commands.

I ran cacls.exe which confgures NTFS permissions for a backup folder and various other tasks, many of which are in libraries, the command "Web.WebSite.CreateVirtualDirectory" actually calls the dll referenced by the XML and passes the parameters in.

Below is a sample target Action which adds a user to a group:

    <Target Name="ASPWorkerProcess">
        <ActiveDirectory.Group.AddUser ContinueOnError="true" GroupName="IIS_WPG" GroupMachine ="$(ServerName)" UserName="$(GatewayIdentityUsername)" UserDomain ="$(GatewayIdentityDomain)" />
    </Target>

Running the tool

Double click WebServices.cmd

Type in DEV (The other environments will fail, unless you configure the properties for them)

image

 

Output of Sample Run

—————————————————————–

Choose environment (DEV, TEST, PREP, PROD) : DEV
Microsoft (R) Build Engine Version 2.0.50727.832
[Microsoft .NET Framework, Version 2.0.50727.832]
Copyright (C) Microsoft Corporation 2005. All rights reserved.

Build started 20/09/2008 10:03:29.
__________________________________________________
Project "C:\MSBuild\WebServices.build" (default targets):

Target All:
    Target AppPool:
        Creating Gateway Application Pool
        Creating app pool "MMITGatewayPool".
        MSBUILD : warning : A task error has occured.
        MSBUILD : warning : Message             = App Pool already exists.
        MSBUILD : warning : MachineName         = localhost
        MSBUILD : warning : AppPoolName         = MMITGatewayPool
        MSBUILD : warning : IdentityType        = 3
        MSBUILD : warning : Identity            = guru-f09deb3e\Administrator
        MSBUILD : warning : Password            = password
        MSBUILD : warning : IdleTimeout         = 20
        MSBUILD : warning : PeriodicRestartTime = 12
        MSBUILD : warning : WorkerProcesses     = 1
        MSBUILD : warning : RestartSchedule     = <String.Empty>
        MSBUILD : warning : RequestQueueLimit   = 1000
        MSBUILD : warning :
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
EnsureAppPool()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
Save()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Web.AppPool.Create.Interna
lExecute()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
        MSBUILD : warning : The system cannot find the path specified.
        MSBUILD : warning :
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind(B
oolean throwIfFail)
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind()

        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.get_Is
Container()
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Chec
kIsContainer()
        MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Add(
String name, String schemaClassName)
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.
EnsureAppPool()
        The previous error was converted to a warning because the task was calle
d with ContinueOnError=true.
        Build continuing because "ContinueOnError" on the task "Web.AppPool.Crea
te" is set to "true".
    Done building target "AppPool" in project "WebServices.build".
    Target VirtualDirectory:
        Creating Gateway Virtual Directory
        Deleting virtual directory "MMIT.Gateway.WebServices".
        Creating virtual directory "Gateway.WebServices".
    Target FolderPermissionsBackup:
        cacls C:\MSBuild\Example\Gateway\Backup  /G guru-f09deb3e\Administrator:
F /T /E
        processed dir: C:\MSBuild\Example\Gateway\Backup
        cacls c:\windows\temp  /G guru-f09deb3e\Administrator:F /T /E
        processed dir: c:\windows\Temp
        processed file: c:\windows\Temp\avg8info.id
        processed file: c:\windows\Temp\DMI6DC.tmp
        processed file: c:\windows\Temp\ehprivjob.log
        processed file: c:\windows\Temp\ehprivjob1.log
        processed file: c:\windows\Temp\MpSigStub.log
        processed file: c:\windows\Temp\WinSAT_DX.etl
        processed file: c:\windows\Temp\WinSAT_KernelLog.etl
        processed file: c:\windows\Temp\WinSAT_StorageAsmt.etl
    Target ASPWorkerProcess:
        MSBUILD : warning : A task error has occured.
        MSBUILD : warning : Message             = The specified domain either do
es not exist or could not be contacted.
        MSBUILD : warning :
        MSBUILD : warning : GroupMachine[0]     = .
        MSBUILD : warning : EnsureUserIsInGroup = False
        MSBUILD : warning : UserName            = Administrator
        MSBUILD : warning : GroupName[0]        = IIS_WPG
        MSBUILD : warning : UserDomain          = guru-f09deb3e
        MSBUILD : warning :
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.ActiveDirect
ory.User.Exists(String username, String domainName)
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.ActiveDirectory.Group.AddU
ser.InternalExecute()
        MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
        The previous error was converted to a warning because the task was calle
d with ContinueOnError=true.
        Build continuing because "ContinueOnError" on the task "ActiveDirectory.
Group.AddUser" is set to "true".
    Done building target "ASPWorkerProcess" in project "WebServices.build".
Done building target "All" in project "WebServices.build".

Done building project "WebServices.build".

Build succeeded.
MSBUILD : warning : A task error has occured.
MSBUILD : warning : Message             = App Pool already exists.
MSBUILD : warning : MachineName         = localhost
MSBUILD : warning : AppPoolName         = MMITGatewayPool
MSBUILD : warning : IdentityType        = 3
MSBUILD : warning : Identity            = guru-f09deb3e\Administrator
MSBUILD : warning : Password            = password
MSBUILD : warning : IdleTimeout         = 20
MSBUILD : warning : PeriodicRestartTime = 12
MSBUILD : warning : WorkerProcesses     = 1
MSBUILD : warning : RestartSchedule     = <String.Empty>
MSBUILD : warning : RequestQueueLimit   = 1000
MSBUILD : warning :
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.EnsureAp
pPool()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.Save()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Web.AppPool.Create.InternalExecute
()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
MSBUILD : warning : The system cannot find the path specified.
MSBUILD : warning :
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind(Boolean t
hrowIfFail)
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.Bind()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntry.get_IsContaine
r()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.CheckIsConta
iner()
MSBUILD : warning :    at System.DirectoryServices.DirectoryEntries.Add(String n
ame, String schemaClassName)
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.Web.AppPool.EnsureAp
pPool()
MSBUILD : warning : A task error has occured.
MSBUILD : warning : Message             = The specified domain either does not e
xist or could not be contacted.
MSBUILD : warning :
MSBUILD : warning : GroupMachine[0]     = .
MSBUILD : warning : EnsureUserIsInGroup = False
MSBUILD : warning : UserName            = Administrator
MSBUILD : warning : GroupName[0]        = IIS_WPG
MSBUILD : warning : UserDomain          = guru-f09deb3e
MSBUILD : warning :
MSBUILD : warning :    at Microsoft.Sdc.Tasks.Configuration.ActiveDirectory.User
.Exists(String username, String domainName)
MSBUILD : warning :    at Microsoft.Sdc.Tasks.ActiveDirectory.Group.AddUser.Inte
rnalExecute()
MSBUILD : warning :    at Microsoft.Sdc.Tasks.TaskBase.Execute()
    2 Warning(s)
    0 Error(s)

Time Elapsed 00:00:02.89
Press any key to continue . . .

—————————————————————–

image

image

Download

http://grounding.co.za/files/folders/documents/entry1841.aspx

Conclusion

 

I think this article gives enough substance to get you going in creating deployment scripts that are kick ass. Play with it, and remember this is designed for IIS 6.0 and above. I did not package in a sample web service file, but I am sure you folks can test it with a real virtual directory, my sample for the dev environment is empty, to keep things simple. Remember, when you run the tool to type DEV and dev etc, the prompts are case sensitive.

This tool is really fun to play with and adds an extra edge to your applications at deployment time. In Part 2, we will take this tool to the limits and automate BizTalk 2006 deployments, which I can assure you, requires some super Geeking!

PowerShell: Calling Static Methods, Object Instantiation and PowerTab

Hi Folks,

In this discussion, we will look at how we can call static methods with PowerShell.

 

Call Static Methods

Allot of the System namespaces are already loaded in PowerShell. So for example the following command can be used to get the current datetime.

So we issue the following command:

$MyDate = [System.DateTime]::Now

image

 

Notice the above is similar to using reflection in .NET to load an assembly:

[System.Reflection.Assembly]::LoadFrom("..\mydll") or calling other methods like assembly loads:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

You can of course run endless number of static methods:

Try this one:

[System.Console]::get_backgroundcolor()

Intellisense

We need a way to enumerate .NET assemblies, like we do in Visual Studio!

Before we get started, I like to use PowerTab for intellisense. You can download it here:

http://thepowershellguy.com/blogs/posh/pages/powertab.aspx

Once I download it and before I run the setup, I need to trust the script, I do it the lazy way and trust all scripts, then afterwards will set it back to default policy:

Set-ExecutionPolicy unrestricted

Later after the install you can set it back to normal:

Set-ExecutionPolicy Restricted

You can read more about signing with this command:

get-help about_signing

 

image

Once I run this, I double click the setup from PowerTab, you will be presented with this dialog box:

image

I press enter to kick off the install.

I answer the various questions during the install, usually the default will suffice:

image

Here is the final screen, once PowerTab is installed:

image

When I close and reopen PowerShell, I now have PowerTab for Intellisense, this can be useful for seeing .NET assemblies using the TAB key.

image

YAY! Now check it out when I press the TAB key when looking for a .NET class to script:

image

I can now easily write some f^%& cool scripts, so who’s your Daddy?

Object Instantiation

Lets try an instantiate a class. So lets assume we have no clue how to do this, use this command to get the help we need:

Get-Help *

This will display a list of help topics, lets see if we can find something on object instantiation. I see new_object, this seems like something we will need:

PS C:\Documents and Settings\romiko> Get-Help new-object

NAME

New-Object

SYNOPSIS

Creates an instance of a .Net or COM object.

SYNTAX

New-Object [-typeName] <string> [[-argumentList] <Object[]>] [<CommonParameters>]

New-Object [-comObject] <string> [-strict] [<CommonParameters>]

DETAILED DESCRIPTION

Creates an instance of a .Net or COM object. You specify either the type of a .Net class or a Programmatic Identifi

er (ProgID) of a COM object. By default, you type the fully-qualified name of a .Net class and the cmdlet returns a

reference to an instance of that class. To create an instance of a COM object, use the ComObject parameter and spe

cify the ProgID of the object as its value.

RELATED LINKS

Compare-Object

Select-Object

Sort-Object

ForEach-Object

Group-Object

Measure-Object

Tee-Object

Where-Object

REMARKS

For more information, type: "get-help New-Object -detailed".

For technical information, type: "get-help New-Object -full".

PS C:\Documents and Settings\romiko>

 

Ok, so from here let us try and instantiate a .NET Object.

Imagine we have a text file with XML:

————myXML.xml———————-

<Person>
<Name>Romiko</Name>
<Surname>Van De Dronker</Surname>
</Person>

———————————————–

Lets say we want to load this into the DOM as an instance of the XMLDocument class.

First we create an instance:

$myInstance = new-object System.Xml.XmlDocument

This is what we can do is load the XML from a file:

image

$myInstance.Load("c:\myXML.xml")

Now we can display the XML from the object

$myInstance.Person

Here is the output:

image

You can even see other properties like:

$myInstance.get_InnerXml()

returns a string

<Person><Name>Romiko</Name><Surname>Van De Dronker</Surname></Person>

Other useful things you can do is see members like when you reflect objects:

[System.Xml.XmlDocument] | get-member

image

Conclusion

Well, I hope this gave you an insight into what we can do with PowerShell, we can pretty much do what we want with the system, and there is also allot of help features and even intellisense, if you take the time to download and install PowerTab, it helps allot when coding against assemblies, you can even use reflection here and load other assemblies and dll’s!

Have fun with it, and I see you folks next time, when we Geek it up with BizTalk/SharePoint and PowerShell.

Cheers

PowerShell: The Warm Up

Hi Folks,

This article is going to give a brief demonstration on how useful PowerShell can be, not only for administrators but also for developers. One of the really cool things with PowerShell is the ability to call static classes in .NET and also instantiate objects from the command console. Sometime if I need to find something deep in the object model of BizTalk and lately even with SharePoint, PowerShell can then be used to quickly check out the hidden agenda’s!

Recently, I wanted to run a test case, where I needed to migrate documents from MOSS 2003 to MOSS 2007. The problem is that I needed to keep the original file names of the migrated documents and do test runs on my virtual machine for all 80000 documents. I decided the best way forward to generate the test data, was use SQL to get a list of the filenames and then use PowerShell to recreate the files with the same filenames but just have small garbage in it, so that my VM will not run out of space when I do a dry run of the migration tool.

So, to start with, I have a text file with the following in it:

—— MyTextFile.txt ——

FileNamea.txt
FileNameb.txt
FileNamec.txt

—————————–

If you feel like using an old dos command to make a file with text:

image

In the above, you use F6 to save the file, which is the ^Z symbol.

What we are going to do, is use PowerShell to loop through the text file and create 1kb files for me with the same name.

First you need to download PowerShell from here:

http://www.microsoft.com/windowsserver2003/technologies/management/powershell/download.mspx

You can then open the console, by navigating to the shortcut in the start menu.

image

Then you run the following command:

type MyTextFile.txt | foreach($_){New-Item -name $_ -itemType file ; "romiko" > $_ }

Here we use the type command to output the contents of the file to standard output in the console. Then we PIPE the output into a foreach statement, where $_ is the standard Console.ReadLine. We then have an action to create a New Item of type File. Once the file is created we redirect a string called "romiko" into the newly create file > $_. This is similar to using this command dir /w > directorylist.txt

Here is the output

image

Notice it says the file is 0 Length, do not believe it, run another dir command:

image

This is the first of many blogs that I will be writing in regards to PowerShell. When I have a moment I will demonstrate the use of PowerShell for checking things out in the SharePoint and BizTalk object model, until then, enjoy the world of powerful scripting.

Now, who said that Windows Shell was not as Powerful as Unix? Thanks to Joe Capka for introducing me to this wonderful tool! You can read Joe Capka’s blogs here:

http://jcapka.blogspot.com/

Regards

Romiko

BizTalk 2006: Analyzing Orchestrations Execution Statistics

Hi Folks,

When you have developed an Orchestration with different possible routes and branches, it might be useful to know which branches are executing the most. I certainly dislike Orchestrations but for this solution, I had to use one due to legacy system design L, so I got a good excuse this time round!

I am using an orchestration here that calls many other orchestrations (BAD PRACTICE, so always avoid it if possible).

You can use a tool called

BizTalk Server 2006 Orchestration Profiler v1.1.1

Download the Code from:

http://www.codeplex.com/BiztalkOrcProfiler/Release/ProjectReleases.aspx?ReleaseId=6375

If you running it on 64 bit machine, change the config of the file for the program files directory!

C:\Program Files\Microsoft Services\BizTalk 2006 Orchestration Profiler

In 64 bit it is C:\Program Files (x86) Microsoft Services\BizTalk 2006 Orchestration Profiler

 

So just fix the configuration file path:

Microsoft.Sdc.OrchestrationProfiler.exe.config

 

Ok, so now we ready to analyze the orchestration. Go to the Start Menu to start the program

Click List Orchestrations

Click the Orchestrations you want to profile. Then Click generate report.

 

If you get this error:

 

 

Then you need to go to the config file I spoke about:

Microsoft.Sdc.OrchestrationProfiler.exe.config

 

And change the path to the location where the hhc.exe file is locate don your computer, on mine it is at:

<appSettings>

<add key="HelpCompilerLocation" value="C:\Program Files\HTML Help Workshop\hhc.exe" />

<add key="ShowHotSpots" value="0" />

</appSettings>

The HHC.EXE file is used to create a chm file which is windows help file, nice to use to page through and stuff like that.

You can download it and install it at:

http://www.microsoft.com/downloads/details.aspx?FamilyID=00535334-c8a6-452f-9aa0-d597d16580cc&DisplayLang=en

 

 

I chose to install it in the same location as the default location in the config fiel above J

 

After installing this etc, I went back the program and generated the report for my orchestration:

It created a report on my c:\ BizTalk Orchestration Profile Report.chm

 

 

 

So from the above you can see that this particular orchestration catches general exceptions and not other types of exceptions, so I know from the web service errors that general exceptions are being thrown in 100% of the cases. I use this example as a example where I ran a test cycle on 200 records and wanted to know where in my orchestration the 38 records out of the 200 failed, I wanted to know if it was a combination of different type of errors, to get an idea of what error handling the external web service may need, since the external web service I call, always returns a False or True (Not a cool way to do things, always develop web services to give decent error messages!)

So from here, I can see the general flow of messages and what exception types occur and make additional improvements to the design.

Also, I can see what takes long.

Also which shapes are a loser.

Hope you also get a chance to profile your orchestrations and see how they are working!

Good Luck, and remember, avoid orchestrations when you can, they the easy way out and can cause an overhead if used in the incorrect manner. I have seen on many occasions people using them in an Object Orientated Manner, not good, the orchestration in this example is a good case to go by, it calles other orchestrations that should actually be treated as objects not orchestrations.

Cheers

BizTalk: Executing Inline Send Pipelines in an Orchestration

Hi Folks,

No, I have not forgotten to post the blog about web services part 2 and MSBuild part 2, I need to find sometime to do it, I hate to rush it, you know…

Introduction

After toiling with Inline pipelines to achieve low latency objectives, I thought it might be a good idea to share what I have learnt about them.

I have two Send Pipelines.

One of them is used to prepare a dynamic Windows SharePoint Services Send Port

The other is a very complicated pipeline that transforms flat files to a very specific XML schema, it has it’s own rule engine and built in auditing components as well as a host of other class libraries that it uses for flat file conversion to XML. You might ask me, why the Fu… did you write your own component, when there is a BizTalk Mapper, you want me to be honest. I think the BizTalk mapper is a load of S%$^ for enterprise applications. It is cool for flight itinerary examples and very small transformations. Secondly it is slow. Thirdly is consumes huge amounts of memory.

So I decided to develop a Flat File mapping tool that is called from within a Pipeline, this allows me to use the streaming model within the pipeline and process one record at a time from a flat file. This I think is much more flexible. Secondly I can use a custom rule engine to apply manipulation and lastly, I can use a XSLT 2.0! You heard me right, BizTalk does not support XSLT 2.0, so there goes allot of mapping features. How it works in a nutshell, is that I have a database that is used to dynamically detect file feeds and apply the appropriate transformation, the XSLT 2.0 templates are stored in a configuration database that the FileManagement web service interfaces with, the Pipeline component library uses the handlers to these libraries for managing all the metadata. It is extremely fast. Also the actually mapping of the data is stored in a Serializable class, and a UI is used to De-serialize and Serialize this mapping data for on the fly changes. This means, that if the flat file schema changes or the mapping needs to change, I DO NOT need to REDPLOY half of my bloody BizTalk assembles, you know the score, the schemas need to be redeployed, the mappings and so the list goes on.

This is not what this blog is about, but I thought it would be nice since the orchestration I show you is a FileManagement orchestration and is responsible for this part. The orchestration will process a flat file, send it to SharePoint for archiving and then transform it to XML format and then send it to a central database for workflow processing.

Inline Send Pipeline

Here is an overview of the Orchestration, I kept it simple, so big warning! This orchestration is not yet optimised to reduce persistence points. However using Inline Pipelines can improve latency, if done correctly. So if you use this orchestration please optimise it. For example, I have no Suspend shapes, and I should have them, I don’t like to lose my data!

SharePoint Send Pipeline

The task of this pipeline is to promote some properties that the dynamic send port will use e.g.

pInMsg.Context.Write("ConfigPropertiesXml", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties", configxml);

You can read my previous blog about this property at:

developing-a-dynamic-biztalk-windows-sharepoint-adapter

Another task that it does is generate custom metadata by interfacing with a Business Object Layer to detect all metadata.

So in a nutshell this pipeline is more of a property manager.

SendWSSProperties.btp

image 

Here is the Execute method of the WSSFilesEncoder component.

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
       {
           IBaseMessage outMsg;          
           string recieveFileNamePath = pInMsg.Context.Read("ReceivedFileName", "http://schemas.microsoft.com/BizTalk/2003/file-properties").ToString();
           string originalFileName = Path.GetFileName(recieveFileNamePath);
           string receiveFolderName = Path.GetDirectoryName(recieveFileNamePath);
           AuditFileFeedRequest request = new AuditFileFeedRequest();
           try
           {
               //Link the context of the output message to the input message
               outMsg = pContext.GetMessageFactory().CreateMessage();
               outMsg.Context = pInMsg.Context;
               outMsg.AddPart(pInMsg.BodyPartName, pContext.GetMessageFactory().CreateMessagePart(), true);

               //initialize the FileMetaData
               string messageID =  pInMsg.Context.Read("InterchangeID", "http://schemas.microsoft.com/BizTalk/2003/system-properties").ToString();
               SharePointMetaData smd = new SharePointMetaData(originalFileName, receiveFolderName, messageID);
               ConfigPropertiesXml  spp = smd.GetCustomSharePointColumns();
               request = AuditManager.GenerateAuditRequest(spp.SPSIDValue, spp.FileFeedSourceValue, spp.OriginalFileNameValue, spp.NameValue);
               outMsg.Context.Write("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties", smd.NewFileName);
               outMsg.Context.Promote("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties", smd.NewFileName);
               string configxml = spp.Serialize().InnerXml;
               pInMsg.Context.Write("ConfigPropertiesXml", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties", configxml);
           }
           catch (Exception e )
           {
               LogManager.LogGeneral("Exception Occured " + e.Message);
               AuditManager.Audit(request, null, "SharePoint SendPort: " + incomingArchiving, "Failure", e.Message);
               throw new Exception("Exception Occured: " + e.Message);

           }
           AuditManager.Audit(request, null, "SharePoint SendPort: " + incomingArchiving, "Success","SharePoint Import Ended");
           return pInMsg;
       }

So above, a flat file goes in and a flat file comes out with some new properties!

For those sharepoint Guru’s? I developed a custom class that can be used to manage properties being sent to a document library.

So if you look at my Document Library, you can see when I send a flat file that metadata is populated, this is all done with the class I show below.

image

It is a really cool class, if using a dynamic send port to SharePoint you can use this class to generate the config properties in the message context at runtime, just customise it for you! Compare this to the Document Library columns I have above. Then in a pipeline you can instantiate this class and fill in the properties from a xml file or a database!

using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace MMIT.FileManagement.BOL.SharePoint
{
    /// <remarks/>
    [GeneratedCode("xsd", "2.0.50727.42")]
    [Serializable]
    [DebuggerStepThrough]
    [DesignerCategory("code")]
    [XmlType(AnonymousType = true)]
    [XmlRootAttribute(Namespace = "", IsNullable = false)]
    public class ConfigPropertiesXml
    {
        private string SPSIDField = "SPSID";

        private string propertySource1Field ="";

        private string FileFeedSourceField = "FileFeedSource";

        private string propertySource2Field = "";

        private string OriginalFileNameField = "OriginalFileName";

        private string propertySource3Field = "";

        private string BizTalkMessageIDField = "BizTalkMessageID";

        private string propertySource4Field = "";

        private string RegionField = "Region";

        private string propertySource5Field = "";

        private string CountryField = "Country";

        private string propertySource6Field = "";

        private string BrandField = "Brand";

        private string propertySource7Field = "";

        private string NameField = "Name";

        private string propertySource8Field = "";     

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName1")]
        public string SPSID
        {
            get { return SPSIDField;}
            set { SPSIDField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource1")]
        public string SPSIDValue
        {
            get { return propertySource1Field; }
            set { propertySource1Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName2")]
        public string FileFeedSource
        {
            get { return FileFeedSourceField; }
            set { FileFeedSourceField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource2")]
        public string FileFeedSourceValue
        {
            get { return propertySource2Field; }
            set { propertySource2Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName3")]
        public string OriginalFileName
        {
            get { return OriginalFileNameField; }
            set { OriginalFileNameField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource3")]
        public string OriginalFileNameValue
        {
            get { return propertySource3Field; }
            set { propertySource3Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName4")]
        public string BizTalkMessageID
        {
            get { return BizTalkMessageIDField; }
            set { BizTalkMessageIDField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource4")]
        public string BizTalkMessageIDValue
        {
            get { return propertySource4Field; }
            set { propertySource4Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName5")]
        public string Region
        {
            get { return RegionField; }
            set { RegionField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource5")]
        public string RegionValue
        {
            get { return propertySource5Field; }
            set { propertySource5Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName6")]
        public string Country
        {
            get { return CountryField; }
            set { CountryField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource6")]
        public string CountryValue
        {
            get { return propertySource6Field; }
            set { propertySource6Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName7")]
        public string Brand
        {
            get { return BrandField; }
            set { BrandField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource7")]
        public string BrandValue
        {
            get { return propertySource7Field; }
            set { propertySource7Field = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertyName8")]
        public string Name
        {
            get { return NameField; }
            set { NameField = value; }
        }

        /// <remarks/>
        [XmlElement(Form = XmlSchemaForm.Unqualified, ElementName = "PropertySource8")]
        public string NameValue
        {
            get { return propertySource8Field; }
            set { propertySource8Field = value; }
        }

        /// <summary>
        /// From XML to Object
        /// </summary>
        /// <param name="doc"></param>
        /// <returns></returns>
        public static ConfigPropertiesXml BuildConfigPropertiesXml(XmlNode doc)
        {
            if (doc == null)
                return null;
            XmlSerializer X = new XmlSerializer(typeof(ConfigPropertiesXml));
            return (ConfigPropertiesXml)X.Deserialize(new XmlNodeReader(doc));
        }

        /// <summary>
        /// From Objec to XML
        /// </summary>
        /// <returns></returns>
        public XmlDocument Serialize()
        {
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
            XmlDocument doc = new XmlDocument();
            XmlWriterSettings writerSettings = new XmlWriterSettings();
            writerSettings.OmitXmlDeclaration = true;
            StringWriter stringWriter = new StringWriter();
            using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, writerSettings))
            {
                XmlSerializer X = new XmlSerializer(typeof(ConfigPropertiesXml));
                X.Serialize(xmlWriter, this,ns);
                doc.LoadXml(stringWriter.ToString());
            }        
            return doc;
        }

        /// <summary>
        /// Returns a populated ConfigPropertiesXml string
        /// </summary>
        /// <returns></returns>
        public static ConfigPropertiesXml SetSharePointColumns(int SharePointID, string FileFeedSource, string OriginalFileName, string BizTalkMessageID, string Region, string Country, string Brand, string NewFileName)
        {
            ConfigPropertiesXml cp = new ConfigPropertiesXml();

            cp.SPSIDValue = SharePointID.ToString();
            cp.FileFeedSourceValue = FileFeedSource;
            cp.OriginalFileNameValue = OriginalFileName;
            cp.BizTalkMessageIDValue = BizTalkMessageID;
            cp.RegionValue = Region;
            cp.CountryValue = Country;
            cp.BrandValue = Brand;
            cp.NameValue = NewFileName;
            return cp;
        }

    }
}

File Translator Pipeline

This pipeline actually modifies the message! A flat file goes in and a XML comes out. I apologise for the name UFT, it means universal file translator, I never made up this name! it is rather funny.

SendUFT.btp

image

Here is the execute method of the FileTranslator Pipeline.

public IBaseMessage Execute(IPipelineContext pc, IBaseMessage pInMsg)
       {
           RecordCount recordCount = new RecordCount();
           string configPropertiesXml = pInMsg.Context.Read("ConfigPropertiesXml", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties").ToString();
           XmlDocument doc = new XmlDocument();
           doc.LoadXml(configPropertiesXml);
           XmlNode node = doc.SelectSingleNode("ConfigPropertiesXml");
           ConfigPropertiesXml spp = ConfigPropertiesXml.BuildConfigPropertiesXml(node);
           AuditFileFeedRequest request = AuditManager.GenerateAuditRequest(spp.SPSIDValue, spp.FileFeedSourceValue, spp.OriginalFileNameValue, spp.NameValue);
           LogManager.LogGeneral("Started, UFT Import from SharePoint: " + spp.NameValue);

           try
           {

               UFTMappingAgent vFileConfig = new UFTMappingAgent(spp.FileFeedSourceValue, spp.SPSIDValue);
               StreamReader inputStream = new StreamReader(pInMsg.BodyPart.GetOriginalDataStream(), vFileConfig.UftMapping.Encoding);
               MemoryStream outputMemoryStream = UFTEngine.StartUFT(inputStream, recordCount, Simulation.Flag.Disabled, vFileConfig);
               outputMemoryStream.Position = 0;
               outputMemoryStream.Seek(0, SeekOrigin.Begin);
               pInMsg.BodyPart.Data = outputMemoryStream;
               LogManager.LogGeneral("Completed UFT PipeLine, File Import: " + spp.NameValue);
               //#if(DEBUG)
               DebugData(outputMemoryStream);
               //#endif
           }
           catch (Exception e)
           {
               LogManager.LogGeneral("File Import Failed: " + spp.NameValue + Environment.NewLine + e.Message);
               AuditManager.Audit(request, recordCount, "UFT", "Failure", e.Message);
               throw;
           }
           AuditManager.Audit(request, recordCount, "UFT", "Success", "UFT Ended");
           return pInMsg;
       }

Orchestration

Ok, now the exciting bit for those of you that already have pipelines and just want to know how to execute them. Here is my orchestration.

image

I highlighted the shapes that are used for preparing Inline Pipelines.

image

In this shape I take the input message that came into the orchestration and I add it to a PipeLine collection of type:

Microsoft.XLANGs.Pipeline.SendPipelineInputMessages

Basically, what you need to realize is that when you call a Send Pipeline it needs to know the 3 things:

1. The type of pipeline to call

2. The SendPipelineInputMessages

3. The Output variable that the pipeline uses to assign it’s output message

So before we execute a pipeline, the first thing we do is prepare the SendPipelineInputMessages. In my case, I am not batching, I just send one message to the collection. Remember messages are immutable in BPEL so when a pipeline spits out a message you MUST assign it to a NEW message. Ok here are the variable properties for my collection.

image

 

Here is the code for the expression shape above:

image

The next shape is specific to my business process and does not concern us much, but I put it here, it is a detection mechanism to detect a file feed and grabs its configuration from a database.

image

image

I then have an If statement to check if the file was successfully detected. If it is we can then execute the pipeline. Since we already added the input message to the collection, we ready to go. But before we do so, I have some good news!! Whenever you add messages to a collection of type Microsoft.XLANGs.Pipeline.SendPipelineInputMessages, the CONTEXT properties associated with the message is preserved!

image

You realise that the call to the Pipeline is ALWAYS in a Construction Shape since we will create a new message, in this case the new message will be: PromotedFlatFile

image

image

Here is the code for executing the pipeline, Notice the variables for the method!

image

So, there you have it, I then take the PromotedFlatFIle and send it to SharePoint.

image

 

image

I also have another pipeline further down, in that section, I do allot of the work in one expression within the Message Construct. Lets check it out!

image

image

Above, I use a new collection for my message (We could have used the existing one, BUT NEVER set it to null, you need the constructor!) So the statement below will never work.

FlatFileUFTPipelineInputMessage = null;

FlatFileUFTPipelineInputMessage.Add(PromotedFlatFile);

Here is the text representation of the call:

System.Diagnostics.Trace.WriteLine("Completed to send flat file to SharePoint");
SendUFTMessage = null;
FlatFileUFTPipelineInputMessage.Add(PromotedFlatFile);
System.Diagnostics.Trace.WriteLine("Executing UFT Pipeline");
Microsoft.XLANGs.Pipeline.XLANGPipelineManager.ExecuteSendPipeline
(
typeof(MMIT.FileManagement.Pipelines.SndUFT),
FlatFileUFTPipelineInputMessage,
SendUFTMessage
);
System.Diagnostics.Trace.WriteLine("Completed Executing UFT Pipeline");

Now I just send the New

SendUFTMessage which is an XML file and not a flat file!

What I do here is call another orchestration to import the xml data.

image

image

 

Conclusion

Executing Inline pipelines is pretty straight forward, however you need to make sure you working with the latest version of a message, in my case the UFT Pipeline was working with a message constructed from the previous pipeline!

Also, the above orchestration is not optimised. So choosing transaction scopes wisely and the use of Suspend shapes is a good idea, which I will do when i go back to work on Monday morning, sigh………..

SharePoint, BizTalk: Developing a Dynamic BizTalk Windows SharePoint Adapter

Hi Folks,

I recently needed to automate our file management system. I decided to use the BizTalk SharePoint Adapter that ships with BizTalk 2006 and R2. This article is for advanced BizTalk developers that are comfortable with pipeline components.

Introduction

How the adapter works is that you need to install the Adapter Web Service on the server hosting the SharePoint site where you want BizTalk messages to be delivered. When you run the setup wizard from the BizTalk CD you will notice the Share Adapter option, it is easy to confuse this with an Adapter, but it is not an adapter, the adapter is already installed in BizTalk, the add-on in the CD is the actual web service that the Adapter will communicate with in order to post documents and messages to a SharePoint site.

I am going to show you how to do this the hardcore way, what I mean is, NOT using Orchestrations, I hate them, I really do hate orchestrations.

Below is a diagram showing the high level architectural overview of the communication layers.

Sources: http://technet.microsoft.com/en-us/library/aa558796.aspx

So the option on the CD is actually to install the BTSharePointAdapterWS.asmx Web service.

The problem I found with the SharePoint adapter is the following:

  1. I have different types of messages that need to be send to SharePoint, and extra column information needs to be populated in the Document Library once BizTalk posts data to the Document Library. The extra column information is different for different messages. I want to avoid having multiple SharePoint Send ports!
  2. When BizTalk posts messages to the SharePoint server, some of the documents are routed directly to an Archive location while others are sent to an Incoming Folder on SharePoint so that a separate BizTalk process can process the files and send them to the BizTalk workflow application. Upon successfully sending a SharePoint document to the workflow system on BizTalk, the SharePoint adapter must Archive the file into an archive location for users to have access to.

The Receive Adapter for SharePoint is rather nice, however I feel it is still an immature product, and needs some work on it. One of the main reasons is that if you look closer at it, it supports archiving documents once pulled from SharePoint.

This is a fantastic feature! But I was soon to be disappointed as I am with allot of the BizTalk features (Such as the aggregator pattern that does not work properly, seem my blog about it). The problem is that the Archive feature above calls the web service and method FinalizeDocuments:

Now if you look at my requirements above, I wanted extra column info to me sent to SharePoint, the send port for SharePoint can do this, see below:

Which is nice, you mention the custom column name you added in the document library view and then the value. This information for all the columns is then translated into an XML document (More on this later) and then sent to the adapter for processing.

So back the problem, if you choose to Archive your files, the web service method will NOT copy all the custom column information across, so you lose it, and this can spell bad news, when you have views in SharePoint that rely on these columns for filtering e.g. By Country, Region, Brand etc. So I hope Microsoft fixes this problem soon, what they need to do is alter the web service to also manage the document metadata from the columns, this is easy for them to do, since there receive adapter automatically gets this information, I will show you how later.

Ok, so I think I have set the scene here in summary we got some serious issues with the SharePoint adapter and we need to address them.

Overview

What we going to do is this.

  1. Install the Web Service on the SharePoint Site
  2. Configure a Dynamic SharePoint Service send port to send documents to SharePoint
  3. Configure a Receive SharePoint port to pull the documents from SharePoint (But not use the archive feature of the receive port, sinceCon archiving does not store custom column information, we need to setup an extra processing round in BizTalk to send it back to SharePoint for archiving)
  4. Configure a Dynamic SharePoint service to Archive documents (Setup a filter expressions to subscribe to messages coming from the SharePoint port)
  5. Configure a send port that subscribes to the same messages as step 4 but routes these to the workflow system

The Pattern is like this

  • File Receive Locations to pickup files
  • Dynamic send port to send documents to the incoming folder to archive folder (messages in incoming are pulled back into BizTalk, messages sent to archive are not meant to go to workflow system)
  • SharePoint Receive Location to pull files from incoming
  • Send location to subscribed to pulled messages and send it to workflow
  • SharePoint send location to subscribed to pulled messages and send it to Archive

Basically we had to introduce extra ports to compensate the bug in the SharePoint adapter where column information is lost, what we did is develop a custom pipeline that will manage the metadata from the SharePoint site when data is pulled off the site.

This is what the configuration looks like in BizTalk.

TIP: In production make a dedicated receive and send handler for SharePoint, if the SharePoint server is down, the BizTalk process will crash! Another bug with the SharePoint adapter, insufficient error handling! Remember in BizTalk that a receive handler and send handler are actual windows services, so make one dedicated to SharePoint, so when it crashes it does not affect other BizTalk processes! MICROSOFT PLEASE FIX THIS, IF SHAREPOINT IS UNAVAILABLE DON’T CRASH THE BIZTALK HOST INSTANCE IT IS RUNNING UNDER!

So as you can see my two custom pipeline components are located in the receive ports, the one custom component on the file receive port is used to prepare the context data in BizTalk so that the Dynamic Send Port can interpret this at runtime, the other custom component is developed to manage the metadata from SharePoint and remember the custom column values when PULLING from SharePoint.

Here are the send ports.

 

Notice the AND filter above, I will discuss this later, it is the DEFAULT value when you create a filter, and it can cause problems with Dynamic Send ports!

Notice the filter above matches the filters below, since these to send ports subscribe to the SAME message:

Remember I spoke about the "AND" above, if you go to the Group Hub Page and check the subscriptions for a DYNAMIC Port:

You get this filter on a dynamic port:

Â

This is how we subscribe to messages, THINK ABOUT THIS…. I hope you had a though about it, remember a dynamic port does not have any configuration forms, so to configure the port, you need to do it at runtime, which means you will need to promote the .OutboundTransportType and OutboundTransportType, since the filter is set in the GUI for ReceivePortName == File Management Importer. Where do you promote these in an orchestration or PIPELINE? We will come back to this, but for now remember, when dealing with dynamic ports, you need to manage the conext information manually in an orchestration or pipeline, since I hate orchestrations, I always develop custom pipelines.

A huge clue to configure a dynamic send port is this article:

http://technet.microsoft.com/en-us/library/aa547920.aspx

We will come back this later, for now, I wanted you to keep in mind that configuring dynamic send ports is not trivial, but also not hard, in fact even without any documentation it can be done, by delving into BizTalk.System Application and checking out the schemas, for this one, I had to read this schema:

If you open it, it provides clues on how to configure message context for the dynamic send port. Remember the static port is similar to the dynamic one!

You can read the above schema view to see what properties to promote, for example, LOOK AT FILENAME, when you receive a file from the file adapter, the property is called Receivefilename,

but in WSS adapter it is different, which means you lose the file name info but you can still save it and then manually promote and set this value, I show you how later, for now, I want you to understand the mechanics of dynamic property promotion, in essence what you looking at above is a BUILT IN PROPERTY SCHEMA.

From the above you can see why when a message goes from one adapter to another that context information can be lost, because adapters have different context property names, so a file name in a file adapter is different to a SharePoint adapter, since they different PROPERTY SCHEMA’S! Ok, enough lets get down configuring the web service.

Configure the security groups

I always have my service accounts and permissions in Active Directory, so get these groups up and running in AD.

  • Create a Windows Group called: SharePoint Enabled Hosts

 

Then you add the BizTalk host to the group, this is the service account username used by BizTalk, the one that is used when you configure a BizTalk handler, I chatted about this before, always have a dedicated handler for SharePoint1

  • On the SharePoint site where the BTSharePointAdapterWS is going to be installed, give the group SharePoint Enabled Hosts the contributor access

 

Configure the SharePoint Web Service

On the SharePoint server use the BizTalk R2 CD to install the SharePoint Adapter Service:

Then run the BizTalk Configuration

It will install the web service on the web site:

 

Edit the web service web.config file by commenting out the remove name element, this will allow you to browse the web service list.

                                <webServices>

                                                <protocols>

                                                                <!–<remove name="Documentation"/>–>

                                                </protocols>

                                </webServices> 

 

Configure the File Receive Port and Location

Now that you got the web service running, we now need to configure the file receive location

Basically, it is pretty easy to setup the receive location; the hard part is developing the custom pipeline component to prepare the document for the Dynamic SharePoint adapter. I assume you know how to write custom pipeline components, the component I developed is a decoder.

Here is the code for my pipeline, I call a custom external class to manage the metadata, you can do the same if you like. The class I use reads a SQL table to detect the file coming in, by reading the pattern in the file name or the folder name where it came from (You can store this, since a property in the File adapter is the file path). I want to keep this document simple, so I assume you know about developing pipeline components, there is many resources on the net.

The main code is in the execute method.

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

{

IBaseMessage outMsg;

string recieveFileNamePath = pInMsg.Context.Read("ReceivedFileName", "http://schemas.microsoft.com/BizTalk/2003/file-properties&quot;).ToString();

string originalFileName = Path.GetFileName(recieveFileNamePath);

////////////////////////////////////////////////////////////////////

LogManager.Log("Preparing to Send File to Sharepoint, Send Files To SharePoint: " + recieveFileNamePath, "General");

////////////////////////////////////////////////////////////////////

try

{

//Link the context of the output message to the input message

outMsg = pContext.GetMessageFactory().CreateMessage();

outMsg.Context = pInMsg.Context;

outMsg.AddPart(pInMsg.BodyPartName, pContext.GetMessageFactory().CreateMessagePart(), true);

 

//initialize the FileMetaData

string messageID = pInMsg.Context.Read("InterchangeID", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;).ToString();

FileMetaData fmd = new FileMetaData(originalFileName, messageID);

 

outMsg.Context.Write("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, fmd.NewFileName);

outMsg.Context.Promote("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, fmd.NewFileName);

 

outMsg.Context.Write("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

outMsg.Context.Promote("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

outMsg.Context.Write("OutboundTransportLocation", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, @"wss://" + fmd.SharePointServer + ":" + fmd.SharePointPortNumber + "/" + fmd.SharePointIncomingDocumentPath);

outMsg.Context.Promote("OutboundTransportLocation", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, @"wss://" + fmd.SharePointServer + ":" + fmd.SharePointPortNumber + "/" + fmd.SharePointIncomingDocumentPath);

 

 

string ConfigPropertiesXml = @"<ConfigPropertiesXml>

<PropertyName1>SPSID</PropertyName1>

<PropertySource1>" + fmd.SharePointID + @"</PropertySource1>

<PropertyName2>FileFeedSource</PropertyName2>

<PropertySource2>" + fmd.FileFeedSource + @"</PropertySource2>

<PropertyName3>OriginalFileName</PropertyName3>

<PropertySource3>" + fmd.OriginalFileName + @"</PropertySource3>

<PropertyName4>BizTalkMessageID</PropertyName4>

<PropertySource4>" + fmd.BizTalkMessageID + @"</PropertySource4>

<PropertyName5>Region</PropertyName5>

<PropertySource5>" + fmd.Region + @"</PropertySource5>

<PropertyName6>Country</PropertyName6>

<PropertySource6>" + fmd.Country + @"</PropertySource6>

<PropertyName7>Brand</PropertyName7>

<PropertySource7>" + fmd.Brand + @"</PropertySource7>

</ConfigPropertiesXml>";

 

outMsg.Context.Write("ConfigPropertiesXml", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, ConfigPropertiesXml);

 

 

////////////////////////////////////////////////////////////////////

LogManager.Log("Writing Context Properties, Send Files To SharePoint: NewFilename=" + fmd.NewFileName + " OriginalFileName:" + originalFileName, "General");

////////////////////////////////////////////////////////////////////

 

}

catch (Exception e )

{

LogManager.Log("Exception Occured " + e.Message ,"General");

throw new Exception("Exception Occured: " + e.Message);

 

}

 

return pInMsg;

}

 

Excuse my class FileMetaData, this is a custom class I developed to manage and store metadata, for this article I won’t delve into it but what it does is basically detects the file feed source and does other business rules which is beyond the scope of this article. But here is an outline of it for interest, from the class below, you can see how much more power I get from using pipelines that orchestrations, and I can use non serializable classes etc.

 

This is how your BizTalk project might look like

Remember multiple BizTalk projects can be installed in ONE BizTalk application, just update the project properties here:

Here in the pipeline, I set the values of the CUSTOM COLUMNS in SharePoint in this variable:

http://technet.microsoft.com/en-us/library/aa547920.aspx

Here is how the data looks like in the message during routing once in the message box:

What’s really cool, is you can manually force a message to fail by shutting down the web service or changing the name of it, then look at the suspended instance in BizTalk and learn how to populate the context properties, here is the important ones, I circle them for you. This was all done with the pipeline above!

This is getting EXCITING!!!

You see what we did here is PREPARE the file for SharePoint way before it gets there, we did this on the file receive port:

NOW DO YOU UNDERSTAND WHY A FILENAME FROM A FILE ADAPTER DOES NOT know how to find it’s way to the filename in the WSS adapter? Look above, the pipeline we developed promoted and populated the values in for the context in red!

How the WSS adapter works and various others is using an XML template, in this case ConfigPropertiesXML (THE SQL adapter does the same thing, remember my article about it, you could bypass orchestrations and prepare the SQL adapter by manually writing data to the configpropertiesxml, there are many articles on how this is done in an orchestration, but bugger that, let’s do them in the component level, much faster and you can do allot of dynamic value management by using a configuration database, and yes it is super fast when combined with Enterprise Library Caching Block!)

Configure Dynamic send port to incoming folder on SharePoint

Now all you need to do is create a dynamic send port and configure the filter to grab documents from the file receive port, remember the filter I mentioned!

This filter will do this in the background:

 

Now if you look at the Pipeline we created above the following code gets the filter working!

outMsg.Context.Write("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

outMsg.Context.Promote("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

 

So it should make sense, since allot of people get stuck trying to route documents to a dynamic port, YOU HAVE to promote or set values for properties, and the best way to find out is to look at the subscription filters in the group hub page!

SharePoint Receive Location to pull files from incoming

Then you create a Receive Location to pull from the incoming folder on SharePoint:

But you need to manage the context information and column information; this is a bit trickier! What I did was I needed to know was:

  • Does the default receive adapter store the column information that gets lost if I used the archive feature from the receive location

The answer is yes it does! What I did was I configure the receive adapter to pull messages from SharePoint, and made no subscription for it, to force a failure and looked in the context of the message to see where this info was stored, then I could access it. The easiest way to force a failure was un-enlist my send ports to workflow and archive on SharePoint, so I get a stuck message in BizTalk that was pull from Sharepoint.

And my receive port is running with this configuration:

NOTICE ANOTHER CUSTOM PIPELINE, I show it later, for now, it is IMPORTANT to understand how we get access to column information when pulling a file from sharepoint:

When you pull documents off SharePoint you must specify a view name, I use ALL Documents here since I have a library dedicated to BizTalk polling! Makes document library management easier.

 

I know drop a file, the file will fail in BizTalk as no active subscriptions are running once the file is pulled from SharePoint:

So in SharePoint the file will go here:

Notice the custom column information.

The receive location will pull the file off SharePoint and suspend.

This is the perfect opportunity to check if the COLUMN INFO is in the message context, if it is, we can write a custom pipeline to get it and store it and then another send port to SharePoint can archive it!!!

WE STYLING THERE IS A FIELD!!

This is the data in the field InPropertiesXml!!!!!!!!!

What I do is click it and press Ctrl-C and then put it in notepad and clean it up a bit so it is readable:

 

Ok, so you get the idea, I used the BizTalk admin console to check the message context of a freshly baked SharePoint file and then write a pipeline component to get this data and then prepare it for sending to SharePoint by transferring the data to the configpropertiesXMLl! Like this:

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)

{

IBaseMessage outMsg;

string sharePointFileName = pInMsg.Context.Read("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;).ToString();

////////////////////////////////////////////////////////////////////

LogManager.Log("Preparing to Send File to Workflow and Archive in Sharepoint, Receive Files from SharePoint: " + sharePointFileName, "General");

////////////////////////////////////////////////////////////////////

try

{

//Link the context of the output message to the input message

outMsg = pContext.GetMessageFactory().CreateMessage();

outMsg.Context = pInMsg.Context;

outMsg.AddPart(pInMsg.BodyPartName, pContext.GetMessageFactory().CreateMessagePart(), true);

 

//initialize the FileMetaData

string messageID = pInMsg.Context.Read("InterchangeID", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;).ToString();

 

//Get the MetaData that was pulled off the SharePoint Server, The Adapter stores it in InPropertiesXml context which is not promoted

string inPropertiesXml = outMsg.Context.Read("InPropertiesXml","http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;).ToString();

XmlDocument doc = new XmlDocument();

doc.LoadXml(inPropertiesXml);

FileMetaData fmd = new FileMetaData();

 

 

 

 

foreach (XmlNode node in doc.SelectSingleNode("InPropertiesXml").ChildNodes) //Since all data is stored in a root node structure

{

if (node.Attributes.Count > 0) //Only look at nodes in the XML with attributes, since the metdata is stored in attributes

{

switch (node.Attributes[0].Value)

{

case "Filename":

fmd.NewFileName = node.InnerText;

break;

case "SPSID":

fmd.SharePointID = int.Parse(node.InnerText);

break;

case "FileFeedSource":

fmd.FileFeedSource = node.InnerText;

break;

case "OriginalFileName":

fmd.OriginalFileName = node.InnerText;

break;

case "BizTalkMessageID":

fmd.BizTalkMessageID = messageID; //Assigns a new BizTalk Message ID

break;

case "Region":

fmd.Region = node.InnerText;

break;

case "Country":

fmd.Country = node.InnerText;

break;

}

}

}

 

fmd.SetFileMetaDataArchive(); //Initilise the object

 

outMsg.Context.Write("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, fmd.NewFileName);

outMsg.Context.Promote("Filename", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, fmd.NewFileName);

 

outMsg.Context.Write("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

outMsg.Context.Promote("OutboundTransportType", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, "Windows SharePoint Services");

outMsg.Context.Write("OutboundTransportLocation", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, @"wss://" + fmd.SharePointServer + ":" + fmd.SharePointPortNumber + "/" + fmd.SharePointArchiveDocumentPath);

outMsg.Context.Promote("OutboundTransportLocation", "http://schemas.microsoft.com/BizTalk/2003/system-properties&quot;, @"wss://" + fmd.SharePointServer + ":" + fmd.SharePointPortNumber + "/" + fmd.SharePointArchiveDocumentPath);

 

 

string ConfigPropertiesXml = @"<ConfigPropertiesXml>

<PropertyName1>SPSID</PropertyName1>

<PropertySource1>" + fmd.SharePointID + @"</PropertySource1>

<PropertyName2>FileFeedSource</PropertyName2>

<PropertySource2>" + fmd.FileFeedSource + @"</PropertySource2>

<PropertyName3>OriginalFileName</PropertyName3>

<PropertySource3>" + fmd.OriginalFileName + @"</PropertySource3>

<PropertyName4>BizTalkMessageID</PropertyName4>

<PropertySource4>" + fmd.BizTalkMessageID + @"</PropertySource4>

<PropertyName5>Region</PropertyName5>

<PropertySource5>" + fmd.Region + @"</PropertySource5>

<PropertyName6>Country</PropertyName6>

<PropertySource6>" + fmd.Country + @"</PropertySource6>

<PropertyName7>Brand</PropertyName7>

<PropertySource7>" + fmd.Brand+ @"</PropertySource7>

</ConfigPropertiesXml>";

 

outMsg.Context.Write("ConfigPropertiesXml", "http://schemas.microsoft.com/BizTalk/2006/WindowsSharePointServices-properties&quot;, ConfigPropertiesXml);

 

}

catch (Exception e )

{

LogManager.Log("Exception Occured " + e.Message ,"General");

throw new Exception("Exception Occured: " + e.Message);

 

}

 

return pInMsg;

}

 

SharePoint send location to Archive

Ok, then all you do is create a new dynamic send port with filters to get messages from this SharePoint port.

Here is the filter in the subscriptions page in GHP.

Send location to subscribed to pulled messages and send it to workflow

With same filters as above, but the not dynamic so they look like this, notice NO AND IN THIS, since it is not dynamic!!!!

SharePoint Libraries

Ok, so now in SharePoint, the final check is to see if the columns are populated in the archive folder!

IT SURE IS!

And the incoming library is now empty J

Conclusion

That’s all folks, We covered allot here, but I hope this article will give you a deeper understanding of modifying context properties to effectively setup dynamic routing, you wondering how the column data is populated, I have a database table that the custom file management class calls to detect the country, region and brand info etc, this all comes from the Data Access Layer class, outlines above in the class diagram, this is beyond the scope of this article, but it does prove that you can route documents and dynamically set properties and EVEN transform flat files to XML using a sophisticated custom class which takes text data and transform them to XML, without using the sluggish BizTalk Mapper, maybe in another blog I will cover a universal way to translate flat files to XML used in BizTalk, we will see, hope you enjoyed it!

Here is a sneak at the SQL table used to configure a file feed, all this is set within a pipeline by using caching, data access layer and a file metadata class as well as a custom file translation design pattern to convert data from flat file to xml, it is extremely fast, a 10MB file will take 2-3 seconds to process in a pipeline, if this is done in an orchestration it would take much longer from 30 seconds to minutes. What I like about a custom file mapper, is I have total contol over encoding.

USE [FileManagement]

GO

/****** Object: Table [dbo].[FileFeeds] Script Date: 06/15/2008 12:53:34 ******/

SET ANSI_NULLS ON

GO

SET QUOTED_IDENTIFIER ON

GO

SET ANSI_PADDING ON

GO

CREATE TABLE [dbo].[FileFeeds](

    [Id] [bigint] IDENTITY(1,1) NOT NULL,

    [FileFeedSource] [nvarchar](255) NOT NULL,

    [StringIdentifierInFileName] [nvarchar](50) NULL,

    [FolderName] [nvarchar](255) NOT NULL,

    [FileType] [nvarchar](50) NOT NULL,

    [Delimiter] [char](1) NULL,

    [Encoding] [nvarchar](25) NOT NULL,

    [SharePointServer] [nvarchar](100) NOT NULL,

    [SharePointPortNumber] [int] NOT NULL,

    [SharePointIncomingDocumentPath] [nvarchar](255) NOT NULL,

    [SharePointArchiveDocumentPath] [nvarchar](255) NULL,

    [ContainMultipleSources] [bit] NOT NULL,

    [DefaultSourceName] [nvarchar](255) NULL,

    [FileMappingXML] [xml] NULL,

    [CreationDate] [datetime] NOT NULL CONSTRAINT [DF_FileFeeds_CreationDate] DEFAULT (getdate()),

    [Region] [varchar](50) NULL,

    [Country] [varchar](50) NULL,

    [Brand] [varchar](255) NULL,

CONSTRAINT [PK_FileFeeds] PRIMARY KEY NONCLUSTERED

(

    [Id] ASC

)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

) ON [PRIMARY]

 

GO

SET ANSI_PADDING OFF

 

Seems to me Microsoft rushed the development of the WSS adapter, else they would have noticed that the static port does not retain column info when you use the intrinsic archive feature! Maybe they will fix this, who knows, a Feature or a Bug?