Powershell - 应用程序配置文件中未找到程序集绑定重定向

12

这里有个问题...

我有一个 Powershell CmdLet,在32位模式下运行正常,但在64位模式下会失败。 问题是原因是什么以及如何解决。

情况

Powershell CmdLet 引用了 'OutlookHelper.Common.dll'。最新版本为2.0.0.0。 CmdLet 还使用了记录日志并引用了 'Logging.dll'。
Logging.dll 也引用了 'OutlookHelper.Common.dll',但只编译为1.0.0.0版本。

我是如何部分解决的

在 Powershell 应用配置文件中使用程序集绑定重定向:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true"> 
    <supportedRuntime version="v4.0.30319"/> 
    <supportedRuntime version="v2.0.50727"/> 
  </startup> 
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="OutlookHelper.Common" publicKeyToken="5e4553dc0df45306"/>
        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Powershell 32位运行良好

在64位计算机上运行时,使用“Windows Powershell(x86)”可以正常工作。程序集管理器会找到程序集绑定重定向:

The operation was successful.
Bind result: hr = 0x0. The operation completed successfully.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  C:\Windows\syswow64\Windowspowershell\v1.0\powershell.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: User = MYDOMAIN\testuser
LOG: DisplayName = OutlookHelper.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5e4553dc0df45306
 (Fully-specified)
LOG: Appbase = file:///C:/Windows/syswow64/Windowspowershell/v1.0/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = powershell.exe
Calling assembly : OutlookHelper.Data.Common, Version=1.0.5295.26925, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: C:\Windows\syswow64\Windowspowershell\v1.0\powershell.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Redirect found in application configuration file: 1.0.0.0 redirected to 2.0.0.0.
LOG: Post-policy reference: OutlookHelper.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5e4553dc0df45306
LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/Windows/syswow64/Windowspowershell/v1.0/OutlookHelper.Common.DLL.
LOG: Attempting download of new URL file:///C:/Windows/syswow64/Windowspowershell/v1.0/OutlookHelper.Common/OutlookHelper.Common.DLL.
LOG: Attempting download of new URL file:///C:/Windows/syswow64/Windowspowershell/v1.0/OutlookHelper.Common.EXE.
LOG: Attempting download of new URL file:///C:/Windows/syswow64/Windowspowershell/v1.0/OutlookHelper.Common/OutlookHelper.Common.EXE.
LOG: Attempting download of new URL file:///D:/SampleApps/_Common/Bin/Outlook.Extensions.Sample/OutlookHelper.Common.DLL.
LOG: Assembly download was successful. Attempting setup of file: D:\SampleApps\_Common\Bin\Outlook.Extensions.Sample\OutlookHelper.Common.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: OutlookHelper.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5e4553dc0df45306
LOG: Where-ref bind Codebase does not match what is found in default context. Keep the result in LoadFrom context.
LOG: Binding succeeds. Returns assembly from D:\SampleApps\_Common\Bin\Outlook.Extensions.Sample\OutlookHelper.Common.dll.
LOG: Assembly is loaded in LoadFrom load context.

以下是Powershell关于程序集标识的内容:

Windows PowerShell (x86)
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\testuser> ([xml](gc $([System.AppDomain]::CurrentDomain.SetupInformation.ConfigurationFile))).configuratio
n.runtime.assemblyBinding.dependentAssembly.assemblyIdentity

name                                                        publicKeyToken
----                                                        --------------
OutlookHelper.Common                                        5e4553dc0df45306

PS C:\Users\testuser>

问题从这里开始...

在64位机器上,使用“Windows Powershell”不起作用。程序集管理器无法找到程序集绑定重定向:

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
Running under executable  C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: User = MYDOMAIN\testuser
LOG: DisplayName = OutlookHelper.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5e4553dc0df45306
 (Fully-specified)
LOG: Appbase = file:///C:/WINDOWS/system32/WindowsPowerShell/v1.0/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = powershell.exe
Calling assembly : OutlookHelper.Data.Common, Version=1.0.5295.26925, Culture=neutral, PublicKeyToken=null.
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: Using application configuration file: C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe.Config
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.
LOG: Post-policy reference: OutlookHelper.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5e4553dc0df45306
LOG: The same bind was seen before, and was failed with hr = 0x80070002.
ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).

以下是Powershell关于程序集标识的内容:

Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\testuser> ([xml](gc $([System.AppDomain]::CurrentDomain.SetupInformation.ConfigurationFile))).configuratio
n.runtime.assemblyBinding.dependentAssembly.assemblyIdentity
PS C:\Users\ccontent01>

当我让PowerShell获取其自己应用程序配置文件的内容时,我会得到以下输出:

