3
 [void] CreateSession() {
    try {
        # Load WinSCP .NET assembly
        Add-Type -Path (Join-Path $PSScriptRoot "\winscp\WinSCPnet.dll")
        # Setup session options
        $this.sessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol = [WinSCP.Protocol]::Sftp 

In the above section of code I encounter the "TypeNotFound" error message regarding "[WinSCP.Protocol]".

  14 |                  Protocol = [WinSCP.Protocol]::Sftp
     |                              ~~~~~~~~~~~~~~~
     | Unable to find type [WinSCP.Protocol].

The .dll file can load correctly, I have verified this previously. I know what is happening is that the PowerShell "parser" is throwing an error because it doesn't recognize the WinSCP library at load time. I have tried adding a module and manifest, but I cannot find a simple example of how to do this properly. Also, it doesn't matter if I'm running PowerShell 5.x or 7.x. All I am wanting is to load this DLL so I can use classes/functions from it. Why is loading a DLL so hard in PowerShell?

What do I need to do to get this WinSCP DLL to load at runtime and not throw an error?

Note on possible duplicates

A very similar question was asked on this site a couple years ago by someone, but there are no answers to it.

Note on suggested duplicate

I am looking for a real example for how to load a DLL file into a script. The linked question does not appropriately do that. Why do I need to create a manifest module thing to import the DLL?

halfer
  • 19,824
  • 17
  • 99
  • 186
CamParker
  • 105
  • 2
  • 9

2 Answers2

5

tl;dr:

  • The problem stems from trying to reference the just-loaded WinSCP types in a class definition, via type literals, such as [WinSCP.Protocol], as explained below.

  • The problem can be bypassed by not using classes at all, and using functions instead.


I am looking for a real example for how to load a DLL file into a script.

Add-Type -Path / -LiteralPath, as shown in your code does just that:

It loads the specified .NET assembly and makes its public types available in the calling session, just like the similar using assembly statement.

However, since you're using a class definition attempting to reference a type from an assembly you are loading from the same script file via a type literal (e.g, [WinSCP.Protocol]), the class definition fails:

At script-file parse time, all types being referenced by a class definition as type literals (e.g. [WinSCP.Protocol]) - whether as property types, in the body of methods, or as a base class / interface to implement (though in the latter case the type literal has no [...] enclosure; e.g. class Foo : WinSCP.Protocol { ... }) - must already have been loaded into the session, as of PowerShell 7.3.6.[1]

  • Removing this counterintuitive requirement for the using assembly statement was green-lit in 2017, but hasn't been implemented as of this writing: see GitHub issue #3641.

Workarounds:

  • This answer offers two solutions:

  • This answer offers a simple two-script solution:

    • One script that loads the dependent assembly first, and then dot-sources another that contains the class definition based on the dependent assembly's types. 

A workaround isn't always needed, namely if you avoid use of type literals, such as [WinSCP.Protocol]

  • With respect to [WinSCP.SessionOptions], you're already doing that by using New-Object WinSCP.SessionOptions instead of the more modern (PSv5+) [WinSCP.SessionOptions]::new()

  • You can also avoid it for the [WinSCP.Protocol]::Sftp enumeration value by simply using a string - 'Sftp' instead - at least for the code snippet shown this would solve your problem; here's a simplified example:

class Foo {
  # Note: Do NOT use [WinSCP.SessionOptions] here.
  [object] $sessionOptions 
  [void] CreateSession() {
      # Load WinSCP .NET assembly
      Add-Type -LiteralPath (Join-Path $PSScriptRoot "\winscp\WinSCPnet.dll")
      # Set up session options
      # Note the use of *string* 'Sftp' in lieu of [WinSCP.Protocol]::Sftp
      $this.sessionOptions = New-Object WinSCP.SessionOptions -Property @{
        Protocol = 'Sftp'  
      }
  }
}

Now you can instantiate [Foo] as you normally would - either with New-Object Foo or, preferable with [Foo]::new() (once [Foo] itself is successfully defined, it's fine to refer to it by a type literal, outside class definitions).


[1] Classes were a relatively late addition to the PowerShell language, and, unfortunately, there are still many problems to be worked out - see the list of pending issues in GitHub issue #6652. Note, however, that feature parity with, say, C# classes was never the aim.

mklement0
  • 382,024
  • 64
  • 607
  • 775
-2

The easiest solution, and the one I have resolved to use, is to just use functions and not classes in PowerShell.

Just use functions, not classes in PowerShell.

CamParker
  • 105
  • 2
  • 9