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.