Update (13/12/2007): The contents of this post have since been largely superseded by the release of PowerShell cmdlets for WebSphere MQ.

This month’s issue of TechNet Magazine includes an article giving a nice introduction to PowerShell – the new Windows command shell and scripting language. It was a useful reminder that I had said that I was going to look into writing some useful PowerShell functions for WebSphere MQ, so last night I had a quick play to see what I could do with it.

In this post, I discuss how PowerShell can be used to extend the capabilities of existing command-line tools for WMQ, and outline how I got it working.

I began by thinking of what PowerShell could do to supplement runmqsc. To recap from my earlier post, PowerShell is a scripting language which supports the use of .NET libraries, such as that provided with WMQ. Two obvious points suggested themselves as places to start:

  1. Connect to remote queue managers over a client connection
    • rather than just local queue managers
  2. Provide more powerful wildcards and filtering when using DISPLAY to show queue manager objects
    • rather than just accepting full names, or prefix followed by *

The full source for my script is included at the end of this post. The rest of this post breaks the script down and discusses each part.

My approach

A global queue manager handle

I don’t want every command – such as a DISPLAY QLOCAL equivalent, or a PUT MESSAGE command – to have to make it’s own connection to the queue manager. Instead, I want to separate out connect-to-queue-manager from do-stuff-with-queue-manager. I decided on this approach for two usability reasons:

  • it means I don’t have to include queue manager information (qmgr name, hostname etc.) in every command I type
  • it is a little clearer for me as a user – connecting to the queue queue manager, do stuff with it, then connect to a different queue manager if I want to do something with another one – this is a paradigm that I am used to, and it is more consistent with using runmqsc

It means I lose a little flexibility in being able to work with multiple queue managers from one command to the next, but this is outweighed by the usability reasons for me. (If this doesn’t suit your needs is easily changed).

To do this, I decided to store my queue manager handle as a single global variable – letting me use it between my different functions without having to worry about passing it around in each command.

So that I know which queue manager, if any, I am connected to – I also overrided the PowerShell prompt function to display the queue manager name in my command prompt.

First steps

Enabling scripts

Execution of PowerShell scripts is disabled by default, so my first step was to enable this. I am using this on a non-production personal machine, so I am happy to allow local scripts to run unsigned, so I entered this at a PowerShell prompt:

Set-ExecutionPolicy RemoteSigned

This means that scripts and configuration files downloaded from the Internet have to be signed by a trusted publisher before they can be run, but scripts that I write locally can be run as-is. Your security needs may vary from mine, so you may want to require that all scripts are signed – and make yourself a certificate to sign your own scripts.

Commands available in every PowerShell window

To make my functions available in every new PowerShell window, I added my functions to my PowerShell profile.

There are a number of locations for PowerShell profiles, depending on whether you want functions to be available to all users, or just one. There is more information about these on MSDN, but for my purposes I am editing C:\Documents and Settings\Dale Lane\My Documents\WindowsPowerShell\profile.ps1, making it available to all of my PowerShell windows. (Note that this location is not pre-created by default – I had to do this myself)

.

1 – Connecting to remote queue managers

I will now step through the profile.ps1 file that I created, highlighting points that I found interesting:

Loading WMQ

#  Load WMQ DLL
#   We do it here to avoid loading it in every function
#
[System.Reflection.Assembly]::LoadFrom("C:\\Program Files\\IBM\\WebSphere MQ\\bin\\amqmdnet.dll")

The first thing that I do in my profile script is to load the WebSphere MQ DLL. This means I don’t have to add this call to every function, and as I am not using PowerShell for anything else at the moment, I don’t mind (in fact, have hardly noticed) the overhead of loading the WMQ into every window.

Connecting to the queue manager