Windows PowerShell
Copyright (C) 2009 Microsoft Corporation. All rights reserved.

PS C:\Users\testuser> gc C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe.Config
<?xml version="1.0"?>
<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v4.0.30319"/>
        <supportedRuntime version="v2.0.50727"/>
    </startup>
</configuration>
PS C:\Users\testuser>

我尝试过的...

  • 检查是否将“Specific Version”设置为“false”->是这种情况。
  • 删除并重新添加应用程序配置文件->未修复。
  • 在SysWOW64文件夹中使用新的应用程序配置文件启动->未修复。
  • 仔细检查已加载的文件内容(powershell.exe.Config和machine.config)->它们是相同的。

我的猜测

  • 程序集管理器无法找到程序集重定向绑定。

有什么解决方案吗?

  • 为什么64位实例的融合日志没有提到类似“在应用程序配置文件中发现重定向:1.0.0.0 重定向到2.0.0.0。”这样的内容?
  • 所有这些的原因可能是什么?
  • 您能想到任何解决方案吗?

磁盘上的dll文件在哪里?它们是否存在于“C:/WINDOWS/system32/WindowsPowerShell/v1.0/”或其他地方? - Vikas Gupta
系统中存在像PSEvents.dll、pspluginwkr.dll、pspluginwkr-v3.dll、pwrshmsg.dll和pwrshsip.dll这样的ddls文件,它们同时出现在system32和syswow64文件夹中。但是这些文件的内容并不相同。 - mathijsuitmegen
3个回答

11

虽然不完全涉及到32/64位问题,但如果有人对工作中的汇编重定向解决方案感兴趣,请看这里Powershell config assembly redirect。你可以使用PowerShell代码进行自定义汇编重定向。

$FSharpCore = [reflection.assembly]::LoadFrom($PSScriptRoot + "\bin\LIBRARY\FSharp.Core.dll") 

$OnAssemblyResolve = [System.ResolveEventHandler] {
  param($sender, $e)

  # from:FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  # to:  FSharp.Core, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  if ($e.Name -eq "FSharp.Core, Version=4.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") { return $FSharpCore }

  foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
  {
    if ($a.FullName -eq $e.Name)
    {
      return $a
    }
  }
  return $null
}

[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)

我首先从某个地方加载正确版本的FSharp.Core,因为GAC中的版本过旧(我猜这也可能是你的情况)


抱歉,该项目已被删除。 - davidpodhola

7

基于 @davidpodhola 先前提供的非常有帮助的答案,我开始在我的 psm1 模块文件中添加类似以下的内容。如果你的较新的程序集已经被加载(比如使用 Import-Module 命令),那么这个方法应该有效:

if (!("Redirector" -as [type]))
{
$source = 
@'
using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

public class Redirector
{
    public readonly string[] ExcludeList;

    public Redirector(string[] ExcludeList = null)
    {
        this.ExcludeList  = ExcludeList;
        this.EventHandler = new ResolveEventHandler(AssemblyResolve);
    }

    public readonly ResolveEventHandler EventHandler;

    protected Assembly AssemblyResolve(object sender, ResolveEventArgs resolveEventArgs)
    {
        Console.WriteLine("Attempting to resolve: " + resolveEventArgs.Name); // remove this after its verified to work
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var pattern  = "PublicKeyToken=(.*)$";
            var info     = assembly.GetName();
            var included = ExcludeList == null || !ExcludeList.Contains(resolveEventArgs.Name.Split(',')[0], StringComparer.InvariantCultureIgnoreCase);

            if (included && resolveEventArgs.Name.StartsWith(info.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                if (Regex.IsMatch(info.FullName, pattern))
                {
                    var Matches        = Regex.Matches(info.FullName, pattern);
                    var publicKeyToken = Matches[0].Groups[1];

                    if (resolveEventArgs.Name.EndsWith("PublicKeyToken=" + publicKeyToken, StringComparison.InvariantCultureIgnoreCase))
                    {
                        Console.WriteLine("Redirecting lib to: " + info.FullName); // remove this after its verified to work
                        return assembly;
                    }
                }
            }
        }

        return null;
    }
}
'@

    $type = Add-Type -TypeDefinition $source -PassThru 
}

