PowerShell defines a number of different types of command. Functions are written in PowerShell (normally), and Cmdlets are written in a compiled language (C# more often than not).

Classes, introduced with PowerShell 5, offer the possibility of creating a Cmdlet in PowerShell. The problem is, defining a class or adding a type is not enough to “register” a Cmdlet. Import-Module handles this step for compiled modules, but not script modules.

The first step is to define a simple class, inheriting from PSCmdlet with all the appropriate attributes.

Once the class has been defined it needs to be imported. Getting at the part of PowerShell that allows a Cmdlet to be imported is a bit more difficult. SessionStateInternal appears to be the right choice of objects to build, but it is a non-public class which expects a non-public argument, ExecutionContext.

As the $ExecutionContext variable holds an EngineIntrinsics object instance it has to be put aside. An alternative is to call the GetExecutionContextFromTLS static method. This is a non-public method on the non-public class, LocalPipeline. Getting the method via reflection provides an avenue for invoking it.

The method itself does not require an argument, but as it is non-public there’s a need to pass binding flags.

Now the ExecutionContext has been acquired it’s time to create the SessionStateInternal object. Get the type and a constructor which accepts ExecutionContext:

Once the constructor is in hand, create the SessionStateInternal object:

A simple check of the current path work towards showing whether or not this is actually the right object.

The SessionStateInternal class has a number of methods for handler addition of functions, Cmdlets, Providers, and so on. The method used to add a Cmdlet expects an object of type SessionStateCmdletEntry. That may be created as follows.

Using GetMethod along with this object allows selection of the correct method from SessionStateInternal which can be immediately invoked.

Putting the whole thing together, a little function may be created.

2 Comments

  1. Hi,

    good article. Thank you very much. But I think that the namespace in the code above is not correct. “Runspaces” is missing: “System.Management.Automation.Runspaces.SessionStateCmdletEntry” should be correct.

    $sessionStateCmdletEntry = New-Object System.Management.Automation.Runspaces.SessionStateCmdletEntry(
    ‘Get-Something’,
    [GetSomethingCommand],
    $null
    )

    Reply

  2. It’s there, but when you hover over the example the first line is hidden behind the utility drop down. I’ll see if I can fix that.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *