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

时间:2017-03-16 14:49:55

标签: powershell winscp-net add-type

我在PS中使用WinSCP Powershell程序集中的类。在其中一种方法中,我使用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
        }

Protocol = [WinSCP.Protocol] :: $ ConnectionType

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

4 个答案:

答案 0 :(得分:10)

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

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

如果不能选择使用模块,请参阅底部的替代解决方案。

这是简化演练

按如下方式创建测试模块tm

  • 在其中创建模块文件夹./tm和清单(*.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
    
  • 在模块文件夹中创建脚本模块文件(*.psm1):

    使用您的类定义创建文件./tm/tm.psm1; e.g:

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

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

使用模块

PS> using module ./tm; (New-Object Foo).Bar()
WinSCP.Protocol

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()

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

答案 1 :(得分:1)

虽然它本身不是解决方案,但我解决了这个问题。但是,我将问题保持开放,因为它仍然存在

我只使用字符串,而不是使用WinSCP类型。因为我已经拥有与WinSCP.Protocol

相同的枚举
$FtpSettings.Protocol = [Protocols]::Sftp

并在FtpSettings中设置了协议

$SessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = $this.FtpSettings.Protocol.ToString()
            HostName = $this.FtpSettings.Server
            UserName = $this.FtpSettings.Username
            Password = $this.FtpSettings.Password
        }

我可以像这样设置协议

$TransferOptions.TransferMode = "Binary" #[WinSCP.TransferMode]::Binary

我在[WinSCP.TransferMode]上使用了类似的东西

dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING))

答案 2 :(得分:0)

首先,我会推荐mklement0的答案。

但是,您可以做一些工作以减少工作量而获得大致相同的效果,这对于较小的项目或早期阶段很有帮助。

可能只是。在代码中找到另一个ps1文件,该文件包含您在加载引用的程序集后引用尚未加载的库的类。

##########
MyClasses.ps1

Class myClass
{
     [3rdParty.Fancy.Object] $MyFancyObject
}

然后,您可以使用。从主脚本中调用自定义类库。

#######
MyMainScriptFile.ps1

#Load fancy object's library
Import-Module Fancy.Module #If it's in a module
Add-Type -Path "c:\Path\To\FancyLibrary.dll" #if it's in a dll you have to reference

. C:\Path\to\MyClasses.ps1

原始解析将通过集合,脚本将开始,您的引用将被添加,然后随着脚本的继续,。源文件将被读取和解析,添加自定义类没有问题,因为它们的引用库在解析代码时已在内存中。

用适当的清单制作和使用模块仍然要好得多,但这很容易实现,并且很容易记住和使用。

答案 3 :(得分:0)

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