Writing Your First Compiled PowerShell Cmdlet

You’re a person who writes PowerShell, right? You write tools and scripts in PowerShell which you and other people run all the time to do cool things. Probably, since you bought this book about PowerShell, you like the language, are looking to learn more about it, and want to take your code and contributions to your company to the next level.

This chapter introduces you to PowerShell cmdlets in C# by going through some relatable examples in .NET Core. You’re not learning best practices, necessarily, but you are learning some cool and interesting things that you can use right away.

The Whys

You might wonder to yourself, “Why would I ever want to write a PowerShell cmdlet in C#?” and I would simply reply with “Why not?”

There is a long-standing attitude in the operations community that there’s some specific difference between a developer and an operator. People believe, for some reason, that developers are these magical people who must have Computer Science degrees, or work in roles titled “Software Engineer.” None of that’s true.

If You Write Code, You’re a Developer

Period. You might not be a good developer or a professional one, but you’re a developer. The act of creating code to run on a computer system is development. Therefore, whether or not you want to admit it, when you’re writing code you’re developing. Don’t let anybody tell you otherwise.

You don’t need unit tests to be a developer. You don’t need to write in Python, Javascript, SQL, Rust, or any other language to be a developer. All you have to do is create code, and that’s what you’re doing pretty much any time you open your editor, right? So, why limit yourself to just PowerShell? There’s a whole world of languages out there, and you shouldn’t keep yourself from diving in just because you don’t meet some arbitrary definition of a “developer.”

Why You Should Use C# to Write PowerShell Tools

C# (pronounced “see sharp”) is a .NET language just like PowerShell. That makes it easier to pick up than a language that has nothing in common. C# is considered a “lower level” language than PowerShell. What does that mean? PowerShell abstracts a lot of programming concepts away from the user writing code. It was initially built to help system administrators with limited coding skills to automate mundane tasks, which it does! PowerShell, in my opinion, is a gateway language and inevitably leads people towards other languages.

C# is much more flexible because it was designed to work in many more scenarios.

  • Linq is much more straightforward in C# and allows you to interact with collections more conveniently.
  • Async APIs and APIs that require generics usually work better in C#.
  • Don’t get me started on multithreading. In PowerShell, it’s about as much fun as slamming your hand in a car door, but in C#, multithreading is much more convenient.

Writing your PowerShell cmdlets in C# also offers the benefit of delivering one compiled artifact instead of a bunch of text (by which I mean .ps1) files. These compiled artifacts, usually Dynamic-Link Libraries (DLLs), are harder for “power users” to tamper with and can often make it easier for you to work with C# code written by others. That’s especially important when you’re writing a wrapper for a library someone else created or contributing to an open source project. For example, maybe you’re the person adding a PowerShell module to a cool C# tool!

Prerequisites

Before you get started writing any C# code, you need to install a couple things first. There are a lot of options for editors out there. Visual Studio is popular but, if you’re into PowerShell, you’re probably already using VS Code. Installing the C# extension is all you need to get started in VS Code.

You also need to install a version of the .NET Core Software Development Kit (SDK). You can use whatever version of the .NET Core SDK you prefer, but be aware that some features in newer versions of .NET Core aren’t backwards compatible.

Hello World

As with all things, you have to crawl before you walk and walk before you run. In that regard, this first example will have you crawling all over the place.

When you start a new C# project, you need to create a little bit of scaffolding. If you’ve ever used something like the Plaster module, or some other template system to create new PowerShell modules, then this will be a somewhat familiar concept. Basically, you use the dotnet command line interface (CLI) tool to create some new artifacts.

First, you create a new classlib which is, as the name suggests, a class library.

Create your new class library, and give it a meaningful name related to what you’re working on.

1 dotnet new classlib --name ThomasWritesPerfectCode
2 cd ThomasWritesPerfectCode

In this example I named my class library ThomasWritesPerfectCode which, I assure you, is absolutely true. Name yours something that’s more relevant to your project. Then use cd (or Push-Location if you’re fancy) to navigate to the directory that was created by dotnet when you created a new class library. If you don’t provide a --name value, your class library will be created in your present working directory, with the same name as that directory.

Now, you perform an optional step. It’s time to add another artifact to the class library using dotnet. This next artifact is a globaljson which, while optional, you should include. It’s used for a lot of things, including tracking the version of .NET Core used in your project.

1 dotnet new globaljson

You don’t have to do anything else to your new global.json file that you see in the root of your class library folder.

Now, you’re ready to write some C#! But, wait. You’re writing a PowerShell cmdlet. In PowerShell, when you’re writing code that’s for another system, often you’ll end up having to import a module or specify a using statement to include commands and other artifacts from that module in your code. C# requires the same sort of statement.

Rub Some PowerShell On It

Using dotnet again, you must add the PowerShell library to your project so you can use all the tools, methods, classes, and other artifacts that the PowerShell team at Microsoft created for people writing their own cmdlets in C#.

1 dotnet add package PowerShellStandard.Library

This command will modify your .csproj file and instruct your computer to retrieve and add a dependency. Specifically, a dependency on the bits needed to write a PowerShell cmdlet.

Writing Some Code

Open the folder you created in VS Code (or your preferred editor of choice). There are already a few files in here, some with bad names. Specifically, rename Class1.cs to something more related to the specific cmdlet you’re making. In this example, it’s renamed to GetGreeting.cs.

Open your newly renamed file, and change the line that reads

1 public class Class1

to

1 public class GetGreeting

Or, more specifically, to match the name of your .cs file. It’s in your best interest to use meaningful names and keep yourself well organized.

Now, it’s time to include the specific pieces of code that allow the .dll that you’re creating to run in PowerShell. At the very top of your currently open file, it says using System;. Immediately following that line, add using System.Management.Automation so that the first two lines look like this:

1 using System;
2 using System.Management.Automation;

Next, it’s time to identify the class GetGreeting as a PowerShell cmdlet within this module. First, append : PSCmdlet to the end of the line that instantiates the class, you edited earlier.

Next, insert a line immediately above that one—this is where you name your cmdlet. The name for this cmdlet is going to end up as Get-Greeting.

1 [Cmdlet(VerbsCommon.Get, "Greeting")]
2 public class GetGreeting : PSCmdlet

There’s a lot to unpack here. The first line adds an attribute to your class, the Cmdlet attribute. It takes a few arguments, but you only need two to give your cmdlet a name. The first is a verb. This is one of the approved verbs that come with PowerShell, which have been conveniently organized for you. For this example, you want to choose the Get verb. The second argument is a noun, which should be singular and doesn’t come from an approved list. My noun here is Greeting. Hence, the name of this cmdlet will be Get-Greeting since the verb and noun will be combined to fit in with all the other PowerShell cmdlets you know and love.

Right now, your GetGreeting.cs file should look like this:

 1 using System;
 2 using System.Management.Automation;
 3 
 4 namespace ThomasWritesPerfectCode
 5 {
 6     [Cmdlet(VerbsCommon.Get, "Greeting")]
 7     public class GetGreeting : PSCmdlet
 8     {
 9     }
10 }

Ship It

This module is technically ready to be built and deployed. In a PowerShell console, in the directory that has your class library, run the following command to build your solution:

1 dotnet build

Unless you made a typo somewhere, you should have output that describes where it created ThomasWritesPerfectCode.dll (or whatever you named your class library), and a portion that lets you know that you have zero errors or warnings.

You can even import this module and run your new cmdlet (remember to specify the full or relative path to the DLL):

1 Import-Module $pathToYourDLL
2 Get-Greeting

