1

I have a need to create a class library in C# which I'd like to include in a PowerShell module I'm writing. I'd like to consume the DLL in PowerShell by invoking a method much like you would as follows:

Add-Type -Path $($variableHash.RootPath + "\Deviceparserstandalone.dll")    
[SkypeHelpers.CQTools.DeviceParser+Audio]::IsCertified($_.Caption)

The above is an example of PowerShell importing a DLL, then using the IsCertified method (i'm assuming it's a method).

Here is what I'm looking for:

  1. Is there anything special I need to do other than create a new Visual Studio project with the .NET Core class library template, import the dependencies I need for my library, then compile the code to a DLL?
  2. How do I expose some methods to be used by the consuming application (in this case PowerShell) while keeping other methods 'private'? Would this be like using an interface to describe the methods available to the consuming application? If so, is this similar to creating an interface for an ASP.NET Core 2.x website?

The goal is to write a class library that PowerShell can consume to create a toast notification for Windows 10. I realize there are public modules on PowerShell Gallery for this but I'm trying to understand how it's built from the ground up.

2
  • public methods should be accessible. What isn't working with the code you posted? Commented Feb 22, 2019 at 20:19
  • 2
    You may want to create a PowerShell module with your own Cmdlets in it. It will look just like any other native PowerShell module if you import it into a PowerShell session. You inherit from System.Management.Automation.Cmdlet, and add some attributes. The class's public properties become cmdlet parameters. You can get full Get-Help support from mostly standard C# XML comments. Take a look at: red-gate.com/simple-talk/dotnet/net-development/… Commented Feb 22, 2019 at 20:56

2 Answers 2

8

Major Update: for Powershell v7 and .NET Core - see below

Another Update: about using VS Code (rather than Visual Studio) - see further below

The other alternative is to make a full-on PowerShell module. Here's the quickie way to do that:

  1. Create a new Class Library Solution
  2. NuGet in Microsoft.PowerShell.5.ReferenceAssemblies

NOTE:
The Microsoft.PowerShell.5.ReferenceAssemblies NuGet package is only compatible with the .NET Framework (up to v4.8) and not with .NET Core. This can be rectified by targeting .NET Standard (see "Major Update" below)

  1. Create a simple POCO class (just as a demo) like:

My Greeting class:

public class GreetingClass
{
    public string Greeting { get; set; }
    public string ToWhom { get; set; }
}
  1. Then create a Cmdlet class that inherits from System.Management.Automation.Cmdlet and comes with a few PowerShell-specific attributes:

Here's mine:

[Cmdlet(VerbsCommon.New, "Greeting")]
[OutputType(typeof(GreetingClass))]
public class NewGreeting : Cmdlet
{
    [Parameter(Mandatory = true)]
    public string Greeting { get; set; }

    [Parameter]
    [Alias("Who")]
    public string ToWhom { get; set; } = "World";

    protected override void ProcessRecord()
    {
        base.ProcessRecord();

        WriteVerbose("Creating and returning the Greeting Object");
        var greeting = new GreetingClass {Greeting = Greeting, ToWhom = ToWhom};
        WriteObject(greeting);
    }
}
  1. In order to debug it, open the Debug tab project properties and set:
  • Start external program: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • Command line arguments: -noprofile -noexit -command "import-module .\yourProjectName.dll

Now press F5. A PowerShell console will come up. Since you imported your module, the New-Greeting cmdlet is loaded. So this all works:

PS C:\> New-Greeting -Greeting "hello there" -ToWhom World

resulting in:

Greeting    ToWhom
--------    ------
hello there World

But, your cmdlet is a full PowerShell cmdlet so it can get in on the pipeline. This:

PS C:\> New-Greeting -Greeting "What's new" -ToWhom Pussycat | Format-List

Will result in:

Greeting : What's new
ToWhom   : Pussycat

Since you start up a PowerShell session when you press F5 and load your module, you can set breakpoints in your code, single-step, etc. within the debugger.

You get all the normal PowerShell goodness. You can use -Who as an alias for -ToWhom. The -Greeting parameter is described as Mandatory, so if you forget it, the console will prompt you.

