Today, I was driven crazy by the Pipeline Editor in Visual Studio. I dropped two different pipeline components in it, then when I closed and re-opened it they would show the same name.

When I opened the .btp file in a text editor it looked okay. So it was apparently a problem displaying the user interface. Also, the run time was having a problem, as the pipeline component I was trying to test wasn’t being executed. My guess it was running the first one twice.

Finally, I found my issue. Yes, it was my fault!

I copied a pipeline line project from an existing one to a new one. I changed the GUID, but I failed to change the class name (and the namespace was identical). So the pipeline editor gets very confused in this scenario.

I had “public class ArbitraryXPathPropertyHandlerComp” in both C# pipeline components. I changed each to have names specific to their functions (as shown below, the name “SetAS2ToPromotedField”).


public class SetAS2ToPromotedField :
BaseCustomTypeDescriptor,
IBaseComponent,
Microsoft.BizTalk.Component.Interop.IComponent,
IPersistPropertyBag,
IComponentUI
{

The .NET System.Message class allow for isHTMLBody to be true or false. Setting to true allows you to use HTML to make fields bolds, to use headers, to change colors, fonts, etc… But sometimes you prefer just a text email. In an HTML email, you cna use

<br /> (break HTML) to create new lines.  In a non-HTML, you can use \r\n or System.EnvironmentNewLine, but only if you set a couple of other parameters (BodyEncoding , BodyTransferEncoding). 

<h4>C# Class Library</h4>
<code><pre>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Mail;

namespace Demo.Common.Helpers
{
    public class Mail
    {
        //send an email alert
        public static void sendEmail(string recipient, string subject, string message, bool isBodyHTML)
        {
            try
            {
                // Could make these two parms or config file parameters 
                string from = "noreply@YourDomain.com";  
                string host = "smtp.YourDomain.com";
                int smtpPort = 25;


                //append the System/Enviornment Name to the email subject
                //string systemEnvironmentName = Config.getConfigValue("systemName");
                //subject = subject.Trim() + " [" + systemEnvironmentName.Trim() + "]";

                MailMessage mail = new MailMessage();
                mail.IsBodyHtml = isBodyHTML;
                if (!isBodyHTML)
                {
                    // https://stackoverflow.com/questions/5020469/how-to-create-a-multi-line-body-in-c-sharp-system-net-mail-mailmessage
                    // next two lines to allow for \r\n new lines in text email 
                    mail.BodyEncoding = Encoding.UTF8;
                    mail.BodyTransferEncoding = System.Net.Mime.TransferEncoding.EightBit;
                }
                SmtpClient client = new SmtpClient();

                //set the email address from address
                MailAddress fromEmail = new MailAddress(from);
                mail.From = fromEmail;

                //split the recipient email address by ';' and add
                foreach (var address in recipient.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    mail.To.Add(address);
                }



                client.Port = smtpPort;
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.UseDefaultCredentials = false;
                client.Host = host;
                mail.Subject = subject;
                mail.Body = message;

                client.Send(mail);
            }
            catch (SmtpException ex)
            {
                //LoggingToFiles.logHelperError("sendEmail", ex.Message);
                throw new Exception("SMTP.Exception in function sendEmail: " + ex.Message);
            }
            catch (Exception ex)
            {
                //LoggingToFiles.logHelperError("sendEmail", ex.Message);
                throw new Exception("Exception in function sendEmail: " + ex.Message);
            }
        }

    }


}

Tester Program

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Demo.Common.ConsoleTests
{
    class TesterProgram
    {
        static void Main(string[] args)
        {

            // Demo non-HTML 
            Demo.Common.Helpers.Mail.sendEmail("youremail@abc.com", "Test line break", "Line1\r\nLine2\r\nLine3", false);

            // Demo HTML 
            Demo.Common.Helpers.Mail.sendEmail("youremail@abc.com", "Test line break", "<h4>Line1</h4><br />Line2<br />Line3", true);

            Console.WriteLine("\n\nPress enter to end:");
            Console.ReadLine();
        }
        
    }
}

The trick for non-HTML was found here on StackOverflow

NOTE: I highly recommend that you create a folder in Task Scheduler for your company name. Do not mix your scheduled tasks with those that are there under various Windows features.

Open Windows “Task Scheduler”, and create a task. Give it a name and a schedule. The part most people have questions about is the “Action Tab”.

For the Program/Script, just specify “Powershell” (it doesn’t need any directory name), then in the “add arguments” specify something like this:

-command ". 'e:\Scripts\Scheduled\PurgeOldBizTalkArchiveFiles.ps1'"

This is illustrate in the screen shot below:

You can also pass parameters from the scheduled job to the PowerShell, see
StackOverflow How to Handle Command Line Arguments

Frequently, we get monitor alerts that the disk is under 20% full.

The question is usually what files are large that can be deleted. I often use the fantastic utility WinDirStat. And there are PowerShell commands to show you the top files on the disk.

But typically, some of the largest files are the paging file, or other system files that cannot be deleted. So I wanted a script to show me the largest of all the other files.

The Get-ChildItem does have an -Exclude parm, but I could’t figure out if it could take multiple directory names. So I broke down a script into 3 parts. First, get the array of files. Second, build a second array of files that are in all but the list of directories I wanted to exclude. Then third, sort that and select the Top 10 or 20 flies, then finally show the output (including the file size in Megabytes and the file creation date/time).

I’m not much of one for one-liner PowerShell commands. I would rather expand it out for more flexibility in the future.

Program Code

cls

$startingDirectory = "c:\"
$numFilesToShow = 20 

$fmtDateTime = $(get-date -f "MM/dd/yyyy HH:mm:ss")
Write-Host “Start: $fmtDateTime”

$files = Get-ChildItem $startingDirectory -file -recurse 
$files2 = @()

# eliminate any folders we can't do much about 
foreach ($file in $files) 
{
   # add any directory to exclude here (with another -or statement) 
   if (!($file.FullName.StartsWith("C:\Windows") -or $file.FullName.StartsWith("C:\Program Files") ) )
   {
      #note the "Not" sign in the if statement above,     
      #we keep in files Not in that list of directories 
      $files2 += $file
   }
} 

# Now sort after removing above folders 
$files3 = $files2 | sort-object Length  -descending | select-object -first $numFilesToShow


foreach ($file in $files3) 
{
   $size = $file.Length 
   $roundedSize = [Math]::Round($size)
   Write-Host "Name:$($file.FullName)  Length:$roundedSize MB  $($file.CreationTime)" 
} 


$fmtDateTime = $(get-date -f "MM/dd/yyyy HH:mm:ss")
Write-Host “END: $fmtDateTime" 

The program above could easily be enhanced to only return files over a given threshold (size).

Example Output

 
Start: 04/20/2020 10:20:22
Name:C:\inetpub\logs\LogFiles\FTPSVC2\u_ex200205.log  Length:2033873 MB  02/04/2020 18:01:07
Name:C:\inetpub\logs\LogFiles\FTPSVC2\u_ex200206.log  Length:1986952 MB  02/05/2020 18:04:29
Name:C:\inetpub\logs\LogFiles\FTPSVC2\u_ex200204.log  Length:1964928 MB  02/03/2020 18:00:19
Name:C:\inetpub\logs\LogFiles\FTPSVC2\u_ex200127.log  Length:1926786 MB  01/26/2020 18:01:43
Name:C:\inetpub\logs\LogFiles\FTPSVC2\u_ex200130.log  Length:1921223 MB  01/29/2020 18:03:02

So you can see from above, we have some log files from IIS/FTP that could potentially be deleted.

The above also demonstrates the following:
1) How to create an empty array in PowerShell
2) How to add an object to an existing array
3) How to round an number to the nearest integer in PowerShell (using the .NET Math.Round)
4) How to show the date/time a file was created (CreationTime)

How do you create an identical copy of a BizTalk ReceiveLocation within a Receive Port? Well, it cannot be 100% identical. The name will of course have to change, the URI will have to be unique as well.

I recently had a case where we wanted the same orchestration to run on two different databases that had the same structure. The ReceiveLocation used WCF-SQL typed polling. I wanted to copy the original ReceiveLocation, and add a new one, except changing just a few minor things.

I wrote a PowerShell to do this. In the prior blog, I also created a “BizTalk Send Port Duplicator”.

The script builds a binding file to modify the ReceivePort and optionally imports it.

Receive Locations are children of the Receive Port, and so in the binding file, you cannot just add a Receive location by itself, You must specify the whole Receive Port with it’s other Receive Locations plus the new one.

You can edit it on disk and manually import yourself if you want. You might need to change some of the other options (such as directory name if using the FILE adapter), or SQL Stored Proc if using the WCF-SQL adapter. You can do that in the disk file, or let the script import the bindings, and then make the changes in BizTalk Admin Console. Set the variable $ynImportNewBinding to “Y” or “N” based on your preference.

Before running the script, find the eight parameters (or just variables actually), and change them in the script below:

#SET THESE EIGHT PARAMETERS
$applicationName = “TMW.Integration”
$sqlServer = “DLBizTalkDev1” #where the BizTalkMgmtDB is
$duplicateReceivePortName = “rp_ABC”
$duplicateReceiveLocation = “rl_ABC_Division1”
$newReceiveLocationName = “rl_ABC_Division2”
#Use the next two fields to change the URI or other patterns
$rcvLocChangeFrom = “Division1”
$rcvLocChangeTo = “Division2”
$ynImportNewBinding = “Y” #set to “Y” to auto-import the new SendPort, set to “N” if you want to edit it on disk

