Cmdlet Creation with PowerShell
I was looking through the PowerShell-Tests repo and I saw they were creating Cmdlets inline for some tests. They were using C# and Add-Type which got me wondering.
Can you create one using pure PowerShell? Yeah, you can. With a little extra handling.
Building the Cmdlet
using namespace System.Management.Automation
using namespace System.Reflection
# Basic do nothing Cmdlet.
[Cmdlet([VerbsDiagnostic]::Test, 'Cmdlet')]
class TestCmdletCommand : PSCmdlet {
[Parameter(ValueFromPipeline)]
[object]
$InputObject;
[void] ProcessRecord () {
$this.WriteObject($this.InputObject)
}
}
Just a basic cmdlet that passes anything it gets from the pipeline. But right now, it’s just a class. Now we need to tell PowerShell it’s a cmdlet. This is where we have to stretch a little.
First, we need to create a session state entry.
$cmdletEntry = [Runspaces.SessionStateCmdletEntry]::new(
<# name: #> 'Test-Cmdlet',
<# implementingType: #> [TestCmdletCommand],
<# helpFileName: #> $null
)
Then we need to get the internal session state. This is the “private” version of the normally accessible
$ExecutionContext.SessionState
.
$internal = $ExecutionContext.SessionState.GetType().
GetProperty('Internal', [BindingFlags]'Instance, NonPublic').
GetValue($ExecutionContext.SessionState)
Now we can load the session state entry into our current session.
$internal.GetType().InvokeMember(
<# name: #> 'AddSessionStateEntry',
<# invokeAttr: #> [BindingFlags]'InvokeMethod, Instance, NonPublic',
<# binder: #> $null,
<# target: #> $internal,
<# args: #> @(
<# entry: #> $cmdletEntry
<# local: #> $true
)
)
That still won’t actually load the cmdlet. But if we wrap all that in a module (dump it in a .psm1 file) and import it, we have ourselves a working cmdlet.
PSClassCmdlet.psm1
Trying it out
PS> Import-Module .\PSClassCmdlet.psm1
PS> Get-Module PSClassCmdlet.psm1
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 PSClassCmdlet Test-Cmdlet
PS> Get-Command Test-Cmdlet
CommandType Name Version Source
----------- ---- ------- ------
Cmdlet Test-Cmdlet 0.0 PSClassCmdlet
PS> 0..3 | Test-Cmdlet
0
1
2
3
Why use this over functions
That’s a good question. I haven’t actually tried using it past this example, but here are some thoughts.
Using the class structure
There doesn’t seem to be a whole lot of adoption of classes just yet. But if you’re a fan this could be a nice alternative to traditional PowerShell functions.
Inheritance
Inheritance is the main value I see. Lets say you are working on a module where every command shares some parameters. You could set up a base cmdlet and every child cmdlet will share those parameters. You could also inherit existing cmdlets. If that’s something you’re interested in you can run this snippet to show you what existing cmdlets can be inherited.
$assemblies = [AppDomain]::CurrentDomain.GetAssemblies()
$assemblies.GetTypes().Where{
[System.Management.Automation.Cmdlet].IsAssignableFrom($_) -and
-not $_.IsSealed
}
Word of warning
Unless there is a much easier way to load these than I found, it’s pretty clear this isn’t exactly supported. If you decide to give this a go, keep in mind that some things may act unpredicably. If I happen to find any gotchas I’ll update this post.