CD Deployment
Making your CD idiot proof.
by Ronnie MacGregor — Date: March 2000

| Introduction Normal Deployment Windows Add/Remove Programs AutoRun your CD CD Launcher The INI file CD Finder Path Changer Screen Resolution Conclusion

To download the files for this article for your own use, click here |

Introduction

> One of the biggest problems in developing applications, is to try and see things, and use things, from the point of view of the user with little computing knowledge. It is difficult in the extreme, if you have knowledge of something, to behave as if you don’t. The subjects which I cover here are the result of discussions with a potential client, who required that his catalogue application on CD should be useable by someone with virtually no knowledge of computers.

> This article is aimed at the beginner tackling CD deployment for the first time. I am sure that there will be other solutions to these problems, but if this is a new subject area for you, then I hope that the solutions which I have found will provide a good head start.

> back to index

#### Normal Deployment

> Visual dBASE deployment has never been easier, with InstallShield Express, and if you are looking for more information on this subject, have a look at Ken Mayer’s Deploy7.How.

> Although InstallShield will create a 650MB directory for CD installation, my preference is to stick to 1.44MB floppy sized directories and this means that if you ever have to provide your application on floppy it is easy to take this from your CD. Irrespective of your choice you will have achieved the production of one or more directories containing all of the files required to deploy your application.

> back to index

#### Windows Add/Remove Programs

> Not everyone knows that you can use Windows Explorer to find and double click on `Setup.exe` residing in the Disk 1 directory on your CD. The user with a little bit of knowledge, and doing things “by the book”, will expect to be able to install a new application from the Add|Remove Programs applet in the Control Panel. Unfortunately when this applet scans the user’s disk drives, it will not look in any directory other than the root directory of any disk it finds, and will therefore fail to find your application.

> What should we do about this? Well there seem to be two options, either empty the contents of your DISK 1 directory into the root directory which is untidy and undesirable, or put something in the root directory for the applet to find. One thing which will not work is simply copying InstallShield’s `Setup.exe` to the root directory.

> Windows Add|Remove Programs is looking for a file called either `Setup` or `Install`. In order to avoid confusion with InstallShield’s `Setup.exe` I have opted to produce `Install.exe`. You can use the `CD.ICO` icon file for this exe to make it look pretty!! This could be one of the shortest programs you’ll ever use, and needs to be compiled to `Install.exe`.

> | `Function CDInstall` `nInstallResult = run(true,"DISK1\Setup.exe")` `if nInstallResult # 0 // 0 success, 2 fail` `msgbox("Launch of DISK1\Setup.exe failed.","CD Error",16)` `endif` `return`

> In order that this will run from the root directory of your CD you need to ensure that the following files from the`\BIN` directory of your Visual dBASE installation are also found there:`Vdb7run.exe`,`Vdb70009.dll` (or other language version) and `Resource.dll`.

> We now have a file that can be found by Windows Install|Remove Programs or by the using Windows Explorer. This is an improvement, but the problem now is that it takes some time for our exe to load up. This is partly because of the fact that we are running it from CD, and partly because of the `Vdb7run` overhead. The problem with this is that the user may think that nothing is happening, and double click again, producing multiple instances of our `Install.exe`. I think the best solution here is to use the ability of a Visual dBASE exe to display a splash screen while it is loading, letting the user know that something is in fact happening. This can easily be done by simply typing msgbox code into the Command window, screengrabbing the msgbox produced (I used PaintShop Pro) and chopping out the button to produce something like this:

> The options to include an icon and a splash screen are to be found in your project properties dialogue as follows:

> These two image files do not need to be “included” in the build of the exe, all this will do is bloat the size of the file.

> back to index

#### AutoRun your CD

> Most users now expect something to happen when they put a CD into a drive, so the next step in helping the user is to arrange for the CD to AutoRun. As far as I am aware the Windows installed default is to have a CD drive AutoRun, so the user with enough knowledge to change this will have enough knowledge to find `Install.exe`. It is very easy to have your CD AutoRun. All you need is a simple text file created with Notepad, called `AUTORUN.INF` which you place in the root directory of your CD. At it’s most basic this is what it needs to contain:

> | `[autorun]` `OPEN=Install.exe` `ICON=My.ico`

> back to index

CD Launcher

> The simple AutoRun above is all very well if you really do want to initiate the install every single time the CD is put into the drive, but what do you do if you have other things on your CD as well. For example a catalogue application may have a large amount of data which it is not desirable (or possible) to copy to the user’s hard drive, and what we want is to install the application to hard drive, but have it access data on the CD. In this instance we are going to need a way of deciding whether our application is already installed or not, and on AutoRun, decide whether to run the pre-existing application, or initiate the install. In order to do this we need another small exe on the root directory of the CD, and I’ve called it `CDLaunch.exe`. Now we want our `AUTORUN.INF` to launch this exe, and we need to pass a parameter to it with the name of our application exe. It will now look something like this:

