Wednesday, June 29, 2005

Biztalk: Delete ReceiveLocation using WMI and VBScript

The default samples that are shipped with the Biztalk SDK do not contain code to delete a ReceiveLocation. Here is something I've written that will delete a ReceiveLocation, specified on the command line.


Please note that:
  • this is a very-very basic script (e.g. no checks on primary ReceiveLocation)

  • I know nothing about VBScript, so if you want parts of it nominated as a WTF, go ahead, make my day ;)


Option Explicit

 

RemoveRecLoc

Sub RemoveRecLoc()

    Dim objArgs: Set objArgs = WScript.Arguments

 

    'error handling is done by explicity checking the err object rather than using

    'the VB ON ERROR construct, so set to resume next on error.

    'on error resume next

 

    Dim strReceiveLocation   

    strReceiveLocation = objArgs(0)

 

    Dim InstSet, Inst

    set InstSet = GetObject ("winmgmts:\root\MicrosoftBizTalkServer").InstancesOf("MSBTS_ReceiveLocation")

 

    Dim objReceivePort

 

    'Check for error condition before continuing.

    If Err <> 0    Then

        PrintWMIErrorThenExit Err.Description, Err.Number

    End If

 

    'Report on number of receive locations found and list each one.

    wscript.echo "A Total of " & InstSet.Count & " Receive Locations were found."

    If InstSet.Count > 0 Then

        For Each Inst In InstSet

            If Inst.Name = strReceiveLocation Then

                wscript.echo "Receive Location Name  : " & Inst.Name

                Inst.Delete_()

                wscript.echo "  Deleted ..."

            End If           

        next

    End If

 

End Sub

 

'This subroutine deals with all errors using the WbemScripting object.  Error descriptions

'are returned to the user by printing to the console.

Sub    PrintWMIErrorThenExit(strErrDesc, ErrNum)

    On Error Resume    Next

    Dim    objWMIError    : Set objWMIError =    CreateObject("WbemScripting.SwbemLastError")

 

    If ( TypeName(objWMIError) = "Empty" ) Then

        wscript.echo strErrDesc & " (HRESULT: "    & Hex(ErrNum) & ")."

    Else

        wscript.echo objWMIError.Description & "(HRESULT: "    & Hex(ErrNum) & ")."

        Set objWMIError    = nothing

    End    If

 

    'bail out

    wscript.quit 0

End    Sub

Friday, June 17, 2005

Mime Decoder Pipeline Component in Biztalk 2004 (the solution)

I promised to publish a solution to read the .eml files. This solution works for both Biztalk 2004 and Biztalk 2004 SP1 installations.



Step 1: Create a custom decoder component

You can download a wizard that will create any pipeline component here. Use this to create your decoder component. Make sure you have the following design time properties defined:


  • FromRegex: this will contain the regular expression to find the sender

  • FromMatch: this will contain the index of the Group and Capture

  • ToRegex: this will contain the regular expression to find the receiver

  • ToMatch: this will contain the index of the Group and Capture

  • SubjectRegex: this will contain the regular expression to find the subject

  • SubjectMatch: this will contain the index of the Group and Capture

  • PropertyNamespace: this will contain the namespace of the properties we're promoting (don't forget to create your property schema with MessageContextPropertyBase properties)


If you're not familiar with regular expressions, you can use the following expression to match the sender:
(?i)from:\s?([^\n\r]*)
If you apply this expression to the incoming message, the sender is found in Group 1, Capture 0. You can use this tool to test your regular expressions.



Step 2: Fill the gaps
Create a private member that will contain Biztalk's default decoder:

        private MIME_SMIME_Decoder containedDecoder = new MIME_SMIME_Decoder();


Some additional members well need:

        private RegExpMatcher fromMatcher = null;

        private RegExpMatcher toMatcher = null;

        private RegExpMatcher subjectMatcher = null;


