Wed, Oct
2 New Articles

The API Corner: Manipulating a Job's Library List

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

Learn what the Change Library List API can do for you.


I recently received a note from Shawn W. related to a situation in which he needs to significantly change a job's library list, perform a function, and then restore the job's library list to the original values. In his case, a corporation on one System i (IBM i, iSeries) might have multiple instances of an entity (multiple plants, multiple companies, etc.) with each entity having multiple libraries associated with it (inventory libraries, financial libraries, etc.). When corporate user X, who might work primarily with entity Y, wants to perform a quick query of entity Z, the need is to save the current library list (presumably, but not guaranteed, oriented to work with entity Y), manipulate the library list to set an environment oriented to entity Z while maintaining other (corporate or facility related) libraries currently in the library list, call a reporting function to report on Z, and on return from the reporting function restore the job environment to the original entity (Y). There are many ways to address these requirements but, as this is The API Corner, we'll approach this task today with the use of several system APIs.

Following is the program we'll be using today. Assuming that you have stored the below source in member SETLIBS of source file QRPGLESRC, you can create the program using the command CRTBNDRPG PGM(SETLIBS).

h dftactgrp(*no)                                                     


dSetLibs          pr                                                 

d Lib1_In                       10a   const                          

d Lib2_In                       10a   const                          


dSetLibs          pi                                                  

d Lib1_In                       10a   const                          

d Lib2_In                       10a   const                          


d AddLib          pr                                                  

d  Lib                          10a   const                          


d ChgLibl         pr                  extpgm('QLICHGLL')             

d  CurLib                       11a   const                          

d  ProdLib1                     11a   const                          

d  ProdLib2                     11a   const                          

d  UsrLibl                   10000a   options(*varsize)              

d  NbrUsrLibl                   10i 0 const                          

d  ErrCde                             likeds(QUSEC)                   


d RmvLib          pr                                                  

d  Lib                          10a   const                           


d RtvJobI         pr                  extpgm('QUSRJOBI')              

d  RcvVar                        1a   options(*varsize)               

d  LenRcvVar                    10i 0 const                           

d  Format                        8a   const                           

d  QualJobName                  26a   const                           

d  IntJobID                     16a   const                           

d  ErrCde                             likeds(QUSEC) options(*nopass)  

d  ResetPfrDta                   1a   const options(*nopass)          


d RunCmd          pr                  extpgm('QCAPCMD')               

d  SrcCmdStr                  4096a   const options(*varsize)         

d  LenSrcStr                    10i 0 const                           

d  OptCtlBlk                  4096a   const options(*varsize)         

d  LenCtlBlk                    10i 0 const                           

d  Format                        8a   const                           

d  ChgCmdStr                     1a   options(*varsize)             

d  LenChgStr                    10i 0 const                         

d  LenRtnChgStr                 10i 0                                

d  ErrCde                             likeds(QUSEC)                 


d SndPgmMsg       pr                  extpgm('QMHSNDPM')            

d  MsgID                         7a   const                         

d  QualMsgF                     20a   const                         

d  MsgDta                      256a   const options(*varsize)       

d  LenMsgDta                    10i 0 const                         

d  MsgType                      10a   const                         

d  CSE                          10a   const                         

d  CSECtr                       10i 0 const                         

d  MsgKey                        4a                                 

d  ErrCde                             likeds(QUSEC)                 

d  LenCSE                       10i 0 const options(*nopass)        

d  CSEQual                      20a   const options(*nopass)        

d  DspMsgWait                   10i 0 const options(*nopass)        

d  CSEType                      10a   const options(*nopass)        

d  CCSID                        10i 0 const options(*nopass)        


d LibInfo         ds                  qualified                  

d  Hdr                                likeds(QUSI0700)           

d  Libs                      10000a                              


d ErrCde          ds                  qualified                  

d  Hdr                                likeds(QUSEC)              

d  MsgDta                      256a                               


d AnotherLib      s             10a                              

d ChgCmdStr       s              1a                              

d CurLib          s             10a                               

d LenChgStr       s             10i 0                            

d LiblEnt         s             11a   based(LiblEntPtr)          

d LiblEntPtr      s               *                              

d Msg             s            512a                               

d MsgKey          s              4a                              

d NA              s             10a                              

d SrcCmdStr       s           4096a   varying                    

d X               s             10i 0                            


 /copy qsysinc/qrpglesrc,qcapcmd                                

 /copy qsysinc/qrpglesrc,qusec                                  

 /copy qsysinc/qrpglesrc,qusrjobi                               




  // Set API QUSEC parameter to send exceptions                 

  QUSBPrv = 0;                                                  


  // Set API ErrCde parameter to not send exceptions            

  ErrCde.Hdr.QUSBPrv = %size(ErrCde);                           


  // Set QCAPCMD Options Control Block for running CL commands  


  QCAP0100 = *Allx'00';                                         

  QCACmdPT = 0;                                                 

  QCABCSDH = '0';                                               

  QCAPA = '0';                                                   

  QCACmdSS = '0';                                               

  QCAMK = *blanks;                                                  

  QCASIDCS = 0;                                                     


  // Get the current library list                                   


  RtvJobI(LibInfo :%size(LibInfo) :'JOBI0700' :'*' :' ' :QUSEC);    


  CurLib = %subst(LibInfo.Libs                                      

                  :(((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL) *     

                     11) + 1)                                        



  // Set current library to Lib1_In                                 


  ChgLibl(Lib1_In :'*SAME' :'*SAME' :NA :-1 :ErrCde);               


  if ErrCde.Hdr.QUSBAvl <> 0;                                        

     Msg = 'Unable to assign curlib of ' + Lib1_In;                 

     SndPgmMsg('CPF9897' :'QCPFMSG   *LIBL'                         

               :Msg :%len(%trimr(Msg))                              

               :'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);        






  AnotherLib = 'QGPL';                                          




  // Restore the user portion of the *LIBL to original values   


  LiblEntPtr = %addr(LibInfo.Libs) +                            

               ((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +       

                 LibInfo.Hdr.QUSCL01) * %size(LiblEnt));        


  ChgLibl(CurLib :'*SAME' :'*SAME'                              

          :LiblEnt :LibInfo.Hdr.QUSLIU :ErrCde);                


  if ErrCde.Hdr.QUSBAvl <> 0;                                   

     Msg = 'Unable to restore previous library list';                 

     SndPgmMsg('CPF9897' :'QCPFMSG   *LIBL'                           

               :Msg :%len(%trimr(Msg))                                 

               :'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);              



  *inlr = *on;                                                        







p RmvLib          b                                                    

d RmvLib          pi                                                  

d  Lib_In                       10a   const                           




  for X = 1 to LibInfo.Hdr.QUSLIU;                                    

      // Check if Lib_In in library list and, if so, remove it     


      if X = 1;                                                    

         LiblEntPtr = %addr(LibInfo.Libs) +                        

                      ((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +   

                        LibInfo.Hdr.QUSCL01) * %size(LiblEnt));    


         LiblEntPtr += %size(LiblEnt);                              



      if LiblEnt = Lib_In;                                         





  if X <= LibInfo.Hdr.QUSLIU;                                      

     // Lib_In currently in *LIBL, need to remove                  


     SrcCmdStr = 'RmvLibLE Lib(' +                                 

                 %trimr(Lib_In) + ')';                             


     RunCmd(SrcCmdStr :%len(SrcCmdStr)                                

            :QCAP0100 :%size(QCAP0100) :'CPOP0100'                    

            :ChgCmdStr :0 :LenChgStr :ErrCde);                        


     if ErrCde.Hdr.QUSBAvl <> 0;                                       

        Msg = 'Unable to remove library list entry ' + Lib_In;        

        SndPgmMsg('CPF9897' :'QCPFMSG   *LIBL'                        

                  :Msg :%len(%trimr(Msg))                             

                  :'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC);           






p RmvLib          e                                                   




p AddLib          b                                                   

d AddLib          pi                                     

d  Lib_In                       10a   const              




  SrcCmdStr = 'AddLibLE Lib(' +                          

              %trimr(Lib_In) + ') Position(*First)';     


  RunCmd(SrcCmdStr :%len(SrcCmdStr)                      

         :QCAP0100 :%size(QCAP0100) :'CPOP0100'          

         :ChgCmdStr :0 :LenChgStr :ErrCde);              


  if ErrCde.Hdr.QUSBAvl <> 0;                            

     Msg = 'Unable to add library list entry ' + Lib_In; 

     SndPgmMsg('CPF9897' :'QCPFMSG   *LIBL'              

               :Msg :%len(%trimr(Msg))                   

               :'*ESCAPE' :'*PGMBDY' :1 :MsgKey :QUSEC); 





p AddLib          e                              

The SETLIBS program is passed two parameters. The first parameter, Lib1_In, is the library that should be used as the job's current library when running the report. The second parameter, Lib2_In, is a library that should be near the top of the job's library list when running the report. As we will see shortly, SETLIBS may also insert additional libraries, prior to Lib2_In, into the current job's library list.

After initializing various API-related variables (an API error code structure QUSEC to have API-detected errors returned as exceptions, an API error code structure ErrCde to have API-detected errors returned in the error code structure, and the control block structure CPOP0100 of the Process Commands (QCAPCMD) API to run CL commands) to enable the environment we want SETLIBS to run in, we start our actual processing.

The first thing we need to do is save the current library list. To accomplish this, we'll use an API that many of you have most likely used in the past: Retrieve Job Information (QUSRJOBI). In previous articles, such as "Easily Identify What Message a Job Is Waiting On," we saw how format JOBI0200 of QUSRJOBI returns information such as if a job is waiting on a reply to a specific message and, if so, what message and where the message can be found. Today, we'll look at format JOBI0700, which returns library list information for an active job. Note that while QUSRJOBI does support other library-related formats, such as JOBI0750, all of the information we'll need today is in JOBI0700, the simpler (and faster) of the library list formats.

The JOBI0700 format returns the following structure.









Number of bytes returned




Number of bytes available




Job name




User name




Job number




Internal job identifier




Job status




Job type




Job subtype








Number of libraries in SYSLIBL




Number of product libraries




Current library existence




Number of libraries in USRLIBL

See note

See note

Array(*) of CHAR(11)

System library list

See note

See note

Array(*) of CHAR(11)

Product libraries

See note

See note

Array(*) of CHAR(11)

Current library

See note

See note

Array(*) of CHAR(11)

User library list

Note: The decimal and hexadecimal offsets depend on the number of libraries you have in the various parts of your library lists. The data is left-justified with a blank pad at the end. The array is   sequential. It is an array or data structure. See the Control language topic collection for the total number of libraries that can be returned to you.

As you can see, the API returns all of the libraries in the system library list of the job, the product libraries of the job, the current library of the job, and the libraries in the user library list of the job. The libraries are returned as Char(11) array elements with the first array element immediately following the Binary(4) Number of libraries in USRLIBL field. Having the libraries returned as Char(11) fields rather than Char(10) is a bit unusual but, as you'll see soon, is also quite convenient when later working with the Change Library List API.

When calling the QUSRJOBI API, SETLIBS uses the LibInfo data structure as the receiver variable. The LibInfo data structure is defined as being LIKEDS the QSYSINC-provided data structure QUSI0700, which defines the fixed-location fields shown above. Following the LIKEDS definition is the field Libs, which is defined as Char(10000), an arbitrary size that is sufficiently large to hold all library names that might be found in the jobs system library list, product library list, current library, and user library list.

To demonstrate how the returned library information might be accessed, SETLIBS next locates and saves the job's current library to the field CurLib. The job's current library is located, relative to the starting location of the returned library list (LibInfo.Libs), by summing the number of libraries in the system portion of the library list (LibInfo.Hdr.QUSLIS) and the number of product libraries (LibInfo.Hdr.QUSPL) and then multiplying this value by the size of each library name returned (11). The resulting displacement into LibInfo.Libs is then used in a %SUBST operation to access the first 10 bytes, which represents the current library value.

A couple of points related to this access to the current library: First, SETLIBS doesn't really need to assign the field CurLib to the value of the job's current library. The use of CurLib is done solely to demonstrate how to navigate the returned list of libraries. Second, CurLib is defined as a Char(10) field within SETLIBS even though both the Retrieve Job Information and the Change Library List APIs use a Char(11) definition. This is done as most applications do work with library names using a Char(10) definition rather than Char(11). When later using CurLib to restore the current library of the job (after our application processing is done), this difference in definition is taken care of due to the prototype of Change Library List defining the current library parameter as a Char(11) constant.

Having saved the current library to CurLib, SETLIBS now changes the current library by calling the Change Library List (QLICHGLL) API. Prototyped as ChgLibl, the QLICHGLL API defines six parameters:

  1. The new library to be used as the job's current library, defined as a Char(11) input
  2. The new library to be used as the job's first product library, defined as a Char(11) input
  3. The new library to be used as the job's second product library, defined as a Char(11) input
  4. An array of library names to be used in setting the job's user library list, with each array element defined as a Char(11) and the parameter used as input
  5. The number of library names being passed in the fourth parameter, defined as a Binary(4) input
  6. The standard API error code structure

Several of the QLICHGLL parameters support special values, one special value in particular corresponding to "do not change." In the initial call to the Change Library List API, as all we want to change is the current library, the following call indicates that no change should be made to the product libraries or the user library list of the job. The only change is that the current library should be set to the value of variable Lib1_In (which, as a reminder, is an input parameter to the SETLIBS program).

  ChgLibl(Lib1_In :'*SAME' :'*SAME' :NA :-1 :ErrCde);               


After verifying that no error was encountered when changing the current library, SETLIBS then makes sure that the second parameter passed to the program, Lib2_In, will be a library near the top of the job's library list when running the report. It accomplishes this by first calling the procedure RmvLib to make sure that Lib2_In isn't currently in the user library list, and then procedure AddLib to add Lib2_In to the top of the current user library list.

The RmvLib procedure could be implemented using a variety of approaches. One that I've run across often is calling a CL program that unconditionally attempts to remove the library using the RMVLIBLE command and then monitoring for the library not being in the user library list with MONMSG. An API-oriented variant of this would be using the QCAPCMD API to run the RMVLIBLE command with an Error Code parameter having a non-0 Bytes provided value (mimicking the CL MONMSG command). In the case of SETLIBS, we have the opportunity to do this processing much more efficiently.

In LibInfo.Libs, SETLIBS already has the full list of library in the job's user library list, so, rather than attempting a RMVLIBLE and then monitoring for a failure, RmvLib enters a FOR group that looks to see if the library to be removed, Lib_In, is indeed in the user library list. Only if Lib_In is found in the current library list does the program run the RMVLIBLE command.

The FOR group processing starts with the first library in the user library list and is constrained by the number of libraries in the user library list (LibInfo.Hdr.QUSLIU). To access the first library in the list (that is, when X is set to 1, indicating the initial iteration of the FOR group), RmvLib sets the pointer LiblEntPtr to the address of the first library. This address is determined in a manner quite similar to how the current library was earlier found. RmvLib takes the address of LibInfo.Libs; sums the number of libraries in the system portion of the library list (LibInfo.Hdr.QUSLIS), the number of product libraries (LibInfo.Hdr.QUSPL), and the number of current libraries (either 0 or 1); and then multiplies this value by the size of each library name returned (11). The resulting pointer is then used with a based variable (LiblEnt) to access the first library. To access subsequent libraries in the user library list (that is, when X > 1) RmvLib simply adds the size of one library entry (11) to the pointer LiblEntPtr in order to access the next library list entry.

Within the FOR group, if LiblEnt is equal to Lib_In, then Lib_In is in the current library list and the FOR group is exited. Upon exit from the FOR group, if X is less than or equal to the number of entries in the library list, then QCAPCMD is used to remove Lib_In from the job's library list; otherwise, RmvLib simply returns.

Having ensured that the library to be added to the top of the current library list does not currently exist in the job's library list, SETLIBS then runs the AddLib procedure. AddLib simply adds the library to the top of the library list using the ADDLIBLE CL command and the QCAPCMD API.

Following this, SETLIBS arbitrarily adds library QGPL to the user library list. This is where Shawn W.'s production application might determine, using whatever logic is appropriate for the environment, whether any additional libraries might be needed. Each of these additional libraries can be added by simply using the RmvLib and AddLib procedures for each library.

Having added all necessary libraries, SETLIBS would now call the reporting tool that is dependent on the library list environment we have created.

After the reporting tool has returned, SETLIBS needs to restore the original library environment prior to returning control to the user. This is accomplished quite easily with the two following operations:

  LiblEntPtr = %addr(LibInfo.Libs) +                            

               ((LibInfo.Hdr.QUSLIS + LibInfo.Hdr.QUSPL +       

                 LibInfo.Hdr.QUSCL01) * %size(LiblEnt));        


  ChgLibl(CurLib :'*SAME' :'*SAME'                              

          :LiblEnt :LibInfo.Hdr.QUSLIU :ErrCde);                

LiblEntPtr is set to address the first library in the original user library list, and the QLICHGLL API is called. The parameters passed to the API are the name of the original current library, the specification that no change is needed to the product libraries (as SETLIBS didn't change them), and the specification that the user library list should be set to the original library list starting with LiblEnt (which is based on LiblEntPtr) for the number of libraries in the original library list (LibInfo.Hdr.QUSLU).

We now have a rather efficient method for manipulating, and later restoring, a job's library list. If, for instance, corporate user X, while working with entity Y, needs to perform a quick query of entity Z, this can be accomplished with CALL PGM(SETLIBS) PARM('ZLIB1' 'ZLIBX'), where ZLIB1 and ZLIBX represent two of the libraries necessary for reporting on entity Z. SETLIBS itself may determine that, like how we added QGPL, libraries ZLIBOTHER, ZLIBINVEN, and ZLIBAP are also needed. The application could then use the RmvLib/AddLib procedures to do this.

As usual, if you have any API questions, send them to me at This email address is being protected from spambots. You need JavaScript enabled to view it.

Bruce Vining

Bruce Vining is president and co-founder of Bruce Vining Services, LLC, a firm providing contract programming and consulting services to the System i community. He began his career in 1979 as an IBM Systems Engineer in St. Louis, Missouri, and then transferred to Rochester, Minnesota, in 1985, where he continues to reside. From 1992 until leaving IBM in 2007, Bruce was a member of the System Design Control Group responsible for OS/400 and i5/OS areas such as System APIs, Globalization, and Software Serviceability. He is also the designer of Control Language for Files (CLF).A frequent speaker and writer, Bruce can be reached at This email address is being protected from spambots. You need JavaScript enabled to view it.. 

MC Press books written by Bruce Vining available now on the MC Press Bookstore.

IBM System i APIs at Work IBM System i APIs at Work
Leverage the power of APIs with this definitive resource.
List Price $89.95

Now On Sale



Support MC Press Online

$0.00 Raised:

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: