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".
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
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"
This should give the following output
PS C:\Projects\Wozzo.SamplePsModule> Test-SampleCmdlet 7 "Cat"
FavoriteNumber FavoritePet
-------------- -----------
7 Cat
PS C:\Projects\Wozzo.SamplePsModule>
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
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; }
}
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();
}
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);
}
}
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>
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>
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...
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)
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: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...