We'll see the implementation of the RegExpMatcher later on.
Initialize the decoder:

        public void InitNew()

        {

            containedDecoder.InitNew();

        }


Make sure the component is able to load its configuration:

        public virtual void Load(IPropertyBag pb, int errlog)

        {

            string regExp = ReadPropertyBag(pb, "FromRegex") as string;

            string regExpMatch = ReadPropertyBag(pb, "FromMatch") as string;

            fromMatcher = GetRegExpMatcher(regExp, regExpMatch);

            ...

        }


        private RegExpMatcher GetRegExpMatcher(string regExp, string regExpMatch)

        {

            if (StringUtils.IsNotEmpty(regExp) && StringUtils.IsNotEmpty(regExpMatch))

            {

                return new RegExpMatcher(regExp, regExpMatch);

            }

            return null;

        }


Make sure the component is able to save its configuration:

        public virtual void Save(IPropertyBag pb, bool fClearDirty, bool fSaveAllProperties)

        {

            WritePropertyBag(pb, "FromRegex", fromMatcher.RegExp);

            WritePropertyBag(pb, "FromMatch", fromMatcher.RegExpMatch);

            ...


Additional properties:

        public string FromRegex

        {

            get

            {

                if (fromMatcher != null)

                {

                    return fromMatcher.RegExp;   

                }

                return null;

            }

            set

            {

                GetRegExpMatcher(ref fromMatcher).RegExp = value;

            }

        }

 

        public string FromMatch

        {

            get

            {

                if (fromMatcher != null)

                {

                    return fromMatcher.RegExpMatch;

                }

                return null;

            }

            set

            {

                GetRegExpMatcher(ref fromMatcher).RegExpMatch = value;

            }

        }


        private RegExpMatcher GetRegExpMatcher(ref RegExpMatcher aRegExpMatcher)

        {

            if (aRegExpMatcher == null)

            {

                aRegExpMatcher = new RegExpMatcher();

            }

            return aRegExpMatcher;

        }



Step 3: Create the Execute method

        public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)

        {

            /*

            * Get the original stream for the incoming message's

            * body part.

            */

            Stream stream = inmsg.BodyPart.GetOriginalDataStream();

 

            /*

            * Now, read the stream in blocks of 1024 bytes, until we

            * find the mime header. If the header is found at position

            * X, reset the stream to position X. The contained mime

            * decoder will start reading from this point forward.

            */

            int mimeHeaderIdx = -1;

            int num = 1;

            byte[] buffer = null;

            StringBuilder text = new StringBuilder();

            while (mimeHeaderIdx < 0 && num > 0)

            {

                buffer = new byte[0x400];

                num = stream.Read(buffer, 0, 0x400);

                ASCIIEncoding encoding = new ASCIIEncoding();

                text.Append(encoding.GetString(buffer, 0, num).ToUpper(CultureInfo.InvariantCulture));

                mimeHeaderIdx = text.ToString().IndexOf(MIME_HEADER);

                if (mimeHeaderIdx >= 0)

                {

                    stream.Seek(mimeHeaderIdx, SeekOrigin.Begin);

                }

            }

            IBaseMessage outmsg = containedDecoder.Execute(pc, inmsg);

            try

            {

                outmsg.Context.Promote("From", propertyNamespace, fromMatcher.Match(text.ToString()));

                outmsg.Context.Promote("To", propertyNamespace, toMatcher.Match(text.ToString()));

                outmsg.Context.Promote("Subject", propertyNamespace, subjectMatcher.Match(text.ToString()));

            }

            catch (Exception e)

            {

                throw e;

            }

            return outmsg;

        }



