2 minute read

PowerShell is a popular and powerful scripting language developed by Microsoft. PowerShell modules are collections of cmdlets, functions, variables, and other assets that can be used to simplify administrative tasks. In this post, I will walk through the process of creating a binary PowerShell module.

Create the module project

First, create the project files for a module named Demo in a new directory named powershell-demo-module. Run the following statements in a PowerShell Terminal window.

You can accomplish the same results using Visual Studio or VS Code; the commands below will create a solution, create a folder for a project, and create the project itself, and remove the Class1.cs file from the template.

Create the project scaffolding:

mkdir powershell-demo-module
cd .\powershell-demo-module
mkdir Demo.PowerShell
dotnet new sln -n Demo.PowerShell -o .\
dotnet new classlib -n Demo.PowerShell -f net6.0 -o .\Demo.PowerShell
dotnet sln .\Demo.PowerShell.sln add .\Demo.PowerShell\Demo.PowerShell.csproj
rm .\Demo.PowerShell\Class1.cs

Next, add a reference to System.Management.Automation:

dotnet add .\Demo.PowerShell\Demo.PowerShell.csproj package System.Management.Automation -v 7.0.0

In order to avoid publishing unnecessary assemblies, the Demo.PowerShell.csproj project file needs to be manually updated. Open Demo.PowerShell.csproj and change:

<PackageReference Include="System.Management.Automation" Version="7.0.0" />

to:

<PackageReference Include="System.Management.Automation" Version="7.0.0">
    <PrivateAssets>all</PrivateAssets>
</PackageReference>

Add a cmdlet

Next, we will create the Get-Greeting cmdlet. This cmdlet will output "Hello, World!".

Create the cmdlet class Get_Greeting.cs:

New-Item -Type File -Path .\Demo.PowerShell\Get_Greeting.cs

Open Get_Greeting.cs, update the content:

using System.Management.Automation;

namespace Demo.PowerShell
{
    [Cmdlet(VerbsCommon.Get, "Greeting")]
    public class Get_Greeting : Cmdlet
    {
        protected override void ProcessRecord()
        {
            WriteObject("Hello, World!");
        }
    }
}

In order for a class to become an exported cmdlet, it must inherit from System.Management.Automation.Cmdlet and be annotated with System.Management.Automation.CmdletAttribute. The annotation gives the cmdlet it’s full name, which is a combination of a verb (usually from the set of approved verbs) and a noun.

Create the module manifest

While optional at this point, including a module manifest is a best practice, and will allow you to include certain metadata about your module.

In PowerShell:

$newModuleManifestArgs = @{
    Author                  = "Demo"
    CmdletsToExport         = "*"
    CompanyName             = "Demo"
    CompatiblePSEditions    = "Core"
    Description             = "Demo PowerShell tools"
    Guid                    = "16A9AB81-7731-4098-BCE0-F8B27B3990DB"
    ModuleVersion           = "1.0.0"
    Path                    = ".\Demo.PowerShell\Demo.PowerShell.psd1"
    PowerShellVersion       = "7.2"
    RootModule              = "Demo.PowerShell.dll"
}

New-ModuleManifest @newModuleManifestArgs

Calling New-ModuleManifest with the parameter values above will create a Demo.PowerShell.psd1 in the Demo.PowerShell project. The next step is to ensure that file is included alongside the build output. Add the following to the Demo.PowerShell.csproj file:

<ItemGroup>
    <None Update="Demo.PowerShell.psd1">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
</ItemGroup>

Use the module locally

Build the project:

dotnet publish ".\Demo.PowerShell\Demo.PowerShell.csproj" `
  --configuration Debug `
  --output ".\Demo.PowerShell\bin\publish" `
  --no-self-contained

Import the module:

Import-Module -Name ".\Demo.PowerShell\bin\publish\Demo.PowerShell.psd1"

Call the cmdlet:

Get-Greeting

Publish the module

Once you have tested a module locally, you may want to publish it to a public or private repository for others to use. The script below demonstrates adding a new module repository and publishing your package.

This script is shown for demonstration purposes, in practice, this action would likely be part of a build pipeline, such as a GitHub Workflow, Azure DevOps Release, etc. You can see it used in a GitHub Workflow in this post.

## Variables
$key = "<GITHUB_TOKEN>"
$user = "<GITHUB_EMAIL>"
$feed = "https://nuget.pkg.github.com/<YOUR_GITHUB_USERNAME>/index.json"
$token = $key | ConvertTo-SecureString -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList @($user, $token)
              
## Force TLS1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
              
## Register repository
$registerArgs = @{
    Name = "GITHUB_REPOSITORY"
    SourceLocation = $feed
    PublishLocation = $feed
    InstallationPolicy = 'Trusted'
    Credential = $creds
}
              
Register-PSRepository @registerArgs

## Publish the module              
Publish-Module -Path ".\Demo.PowerShell\bin\publish\" -Repository $repositoryName -NuGetApiKey $key

References

PowerShell Docs

PowerShell Verbs