Virtual OS/2 International Consumer Education
VOICE Home Page: http://www.os2voice.org
September 2003

[Newsletter Index]
[Previous Page] [Next Page]
[Feature Index]

editor@os2voice.org


Creating Scripts to Automate Application Migration

By Chris Clayton © September 2003

Introduction.

In the past I usually upgraded my computers by migrating to a newer version rather than doing a fresh install. In fact, the operating system on one of my computers has been virtually the same since 1993, other than OS/2 upgrades and fix packs, even though the hardware has changed many times. Thus I was able to keep my desktop virtually intact all of these years.

Since the introduction of OS/2 version 4.5 and eComStation (eCS) 1.0 this has become more difficult. Now with eCS 1.1, the recommendation is not to even try to migrate, but rather to do a fresh install. So when Serenity Systems released a test version of eCS 1.1 in December, 2002, I decided to do a fresh install and migrate my applications. Apparently the 'Add Programs' utility hasn't been updated since 1994, as very few of the 60+ applications I use are known by it. Thus, while the installation of eCS 1.1 was fast, it took almost two days to get my desktop back to the way I like it.

After doing this a few times, I realized I was performing the same tasks over and over: renaming icons, moving folders, and customizing objects. Performing repetitive tasks, to me, means they should be programmed. There are several good tools for saving and restoring the state of the desktop, but where objects are kept has changed drastically with eCS 1.1 and I needed flexibility to deal with the different configurations I have. My systems use several versions of OS/2 from Warp 4.0 to eCS 1.1. Each system has a different suite of applications in use. So I investigated the capabilities of the REXX programming language. Taking advantage of the REXX utility functions, I created three REXX programs, which I call scripts, and a 4OS2 batch file to automate the migration process. In this article I will describe what tools you will need, how to use the REXX functions I wrote so you can create your own scripts, and how I used my scripts.

Requirements.

Even though the functions make writing scripts straightforward, you will need a rudimentary knowledge of the REXX programming language. The "OS/2 Procedures Language 2/REXX manual" provided with the OS/2 or eCS basic installation is a good place to start. Browse the Help Center or execute the following from a command prompt:

view REXX.INF
Also, the VOICE magazine has published a series of articles by Thomas Klein on REXX programming. See: http://www.os2voice.org/VNL/past_issues/VNL0703H/vnewsf2.htm for the latest article.

The "Workplace Shell Programming Reference" is a good resource for information on creating WPS objects. At one time, this book was available from IBM's Developers Connection web site: http://service2.boulder.ibm.com/devcon However, I was not able to find it recently. It is provided with OS/2 version 4.5x on Installation CD in Directory \TOOLKIT\OS2TK45\BOOK, eComStation 1.0 on CDRom 3 in file \IBMMisc\DevToolKit\toolkit.zip, and eComStation 1.1 on CDRom 3 in directory \TOOLKIT\OS2TK45\BOOK. You will need to copy or extract wps1.inf, wps2.inf and wps3.inf to d:\OS2\BOOK (where d: is the eCS boot drive), and then execute the following command:

view wps1.inf+wps2.inf+wps3.inf
Finally, you will need a tool that can display the properties of Workplace Shell (WPS) objects and application initialization files (INI). Unimaint is the program I used. This is a commercial program that provides a graphical interface and is available from BMT Micro. A freeware utility is Henk Gelder's WPTOOLS. This utility consists of a series of programs and REXX scripts that are run from a command shell prompt. The latest version is wptool29.zip and it is available on Hobbes. Also, Mark Dodel mentioned that XWorkplace may have an option to display WPS information. He says that under the object properties icon tab there is a details button. I was not able to confirm this since I do not have XWorkplace installed.

Writing the script.

To show how I created these scripts, I will use an example that migrates Symantec Antivirus. Even though this application is not easily obtained by the general public (I have it because my workplace mandates and supplies it), this example is still a good one because many of the migration activities you need to worry about are performed. I will use snippets of the code in the body of the article. These are displayed using a monospaced font: REXX code lines I have tried to keep these codes line under 80 characters in length to avoid wrapping. In some cases this is not possible. If a code line exceeds 80 characters, the wrapped portion of the line is indented with respect to the first part. The entire REXX example program, including functions, is available on the V.O.I.C.E. server. <MigrSyma.cmd>

Some word processors add formatting information and will save ASCII files using a rich text format that retains this information and might even include 'invisible' control characters. These will cause problems when you try to run your script. Be sure to check that your word processor will save files in pure ASCII text without formatting. If you are not sure what your word processor is doing, be safe and use the builtin eCS text editor 'e.exe' to write or change your scripts.

The first step in the migration process is to investigate the changes that need to be made to the WPS. This is the tedious part. At the very least, you will need to find out the program session type (e.g. OS/2 Program Manager [PM], DOS full screen [VDM], OS/2 command window [WINDOWABLEVIO], or full screen WINeCS [WIN]), the executable used and any parameters. In addition, you may need to record icon names, specific DOS property settings, folder names, and INI file entries. For the later, you will need an INI file examination tool, such as UniMaint. Once you have this information, you can start creating the scripts. To make this easier, I have created a set of functions so that most actions require only one function call that can be cut and pasted. Each function performs rudimentary error checking, logs what is happening, and returns a value that is either an indication of success, or is needed to continue the migration process.

To start the scripts a few initialization steps need to be performed. The first is a comment line that lets eCS know that this is a REXX script.

/* Migration script for Symantec Antivirus */
Then a few global variables need to be defined. The first set are those intended to be modified by the user. These include the folder class to be created. This usually is a WPS folder, WpFolder, but you could substitute another class such an Object Desktop enhanced folder, TSEnhFolder, if you have Object Desktop installed:
 clFolderClass = 'WpFolder'
It is good to keep a log of what has happened since a lot is going on rapidly, so a file name needs to be defined:
fLogFile = 'SymaMigr.log' /* log file - change to '' if not needed */
We also need to tell the script how to make the config.sys changes:
 sCommitCgs = 'N'
/* 'Y' = save changes to config.sys (old as config.mig) */
/* 'N' = save changes to config.mig (config.sys unchanged) */
Finally we need to define an initialization file (INI) that a given application uses. For Symantec that is actually the global 'user' INI file. os2.ini. But, here, we actually use a file that will be created in the current working directory for testing purposes.
 fDestINI = 'symatest.ini' /* USER */
You may wish to define a few others for your particular migration script. For example:
 sIcons = 'E:\UTILITY\ICONS\'  /*  my icons directory */
The next category of global variable definitions are not intended for modification by the user, except as noted, but are needed so the script can complete its tasks.

The first is the location of the config.sys file. This is usually the eCS system drive, but for testing purposes use the current working directory '.' and transfer a copy of config.sys into it:

 /* find the OS/2 drive letter (system booted to) */
sBootDrive = Substr(Translate(Value('PATH',,'OS2ENVIRONMENT')),Pos('\OS2\SYSTEM',Translate(Value('PATH',,'OS2ENVIRONMENT')))-2,2)
/* config.sys location - normally sBootdrive but use '.' for testing*/
sCfigDrive = '.' /* sBootDrive */
If you are going to make modifications to config.sys, then the following global variable need to be defined (more on them later):
/*  variables that will contain config.sys additions to: */
sNewBook = ''     /* BOOKSHELF environment variable */
sNewDpath = ''           /* DPATH environment variable */
sNewHelp = ''                /* HELP environment variable */
sNewLib = ''                                /* LIBPATH directive */
sNewPath = ''               /* PATH environment variable */

 /* the following stem variable is for additional config.sys entries */
stAddlCfg.0 = 0  /* the first will contain the number of entries */
/* .1 .. .n will contain the value of each entry */
Finally we need to load the REXX utility functions:
 Call RxFuncAdd 'SysLoadFuncs', 'REXXUTIL', 'SysLoadFuncs';
Call SysLoadFuncs;
While you can migrate your applications to any existing folder within the WPS, it is wise to create your own folder. For this operation you will need to define an unique object identification variable (ID) for the folder, a title and a place to put it. To be sure to each ID I use is unique, I begin each one with my initials followed by an underscore: 'CMC_'. The following code segment tries to create a folder title 'My Folder' on the Desktop. I say try because it will fail by design if I didn't chose an unique ID:
/* Create a destination folder on the Desktop */
sFolder = '<CMC_MY_PROGS>'
sName = 'My Folder'
rc = Message( 'Creating destination folder: '||sName )
rc = SysSetObjectData(sFolder,'' ) /* see if the objects exists */
if RC = 1 THEN
  DO
    rc = Message( ' Unable to create folder '||sName||' because object ID '||sFolder||' exists' )
    EXIT
  END
ELSE
  DO
    sOptions = 'OBJECTID='||sFolder||';CONCURRENTVIEW=NO;'
    sOptions = sOptions||'VIEWBUTTON=MINIMIZE;MINWIN=HIDE;'
    sOptions = sOptions||'ICONVIEW=NONGRID,NORMAL;ICONVIEWPOS=25,10,50,70'
    rc = SysCreateObject( clFolderClass, sName, '<WP_DESKTOP>', sOptions, 'fail' )
    IF RC = 0 THEN
    DO
      rc = Message( ' Unable to create folder '||sName )
      rc = Message( ' Make sure it is deleted and retry the command.' )
      EXIT
    END
  END /* sObject else */
Notice that the routine Message is used to report the progress and result of the operations. This routine outputs text to the screen as well as any logging file that has been defined. This verbose logging is typical of all the function calls that will be described below. Also I used the ID '<WP_DESKTOP>', the WPS Desktop, to specify where the folder is to be created. I could have used any other valid folder ID to do this. To find out what ID a folder uses, you will need to use the WPS inspection tool.

When you are migrating several applications, it is a good idea to create several subfolders within this main folder you just created. To do this, I have defined a function, CreateFolder, that makes this process simple:

sNewFolder = CreateFolder( sDestination, sTitle, sObjectID )
where sDestination is the location to create the folder in, sTitle is the object title, and sObjectID is an unique object identifier. The main difference between this function and the code segment shown above is that it will not terminate the script if problems are encountered. Rather, if this operation is successful, the ID for the new folder is returned, otherwise the ID of the destination folder is returned. In the Symantec migration example, I create a utility subfolder within the main folder 'MyFolder' (sFolder):
sUtility = CreateFolder( sFolder, 'Utility', '<CMC_MY_UTIL>' )
Now it is time to create objects for the applications you wish to migrate. Again, I have created a function, CreateApp, that makes this operation straightforward:
rc = CreateApp( sDestination sTitle, fExe, sObjectID, sProgType, sExtras )
where sDestination is the folder to create the object in, sTtile is the object title, fExe is the executable file name, including a complete path, sObjectID is an unique program object identifier, sProgType is the session type of the program, and sExtras is a string of additional object creation parameters. The session type was discussed above when we in the information gathering phase. The sExtras is usually used to define the parameters to be passed to the program:
sExtras = 'PARAMETERS=....;'
But this variable can be used to define much more, such as setting DOS session variables. There are also quite a more few session types available than what was cited above. To find out more about these settings see the "OS/2 Warp Workplace Shell Programming Reference".

In the example script, we migrate several applications. The first is a straight forward WPS program, that doesn't require any additional object creation parameters, so sExtras is set to a null string, '':

rc = CreateApp( sUtility, sName, fFile, '<CMC_AV_PROG>', 'PM', '' )
The second is a program that is run from a windowed eCS command shell, and an icon is defined along with parameters:
sParms = 'PARAMETERS=a:\*.* /doallfiles /repair /s+;ICONFILE='||sIcons||'VIRUS.ICO'
rc = CreateApp( sUtility, 'Scan a:', 'E:\SYMANTEC\NAVDXOS2.EXE', '<CMC_AV_SCANA>', 'WINDOWABLEVIO', sParms )
Notice that when multiple extra definition strings are used, they are separated by ';'. As a word of caution, the CreateApp function makes a few assumptions. One of which is that the default path is set to the location of the executable. If these assumptions are not what you need, then follow up the object creation with a call to the REXX utility function SysSetObjectData. For example:
rc = SysSetObjectData( '<CMC_AV_SCANA>', 'STARTUPDIR=E:\SYMANTEC' )
Some programs need modifications to the path definitions and other config.sys entries. For these changes I have created three functions. The first two, ConcactPath and AddCfgEntry, are used throughout the script using global variables to store the changes until, at the end of the script, UpdateConfigSys is called to actually commit the changes.

ConcactPath is used to add a directory to the appropriate path variable. It first checks to see if the directory exists and issues a warning message if the path does not exist. Then the path is added to the end of existing path additions with the correct punctuation. Depending whether the directory is to be added to the BOOKSHELF, DPATH, HELP, LIBPATH or PATH environment, the appropriate global variable needs to be passed to and returned from ConcactPath: sNewBook, sNewDpath, sNewHelp, sNewLib, or sNewPath, respectively. These variables were set to a null string during the script initialization, above. In the example script one call to ConcactPath is:

sNewLib = ConcactPath( sNewLib, 'E:\SYMANTEC', 'LIBPATH' )
Here, the LIBPATH is to be modified, so sNewLib is passed as the first parameter. The second parameter is the directory to be added, and the third parameter is used to identify the environment variable added for logging purposes. The result of the function call is placed in sNewLib.

