28
Thu, Nov
0 New Articles

Say Goodbye to CL-Use Service Programs to Control Your AS/400

General
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

In the AS/400 environments I've worked in, there is an almost universal understanding of the roles of CL and RPG that goes something like this: Use CL to control the operating system and system objects, and use RPG to control database, display, and print files. Certainly, some sites employ CL far more than others, but I expect most of you would recognize this as a generalization.

Of course, this is no accident. The respective capabilities of CL and RPG are such that this kind of use is natural. But haven't you ever wished that CL had a more readable syntax? Or had arrays? Maybe better file handling and, while we're at it, select, do while, and do until statements? In short, if CL had all the features of RPG, everything would be fine and dandy. You could put this in reverse and wish that RPG had all the powerful features of CL. Well, you need wish no more-with RPG IV and ILE, all of this is possible.

But there's more to this than just gaining a flashy CL or RPG syntax. The division of computer tasks into system (read CL) and business (RPG) isn't particularly relevant when designing components for software models that have to deal with real-world applications as diverse as ordering wood pulp and landing a probe on Mars. What is relevant is that these components match the concepts we visualize when designing our model. It is the computer that should mimic us, not the other way round.

The RPG IV procedure goes a long way toward making this possible, and you should adopt it as the fundamental building block of the more complex systems you design and assemble. Allow me to show you how easy this is to do. In this article, I'll introduce you to two service programs that make interacting with OS/400 a breeze and are indispensable in developing more sophisticated procedures. The first is the COMMAND service program, which allows you to issue commands, and the second is the SYSTEM service program, which provides all the functionality usually associated with CL.

Service programs are a new concept to many RPG programmers. You code them in the same way as you would any other RPG IV program, only there isn't any main-line. That's because you don't execute a service program-you just call its procedures. You can think of a service program as a collection of procedures that any program can use. Creating a service program is similar to creating a program. With both programs and service programs, you use the Create RPG Module (CRTRPGMOD) command to compile your source and create a module. The module is a system object consisting of compiled code, but it isn't executable. You first have to bind one or more modules together to create a program or service program using the CRTPGM (Create Program) or CRTSRVPGM (Create Service Program) commands. The key difference between the two is that a program must include at least one module that has a main-line.

If you want to use a procedure that resides in another service program, you have to make the compiler aware that this procedure exists and aware of how it can be used (i.e., what parameters are expected and what values are returned, if any). This is what the prototype is for. Whenever a procedure is referred to in source code, the compiler checks the prototype to ensure the usage conforms to it. If it doesn't, the compiler will issue an error message, and no module will be created. As you can imagine, coding a prototype in your source every time you want to use a commonly used procedure would become tedious. The best approach is to code your prototypes only once and keep them in a source file of their own. I keep my prototype source in a file called QCPYSRC. Whenever I want to use the procedures in a service program, I include the line /COPY QCPYSRC,xxx where xxx is the name of the prototype source member. I also make a point of having a prototype source member for every service program, rather than lumping all prototypes together in one big member. An outcome of this technique is that, for every service program, I have a source member in QRPGLESRC (where the actual procedure code is) and another member by the same name in QCPYSRC (which consists of prototypes only for these procedures).

It should be clear now why a module isn't executable. If you use a procedure that is defined elsewhere, the compiler may be satisfied that you are using this procedure correctly (by checking your usage against the prototype) and successfully create a module, but the code that makes the referenced procedure work is still unknown to the module. The module must be linked to the service program where the called procedure resides. You do this in the CRTPGM and CRTSRVPGM steps, where you list all the service programs required as the BNDSRVPGM parameter.