You can also expose methods on your POCO class. For example, if you add this method to the GreetingClass:

public void SayHello()
{
    Console.WriteLine($"{Greeting} {ToWhom}");
}

You can do this within PowerShell:

PS C:\> $greet = New-Greeting -Greeting Hello -Who World
PS C:\> $greet.SayHello()

and get this as a result:

Hello World

I learned a lot of this from: https://www.red-gate.com/simple-talk/dotnet/net-development/using-c-to-create-powershell-cmdlets-the-basics/

You get basic Get-Help help auto-magically from PowerShell, but if you follow along the Red-Gate demos (from the link above), you will learn how to get rich Get-Help help from standard C# XML Comments (from the XmlDoc2CmdletDoc NuGet package).

The XML Comments to Get-Help capabilities of XmlDoc2CmdletDoc are concisely described on the GitHub ReadMe page for XmlDoc2CmdletDoc: https://github.com/red-gate/XmlDoc2CmdletDoc


Major Update

I got this working using a .NET Standard assembly (i.e., .NET Core compatible) and the new Core-based PowerShell. (PowerShell v7). For the most part, everything above is still mostly valid.

  • Using Visual Studio 2022
  • Create a Class Library - C# (not .NET Framework) project
    • Set the Target Framework to .NET Standard 2.0
  • NuGet in Microsoft.PowerShell.5.1.ReferenceAssemblies

Getting it to debug is more difficult (because of the wacky new Debug setup for .NET Core applications):

  • Right-click the project in the Solution Explorer and choose Properties
  • On the Debug tab, choose the Open debug launch profiles UI link
  • In the Launch Profiles window that pops up:
    • Click the Create a new profile icon (the top-left-est icon in the window)
    • Choose it to be Executable
    • Set the Executable name to be C:\Program Files\PowerShell\7\pwsh.exe (or wherever you have PowerShell 7 installed)
    • Use the same Command Line Arguments as above (-noprofile -noexit -command "import-module .\yourProjectName.dll)
    • Once you have that set up, rename the profile (the right-most icon) to be exactly the same as the default profile that's already there.

That should do it. The same .NET Standard assembly will now work with both the traditional Windows PowerShell and the new-fangled PowerShell 7 (I'm using version 7.2.8).

Another Update: Using VS Code

To use this with VS Code:

  1. Open a command prompt
  2. Create the project in a new folder:
    dotnet new classlib -f netstandard2.0 -n [YourProjectName]
  3. Change to the newly created directory
  4. NuGet in the Microsoft.PowerShell.5.1.ReferenceAssemblies package using:
    dotnet add package Microsoft.PowerShell.5.1.ReferenceAssemblies
  5. Rename the sample CS file that got created to “Greeting.cs”
  6. Open the folder in VS Code
  7. Set things up so that it compiles a C# project in the normal fashion
  8. Add the following as launch.json:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "PowerShell cmdlets: pwsh",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            "program": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
            "args": [
                "-noprofile", 
                "-noexit", 
                "-command",
                "import-module .\\bin\\Debug\\netstandard2.0\\PowerShellVsCode.dll"
            ],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "console": "externalTerminal"
        }
    ]
}

If you want to use the XmlDoc2CmdletDoc NuGet package, you'll need to NuGet it in:
dotnet add package XmlDoc2CmdletDoc

And you will need to add the following line to the "args:" under "tasks:" in tasks.json:
"/property:GenerateDocumentationFile=true",


If I messed something up in these instructions, let me know in a comment.

Sign up to request clarification or add additional context in comments.

Comments

2

Nothing special to do. Try creating a new class library named "Animal" and add this code:

namespace Animal
{
    public class Dog
    {
        public string Bark()
        {
            return "Woof!";
        }
    }
}

Build the project (to create a .dll). You can find the .dll under <projectLocation>\bin\<Release|Debug>.

Now open powershell and enter this code:

Add-Type -Path "C:\...\Projects\Animal\Animal\bin\Debug\Animal.dll"
$dog = [Animal.Dog]::new()
$dog.Bark()

Output

enter image description here

1 Comment

You can even use Add-Type to compile the code itself to a dll @JasonShave

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.