Powershell: Unable to find type when using PS 5 classes

孤人 提交于 2019-12-03 12:43:35

As you've discovered, PowerShell refuses to run scripts that contains class definitions that reference then-unavailable (not-yet-loaded) types - the script parsing stage fails.

The proper solution is to create a script module (*.psm1) whose associated manifest (*.psd1) declares the assembly containing the referenced types a prerequisite, via the RequiredAssemblies key.

See alternative solution at the bottom if using modules is not an option.

Here's a simplified walk-through:

Create test module tm as follows:

  • Create module folder ./tm and manifest (*.psd1) in it:

    # 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
    
  • Create the script module file (*.psm1) in the module folder:

    Create file ./tm/tm.psm1 with your class definition; e.g.:

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

    Note: In the real world, modules are usually placed in one of the standard locations defined in $env:PSMODULEPATH, so that the module can be referenced by name only, without needing to specify a (relative) path.

Use the module:

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

The using module statement imports the module and - unlike Import-Module - also makes the class defined in the module available to the current session.

Since importing the module implicitly loaded the WinSCP assembly thanks to the RequiredAssemblies key in the module manifest, instantiating class Foo, which references the assembly's types, succeeded.


If your use case doesn't allow the use of modules, you can use Invoke-Expression in a pinch, but note that it's generally better to avoid Invoke-Expression in the interest of robustness and so as to avoid security risks[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] It's not a concern in this case, but generally, given that Invoke-Expression can invoke any command stored in a string, applying it to strings not fully under your control can result in the execution of malicious commands. This caveat applies to other language analogously, such as to Bash's built-in eval command.

Although it's not the solution per se, I worked around it. However, I'll leave the question open as it still stands

Instead of using WinSCP-types, I just use strings. Seeing as I already have enumerals that are identical to WinSCP.Protocol

Enum Protocols {
    Sftp
    Ftp
    Ftps
}

And have set Protocol in FtpSettings

$FtpSettings.Protocol = [Protocols]::Sftp

I can set the protocol like this

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

I used similar on [WinSCP.TransferMode]

$TransferOptions.TransferMode = "Binary" #[WinSCP.TransferMode]::Binary
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!