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!