Step 4: Create the RegExpMatcher component
This is the class that will ease our use of regular expressions:

    public class RegExpMatcher

    {

        private int group = -1;

        private int capture = -1;

        private string regExp = null;

        private string regExpMatch = null;

        private DelimitedString delimitedString = null;

 

        public RegExpMatcher()

        {

        }

 

        public RegExpMatcher(string aRegExp, string aRegExpMatch)

        {

            if (StringUtils.IsEmpty(aRegExp))

            {

                throw new ApplicationException("Regular expression was empty.");               

            }

            if (StringUtils.IsEmpty(aRegExpMatch))

            {

                throw new ApplicationException("Regular expression match was empty.");               

            }

            regExp = aRegExp;

            regExpMatch = aRegExpMatch;

            CreateGroupAndCapture(regExpMatch);

        }

 

        private void CreateGroupAndCapture(string aRegExpMatch)

        {

            delimitedString = new DelimitedString(aRegExpMatch);

            if(delimitedString.Parts.Length < 2)

            {

                throw new ApplicationException(string.Format(@"{0} should be a delimited string having 2 parts.", regExpMatch));

            }

            group = IntUtils.ParseInt(delimitedString.Parts[0]);

            capture = IntUtils.ParseInt(delimitedString.Parts[1]);       

        }

 

        public string RegExp

        {

            get

            {

                return regExp;

            }

            set

            {

                regExp = value;

            }

        }

 

        public string RegExpMatch

        {

            get

            {

                return regExpMatch;

            }

            set

            {

                regExpMatch = value;

                CreateGroupAndCapture(regExpMatch);

            }

        }

 

        public int Group

        {

            get

            {

                if(group < 0)

                {

                    throw new ApplicationException(string.Format("Group should be an integer, found {0}.", delimitedString.Parts[0]));

                }

                return group;

            }

        }

 

        public int Capture

        {

            get

            {

                if(capture < 0)

                {

                    throw new ApplicationException(string.Format("Capture should be an integer, found {0}.", delimitedString.Parts[1]));

                }

                return capture;

            }

        }

 

        public string Match(string part)

        {

            Match m = Regex.Match(part, regExp, RegexOptions.Singleline);

            if (m.Success)

            {

                object found = m.Groups[this.Group].Captures[this.Capture];

                if (found != null)

                {

                    return found.ToString();

                }

            }

            return "";

        }

    }



Step 5: Build and deploy your component
Now you're ready to build and deploy the component. Just copy the resulting .dll to C:\Program Files\Microsoft BizTalk Server 2004\Pipeline Components and you're set.

Step 6: Use your component
After you successfully deployed your component, you're ready to use it:

  1. Add the component to your toolbox in Visual Studio

  2. Create a new Biztalk project

  3. Add a pipeline

  4. Drag your component on the decode stage of your pipeline

  5. Configure your component

  6. Test

The Daemon, the GNU and the Penguin (12)

Read all about it here.
Very interesting series. I've read to chapter 4 the other night ...

Wednesday, June 15, 2005

Mime Decoder Pipeline Component in Biztalk 2004

This is so funny. I've been struggling for a while to be able to read .eml files through Biztalk. Seems the the MIME decoder that ships with Biztalk 2004 tries to find "MIME-VERSION" in the first 1024 bytes of the .eml-file. The problem is that, in my file, the MIME-VERSION-string was near byte 1324 (or so). So the decoder tells me the file is not valid. Seems this problem is resolved in SP1. In a next post, I'll post some code ...

Wednesday, June 01, 2005

Biztalk: RIP

Biztalk died on me today after choking on an 18 MB IDOC (= flat file message from SAP) message.

A catastrophic failure occurred in the BizTalk service.
The service will shutdown and auto-restart in 1 minute.
If the database is still unavailable, this cycle will be repeated.

Error message: Parameter is incorrect
Error source: System

BizTalk host name: BizTalkServerApplication
Windows service name: BTSSvc{940AAE5E-0C6F-401E-83B2-D00BFF7792BB}

I already ran the Config Framework wizard, but untill now, no joy :(

 


Update!


I found why I was getting the above error. It seems one of the IDOCS I was using for testing lost some trailing spaces in one of its segments. This resulted in the above error ...