MQSCX V9.3.1 new expression functions

MQSCX V9.3.1 has two new expression functions.

  • trimstr()
    We were prompted to create this new expression function after writing the script shown here which makes it’s own Trim function. Most of the time when writing scripts that manipulate MQSC response variables, MQSCX already trims off the trailing blanks for you, but processing the CUSTOM attribute was a little different, and thus prompted this new addition.

    trimstr() takes up to three parameters. The first parameter is always the string to be trimmed. The second parameter contains the leading and trailing parameters you want removed. If it is omitted then blanks are assumed to be what you want removed. The third parameter is for the times when you want to remove different leading characters to trailing characters, then the second parameter becomes the leading characters to remove, and the third parameter contains the trailing characters to remove.

    Another example (in addition to above mentioned script) you could find to use this, would be to extract the port number out of a channel’s CONNAME field. Something like this:-

    DISPLAY CHANNEL(MQG1.TO.MQG2) CONNAME
    @offset = findstr(CONNAME,"(")
    @port = trimstr(substr(CONNAME, @offset, 264),"()")
    print @port

    Or you could go all in and use it to check valid character sets are in use in your object naming conventions, like this.

    foreach(DISPLAY QUEUE(*))
      @test = trimstr(QUEUE, "ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789")
      if (@test)
        print "Queue name", QUEUE, "doesn't follow convention"
      endif
    endfor
  • attrstr()
    This function takes an attribute name to look for and a string to search for it in. For example the _lastresp string, or in our previous case the CUSTOM string. It finds the attribute name and the value of the attribute between the brackets and returns the whole string, e.g. “DISCINT(6000)”.

    This function changes our previously written script shown here quite markedly. Instead of the two separate functions needed to build a new version of the CUSTOM string by manipulating it to remove the CAPEXPRY piece, we can now quite simply do the following:-

    @CmdStr = "ALTER " + _item + "(" + @ObjName + ") " +
            + @NewCapExp +
            + " CUSTOM('" +
            + strreplace(CUSTOM,attrstr("CAPEXPRY",CUSTOM),"") +
            + " ')"

    You could also make great use of this new function in any script where you might be building up an MQSC command based on the output of another DISPLAY command. Where previously you might have had to write something like this:-

    @CmdStr = "ALTER CHANNEL(MQG1.TO.MQG2) "
    
    @hbint = HBINT
    if (@hbint)
      @CmdStr = @CmdStr + "HBINT(" + str(@hbint) + ") "
    endif
    
    @kaint = KAINT
    if (@kaint)
      @CmdStr = @CmdStr + "KAINT(" + str(@kaint) + ") "
    endif
    
    @discint = DISCINT
    if (@discint)
      @CmdStr = @CmdStr + "DISCINT(" + str(@discint) + ") "
    endif

    This could now be replaced with this, which is more succinct, and more efficient:-

    @CmdStr = "ALTER CHANNEL(MQG1.TO.MQG2) " +
            + attrstr("HBINT",_lastresp) +
            + attrstr("KAINT",_lastresp) +
            + attrstr("DISCINT",_lastresp)

    You don’t need any of the if tests from the above example because concatenating and empty string is a no-op. If attrstr() doesn’t find the attribute it returns the empty string.


The new version can be downloaded from the MQSCX Download Page. Any current licensed users of MQSCX can run the new version on their existing licence. If you don’t have a licence and would like to try out MQSCX then send a note to support@mqgem.com and a 1-month trial licence will be sent to you.

Advertisement

Completely Successful Scripts

If you create and run MQSC script files, you may make use of the Command Summary that is output at the end of a script, which looks something like this:

MQSC Command summary - Issued: 3  Success: 2  Fail: 1

If all the commands are successful, then there is no need to inspect the rest of the output as you know everything worked.

You may have noticed however, that any DISPLAY commands that return “nothing found” are included in the fail count. This is because the command returns an error. We don’t think that this is actually very helpful behaviour, and have, in the recently released MQSCX V9.3.1 made a change to that. Now, by default, such empty DISPLAY commands will be treated as a success by default.

Take for example, the following little script that defines and starts a listener. It is written to ensure the listener has actually successfully started, and not just to issue the START LSTR command and hope for the best.

@LstrName = "TCP.LSTR"
@LstrPort = 1555

*********************************************************************
* Define the listener object, use REPLACE for repeatability         *
*********************************************************************
DEFINE LSTR(<@LstrName>) TRPTYPE(TCP) PORT(<@LstrPort>) +
       CONTROL(MANUAL) REPLACE

*********************************************************************
* If the listener is not already running, start it and check        *
*********************************************************************
DISPLAY LSSTATUS(<@LstrName>) STATUS
if (_matches = 0)
  START LSTR(<@LstrName>)

  while(1)
    wait(1)
    DISPLAY LSSTATUS(<@LstrName>) STATUS
    if (_matches = 0)
      print "Lstr",@LstrName,"failed to start - check AMQERR01.LOG"
      break
    else
      if (STATUS = "RUNNING") break; endif
    endif
  endwhile
endif

