Let's create a PowerShell Module in C# .NET Core with unit testing.
Note: This demonstration uses the dotnet core CLI to initialise and set up the project before heading into Visual Studio. This is my personal preference but it might be advantageous to see the CLI in action.
Follow along with the GitHub repository for this article here.
You may need to install the PowerShell template for .NET Core if you haven't already:
dotnet new -i Microsoft.PowerShell.Standard.Module.Template
Confirm the presence of the template:
dotnet new --list
You should see one line within the output stating the following:
PowerShell Standard Module psmodule [C#] Library/PowerShell/Module
Using the command line in a directory of your choosing, enter the following commands one-by-one:
# Create the new solution file (PowerShellModuleTest.sln)
dotnet new sln -o PowerShellModuleTest
# Change into the project directory
cd PowerShellModuleTest
# Create a new project based on the psmodule template (PowerShellModuleTest.Cmdlet)
dotnet new psmodule -o PowerShellModuleTest.Cmdlet
# Add the commandlet project to the solution
dotnet sln add PowerShellModuleTest.Cmdlet
The default module from the template takes in two parameters and essentially wraps them in an object and echoes them back out:
using System.Management.Automation;
namespace PowerShellModuleTest.Cmdlet
{
[Cmdlet(VerbsDiagnostic.Test, "SampleCmdlet")]
[OutputType(typeof(FavoriteStuff))]
public class TestSampleCmdletCommand : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public int FavoriteNumber { get; set; }
[Parameter(Position = 1, ValueFromPipelineByPropertyName = true)]
[ValidateSet("Cat", "Dog", "Horse")]
public string FavoritePet { get; set; } = "Dog";
// This method gets called once for each cmdlet in the pipeline when the pipeline starts executing
protected override void BeginProcessing()
{
WriteVerbose("Begin!");
}
// This method will be called for each input received from the pipeline to this cmdlet; if no input is received, this method is not called
protected override void ProcessRecord()
{
WriteObject(new FavoriteStuff
{
FavoriteNumber = FavoriteNumber,
FavoritePet = FavoritePet
});
}
// This method will be called once at the end of pipeline execution; if no input is received, this method is not called
protected override void EndProcessing()
{
WriteVerbose("End!");
}
}
public class FavoriteStuff
{
public int FavoriteNumber { get; set; }
public string FavoritePet { get; set; }
}
}
First we need to import our .DLL with the commandlet in. From the root directory (with the .sln file) run the following commands:
# Build the project in release mode at the root of the directory in a build folder
dotnet build --configuration Release --output ./build .\PowerShellModuleTest.Cmdlet\
# Import the DLL that contains our commandlet
Import-Module ./build/PowerShellModuleTest.Cmdlet.dll
With our module loaded, run the commandlet:
Test-SampleCmdlet -FavoriteNumber 51 -FavoritePet Cat
This will produce the following output (wrapped in an object and echoed back):
FavoriteNumber FavoritePet
-------------- -----------
51 Cat
From within the same directory as the .sln file, run the following:
# Create the unit test project
dotnet new mstest -o .\PowerShellModuleTest.UnitTests
# Add the unit test project to the solution
dotnet sln add .\PowerShellModuleTest.UnitTests
# Adds the PowerShellModuleTest.Cmdlet as a reference to the PowerShellModuleTest.UnitTests project
dotnet add .\PowerShellModuleTest.UnitTests\ reference .\PowerShellModuleTest.Cmdlet\
# Add the Microsoft.PowerShell.SDK to PowerShellModuleTest.UnitTests to allow Invoking our commandlet
dotnet add .\PowerShellModuleTest.UnitTests\ package Microsoft.PowerShell.SDK
Replace the contents of UnitTest1.cs
with the following:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PowerShellModuleTest.Cmdlet;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace PowerShellModuleTest.UnitTests
{
[TestClass]
public class UnitTest1
{
private Runspace _runspace;
[TestInitialize]
public void Init()
{
var initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.Commands.Add(
new SessionStateCmdletEntry("Test-SampleCmdlet", typeof(TestSampleCmdletCommand), null)
);
_runspace = RunspaceFactory.CreateRunspace(initialSessionState);
_runspace.Open();
}
[TestMethod]
public void Basic()
{
using var powershell = PowerShell.Create(_runspace);
// Configure Command
var command = new Command("Test-SampleCmdlet");
command.Parameters.Add("FavoriteNumber", 51);
command.Parameters.Add("FavoritePet", "Cat");
powershell.Commands.AddCommand(command);
// Run Command
var result = powershell.Invoke<FavoriteStuff>()[0];
// Assert
Assert.IsInstanceOfType(result, typeof(FavoriteStuff));
Assert.AreEqual(result.FavoriteNumber, 51);
Assert.AreEqual(result.FavoritePet, "Cat");
}
}
}
Running the Unit test creates a PowerShell instance loaded with parameters and tests the response.
Let's write another commandlet to multiply two values:
The commandlet:
using System.Management.Automation;
namespace PowerShellModuleTest.Cmdlet
{
[Cmdlet(VerbsDiagnostic.Test, "MultiplyCmdlet")]
[OutputType(typeof(TestMultiplyCmdletCommandResponse))]
public class TestMultiplyCmdletCommand : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)]
public int FirstValue { get; set; }
[Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)]
public int SecondValue { get; set; }
protected override void ProcessRecord()
{
WriteObject(new TestMultiplyCmdletCommandResponse
{
Result = FirstValue * SecondValue
});
}
}
public class TestMultiplyCmdletCommandResponse
{
public int Result { get; set; }
}
}
The Unit Test:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PowerShellModuleTest.Cmdlet;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace PowerShellModuleTest.UnitTests
{
[TestClass]
public class TestMultiplyCmdletCommandTest
{
private Runspace _runspace;
[TestInitialize]
public void Init()
{
var initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.Commands.Add(
new SessionStateCmdletEntry("Test-MultiplyCmdlet", typeof(TestMultiplyCmdletCommand), null)
);
_runspace = RunspaceFactory.CreateRunspace(initialSessionState);
_runspace.Open();
}
[TestMethod]
public void Run()
{
using var powershell = PowerShell.Create(_runspace);
// Configure Command
var command = new Command("Test-MultiplyCmdlet");
command.Parameters.Add("FirstValue", 4);
command.Parameters.Add("SecondValue", 8);
powershell.Commands.AddCommand(command);
// Run Command
var result = powershell.Invoke<TestMultiplyCmdletCommandResponse>()[0];
// Assert
Assert.IsInstanceOfType(result, typeof(TestMultiplyCmdletCommandResponse));
Assert.AreEqual(result.Result, 32);
}
}
}