Writing a PowerShell module in C#, Part 1: The basics

In this series we will cover the basics of building a Windows PowerShell binary module using C#. In the first part of the series we will build a module with just one cmdlet called Get-Salutation that will resemble the traditional “Hello World” example.

We will use Visual Studio 2013 since it includes the reference assemblies for System.Management.Automation–the root namespace for Windows PowerShell–for Microsoft .Net Framework 4.0. Microsoft has not released a PowerShell 4.0 SDK with reference assemblies using Microsoft .NET Framework 4.5 at this moment. If you are planning to support Windows PowerShell  2.0 then you will need to download the Windows PowerShell 2.0 SDK from Microsoft http://www.microsoft.com/en-us/download/details.aspx?id=2560. Visual Studio 2013 Express for Windows Desktop version can also be used.

Open Visual Studio and create a new project from the File menu:


Select the Visual C# from the installed Templates list and then pick Class Library since compiled modules in PowerShell are in DLL format:


We give it a name and specify a location for our project. For the purpose of this tutorial I will name my project MyModule.

Next step is to set the project’s minimum target .NET Framework depending on the lowest version of PowerShell we want to support. You can use as a reference:

  • PowerShell 2.0 – .NET Framework 3.5
  • PowerShell 3.0 – .NET Framework 4
  • PowerShell 4.0 – .NET Framework 4.5


Now, on the Application tab we select the target framework from the dropdown list.


For this example project we use a PowerShell 3.0 assembly and we want to support that as the lowest version of PowerShell. That’s why we select .NET Framework 4. We will be asked to confirm the change and it will re-open the project.

We need to load the System.Management.Automation library as a reference to our project to make the PowerShell API calls available. To do this we right click on Reference in the Solution Explorer and select Add Reference


In the Reference Module Manager click on Browse:


Now navigate to C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0 This folder is created with the installation of Visual Studio 2013. There we select the System.Management.Automation.dll to add it to our references.


Once we have the reference assembly in our project we can now use the assembly to get access to the API calls we need to build a PowerShell module. We start by adding System.Management.Automation to the default list that Class template has provided.


Those familiar with PowerShell advanced functions will notice that process for creating a cmdlet in C#  is very similar. The major difference is that a module with advanced functions is stored in a .psm1 file and cmdlets are stored in a DLL file. The attributes are used even in the same manner so for a person that has written script modules in PowerShell going to C# is very easy.

Let’s start by applying the attribute to the default class created in our namespace. You will see that in the case of C# we have to define the verb and the noun for the cmdlet. Thankfully, Visual Studio supports the IntelliSense for the verb group and the verbs inside the group.



After selecting a verb, we need to add the second parameter to the attribute–the noun we want to use (in our case a noun is Salutation). The verb and noun combination we use in the attribute is how the cmdlet will be named in PowerShell. The class name has no impact. As you can see it the following screenshot, if we place a comma after the second parameter, IntelliSense will suggest other named parameters we can use when defining the cmdlet.


One thing we have to make sure of is that the class inherits from the PSCmdlet class. This is done by adding : PSCmdlet at the end of the class name.


I like naming my classes <verb><noun> in Pascal case following Microsoft naming guidelines http://msdn.microsoft.com/en-us/library/4xhs4564(v=vs.71).aspx to easily identify them when working inside of Microsoft Visual Studio.

In PowerShell, the parameters of an advanced function become the named parameters of the command, in the case of a class that defines a cmdlet it is a public class properties that become the named parameters of the command. In the case of our Get-Salutation cmdlet we would like to be able to provide a single name or array of names we would like to get a salutation for. Also, we would like our Name parameter to have the following characteristics:

  • The parameter has to be mandatory.
  • To accept pipeline input by property name.
  • Use aliases for common property names that can be used to reference a person’s name in an object.
  • Provide a Help Message to describe it when looking at help or being asked to provide a value.

The syntax is almost identical to PowerShell. We apply the attributes to the public property and its name becomes the name of the parameter. For more information on using properties check http://msdn.microsoft.com/en-us/library/w86s7x04.aspx


A PowerShell function can have the following named script blocks:

  • Begin – This block is used to provide optional one-time pre-processing. In the case of multiple values provided through the pipeline this block only executes once.
  • Process – This block executes each time the command is called. In the case of multiple values provided through the pipeline this block executes for each one of those values.
  • End – This block is used to provide optional one-time post-processing.

When we write a cmdlet in C# we accomplish the same using the following methods:

  • BeginProcessing() – Provides a one-time, preprocessing functionality for the cmdlet.
  • ProcessRecord() – Provides a record-by-record processing functionality for the cmdlet.
  • EndProcessing() – Provides a one-time, post-processing functionality for the cmdlet.

Each of the methods is inherited from the PSCmdlet class so we need to use the protected and override modifiers.


The list of all supported methods is available at http://msdn.microsoft.com/en-us/library/system.management.automation.cmdlet_methods(v=vs.85).aspx . Since we are building a super simple module just to show the basic concepts we only need the ProcessRecord() method (we won’t need to initialize any data or finalize any action or actions at the end of execution). Also, we’ll use the WriteVerbose() method to write verbose information if requested. In an advanced function this would be the equivalent of using the Write-Verbose cmdlet. Next, a string object is created and returned to pipeline using the WriteObject() method. This is equivalent to placing a variable containing an object or collection of objects on its own in the body of an advanced function so it would be send down the pipeline.


