Install and register assemblies in a BizTalk Application with PowerShell (Part II)

  • Sandro Pereira
  • Nov 25, 2025
  • 5 min read

Following my last blog post Install and register assemblies in a BizTalk Application with PowerShell, where we add the first version of a PowerShell script to install and register assemblies into a BizTalk Application, and where we highlight one huge limitation: assembly dependencies that need to be installed in order.

📝 One-Minute Brief

This is a PowerShell script that goes to a folder containing assemblies and install and register them in a specific order for a specific BizTalk Application.

Today, we will address a solution to this problem without the need to create a guideline document to set up the installation order process. Luckily, in our case, we had a good project naming convention, which in turn led to a good assembly naming convention. In our case, we had:

  • Helper classes resources were inside assemblies with the string Helper in the name, for example:
    • MyApp.Common.Helper
    • MyApp.ProcessName.Helper
  • Common shared resources were inside assemblies with the string Common in the name, for example:
    • MyApp.Common.Messaging
    • MyApp.Common.Processing
  • The schemas and mapping resources were inside assemblies with the string Messaging in the name, for example:
    • MyApp.ProcessName.Messaging
  • The orchestration resources were inside assemblies with the string Processing in the name, for example:
    • MyApp.ProcessName.Processing
  • And pipeline resources were inside assemblies with the string Staging in the name, for example:
    • MyApp.ProcessName.Staging

So for us, the order logic was quite simple:

  • First, we need to install all the Helper Classes – the assemblies that have Helper in the name.
  • Second, we need to install all the Common resources – the assemblies that have Common in the name.
  • Third, we need to install all the Schemas and Maps – the assemblies that have Messaging in the name.
  • Four, we need to install all the Pipelines – the assemblies that have Staging in the name.
  • Finally, we need to install all the Orchestrations – the assemblies that have Processing in the name.

With this deploy strategy, we have minimized the risk of failed dependencies to something that works for us – maybe we get one or two failures due to a missing dependency, something that we easily solve manually.

Here is a sample of the script:

# Load DLLs (add -Recurse if needed)
$dlls = Get-ChildItem -LiteralPath $SourceDir -Filter *.dll -File -ErrorAction SilentlyContinue
if (-not $dlls) { Write-Warning "No DLLs found in $SourceDir"; return }
 
$termsInOrder = @('Helper','Common','Messaging','Staging','Processing')
 
# Build the install queue by filename (case-insensitive)
$installQueue = @()
$remaining = $dlls
foreach ($t in $termsInOrder) {
  $pattern = [regex]::Escape($t)
  $matched = $remaining | Where-Object { $_.Name -match $pattern } | Sort-Object Name
  if ($matched) {
    $installQueue += $matched
    $remaining = $remaining | Where-Object { $matched.FullName -notcontains $_.FullName }
  } else {
    Write-Host "No DLL filenames containing '$t'." -ForegroundColor DarkGray
  }
}
 
if (-not $installQueue) { Write-Warning "No DLLs match: $($termsInOrder -join ', ')"; return }
 
function Get-AssemblyStrongNameInfo {
  param([Parameter(Mandatory=$true)][string]$Path)
  try {
    $an = [System.Reflection.AssemblyName]::GetAssemblyName($Path)
    $pkt = $an.GetPublicKeyToken()
    [pscustomobject]@{
      IsStrongNamed = ($pkt -and $pkt.Length -gt 0)
      Name          = $an.Name
      Version       = $an.Version
    }
  } catch {
    [pscustomobject]@{
      IsStrongNamed = $false
      Name          = (Split-Path $Path -Leaf)
      Version       = $null
    }
  }
}
 
function Invoke-BTSTask {
  param(
    [Parameter(Mandatory=$true)][string]$BTSTaskPath,
    [Parameter(Mandatory=$true)][string[]]$Arguments
  )
  $psi = New-Object System.Diagnostics.ProcessStartInfo
  $psi.FileName = $BTSTaskPath
  $psi.Arguments = ($Arguments -join ' ')
  $psi.UseShellExecute = $false
  $psi.RedirectStandardOutput = $true
  $psi.RedirectStandardError  = $true
 
  $p = New-Object System.Diagnostics.Process
  $p.StartInfo = $psi | Out-Null
  [void]$p.Start()
  $stdout = $p.StandardOutput.ReadToEnd()
  $stderr = $p.StandardError.ReadToEnd()
  $p.WaitForExit()
 
  [pscustomobject]@{
    ExitCode = $p.ExitCode
    StdOut   = $stdout
    StdErr   = $stderr
  }
}
 