#
# connect to the queue manager, and store the handle in a global variable
#  where it can be accessed by other functions in this profile
#
function Connect-WMQQmgr($name, $hostname, $port, $svrconn)
{
    # display details of any WMQ errors encountered in this function
    Trap [IBM.WMQ.MQException] 
    { 
        Write-Error ("WMQ Exception: CC=" + $_.Exception.CompletionCode + " RC=" + $_.Exception.ReasonCode)
        continue
    }

    # if we already have a handle to another queue manager, 
    #  we disconnect first
    if ($Global:psWMQQmgrHandle -ne $null)
    {
        Disconnect-WMQQmgr
    }


    # hashtable to describe the connection to the queue manager
    $connProperties = new-object System.Collections.Hashtable


    if (($hostname -eq $null) -and ($port -eq $null) -and ($svrconn -eq $null))
    {
        
        # user has not provided any information for a client connection
        #  so we default to a local bindings connection type 

        $connProperties.Add([string][IBM.WMQ.MQC]::TRANSPORT_PROPERTY, 
                            [string][IBM.WMQ.MQC]::TRANSPORT_MQSERIES_BINDINGS)

    }
    else 
    {

        # user has provided some information for a client connection
        #
        # a future version of this should provide support for other 
        #  connection types (e.g. managed or XA client) but for 
        #  my initial purposes, bindings and client connections are 
        #  sufficient

        $connProperties.Add([string][IBM.WMQ.MQC]::TRANSPORT_PROPERTY, 
                            [string][IBM.WMQ.MQC]::TRANSPORT_MQSERIES_CLIENT)

        if ($hostname -ne $null) 
        {
            $connProperties.Add([string][IBM.WMQ.MQC]::HOST_NAME_PROPERTY, 
                                $hostname)
        }

        if ($svrconn -ne $null) 
        {
            $connProperties.Add([string][IBM.WMQ.MQC]::CHANNEL_PROPERTY, 
                                $svrconn)
        }
        else 
        {
            # use a sensible default
            #  this wont be to everyone's tastes, but will often be
            #  right for me, and will save me a lot of typing!

            $connProperties.Add([string][IBM.WMQ.MQC]::CHANNEL_PROPERTY, 
                                "SYSTEM.DEF.SVRCONN")
        }

        if ($port -ne $null) 
        {
            $connProperties.Add([string][IBM.WMQ.MQC]::PORT_PROPERTY, 
                                $port)
        }
        else 
        {
            # use a sensible default
            #  this wont be to everyone's tastes, but will often be
            #  right for me, and will save me a lot of typing!

            $connProperties.Add([string][IBM.WMQ.MQC]::PORT_PROPERTY, 
                                "1414")
        }
    }

    $Global:psWMQQmgrHandle = new-object IBM.WMQ.MQQueueManager($name, $connProperties)
}

Notice:

  • Use of global variable $Global:psWMQQmgrHandle to store the queue manager handle as discussed earlier. Prefixing the variable with the scope ‘Global’ makes it available to all functions. I’ve chosen what is hopefully an obscure enough name that it wont clash with anything I might want to do interactively.
  • Accessing WebSphere MQ constants from the .NET library by prefixing them with [IBM.WMQ.MQC]::
  • Use of trap in case something goes wrong. Putting trap at the start of my function registers an exception handler. You can register multiple exception handlers, and/or have a trap block without specifying an exception type to catch all other exceptions.
    As you can see from the function about, I am only interested in catching IBM.WMQ.MQException exceptions, so that I can get the reason and completion codes out of it if something fails.
  • Use of [ ] to specify types of variables – sort of like a cast? For example,
    [string][IBM.WMQ.MQC]::PORT_PROPERTY

    to show that this is a string. I am not sure if this is absolutely required in the final script, but it was certainly a very useful trick while developing the script. By telling PowerShell what type an object is, it can give you more useful tab-completion with it, because it knows what methods are appropriate for an object.

  • Comparisons using switch-type syntax. E.g.
    ($hostname -eq $null) -and ($port -eq $null)

    This caught me out at first when writing commands as I would normally write C# / .NET code. These commands are all documented in the User Guide that comes with the PowerShell installation. Presumably these are used in place of more traditional syntax like ‘|’ because those characters have different meanings in PowerShell for pipes, redirects etc.

  • NULL can be accessed with $null for use in comparisons