COMMAND is about as simple as a service program can get. It consists of only one procedure and requires no other service program. The procedure is called ExecCmd, and the prototype is shown in Figure 1. First, a word or two about the conventions I use: The return value of ExecCmd is a 7-alpha character string, which will be loaded with a message identifier should anything unexpected happen. If you pass OptParms, it will be loaded with any parameters associated with the error message. I use these two parameters in this way very often for indicating error situations and providing information about them, but I never use them to communicate informational-only messages (as opposed to true errors). This is to make life simple for the caller. If a procedure returns blanks, it succeeded. If it returns a message, it failed. If you want to pass a message back indicating something like "Job submitted" or "Operation succeeded," then use another parameter dedicated to this purpose. I also prefix all optional parameters with the letters Opt. While this enhances readability a little, the real reason will

become apparent soon. Let's get back to ExecCmd.

As Figure 1 shows, ExecCmd has only one mandatory parameter, and that's the command you want to execute. Note that this is a pass-by-value parameter (indicated by the word value on the prototype), so the command you pass doesn't have to be 2,048 characters long and could be a literal. Let's delete all files starting with WORK currently in library QTEMP.

The program in Figure 2 shows how you would do this. This would work fine if there were some files starting with WORK in QTEMP, but what if there weren't? With CL, we have to insert MONMSG statements to deal with the unexpected; otherwise, the program crashes. Not so with ExecCmd. It just passes back the message indicating an error occurred, then carries on to the next statement.

In the above example, I'm not tracking the return value, because I want to delete WORK* files only if they exist. If I were interested in the success or failure of the command, I would code something similar to Figure 3.

So what's really going on behind the scenes when ExecCmd gets called? Let's take a look at the code in Figure 4. Yes, it's the API QCAPCMD. Like all APIs, if you pass an error data structure (ERRC0100) and an error occurs, the error details are passed back to the caller, and the system error handler is not invoked (that's the thing that displays highlighted messages when you forget to code a MONMSG in a CL). But rather than dwell on the code, let's just compare it to the prototype. Which do you think is easier to understand? Would you rather use ExecCmd or QCAPCMD directly? This highlights the thinking behind using procedures. Hide all the code behind the prototype and make the prototype clear and practical. Then, no one else needs to waste any time figuring out how it works in order to use it.

So why do I think ExecCmd warrants a service program all of its own? The answer to this requires an understanding of activation groups. An activation group isn't some kind of system object that you have to create-it's an attribute of a program or service program that you specify when creating the program. You use them to subdivide your job. Amongst other things, programs that have the same activation group can issue the Override Database File (OVRDBF) and Open Query File (OPNQRYF) commands and share the same open data paths without affecting programs running in other activation groups. COMMAND should be created with an activation group of *CALLER, meaning every activation group that uses COMMAND gets its very own copy of it. This allows commands like OVRDBF and OPNQRYF to be effective over the activation group that invokes them. If COMMAND resided in an activation group of its own and another activation group issued an OVRDBF using ExecCmd, that override would be effective only within the activation group that COMMAND resided in. The caller would be wasting its time. In most cases, it's better to create service programs with activation groups of their own so that only one copy is active per job. COMMAND is an exception to this. SYSTEM, the next service program I'll deal with, is not. It will have dozens, perhaps hundreds, of procedures, and only one copy of it needs to be active per job.

(I'm using the term "copy of a service program" very loosely here. There will only ever be one copy of the executable code running, no matter how many activation groups. By "copy" I'm referring to file data paths and buffers, variables, and allocated memory.)

Within SYSTEM, you'll recognize many of the procedures that are more commonly connected with CL. Rather than go into all the procedures I'm using, I'll demonstrate how to create them and leave the rest up to you. The key thing here is that service programs are inherently extendable, so, as the need for more procedures arises, just add them. Let's create a procedure called CpyF that allows us to use the Copy File (CPYF) command. It makes sense to use the same names for procedures as the command you're implementing so other programmers don't have to guess, though I do capitalize key letters to enhance readability. I show the prototype in Figure 5.