> | `[autorun]` `OPEN=CDLaunch.exe MyApp.exe` `ICON=My.ico`

> The logic used here is simple. Look for a pre-existing installation of our application anywhere on the users system. If we find it, we’ll run it. If not we will initiate the installation.

> Again some time is taken for this exe to load up, so it’s probably best to give the user something to look at, again by way of a splash screen, perhaps something like this:

> In order to ensure that we can find our application, we need to ensure that as part of the InstallShield Express installation we write a couple of bits of information into the Windows registry. Some developers seem to avoid the use of the Registry, or treat it as some sort of black art, but it’s actually very easy to accomplish. InstallShield will do everything for us, all we need to do is Add Keys to match the path shown below leading to an entry called `MyApp.exe` (whatever you have called it!) as in the following example:

> In this Key we want to add the following values:

> Having done this, our information will be written to the registry on installation and removed on un-install. This means that wherever the user decides to install our application, we can find it, simply by looking in the registry.

> In order to retrieve this information we can use `Registry.prg`, which can be found in the`\Samples` directory of your Visual dBASE installation. This program in turn needs `winreg.h` and `windef.h` which can be found in the`\Include` directory of your Visual dBASE installation. The best way I have found to use these is simply to copy them into the project directory you are working in, and then we can do the following:

> | `Function CDLaunch(cExeName)` `local cPath, nExeResult, nSetupResult` `//` `set procedure to registry.prg additive` `//` `// Check parameter` `if empty(argvector(1))` `msgbox("Required parameter not passed.","Call Error",16)` `return` `endif` `// Try to get app path from registry` `try` `reg = new Registry((0x80000002),;` `"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\";` `+ cExeName)` `cPath = reg.queryValue("Path")` `catch(Exception e)` `// Just continue !!` `endtry` `// Check app not already running` `try` `fClose(fOpen(cPath + "\" + cExeName,"RW"))` `catch(Exception e )` `if e.code == 108 // file in use by another` `return // quit —- quit causes screen flash` `endif` `endtry` `// Try to run app` `if(reg.error == 0) // No error` `cd (cPath)` `nExeResult = run(true,cPath + "\" + cExeName)` `else` `nExeResult = run(true,cExeName)` `endif` `// If app didn't run initiate install` `if nExeResult # 0 // 0 success, 2 fail` `nSetupResult = run(true,"DISK1\Setup.exe")` `if nSetupResult # 0 // 0 success, 2 fail` `msgbox("Automatic CD launch failed.","CD Error",16)` `endif` `endif` `//` `return`

> We have checked to see if the application is already running before we try to run it, and this is to prevent a second instance of the application being run in the event that the application is started from the Windows Start menu, and the CD is subsequently inserted to provide the data, whereupon it will AutoRun. The technique of establishing whether our application is already running is from Romain Streiff’s `IsExeOpen.prg` in the dUFLP.

> It is also worth pointing out that prior to running our pre-installed application, we have changed directory to the application directory. If we did not do this the current directory would be the root directory of our CD drive, and that might make it difficult for our application to find files it expects to find in the current directory. It is also of importance with regard to our application’s INI file (see later).

> Note that if we find that an install is required we bypass our `Install.exe` and go straight to InstallShield’s `Setup.exe` for speed.

> The above code can be compiled to `CDLaunch.exe`, and again in order that this will run from the root directory of your CD you need to ensure that the following files from the`\BIN` directory of your Visual dBASE installation are also found there:`vdb7run.exe`,`Vdb70009.dll` (or other language version) and `Resource.dll`.

> back to index

The INI file

> The ini file is absolutely critical in ensuring that the behaviour of your deployed application matches that of the development version. If you are looking for more information on this subject, again have a look at Ken Mayer’s Deploy7.How.

> The INI file for your application should have the same name as the exe, and should be deployed using InstallShield. The part of the INI file that we are interested in here is the Directories section. There are two problems with this section. We do not know where the user might choose to install the application, so we cannot hard code the directory path prior to deployment. If we remove this section altogether from the INI file then we may have problems getting our application to run. The answer is to include the heading, but not to include any information after it, like so:

> | `[Directories]`

> When the application is run, Visual dBASE will write the current directory information back to your INI file when it closes so that it might now look like this:

> | `[Directories]` `0=C:\MyAppDir` `Current=0`

> This is of critical importance when we realise that we may be launching our hard drive installed application from the root directory of the CD. If we are not careful the CD drive root directory will be written to our INI file and subsequent attempts to run the application will fail. This is why in our `CDLaunch.exe` we change directory to that of the application before running it.

