Friday 12 November 2021

Executing Priority Commands from an External Application

This is another topic that is documented in the SDK (in chapter 15, advanced programming tools), but as always, what is written is somewhat ambiguous and a few more examples would not have hurt. After having spent no small amount of time figuring out what works, I want to record my experiences here.

But first of all: why do I need to execute a Priority command from an external application? Lately we've been working on raising the level of computerisation on the factory floor by means of barcode readers. My original concept was that there would be a computer running Priority with a special, private and bespoke, screen being displayed, into which barcodes are scanned. The screen has a post-insert trigger for the lines that does something useful and clever.

The next step came from someone asking why this computer has to be running Priority? There are both advantages and disadvantages to this: maybe the disadvantages can be overcome without losing the advantages? So I suggested writing a program in Delphi that accepts the barcodes, saves them to a file and then transfers that file to the server where it is read by a procedure that is activated every half an hour.

An even better solution would be that the computer running a program in Delphi would accept a barcode and transmit it immediately to Priority, possibly handling that barcode at the same time. I had an arrangement like this twenty years ago! It's called Remote Execution, or Rexec.

In a part of the SDK that I had managed to miss was the section that I needed: the calling computer runs a program called WINRUN on the server that runs WINACTIV that runs a procedure (this is only one of the possibilities documented). As I wrote at the beginning, the documentation is ambiguous and I want to explain it fully here.

First off: TABULA.INI. I found the copy of this file that is stored on the SQL server (i.e. the copy that is called when Priority is run on the server) - it's stored in C:\WINDOWS of the server and so is not normally visible. On our server there is a directory D:\pr_SQL that has subdirectories BIN.95, system, etc - i.e. Priority; each installation chooses its own directory for this. This directory is mapped as X: on client computers; on the server I copied c:\windows\tabula.ini to x:\1\tabula.ini, making this file visible to all. On the client computer, where my Delphi program will be run, I defined an environmental variable thus: set TABULAINI=x:\1\tabula.ini. This only has to be done once on the computer.

I then wrote a procedure in Priority, TEST_UPDCONST. This procedure simply updates a value in a private table whenever it is run - this way I could see whether what I was doing was working. Once I had the command debugged, I then investigated how I could pass parameters to the procedure (this obviates the need to transfer files or similar).

Here is the final command that must be sent to Priority, along with comments.

Command part Description Constant
X:\BIN.95\WINRUN Winrun is the program that is run on the server; it is situated at X:\BIN.95 on my server. The location should be BIN.95 on the drive where Priority is located. Y
""
Two double quotation marks. Don't ask me why. Y
tabula 123456
The account name and password. It makes sense to have tabula run the procedure, thus preventing any problem with permissions. Of course, the password is not 123456.
x:\system\prep
This directory is always used when preparing procedures. Y
demo
The name of the company (environment) in which the command will be run
X:\BIN.95\WINACTIV.EXE This is the name of the program that runs the procedure. It didn't work without prefacing the name with its location. Y
-P A procedure is being run Y
TEST_INSERTNESTBC This is the name of the procedure
-var:BC "A09" First variable that is being passed to the procedure, whose name is BC and whose value is "A09"
-var:TS "12/11/21 09:04" Second variable that is being passed to the procedure, whose name is TS and whose value is "12/11/21 09:04" (i.e. the current date and time)

Put together, the complete command is X:\BIN.95\WINRUN "" tabula 123456 x:\system\prep demo X:\BIN.95\WINACTIV.EXE -P TEST_INSERTNESTBC -var:BC "A09" -var:TS "12/11/21 09:04".

In the above table, there is a column 'constant'; if the value is Y, then the command part should be written as is (obviously substituting the correct directory on the server). The SDK does not mention that the parameters of the command are case sensitive: if one is passing parameters to the procedure being called, then one must write "-var" and not "-VAR" as I originally did. One optional parameter is "err <filename>": this causes error messages from the called procedure to be written in this file; again, it's "err" in lower case, not "ERR".

The SDK also documents how the called procedure should accept its parameters; I thought that this was ambiguous but turns out to be accurate. In the case shown above, the called procedure (TEST_INSERTNESTBC) has two char parameters, BC and TS. Inside the procedure, they are referred to as :EXTERNAL.BC and :EXTERNAL.TS, not :$.BC and :$.TS. My misunderstanding was with reference to 'EXTERNAL'; in chapter 2 of the SDK there is a list of predefined variables, such as :PAR1, :PAR2, :PAR3 and :KEYSTROKES. I've noted in this blog a few undocumented variables to add to this list. I did not notice that the last variable appearing in the list is :EXTERNAL.VARNAME, "used in procedures to refer to variables inputted [sic] as part of the WINRUN command". This is similar to the half-documented variable :PROGPAR.VARNAME that I discussed here.

So now I have a very powerful tool at my disposal. First it will be used in a specific setting (that received its inspiration from another Delphi program that I developed this week that avoided having to access Priority), but I'm sure that in the coming months, this technique will be used more frequently.

It should be noted that this technique is s-l-o-w: it takes about five minutes for the entire process.