Anyone familiar with CPYF will understand these parameters without explanation. Because the parameters you pass will be embedded within a real CPYF command, any value that is acceptable to CPYF can be passed (e.g., *LIBL is valid as a FromLib value). Notice also that the same convention I used with ExecCmd for dealing with errors is at work here. If CpyF ends without incident, *BLANKS are returned; otherwise, the message identifier is returned and optional parameters are loaded. Figure 6 shows code that is extracted from an interactive program that has display fields X_FILE, X_LIB, X_TOFILE, and X_TOLIB.

Here, the user enters the name of the file and library to be copied, along with the name of the file and library to be copied into. If anything goes wrong, the error message and any parameters are passed to procedure DspError, which highlights and positions to the field named and displays the error (DspError is part of a suite of procedures I use that streamline interactive programming, but that's another story). Notice that I haven't bothered to edit any of the screen values before calling CpyF. That's because I don't need to. If the values of X_FILE and the rest are invalid, CpyF will fail and return the appropriate message. Sometimes, system return messages can be a bit vague, so in those cases, a precheck of data is appropriate. Figure 7 shows how CpyF works.

At this point, I should explain optional parameters a bit further. You can specify options(*NOPASS) against a parameter to indicate that it doesn't need to be passed. Once you do this, though, all the following parameters must be defined as optional. You must also take care not to use an optional parameter in your procedure if it hasn't been passed. If you do, you'll get a "Reference to location not found" error at runtime, causing your program to crash. My approach is to define a local variable within the procedure for every optional parameter. I initialize this local variable with a default value (i.e., the value used if the parameter isn't passed), and then I use the %parms built-in function to see if the parameter was passed or not. If it was, I reset the value of the local variable to what was passed.

Once I've counted the parameters and set the values of the local variables, all the following logic will use only these locals, with the assurance that they do indeed exist. The optional parameters, those that I prefix with an Opt, are referenced only within a %parms condition. The rest is pretty straightforward. The CPYF command is constructed using the passed parameters and the priceless %trim function, and ExecCmd is called to do the job.

It's also no big deal if you want to specify parameters other than the ones I'm using. By adding new optional parameters after those already in place (rather than rearranging them), you won't need to recode any logic already using CpyF. What I've done here for CpyF goes just as well for any other command you can think of, and, within minutes, you can create DltF, ClrPFM, ChkObj, and more. Once you've got these basic procedures together, it's an easy step to create more complex functions like CpyPF and LFs. (If you can't guess what that does, then I've failed

to give it an adequate name.)

Some system commands, like Create Program (CRTPGM) and Create Service Program (CRTSRVPGM), require lists of parameter values, rather than the single instance parameters I've used up until now. In those cases, I pass the name of a dynamic table (see "Dynamic Tables in RPG," MC, December 1997), which I preload before calling. CRTPGM requires a list of the modules and, optionally, the service programs that are to be bound together. My CrtPgm prototype looks like Figure 8. But CL does more than just run commands. What about all those functions like Retrieve Job Attributes (RTVJOBA) and Retrieve System Value (RTVSYSVAL) that extract valuable system information? How can ExecCmd retrieve it? Well, to tell the truth, I don't know of a way yet, so I don't use it. In these cases, I put together small CL modules dedicated to retrieving these values, prototype the procedure as usual, and then bind them into SYSTEM.

To create a CL ILE module, the source code must first be of type CLLE rather than the CLP you're familiar with. It would be wise to create a new source file called QCLLESRC to keep it in. You use command Create CL Module (CRTCLMOD) to create the module and then bind it into a program or service program as you would any other module, whether it be RPG, C, or CL. This is, after all, what ILE is all about.

You needn't limit yourself to implementing commands that operate on system objects currently available on the AS/400. CrtWidget, DltWidget, and CpyWidget could all take their places alongside the more familiar CpyF and CrtDupObj commands. Widget could be a combination of files, data structures, user spaces-you name it.

RPG IV may not be a fully fledged object-oriented language yet, but it certainly makes it a lot easier to adopt object-oriented design philosophy than its predecessor.

John V. Thompson is a technical consultant at Honda New Zealand. He can be contacted by email at This email address is being protected from spambots. You need JavaScript enabled to view it..