AddCfgEntry enables you to add entire lines to config.sys. To do this it uses a REXX stem variable, stAddlCfg, to save the changes until they are added to config.sys. A stem variable is equivalent to an array variable in other programming languages. In this case the first array element, stAddlCfg.0, contains the number of lines to be added. The rest of the array, stAddlCfg.n, hold the actual lines to be added. In the following examples, a comment line is added followed by the setting of an environment variable:

rc = AddCfgEntry( 'REM Symantec Antivirus' )
rc = AddCfgEntry( 'SET SYMANTEC=E:\SYMANTEC' )
This function can also be used to add drivers that an application may need.

At the end of the script UpdateConfigSys is called to implement the config.sys changes:

IF bOK = 'TRUE' THEN rc = UpdateConfigSys( sCommitCgs )
exit bOK \= 'TRUE'
The sCommitCgs variable was defined during script initialization and is used to denote where the config.sys changes are to be committed to: config.sys itself, or an alternate file, config.mig.

UpdateConfigSys make the changes by first scanning through the config.sys file and making path environment changes when it finds the appropriate variable. The function does check to see if the requested additions are already present. If they are, existing entries are used. However this check is simplistic in that only the presence of the entire string as it was passed is checked. It is possible to check for each directory entry individually. This is messy but is not difficult code to write, so I have left as an exercise for the reader. As an aside, since the function is already scanning through the config.sys file, this is an excellent opportunity to make changes to existing config.sys entries, such as adding parameters to drivers. The symantec migration script includes a few examples of this process.

After scanning through config.sys, the lines stored in the stem variable stAddlCfg are added. Since there is no way to determine if the syntax of the added lines is correct, no error checking is done. To be safe, make a copy of the original config.sys, even if you decide to save changes to an alternate file.

To successfully migrate some programs it is necessary to either create an INI file or modify an existing files such as the user INI file, os2.ini. If you do need to make changes to an existing INI file be sure to save a backup copy before proceeding further. The function CreateINIentry simplifies the addition of entries to an INI file:

rc = CreateINIentry( fDestINI, 'SymantecInstalledApps', 'AVENGEDEFS', sDefsDir, 'Y' )
where fDestINI is the INI file to be modified. This can either be a file name, which will be created if it does not exist, 'USER' for the user INI file, 'SYSTEM' for the system INI file, os2ini.sys, or 'BOTH'. The latter searches both the system and user INI files, but only modifies the user file. The next parameter is the application name to add, the third parameter is the key name to add for the given application, the fourth parameter is the value to assign to the particular key. The final parameter is 'Y' or 'N' to indicate if a null terminator is to be added to the passed key value. This parameter is necessary since REXX strings do not use a terminator, while many INI entries do. Returned by CreateINIentry is the result of the operation: a null string '' if the key was successfully added, the value of any existing entry, or 'ERROR:' if something went wrong. Since we are attempting to make changes to a file that may be used by eCS itself, this functions does extensive error checking before making any changes. The first is to see if the requested application and key already exist. In this case, the existing key value is returned and logged and no changes are made. Next, the result of the attempt to add a new key is logged. Note, this function is primarily used to add key values that are in text. Some programs use INI file entries that are binary. While this function can handle this case, these entries can be quite long and hard to implement. In this case, I suggest you assume the application is well written and can make default assumptions. First try not adding the binary key and see if the application will run. If it doesn't add the application and key with a null value, the null string '' and a 'Y' to add a null terminator. If this doesn't work, then you have an application that is ill-written and needs to be reinstalled.

This completes the activities that I have done to migrate applications automatically. However, there is one extra step that some applications may need. This is the registering of dynamic link libraries (DLL) with eCS. The is not hard to do via the REXX utility function, SysRegisterObjectClass( classname, modulename). The trick is to find out the value of the function parameters. This is easily done by finding out what DLLs have been registered to your system before performing the eCS installation. Unimaint can provide this information, or REXX includes a function SysQueryClassList that will fill a stem variable with the objects that are currently registered. See the REXX Scripting Language manual that is supplied with OS/2 and eCS for more information

I have provided a few additional functions that are not specifically used in the sample Symantec migration script and have not previously mentioned, but are used in one of my migration scripts. They are ConcactTabID, MoveObject and ShadowObject. The first function is used to automatically add objects to the Object Desktop Tab Launchbar:

sDest = ConcactTabID( sDest, iTab, sObjID, sPlace )
This function is used in a way that is similar to the function that adds path environment directories. sDest is the string that is used to save the object ids to be added. It is both passed to the function and returned. iTab is the tab number to be added to. sObjectID is the unique object ID to be added, sPlace is a the name of the object that is being added and is used for logging purposes. As with the path environment variables, this function is used to accumulate the Tab Launch bar additions until they are added at one time by a call to SysSetObjectData:
rc = SysSetObjectData('<OBJD_TABLAUNCHPAD>', 'ADDOBJECTS='||sDest )
It may be possible to create a similar routine that add trays and objects to eCenter. The trick is to find out the correct properties or objects to be modified. Perhaps the authors of eCenter could write a short article about the object manipulation parameters they have defined. Such, as adding and removing trays, and adding and removing icons to and from a particular tray.

Another of the provided additional functions is used to move objects from one folder to another:

sResult = MoveObject( sSource, sDest, sName )
sSource and sDest are object IDs for the object to be moved and its destination, respectively. sName is again an object description that is used for logging purposes. This function checks that the object to be moved exists. The function then returns the result of the move operations. If it fails, be sure to see if the destination object is a folder that exists. The third function creates a shadow of an object in a folder:
sResult = ShadowObject( sSource, sDest, sName )
The parameters are the same as for MoveObject. Using these functions in my scripts, I am able to rapidly make the final touches to my Desktop.

Example.

As an example I will detail how I use my scripts. My eCS installation is done in four steps. The first step, of course, is the basic eCS install. I use the advanced installation method so I can tailor the features and networking that are installed. After completing phase four, I immediately make an archive. I know that eCS automatically makes an archive after it installs, but many of the final activities are not saved. Since the scripts I create manipulate the workplace shell (WPS), it is essential to have a clean starting point to return to in case things go wrong. As an aside, it is imperative to make sure that the archive you requested is actually created. I have noticed that sometimes the first attempt to create an archive does not 'stick.' To be sure, check the contents of d:\OS2\BOOT\ALTF1MID.SCR. If the top entry does not have the date and time stamp of the most recent reboot, immediately reboot again and ensure an archive is created. Then you can continue with the migration process.

In the second step of my migration process, I run a script that copies several driver and configuration files to the installation drive, modifies config.sys in preparation for later steps, and creates a 4OS2 object on the Desktop. The use of 4OS2 (a replacement command processor for OS/2) is not mandatory for running the migrations, it just makes things a little more convenient. This is followed by installation of drivers. When I am satisfied that all is well, I archive the Desktop. The third step is where the bulk of the migration occurs. I start by running a script that migrates WPS enhancements such as Ctrl-Alt-Del Commander and Object Desktop, and applications that already have an object recreation program available. After this, I modify the WINeCS program manager initialization file, PROGMAN.INI, to add the several WINeCS groups that I copied over in Step 2. Then I run 'Add Programs' to migrate these groups and other WINeCS applications that are in the database. There are a few programs that can easily be migrated using the installation disks, so I run these next. Then, I execute the script that migrates the bulk of the applications, over 40. Once again, I create an archive. In the final step, I run a script that moves folders and program objects, and creates shadows of frequently used objects on the desktop. After a bit of manual tweaking, I am done. Total time: less than two hours!

Summary.

I have provided several functions and an example script that automates the migration process when installing OS/2 eCS on a system that has existing applications. These functions are made to be used in a cut-and-paste fashion and cover folder creation, application object creation, config.sys changes, and INI file entry creation. As a reminder, I strongly suggest you make frequent system file backups and archives during this migration process. I hope these functions speed up your migration to eCS 1.1 as well as future releases.

References:

"OS/2 Procedures Language 2/REXX manual", International Business Machines (1997).
OS/2 Warp Version 4 Technical Library, "Workplace Shell Programming Reference," International Business Machines, (November, 1996).
Sample migration script for Symantec Antivirus - http://www.os2voice.org/VNL/past_issues/VNL0903H/MigrSyma.zip
Other links referenced:
    Unimaint with manuals is available from BMT Micro for $80.00 US ($65.00 without manuals) - http://www.bmtmicro.com/BMTCatalog/os2/unimaint.html
    WPTOOLS is available from Hobbes - http://hobbes.nmsu.edu/cgi-bin/h-search?key=wptool


[Feature Index]
editor@os2voice.org
[Previous Page] [Newsletter Index] [Next Page]
VOICE Home Page: http://www.os2voice.org