Writing PowerShell cmdlets

After a conversation with someone at IBM last week about PowerShell, I picked it up again and have been having a play. I’ve been trying to write my own cmdlets, which has been an interesting experience – so I thought I’d jot down a few quick notes about what it’s been like.

First… a quick recap. Windows PowerShell is a command-line shell for system administrators. It has a number of neat features, but perhaps the most obvious is it’s object oriented approach – letting you pass objects (rather than strings) between commands in a pipe. I’ve done some work on writing PowerShell scripts before, but this time I approached it more as a developer – looking at how to extend the shell with new commands.

The .NET nature of PowerShell means you can run any .NET API at the Shell. In fact, this is what I did when I first played with PowerShell using the WebSphere MQ .NET API. It’s a quick way to get started without having to learn much about PowerShell – and I played around writing some simple scripts that were a translation of C# programs into the PowerShell syntax.

But it wasn’t really using PowerShell in the PowerShell way. What I have tried doing over the last couple of evenings is to extend PowerShell to include new commands that support WebSphere MQ administration work. I’ve been writing Cmdlets.

(If you’re not too interested in the detail of the whys or the hows of creating cmdlets but you are interested in WebSphere MQ, you might want to skip to the end of this post to see examples of what I’ve done in action…)

What are ‘Cmdlets’?

The PowerShell term for a command is “Cmdlet”. There is a good discussion about the distinction between Cmdlets and APIs on the PowerShell blog – essentially looking at if you already have a .NET API (as we do with WebSphere MQ), why bother creating Cmdlets as well?

The question boils down to “Why not let sys-admins use the API?”. And the best answer is because “sys-admins aren’t programmers”. If you have a very nice, clear API, maybe you can get away with giving them the API. But the focus of Cmdlets seem to be more about providing this function in an abstract, task-oriented, user-friendly way.

Besides this difference in focus and “mindset”, some other neat characteristics of Cmdlets worth mentioning…

All commands follow a VERB-NOUN pattern – a verb to describe the action to perform, and a noun to identify what to perform the action on. For example, Get-Process returns a list of processes running on the system. Putting the verb first is apparently supposed to reinforce the task-oriented mindset of cmdlets – it’s about what the user wants to do, rather than focusing on the product.

When writing a new Cmdlet, you choose a verb from the predefined list of Windows PowerShell verbs. I really like this constrained approach. It makes it much easier to start using an unfamiliar interface if you can make reasonable guesses at what the commands will be before you even start. (And with the tab-completion for all registered commands, guessing near to what a command is normally enough).

If that is not enough help, then there are standard ways to get more information about commands – from consistent approaches to get documentation for a command (you can use Get-Help – similar to the UNIX ‘man’ command) to ways to find a command if you can’t guess it’s name (with Get-Command).

The user gets to choose how to handle errors when running commands – when developing a Cmdlet, you distinguish between non-terminating and terminating errors – giving sys-admins the ability to choose the behaviour in the event of a non-terminating error (e.g. continue, continue silently, halt, prompt) in a consistent way for all Cmdlets.

Cmdlets (should!) support wildcards – even if the API they are wrapping does not. This is something that I think will make a big difference between a set of WebSphere MQ cmdlets, and the command-line tools we currently have. And the PowerShell engine does a lot to support the Cmdlet developer to make this possible.

The way you can pass objects through pipelines between commands is very powerful. For example, in PowerShell Stop-Process lets you stop a running process – similar to UNIX’s kill -9, and Get-Process lets you get the current processes – similar to UNIX’s ps. But putting them together is so much easier than in UNIX:

Get-Process notepad | Stop-Process
This stops all of your notepad processes. Doing this in a UNIX script means piping ps output into sed or something like that, to get the PIDs out for passing to kill.

Cmdlets can write output to more than just stdout, with the ability to generate output in various forms such as webpages, spreadsheets or RSS feeds.

Writing my first Cmdlets

1 – Identify what cmdlets I need.
I made a list of tasks WebSphere MQ sys-admins will want to be able to do – then matched these tasks up with a verb from the list of common Cmdlet verbs that was the closest match for each. (I was expecting this to be difficult – wondering how I’d handle it when I needed to do something that wasn’t covered by one of the verbs there – but that hasn’t happened. Everything that I’ve wanted to do so far can be described with one from the PowerShell set.)

2 – Check what they need to be able to do
The Cmdlet Development Guidelines was a useful document – containing guidelines on what Cmdlets should do, and how they should work. For example, -Debug should cause the cmdlet to write out debug information, and -Verbose should cause it to produce more detailed output. It goes back to what I was saying above about consistency making life easier for sys-admins starting with an unfamiliar product. So I tried to design functions which conformed to the norms of a PowerShell command.