Figure 1: The ExecCmd prototype

*===============================================================

* Execute CmdString, return message.
*IfOptParmsispassed,itwillbereturnedwithanyparameters
* associated with the returned error message.

* If OptSubmit passed as *ON, command will be submitted. Default
*is*OFF
*

D ExecCmd PR 7A
D CmdString 2048A value
D OptParms 128A options(*NOPASS)
D OptSubmit 1A value options(*NOPASS)
*===============================================================

*========================================================

* To compile:
*

* CRTRPGMOD MODULE(XXX/EXECTEST) SRCFILE(XXX/QRPGLESRC)
*

* CRTPGM PGM(XXX/EXECTEST) BNDSRVPGM(XXX/COMMAND)
*

*===============================================================

/COPY XXX/QCPYSRC,Command
C callp ExecCmd('DLTF QTEMP/WORK*')

Figure 2: Using ExecCmd in a test program

C Eval *InLr = *On *====================================================

C eval MsgID=
C ExecCmd('DLTF QTEMP/WORK*':Parms)
C select
C when %subst(MsgID:1:3)='CPF'

*

* logic to process any kind of CPFxxxx message.
C when %subst(MsgID:1:3)='CPA' *

* logic to process any kind of CPAxxxx message.
C endsl

*========================================================

*=========================================================

* To compile:
*

* CRTRPGMOD MODULE(XXX/COMMAND) SRCFILE(XXX/QRPGLESRC)
*

* CRTSRVPGM SRVPGM(XXX/COMMAND) MODULE(XXX/COMMAND) +
* EXPORT(*ALL) ACTGRP(*CALLER)

*

*=========================================================

H nomain
H/TITLE Command Procedures

*******************************************************

* PROTOTYPES
*******************************************************

/COPY XXX/QCPYSRC,Command
*

D QCAPCMD PR extpgm('QCAPCMD')
D CmdString 2048A
D CmdLength 9B 0
D OptCtlBlk 20A
D OCBLen 9B 0
D OCBFmt 8A
D ChgCmdStr 1A
D LenAvailCS 9B 0
D LenOfChgCS 9B 0
D ErrorCode 116A

**********************************************************

* EXPORTED PROCEDURES
**********************************************************

P ExecCmd B export

* Execute command in CmdString
*

D ExecCmd PI 7A
D CmdString 2048A value
D OptParms 128A options(*NOPASS)
D OptSubmit 1A value options(*NOPASS)
*

* Locals:
D Submit S 1A inz(*OFF)
D CmdLength S 9B 0 inz(2048)
D OptCtlBlk DS
D TypOfCmdP 1 4B 0 inz(0)

D DBCS 5 5A inz('0')
D Prompt 6 6A inz('2')
D CmdStrSyn 7 7A inz('0')
D MsgKey 8 11A inz(*BLANKS)
D Resvd1 12 20A inz(*LOVAL)
D OCBLen S 9B 0 inz(20)

D OCBFmt S 8A inz('CPOP0100')
D ChgCmdStr S 1A inz(*BLANKS)

D LenAvailCS S 9B 0 inz(0)
D LenOfChgCS S 9B 0 inz(0)
D ERRC0100 DS
D BytesProv 1 4B 0 inz(144)
D BytesAvail 5 8B 0 inz(0)

Figure 3: Scenario for retrieving error information

Figure 4: COMMAND service program source

D ExcepID 9 15A inz(*BLANKS)
D Resvd2 16 16A inz(*BLANKS)
D Exception 17 144A inz(*BLANKS) *

C if %parms 2
C eval Submit=OptSubmit
C endif *

C if Submit=*ON
C eval CmdString=
C 'SBMJOB CMD(' + %trim(CmdString)
C + ') LOG(*JOBD *JOBD *NOLIST)'

C endif *

C callp QCAPCMD(CmdString:CmdLength
C :OptCtlBlk:OCBLen
C :OCBFmt:ChgCmdStr
C :LenAvailCS:LenOfChgCS
C :ERRC0100)