$BTSTaskFilePath = “e:\BizTalkServer2016\BTSTask.exe”

<#

       Author: Neal Walters 
         Date: 04/15/2020 
  Description: Description: Duplicate a BizTalk ReceiveLocation 
         
      Details: User has to set five variables/parameters 
               (search for #SET THESE FIVE PARAMETERS). 

               Logic is as follows: 
               Export bindings, make a copy to work file, 
               remove each "collection", 
               pull out one ReceivePort, 
               duplicate the ReceiveLocation within it, put it back in, 
               and import new SendPort bindings. 
#>



function removeSection ($filename, $section) 
{
   $contents = [System.IO.File]::ReadAllText($filename)

   #Handle EmptyTag 
   $contents2 = $contents.Replace("<$section />", "") 
   if ($contents2 -eq $contents)
      {
           $contents = $contents2 

           #Handle non-EmptyTag
           $startPos = $contents.IndexOf("<$section") 
           $endPos = $contents.IndexOf("/$section>") 
           $endPos = $endPos + $section.Length 
           if ($endPos -gt $startPos) 
           {
              #Write-Host "$section : startPos=$startPos endPos=$endPos" 
              $contentsBefore = $contents.Substring(0,$startPos-1)
              $contentsAfter = $contents.Substring($endPos + 2) 
              $contents = $contentsBefore + $contentsAfter
           }
       }
       else 
       {
         $contents = $contents2 
       }
       [System.IO.File]::WriteAllText($filename, $contents) 
}

function AddReceivePort ($filename, $sendPortXML) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   $contents = $contents.Replace("</Timestamp>","</Timestamp>`n`n<ReceivePortCollection>`n$sendPortXML`n</ReceivePortCollection>`n")
   [System.IO.File]::WriteAllText($filename, $contents) 
}


function dupReceiveLocation ($filename, $receiveLocName, $receiveLocNewName, $rcvLocChangeFrom, $rcvLocChangeTo ) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   $startPos = $contents.IndexOf("<ReceiveLocation Name=`"$receiveLocName`"") 
   $endPos = $contents.IndexOf("</ReceiveLocation>",$startPos) 
   $extractLength = $endPos - $startPos + $section.Length + 18
   $rcvLocBinding = $contents.Substring($startPos, $extractLength) 
   $rcvLocBinding = $rcvLocBinding.Replace($receiveLocName, $receiveLocNewName) 
   $contents = $contents.Replace("</ReceiveLocations>","`n$rcvLocBinding`n</ReceiveLocations>")
   $contents = $contents.Replace($rcvLocChangeFrom, $rcvLocChangeTo)
   #we don't want to copy the flag that says this is the primary Receive Location 
   $contents = $contents.Replace("<Primary>true</Primary>","<Primary>false</Primary>")
   [System.IO.File]::WriteAllText($filename, $contents) 
}

function getReceivePort ($filename, $rcvPortName) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   $startPos = $contents.IndexOf("<ReceivePort Name=`"$rcvPortName`"") 
   $endPos = $contents.IndexOf("</ReceivePort",$startPos) 
   $extractLength = $endPos - $startPos + $section.Length + 14
   $endPos = $endPos + $section.Length 
   #Write-Host "$section : startPos=$startPos endPos=$endPos Length=$extractLength" 
   $contentsReceivePort = $contents.Substring($startPos, $extractLength) 
   return $contentsReceivePort 
}

function fileReplace ($filename, $searchString, $replaceString) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   #Write-Host "fileReplace: Read Len of contents= $($contents.length)" 
   $contents = $contents.Replace($searchString, $replaceString) 
   #Write-Host $contents 
   #Write-Host "fileReplace: Write Len of contents= $($contents.length) from $filename" 
   [System.IO.File]::WriteAllText($filename, $contents) 
}

cls
$ErrorActionPreference = "Stop"
$formatDate = Get-Date -Format "yyyy-MM-dd hh:mm:ss"
Write-Host “Start: DateTime= $formatDate”

#------------------------------------------------------
#SET THESE EIGHT PARAMETERS
$applicationName = "TMW.Integration" 
$sqlServer = "DLBizTalkDev1"    #where the BizTalkMgmtDB is 
$duplicateReceivePortName = "rp_ABC" 
$duplicateReceiveLocation = "rl_ABC_Division1"
$newReceiveLocationName   = "rl_ABC_Division2"
#Use the next two fields to change the URI or other patterns 
$rcvLocChangeFrom = "Division1" 
$rcvLocChangeTo = "Division2" 
$ynImportNewBinding = "Y"   #set to "Y" to auto-import the new SendPort, set to "N" if you want to edit it on disk 
#------------------------------------------------------

