DEV Community

Warren
Warren

Posted on • Updated on

Creating a PowerShell Binary Module

Creating a PowerShell Binary Module

PowerShell is a fantastic tool for any computer user to get to grips with, and writing your own module is a great way to improve your productivity with commands you run regularly. I'm going to run through how to create a new binary module and how to include nuget package dependencies so that the module can be used.

Prerequisites

I'm going to assume you're comfortable programming in C# and have a working knowledge of PowerShell, i.e. you are comfortable navigating to specific directories and running commands/scripts.

You'll need the (dotnet core SDK)[https://dotnet.microsoft.com/download], at least version 2.1.x, installed. I'm going to be using VSCode for the development work, but any text editor should suffice.

Creating the project

In PowerShell navigate to where you want to keep your project. Mine is C:\Projects\ and I'll create a PowerShell directory with the name of my module. In this case "Wozzo.SamplePsModule".

Create and enter PowerShell Directory

Now we're going to use the dotnet cli to install a template for the PowerShell module and then create a project from that template

dotnet new install Microsoft.PowerShell.Standard.Module.Template
dotnet new psmodule
Enter fullscreen mode Exit fullscreen mode

This will have created a new project with a sample Cmdlet which is ready to run. To import this module and run it use the following commands.
Tip: I recommend doing this in a separate powershell window that you can close and reload since whenever you import the module the session will lock the dll file and you'll be unable to build again until it has been closed.

dotnet build
Import-Module ".\bin\Debug\netstandard2.0\Wozzo.SamplePsModule.dll"
Test-SampleCmdlet 7 "Cat"
Enter fullscreen mode Exit fullscreen mode

This should give the following output

PS C:\Projects\Wozzo.SamplePsModule> Test-SampleCmdlet 7 "Cat"

FavoriteNumber FavoritePet
-------------- -----------
             7 Cat


PS C:\Projects\Wozzo.SamplePsModule>
Enter fullscreen mode Exit fullscreen mode

The TestSampleCmdletCommand.cs file contains the code for this Cmdlet. The class must inherit from System.Management.Automation.PsCmdlet, and then uses several attributes to control how powershell describes the command and for parameters.

Attribute Description Example
Cmdlet Used to give the Cmdlet it's name. Accepts a verb and noun part to build the command name e.g Get-Location uses the verb 'Get' and the noun 'Location'. You should use the properties from the Verbs... classes provided by System.Management.Automation. [Cmdlet (VerbsCommon .Get ,"Location" )]
OutputType Used to specify the expected output type from the Cmdlet [OutputType(typeof(int))]
Parameter Used to specify that a property is to be used to store the value of a parameter from the command line. [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]

There are other parameters, but these are the most common and required ones for building a basic cmdlet.

Note: Remember to build the project again you'll need to close the PowerShell window and open a new one to unlock the dll file.

Adding a dependency

For any but the simplest of cmdlet's you're likely to want to use additional packages in your project. I'm going to create a Cmdlet which retrieves some json data from a http endpoint. To help me do this I'd like to use Refit, which is available as a nuget package, to create my client. Run the following command in the project root to add refit as a dependency.

dotnet add package refit
Enter fullscreen mode Exit fullscreen mode

Then create the following three items in new files in your project.

Employee

This class will store the response from the API. Normally I'd tidy up the names, and use attributes to specify what json I'm expecting, but I'm trying to keep this simple.

    public class Employee
    {
        public int id { get; set; }
        public string employee_name { get; set; }
        public int employee_salary { get; set; }
        public int employee_age { get; set; }
        public string profile_image { get; set; }
    }
Enter fullscreen mode Exit fullscreen mode

IPlaceholderApiClient

Not aiming to teach refit today, but check out the refit readme on the project page for more details. Tldr; Just add methods to an interface to enable retrieving data from that endpoint.

    public interface IPlaceholderApiClient {
        [Get("/api/v1/employees")]
        Task<IReadOnlyCollection<Employee>> GetEmployees();
    }
Enter fullscreen mode Exit fullscreen mode

The cmdlet

The cmdlet will use refit to create an instance of the client, then retrieve the result from the endpoint and then output it to the powershell pipeline.

    [Cmdlet(VerbsCommon.Get, "Employees")]
    [OutputType(typeof(IReadOnlyCollection<Employee>))]
    public class GetEmployeesCmdlet : PSCmdlet
    {
        protected override void ProcessRecord()
        {
            var client = RestService.For<IPlaceholderApiClient>("http://placeholder.restapiexample.com");
            var employees = client.GetEmployees().Result;
            WriteObject(employees);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Note that the interface returns Tasks, and because PowerShell won't be too happy with us using async/await, we need to use the call to .Result to get the response from the endpoint.

Running

Run the build command and try importing the module again. There should be no errors and the Get-Employee cmdlet should now be available. If we try to run it though...

PS C:\Projects\Wozzo.SamplePsModule> Get-Employee 9004
Get-Employee : Could not load file or assembly 'Refit, Version=4.6.0.0, Culture=neutral, PublicKeyToken=null' or one of its
dependencies. The system cannot find the file specified.
At line:1 char:1
+ Get-Employee 9004
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Employee], FileNotFoundException
    + FullyQualifiedErrorId : System.IO.FileNotFoundException,Wozzo.SamplePsModule.GetEmployeeCmdlet

PS C:\Projects\Wozzo.SamplePsModule>
Enter fullscreen mode Exit fullscreen mode

This is because our build isn't including the refit.dll and our module doesn't know it needs to be loaded when it is imported. We need to fix these things before we can proceed.

Edit the .csproj file and in the <PropertyGroup> section add the following

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
Enter fullscreen mode Exit fullscreen mode

At this point when you build the dll's for the dependencies should now be included. If you try to run the command again the output should give a collection of employees

PS C:\Projects\Wozzo.SamplePsModule> Get-Employees

id              : 10136
employee_name   : nick
employee_salary : 123
employee_age    : 23
profile_image   :

id              : 10137...
Enter fullscreen mode Exit fullscreen mode

Next steps

At this point your module is ready to be used. Next steps to get it production ready would be to create a module manifest. After that you could consider publishing it on the PSGallery. Happy PoShing

Top comments (2)

Collapse
 
caiohamamura profile image
Caio Hamamura

Thank you for your very nice article! But I have no clue how the manifest psd1 should look like, do you have an example? Whenever I try to publish I get:

The specified module with path '.' was not published because no valid module was found with that path
Collapse
 
wozzo profile image
Warren

Ah, spotted a broken link for that bit.
I've updated it but MS has a page on writing module manifests
learn.microsoft.com/en-us/powershe...