Powershell:使用PS 5类时无法找到类型

dbs*_*bso 8 powershell add-type winscp-net

我在PS中使用WinSCP Powershell Assembly.在其中一种方法中,我使用的是WinSCP中的各种类型.

只要我已经添加了程序集,这就可以正常工作 - 但是,由于Powershell在使用类时读取脚本的方式(我假设?),在加载程序集之前会抛出错误.

实际上,即使我将Write-Host放在顶部,它也不会加载.

在解析文件的其余部分之前,有没有办法强制运行某些东西?

Transfer() {
    $this.Logger = [Logger]::new()
    try {

        Add-Type -Path $this.Paths.WinSCP            
        $ConnectionType = $this.FtpSettings.Protocol.ToString()
        $SessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::$ConnectionType
            HostName = $this.FtpSettings.Server
            UserName = $this.FtpSettings.Username
            Password = $this.FtpSettings.Password
        }
Run Code Online (Sandbox Code Playgroud)

Protocol = [WinSCP.Protocol] :: $ ConnectionType

无法找到类型[WinSCP.Protocol].

mkl*_*nt0 12

正如您所发现的,PowerShell拒绝运行包含引用当时不可用(尚未加载)类型的类定义的脚本 - 脚本解析阶段失败.

正确的解决方案是创建一个脚本模块(*.psm1),其关联的manifest(*.psd1)通过RequiredAssemblies密钥声明包含引用类型的程序集作为先决条件.

如果不能使用模块,请参见底部的替代解决方案.

这是一个简化的演练:

创建测试模块tm如下:

  • 在其中创建模块文件夹./tm和manifest(*.psd1):

    # Create module folder
    mkdir ./tm
    
    # Create manifest file that declares the WinSCP assembly a prerequisite.
    # Modify the path to the assembly as needed; you may specify a relative path, but
    # note that the path must not contain variable references (e.g., $HOME).
    New-ModuleManifest ./tm/tm.psd1 -RootModule tm.psm1 `
      -RequiredAssemblies C:\path\to\WinSCPnet.dll
    
    Run Code Online (Sandbox Code Playgroud)
  • *.psm1在模块文件夹中创建脚本模块文件():

    ./tm/tm.psm1使用类定义创建文件; 例如:

    class Foo {
      # Simply return the full name of the WinSCP type.
      [string] Bar() {
        return [WinSCP.Protocol].FullName
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    注意:在现实世界中,模块通常放置在其中定义的标准位置之一中$env:PSMODULEPATH,因此模块只能通过名称引用,而无需指定(相对)路径.

使用模块:

PS> using module ./tm; (New-Object Foo).Bar()
WinSCP.Protocol
Run Code Online (Sandbox Code Playgroud)

using module语句导入模块 - 与 - 不同Import-Module- 也使模块中定义的可用于当前会话.

由于RequiredAssemblies模块清单中的键导入模块隐式加载了WinSCP程序集,因此实例化Foo引用程序集类型的类成功.


如果您的用例不允许使用模块,您可以Invoke-Expression在紧要关头使用,但请注意,Invoke-Expression为了避免安全风险,通常最好避免使用,以避免安全风险[1] .

# Adjust this path as needed.
Add-Type -LiteralPath C:\path\to\WinSCPnet.dll

# By placing the class definition in a string that is invoked at *runtime*
# via Invoke-Expression, *after* the WinSCP assembly has been loaded, the
# class definition succeeds.
Invoke-Expression @'
class Foo {
  # Simply return the full name of the WinSCP type.
  [string] Bar() {
    return [WinSCP.Protocol].FullName
  }
}
'@

(New-Object Foo).Bar()
Run Code Online (Sandbox Code Playgroud)

[1]在这种情况下,这并不是一个问题,但通常情况下,如果Invoke-Expression可以调用存储在字符串中的任何命令,将其应用于不完全受您控制的字符串可能会导致执行恶意命令.这个警告类似地适用于其他语言,例如Bash的内置eval命令.


Jus*_*ote 5

另一种解决方案是将您的 Add-Type 逻辑放入单独的 .ps1 文件中(命名它AssemblyBootStrap.ps1或其他名称),然后将其添加到ScriptsToProcess模块清单的部分。ScriptsToProcess在根脚本模块 ( *.psm1) 之前运行,并且程序集将在类定义查找它们时加载。