$formatDateTimeForFile = get-date -f yyyy_MM_dd
$username = $env:username
$exportFileNameOrig = "c:\Users\$username\AppData\Local\BizTalk\Binding Files\$applicationName.Binding_$formatDateTimeForFile.xml" 
$exportFileNameWork = "c:\Users\$username\AppData\Local\BizTalk\Binding Files\$applicationName.Binding_$($formatDateTimeForFile)_ReceivePort.xml" 

Write-Host $exportFileNameOrig
Write-Host $exportFileNameWork

#------------------------------------------------------

#$BTSTaskResults = &"$BTSTaskFilePath" ImportBindings /ApplicationName:$applicationName /Source:"$($file.FullName)" /Server:"$SqlServer" /Database:BizTalkMgmtDb 
$BTSTaskResults = &"$BTSTaskFilePath" ExportBindings /ApplicationName:$applicationName /Destination:"$exportFileNameOrig" /Server:"$SqlServer" /Database:BizTalkMgmtDb 
copy-item -Path $exportFileNameOrig -Destination $exportFileNameWork
Write-Host "============================================="
Write-Host "EXPORT BINDINGS RESULTS:" 
Write-Host $BTSTaskResults
Write-Host "============================================="

#call functions 
removeSection $exportFileNameWork "ModuleRefCollection" 
removeSection $exportFileNameWork "DistributionListCollection" 
removeSection $exportFileNameWork "SendPortCollection" 
fileReplace   $exportFileNameWork "<PartyCollection xsi:nil=`"true`" />" ""
$receivePortBindings = getReceivePort $exportFileNameWork $duplicateReceivePortName
#$receivePortBindings = $receivePortBindings.Replace($duplicateSendPort, $newSendPortName) 
Write-Host "receivePortBindings.Len=$($receivePortBindings.Length)" 
removeSection $exportFileNameWork "ReceivePortCollection" 
addReceivePort $exportFileNameWork $receivePortBindings 
dupReceiveLocation $exportFileNameWork $duplicateReceiveLocation $newReceiveLocationName $rcvLocChangeFrom $rcvLocChangeTo 


Write-Host "FileName: $exportFileNameWork" 
if ($ynImportNewBinding -eq "Y")   
   {
        $BTSTaskResults = &"$BTSTaskFilePath" ImportBindings /ApplicationName:$applicationName /Source:"$exportFileNameWork" /Server:"$SqlServer" /Database:BizTalkMgmtDb 
        Write-Host "============================================="
        Write-Host "IMPORT BINDINGS RESULTS:" 
        Write-Host $BTSTaskResults
        Write-Host "============================================="
   }
else 
    {
       Write-Host "You have chose to manually edit the file (not improted into Biztalk)" 
    }

$formatDate = Get-Date -Format "yyyy-MM-dd hh:mm:ss"
Write-Host “End : DateTime= $formatDate”


Here are references to other tools that do similar functions. I like the idea of having a simple script that I can customize as needed and I know exactly what it’s doing.

Sandro Pereira Port Multiplier Tool (Aug 2019)
Richard Seroter’s BizTalk Send Port Duplicator (from 2007)

How do you create an identical copy of a BizTalk SendPort except for the name?

It’s not uncommon for me to want create a duplicate of a SendPort, especially if it’s a more complicated SendPort like WCF or SQL with lots of parameters.

I finally wrote a PowerShell to do this. Maybe later, I’ll do the same for Receive Locations.

This script also demonstrates how to call BTSTask from PowerShell, and capture the results into a string. BtsTask is the command line tool that allows you to import and export bindings.

The script builds a binding file to add the new SendPort and optionally imports it. You can edit it on disk and manually import yourself if you want. You probably will change some of the other options (such as directory name if using the FILE adapter). You can do that in the disk file, or let the script import the bindings, and then make the changes in BizTalk Admin Console. Set the variable $ynImportNewSendPort to “Y” or “N” based on your preference.

Before running the script, find the five parameters (or just variables actually), and change them in the script below:

#SET THESE FIVE PARAMETERS
$applicationName = “MyCoolApplication”
$sqlServer = “YourServerName” #where the BizTalkMgmtDB is
$duplicateSendPortName = “sp_Archive”
$newSendPortName = “sp_Archive2”
$ynImportNewSendPort = “Y” #set to “Y” to auto-import the new SendPort, set to “N” if you want to edit it on disk

$BTSTaskFilePath = “e:\BizTalkServer2016\BTSTask.exe”

<#

       Author: Neal Walters 
         Date: 04/15/2020 
  Description: Duplicate a BizTalk SendPort
         
      Details: User has to set five variables/parameters 
               (search for #SET THESE FIVE PARAMETERS). 

               Logic is as follows: 
               Export bindings, make a copy to work file, 
               remove each "collection", 
               pull out one SendPort, change it, put it back in, 
               and import new SendPort bindings. 
#>



function removeSection ($filename, $section) 
{
   $contents = [System.IO.File]::ReadAllText($filename)

   #Handle EmptyTag 
   $contents2 = $contents.Replace("<$section />", "") 
   if ($contents2 -eq $contents)
      {
           $contents = $contents2 

           #Handle non-EmptyTag
           $startPos = $contents.IndexOf("<$section") 
           $endPos = $contents.IndexOf("/$section>") 
           $endPos = $endPos + $section.Length 
           if ($endPos -gt $startPos) 
           {
              #Write-Host "$section : startPos=$startPos endPos=$endPos" 
              $contentsBefore = $contents.Substring(0,$startPos-1)
              $contentsAfter = $contents.Substring($endPos + 2) 
              $contents = $contentsBefore + $contentsAfter
           }
       }
       else 
       {
         $contents = $contents2 
       }
       [System.IO.File]::WriteAllText($filename, $contents) 
}

function AddSendPort ($filename, $sendPortXML) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   $contents = $contents.Replace("</Timestamp>","</Timestamp>`n`n<SendPortCollection>`n$sendPortXML`n</SendPortCollection>`n")
   [System.IO.File]::WriteAllText($filename, $contents) 
}

