Developing NetWare Server Utilities with Simware's REXXWARE
Articles and Tips: article
Senior Research Engineer
Novell Research
01 Nov 1994
REXXWARE is an implementation of the REXX language for NetWare file servers. REXXWARE provides both a scripting language that enables network managers to automate management functions, and a programming language that enables developers to create server-based applications without having to deal with many of the intricacies of writing NetWare Loadable Modules (NLMs).
- What is REXXWARE?
- REXXWARE's Job Manager
- Example Programs
- Obtaining REXXWARE
- Example Program: WHOSON
What is REXXWARE?
REXXWARE is an implementation of the REXX programming language for NetWare file servers, developed by Simware Inc. of Ottawa, Ontario, Canada. REXX is a high-level scripting/programming language originally developed for IBM mainframes. REXX is also included with OS/2. (Little-known fact: REXX is an acronym for Restructured Extended Executor.) REXX is frequently referred to as a scripting language because it is used to execute and monitor programs, and take specified actions based on their output. REXX is also a programming language, in that it can be used to create new programs. In this way, REXX is more like BASIC or Unix shell scripts than the DOS batch file language or mainframe job control languages.
Simware developed REXXWARE because NetWare lacks a scripting language. You can use REXXWARE to load and unload NLMs on a file server, provide input (including keystrokes) and take prescribed actions based on output. REXXWARE also interfaces with Btrieve and NetWare's CLIB function library, so your REXXWARE programs have access to all of NetWare's services. Many applications you might implement as NLMs can be implemented using REXXWARE. And because REXXWARE handles memory allocation, thread switching, and type conversion of parameters passed to CLIB functions, application development in REXXWARE is less complex than writing NLMs in C. Systems programmers will note that the tradeoff is the loss of direct control of memory management and low-level access to the CPU, but REXXWARE is not for writing device drivers. It is for those who write programs that manage networks and the applications that run on them.
REXXWARE is an interpreted language, although REXXWARE programs can be compiled to a tokenized format that executes faster than a text program. REXXWARE programs are executed by the REXXWARE NLM. REXXWARE programs can
Load and unload other NLMs.
Read information displayed on any NLM screen.
Send keystrokes to any NLM screen.
Log in to remote file servers and act as a client of that server.
Access the server's DOS partition.
Control the consoles of other file servers that have the REXXWARE NLM loaded.
Call CLIB (including NetWare Directory Services) and Btrieve functions to control, gather data about, and report on your network.
Once you have written a REXXWARE program, there are four ways to execute it:
Immediately, by running it at the server console.
At server startup, by running the program in the AUTOEXEC.NCF file.
At a scheduled time, by scheduling the program with REXXWARE's Job Manager (discussed below).
When a specified event occurs, also controlled by the Job Manager.
REXXWARE's Job Manager
The REXXWARE Job Manager is a Windows program that allows you to schedule REXXWARE programs to execute at a specified time, or when a specified event occurs. Figure 1 shows Job Manager's main screen.
Figure 1: REXXWARE's Job Manager.

