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.
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.
Verify you are running the 2.0.0 version of the
dotnet
CLI.
--version dotnet
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.
Use the dotnet
CLI to create a starter
classlib
project based on .NET Standard 2.0 (the default
for classlib projects).
--name MyModule dotnet new classlib
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
--sdk-version 2.0.0 dotnet new globaljson
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.
.Library --version 3.0.0-preview-01 dotnet add package PowerShellStandard
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();
}
}
}
Build the project.
dotnet build
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
-TimestampedMessage "Test message." Write
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 to use this module in Windows PowerShell on Windows 10 CU (1703 or lower) without .NET Framework 4.7.1 installed.
Copy MyModule.dll
to a folder on a Windows
machine.
Import the module.
Import-Module .\MyModule.dll
Note: The module should import without errors.
Execute the Write-TimestampedMessage
command.
-TimestampedMessage "Test message." Write
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.
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.
Install .NET Core SDK for Windows, if it isn't already installed.
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
.
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' .
Import the module and execute the command:
Import-Module .\MyModule.dll
-TimestampedMessage "Test message." Write
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
.
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.