function getSendPort ($filename, $sendPortName) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   $startPos = $contents.IndexOf("<SendPort Name=`"$sendPortName`"") 
   $endPos = $contents.IndexOf("</SendPort",$startPos) 
   $extractLength = $endPos - $startPos + $section.Length + 11 
   $endPos = $endPos + $section.Length 
   #Write-Host "$section : startPos=$startPos endPos=$endPos Length=$extractLength" 
   $contentsSendPort = $contents.Substring($startPos, $extractLength) 
   return $contentsSendPort 
}

function fileReplace ($filename, $searchString, $replaceString) 
{
   $contents = [System.IO.File]::ReadAllText($filename)
   #Write-Host "fileReplace: Read Len of contents= $($contents.length)" 
   $contents = $contents.Replace($searchString, $replaceString) 
   #Write-Host $contents 
   #Write-Host "fileReplace: Write Len of contents= $($contents.length) from $filename" 
   [System.IO.File]::WriteAllText($filename, $contents) 
}



cls
$ErrorActionPreference = "Stop"
$formatDate = Get-Date -Format "yyyy-MM-dd hh:mm:ss"
Write-Host “Start: DateTime= $formatDate”

#------------------------------------------------------
#SET THESE FIVE PARAMETERS
$applicationName = "MyCoolApplication" 
$sqlServer = "YourServerName"    #where the BizTalkMgmtDB is 
$duplicateSendPortName = "sp_Archive" 
$newSendPortName       = "sp_Archive2" 
$ynImportNewSendPort = "Y"   #set to "Y" to auto-import the new SendPort, set to "N" if you want to edit it on disk 
#------------------------------------------------------

$formatDateTimeForFile = get-date -f yyyy_MM_dd
$username = $env:username
$exportFileNameOrig = "c:\Users\$username\AppData\Local\BizTalk\Binding Files\$applicationName.Binding_$formatDateTimeForFile.xml" 
$exportFileNameWork = "c:\Users\$username\AppData\Local\BizTalk\Binding Files\$applicationName.Binding_$($formatDateTimeForFile)_NewSendPort.xml" 

Write-Host $exportFileNameOrig
Write-Host $exportFileNameWork

#------------------------------------------------------

$BTSTaskResults = &"$BTSTaskFilePath" ExportBindings /ApplicationName:$applicationName /Destination:"$exportFileNameOrig" /Server:"$SqlServer" /Database:BizTalkMgmtDb 
copy-item -Path $exportFileNameOrig -Destination $exportFileNameWork
Write-Host "============================================="
Write-Host "EXPORT RESULTS:" 
Write-Host $BTSTaskResults
Write-Host "============================================="

#call functions 
removeSection $exportFileNameWork "ModuleRefCollection" 
removeSection $exportFileNameWork "DistributionListCollection" 
removeSection $exportFileNameWork "ReceivePortCollection" 
fileReplace   $exportFileNameWork "<PartyCollection xsi:nil=`"true`" />" ""
$sendPortBindings = getSendPort $exportFileNameWork $duplicateSendPortName
$sendPortBindings = $sendPortBindings.Replace($duplicateSendPortName , $newSendPortName) 
Write-Host "sendPortBindings.Len=$($sendPortBindings.Length)" 
removeSection $exportFileNameWork "SendPortCollection" 
addSendPort $exportFileNameWork $sendPortBindings 


Write-Host "FileName: $exportFileNameWork" 
if ($ynImportNewSendPort -eq "Y")   
   {
        $BTSTaskResults = &"$BTSTaskFilePath" ImportBindings /ApplicationName:$applicationName /Source:"$exportFileNameWork" /Server:"$SqlServer" /Database:BizTalkMgmtDb 
        Write-Host "============================================="
        Write-Host "IMPORT RESULTS:" 
        Write-Host $BTSTaskResults
        Write-Host "============================================="
   }
else 
    {
       Write-Host "You have chose to manually edit the file (not imported into Biztalk)" 
    }

$formatDate = Get-Date -Format "yyyy-MM-dd hh:mm:ss"
Write-Host “End : DateTime= $formatDate”

Here are references to other tools that do similar functions. I like the idea of having a simple script that I can customize as needed and I know exactly what it’s doing.

Sandro Pereira Port Multiplier Tool (Aug 2019)
Richard Seroter’s BizTalk Send Port Duplicator (from 2007)

In BizTalk, you can export and import and export bindings to/from the BizTalk Database using the BizTalk Administration Console and/or BTSTask.exe (command line utility).

The binding file has five major sections:

1) ModuleRefCollection (Orchestrations a.k.a. “Services” and Orchestration bindings)
2) SendPortCollection (Send Ports)
3) DistributionListCollection (Send Port Groups)
4) ReceivePortCollection (Receive Port and their related Receive Locations)
5) PartCollection (Trading Partner Management [TPM] Parties)