> back to index

CD Finder

> The next thing that we have to deal with is accessing our data when it remains on the CD. The problems here are that we don’t know the drive letter of the user’s CD drive, and we don’t know if our CD is in the drive.

> The first task is to get the users CD drive, and in the case of multiple CD drives we will use the first drive that we find. I have done this by using the `GetDrives()` function which is available in the dUFLP, and have incorporated it into my code. Having found a CD drive, then if an optional object reference is passed to the function, then a custom property `CDdrive` will be attached to that object, containing the drive letter for the CD Drive. This is then available to be used programmatically.

> We now have to look for our CD in the drive, and we do this by looking for the existence of a unique file which will only be found on this CD. Again we have the task of holding the users hand, so if we don’t find the CD, we pop up a msgbox requesting that “CD Name” is placed in drive “Drive Letter”, and we repeat this until the user complies or quits. If the correct CD is successfully found then the CD drive letter is returned, otherwise the space character is returned.

> There is nothing particularly complicated about this code, but if you are new to this subject then it is probably worth working through it to see how it works.

> | `Function CDFinder(cFile,cCDname,oRef)` `local cReturn, nChoice` `//` `// Check parameters` `if empty(argvector(1))` `MsgBox("File name parameter not passed","Call Error")` `cReturn = " "` `return cReturn` `else` `// strip off any drive letters etc` `cFile = substr(cFile,(at(":",cFile,1)+1),len(cFile))` `if substr(cFile,1,1) = "\"` `cFile = substr(cFile,2,len(cFile))` `endif` `endif` `if empty(argvector(2))` `cCDname = "required"` `endif` `//` `// Find CDROM Drives` `aCDdrives=Getdrives(5) // returns array containing all the CD drives` `// Next 3 lines output the drives found in the results pane` `// for n=1 to aCDdrives.size` `// ? aCDdrives[n]` `// next` `if aCDdrives.size = 0` `MsgBox("CD ROM drive not found or faulty ","CD ROM drive error")` `cReturn = " "` `else` `// Check object flagging parameter` `if not empty(argvector(3))` `try` `oRef.CDdrive = aCDdrives[1]` `catch ( Exception e )` `msgbox("The calling program did not pass" +chr(13)+;` `"a valid object reference parameter.","Call Error",16)` `endtry` `endif` `//` `// Look for CD in drive` `nChoice = 0` `// Loop exit control is inside loop` `do while nChoice < 3` `if not file(aCDdrives[1] + ":\" + cFile) // file only on this CD` `nChoice = MsgBox("Please insert the " + cCDname ;` `+ " CD into Drive " + aCDdrives[1],"CD ROM drive error",1)` `if nChoice = 1` `loop // Until correct CD or the user cancels` `endif` `if nChoice = 2` `cReturn = " " // User cancel` `exit // Exit loop` `endif` `endif` `cReturn = aCDdrives[1]` `exit // Exit loop if CD is found` `enddo` `endif` `return cReturn`

> `CDfinder.prg` and a demo/test form `CDfinder.wfm` can be downloaded from the top of this document. Further information can be found in the program header.

> back to index

Path Changer

> We now know the drive letter of the users CD drive, and we know that the CD is in the drive ready for use, but the question is, how do we ensure that our datapath is correct, and ensure that we can access our tables? Again there are probably many ways to do this, and one of those is to use the `CDfinder()` function to attach a custom property somewhere, and this could be used programmatically to create an sql on the fly. Here is an alternative method which I find to be simple and effective.

> Most people will use an`.sql` or preferably a`.dmd` file to set up their data access. These files can be included in the build of your exe, but lets make a decision here to leave them outside the exe, and deploy them as stand alone files in the application’s directory. These files contain hard coded data paths which are fine if the data is in the application directory, but does not help us much when our data is on a CD. It is however a simple matter to use the `PathChanger()` function to open your sql or dmd file, search for drive letters, and substitute any occurrences with the drive letter you have just established as containing your CD.

> | `Function PathChanger(cFile, cDrive)` `local cThisChar` `// Check parameters` `if empty(argvector(1))` `MsgBox("File Name parameter not passed","Call Error")` `return False` `endif` `if empty(argvector(2))` `MsgBox("Drive Letter parameter not passed","Call Error")` `return False` `endif` `// Find and change Drive Letters` `fChange = new File()` `try` `fChange.Open(cFile,"RW")` `do while not fChange.eof()` `cThisChar = fChange.Read(1)` `if cThisChar = ":"` `cThisChar = fChange.Read(1)` `if cThisChar = "\"` `fChange.Seek(-3,1)` `fChange.Write(cDrive)` `fChange.Seek(3,1)` `endif` `endif` `enddo` `fChange.Close()` `catch(Exception e)` `return False` `endtry` `return True`

