Contents
This is the first of a series of blog posts that will help you take advantage of a new NuGet package PowerShellStandard Library 5.1.0. This package allows developers to create modules that are portable between Windows PowerShell 5.1 and PowerShell Core 6.0. This means that you can create PowerShell modules that run on Windows, Linux, and macOS with a single binary!
The version of PowerShell Standard Library indicates the lowest version of PowerShell that it is compatible with. The community promise is that it is always forward compatible. So a module built against PowerShell Standard Library v3 is compatible with Windows PowerShell v3, v4, v5.1, PowerShell Core 6, and the upcoming PowerShell Core 6.1. Compatibility is achieved by providing a subset of the APIs common across all those versions of PowerShell. This reference assembly is the equivalent to a header file for C/C++ where it has the APIs defined, but no implementation. During runtime, the module would use the version of System.Management.Automation.dll that is used by the PowerShell host.
Creating a PowerShell Module
In this post, I’ll walk through the steps for creating a simple C# module with a single cmdlet. I will also be using the DotNet CLI tools for creating everything I need.
Installing the PowerShell Standard Module Template
First, we can leverage a new template that we published for DotNet CLI, but we need to install it first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | PS<span class="pl-k">></span> dotnet new <span class="pl-k">-</span>i Microsoft.PowerShell.Standard.Module.Template Restoring packages <span class="pl-k">for</span> C:UsersJames.templateenginedotnetcliv2.<span class="pl-c1">1.302</span>scratchrestore.csproj... Installing Microsoft.PowerShell.Standard.Module.Template <span class="pl-c1">0.1</span>.<span class="pl-c1">3</span>. Generating MSBuild file C:UsersJames.templateenginedotnetcliv2.<span class="pl-c1">1.302</span>scratchobjrestore.csproj.nuget.g.props. Generating MSBuild file C:UsersJames.templateenginedotnetcliv2.<span class="pl-c1">1.302</span>scratchobjrestore.csproj.nuget.g.targets. Restore completed <span class="pl-k">in</span> <span class="pl-c1">1.66</span> sec <span class="pl-k">for</span> C:UsersJames.templateenginedotnetcliv2.<span class="pl-c1">1.302</span>scratchrestore.csproj. Usage: new [<span class="pl-k">options</span>] Options: <span class="pl-k">-</span>h<span class="pl-k">,</span> <span class="pl-k">--</span>help Displays help <span class="pl-k">for</span> this command. <span class="pl-k">-</span>l<span class="pl-k">,</span> <span class="pl-k">--</span>list Lists templates containing the specified name. <span class="pl-k">If</span> no name is specified<span class="pl-k">,</span> lists all templates. <span class="pl-k">-</span>n<span class="pl-k">,</span> <span class="pl-k">--</span>name The name <span class="pl-k">for</span> the output being created. <span class="pl-k">If</span> no name is specified<span class="pl-k">,</span> the name of the current directory is used. <span class="pl-k">-</span>o<span class="pl-k">,</span> <span class="pl-k">--</span>output Location to place the generated output. <span class="pl-k">-</span>i<span class="pl-k">,</span> <span class="pl-k">--</span>install Installs a source or a template pack. <span class="pl-k">-</span>u<span class="pl-k">,</span> <span class="pl-k">--</span>uninstall Uninstalls a source or a template pack. <span class="pl-k">--</span>nuget<span class="pl-k">-</span>source Specifies a NuGet source to use during install. <span class="pl-k">--</span>type Filters templates based on available types. Predefined values are <span class="pl-s"><span class="pl-pds">"</span>project<span class="pl-pds">"</span></span><span class="pl-k">,</span> <span class="pl-s"><span class="pl-pds">"</span>item<span class="pl-pds">"</span></span> or <span class="pl-s"><span class="pl-pds">"</span>other<span class="pl-pds">"</span></span>. <span class="pl-k">--</span>force Forces content to be generated even <span class="pl-k">if</span> it would change existing files. <span class="pl-k">-</span>lang<span class="pl-k">,</span> <span class="pl-k">--</span>language Filters templates based on language and specifies the language of the template to create. Templates Short Name Language Tags <span class="pl-k">----------------------------------------------------------------------------------------------------------------------------</span> Console Application console [<span class="pl-k">C</span><span class="pl-c">#], F#, VB Common/Console </span> <span class="pl-k">Class</span> <span class="pl-k">library</span> <span class="pl-k">classlib</span> [<span class="pl-k">C</span><span class="pl-c">#], F#, VB Common/Library </span> <span class="pl-k">PowerShell</span> <span class="pl-k">Standard</span> <span class="pl-k">Module</span> <span class="pl-k">psmodule</span> [<span class="pl-k">C</span><span class="pl-c">#] Library/PowerShell/Module </span> ... |
A new template called psmodule
is now available making it easy to start a new C# based PowerShell module. Any issues, feedback, or suggestions for this template should be opened in the PowerShell Standard repo.
Creating a new project
We need to create a location for our new project and then use the template to create the project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | PS<span class="pl-k">></span> mkdir myModule Directory: C:UsersJames Mode LastWriteTime Length Name <span class="pl-k">----</span> <span class="pl-k">-------------</span> <span class="pl-k">------</span> <span class="pl-k">----</span> d<span class="pl-k">-----</span> <span class="pl-c1">8</span><span class="pl-k">/</span><span class="pl-c1">3</span><span class="pl-k">/</span><span class="pl-c1">2018</span> <span class="pl-c1">2</span>:<span class="pl-c1">41</span> PM myModule PS<span class="pl-k">></span> cd myModule PS C:UsersJamesmyModule<span class="pl-k">></span> dotnet new psmodule The template <span class="pl-s"><span class="pl-pds">"</span>PowerShell Standard Module<span class="pl-pds">"</span></span> was created successfully. Processing post<span class="pl-k">-</span>creation actions... Running <span class="pl-s"><span class="pl-pds">'</span>dotnet restore<span class="pl-pds">'</span></span> on C:UsersJamesmyModulemyModule.csproj... Restoring packages <span class="pl-k">for</span> C:UsersJamesmyModulemyModule.csproj... Installing PowerShellStandard.Library <span class="pl-c1">5.1</span>.<span class="pl-c1">0</span><span class="pl-k">-</span>preview<span class="pl-k">-</span><span class="pl-c1">06</span>. Generating MSBuild file C:UsersJamesmyModuleobjmyModule.csproj.nuget.g.props. Generating MSBuild file C:UsersJamesmyModuleobjmyModule.csproj.nuget.g.targets. Restore completed <span class="pl-k">in</span> <span class="pl-c1">1.76</span> sec <span class="pl-k">for</span> C:UsersJamesmyModulemyModule.csproj. Restore succeeded. |
You can see that the dotnet cli has created a source file and .csproj file for my project:
1 2 3 4 5 6 7 8 9 10 11 | PS C:UsersJamesmyModule<span class="pl-k">></span> dir Directory: C:UsersJamesmyModule Mode LastWriteTime Length Name <span class="pl-k">----</span> <span class="pl-k">-------------</span> <span class="pl-k">------</span> <span class="pl-k">----</span> d<span class="pl-k">-----</span> <span class="pl-c1">8</span><span class="pl-k">/</span><span class="pl-c1">3</span><span class="pl-k">/</span><span class="pl-c1">2018</span> <span class="pl-c1">1</span>:<span class="pl-c1">48</span> PM obj <span class="pl-k">-</span>a<span class="pl-k">----</span> <span class="pl-c1">8</span><span class="pl-k">/</span><span class="pl-c1">3</span><span class="pl-k">/</span><span class="pl-c1">2018</span> <span class="pl-c1">1</span>:<span class="pl-c1">48</span> PM <span class="pl-c1">376</span> myModule.csproj <span class="pl-k">-</span>a<span class="pl-k">----</span> <span class="pl-c1">8</span><span class="pl-k">/</span><span class="pl-c1">3</span><span class="pl-k">/</span><span class="pl-c1">2018</span> <span class="pl-c1">1</span>:<span class="pl-c1">48</span> PM <span class="pl-c1">1698</span> TestSampleCmdletCommand.cs |
The sample from the template demonstrates a simple cmdlet with two parameters that outputs results with a custom class.
Building the module
Building the sample is code is easy with DotNet CLI:
1 2 3 4 5 6 7 8 9 10 11 12 | PS C:UsersJamesmyModule<span class="pl-k">></span> dotnet build Microsoft (R) Build Engine version <span class="pl-c1">15.7</span>.<span class="pl-c1">179.6572</span> <span class="pl-k">for</span> .NET Core Copyright (C) Microsoft Corporation. All rights reserved. Restore completed <span class="pl-k">in</span> <span class="pl-c1">76.85</span> ms <span class="pl-k">for</span> C:UsersJamesmyModulemyModule.csproj. myModule <span class="pl-k">-</span><span class="pl-k">></span> C:UsersJamesmyModulebinDebugnetstandard2.<span class="pl-c1">0</span>myModule.dll Build succeeded. <span class="pl-c1">0</span> Warning(s) <span class="pl-c1">0</span> Error(s) Time Elapsed <span class="pl-c1">00</span>:<span class="pl-c1">00</span>:<span class="pl-c1">05.40</span> |
Testing the built module
To test this sample module, we just need to import it. We can check to see what it supports and try running it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | PS C:UsersJamesmyModule<span class="pl-k">></span> ipmo .binDebugnetstandard2.<span class="pl-c1">0</span>myModule.dll PS C:UsersJamesmyModule<span class="pl-k">></span> <span class="pl-c1">Test-SampleCmdlet</span> <span class="pl-k">-</span>? NAME <span class="pl-c1">Test-SampleCmdlet</span> SYNTAX <span class="pl-c1">Test-SampleCmdlet</span> [<span class="pl-k">-</span><span class="pl-k">FavoriteNumber</span>] <span class="pl-k"><</span>int<span class="pl-k">></span> [[<span class="pl-k">-</span><span class="pl-k">FavoritePet</span>] {Cat <span class="pl-k">|</span> Dog <span class="pl-k">|</span> Horse}] [<span class="pl-k"><</span><span class="pl-k">CommonParameters</span><span class="pl-k">></span>] ALIASES None REMARKS None PS C:UsersJamesmyModule<span class="pl-k">></span> <span class="pl-c1">Test-SampleCmdlet</span> <span class="pl-k">-</span>FavoriteNumber <span class="pl-c1">7</span> <span class="pl-k">-</span>FavoritePet Cat FavoriteNumber FavoritePet <span class="pl-k">--------------</span> <span class="pl-k">-----------</span> <span class="pl-c1">7</span> Cat |
This sample is pretty simple as it’s intended to just show how to get started on writing a PowerShell module from scratch. The important point is that using PowerShell Standard Library, this assembly can be used in both PowerShell Core 6 as well as Windows PowerShell. This sample will even work on Windows, Linux, or macOS without any changes.
In the next part of this series, I’ll cover other aspects of PowerShell module authoring such as module manifests and writing Pester tests.
James Truher
Senior Software Engineer
PowerShell Team