Dot-Net

Powershell - 在應用程序配置文件中找不到程序集綁定重定向

  • June 5, 2020

這裡有問題…

我有一個 Powershell CmdLet,它在 32 位模式下執行時工作,在 64 位模式下失敗。問題是原因是什麼以及如何解決。

情況

引用“OutlookHelper.Common.dll”的 Powershell CmdLet。最新版本是 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>

我試過的…

  • 檢查“特定版本”是否設置為“假”-> 是這種情況。
  • 刪除並重新添加了應用程序配置文件 -> 沒有修復它。
  • 從 SysWOW64 文件夾中的新應用程序配置文件開始 -> 沒有修復它。
  • 仔細檢查載入的文件的內容(powershell.exe.Config 和 machine.config)-> 它們是相同的。

我猜

  • 程序集管理器找不到程序集重定向綁定。

有什麼解決辦法嗎?

  • 為什麼 64 位實例的 Fusion 日誌沒有提及“在應用程序配置文件中找到重定向:1.0.0.0 重定向到 2.0.0.0。”之類的內容?
  • 這一切的原因是什麼?
  • 你能想出什麼解決辦法嗎?

不是 100% 與 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 非常有用的答案,我開始在我的 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 似乎有一個錯誤,在呼叫某些命令(如 Out-GridView)時,簡單地註冊程序集解析腳本塊可能會導致 StackOverflowException。我更新了程式碼以使用使用 Add-Type 編譯的版本,似乎可以解決問題。

引用自:https://stackoverflow.com/questions/26652204