The only problem is that nothing happens when you run Get-Greeting. What happened there? Well, you haven’t actually made your cmdlet do anything yet. After your class declaration (where you appended : PSCmdlet), there’s an opening curly brace ({), and that’s where code gets added.

Make Some Magic Happen

It’s time to make your new cmdlet do something. This is where it pays to be a little familiar with C#, but it’s alright if you’re not. C# is .NET based, just like PowerShell, so you’ll find yourself looking up syntax and finding your way around the basics, but you’ll pick it up quickly.

Classes in C# can have properties. A property is just a place holder for a variable. You’ve worked with properties before on objects in PowerShell. For instance, the Length property on a variable that holds a string tells you how long the text is.

1 $myString.Length

In C# PowerShell cmdlets, parameters are properties, although they’re a little special. Add the following inside of the curly brace that comes right after the class declaration.

1 [Parameter()]
2 public string Name { get; set; }

For now, you can safely ignore the curly braces and the get and set parts. You’ll learn more about that as you get to know C#. The first line is an attribute that applies to the line that comes after it. That attribute simply identifies the property on the second line as a parameter for this cmdlet. The second line states that the Name parameter is of type string and is public, which is normal.

In PowerShell you might have written this instead:

1 [CmdletBinding()]
2 param (
3     [Parameter()]
4     [string]
5     $Name
6 )

But you didn’t, because this is C#. Your GetGreeting.cs file should now look like this:

 1 using System;
 2 using System.Management.Automation;
 3 
 4 namespace ThomasWritesPerfectCode
 5 {
 6     [Cmdlet(VerbsCommon.Get, "Greeting")]
 7     public class GetGreeting : PSCmdlet
 8     {
 9         [Parameter()]
10         public string Name { get; set; }
11     }
12 }

The point of adding this parameter for this example is so that your cmdlet can either write out Hello World or it can write out Hello Name where the name passed to the cmdlet is used. You didn’t make that parameter mandatory (you could have put Mandatory = true inside the round brackets in the Parameter attribute), so you must handle cases where the user doesn’t specify a Name.

Now it’s time to construct the message that will be sent back to the user. You’ll need to break it down into its parts. First, determine the subject. Did the user provide a value for the Name parameter? Or did they leave it blank, therefore implying the valid output is Hello World? Add this line below the parameter declaration to fill in the subject being greeted.

1 string subject = string.IsNullOrEmpty(Name) ? "World" : Name;

This line declares a new variable of type string. It’s being set equal to a value that looks a little weird if you’ve only ever worked in PowerShell. This is called a ternary statement and it works a lot like an if/else does in PowerShell. The first part, string.IsNullOrEmpty(Name) returns true or false depending on if the Name variable contains a value or not. If Name doesn’t contain a value, it will return true, and the part between the ? and the : will be returned, in this case “World” is what would be returned. If Name does contain a value, the part after the : will be returned, in this case, the value for Name.

But Wait, There’s a Bug

A bug already? If you’re following along with this example, you noticed that your editor is unhappy with you. In C#, operations like the one you’re doing right now to create variables and programmatically determine their values need to be inside of a method. Methods in C# are just like functions in PowerShell. There’s a specific method that you need to use here, and it might look a little familiar.

Above the line you just made to declare the subject variable, add the following.

1 protected override void ProcessRecord()
2 {
3 }

Then, put the line declaring subject inside the curly braces. This is roughly equivalent to the process block of a PowerShell function. You can’t get away without using begin, process, and end blocks here like you can in PowerShell.

Your GetGreeting.cs file should now look like this.

 1 using System;
 2 using System.Management.Automation;
 3 
 4 namespace ThomasWritesPerfectCode
 5 {
 6     [Cmdlet(VerbsCommon.Get, "Greeting")]
 7     public class GetGreeting : PSCmdlet
 8     {
 9         [Parameter()]
10         public string Name { get; set; }
11 
12         protected override void ProcessRecord()
13         {
14             string subject = string.IsNullOrEmpty(Name) ? "World" : Name;
15         }
16     }
17 }

Now that you’ve got the who you’re greeting figured out, it’s time to construct the greeting and send it to the user.

Start by declaring another variable right below the one that holds the subject, and use string concatenation to make your greeting.

1 string greeting = "Hello " + subject;

Then, send that greeting to the user. In PowerShell, you’d normally use Write-Output to send this greeting to the user, but since this isn’t PowerShell, you need to use something else. You’re still in PowerShell when you’re running this cmdlet, so you still want your greeting to go to the output stream. There’s just a different way of doing that in C# than there is in PowerShell.

1 WriteObject(greeting);

There is also WriteVerbose() and WriteDebug() and methods to write to all the different output streams you’re used to. Since this is just standard output that’s destined for the output stream, you can just use WriteObject().

Your GetGreeting.cs file should now look like this if you’ve been following along:

 1 using System;
 2 using System.Management.Automation;
 3 
 4 namespace ThomasWritesPerfectCode
 5 {
 6     [Cmdlet(VerbsCommon.Get, "Greeting")]
 7     public class GetGreeting : PSCmdlet
 8     {
 9         [Parameter()]
10         public string Name { get; set; }
11 
12         protected override void ProcessRecord()
13         {
14             string subject = string.IsNullOrEmpty(Name) ? "World" : Name;
15             string greeting = "Hello " + subject;
16             WriteObject(greeting);
17         }
18     }
19 }

Ship It for Real

Now, run dotnet build again to recompile your project.

Once your code compiles without error, import the new DLL (in the same location), and you can witness the glory of your very first compiled PowerShell cmdlet.

1 dotnet build
2 # No errors!
3 Import-Module $pathToDLL
4 Get-Module

Running the Get-Module command returns the usual suspects (things like Microsoft.PowerShell.Utility and PSReadLine), and also shows your new module and the exported commands. Run Get-Command -Module ThomasWritesPerfectCode and explicitly see the Get-Greeting command that your compiled module makes available.

Take This Puppy for a Spin

Run Get-Greeting in your PowerShell window you imported your new module into. If your code looks like my code, it returned Hello World! Happy days! Rejoice!

But wait, you added another feature. Now call Get-Greeting again, but this time provide a value for -Name.

1 Get-Greeting -Name Thomas
2 # returns "Hello Thomas"

Amazing! You did it. You wrote code in C# that executes in a PowerShell console. You’ve written (and run) your very first compiled PowerShell cmdlet.

Summary

This chapter showed you the basics of writing compiled PowerShell cmdlets in C# You saw how to get up and running, how to get started writing C# for PowerShell, and how to build and run your compiled modules. This is just the tip of the iceberg, though!

From here, you’re invited to dig deeper on your own. Earlier in this chapter, there is a list of things that are generally easier, or at least better suited for C# than PowerShell. Did any of those things tickle your fancy? Try them out! Worst case scenario, you learn something new.

Writing compiled PowerShell cmdlets isn’t a one-size-fits-all solution, just like PowerShell isn’t a one-size-fits-all solution. The point of learning how to do this is to expand your skill set and add tools to your proverbial toolbox. There’s no reason to let a job title hold you back.

There’s no reason you can’t get started right now.