如何在PowerShell中加载程序集?

172
以下是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

互联网上的每个答案都写道我必须加载程序集 - 当然,我可以从错误消息中读到这个 :-) - 问题是:

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

11个回答

200

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

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

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


2
好的,我使用PowerShell3 - 这些包括命令似乎非常复杂。我只希望像“包括文件名”这样简单的命令。 - Baxter
6
PowerShell是不区分大小写的(除非你使用像-cmatch、-ceq这样的操作符来强制区分大小写),因此命令名称和参数的大小写没有影响。 - Keith Hill
5
是的。请看顶部的注释 - “此 API 现已过时”。 当然,这并不妨碍人们继续使用它。 - Keith Hill
2
虽然 LoadWithPartialName 已被弃用,但是根据 http://blogs.msdn.com/b/suzcook/archive/2003/05/30/57159.aspx 中所述的原因,在交互式 Powershell 会话中显然不适用。我建议您添加一条注释,说明该 API 在交互式 Powershell 中使用是可以的。 - Micha Wiedenmann
1
这对我也有帮助 add-Type -Path 'C:\Program Files (x86)\Microsoft SQL Server Management Studio 18\Common7\IDE\Microsoft.SqlServer.Smo.dll' - GoldenAge
显示剩余2条评论

80
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")

10
这个工具非常有用,不应该被废弃而没有替代品!我的团队使用2008年和2012年的客户端工具混合。这是唯一的方法,可以让我的PowerShell脚本适用于整个团队,而无需包含笨拙的版本回退逻辑。 - Iain Samuel McLean Elder
4
如果您不想让全局程序集缓存(GAC)回显信息,您可以将输出导入“Out-Null”中。 - Iain Samuel McLean Elder
3
@Baxter——你应该接受这个答案或者Keith的答案,并且让这个问题被标记为已回答。 - Jaykul
3
我使用 [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")。 - Soeren L. Nielsen
@IainElder "笨拙的版本回退逻辑" 你这样说,直到你遇到版本不兼容的情况!这并不难,只需说Add-Type -Path [...]; if (!$?) { Add-Type -Path [...] } elseif [...] - Bacon Bits

65

大部分人现在都知道System.Reflection.Assembly.LoadWithPartialName已经过时了,但是事实证明Add-Type -AssemblyName Microsoft.VisualBasic的表现并不比LoadWithPartialName更好:(参见此链接)

相比于在上下文中解析请求,[Add-Type]只看一个静态的内部表来将“partial name”翻译成“full name”。

如果你的“partial name”没有出现在他们的表中,你的脚本会失败。

如果你的电脑上安装了多个版本的程序集,[Add-Type]没有智能算法来选择它们之间的区别。你只会得到出现在他们表中的那个,很可能是更老、过时的那个。

如果你安装的所有版本都比表中的过时版本新,你的脚本也会失败。

.LoadWithPartialNames有着Add-Type所没有的智能“partial names”解析器。

微软的 .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 模块Get-GacAssembly -Name 'Microsoft.SqlServer.Smo*' | Select Name, Version, FullName 的效果相当好。

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

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

首先,找出 Add-Type 是来自哪个库:

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

用你的反射器打开生成的DLL,我用ILSpy ,因为它是自由软件,但任何C#反射器都应该可以使用。打开该库,并查看 Microsoft.Powershell.Commands.Utility。在Microsoft.Powershell.Commands下,应该有AddTypeCommand

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

更新:现在PowerShell Core 6.0是开源的。对于该版本,您可以跳过上述步骤,直接在他们的GitHub存储库上在线查看代码。但我不能保证该代码与PowerShell的任何其他版本匹配。

更新2: Powershell 7+似乎不再使用哈希表查找。相反,他们使用一个LoadAssemblyHelper()方法,该注释称其为“最接近LoadWithPartialName的近似方法”。基本上,他们这样做:

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

现在,评论中也提到“用户可以只说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'))

因此,评论似乎有些神秘。

当未指定版本或公钥令牌时,我不知道Assembly.Load(AssemblyName)中的逻辑是什么。我预期这会遇到与LoadWithPartialName相同的许多问题,如在安装了多个程序集的情况下可能加载错误版本的程序集。


第三个未回答的问题:如果我不想要求特定版本怎么办? - jpmc26
1
@jpmc26 嗯,你可以使用 Add-TypeLoadWithPartialName(),但你需要知道前者在不同版本中不会完全一致,而后者是一种过时的方法。换句话说,.Net希望你关注加载的库的版本。 - Bacon Bits
@BaconBits 对于jpmc26的问题的完整答案是,取决于您使用的是PowerShell 5还是PowerShell 6,加载的程序集可能会有所不同。JSON.NET在Azure PS函数中存在此问题。 - John Zabroski
@BaconBits 这是一次真正深入 PowerShell 的奇妙探索。你应该写一本书。 - John Zabroski
1
@KolobCanyon 因为在这种情况下,你通常应该使用 Add-Type -Path,这是第二个提到的代码,或者使用 Assembly.LoadFrom(),它会为您解析依赖项(据我所知,这就是 Add-Type -Path 使用的方法)。唯一需要使用 Assembly.LoadFile() 的时候是当您需要加载具有相同标识但不同路径的多个程序集时。那是一个奇怪的情况。 - Bacon Bits
显示剩余2条评论

29

如果你想在 PowerShell 会话期间不锁定程序集,可以使用以下代码:

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

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

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


2
太棒了。因为在Visual Studio中调试Powershell时,PS会话在执行后仍然挂起(通过PowerShellToolsProcessHost)。这种方法可以解决这个问题。谢谢。 - CJBS
我敢打赌Azure DevOps也会保留它,如果是真的,这将使其成为管道任务的合适方法。| @CJBS - InteXX

16

14

您可以使用以下方式加载整个 *.dll 程序集:

$Assembly = [System.Reflection.Assembly]::LoadFrom("C:\folder\file.dll");

4

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

只需运行:

Import-module SQLPS

注意:感谢Jason指出SQLPS已被弃用
请改为运行:
Import-Module SqlServer

或者

Install-Module SqlServer

2
对于使用此方法的任何人,请注意 sqlps 已被弃用,建议使用模块 sqlserver - Jason

2

请确保按照以下顺序安装以下功能:

  1. Microsoft System CLR Types for SQL Server
  2. Microsoft SQL Server Shared Management Objects
  3. Microsoft Windows PowerShell Extensions

另外,您可能需要加载

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"

我花了一周的时间尝试加载程序集,但是没有看到任何载入语句的输出,但当我尝试使用它时,却收到了错误提示。当我安装了这三个组件之后,它就正常工作了。- 谢谢 - pparas

2

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") 对我很有效。


该代码段是使用PowerShell加载Microsoft.SqlServer.Smo程序集的示例。

2
你可以使用LoadWithPartialName,但是它已经被弃用了。
你也可以采用Add-Type。除了其他答案之外,如果你不想指定.dll文件的完整路径,只需简单地执行以下操作即可:
Add-Type -AssemblyName "Microsoft.SqlServer.Management.SMO"

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

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

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

查找属于特定类的程序集名称示例


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