Structure of Binding File – Top-Most Elements (Sections)

<?xml version="1.0" encoding="utf-8"?>
<BindingInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Assembly="Microsoft.BizTalk.Deployment, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Version="3.5.1.0" BindingStatus="FullyBound" BoundEndpoints="18" TotalEndpoints="18">

  <Timestamp>2020-03-30T10:19:30.8927543-05:00</Timestamp>
  
  <ModuleRefCollection>
     <TrackedSchemas...
     <Service...
  </ModuleRefCollection>
  
  <SendPortCollection>
  </SendPortCollection>
  
  <DistributionListCollection />
  
  <ReceivePortCollection>
  </ReceivePortCollection>
  
  <PartyCollection xsi:nil="true" />

</BindingInfo>

Creating the Binding File

The normal process is to go to an application in BizTalk Admin Console, right click, and select “Export”, then “Bindings…”. You will be prompted for a file name (the directory defaults to: C:\Users\YourUser\AppData\Local\BizTalk\Binding Files\, but you can change it). By default, the filename is the name of your application followed by “_Bindinginfo.xml”.

So for example, for the Application called “Lesson1”, then binding file by default would be:
C:\Users\YourUser\AppData\Local\BizTalk\Binding Files\Lesson1.BindingInfo.xml

Editing the binding file

You can open the binding file in NotePad++ or your favorite editor. It’s just XML, don’t be afraid of it.
For example, when you deploy from test to production environments, you often need to change directory names, SQL Server/Database Names, and/or WebSite URLs.

Duplicating a SendPort

To duplicate a Send Port, you can export the bindings for the app, remove everything except the root element, the and the one you want to duplicate. Change the name of the SendPort, and save the file. You can use an XML plugin to NotePad++ to make sure the file is still standard XML. Usually the URI (disk path or URL) will be changed as well. Then import back into the same application, and voila – your Send Port is copied or duplicated.

You can do similarly for Receive Ports, but note two things:
1) A Receive Port can have multiple ReceiveLocations, and Receive Locations always exist “under” the Receive Port.
2) The Receive Locations URI must be unique (change it before you import it).

