Novell is now a part of Micro Focus

Developing NetWare Server Utilities with Simware's REXXWARE

Articles and Tips: article

MORGAN B. ADAIR
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 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.

© Copyright Micro Focus or one of its affiliates