I'm trying to consume Azure DevOps .NET API, specifically the Git client, from Powershell 5.1. There is a copy of all client libraries under C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer
.
So first I tried that in a C# program:
GitHttpClient Cli = new GitHttpClient(new Uri("http://tfs.example.com:8080/tfs/MyCollection/"), new VssCredentials(true));
This line would throw an error that Newtonsoft.Json, v9.0.0.0 was not found. A copy of Newtonsoft.Json.dll is present in the same folder, except its version is 12. I've added an explicit reference to Newtonsoft.Json.dll to the project, rebuilt, and it worked - presumably because the program loads Newtonsoft.Json.dll v12 before AzDevOps client DLLs and the dependency resolution picks that one up despite version mismatch.
Now I'm trying the same in Windows Powershell 5.1 (interactive for now). So first, I'd do
$APIPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer"
Add-Type -Path "$APIPath\Newtonsoft.Json.dll"
Then I'd do
Add-Type -Path "$APIPath\Microsoft.TeamFoundation.SourceControl.WebApi.dll"
And that throws an error that Newtonsoft.JSON, v9.0.0.0, or one of its dependencies, can't be found. Why this discrepancy? Wouldn't the previous Add-Type
load the DLL into the process and short out the dependency resolution at that, like the C# counterpart does?
Tried constructing a random Newtonsoft object before the second Add-Type
to force Newtonsoft DLL loading, under assumption that Add-Type is lazy - same result. The object constructs.
If there is a way to somehow tell Powershell "Newtonsoft 12 is to be used whenever Newtonsoft9 is requested", I'd gladly use that.
UPD: the loaded assembly dump as outlined at https://www.koskila.net/how-to-list-all-of-the-assemblies-loaded-in-a-powershell-session/ confirms that Newtonsoft 12 in loaded. Elsewhere at SO they claim that the first loaded version wins and having multiple versions loaded at the same time is not allowed without some deep magic (multiple AppDomains and such). Yet that's not what I'm seeing.
The loaded assembly list claims that Microsoft.TeamFoundation.SourceControl.WebApi.dll is loaded, but trying to construct the GitHttpClient
throws the "can't load Newtonsoft" error.
UPD2: even hairier. So I've located a copy of Newtonsoft 9 on the system, loaded that into Powershell. Now the Add-Type -Path "$APIPath\Microsoft.TeamFoundation.SourceControl.WebApi.dll"
line executes, but the GitHttpClient constructor errors our claiming Newtonsoft 6 can't be found. I've poked around with ILSpy, found that:
- MS.TF.SourceControl.WebApi requires Newtonsoft 9
- MS.TF.SourceControl.WebApi requires System.Net.Http.Formatting (present in the same API folder)
- System.Net.Http.Formatting requires Newtonsoft 6
So without whatever magic exists in desktop C# applications and allows upstream dependency resolution, it's just not possible.
UPD3: considered hooking AppDomain.AssemblyResolve, but Powershell (at least v5) can't hook events with return values. Elsewhere they claim that later versions of the assembly should satisfy requirements for earlier ones, but it seems that it only works among major versions. In the AppDomain of the C# application, the AssemblyResolve method doesn't seem to be caught. Could it be driven by AppDomain properties?