Disconnecting from the queue manager

# 
# disconnect from the queue manager
#
function Disconnect-WMQQmgr()
{
    # display details of any WMQ errors encountered in this function
    Trap [IBM.WMQ.MQException] 
    { 
        Write-Error ("WMQ Exception: CC=" + $_.Exception.CompletionCode + " RC=" + $_.Exception.ReasonCode)
        continue
    }

    # if we have a connection to a queue manager...
    if ($Global:psWMQQmgrHandle -ne $null)
    {
        # disconnect from it
        $Global:psWMQQmgrHandle.Disconnect()
    
        # clear the handle
        $Global:psWMQQmgrHandle = $null
    }
}

This is fairly straight-forward, and there is nothing in the function that is very different from the connect function above.

Customising the command prompt to show queue manager name

As discussed above, I use the command prompt to remind me which – if any – queue managers I have connected to. PowerShell displays a prompt by calling the function prompt.

By overriding this function, you can get the prompt to display anything you want, for example:

#
# customise the user prompt to display the queue manager name if the 
#  user has connected to a queue manager
#
function prompt 
{
   if ($Global:psWMQQmgrHandle -ne $null)
   {
        Write-Host ("PS-WMQ " + $Global:psWMQQmgrHandle.Name.Trim() + " > ") -nonewline
   }
   else 
   {
        Write-Host ("PS " + $(get-location) + "> ") -nonewline
   }

   return " "
}

You can use Get-Content function:prompt to see what you’ve already got, which is a useful way to get started.

Displaying queue manager attributes

We can now use the queue manager handle to very simply display it’s attributes:

#
# display the attributes of the current queue manager
#
function Display-WMQQmgrAttrs($name)
{
    # if we have a connection to a queue manager...
    if ($Global:psWMQQmgrHandle -ne $null)
    {
        $Global:psWMQQmgrHandle
    }
    else 
    {
        Write-Host "Not connected to a queue manager"
    }
}

Using aliases to have slightly more familiar command names

We now have the following commands:

PS C:\>  Connect-WMQQmgr -name DALE
PS-WMQ DALE >  Display-WMQQmgrAttrs

IsConnected                    : True
IsClient                       : False
CodedCharSetId                 : 850
AlterationDateTime             : 16/04/2007 15:09:10
AuthorityEvent                 : 0
BeginOptions                   : 0
ChannelAutoDefinition          : 0
ChannelAutoDefinitionEvent     : 0
ChannelAutoDefinitionExit      :                                                                                                                               
                                   
CharacterSet                   : 850
ClusterWorkLoadData            :                                 
ClusterWorkLoadExit            :                                                                                                                               
                                   
ClusterWorkLoadLength          : 100
CommandInputQueueName          : SYSTEM.ADMIN.COMMAND.QUEUE                      
CommandLevel                   : 600
CreationDateTime               : 16/04/2007 15:09:10
DeadLetterQueueName            :                                                 
DefaultTranmissionQueueName    :                                                 
DistributionListCapable        : True
ExpiryInterval                 : 
InhibitEvent                   : 0
LocalEvent                     : 0
MaximumHandles                 : 256
MaximumMessageLength           : 4194304
MaximumPriority                : 9
MaximumUncommittedMessages     : 10000
PerformanceEvent               : 0
Platform                       : 11
QueueManagerDescription        :                                                                 
QueueManagerIdentifier         : DALE_2007-04-16_15.09.10                        

etc.

PowerShell is smart enough to not require you to enter parameter names in full – if you enter enough to uniquely identify the parameter, it will fill in the rest. So, this is valid:

Connect-WMQQmgr -n DALE -h localhost -p 1418

Even so, I added some aliases to my profile to make it look a little more like using runmqsc:

