Running z/OS REXX from Persistent TSO Address Space w/Zowe CLI

Dan Kelosky
3 min readOct 11, 2023

Previously we shared how to run z/OS REXX faster with Zowe CLI. One technique described there involved using a persistent (longer running) TSO address space. However, specific details on how to do this were skipped.

Below we’ll look at the steps used to keep a persistent TSO address space to quickly run REXX via something like Zowe Explorer:

Overview

We’ll rely on a script, keep-tso.mjs, (source below) which does three primary things:

  1. runs azowe tso start address-space command
  2. executes any TSO environment initialization commands (e.g. dynamic allocation)
  3. periodically pings the TSO address space to keep it alive

keep-tso.mjs

You can copy this script in a project and keep it in source control:

import { execSync } from 'child_process';
import { writeFileSync } from 'fs';
import { readFileSync } from 'fs';

const KEY_FILE = 'key.txt';
const CMD_FILE = 'cmds.txt';
const MAX_FAIL = 5;
const WAIT_TIME = 1000 * 60 * 5; // 5 minutes (in milliseconds) - milliseconds * seconds * minutes

function start() {
console.log(`Starting TSO...`);
const key = execSync(`zowe tso start as --sko`).toString().trim();
writeFileSync(KEY_FILE, key);

commands().forEach(cmd => {
console.log(`running '${cmd}'`)
send(key, cmd);
});

console.log(`... key was ${key}`);
return key;
}

function ping(key) {
try {
console.log(execSync(`zowe tso ping as ${key}`).toString().trim());
return true;
} catch (e) {
return false;
}
}

function stop(key) {
try {
console.log(execSync(`zowe tso stop as ${key}`).toString().trim());
return true;
} catch (e) {
return false;
}
}

function read() {
try {
return readFileSync(KEY_FILE).toString().trim();
} catch (e) {
}
return `fakekey`;
}

function commands() {
try {
return readFileSync(CMD_FILE).toString().trim().split(/\r?\n/g);
} catch (e) {
}
return [];
}

function send(key, cmd) {
try {
console.log(execSync(`zowe tso send as ${key} --data "${cmd}"`).toString().trim());
} catch (e) {
// do nothing
}
}

function keep() {

let failed = 0;
let key = read();
if (!ping(key)) {
failed++;
if (failed > MAX_FAIL) {
console.log(`failed too many times`);
process.exit(1);
}
key = start();
}
}

// stop(read()); // debug
keep();
setInterval(() => {
keep();
}, WAIT_TIME);

cmds.txt

If you need any one-time initialization commands to run at TSO address space start time, you can create an adjacent file, cmds.txt, and if found, keep-tso.mjs runs the TSO commands found there (to do things like dynamic allocation).

For example:

allocate file(ops$opsy) dummy
allocate file(myexec) dsn('opsmvs.kelda16.ops.dev.rexx') shr
concat file(sysexec myexec)
allocate file(isplog) dummy

In this case, a data set, OPSMVS.KELDA16.OPS.DEV.REXX, is allocated to a SYSEXEC DD.

Running the Script

Ideally, you’ll run the script in a dedicated terminal window (instead of something like the VS Code terminal which tends to crash more often than not):

keep-tso.mjs startup with intential first error

Note that when the script runs, if firsts attempts to ping a previous TSO address space. If the ping fails, it starts a new instance (which is why there is an error message issued in this gif).

When the script runs, it also writes a file, key.txt. This file should be in your .gitignore.

oi.bat

Lastly, you can use Windows batch files (as an example) to recreate a mainframe experience. Here we use a file oi.bat with these contents:

@echo off
set /P TSO_KEY=<key.txt
zowe tso send as %TSO_KEY% --data "oi %1"

This allows you to run oi from your workstation terminal, similar to how you might run oi from a TSO interface:

Running REXX from green screen (left) and workstation terminal (right)

--

--

Dan Kelosky

Likes programming/automation in mainframe (assembler, C/C++), distributed (Node.js), and web development (Firebase, Angular).