> A bit unorthodox ? . . . Well it works !

> back to index

Screen Resolution

> How do you know if the end user has a sufficiently high screen resolution or colour setting such that your application will display properly? Some people will argue that you should design for the lowest common denominator, but if you do that there will never be any advances made. I personally find it hard to believe that in this day and age people are still working at 640x480, but it’s true. The user’s computer arrives on their desk that way, and they don’t even think about it let alone have a clue how to change their settings.

> This issue was brought to my attention when a potential client reported that they couldn’t use my demo disk because the 800x600 forms didn’t fit on the screen. To be told on the CD inlay that this was the required resolution wasn’t good enough, and he requested that I did something about it. My immediate thought was to put a txt file on the CD with instructions on how to access the Windows Screen Settings dialogue, but I realised that someone who can’t change their screen settings is unlikely to be able to find that file or access it.

> My solution was to write a program which can be called by an application on startup, passing the required resolution and colour depth information to it. The two main functions used to retrieve this information from the users system,`GetScreenRes()` and `GetColourDepth()` are derived from Gary White’s approach in `SysInfo.cc` in the dUFLP.

> The next task was how to deal with things if the user’s display settings are not high enough. For the user with no knowledge, we need a method of giving them a little bit of knowledge, and holding their hand at the same time. If the user’s settings are not high enough then the following form will be opened:

> If the user chooses to change their display properties, then the Windows Display Properties dialogue is opened up with the settings tab already selected. This is achieved with the following single line of code:

> | `run(true,"rundll32.exe shell32.dll,Control_RunDLL desk.cpl,,3")`

> I don’t think you can really help the user much more than that!

> The function returns a numerical code covering every possible outcome/user choice, including the use of the Window close button “X”. The function can easily be used in your application header by coding along the lines of the following:

> | `// Check display settings` `set procedure to ScrCheck.prg additive` `local nScrCheck` `nScrCheck = ScrCheck("800x600",16) // use 1600x1200 to test` `if nScrCheck <> 0 // 0 current settings OK` `do case` `case nScrCheck = -1 // Problem with passed parameters` `msgbox("Problem with passed parameters",;` `"Screen Check",16)` `quit` `case nScrCheck = 1 // Change Settings clicked` `msgbox("Please change your display settings in the";` `+ chr(13) +;` `"Windows dialogue that has just popped up, and";` `+ chr(13) +;` `"re-launch this application.","Screen Check",64)` `quit` `case nScrCheck = 2 // Continue unchanged clicked` `msgbox("Correct display may not be possible with;` `your present screen settings.","Screen Check",16)` `case nScrCheck = 3 // Quit clicked` `msgbox("You have chosen to Quit.","Screen Check",16)` `quit` `case nScrCheck = 4 // Windows Close "x" clicked` `msgbox("The application will quit.","Screen Check",16)` `quit` `endcase` `endif` `release nScrCheck`

> `ScrCheck.prg` and a demo/test form `ScrDemo.prg` can be downloaded from the top of this document. Further information can be found in the program header.

> back to index

### Conclusion

> The functions, programs, and exes we have looked at here go together to form an instantly re-useable set with which it is very easy to deploy your application on CD while making your CD idiot proof. I hope that for the beginner this provides a good head start, and that for the seasoned developer there might be something in there to think about.

> The next logical step would be to provide a UI form with the CD Launcher where the user could choose to Install the application, Run the installed version, or run the application completely on the CD. I haven’t yet had the need to do this, but I can see that the day may come.

> You can download all of the files for this article from the top of this page, and are free to use them as you wish. I will be happy to receive any feedback by e-mail, or by posting in the Visual dBASE Newsgroups.

> Have Fun !

Acknowledgements: Thanks to Romain Strieff, Gary White, Ken Mayer, Dan Howard and Ivar B Jessen for their help via the Visual dBASE Newsgroups in helping me overcome some of the issues here which in turn have given rise to this article. Thanks to my wife Sarah for proof reading.

Ronnie MacGregor started developing dBASE IV applications around 15 years ago to help administer his businesses in Scotland. Many of these applications are still in daily use, but are gradually being replaced with 32bit Visual dBASE apps. Although with no formal programming training, and essentially a “hobby” programmer, some of his niche applications are used by similar businesses in Britain. He can be contacted at:

Addendum

April 2005:
Since this article was written, Visual dBASE has changed to dB2K, and now to dBASE PLUS. This priciples in this article remain valid, but specific dBASE related file names may have changed.

The latest version of the utility code included in this article is available from: www.dBASEdeveloper.co.uk