#
# Set some aliases which make the commands look a bit more
#  like the traditional runmqsc command line!
#
Set-Alias -Name runmqscps -Value Connect-WMQQmgr 
Set-Alias -Name disqmgr   -Value Display-WMQQmgrAttrs

So I can now do:

PS C:\>  runmqscps DALE
PS-WMQ DALE >  disqmgr


IsConnected                    : True
IsClient                       : False
CodedCharSetId                 : 850
AlterationDateTime             : 16/04/2007 15:09:10

etc.

.

2 – Displaying queue manager objects from wildcard names

A quick disclaimer

There is a bit of a problem with doing this – the documented C# API for WebSphere MQ does not provide the ability to get a list of queue manager objects. There is a PCF interface to get this information, but this is undocumented and I would imagine therefore unsupported.

For my purposes with PowerShell, that is fine – but please don’t take this as any more than a discussion of me playing around with code. It is not intended to be any sort of IBM recommendation or endorsement of using PCF in C#.

Using filters

I wrote the function Get-WMQQueueNames to get all queue names from a queue manager using PCF objects. You can see the source in the file attached at the end of this post, but for now – you just need to know that it returns an array of strings containing the queue names.

#
# Displays queue information for the current queue manager
#
#  optional filter argument can be used to limit which queues
#  to display - supporting wildcards on the queue names
#
function Display-WMQQueues($filter)
{
    # display details of any WMQ errors encountered in this function
    Trap [IBM.WMQ.MQException] 
    { 
        Write-Error ("WMQ Exception: CC=" + $_.Exception.CompletionCode + " RC=" + $_.Exception.ReasonCode)
        continue
    }

    # if we have a connection to a queue manager...
    if ($Global:psWMQQmgrHandle -ne $null)
    {
        $qNames = Get-WMQQueueNames

        # have we been given a wildcard name filter?
        if ($filter -ne $null)
        {
            $qNames = $qNames | Where-Object -FilterScript {$_ -like $filter}
        }

        foreach ($queuename in $qNames)
        {
            $nextQueue = $Global:psWMQQmgrHandle.AccessQueue($queuename.Trim(),
                                                             [IBM.WMQ.MQC]::MQOO_INQUIRE)

            # display the whole queue object - next step will be
            #  to support displaying certain selected attributes
            $nextQueue
            Write-Host "-----------------------------------------"
        }
    }
    else 
    {
        Write-Host "Not connected to a queue manager"
    }
}

Notice:

  • Use of Where-Object to provide wildcard-capable filtering. I am using -like but PowerShell supports many other types of comparisons, so once I have decided on some syntax for my function, I can make it much more powerful. For example, I can see -notlike being quite useful to perform inverse matching.
  • Use of aliases again – letting me use disql as a shorthand for the function.
  • Use of an object definition to dump all parameters to the console: $nextQueue. This is very verbose – perhaps too much so (the equivalent of doing DIS QL(*) ALL in runmqsc). So a good next step would be to refine this function to output identified parameters. These could be displayed using commands like: Write-Host $nextQueue.Name or Write-Host $nextQueue.Description

This means that I can now do:

PS C:\>  runmqscps DALE
PS-WMQ DALE >  disql *CLUSTER*

To get a list of all queues containing the string “CLUSTER”. This is already an improvement on what I can do with runmqsc.

.

Finally

This is by no means intended to be presented as a finished toolset – it is the result of playing around with PowerShell for a single evening which I am publishing to highlight what is possible, together with some of the differences in syntax for those such as myself who are more used to C#.

I have tried to follow what I think are PowerShell conventions – such as in the Verb-Noun approach to naming functions – but I am very much a beginner so please do not see my comments as any sort of guide to best practice with PowerShell!

I hope that it might give you ideas for what you could use PowerShell for. I am planning on adding to and refining my functions as I use them, and would welcome any suggestions or comments on how they could be improved. (Error-handling, for example, would be a useful addition!)

.

The source

PowerShell script source

Advertisements