Develop Binary Cmdlets for PowerShell Core on Mac OS X

Have you wanted to develop binary cmdlets using C# .NET for PowerShell Core on Mac OS X or Linux? If so, you and I are in the same boat. In this document, I’ll cover the steps necessary to develop C# cmdlets and package them up into a PowerShell module. We’ll build and test on Mac OS X using Visual Studio Code, .NET Core, and PowerShell Core.

There are some outdated directions on this StackOverflow Q&A that I worked off of. Also, special thanks to David Wilson for helping me get unblocked in writing this article!

Environment Setup

First, you’ll need to prepare your Mac environment to start developing binary PowerShell modules.

You might have to install these in a different order than listed above, but these are the essentials that you’ll need.

Create .NET Core Class Library Project

Once your software development environment is set up, let’s start creating our project! First, create a folder for your project, and create a new class library project.

mkdir ~/coremodule && cd ~/coremodule && dotnet new classlib

Now that you’ve created the project, open the current directory in Visual Studio Code.

code .

Set up NuGet Configuration

As of this writing, the PowerShell SDK is hosted via a couple of non-standard NuGet feeds. Create a nuget.config file in the root of your project and place the contents below inside of it, and of course, save the file. You can visually browse the .NET Core MyGet feed and the PowerShell Core MyGet feed.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="powershell-core" value="https://powershell.myget.org/F/powershell-core/api/v3/index.json" />
    <add key="CI Builds (dotnet-core)" value="https://www.myget.org/F/dotnet-core/api/v3/index.json" />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
  </packageSources>
</configuration>

 Change the Target .NET Framework

At this point, you should have a project open in Visual Studio Code, with just two files:

  • C# source file (.cs file extension)
  • .NET Core Project file (.csproj file extension)

The default class library template in .NET Core references the .NET Standard 1.4 framework. You’ll need to change this to target the .NET Standard 1.6 framework instead. You can copy / paste the code below directly under the root <Project> XML node in your .csproj file.

<PropertyGroup>
  <TargetFramework>netstandard1.6</TargetFramework>
</PropertyGroup>

Add the PowerShell SDK Dependency

Open your .csproj file, if you closed it, and add the Microsoft.PowerShell.SDK project dependency. You can copy / paste the code below underneath the root XML node <Project> in your .csproj. Keep in mind that newer versions of the dependency may be released, so check the NuGet URL to see what versions are available.

<ItemGroup>
  <PackageReference Include = "Microsoft.PowerShell.SDK" Version = "6.0.0-alpha18" />
</ItemGroup>

Restore .NET Core Dependencies

Now that you’ve setup the custom NuGet feeds and added a package reference to Microsoft.PowerShell.SDK, you are ready to restore dependencies. To do this, simply click the button when prompted in Visual Studio Code or run this line in your terminal:

dotnet restore

Now that you’ve restored your .NET Core project dependencies, you’re almost ready to start writing PowerShell cmdlet code! One more thing we need to take care of is creating our PowerShell module manifest (.psd1 file).

Create a PowerShell Module Manifest

To create a proper PowerShell module, you need to add a PowerShell module manifest. The PowerShell module manifest contains metadata that describes your PowerShell modules, such as which commands, aliases, and DSC resources are “exported” (publicly visible) from the PowerShell module. Creating the manifest is very easy, especially if you have the PowerShell extension for Visual Studio Code installed.

Create a new file in the root of your project named coremodule.psd1. As long as you have the PowerShell Extension for VSCode installed, you can simply type “manifest” and hit ENTER to invoke a code snippet. That will give you the skeleton of the module manifest, and you can fill out the details. Most importantly, you’ll need to fill out the NestedModules array. This simply references the .NET assemblies that make up your PowerShell module. You can leave the RootModule key empty.

Set Copy Operation for PowerShell Module Manifest

You’ll need to copy the module manifest, that you just created, into the bin/debug directory each time that you update your code and run a build operation. We can use the .csproj file to trigger a file copy operation each time we run a build.

Inside the <PropertyGroup> XML node, add the following node:

<None Include="coremodule.psd1" CopyToOutputDirectory="Always" />

Write Your Code

At this point, you can start writing your PowerShell code. You’ll want to add a using statement to access the System.Management.Automation namespace, where the majority of the PowerShell SDK exists. You’ll use the [CmdletAttribute()] to define the verb and noun that you want each cmdlet class to have. You’ll inherit from the Cmdlet or PSCmdlet class, depending on how much PowerShell metadata you want the cmdlet to have access to. Finally, you’ll implement method overrrides for the virtual methods that exist on the base class, such as ProcessRecord().

In your Class1.cs file, add the following code:

using System.Management.Automation;

namespace coremodule {
  public class Person {
   public string FirstName { get; set; }
   public string LastName { get; set; }

   public Person(string first, string last) {
     this.FirstName = first;
     this.LastName = last;
   }
  }

  [CmdletAttribute(VerbsCommon.New, "Person")]
  public class NewPersonCmdlet : Cmdlet {
    protected override void ProcessRecord() {
      WriteObject(new Person("Trevor", "Sullivan"));
    }
  }
}

This will serve as a very basic cmdlet implementation, which isn’t terribly useful, but at least it demonstrates the basics.

Build Your PowerShell Module

Once you’ve written a PowerShell cmdlet, it’s time to build the .NET Core project. Running a .NET build will compile the project into a Dynamic Link Library (DLL), also known as a .NET assembly. In your terminal session, navigate to the root of your project directory and run:

dotnet build

See? Building your project was easy.

Import the Module

After building your PowerShell module project, you can import the module into a new PowerShell session. It’s technically possible to import a valid .NET Core assembly (DLL) directly, you should always import the module manifest, for good practice. Open PowerShell, change into your bin/Debug directory, and then run the command below.

Import-Module -Name coremodule.psd1

After importing a binary PowerShell module, the .NET Core framework will place a lock on the DLL’s loaded into the runtime. You’ll need to either manually remove the file from the filesystem or quit the PowerShell processes that have a lock on the file, so you can run a new build.

Conclusion

While there’s quite a few steps to take into account when building a binary module on PowerShell Core, it’s a pretty straightforward process. Now you can write native PowerShell cmdlets on non-Windows platforms! Have fun creating new things!