3 – Download the Visual Studio PowerShell templates
I’m quite familiar with Visual Studio 2005, as I use it for my Windows Mobile development work – so the Windows PowerShell Visual Studio 2005 Templates pack was a big help. With this installed, I can create PowerShell projects in Visual Studio which makes the writing and compiling of Cmdlets much easier.

4 – Creating my first Get-WMQ… Cmdlet
The “How to Create a Windows PowerShell Cmdlet” walkthrough on MSDN is a good start – it goes through the steps required to write the Get-Process cmdlet, and included enough samples to get me going with my first cmdlet, Get-WMQQueueManager.

5 – Write the Cmdlet
I’m writing them in C#, using the WebSphere MQ .NET API. You basically need to create subclasses of Cmdlet or PSCmdlet, overriding methods which PowerShell will invoke to execute the command (typically, ProcessRecord).

6 – Build the Cmdlet
Visual Studio does most of the work for me, and the remaining steps are well described in a blog post on how to use the Visual Studio templates, but essentially I compile my C# cmdlet classes into a DLL. This DLL can then be registered with PowerShell, which adds all of my shiny new WebSphere MQ commands to the Shell.

What they can do

My work is nowhere near finished. There are several verbs I want to support (nearly two dozen), and nine main types of WMQ object – so it will take me a while before I cover all the combinations.

But even after a couple of evenings of coding, (and this is all output from working code… nothing faked here – honest! 🙂) I can do the following:


get me a list of local queue managers, reverse sorted by name, and show me their name, qmid and description

 
Get-WMQQueueManager * | Select Name, QueueManagerIdentifier, QueueManagerDescription | Sort-Object Name -descending 

Name                                QueueManagerIdentifier                       QueueManagerDescription 
----                                ----------------------                       ----------------------- 
Test                                Test_2007-09-03_09.33.44                     to check name collision 
TeSt                                TeSt_2007-09-03_09.33.47                     to check name collision 
TEST                                TEST_2007-09-03_09.34.02                     Test queue manager - to be deleted 
test                                test_2007-09-02_22.14.03 
post                                post_2007-09-03_09.34.19 
dale                                dale_2007-09-03_09.01.37                     personal qmgr - for client development work 


show me the name and QMID of all local queue managers which have names ending in ‘st’

  
Get-WMQQueueManager *st | select Name, QueueManagerIdentifier 

Name                                 QueueManagerIdentifier 
----                                 ---------------------- 
post                                 post_2007-09-03_09.34.19 
test                                 test_2007-09-02_22.14.03 
Test                                 Test_2007-09-03_09.33.44 


find which queue managers have a queue called “FINAL.Q”

 
Get-WMQQueue FINAL.Q * | Select Name, @{e={$_.QueueManager.Name};n='Queue Manager'} 

Name                                    Queue Manager 
----                                    ------------- 
FINAL.Q                                 post                                ... 


get a list of all queues which contain the word “CLUSTER” in their name, from queue managers with names ending in “st”

 
Get-WMQQueue *CLUSTER* *st | Select Name, @{e={$_.QueueManager.Name};n='Qmgr'} 

Name                                              Qmgr 
----                                              ---- 
SYSTEM.CLUSTER.COMMAND.QUEUE                      post 
SYSTEM.CLUSTER.REPOSITORY.QUEUE                   post 
SYSTEM.CLUSTER.TRANSMIT.QUEUE                     post 
SYSTEM.CLUSTER.COMMAND.QUEUE                      test 
SYSTEM.CLUSTER.REPOSITORY.QUEUE                   test 
SYSTEM.CLUSTER.TRANSMIT.QUEUE                     test 
SYSTEM.CLUSTER.COMMAND.QUEUE                      Test 
SYSTEM.CLUSTER.REPOSITORY.QUEUE                   Test 
SYSTEM.CLUSTER.TRANSMIT.QUEUE                     Test 


show me all local queues on all local queue managers where the current queue depth is less than 10 messages away from it’s max-depth setting

 
Get-WMQQueue * * | Where {$_.QueueType -eq [IBM.WMQ.MQC]::MQQT_LOCAL -and $_.CurrentDepth -gt ($_.MaximumDepth - 10)} | Select Name, CurrentDepth, MaximumDepth 

Name                                     CurrentDepth              MaximumDepth 
----                                     ------------              ------------ 
MYQ                    ...                          7                        15 


get all transmission queues (except the cluster transmit queue) from all queue managers and show their depths and open counts

 
Get-WMQQueue * * | Where {$_.Usage -eq [IBM.WMQ.MQC]::MQUS_TRANSMISSION -and $_.Name -ne "SYSTEM.CLUSTER.TRANSMIT.QUEUE"} | Select Name, CurrentDepth, OpenInputCount, OpenOutputCount, @{e={$_.QueueManager.Name};n='Queue Manager'} 

Name                       CurrentDepth      OpenInputCount     OpenOutputCount Queue Manager 
----                       ------------      --------------     --------------- ------------- 
TRANS1                                0                   0                   0 dale            ... 


get me all channels from all local queue managers which have an SSL Cipher Spec applied, and show me their name, sslciph, conname and the name of the queue manager they are on – sorted by channel name

 
Get-WMQChannel * * | Where {$_.SSLCipherSpec -ne ''} | Select Name, @{e={$_.QueueManager.Name};n='Queue Manager'}, SSLCipherSpec, ConnectionName | Sort Name 

Name                                 Queue Manager                        SSLCipherSpec                        ConnectionName 
----                                 -------------                        -------------                        -------------- 
SECURE                               post                             ... NULL_MD5                             dlane.hursley.ibm.com(9090) 
SECURE.R                             test                             ... TRIPLE_DES_SHA_US                    dlane.hursley.ibm.com(9091) 
SECURE.X                             dale                             ... TLS_RSA_WITH_AES_256_CBC_SHA         dlane.hursley.ibm.com(9094) 


get me all non-system (i.e. channels with names that don’t start with SYSTEM) sender channels from local queue managers with names ending in “st”, and show me their name, conname, transmit queue, sslciph, and the name of the queue manager they are on

 
Get-WMQChannel * *st | Where {$_.Name -match "^(?!SYSTEM).*" -and $_.ChannelType -eq [IBM.WMQ.MQC]::MQCHT_SENDER} | Select Name, ConnectionName, TransmissionQueueName, SSLCipherSpec, @{e={$_.QueueManager.Name};n='Hosting Queue Manager'}  

Name                            ConnectionName                  TransmissionQueueName           SSLCipherSpec                   Hosting Queue Manager 
----                            --------------                  ---------------------           -------------                   --------------------- 
SECURE                          dlane.hursley.ibm.com(9090)     TRANS1                          NULL_MD5                        post                        ... 
SECURE.R                        dlane.hursley.ibm.com(9091)     TRANSR                          TRIPLE_DES_SHA_US               test                        ... 


generate an HTML webpage with a table showing the name, description and depth information for all queues on the ‘test’ queue manager, and open this HTML file in a web-browser

 
Get-WMQQueue * test | ConvertTo-Html -property Name,Description,CurrentDepth,MaximumDepth -title "Queues on my test queue manager" > myqueues.htm 
Invoke-Item myqueues.htm 


generate a CSV spreadsheet containing the name, description and depth information for all queues on the ‘test’ queue manager, and open this in Excel

 
Get-WMQQueue * test | Select Name, Description, CurrentDepth, MaximumDepth | Export-Csv -path myqueues.csv 
Invoke-Item myqueues.csv 

2 Responses to “Writing PowerShell cmdlets”

  1. John says:

    Hello..I have been reading a ton of your stuff lately it seems and came across this webSphereMQ stuff. I, FOR THE LIFE OF ME, cannot figure out how show my remote queues sing powershell. I have used everything I have read and cannot get it to show anything when running the commands. Am I missing something?

    PS C:\> $qmconns = @()
    PS C:\> $qmconns += New-WMQQmgrConnDef -Name DALEQM -Hostname dlane.hursley.ibm.com -Channel SVRCN -Port 1414
    PS C:\> $qmconns += New-WMQQmgrConnDef -Name CENTQM -Hostname sysserv.boulder.ibm.com -Channel SVRCN -Port 1418
    PS C:\>
    PS C:\> $qmgrs = Get-WMQQueueManager –Connections $qmconns

    Is something supposed to show up after this last command? I thought not..so I typed the next entry in the guide and nothing. When I log on locally and run the local commands..I have no issues at all.

    Any idea what may be going on here?

  2. dale says:

    John

    I wouldn’t expect you to have any luck getting a connection to dlane.hursley.ibm.com working – that’s my server! (And I don’t have a queue manager running at the moment, anyway)

    Is sysserv.boulder.ibm.com a server of yours? It seems unlikely… if not, then you need to be trying to connect to your own servers, not IBM’s.

    Kind regards

    D