Example of a Send Port Binding

    <SendPort Name="sp_Lesson1_Outbound" IsStatic="true" IsTwoWay="false" BindingOption="0" AnalyticsEnabled="false">
      <Description xsi:nil="true" />
      <TransmitPipeline Name="Microsoft.BizTalk.DefaultPipelines.PassThruTransmit" FullyQualifiedName="Microsoft.BizTalk.DefaultPipelines.PassThruTransmit, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Type="2" TrackingOption="None" Description="" />
      <PrimaryTransport>
        <Address>c:\BizTalk\Lesson1\Outbound\%MessageID%_%SourceFileName%</Address>
        <TransportType Name="FILE" Capabilities="11" ConfigurationClsid="5e49e3a6-b4fc-4077-b44c-22f34a242fdb" />
        <TransportTypeData>&lt;CustomProps&gt;&lt;UseTempFileOnWrite vt="11"&gt;0&lt;/UseTempFileOnWrite&gt;&lt;AllowCacheOnWrite vt="11"&gt;0&lt;/AllowCacheOnWrite&gt;&lt;CopyMode vt="19"&gt;1&lt;/CopyMode&gt;&lt;FileName vt="8"&gt;%MessageID%_%SourceFileName%&lt;/FileName&gt;&lt;/CustomProps&gt;</TransportTypeData>
        <RetryCount>3</RetryCount>
        <RetryInterval>1</RetryInterval>
        <ServiceWindowEnabled>false</ServiceWindowEnabled>
        <FromTime>2020-02-09T08:00:00</FromTime>
        <ToTime>2020-02-10T07:59:59</ToTime>
        <Primary>true</Primary>
        <OrderedDelivery>false</OrderedDelivery>
        <DeliveryNotification>1</DeliveryNotification>
        <SendHandler Name="BizTalkServerApplication" HostTrusted="false">
          <TransportType Name="FILE" Capabilities="11" ConfigurationClsid="5e49e3a6-b4fc-4077-b44c-22f34a242fdb" />
        </SendHandler>
      </PrimaryTransport>
      <SecondaryTransport>
        <Address />
        <RetryCount>3</RetryCount>
        <RetryInterval>5</RetryInterval>
        <ServiceWindowEnabled>false</ServiceWindowEnabled>
        <FromTime>2020-02-09T08:00:00</FromTime>
        <ToTime>2020-02-10T07:59:59</ToTime>
        <Primary>false</Primary>
        <OrderedDelivery>false</OrderedDelivery>
        <DeliveryNotification>1</DeliveryNotification>
        <SendHandler xsi:nil="true" />
      </SecondaryTransport>
      <ReceivePipelineData xsi:nil="true" />
      <Tracking>204</Tracking>
      <Filter>&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;Filter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  &lt;Group&gt;
    &lt;Statement Property="BTS.ReceivePortName" Operator="0" Value="rp_Lesson1_Inbound" /&gt;
  &lt;/Group&gt;
&lt;/Filter&gt;</Filter>
      <Transforms />
      <OrderedDelivery>false</OrderedDelivery>
      <Priority>5</Priority>
      <StopSendingOnFailure>false</StopSendingOnFailure>
      <RouteFailedMessage>false</RouteFailedMessage>
      <ApplicationName>Lesson1</ApplicationName>
    </SendPort>

The trickiest part of the above is this:

        <TransportTypeData>&lt;CustomProps&gt;&lt;UseTempFileOnWrite vt="11"&gt;0&lt;/UseTempFileOnWrite&gt;&lt;AllowCacheOnWrite vt="11"&gt;0&lt;/AllowCacheOnWrite&gt;&lt;CopyMode vt="19"&gt;1&lt;/CopyMode&gt;&lt;FileName vt="8"&gt;%MessageID%_%SourceFileName%&lt;/FileName&gt;&lt;/CustomProps&gt;</TransportTypeData>

This is sometimes called encoding. The value of is actually more XML, but it is different depending on what adapter you use in your Send Port. So BizTalk encodes that XML with something like URLEncoding.

To make it more readable, you can do two change all commands in NotePad++:
1) Change > to >
2) Change < to < However I do not recommend going the other direction, there may be some conflicts. After that, and formatting it, it looks like this:

<TransportTypeData>
	<CustomProps>
		<UseTempFileOnWrite vt="11">0</UseTempFileOnWrite>
		<AllowCacheOnWrite vt="11">0</AllowCacheOnWrite>
		<CopyMode vt="19">1</CopyMode>
		<FileName vt="8">%MessageID%_%SourceFileName%</FileName>
	</CustomProps>
</TransportTypeData>

Send Ports for WCF and SQL are much more complicated then the simple one below, as they can have a dozen or more parameters.

The “Filter” section is also encoded:

      <Filter>&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;Filter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  &lt;Group&gt;
    &lt;Statement Property="BTS.ReceivePortName" Operator="0" Value="rp_Lesson1_Inbound" /&gt;
  &lt;/Group&gt;
&lt;/Filter&gt;</Filter>

Unencoded, it looks like this:

      <Filter><?xml version="1.0" encoding="utf-16"?>
<Filter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Group>
    <Statement Property="BTS.ReceivePortName" Operator="0" Value="rp_Lesson1_Inbound" />
  </Group>
</Filter></Filter>

This shows you that the Send Port has a subscription (filter) on BTS.ReceivePortName = "rp_Lesson1_Inbound". The "equal" sign is encoded into the Operator=0 statement. I don't have an quick listing of the possible values of the Operator attribute.

I had a huge 550 MB file, I wanted to split it into 55 or so files of 10 MB each.

The one improvement I would make to this next time is to make the suffix a 3 digit number so when the files sort by name they would be in numerical order (currently _1 and _10 sort together, _2 and _20 and so on).

cls
$ErrorActionPreference = "Stop"
$upperBound = 10MB # calculated by Powershell
$ext = "log"
$rootName = "c:\ProgramData\ssh\logs\sshd_log_Split_"

