无法从 Powershell 使用 GitHttpClient,Newtonsoft.Json 版本冲突

Can't use GitHttpClient from Powershell, Newtonsoft.Json version conflict

我正在尝试使用来自 Powershell 5.1 的 Azure DevOps .NET API,特别是 Git 客户端。 C:\Program Files\Microsoft Visual Studio22\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer.

下有所有客户端库的副本

首先我在 C# 程序中尝试了:

GitHttpClient Cli = new GitHttpClient(new Uri("http://tfs.example.com:8080/tfs/MyCollection/"), new VssCredentials(true));

此行会抛出错误 Newtonsoft.Json,未找到 v9.0.0.0。 Newtonsoft.Json.dll 的副本存在于同一个文件夹中,除了它的版本是 12。我在项目中添加了对 Newtonsoft.Json.dll 的显式引用,重建,它工作 - 大概是因为该程序在 AzDevOps 客户端 DLL 之前加载 Newtonsoft.Json.dll v12,尽管版本不匹配,但依赖项解析仍选择了那个。

现在我在 Windows Powershell 5.1 中尝试同样的操作(现在是交互式的)。所以首先,我会做

$APIPath = "C:\Program Files\Microsoft Visual Studio22\Enterprise\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer"
Add-Type -Path "$APIPath\Newtonsoft.Json.dll" 

那我就

Add-Type -Path "$APIPath\Microsoft.TeamFoundation.SourceControl.WebApi.dll"

这会引发错误,无法找到 Newtonsoft.JSON、v9.0.0.0 或其依赖项之一。为什么会有这种差异?之前的 Add-Type 不会像 C# 对应的那样将 DLL 加载到进程中并缩短依赖项解析吗?

尝试在第二个 Add-Type 之前构建随机 Newtonsoft 对象以强制加载 Newtonsoft DLL,假设 Add-Type 是惰性的 - 结果相同。对象构造。

如果有办法以某种方式告诉 Powershell“每当请求 Newtonsoft9 时都将使用 Newtonsoft 12”,我很乐意使用它。

UPD:加载的程序集转储如 https://www.koskila.net/how-to-list-all-of-the-assemblies-loaded-in-a-powershell-session/ confirms that Newtonsoft 12 in loaded. 中所述,他们声称第一个加载的版本获胜,并且如果没有一些深奥的魔法(多个 AppDomains 等),则不允许同时加载多个版本。然而,这不是我所看到的。

加载的程序集列表声称 Microsoft.TeamFoundation.SourceControl.WebApi.dll 已加载,但尝试构建 GitHttpClient 会抛出“无法加载 Newtonsoft”错误。

UPD2:更毛茸茸的。所以我在系统上找到了 Newtonsoft 9 的副本,并将其加载到 Powershell 中。现在 Add-Type -Path "$APIPath\Microsoft.TeamFoundation.SourceControl.WebApi.dll" 行执行了,但是 GitHttpClient 构造函数出错,我们声称找不到 Newtonsoft 6。我查了一下 ILSpy,发现:

因此,如果桌面 ​​C# 应用程序中没有任何魔法并允许上游依赖项解析,这是不可能的。

UPD3:考虑挂钩 AppDomain.AssemblyResolve,但 Powershell(至少 v5)无法挂钩具有 return 值的事件。在其他地方,他们声称程序集的更高版本应该满足早期版本的要求,但它似乎只适用于主要版本。在C#应用程序的AppDomain中,AssemblyResolve方法似乎没有被捕获。它可以由 AppDomain 属性驱动吗?

序言: C# 程序中的依赖项解析未获取 v12,其中 v9/6 是自动请求的;它这样做只是因为编译程序的配置文件告诉它这样,而且只发生过一次,因为项目中引用了 Newtonsoft v12。感谢@n0rd 指出这一点。将强命名的依赖程序集解析为更高的主要版本不是.NET 4.5-8 中的默认行为。

修改 Powershell 的配置以实现相同的可能是可能的,但我没有去那里。需要此逻辑的原始部分最终将 运行 连接到我无法控制的服务器上,因此管理开销越少越好。现在,为工作答案。


毕竟您可以在 Powershell 5 中提供解析处理程序,告诉 .NET 使用 Newtonsoft 的加载版本代替任何其他版本。它是这样的:

$OnAssemblyResolve = [ResolveEventHandler] {
    param($o, $e)
    if($e.Name.StartsWith("Newtonsoft.Json,"))
    {
        return [AppDomain]::CurrentDomain.GetAssemblies() | ?{$_.FullName.StartsWith("Newtonsoft.Json,")}
    }
    return $null
}

Add-Type -Path "$APIPath\Newtonsoft.Json.dll"
[AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)
Add-Type -Path "$APIPath\Microsoft.TeamFoundation.SourceControl.WebApi.dll"
Add-Type -Path "$APIPath\Microsoft.VisualStudio.Services.WebApi.dll"
Add-Type -Path "$APIPath\System.Net.Http.Formatting.dll"
[AppDomain]::CurrentDomain.remove_AssemblyResolve($OnAssemblyResolve)

加载完 Newtonsoft 依赖程序集(特别是 System.Net.Http.Formatting)后,删除处理程序。否则,它可能会干扰 Powershell 自身的功能并导致堆栈溢出异常,其中 Powershell 中的“找不到程序集”条件会触发处理程序,该处理程序需要相同的程序集 运行,从而导致无休止的递归。在我的例子中,它发生在下游,当脚本试图抛出一个不相关的异常时,这需要加载 System.Management.Automation.resources,但找不到,等等


我之前关于 Powershell 5 无法使用 return 值挂钩 .NET 事件的说法是错误的。我依稀记得阅读过一些事件处理 cmdlet 的文档,其中提到 returning 处理程序块中的值不受支持,猜想这就是我的误解的来源。