Running this script on the prior version of MQSCX would give you output like this:-

Connected to 'MQG1'
DEFINE LSTR(TCP.LSTR) TRPTYPE(TCP) PORT(1555) CONTROL(MANUAL) REPLACE
AMQ8626I: IBM MQ listener created.
DISPLAY LSSTATUS(TCP.LSTR) STATUS
AMQ8147E: IBM MQ object TCP.LSTR not found.
START LSTR(TCP.LSTR)
AMQ8021I: Request to start IBM MQ listener accepted.
DISPLAY LSSTATUS(TCP.LSTR) STATUS
AMQ8631I: Display listener status details.
LISTENER(TCP.LSTR)                 STATUS(RUNNING)     PID(10488)
Disconnected from 'MQG1'
MQSC Command summary - Issued: 4  Success: 3  Fail: 1

As you can see, this script did not report full success because the DISPLAY LSSTATUS command failed – you can see the error message from the MQ command server AMQ8147E. However, this error isn’t really an error, it’s just reporting that it didn’t find any status for the name you requested, which is exactly what the command at that point in the script was checking for. Nothing has gone wrong here at all.

In MQSCX V9.3.1 and onwards, the output of this same script will now finish with:-

MQSC Command summary - Issued: 4  Success: 4  Fail: 0

Which makes it very much more obvious that nothing has gone wrong.

This change in behaviour is controlled with the following setting.

=count emptydsp(OK | FAIL)

By default we consider empty DISPLAY commands to be a success (OK). This is a change in behaviour. If you prefer to have such empty DISPLAY commands to continue to be treated as a failure (FAIL) then you can use this setting to force that behaviour.

In addition to this change, the =count command has a few other settings.

You can control whether DISPLAY commands are counted at all using:-

=count dspcmds(yes | no)

You might not be interested in the 47 DISPLAY commands you issued, and only want to report on the 4 ALTER commands that are the real meat of the script.

You can also control whether all commands are counted, using:-

=count cmds(yes | no)

We imagine you might add this around a section of the your script where you don’t care to have the activities counted and reported on at the end. You might turn off the counting, issue several commands, and then turn back on counting. The choice is up to you.

And finally, you can also change what the summary reports to you. The default remains as shown above, but instead you can, for example, have:-

=count summary(total, failnz)

which shows the total, and a fail count if it is non-zero.

Or any combination of :-

=count summary(default, fail, failnz, none, ok, oknz, total)

To build up the summary string you want to see.

We hope these make it easier for you to create MQSC scripts where it is easy to see, at a glance, that they completed successfully.


If you don’t already have MQSCX and you’d like to try it out, please email support@mqgem.com to request a trial licence. You can download MQSCX from our website.

Quickly convert CUSTOM CAPEXPRY

CAPEXPRY has been around in the CUSTOM field of queues and topics since V8.0.0.4. Now in V9.3.1 on Distributed platforms CAPEXPRY has been promoted to a real attribute. Vasily Shcherbinin writes more about this in his recent post on the IBM Community. In there he says the following:

The CUSTOM CAPEXPRY parameter will remain, but we do recommend and urge our users to adopt the new CAPEXPRY attribute and to migrate to it. There’s no specific plans at the moment, but it is possible that in some future release, the CUSTOM CAPEXPRY way of setting the message expiry will be deprecated and even potentially removed – again, there’s no specific plans for this yet, but something that we are considering doing at some point. So adopting the new CAPEXPRY parameter early in your scripts and environments could be beneficial.

This post shows an MQSCX script that will accomplish the aforementioned migration of your objects, that are currently using CAPEXPRY in CUSTOM, over to using the new CAPEXPRY attribute.

At a very simple level, you want to find all the queues that have something in their CUSTOM field at all, and then work with those. So to start with, we use a foreach loop as follows:-