$reader = new-object System.IO.StreamReader("c:\ProgramData\ssh\logs\sshd_Archive.log")
$count = 1
$fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext)
while(($line = $reader.ReadLine()) -ne $null)
{
    Add-Content -path $fileName -value $line
    if((Get-ChildItem -path $fileName).Length -ge $upperBound)
    {
        ++$count
        $fileName = "{0}{1}.{2}" -f ($rootName, $count, $ext)
    }
}

$reader.Close()

The above is based some minor modifications to a sample found here: https://stackoverflow.com/questions/1001776/how-can-i-split-a-text-file-using-powershell

You will likely get a memory error (System.OutOfMemoryException) if you use Get-Content to try to load a 550MB file into a string variable. We can using a System.IO.StreamRead in PowerShell to stream the data instead of doing one big massive load into memory. The script below allows you to search a huge file for a string. For example, in this case I had an OpenSSH SFTP log with 4 months of data in it. I wanted to find the date/time when a user last attempted to connect.

# Example from log file when a userid is matched 
#10464 2020-03-31 13:47:30.273 debug1: user Walmart matched 'User Walmart ' at line 134

cls
Write-Host "Start" 


#$logFile = "c:\ProgramData\ssh\logs\sshd_log_Split_19_2020_03_30_to_03_31.log" 
#$logFile = "c:\ProgramData\ssh\logs\sshd_log_Split_20_2020_03_31_to_04_01.log" 
$logFile = "c:\ProgramData\ssh\logs\sshd_log_Split_21_2020_04_01B.log" 
#$logFile = "c:\ProgramData\ssh\logs\sshd_log_Split_22_2020_04_01_to_04_02.log" 


$ErrorActionPreference = "Stop"

$reader = new-object System.IO.StreamReader($logFile)
$count = 1
$lineNumber = 1 
while(($line = $reader.ReadLine()) -ne $null)
{
    ++$lineNumber 
    if ($line.ToLower().Indexof("user walmart matched") -ge 0) 
    {
        ++$count
        Write-Host $count $lineNumber $line 
    }
}

$reader.Close()
Write-Host "End" 

The Full Error Was

The message could not be sent to the SMTP server. The transport error code was 0x80040217

Solution

I had to go to the BizTalk Adapters in BizTalk Admin Console, find the SMTP adapter, then configure it for the host.
On the properties window, there is an “Authentication Type” which defaulted to “NTLM Authentication”.

You may need to talk to your infrastructure/email team to see what is required by your email server. You might need to provide a basic authentication userid/password here.

You can optionally specify your email server name here, and default “reply-to email address”, rather having to specify it in every single SMTP Send Port.

Alternatively

C# to Send Email

The C# Mail.sendEmail method below can be called from a BizTalk Orchestration to send an email.
You can also write a wrapper test method to call it. When I got the error above, I checked this C# first, and it was able to send an email, so I knew the issue was in BizTalk.

The routine below is bare-bones as simple as can be. It doesn’t handle authentication or attachments or HTML bodies. So it could be enhanced with more parms to handle those types of scennarios.

Alternatively, you could try turning off or changing the authentication in the Send Port itself, but several other issues and blogs I found suggested that you have to setup the adapter first, before trying any overrides on the Send Port.


    public class Mail
    {

        // Example Call: 
          ABC.Helpers.Mail.sendEmail("nrwalters@abc.com", "EmailTester C#", "This is the message body"); 

        //send an email alert
        public static void sendEmail(string recipient, string subject, string message)
        {
            try
            {
                string from = "noreply@abc.com";
                //string host = "smtp.abc.com";
                int smtpPort = 25;

                //append the system name to the email
                string systemEnvironmentName = Config.getConfigValue("systemName");
                subject = subject.Trim() + " [" + systemEnvironmentName.Trim() + "]";

                MailMessage mail = new MailMessage();
                SmtpClient client = new SmtpClient();

                //set the email address from address
                MailAddress fromEmail = new MailAddress(from);
                mail.From = fromEmail;

                //split the recipient email address by ';' and add
                foreach (var address in recipient.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
                {
                    mail.To.Add(address);
                }



                client.Port = smtpPort;
                client.DeliveryMethod = SmtpDeliveryMethod.Network;
                client.UseDefaultCredentials = false;
                client.Host = host;
                mail.Subject = subject;
                mail.Body = message;
                mail.IsBodyHtml = false;

                client.Send(mail);
            }
            catch (SmtpException ex)
            {
                LoggingToFiles.logHelperError("sendEmail", ex.Message);
                throw new Exception("SMTP.Exception in function sendEmail: " + ex.Message);
            }
            catch (Exception ex)
            {
                LoggingToFiles.logHelperError("sendEmail", ex.Message);
                throw new Exception("Exception in function sendEmail: " + ex.Message);
            }
        }

    }