*

C if %parms 1
C eval OptParms = %trim(Exception)
C endif *

C return ExcepID *

PE ************************************************************

*===============================================================

*CpyF
* If OptCrtFile not passed, defaults to *NO.
* If OptReplAdd not passed, defaults to '*REPLACE'.
*

DCpyF PR 7A
D FromFile 10A value
D FromLib 10A value
D ToFile 10A value
D ToLib 10A value
D OptParms 128A options(*NOPASS)
D OptCrtFile 4A value options(*NOPASS)
D OptReplAdd 8A value options(*NOPASS)
*

*=========================================================

C eval MsgID=CpyF(X_FILE:X_LIB
C :X_TOFILE:X_TOLIB:Parms)
C if MsgID<>*BLANKS
C callp DspError(MsgID:'X_FILE':Parms)
C endif
*============================================================

*===============================================================

* To compile:
*

* CRTRPGMOD MODULE(XXX/SYSTEM) SRCFILE(XXX/QRPGLESRC)
*

* CRTSRVPGM SRVPGM(XXX/SYSTEM) MODULE(XXX/SYSTEM) +
* EXPORT(*ALL) ACTGRP(SYSTEM) +
* BNDSRVPGM(COMMAND)
*===============================================================

H nomain

/COPY XXX/QCPYSRC,System
/COPY XXX/QCPYSRC,Command
P CpyF B export *

*CopyFile

Figure 5: CpyF prototype

Figure 6: CpyF in use in an interactive program

Figure 7: SYSTEM service program source (CpyF procedure only)

* If OptCrtFile not passed, defaults to *NO.
* If OptReplAdd not passed, defaults to '*REPLACE'.
*

DCpyF PI 7A
D FromFile 10A value
D FromLib 10A value
D ToFile 10A value
D ToLib 10A value
D OptParms 128A options(*NOPASS)
D OptCrtFile 4A value options(*NOPASS)
D OptReplAdd 8A value options(*NOPASS)
*

* Locals:
D MsgID S 7A inz(*BLANKS)
D Parms S 128A inz(*BLANKS)
D CrtFile S 4A inz('*NO')

D ReplAdd S 8A inz('*REPLACE') *

C if %parms 5
C eval CrtFile=OptCrtFile
C endif *

C if %parms 6
C eval ReplAdd=OptReplAdd
C endif *

C eval MsgID=ExecCmd(
C 'CPYF FROMFILE(' + %trim(FromLib)
C+ '/'

C + %trim(FromFile) + ') '
C + 'TOFILE(' + %trim(ToLib) + '/'
C + %trim(ToFile) + ') '

C + 'FROMMBR(*FIRST) '
C + 'TOMBR(*FIRST) '

C + 'MBROPT(' + %trim(ReplAdd) + ') '
C + 'CRTFILE(' + %trim(CrtFile) + ') '
C :Parms)

*

C if %parms 4
C eval OptParms=Parms
C endif *

C return MsgID *

PE *==================================================================

* Create Program. Returns error message, and loads OptParms, if
* unsuccessful.

* T_Modules is a dynamic table holding the names of all modules to
* be bound together.

* Element is 20 alpha, 1-10 is the module name, 11-20 is the
* library name.
* OptT_SrvPgms is a dynamic table of service programs to be bound
* together.

* Element is 20 alpha, 1-10 is the service pgm name, 11-20 is the
* library name.

* If OptActGrp not passed or blank, defaults to value of Pgm.
* If OptEntryMod not passed,defaults to first module in T_Modules.
*

D CrtPgm PR 7A
D Pgm 10A value
D PgmLib 10A value
D T_Modules 64A value
D OptT_SrvPgms 64A value options(*NOPASS)
D OptActGrp 10A value options(*NOPASS)
D OptEntryMod 64A value options(*NOPASS)
D OptParms 128A options(*NOPASS)
******* End of data ***********************************************

Figure 8: CrtPgm prototype

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: