powershelldlldllimport

How can I import/load a .dll file to use in a PowerShell script without getting "TypeNotFound" error?


 [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?


Solution

  • tl;dr:


    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]

    Workarounds:


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

    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.