#exclude all powershell related stuff, not sure this strictly necessary
$redirectExcludes = 
    @(
        "System.Management.Automation", 
        "Microsoft.PowerShell.Commands.Utility",
        "Microsoft.PowerShell.Commands.Management",
        "Microsoft.PowerShell.Security",
        "Microsoft.WSMan.Management",    
        "Microsoft.PowerShell.ConsoleHost",
        "Microsoft.Management.Infrastructure",
        "Microsoft.Powershell.PSReadline",
        "Microsoft.PowerShell.GraphicalHost"
        "System.Management.Automation.HostUtilities",

        "System.Management.Automation.resources",
        "Microsoft.PowerShell.Commands.Management.resources",
        "Microsoft.PowerShell.Commands.Utility.resources",
        "Microsoft.PowerShell.Security.resources",
        "Microsoft.WSMan.Management.resources",
        "Microsoft.PowerShell.ConsoleHost.resources",
        "Microsoft.Management.Infrastructure.resources",
        "Microsoft.Powershell.PSReadline.resources",
        "Microsoft.PowerShell.GraphicalHost.resources",
        "System.Management.Automation.HostUtilities.resources"
    )
try
{
    $redirector = [Redirector]::new($redirectExcludes)
    [System.AppDomain]::CurrentDomain.add_AssemblyResolve($redirector.EventHandler)
}
catch
{
    #.net core uses a different redirect method
    write-warning "Unable to register assembly redirect(s). Are you on ARM (.Net Core)?"
}

更新: Powershell似乎存在一个bug,仅仅注册一个程序集解析脚本块就会导致在调用一些命令如Out-GridView时出现StackOverflowException。我更新了代码,使用了通过Add-Type编译的版本,似乎可以解决这个问题。


1
这很好用,但是如何避免退出时出现堆栈溢出?如果我保留AssemblyResolve处理程序,则PowerShell在退出时会崩溃。以下代码可以防止这种情况 [System.AppDomain]::CurrentDomain.remove_AssemblyResolve($OnAssemblyResolve) 但这会破坏封装性,并意味着程序集的用户必须知道取消注册事件处理程序。我已经尝试在AppDomain卸载和进程退出处理程序中执行此操作,但没有成功。 - ben
你知道我偶尔会遇到那个异常,我以为是因为我写的所有互操作代码中有一些隐藏的问题,因为它会导致进程崩溃。不幸的是,我认为问题比仅仅删除解析器要深入,因为在我的机器上,通过注册一个什么都不做的解析器和一个调用 out-gridview 就可以 100% 复现。AssemblyResolve 的文档指出,如果一个解析处理程序导致调用 AppDomain.Load 或 Assembly.Load,你可能会得到 StackOverflowException,但我不确定如何在这个小代码片段中发生这种情况: - Tim
$OnAssemblyResolve = [System.ResolveEventHandler] { param($sender, $resolveEventArgs) Write-Host -fore Green "尝试解析某些内容" return null; } [System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve);#这将导致PowerShell崩溃并出现StackOverflowException try { 1..10 | out-gridview } catch { #这里不会被执行 } - Tim
1
@ben 我之前和微软内部的某个人有过一次讨论,他建议我使用Add-Type编译解析器来避免堆栈溢出。虽然我不太确定它为什么会有所不同,但似乎确实有效。我会更新我的示例... - Tim

3

在64位计算机上,有两个配置文件:

C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe.Config
C:\Windows\syswow64\Windowspowershell\v1.0\powershell.exe.Config

您在64位计算机上都编辑过这两个文件吗?

在Windows的64位版本中,32位进程(如notepad++)会被操作系统透明地重定向从C:\WINDOWS\System32到C:\ WINDOWS \ SysWOW64。

您需要确保使用内置的64位文本编辑器(如notepad.exe)编辑这两个文件。这将确保您不会遭受这个微妙问题的困扰,而导致混淆不清。


是的,我试过编辑其中一个文件,然后操作系统会将更改同步到另一个文件。所以我尝试更改system32中的文件->没有成功。尝试更改syswow64中的文件。尝试删除两个文件,然后测试两种情况:第一种情况在system32中添加文件,第二种情况在syswow64文件夹中添加文件->没有成功。 - mathijsuitmegen
你能在 Powershell 和 Powershell (x86) 中运行以下代码,并告诉我每个输出结果是什么吗?([xml](gc $([System.AppDomain]::CurrentDomain.SetupInformation.ConfigurationFile))).configuration.runtime.assemblyBinding.dependentAssembly.assemblyIdentity - DanL
谢谢你的评论,丹!我已经更新了我的问题,并提供了你所请求的信息。似乎64位缺少某些东西。 - mathijsuitmegen
你说得对,丹!Powershell没有读取整个文件。当我在cmd.exe上启动并在同一文件上执行“type”命令时,文件的全部内容都会显示出来(包括元素“runtime”)。 - mathijsuitmegen
你尝试过在记事本中编辑文件以添加运行时设置,然后重新测试吗? - DanL
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接