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?

0

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

-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.

2 Comments

Please provide additional details in your answer. As it's currently written, it's hard to understand your solution.
PowerShell do support using classes as well. But that wasn't the question...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.