Job Manager gives you a wide range of options for scheduling when programs will execute. Jobs scheduled by time can be execute
Once, at a specified day and time
Daily, at a specified time
Weekly
Bi-weekly
Monthly by date (1st, 15th, etc.)
On specified days of the month (first Monday, last Friday, etc.)
Scheduling jobs by time allows you to automate many of the routine tasks involved in managing a network.
Job Manager also enables you to schedule programs to execute when an event occurs. The following events can trigger execution of a program.
|
Volume mounted |
File purged |
|
Volume dismounted |
File renamed |
|
Bindery object created |
File salvaged |
|
Bindery object deleted |
MLID registered |
|
User (or other object) connects to file server |
MLID deregistered |
|
Module loaded |
|
|
Connection cleared |
Module unloaded |
|
Login |
Process not relinquishing control |
|
Logout |
|
|
Subdirectory created |
Process created |
|
Subdirectory deleted |
Process destroyed |
|
Directory entry modified |
Protocol bound |
|
File opened |
Protocol unbound |
|
File closed |
Security equivalence changed |
|
File created |
Volume SYS mounted |
|
File created and opened |
Volume SYS dismounted |
|
File deleted |
Time changed |
|
File moved |
Trustee rights changed |
Example Programs
REXXWARE comes with over 100 sample programs that illustrate how to use REXXWARE to perform various network management functions. Some of these programs are
|
VOLINFO.SRX |
Lists volume statistics on a local or remote file server. |
|
USERLIST.SRX |
Lists all the users in the Bindery of a local or remote file server. |
|
USRTRUST.SRX |
Lists a user's trustee rights for all volumes. |
|
READSYS.SRX |
Reads information from the System Console screen. |
|
RXCOPY.SRX |
Copy files on a file server, or from one file server to another. |
|
PARTMNGR.SRX |
Manage NetWare Directory Services (NDS) partitions. |
One set of programs automates the backup process:
|
BWHOSON.SRX |
Broadcasts a warning message for all users to log offthe fileserver, then, after five minutes, clears any remaining connections. |
|
VIRSCAN.SRX |
Runs a virus scan program. |
|
VREPAIR.SRX |
Loads the VREPAIR NLM to defragment the volume. |
|
BACK.SRX |
Loads the NLM that performs the backup. |
Obtaining REXXWARE
You can purchase REXXWARE directly from Simware. You can contact Simware at
Simware Inc. 2 Gurdwara Road Ottawa, Ontario, Canada K2E 1A2 (800) 267-9991 (613) 727-1779 Fax: (613) 727-3533
Simware U.K. 011-44-1344-869500
Simware Germany 011-49-2154-91460 Internet: rexxware@simware.com
Example Program: WHOSON
The best way to get a feel for what its like to develop a program with REXXWARE is to look at a sample. The sample program included here makes up the WHOSON utility that is supplied with REXXWARE. There are 4 source files required:
WHOSON.SRX - the main program. Performs the following tasks:
Prompts user to select a server by calling SELSERV.SRX
Logs on to the server by calling LOGON.SRX
Finds out how many connections are in use on the server and gets information about each connection in use.
Displays how long a user has been on the connection and the network address.
Logs off the server.
WHOSON support scripts:
|
SELSERV.SRX |
Displays list of available servers and asks user which server should be used. |
|
LOGON.SRX |
Logs on to the server selected with SELSERV. |
|
LOGOFF.SRX |
Logs off the server selected with SELSERV. |
/*****************************************************************************
REXXWARE SAMPLE PROGRAM - WHOSON.SRX
****************************************************************************
/main:
parse source . . path
/* Setup REXXWARE path to point to where this file is executing from */
lastFwdSlash = LASTPOS("/", path) + 1
fileName = SUBSTR(path, lastFwdSlash)
filePath = DELSTR(path, lastFwdSlash)
firstColon = POS(":", filePath) + 1
fileVol = DELSTR(filePath, firstColon)
oldpath = CURRENTPATH()
call CURRENTPATH filePath
ResetFilePath = 0
signal on syntax name NoScript
CONTINUE: thisServerID = GETCURRENTFILESERVERID()
thisConnection = GETCURRENTCONNECTION()
retCode = GETFILESERVERNAME(thisServerID, "thisServerName")
getServer = "getServer"
logon = "logon"
showUsers = "showUsers"
logoff = "logoff"
done = "done"
nextMove = getServer
do while nextmove \= done
select
when nextmove == getServer then
do
serverName = ""
serverName = "selserv.srx"()
if (serverName == thisServerName) then
nextMove = showUsers
else if serverName \= "" then
nextMove = logon
else
nextMove = done
end
when nextMove == logon then
do
serverConnection = -1
serverConnection = "logon.srx"(serverName)
if serverConnection >= 0 then >
nextMove = showUsers
else
nextMove = getServer
end
when nextMove == showUsers then
do
retCode = GetUserInformation()
if (retCode == 0) then
retCode = ShowUserInformation()
nextMove = logoff
end
when nextMove == logoff then
do
if (thisServerName == serverName) then
nextMove = getServer
else
do
call "logoff.srx" serverConnection
retCode = SETCURRENTFILESERVERID(thisServerID)
retCode = SETCURRENTCONNECTION(thisConnection)
nextMove = getServer
end
end
otherwise
do
SAY "Invalid select: " nextMove
SAY "press enter to continue..."
call HIDECURSOR
pull input
call SHOWCURSOR
nextMove = done
end
end
end
exit 0
/*****************************************************************************
SUBROUTINE GetUserInformation()
* Reads the connection information for all connections. All
* data is stored in the compound variable "userInfo." as an
* array of connections 1 to n, with stems "userName",
* "connectTime" and "netAddr".
* INPUT: none
* RETURNS: 0
****************************************************************************/
GetUserInformation:
retCode = GETSERVERINFORMATION('serverInfo.')
if (retCode == 0) then
do
connection = 1 to serverInfo.connectionsInUse
retCode = GETCONNECTIONINFORMATION(connection,"connectionInfo.")
if (retCode == 0 & connectionInfo.objectName \= "") then &
do
connection.0 = connection
days = FindJulianDate('connectionInfo.')
duration = Calctime(Days, connectionInfo.loginHour,
connectionInfo.loginMinute)
userInfo.connection.userName = connectionInfo.objectName
userInfo.connection.connectTime = duration
end
else
do
userInfo.connection.userName = (Unknown)
userInfo.connection.connectTime = (UnKnown)
end
if (retCode == 0) then
do
retCode = GETINTERNETADDRESS(connection,"networkAddr","nodeAddr")
if (retCode == 0) then
userInfo.connection.netAddr = RIGHT(STRIP(networkAddr), 8, " ")
||':' ||,
RIGHT(STRIP(nodeAddr), 12, " ")
else
userInfo.connection.netAddr = "(Unknown)"
retCode = 0
end
end
else
do
SAY "Could not read connection information, rc = " retCode
SAY "Press enter to continue..."
PULL input
end
return(retCode)
/****************************************************************************
* SUBROUTINE ShowUserInformation()
* Show the information stored by GetUserInformation above.
* Display handles 20 connections pre screen.
* INPUT: none
* RETURNS: 0
****************************************************************************/
ShowUserInformation:
do connection = 1 to connection.0 by 16
start = connection
end = connection.0
if (end - start > 16) then >
do
call ShowBanner
call ShowConnections start start+15
end
else if (end - start >= 0) then>
do
call ShowBanner
call ShowConnections start end
end
else
leave
SAY ""
SAY "Press enter to continue..."
call HIDECURSOR
pull input
call SHOWCURSOR
end
return 0
/****************************************************************************
* SUBROUTINE ShowBanner()
* Displays the banner for the user connection information.
* INPUT: none
* RETURNS: 0
****************************************************************************/
ShowBanner:
call CLEARSCREEN
SAY ""
SAY CENTER("Logged in User Information", 79)
SAY
"-------------------------------------------------------------------------------"
SAY LEFT("Connection#", 11) LEFT("UserName",16) LEFT("Log Time (DDD:HH:MM)",20)
CENTER("Network Address", 23)
SAY
"-------------------------------------------------------------------------------"
return 0
/****************************************************************************
* SUBROUTINE ShowConnections
* Displays the connection information.
* INPUT: none
* RETURNS: 0
****************************************************************************/
ShowConnections:
arg first last
do nextConnection = first to last
SAY LEFT(nextConnection , 11),
LEFT(userInfo.nextConnection.userName , 16),
RIGHT(userInfo.nextConnection.connectTime,19),
|| " [" ||,
RIGHT(userInfo.nextConnection.netAddr ,21),
|| "]"
end
return 0
/****************************************************************************
* SUBROUTINE FindJulianDate()
* INPUT: DATE in the stem filled in by GETCONNECTIONINFORMATION.
* RETURNS: days in the year.
****************************************************************************/
FindJulianDate:
arg Date
/* these are the julian dates of the last day of previous months */
lastday.0 = 0
lastday.1 = 31 /* last day in jan */
lastday.2 = 59
lastday.3 = 90 /* last day in March */
lastday.4 = 120
lastday.5 = 151 /* last day in May */
lastday.6 = 181
lastday.7 = 212 /* last day in July */
lastday.8 = 243
lastday.9 = 273 /* last day in sep */
lastday.10 = 304
lastday.11 = 334 /* last day in nov */
nMonth = value(Date||'loginMonth')
nday = value(Date||'loginDay')
nMonth = nMonth - 1
nDaysInYear = lastday.nMonth + nday
return nDaysInYear
/****************************************************************************
* SUBROUTINE CalcTime()
* INPUT: Julian day, hour and minutes elapsed since connect time.
* RETURNS: The string hours and minutes in the form DDD:HH:MM.
****************************************************************************/
CalcTime:
parse arg oldJulian, oldhour, oldmins
/*find the elapsed number of days*/
currentDate = DATE(J)
nDays = substr(currentDate,3,3)
nDays = nDays - oldJulian
currentTime = TIME()
cMins = TIME("M")
cHours = 0
do while cMins > 59
cHours = cHours + 1
cMins = cMins - 60
end
if (cMins >= oldmins) then
do
nMins = cMins - oldmins
factor = 0
end
else
do
nMins = (60 - oldmins) + cMins
factor = 1
end
if (cHours >= oldhour) then
do
nHours = ((cHours - oldhour) -factor)
if (nHours < 0) then<
nHours = 0
end
else
do
nHours = (24-oldhour) + cHours
end
return RIGHT(STRIP(nDays) , 3, " ") || ":" ||,
RIGHT(STRIP(nHours), 2, "0") || ":" ||,
RIGHT(STRIP(nMins) , 2, "0")
/*=========================================================================*/
NoScript:
if (resetFilePath == 0 & RC == 43) then
do
resetFilePath = 1
x = CURRENTPATH( fileVol ||"rexxware")
y = CURRENTPATH()
say "CurrentPath = "y
signal CONTINUE
end
else
do
say " ResetFilePath = "ResetFilePath "Rc = " RC
end
exit
/****************************************************************************
* REXXWARE SAMPLE PROGRAM - SELSERV.SRX
****************************************************************************/
main:
parse arg serverType
if (serverType == "") then
serverType = "OT_FILE_SERVER"
prompt = "Enter a server number or press enter to see more servers (0 to exit): "
call Getservers
serverName = SelectServer()
return serverName
GetServers:
objectID = -1
serverIndex = 0
serverList.0 = 0
do forever
retCode = ScanBinderyObject('*', serverType,'objectID','object.')
if retCode \= 0 then leave
serverIndex = serverIndex + 1
serverList.serverIndex = object.objectName
end
if (serverIndex > 0) then
do
serverList.0 = serverIndex
call sort "serverList."
end
return 0
SelectServer:
serverNum = -1
if (serverList.0 < 1) then<
do
call ShowServerBanner
SAY ""
SAY "There are no servers in the bindery"
SAY "Press enter to continue..."
call HIDECURSOR
pull input
call SHOWCURSOR
serverNum = 0
end
else
do while serverNum == -1
do server = 1 to serverList.0 by 64
start = server
end = serverList.0
if (end - start > 64) then
do
end = start + 63
call ShowServerBanner
call ShowServerList start end
end
else if (end - start >= 0) then
do
call ShowServerBanner
call ShowServerList start end
end
else
do
server = 1
start = server
end = MIN(start + 63, end)
call ShowServerBanner
call ShowServerList start serverList.0
end
SAY ""
call GETCURSOR "cursor."
SAY prompt
call SETCURSOR cursor.row, LENGTH(prompt)+1
pull input
if (input == "") then
nop
else if (DATATYPE(input, W) == 1) & (input == 0) then
do
serverNum = 0
leave
end
else if (DATATYPE(input, W) \= 1) | (input < start) | (input > end) then<
do
call HIDECURSOR
SAY "Invalid Selection, press enter to continue"
pull input
call SHOWCURSOR
server = start - 64
end
else if (input \= "") then
do
serverNum = input
leave
end
end
end
if (serverNum > 0) then
serverName = serverList.serverNum
else
serverName = ""
return serverName
ShowServerBanner:
call CLEARSCREEN
SAY "-------------------------------------------------------------------------------"
SAY CENTER("List of servers", 79)
SAY "-------------------------------------------------------------------------------"
return 0
ShowServerList:
arg first last
rows = (last - first + 1) % 4
remainder = (last - first + 1) // 4
if (rows == 0 | remainder \= 0) then
rows = rows + 1
row = 1
mod1 = first
do while row <= rows<
mod2 = mod1+1
mod3 = mod1+2
mod4 = mod1+3
if (mod2 > last) then
say left(mod1, 4) serverList.mod1
else if (mod3 > last) then
say left(mod1, 4) left(serverList.mod1,14),
left(mod2, 4) left(serverList.mod2,14)
else if (mod4 > last) then
say left(mod1, 4) left(serverList.mod1,14),
left(mod2, 4) left(serverList.mod2,14),
left(mod3, 4) left(serverList.mod3,14)
else
say left(mod1, 4) left(serverList.mod1,14),
left(mod2, 4) left(serverList.mod2,14),
left(mod3, 4) left(serverList.mod3,14),
left(mod4, 4) left(serverList.mod4,14)
mod1 = mod1 + 4
row = row+1
end
SAY "-------------------------------------------------------------------------------"
return 0
/****************************************************************************
* REXXWARE SAMPLE PROGRAM - LOGON.SRX
****************************************************************************/
main:
parse arg serverName ReturnInfo
if (serverName == "") then return -1
retCode = 0
promptName = "Type the userid and then press the enter key : "
promptPswd = "and the password (leave blank if none required) : "
promptRetry = "Attempt to retry?(y/n): "
call CLEARSCREEN
SAY ""
call GETCURSOR "cursor."
connNumber = -1
attempted = 0
do while attempted == 0
userName = ""
userPswd = ""
call CLEARSCREEN
SAY ""
SAY CENTER("Login to file server: " serverName, 70)
SAY "----------------------------------------------------------------------"
SAY ""
call GETCURSOR "cursor."
if (GetUserName() \= 0) then
do
if (Retry() \= 0) then attempted = 1
end
else if (GetUserPswd() \= 0) then
do
if (Retry() \= 0) then attempted = 1
end
else if (AttemptLogin() < 0) then<
do
if (Retry() \= 0) then attempted = 1
end
else
attempted = 1
end
if ReturnInfo == 1 then do
call value userID, userName, GLOBAL
if userPswd == "" then
userPswd = " "
call value userPasswd, userPswd, GLOBAL
end
return connNumber
GetUserName:
call GETCURSOR "temp."
SAY promptName
call SETCURSOR temp.row, LENGTH(promptName)+1
pull input
if (input == "") then
do
Say "Invalid userid"
retCode = -1
end
else
do
userName = input
retCode = 0
end
return retCode
GetUserPswd:
call GETCURSOR "temp."
SAY promptPswd
call SETCURSOR temp.row, LENGTH(promptPswd)+1
parse mask pull input
userPswd = input
return 0
AttemptLogin:
retCode = ATTACHTOFILESERVER(serverName, "serverID")
if (retCode \= 0) then
do
SAY "Could not attach to the file server, rc = " retCode
connNumber = -1
end
else
do
connNumber = GETCURRENTCONNECTION()
retCode = LOGINOBJECT(connNumber, userName, "OT_USER", userPswd)
if (retCode \= 0) then
do
SAY "Could not login to the file server, rc " retCode
connNumber = -1
end
end
return connNumber
Retry:
call GETCURSOR "temp."
response = 0
do while response == 0
call SETCURSOR temp.row, 1
SAY promptReTry
call SETCURSOR temp.row, LENGTH(promptRetry)+1
pull input
if (input \= "Y" & input \= "y" & input \= "N" & input \= "n") then
do
call SETCURSOR temp.row, 1
SAY INSERT("", "" ,, 79,)
end
else
response = 1
end
return input == "N" | input == "n"
/****************************************************************************
* REXXWARE SAMPLE PROGRAM - LOGOFF.SRX
****************************************************************************/
main:
parse arg connection
if (connection == "") | (DATATYPE(connection, W) \= 1) | (connection <= 0) then<
return -1
else
return LOGOUTOBJECT(connection)
* Originally published in Novell AppNotes
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.