Permalink
8a0abf4 Sep 24, 2018
3 contributors

Users who have contributed to this file

@bergmeister @TravisEz13 @rkeithhill
232 lines (172 sloc) 9.52 KB

Creating a cross-platform binary module with the .NET Core command-line interface tools

This example uses the .NET Core command-line interface tools (aka dotnet CLI) to demonstrate how to create a binary module that is portable across operating systems supported by PowerShell Core as well as Windows PowerShell version 3 and higher.

Because the binary module's assembly will be created as a .NET Standard 2.0 class library, the same assembly can be imported into both PowerShell Core and Windows PowerShell. This means you do not have to build and distribute separate assemblies that target these two different implementations of PowerShell.

Prerequisites

  • PowerShell Core and/or Windows PowerShell

    For this example, you can use any operating system that is supported by PowerShell Core. To see if your operating system is supported and to get instructions on how to install PowerShell Core on your operating system, see the Get PowerShell topic in the PowerShell repo's README.md file.

    Note: On Windows 10 Anniversary Update or higher, you can use the Windows Subsystem for Linux (WSL) console to build the module. In order to import and use the module, you'll need to install PowerShell Core for the distribution and version of Linux you're running. You can get that version info by running the command lsb_release -a from the WSL console.

  • .NET Core 2.x SDK

    Download and install the .NET Core 2.x SDK for your operating system. It is recommended that you use a package manager to install the SDK on Linux. See these instructions on how to install the SDK on Linux. Be sure to pick your distribution of Linux e.g. RHEL, Debian, etc to get the appropriate instructions for your platform.

Create the .NET Standard 2.0 Binary Module

  1. Verify you are running the 2.0.0 version of the dotnet CLI.

    dotnet --version

    This should output 2.0.0 or higher. If it returns a major version of 1, make sure you have installed the .NET Core 2.x SDK and have restarted your shell to get the newer version of the SDK tools.

  2. Use the dotnet CLI to create a starter classlib project based on .NET Standard 2.0 (the default for classlib projects).

    dotnet new classlib --name MyModule
  3. Add a global.json file that specifies that the project requires the 2.0.0 version of the .NET Core SDK. This is necessary to prevent issues if you have more than one version of the .NET Core SDK installed.

    cd MyModule
    dotnet new globaljson --sdk-version 2.0.0
  4. Add the PowerShell Standard Library package to the project file. This package provides the System.Management.Automation assembly.

    Note: As newer versions of this library are released, update the version number in this command to match the latest version.

    dotnet add package PowerShellStandard.Library --version 3.0.0-preview-01
  5. Add source code for a simple PowerShell command to the Class1.cs file by opening that file in an editor and replacing the existing code with the following code.

    using System;
    using System.Management.Automation;
    
    namespace MyModule
    {
        [Cmdlet(VerbsCommunications.Write, "TimestampedMessage")]
        public class WriteTimestampedMessageCommand : PSCmdlet
        {
            [Parameter(Position=1)]
            public string Message { get; set; } = string.Empty;
    
            protected override void EndProcessing()
            {
                string timestamp = DateTime.Now.ToString("u");
                this.WriteObject($"[{timestamp}] - {this.Message}");
                base.EndProcessing();
            }
        }
    }
  6. Build the project.

    dotnet build
  7. Import the binary module and invoke the new command.

    Note: The previous steps could have been performed in a different shell such as Bash if you're on Linux. For this step, make sure you are running PowerShell Core.

    cd 'bin/Debug/netstandard2.0'
    Import-Module ./MyModule.dll
    Write-TimestampedMessage "Test message."

Using a .NET Standard 2.0 based binary module in Windows PowerShell

You may have heard that a .NET assembly compiled as a .NET Standard 2.0 class library will load into both .NET Core 2.x applications such as PowerShell Core and .NET Framework 4.6.1 (or higher) applications such as Windows PowerShell. This allows you to build a single, cross-platform binary module.

Unfortunately, this works best when the .NET Framework application, in this case Windows PowerShell, has either been compiled against a .NET Standard 2.0 library or with support declared for .NET Standard libraries. In which case, the build system can provide the appropriate binding redirects and facade and shim assemblies so that the .NET Standard 2.0 library can find the .NET Framework types it needs within the context of the running application.

Fortunately, this has been fixed in .NET Framework 4.7.1 and in the Windows 10 Fall Creators Update. This version of the .NET Framework allows existing applications to "just work" without the need to modify and/or re-compile them. On these systems, a .NET Standard 2.0 based binary module will work in Windows PowerShell.

However, for Windows systems that have not been updated to .NET Framework 4.7.1 such a binary module will not run correctly in Windows PowerShell.

Let's see what happens when you attempt use this module in Windows PowerShell on Windows 10 CU (1703 or lower) without .NET Framework 4.7.1 installed.

  1. Copy MyModule.dll to a folder on a Windows machine.

  2. Import the module.

    Import-Module .\MyModule.dll

    Note: The module should import without errors.

  3. Execute the Write-TimestampedMessage command.

    Write-TimestampedMessage "Test message."

    This will result in the following error:

    Write-TimestampedMessage : Could not load file or assembly 'netstandard, Version=2.0.0.0, Culture=neutral,
    PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.
    At line:1 char:1
    + Write-TimestampedMessage "Test message."
    + ~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [], FileNotFoundException
        + FullyQualifiedErrorId : System.IO.FileNotFoundException
    

If the command worked, congratulations! Your system was probably updated to .NET Framework 4.7.1. Otherwise, this error indicates that the MyModule.dll assembly can't find the netstandard.dll "implementation" assembly for the version of the .NET Framework that Windows PowerShell is using.

The fix for missing netstandard.dll

If you install (or already have) the .NET Core SDK for Windows, you can find the netstandard.dll implementation assembly for .NET 4.6.1 in the following directory: C:\Program Files\dotnet\sdk\<version-number>\Microsoft\Microsoft.NET.Build.Extensions\net461\lib. Note that, the version number in the path may vary depending on the installed SDK.

If you copy netstandard.dll from this directory to the directory containing MyModule.dll, the Write-TimestampedMessage command will work. Let's try that.

  1. Install .NET Core SDK for Windows, if it isn't already installed.

  2. Start a new Windows PowerShell console. Remember that once a binary assembly is loaded into PowerShell it can't be unloaded. Restarting PowerShell is necessary to get it to reload MyModule.dll.

  3. Copy the netstandard.dll implementation assembly for .NET 4.6.1 to the module's directory.

    cd 'path-to-where-you-copied-module.dll'
    Copy-Item 'C:\Program Files\dotnet\sdk\<version-number>\Microsoft\Microsoft.NET.Build.Extensions\net461\lib\netstandard.dll' .
  4. Import the module and execute the command:

    Import-Module .\MyModule.dll
    Write-TimestampedMessage "Test message."

    Now the command should succeed.

    Note: If it fails, restart Windows PowerShell to make sure you don't have a previously loaded version of the assembly in the session and repeat step 4.

If you use additional libraries there may be more work involved. This approach has been successfully tested using types from System.Xml and System.Web.

Wrap-up

In a few steps, we have built a PowerShell binary module using a .NET Standard 2.0 class library that will run in PowerShell Core on multiple operating systems. It will also run in Windows PowerShell on Windows systems that have been updated to .NET Framework 4.7.1 as well as the Windows 10 Fall Creators Update which comes with that version pre-installed. Furthermore, this binary module can be built on Linux and macOS as well as Windows using the .NET Core 2.x SDK command-line tools.

For more information on .NET Standard, check out the documentation and the .NET Standard YouTube channel.