foreach(DISPLAY QLOCAL(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
  :
endfor

Each iteration of this loop will be a possible object that needs to be changed. We inspect the CUSTOM string that is returned by the command to see if it contains a CAPEXPRY value:-

foreach(DISPLAY QLOCAL(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
  @OldCapExp = valueof("CAPEXPRY",CUSTOM)
  if (exists(@OldCapExp))
    :
  endif
endfor

If we find that it does, we build up an ALTER command to change the queue and run it.

*********************************************************************
* For every queue found to have a CUSTOM value, look for CAPEXPRY   *
* in the CUSTOM value and convert into the real CAPEXPRY attribute. *
*********************************************************************
foreach(DISPLAY QLOCAL(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
  @ObjName   = QUEUE
  @OldCapExp = valueof("CAPEXPRY",CUSTOM)
  if (exists(@OldCapExp))
    *****************************************************************
    * Build up MQSC command string and issue it                     *
    *****************************************************************
    @CmdStr    = "ALTER QLOCAL(" + @ObjName + ") " +
               + "CAPEXPRY(" + @OldCapExp + ") CUSTOM(' ')"
    @CmdStr
  endif
endfor

This version of the script is too simplistic though, although it serves to illustrate the main bones of the script. The main problem with this script is that is completely blanks out the CUSTOM field, rather than just removing the CAPEXPRY piece. If you have other things encoded in the CUSTOM string you would like those to remain. So, we enhance the script further, to manipulate the CUSTOM string to remove the CAPEXPRY part and return what remains.

We add a function GetNewCustom which, given the original CUSTOM string from the object, locates the start of the CAPEXPRY string and the end (by finding it’s closing bracket). It then creates two sub-strings for the pieces before and after the CAPEXPRY string and returns those two trimmed strings concatenated.

*********************************************************************
* FUNCTION: Trim                                                    *
*           Remove spaces from the front and end of the given string*
*********************************************************************
func Trim(str)
  @e = strlen(@str)
  @s = 1
  while (@s < @e & substr(@str, @s, 1) = " ") @s = @s + 1; endwhile
  while (@s < @e & substr(@str, @e, 1) = " ") @e = @e - 1; endwhile
  return substr(@str, @s, @e-@s+1)
endfunc

*********************************************************************
* FUNCTION: GetNewCustom                                            *
*         Remove the CAPEXPRY string from CUSTOM & return remainder *
*********************************************************************
func GetNewCustom(Custom)
  @Start     = findstri(@Custom, "CAPEXPRY")
  @End       = findstr(substr(@Custom, @Start, 128),")") + @Start
  @BeforeCap = Trim(substr(@Custom, 1,    @Start-1))
  @AfterCap  = Trim(substr(@Custom, @End, 128))
  return @BeforeCap + " " + @AfterCap
endfunc

*********************************************************************
* For every queue found to have a CUSTOM value, look for CAPEXPRY   *
* in the CUSTOM value and convert into the real CAPEXPRY attribute. *
*********************************************************************
foreach(DISPLAY QLOCAL(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
  @ObjName   = QUEUE
  @OldCapExp = valueof("CAPEXPRY",CUSTOM)
  if (exists(@OldCapExp))
    *****************************************************************
    * Build up MQSC command string and issue it                     *
    *****************************************************************
    @CmdStr    = "ALTER QLOCAL(" + @ObjName + ") " +
               + "CAPEXPRY(" + @OldCapExp + ") " +
               + "CUSTOM('" + GetNewCustom(CUSTOM) + "')"
    @CmdStr
  endif
endfor

Now we have the loop working for QLOCAL objects, we extend it to cover all the object types that can have CAPEXPRY.

@ObjTypeList = "QLOCAL,QALIAS,QREMOTE,QMODEL,TOPIC"
*********************************************************************
* For every object found to have a CUSTOM value, look for CAPEXPRY  *
* in the CUSTOM value and convert into the real CAPEXPRY attribute. *
*********************************************************************
foritem(@ObjTypeList)
  foreach(DISPLAY <_item>(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
    if (QUEUE) @ObjName = QUEUE;
    else       @ObjName = eval(_item); endif
    @OldCapExp = valueof("CAPEXPRY",CUSTOM)
    if (exists(@OldCapExp))
      ***************************************************************
      * Build up MQSC command string and issue it                   *
      ***************************************************************
      @CmdStr    = "ALTER " + _item + "(" + @ObjName + ") " +
                 + "CAPEXPRY(" + @OldCapExp + ") " +
                 + "CUSTOM('" + GetNewCustom(CUSTOM) + "')"
      @CmdStr
    endif
  endfor
endfor

Finally to finish off this script we check the version and platform of the connected queue manager before we do anything.

if (_platform = MVS | _cmdlevel < 931)
  print "CAPEXPRY is not available as an attribute until 931"
  leave
endif

And we count up how many objects we found to change so we can report if there were none.

@ObjTypeList = "QLOCAL,QALIAS,QREMOTE,QMODEL,TOPIC"
*********************************************************************
* For every object found to have a CUSTOM value, look for CAPEXPRY  *
* in the CUSTOM value and convert into the real CAPEXPRY attribute. *
*********************************************************************
foritem(@ObjTypeList)
  @Found = 0
  foreach(DISPLAY (*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
    if (QUEUE) @ObjName = QUEUE;
    else       @ObjName = eval(_item); endif
    @OldCapExp = valueof("CAPEXPRY",CUSTOM)
    if (exists(@OldCapExp))
      @Found     = @Found + 1
      ***************************************************************
      * Build up MQSC command string and issue it                   *
      ***************************************************************
      @CmdStr    = "ALTER " + _item + "(" + @ObjName + ") " +
                 + "CAPEXPRY(" + @OldCapExp + ") " +
                 + "CUSTOM('" + GetNewCustom(CUSTOM) + "')"
      @CmdStr
    endif
  endfor
  if (@Found = 0)
    print "No", _item, +
          "objects were found with CAPEXPRY in their CUSTOM field"
  endif
endfor

Open the twisty to see the whole script. This script can also be downloaded as part of our example set of MQSCX scripts on our website.

Complete MQSCX script
*********************************************************************
*                                                                   *
* FUNCTION: MigCapExp                                               *
*           A short MQSCX script to migrate from using CAPEXPRY in  *
*           the CUSTOM fields to using the first class attribute.   *
*                                                                   *
*********************************************************************

if (_platform = MVS | _cmdlevel < 931)
  print "CAPEXPRY is not available as an attribute until 931"
  leave
endif

*********************************************************************
* FUNCTION: Trim                                                    *
*           Remove spaces from the front and end of the given string*
*********************************************************************
func Trim(str)
  @e = strlen(@str)
  @s = 1
  while (@s < @e & substr(@str, @s, 1) = " ") @s = @s + 1; endwhile
  while (@s < @e & substr(@str, @e, 1) = " ") @e = @e - 1; endwhile
  return substr(@str, @s, @e-@s+1)
endfunc

*********************************************************************
* FUNCTION: GetNewCustom                                            *
*         Remove the CAPEXPRY string from CUSTOM & return remainder *
*********************************************************************
func GetNewCustom(Custom)
  @Start     = findstri(@Custom, "CAPEXPRY")
  @End       = findstr(substr(@Custom, @Start, 128),")") + @Start
  @BeforeCap = Trim(substr(@Custom, 1,    @Start-1))
  @AfterCap  = Trim(substr(@Custom, @End, 128))
  return @BeforeCap + " " + @AfterCap
endfunc

@ObjTypeList = "QLOCAL,QALIAS,QREMOTE,QMODEL,TOPIC"
*********************************************************************
* For every object found to have a CUSTOM value, look for CAPEXPRY  *
* in the CUSTOM value and convert into the real CAPEXPRY attribute. *
*********************************************************************
foritem(@ObjTypeList)
  @Found = 0
  foreach(DISPLAY <_item>(*) WHERE(CUSTOM NE ' ') CAPEXPRY CUSTOM)
    if (QUEUE) @ObjName = QUEUE;
    else       @ObjName = eval(_item); endif
    @OldCapExp = valueof("CAPEXPRY",CUSTOM)
    if (exists(@OldCapExp))
      @Found     = @Found + 1
      ***************************************************************
      * Build up MQSC command string and issue it                   *
      ***************************************************************
      @CmdStr    = "ALTER " + _item + "(" + @ObjName + ") " +
                 + "CAPEXPRY(" + @OldCapExp + ") " +
                 + "CUSTOM('" + GetNewCustom(CUSTOM) + "')"
      @CmdStr
    endif
  endfor
  if (@Found = 0)
    print "No", _item, +
          "objects were found with CAPEXPRY in their CUSTOM field"
  endif
endfor

If you don’t already have MQSCX and you’d like to try it out, please email support@mqgem.com to request a trial licence. You can download MQSCX from our website.

File names in JCL

As a result of reading Lyn Elkins’ posts about how she struggled with the quotation marks required to specify a file name as a parameter in a JCL PARM string, I thought I would write a little summary. I personally prefer to use DD cards for my file names whenever possible as these avoid the need for quotation marks and leave your eye less likely to be squinting by the time you are done. I will show the fully qualified example right at the end for anyone who really wants it though. I am using QLOAD as an example program here, but any application that simply does an fopen() can make use of the same syntax. For example, when supplying a script in a file using MQSCX, providing an input file to load onto a queue in QLOAD, or some string data to become message data in the Q program. Any parameter that expects a file name can also instead take a string of the format “DD:card-name” (note that you MUST use capital letter D’s here). All of these use the same syntax as illustrated here.

//QLOAD    JOB
//*********************************************************************
//* QLOAD Load messages onto Q1
//*********************************************************************
//LOADTOQ  EXEC PGM=QLOAD,
//         PARM=('-m MQG1 -o Q1 -f DD:INFILE1')
//STEPLIB  DD DSN=GEMUSER.USER.LOAD,DISP=SHR                         
//         DD DSN=IBM.MQ.SCSQAUTH,DISP=SHR                           
//         DD DSN=IBM.MQ.SCSQANLE,DISP=SHR                           
//SYSPRINT DD SYSOUT=*  
//SYSOUT   DD SYSOUT=*      
//INFILE1  DD DSN=GEMUSER.DATA.MSGFILES(EXMPL1),DISP=SHR
//INFILE2  DD DSN=GEMUSER.DATA.OUTPUTQ1,DISP=SHR 
//INFILE3  DD PATH='/u/gemuser/Q1.qld',PATHOPTS=(ORDONLY)          
//MQGEML   DD DSN=GEMUSER.MQGEM.LIC,DISP=SHR
//

Here you can see that the JCL PARM string does not need any extra quotes at all, just the string DD:INFILE1 where INFILE1 is the name of a DD card in the JCL job. The DD card can then specify a fully qualified PDS member, MVS file, or HFS file as the three examples show.

To do the same job as shown in these three examples by providing the file name inline in the JCL PARM string, things start getting complicated by the fact that extra quotation makes are needed to escape the quotes since the whole JCL PARM string is surrounded by single quotes itself. This is what makes it so problematic, and why I always use DD cards instead.

If you take advantage of that the fact that MVS knows your TSO login and will pre-pend it to the MVS file name you supply, you can avoid quite a number of the quotation marks needed. The following two EXEC statements are equivalent when the TSO login is GEMUSER. The way you tell MVS not to pre-pend your TSO login is to surround the name you supply with single quotes, which turn into doubled up single quotes when used inside a JCL PARM string! The whole thing also needs to be enclosed in double quotes because of the slashes which are considered a delimiter to separate a JCL PARM string into run-time options and parameters to pass to the program.

//LOADTOQ  EXEC PGM=QLOAD,
//         PARM=('-m MQG1 -o Q1 -f"//DATA.MSGFILES(EXMPL1)"')
//LOADTOQ  EXEC PGM=QLOAD,
//         PARM=('-m MQG1 -o Q1 -f"//''GEMUSER.DATA.MSGFILES(EXMPL1)''"')

The two slashes shown in both the above example JCL PARM strings are how you tell fopen() that you are referencing an MVS file (to distinguish it from an HFS file). Here is an example EXEC statement using an HFS file instead. Again, this string needs to be surrounded by double quotes because of the slashes. The second example has no slashes, and will end up in your current directory. In fact any string that does not start with DD: or a slash (or two) will be interpreted as an HFS file in your current directory, and is why you might end up with an HFS file called “dd:SUMMARY” if you forgot to use capital letter D’s.

//LOADTOQ  EXEC PGM=QLOAD,
//         PARM=('-m MQG1 -o Q1 -f"/u/gemuser/Q1.qld"')
//LOADTOQ  EXEC PGM=QLOAD,
//         PARM=('-m MQG1 -o Q1 -fQ1.qld')

Well, I hope that helps, and also probably convinces you that DD cards are the way to go.

I didn’t invent this stuff, I’ve just spent a lot of time using it. Here is some reference material that helped me get to grips with it. Perhaps it can help you too.

On a separate note, if you ever have a long JCL PARM string, don’t struggle with line continuations, move over to using PARMDD – see JCL with long PARM strings

Is there a way to clear an MQ queue of all messages?

We were recently asked how to clear a queue of all messages within one of our tools.

In this post we cover how to do this using MQGem tools as several of them offer this capability. Open up the twisty for each tool to read how to do this.

Using MO71

There are two options for clearing a queue of all messages when using MO71. One directly interacts with the messages in a Message List dialog, and the other uses the Clear Queue command via the command server. The latter can fail if someone else has the queue open.

MO71: Using the Message List dialog

In MO71 you can browse a list of messages on a queue, then in the context menu of that dialog choose Message Selection → Apply to all Messages. Then press the Delete All button, or select Delete All from the right-mouse button context menu and all the messages will be deleted.

Delete all messages using MO71

Take care when deleting messages from a production queue manager. Consider that it might be safer to Move the message(s) to a separate holding queue, just in case the message proves to be important after all. MO71 makes it simple to Move messages too. Just fill in the holding queue name, and then press the Move All, rather than Delete All, button.

MO71: Using the Clear Queue command

From a Queue List dialog in MO71, you can choose to clear a queue from the context menu. As a destructive command, this will show a confirmation dialog before it goes ahead.

Delete all messages using the Clear command in MO71

Remember that this command can fail if the queue is open by another application at the time.

Using the Q program

You can use the Q program to quickly destructively get off all the messages on a queue, to ensure it is empty before you begin using it for something else. This can work even when the queue is in use (unless it is exclusively in use of course) and the CLEAR QLOCAL command can’t be used.

Here is an example which destructively gets (-I) from Q1 and uses a zero-length buffer and accepts that the messages will be truncated (-=t0):-

q -m MQG1 -I Q1 -=t0

Take care when deleting messages from a production queue manager. Consider that it might be safer to Move the messages to a separate holding queue, just in case they prove to be important after all. This is simple to achieve using a command like the following:

q -m MQG1 -I Q1 -o HOLDING.Q
Using QLOAD

You can use QLOAD to quickly destructively get off all the messages on a queue, to ensure it is empty before you begin using it for something else. This can work even when the queue is in use (unless it is exclusively in use of course) and the CLEAR QLOCAL command can’t be used.

Here is an example which destructively gets (-I) from Q1 and discards them (-f null), aka sends them to the null file destination:-

qload -m MQG1 -I Q1 -f null

Take care when deleting messages from a production queue manager. We would recommend using QLOAD to actually move the messages to an output file, in case you change your mind about deleting them!

qload -m MQG1 -I Q1 -f c:\temp\Q1deletedmsgs.qld 
Using MQEdit

In MQEdit you can browse a list of messages on a queue, then in the context menu of that dialog choose Message Operations → Apply to all Messages. Then press the Delete All button, or select Delete All from the right-mouse button context menu and all the messages will be deleted.

Delete all messages using MQEdit

Take care when deleting messages from a production queue manager. Consider that it might be safer to Move the message(s) to a separate holding queue, just in case the message proves to be important after all. MQEdit makes it simple to Move messages too. Just fill in the holding queue name, and then press the Move All, rather than Delete All, button, or simply Drag and Drop the messages onto the new queue.

Using MQSCX

You can of course issue the MQSC CLEAR QLOCAL(q-name) command from MQSCX. However, it can go one better than this. The CLEAR QLOCAL command is problematic because it can fail when someone else has the queue open, even if it is not open in a way that stops you getting messages. Instead you can issue the following command in MQSCX:-

=clear qlocal(Q1)

then MQSCX will first attempt the CLEAR QLOCAL command and if that fails with "object in use" then it will switch to getting the messages off the queue instead. You can read more about this in MQSCX: Clearly a better way.


If you don't have any of these tools, but would like to try any or all of them out, please contact support@mqgem.com and a 1-month trial licence will be sent to you with no obligation to buy. You can download the tools from our website.

Displaying only non-system queues with an MQSC command

IBM Support recently released a Support Doc which provided a variety of examples on how to use the WHERE clause on MQSC DISPLAY commands. The final example was actually illustrating something that the WHERE clause could not do and showing how to use alternate UNIX script methods such as grep, xargs, cut and tr to work round the limitations of the WHERE clause in that instance.

Our MQSCX product also has a WHERE clause, with keyword =WHERE to distinguish it from the IBM MQ WHERE clause. Our aim with the =WHERE clause was to remove some of the restrictions of the IBM MQ WHERE clause. The example in question is one of those restrictions we lifted.

Here is the direct quote of the example from the Support Doc.

Question: Displaying only non-system queues (do not display SYSTEM.* queues)

You wish to see all non-system queues (for example LOCAL QUEUES) using DISPLAY command.

It is not possible to do this query with the SQL-like facilities in the WHERE clause.

The following was found in a forum of MQ users, and it is added here for completeness.

For Unix (it is a SINGLE line – very long line!):

echo "dis ql(*)" | runmqsc QM92 | grep QUEUE | tr '()' ' ' | tr -s ' ' | cut -f3 -d' ' | grep -v ^SYSTEM | xargs -I %%
echo "dis ql(%%)" | runmqsc QM92

Compare this to the command you can use in MQSCX to achieve the same, a list of all the queues which do not start with the name SYSTEM:

DISPLAY QLOCAL(*) =WHERE(QUEUE NL 'SYSTEM.*')

This uses the NL (Not-Like) operator which is the opposite of the LK (Like) operator. These two operators work with wildcarded string values.

The reason it was not possible to do this with the IBM MQ WHERE clause is because the name of the object is disallowed from the WHERE clause. This is one of the restrictions we lifted when creating the =WHERE clause.


If you don’t already have MQSCX and you’d like to try it out, please email support@mqgem.com to request a trial licence. You can download MQSCX from our website.

Latest MQEV and MQSCX released on z/OS

Our recently released versions of MQEV V9.2.2 and MQSCX V9.2.1 are now also available on z/OS.

Read more about these versions here:-


The new versions can be downloaded from the MQEV Download Page and the MQSCX Download Page. Any current licensed users of these products can run the new version on their existing licence. If you don’t have a licence and would like to try out MQEV or MQSCX then send an email to support@mqgem.com and a 1-month trial licence will be sent to you.

MQSCX: Clearly a better way

A new version of MQSCX was recently released, and one feature in that release was the introduction of a new MQSCX command, =clear qlocal.

Have you ever issued the MQSC CLEAR QLOCAL command and been told:-

AMQ8148E: IBM MQ object in use.

This can happen even if the queue is open for shared input or just for output. It can be very annoying if you have to stop a program that is holding a queue open, just to be able to clear it of messages.

The =clear qlocal works round this by operating in the following way:-

  • Attempt the MQSC CLEAR QLOCAL.
  • If this fails with the above error, then switch to using MQGET to remove the messages off the queue.

That’s the simple explanation, however, if you want more gory details, open the twisty below.

The gory details
  • What happens if the initial MQSC CLEAR QLOCAL fails for some other reason?
    In this case the command does not continue to the MQGET phase. This might be a security failure for example, so if you are not authorised to issue the MQSC CLEAR QLOCAL command, then that effectively carries over into this command as well.
  • I don’t want to issue millions of MQGETs if the MQSC CLEAR QLOCAL fails!
    The MQSCX =clear qlocal command has some additional parameters, one of them is depth() which sets the maximum depth of the queue that does not require a user confirmation to proceed. It defaults to 1000 messages. So it will do 1000 MQGETs without asking for you to confirm, but if there are 1001 messages, it will first prompt to confirm you wish to go ahead.
  • I don’t want a manual prompt – I’m sure, just get on with it!
    The MQSCX =clear qlocal command has one more parameter which is confirm(yes | no). This will be useful in scripts where you don’t want to have a human interaction, but just want the command to go ahead regardless of how many messages there are on the queue – just get it emptied.
  • What happens if the queue is opened exclusively?
    Unfortunately, there’s no magic here. If the queue is opened exclusively, neither the MQSC CLEAR QLOCAL command, nor an application MQGET can do anything to the queue. So make sure that your queues are not defined with DEFSOPT(EXCL) and NOSHARE unless they need to be – I’m looking at you z/OS queue managers!
  • So how does the full command look?
    The full command looks like this:-
    =clear qlocal(<queue name>)
           [confirm (yes | no)]
           [depth(<queue depth threshold>)]

Summary

Never struggle to empty a queue again – a swift =clear qlocal(queue name) and all is done.


If you don’t already have MQSCX and you’d like to try it out, please email support@mqgem.com to request a trial licence. You can download MQSCX from our website.

Text Files in Preview Pane

If you’re anything like me, you have a number of text files on your Windows machine that do not use the .txt file extension.

For all of these I have associated Notepad as the application to be used by default when I double click on these file types from the Windows File Explorer. I’m probably a 70:30 command line vs File Explorer user, but it is from time to time handy to be able to double click too.

What I’d never managed to figure out though (until today) was how to get the preview pane in Windows File Explorer to display these text formats with different file extensions. Now that I have found out how to do it, I thought I would share it with all of you too.

Windows File Association Dialog

Windows File Association Dialog

Firstly, it’s important to have the file association in place first, specifically the “Always use this app to open … files” checkbox. This seems to be what adds the file extension into the list you are about to manipulate. So take a moment to make sure all the extensions you want to preview have a default application set.

Now open the registry editor and navigate to Computer > HKEY_CLASSES_ROOT. In here you will see a list of all the file extensions your machine knows about. Locate your file extension that you want to preview (remember you can type into the entry field just below the menu bar). Select the file extension, in this example I am fixing up the .mqx extension; and on the right hand pane right-click and select New > String Value. Give the new key the name “PerceivedType” and then modify it to give it the data value of “text”.

Adding a new String Value in Windows Registry

Adding a new String Value in Windows Registry

Giving new registry key a value of text

Giving new registry key a value of text

Now go back to your File Explorer and you should be able to see your .mqx file in the preview pane.

View .mqx file in Preview Pane

View .mqx file in Windows File Explorer Preview Pane


Many thanks to Scott Williams who wrote about this in a blog post in 2013 which I finally stumbled across in 2020 and it made my day!

Working with JSON CCDT files

A recent feature added to IBM MQ are Client Channel Definition Table (CCDT) files in JSON format.

One of the features of MQSCX is to work with CCDT files, both the binary format, and now (since MQSCX V9.2.0) the JSON format.

With MQSCX you can display or create/alter a JSON CCDT file using familiar MQSC commands. Your tried and tested scripts for creating binary format CCDT files, can be used to create the same in JSON format.

For example, this file:

DEFINE CHANNEL(MQG1.SVRCONN) CHLTYPE(CLNTCONN) +
       CONNAME('gemmvs(1701)') QMNAME(MQG1)
DEFINE CHANNEL(MQG2.SVRCONN) CHLTYPE(CLNTCONN) +
       CONNAME('gemmvs(1702)') QMNAME(MQG2)

ClientChls.mqs

Run with this command:

mqscx -j -i ClientChls.mqs

Will produce a JSON CCDT file like this where the MQCHLLIB and MQCHLTAB environment variables point:

ClientChls.json
{
  "channel":
  [
    {
      "name": "MQG1.SVRCONN",
      "type": "clientConnection",
      "clientConnection":
      {
        "connection": 
        [
          {
            "host": "gemmvs",
            "port": 1701
          }
        ],
        "queueManager": "MQG1"
      }
    },
    {
      "name": "MQG2.SVRCONN",
      "type": "clientConnection",
      "clientConnection":
      {
        "connection": 
        [
          {
            "host": "gemmvs",
            "port": 1702
          }
        ],
        "queueManager": "MQG2"
      }
    }
  ]
}

You can also use MQSCX interactively and type in these familiar MQSC commands, using TAB auto-complete to assist, and create a JSON CCDT that way too.

Validation

One of the benefits of using a JSON format CCDT is that it is a text file instead of the proprietary binary file. So why do you need a tool like MQSCX to create one? Well the answer is validation. If you create a JSON CCDT file by hand, you may have a typo or spelling error in any of the field names. If you do this and display your file using runmqsc, or run it using an MQ Client, neither will inform you of your typo because they are designed to be future proof. There is no versioning on a JSON CCDT file, so if the MQ Client uses one that is from a newer version of MQ, it just ignores anything that it doesn’t recognise. This includes your misspelled attributes, that you actually wanted it to use!

Misspelled attributes

Here’s an example JSON CCDT with a typo:

{
  "channel":
  [
    {
      "name": "MQGEM.SVRCONN",
      "type": "clientConnection",
      "clientConnection":
      {
        "connection":
        [
          {
            "host": "localhost",
            "port": 1701
          }
        ]
      },
      "connectionManagement":
      {
         "hbInterval": 60
      }
    }
  ]
}

HBintClnt.json

If I use runmqsc to look at it, it won’t complain and it will tell me that my heartbeat interval value is 300.

Note: runmqsc can be used to display the contents of the JSON CCDT file, but not to create or edit its contents.

runmqsc -n
5724-H72 (C) Copyright IBM Corp. 1994, 2019.
Starting local MQSC for 'HBintClnt.json'.
DISPLAY CHANNEL(*) HBINT
     1 : DISPLAY CHANNEL(*) HBINT
AMQ8414I: Display Channel details.
   CHANNEL(MQGEM.SVRCONN)                  CHLTYPE(CLNTCONN)
   HBINT(300) 

With a SVRCONN defined with HBINT(60), a client running using this CCDT, which should be using 60 seconds for the heartbeat interval, will actually use 300, and so the running SVRCONN will end up with 300 as well.

AMQ8417I: Display Channel Status details.
   CHANNEL(MQGEM.SVRCONN)                  CHLTYPE(SVRCONN)
   CONNAME(127.0.0.1)                      CURRENT
   HBINT(300)                              STATUS(RUNNING)
   SUBSTATE(RECEIVE)                    

Running MQSCX to look at this JSON CCDT file, will show the following:

MQSCX Extended MQSC Program – Version 9.2.0

At line 20 of file ‘C:\MQGem\HBintClnt.json’, unrecognised field name ‘hbInterval’ found

CCDT file ‘C:\MQGem\HBintClnt.json’, 1 channels read

Licenced to Paul Clarke

Licence Location: Head Office

So, you are immediately aware of the issue in your CCDT file.

Missing mandatory attributes

There aren’t many mandatory attributes on a client channel definition, but those that are, are important!

If you omit a mandatory attribute, again runmqsc won’t complain, but of course the client channel will when it tries to make a connection.

Here’s an example JSON CCDT with a channel that hasn’t specified a connection name:

{
  "channel":
  [
    {
      "name": "MQGEM.SVRCONN",
      "type": "clientConnection",
    }
  ]
}

ConnameClnt.json

When a client application attempts to run using this, you’ll get something like the following in your client AMQERR01.LOG:

AMQ9203E: A configuration error for TCP/IP occurred.

EXPLANATION:
Error in configuration for communications to host ' '.  Allocation of
a TCP/IP conversation to host ' ' was not possible.

Running MQSCX to view, and therefore validate, this JSON CCDT file, will show the following:

MQSCX Extended MQSC Program – Version 9.2.0

The channel definition ending at line 7 of file ‘C:\MQGem\ConnameClnt.json’ has not specified a connection name

CCDT file ‘C:\MQGem\ConnameClnt.json’, 1 channels read

Licenced to Paul Clarke

Licence Location: Head Office

Enhanced Filtering

In addition to the above, using MQSCX to view and manipulate your CCDTs (both binary and JSON) allows you to utilise the power of MQSCX filtering. For example you can use a command like the following, which wouldn’t be allowed through runmqsc.

DISPLAY CHANNEL(*MQGEM*)

Or you could use enhanced WHERE clauses that are more powerful than the IBM MQ supplied WHERE clause. For example, you can use a command like the following:

DISPLAY CHANNEL(*) =WHERE(NOT(SCYEXIT AND SCYDATA))

Re-ordering your JSON defined channels

The order that an MQ client will pick channels to use from your JSON CCDT is dependant on the order they are in the file, and you control that order. In the binary CCDT, the file was always internally ordered alphabetically, but with a text file like the JSON CCDT you are in complete control.

As it is a text file, you can just use your favourite editor to copy the lines around, just make sure you have all the right brackets and commas in place. Alternatively, you can use MQSCX to reorder your file just how you want it as follows.

MOVE CHANNEL(MQG1.SVRCONN) POSITION(1) NEWPOS(2)

You can also indicate the position in the file for a channel definition at the time you create it, for example, using a command like this to make the new channel first in the file:

DEFINE CHANNEL(MQG1.SVRCONN) CHLTYPE(CLNTCONN) +
       CONNAME('gemmvs(1701)') QMNAME(MQG1) INDEX(BEFORE)

JSON file format

You also have some control over the contents of the JSON CCDT file that is created by MQSCX with three options on how the fields are output.

  • =set jsonfields(all)
    This option will create a JSON CCDT file with all the values output, even those that just contain the default or blank values. This makes your JSON CCDT file bigger of course, but might be useful if you need to manually edit it later and you are unsure of the field names.
  • =set jsonfields(date)
    This option will create a JSON CCDT file with the minimum set of fields (see below) and additionally the alteration date/time field. The MQ Client does not make use the of this date information, so it is only added if you ask for it. We assume, since it is in the JSON CCDT schema that someone might want it for something.
  • =set jsonfields(min)
    This is the default setting, and only outputs the channel fields are have non-default or non-blank values. This will make your JSON CCDT file much smaller than you would get with jsonfields(all), and is the minimum the MQ Client needs to work with.

Summary

So if you’re starting to look into using JSON CCDT files, perhaps for their ability to define channels with the same name within one CCDT, and you’re not a JSON format expert, take a look at MQSCX to produce your CCDTs and ensure that you always have a valid format CCDT file to use with your MQ Clients.


If you don’t already have MQSCX and you’d like to try it out, please email support@mqgem.com to request a trial licence. You can download MQSCX from our website.