【问题标题】:How to load assemblies in PowerShell?如何在 PowerShell 中加载程序集?
【发布时间】:2012-10-07 01:08:05
【问题描述】:

以下 PowerShell 代码

#Get a server object which corresponds to the default instance
$srv = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Server
... rest of the script ...

给出以下错误信息:

New-Object : Cannot find type [Microsoft.SqlServer.Management.SMO.Server]: make sure 
the assembly containing this type is loaded.
At C:\Users\sortelyn\ ... \tools\sql_express_backup\backup.ps1:6  char:8
+ $srv = New-Object -TypeName Microsoft.SqlServer.Management.SMO.Server
+        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
+ FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand

互联网上的每个答案都写着我必须加载程序集 - 很确定我可以从错误消息中读到 :-) - 问题是:

如何加载程序集并使脚本工作?

【问题讨论】:

    标签: powershell powershell-3.0


    【解决方案1】:

    LoadWithPartialName 已被弃用。 PowerShell V3 的推荐解决方案是使用 Add-Type cmdlet,例如:

    Add-Type -Path 'C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Smo.dll'
    

    有多个不同的版本,您可能想要选择一个特定的版本。 :-)

    【讨论】:

    • 好的,我使用 PowerShell3 - 这些包含命令似乎非常复杂。我只希望像“包含文件名”这样的东西。
    • PowerShell 不区分大小写(除非您使用 -cmatch、-ceq 等运算符告诉它区分大小写)。所以命令名称和参数的大小写无关紧要。
    • 是的。 msdn.microsoft.com/en-us/library/12xc5368(v=vs.110).aspx 请参阅顶部的注释 - This API is now obsolete. 当然,这并不能阻止人们使用它。
    • 虽然 LoadWithPartialName 已被弃用在技术上是正确的,但原因(如 blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx 中所述)显然不适用于交互式 Powershell 会话。我建议您添加一个说明,该 API 非常适合交互式 Powershell 使用。
    • 这对我也有帮助 add-Type -Path 'C:\Program Files (x86)\Microsoft SQL Server Management Studio 18\Common7\IDE\Microsoft.SqlServer.Smo.dll'
    【解决方案2】:
    [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
    

    【讨论】:

    • 这太有用了,不能在没有替换的情况下弃用!我的团队混合使用 2008 和 2012 客户端工具。这是使我的 PowerShell 脚本适用于我的所有团队的唯一方法,而不包括笨拙的版本回退逻辑。
    • 如果您不希望 GAC 回显内容,可以将输出通过管道传输到 Out-Null
    • @Baxter -- 你应该接受这个答案或 Keith 的答案,并让这个问题被标记为已回答。
    • 我使用 [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
    • @IainElder "笨拙的版本回退逻辑" 你这么说,直到你遇到版本不兼容! Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...]说起来并不难。
    【解决方案3】:

    现在大多数人都知道System.Reflection.Assembly.LoadWithPartialName 已被弃用,但事实证明Add-Type -AssemblyName Microsoft.VisualBasic does not behave much better than LoadWithPartialName

    而不是尝试在上下文中解析您的请求 在您的系统中,[Add-Type] 会查看一个静态的内部表来翻译 “部分名称”改为“全名”。

    如果您的“部分名称”没有出现在他们的表格中,您的脚本将 失败。

    如果您的系统上安装了多个版本的程序集 计算机,没有智能算法可以在它们之间进行选择。 你会得到他们表中出现的任何一个,可能 旧的,过时的。

    如果您安装的版本都比过时的版本新 在表格中,您的脚本将失败。

    Add-Type 没有像“部分名称”这样的智能解析器 .LoadWithPartialNames.

    Microsoft 的 .Net 团队说您实际上应该这样做:

    Add-Type -AssemblyName 'Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
    

    或者,如果您知道路径,则如下所示:

    Add-Type -Path 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\Microsoft.VisualBasic\v4.0_10.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualBasic.dll'
    

    为程序集指定的长名称称为强名称,它对版本和程序集都是唯一的,有时也称为全名。

    但这留下了几个问题没有得到解答:

    1. 如何使用给定的部分名称确定系统上实际加载的内容的强名称?

      [System.Reflection.Assembly]::LoadWithPartialName($TypeName).Location; [System.Reflection.Assembly]::LoadWithPartialName($TypeName).FullName;

    这些也应该有效:

    Add-Type -AssemblyName $TypeName -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
    
    1. 如果我希望我的脚本始终使用特定版本的 .dll,但我不能确定它的安装位置,我如何确定 .dll 中的强名称是什么?

      [System.Reflection.AssemblyName]::GetAssemblyName($Path).FullName;

    或者:

    Add-Type $Path -PassThru | Select-Object -ExpandProperty Assembly | Select-Object -ExpandProperty FullName -Unique
    
    1. 如果我知道强名称,如何确定 .dll 路径?

      [Reflection.Assembly]::Load('Microsoft.VisualBasic, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a').Location;

    2. 同样,如果我知道我正在使用的类型名称,我怎么知道它来自哪个程序集?

      [Reflection.Assembly]::GetAssembly([Type]).Location [Reflection.Assembly]::GetAssembly([Type]).FullName

    3. 我如何查看可用的程序集?

    我建议GAC PowerShell moduleGet-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullName 效果很好。

    1. 如何查看Add-Type 使用的列表?

    这有点复杂。我可以描述如何使用 .Net 反射器访问任何版本的 PowerShell(请参阅下面的 PowerShell Core 6.0 更新)。

    首先,弄清楚Add-Type来自哪个库:

    Get-Command -Name Add-Type | Select-Object -Property DLL
    

    使用反射器打开生成的 DLL。我为此使用了ILSpy,因为它是 FLOSS,但任何 C# 反射器都应该可以工作。打开那个库,查看Microsoft.Powershell.Commands.Utility。在Microsoft.Powershell.Commands 下,应该有AddTypeCommand

    在代码清单中,有一个私有类InitializeStrongNameDictionary()。这列出了将短名称映射到强名称的字典。我查看的库中有近 750 个条目。

    更新: 现在 PowerShell Core 6.0 是开源的。对于那个版本,你可以跳过上面的步骤,直接看代码online in their GitHub repository。但是,我不能保证该代码与任何其他版本的 PowerShell 匹配。

    更新 2: Powershell 7+ 似乎不再具有哈希表查找功能。相反,他们使用a LoadAssemblyHelper() method,cmets 将其称为 LoadWithPartialName 的“可能的最接近的近似值”。基本上,他们这样做:

    loadedAssembly = Assembly.Load(new AssemblyName(assemblyName));
    

    现在,cmets 还说“用户可以说Add-Type -AssemblyName Forms (而不是 System.Windows.Forms)”。但是,这不是我在 Windows 10 2004 上的 Powershell v7.0.3 中看到的。

    # Returns an error
    Add-Type -AssemblyName Forms
    
    # Returns an error
    [System.Reflection.Assembly]::Load([System.Reflection.AssemblyName]::new('Forms'))
    
    # Works fine
    Add-Type -AssemblyName System.Windows.Forms
    
    # Works fine
    [System.Reflection.Assembly]::Load([System.Reflection.AssemblyName]::new('System.Windows.Forms'))
    

    所以 cmets 似乎有点神秘。

    当没有指定版本或公钥令牌时,我不知道Assembly.Load(AssemblyName) 中的确切逻辑是什么。我希望这有许多与 LoadWithPartialName 相同的问题,如果您安装了多个,可能会加载错误版本的程序集。

    【讨论】:

    • 第三个未回答的问题:如果我不想要求特定版本怎么办?
    • @jpmc26 好吧,您可以只使用Add-TypeLoadWithPartialName(),但您需要注意,前者不会在各个版本之间保持 100% 一致,而后者已过时方法。换句话说,.Net 希望您关心您加载的库的版本。
    • @BaconBits jpmc26 问题的完整答案是,根据您使用的是 PowerShell 5 还是 PowerShell 6,加载的程序集可能会有所不同。 JSON.NET 在 Azure PS 函数中存在这个问题。
    • @BaconBits 这是对 PowerShell 的真正精彩的深入探讨。你应该写一本书。
    • @KolobCanyon 因为在这种情况下,您通常应该使用Add-Type -Path,这是提到的第二个代码,或者Assembly.LoadFrom(),它为您解决依赖关系(据我所知,是Add-Type -Path 使用)。唯一应该使用Assembly.LoadFile() 的情况是,如果您需要加载具有相同标识但路径不同的多个程序集。这是一个奇怪的情况。
    【解决方案4】:

    如果您想加载程序集在 PowerShell 会话期间不锁定它,请使用以下命令:

    $bytes = [System.IO.File]::ReadAllBytes($storageAssemblyPath)
    [System.Reflection.Assembly]::Load($bytes)
    

    $storageAssemblyPath 是程序集的文件路径。

    如果您需要清理会话中的资源,这尤其有用。例如在部署脚本中。

    【讨论】:

    • ? ? 棒极了。因为在 Visual Studio 中,调试 Powershell 时,PS 会话在执行后会挂起(通过 PowerShellToolsProcessHost)。这种方法解决了这个问题。谢谢。
    • 我打赌 Azure DevOps 也会继续使用它,如果属实,这将使其成为管道任务的合适方法。 | @CJBS
    【解决方案5】:

    这里有一些博客文章,其中包含大量在 PowerShell v1、v2 和 v3 中加载程序集的示例。

    方式包括:

    • 从源文件动态地
    • 从程序集中动态地
    • 使用其他代码类型,例如 F#

    v1.0 How To Load .NET Assemblies In A PowerShell Session
    v2.0 Using CSharp (C#) code in PowerShell scripts 2.0
    v3.0 Using .NET Framework Assemblies in Windows PowerShell

    【讨论】:

      【解决方案6】:

      您可以使用

      加载整个 *.dll 程序集
      $Assembly = [System.Reflection.Assembly]::LoadFrom("C:\folder\file.dll");
      

      【讨论】:

        【解决方案7】:

        没有一个答案对我有帮助,所以我发布了对我有用的解决方案,我所要做的就是导入 SQLPS 模块,当我偶然运行 Restore-SqlDatabase 命令并开始工作时,我意识到了这一点,这意味着该程序集以某种方式在该模块中被引用。

        只要运行:

        Import-module SQLPS
        

        注意:感谢 Jason 指出 SQLPS 已弃用

        改为运行:

        Import-Module SqlServer
        

        Install-Module SqlServer
        

        【讨论】:

        • 对于使用这种方法的任何人,仅供参考,sqlps 已被弃用,而支持模块 sqlserver
        【解决方案8】:

        [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") 为我工作。

        【讨论】:

          【解决方案9】:

          您可以使用LoadWithPartialName。但是,正如他们所说的那样,这已被弃用。

          您确实可以使用Add-Type,除了其他答案之外,如果您不想指定 .dll 文件的完整路径,您可以简单地这样做:

          Add-Type -AssemblyName "Microsoft.SqlServer.Management.SMO"
          

          对我来说,这返回了一个错误,因为我没有安装 SQL Server(我猜),但是,以同样的想法,我能够加载 Windows 窗体程序集:

          Add-Type -AssemblyName "System.Windows.Forms"
          

          您可以在 MSDN 网站上找到属于特定类的确切程序集名称:

          【讨论】:

            【解决方案10】:

            确保您已按顺序安装以下功能

            1. SQL Server 的 Microsoft System CLR 类型
            2. Microsoft SQL Server 共享管理对象
            3. Microsoft Windows PowerShell 扩展

            另外你可能需要加载

            Add-Type -Path "C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.Smo.dll"
            Add-Type -Path "C:\Program Files\Microsoft SQL Server\110\SDK\Assemblies\Microsoft.SqlServer.SqlWmiManagement.dll"
            

            【讨论】:

            • 我花了一周时间尝试加载程序集,并没有看到加载它们的语句的任何输出,但是当我尝试使用它时,我得到了错误。当我安装这三个东西时,它工作。 - 谢谢
            【解决方案11】:

            在顶部添加程序集引用。

            #Load the required assemblies SMO and SmoExtended.
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null
            

            【讨论】:

            • 你能举个例子吗?
            • 您只需将其添加到 powershell 脚本的开头即可。例如:创建数据库备份:[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO") | Out-Null [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SmoExtended") | Out-Null $SQLServer= Read-Host -Prompt 'SQL Server name(optional) ' IF ([string]::IsNullOrWhitespace($SQLServer)){$SQLServer="XXX";} $SQLDBName = Read-Host -Prompt ' SQL 数据库名称(可选)' IF ([string]::IsNullOrWhitespace($SQLDBName)){$SQLDBName="XXX";} $SQLLogin = Read-Host -Prompt 'Login'
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-03-24
            • 2014-10-06
            • 1970-01-01
            • 1970-01-01
            • 2015-10-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多