function Install-DllAuto {
  param(
    [Parameter(Mandatory=$true)][string]$DllPath,
    [Parameter(Mandatory=$true)][string]$ApplicationName,
    [Parameter(Mandatory=$true)][string]$BTSTaskPath
  )
 
  $info = Get-AssemblyStrongNameInfo -Path $DllPath
  if (-not $info.IsStrongNamed) {
    Write-Warning "DLL is not strong-named (GAC will fail): $DllPath"
  }
 
  # 1) Try as BizTalkAssembly (schemas/maps/orchestrations)
  $args1 = @(
    'AddResource',
    "/ApplicationName:$ApplicationName",
    '/Type:System.BizTalk:BizTalkAssembly',
    '/Overwrite',
    "/Source:`"$DllPath`"",
    '/Options:GacOnAdd,GacOnInstall,GacOnImport'
  )
  $r1 = Invoke-BTSTask -BTSTaskPath $BTSTaskPath -Arguments $args1
 
  if ($r1.ExitCode -eq 0) {
    return [pscustomobject]@{ ExitCode=0; Type='BizTalkAssembly'; Output=$r1.StdOut; Error=$r1.StdErr }
  }
 
  # 2) Retry as plain .NET Assembly (helper libraries)
  $args2 = @(
    'AddResource',
    "/ApplicationName:$ApplicationName",
    '/Type:System.BizTalk:Assembly',
    '/Overwrite',
    "/Source:`"$DllPath`"",
    '/Options:GacOnAdd,GacOnInstall,GacOnImport'
  )
  $r2 = Invoke-BTSTask -BTSTaskPath $BTSTaskPath -Arguments $args2
 
  if ($r2.ExitCode -eq 0) {
    return [pscustomobject]@{ ExitCode=0; Type='Assembly'; Output=$r2.StdOut; Error=$r2.StdErr }
  }
 
  # 3) If still failing and not strong-named, last resort: add as File (no GAC)
  if (-not $info.IsStrongNamed) {
    Write-Warning "Falling back to File resource (no GAC): $DllPath"
    $args3 = @(
      'AddResource',
      "/ApplicationName:$ApplicationName",
      '/Type:System.BizTalk:File',
      '/Overwrite',
      "/Source:`"$DllPath`""
    )
    $r3 = Invoke-BTSTask -BTSTaskPath $BTSTaskPath -Arguments $args3
    if ($r3.ExitCode -eq 0) {
      return [pscustomobject]@{ ExitCode=0; Type='File'; Output=$r3.StdOut; Error=$r3.StdErr }
    } else {
      return [pscustomobject]@{ ExitCode=$r3.ExitCode; Type='File'; Output=$r3.StdOut; Error=$r3.StdErr }
    }
  }
 
  # Failed both typed installs
  return [pscustomobject]@{ ExitCode=$r2.ExitCode; Type='Assembly'; Output=$r2.StdOut; Error=$r2.StdErr }
}
 
$results = @()
foreach ($dll in $installQueue) {
  Write-Host ("Installing -> {0}" -f $dll.Name) -ForegroundColor Yellow
  $res = Install-DllAuto -DllPath $dll.FullName -ApplicationName $AppName -BTSTaskPath $BTSTask
  if ($res.ExitCode -eq 0) {
    Write-Host ("OK  [{0}] -> {1}" -f $res.Type, $dll.Name) -ForegroundColor Green
    $results += [pscustomobject]@{
      File = $dll.Name; Path = $dll.FullName; Type = $res.Type; Result='Added'
    }
  } else {
    Write-Warning ("FAIL [{0}] -> {1} (ExitCode {2})" -f $res.Type, $dll.Name, $res.ExitCode)
    if ($res.Error) { Write-Host $res.Error -ForegroundColor DarkRed }
    $results += [pscustomobject]@{
      File = $dll.Name; Path = $dll.FullName; Type = $res.Type; Result="ERROR $($res.ExitCode)"
    }
  }
}

Again, this did in fact 98% of the work we needed, and we fixed the 2% manually. That 2% was due to some process assemblies adding dependencies on other process assemblies, which resulted from some exceptions that should not have happened, and we will be fixing it.

Download

THIS POWERSHELL SCRIPT IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND.

You can download the PowerShell Script used from GitHub here:

Hope you find this helpful! If you enjoyed the content or found it useful, and wish to support our efforts to create more, you can contribute to purchasing a Star Wars Lego set for my son!

Author: Sandro Pereira

Sandro Pereira lives in Portugal and works as a consultant at DevScope. In the past years, he has been working on implementing Integration scenarios both on-premises and cloud for various clients, each with different scenarios from a technical point of view, size, and criticality, using Microsoft Azure, Microsoft BizTalk Server and different technologies like AS2, EDI, RosettaNet, SAP, TIBCO etc. He is a regular blogger, international speaker, and technical reviewer of several BizTalk books all focused on Integration. He is also the author of the book “BizTalk Mapping Patterns & Best Practices”. He has been awarded MVP since 2011 for his contributions to the integration community.

Leave a Reply

Your email address will not be published. Required fields are marked *

The Ultimate Cloud
Management Platform for Azure

Supercharge your Azure Cost Saving

Learn More
Turbo360 Widget

Back to Top