Now we just need to build the solution by pressing F7 or selecting Build Solution from the BUILD menu.  In the Output pane of Visual Studio we should see that it builds successfully and provides us a path to the generated DLL.


To test the module we open a PowerShell session, navigate to where the DLL is and use the Import-Module cmdlet with the -Verbose parameter to see if it loads our cmdlet.


If we use the Get-Help cmdlet against Get-Salutation we should see the Name parameter and the proper information for it.


Let’s test the cmdlet directly and from the pipeline to make sure it works like it should. First, we test giving it several values to the parameter, then a collection of strings from the pipeline and at the end, an object with property that matches one of the aliases.


In the next part we will look at setting up debugging for the module inside of Visual Studio 2013.

If you are interested in more advanced examples for PowerShell 3.0 and 4.0 take a look at the sample pack in MSDN http://code.msdn.microsoft.com/Windows-PowerShell-30-SDK-9a34641d

Filed in: Articles, Online Only Tags: , , , ,

19 Responses to "Writing a PowerShell module in C#, Part 1: The basics"

  1. Mad Tom Vane says:

    Other than giving you experience with C# why is this way better than using a .psm1 file? Is it faster? Does it protect your work? Will people be able to create proxy cmdlets with them?

    • Ravikanth says:

      IMHO, this is not only about protecting work. I have worked on designing several PowerShell modules and one factor that should be considered while creating modules is the access to core product functionality. For example, many of the products for which we created PowerShell modules had either .NET or native API interfaces. It is easy, in such a case, to use C# and create cmdlets than fighting those semantics in PowerShell. PInvoke or Add-Type can get very ugly, at times.

    • Carlos Perez says:

      In my case I started writing some where I needed more control over time sensitive events, network discovery tasks with multiple threads and large file encryption where the speed of working directly with .Net played a considerable role in my decision in those cases. Also have friends that are looking at it as you mentioned to protect their IP using Redgate SmartAssembly to obfuscate their code. You can say if you know C# it is another tool in the toolbox for use.

    • Dave Wyatt says:

      C# code executes much faster than PowerShell code, which can be a concern if your function / cmdlet will be used to process large data sets. In a Cmdlet, you also get full access to object-oriented design patterns, multithreading, etc; many of these features of C# are difficult or impossible to accomplish in a PowerShell advanced function.

  2. Pietro Reverso says:

    Very good job here Carlos, the article is very much focused on practical step-by-step procedures and easy to read. Many thanks for sharing!

    Just a question about variables: is it possible to have a default value which is not a constant, but a PowerShell variable (i.e. a global variable like $Global:MyGlobalVar)?
    I have a sort of “scripting framework” where global options are stored to global variables, and some functions to transform into C# modules using them.

    I guess this is doable but I have to involve other classes in the System.Management.Automation library to access this variable in the stack and use it in the ‘Process’ methods , correct? Do you have any examples to drive me to the right direction?

    Many thanks again

    • Carlos PErez says:

      thanks, When you create the runspace you can call runSpace.SessionStateProxy.SetVariable() and runSpace.SessionStateProxy.PSVariable.GetValue() to get and set variables.

      • Prakash Iyengar says:

        Hi Carlos,

        Thanks for great article,need your help on below code,i have below C# module.This watches files in given directory.The same is working in console app.
        but when i create this as module and run it in powershell,it throws error and powershell.exe terminates.
        pls guide me if iam doing anything wrong.
        protected override void ProcessRecord()
        FileSystemWatcher fw = new FileSystemWatcher(paths);
        fw.EnableRaisingEvents = true;
        WriteObject($”watching all files in {paths}”);
        while (true)
        fw.Changed += (object sender, FileSystemEventArgs e) => { WriteObject($”{e.Name} has been changed and its path {e.FullPath} and type of change {e.ChangeType}”); };
        fw.Created += (object sender, FileSystemEventArgs e) => { WriteObject($”{e.Name} has been changed and its path {e.FullPath} and type of change {e.ChangeType}”); };
        fw.Deleted += (object sender, FileSystemEventArgs e) => { WriteObject($”{e.Name} has been changed and its path {e.FullPath} and type of change {e.ChangeType}”); };
        fw.Renamed += (object sender, RenamedEventArgs e) => { WriteObject($”{e.OldName} has been changed to {e.Name} and its path {e.FullPath} and type of change {e.ChangeType}”); };
        fw.Error += (object sender, ErrorEventArgs e) => { WriteObject($”Not able monitor”); };

  3. Marck says:

    I’m trying to create this module with .Net Core, but fails. I thik it is caused by the .dll System.Management.Automation (not available for Core) What .dll should I reference instead?

    Get-Salutation : Could not load file or assembly ‘System.Runtime, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file specified.
    At line:1 char:1
    + Get-Salutation -Name Marck
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Get-Salutation], FileNotFoundException
    + FullyQualifiedErrorId : System.IO.FileNotFoundException,ALM.PowerShell.Client.Class1

Leave a Reply

Submit Comment

© 2018 PowerShell Magazine. All rights reserved. XHTML / CSS Valid.
Proudly designed by Theme Junkie.
%d bloggers like this: