Sometimes in .NET you end up in assembly version hell. When you’re trying to get your assembly references and binding redirects right, this function can really come in handy, it works directly on a file, or on directories and has the recurse switch available. It can tell you what version of files you currently have installed, and generate binding redirects.
function Get-AssemblyName($path, [Switch] $Recurse) {
$path = Resolve-Path $path
if ((Get-Item $path) -is [System.IO.DirectoryInfo]) {
Return Get-ChildItem -Path $path -Filter '*.dll' -recurse:$Recurse | % { Get-AssemblyName $_.FullName } | sort-object -Property Name -Unique
}
$fileVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path).FileVersion
Try {
$name = [System.Reflection.AssemblyName]::GetAssemblyName($path)
$publicKeyToken = "null"
if ($name.GetPublicKeyToken()) {
$publicKeyTokenParts = $name.GetPublicKeyToken() | % { $_.ToString("x2")}
$publicKeyToken = [String]::Join("", $publicKeyTokenParts)
}
if ($name.ProcessorArchitecture -ne 'None') {
$bindingRedirect =
@"
<dependentAssembly>
<assemblyIdentity name=`"$($name.Name)`" publicKeyToken=`"$publicKeyToken`" />
<bindingRedirect oldVersion=`"0.0.0.0-$($name.Version)`" newVersion=`"$($name.Version)`" />
</dependentAssembly>
"@
}
Return New-Object –TypeName PSObject –Prop ([ordered]@{Name=$name.Name; NetVersion=$name.Version; PublicKeyToken=$publicKeyToken; FileVersion=$fileVersion; Arch=$name.ProcessorArchitecture; Path=$path; DotNet=$true; BindingRedirect=$bindingRedirect})
} Catch [System.BadImageFormatException] {
$fileName = (Get-Item $path).BaseName
$fileVersion = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($path).FileVersion
Return New-Object –TypeName PSObject –Prop ([ordered]@{Name=$fileName; FileVersion=$fileVersion; Path=$path; DotNet=$false})
}
}
# (Get-AssemblyName -Recurse "bin").BindingRedirect > out.txt
Fixing assembly version errors
Short term
If you write the generated redirects to a file, it’s easy to copy them into your web/app.config:
(Get-AssemblyName -Recurse “Path\to\bin”).BindingRedirect > out.txt
Long term
I’ve found assembly version hell happens much more often to me since I started trying to use .NET standard on .NET framework 4.6.1 because it’s only really supported as a hack. .NET framework 4.6.1 doesn’t contain all the assemblies for .NET standard 2.0 because .NET standard 2.0 didn’t exist when the framework version shipped, but it is still said to “support” it because it’s possible to use NuGet to pull in the required references.
However, this usually requires an explicity reference to the .NET standard dll in the sdk:
$(MSBuildSDKsPath)\..\Microsoft\Microsoft.NET.Build.Extensions\net461\lib\netstandard.dll
I also defined MSBuildSDKsPath on TeamCity as a root level environment variable so that it was present for all my builds.
You’ll then need to get the right NuGet packages and the the right assembly binding redirects to the version that’s in those packages. But even after doing all that you might still get runtime errors, like:
[FileNotFoundException: Could not load file or assembly ‘System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file specified.]
System.Web.Http.GlobalConfiguration..cctor() +0
One cause of this, is if the machine doing the build actually built against a later version of the .NET framework, which did contain the dlls you NuGetted. If Microsoft had shipped those latest versions to NuGet, we could just use them, binding redirect to them, and everything would be fine, but alas, the did stopped shipping updates to the NuGet packages once they were in proper framework releases!
So on build machines that have those later versions you need to very carefully avoid the framework version being copied, otherwise your binding redirects will point to the wrong version. That’s where the PowerShell function at the top comes in – use it to find which project’s bin directory has the wrong version in, and then trace back where it came from and eliminate it. i.e. Make sure the only reference to the culprit in that project is to the NuGet package (no plain assembly references), and add a binding redirect if something in that project wants a different version.
Of course the real solution is for us to upgrade all our build tools, servers and any customer machines to .NET Framework 4.7.1 which does fully support .NET standard 2.0, and stop all of this insanity, but that’s going to take anywhere between a few weeks and a few years depending on the product, so for now this is what we’re stuck with.
Thankfully the new Visual Studio 2017 project format by default doesn’t add the GAC to the assembly search paths in a build – see this GitHub issue. So if you use that, you’ll probably avoid accidentally copying a file from your build machine at least. If you can’t move to that yet, it’s also possible to manually set the assembly search paths in each project file (or a common props file) without using the new format.