Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Examining the Windows Setup Toolkit


FEB94: Examining the Windows Setup Toolkit

Taking the pain out of installation

Walter is a freelance developer and software consultant based in Boston. He specializes in system tools and in interfacing complex applications to Windows, DOS, and NT. You can reach him on CompuServe at 73730,553.


One of the best-kept secrets in the Microsoft Windows Software Development Kit (SDK) is the Setup Toolkit. In this article, I'll describe the contents of the Setup Toolkit and explain how you can use it to quickly build high-quality setup programs for your Windows application.

Writing setup programs is often a thankless task. If the installation goes smoothly, the users talk about the product instead; if it goes badly, your customer-support department won't let you forget the flood of complaints. In writing the install program, you must design a creative, visually appealing introduction to your product--and at the same time be utterly paranoid in planning for all the varied configurations and preferences that the install program will confront. Moreover, setup programs must often be written under absurd time pressure, after everything else is done.

Good Windows setup programs are especially hard to write. All of the work of determining what to install and where and of prompting the user through a series of diskettes has to be done with an event-driven GUI. You must let an impatient user cancel the process at any time. Often you have to provide special drivers, only one of which is appropriate to any given user configuration. Context-sensitive help may be an additional requirement. You may need to update one or more profile files, and, if you're going to be working with an OLE server, the Windows registry database. You may have to install a redistributable system component like TOOLHELP.DLL or Win32s, and you must then take care to put files into the right directories after verifying that the user doesn't already have a more recent version from some other vendor.

Most major vendors of Windows applications have built their own install programs to enable them to wring the last possible custom nuance out of their GUI. These one-of-a-kind programs are major development undertakings and are beyond the reach of most developers. Unless you've written your application in Visual Basic 3.0 (which lets you use Setup Wizard to build an install program), you're probably stuck.

But as you'll see, the Setup Toolkit in the Windows SDK lets you build a pretty good setup program with minimum fuss--by just writing a Basic program and customizing a few dialog templates. You won't get all the effects of a fully handcrafted setup program, but you will get a credibly professional result. Although you have to write some code, the toolkit solves the harder problems: interacting with the user, reading and parsing control files, interfacing with DOS to check disk space and the like, and recovering from errors. The toolkit is also available in a 32-bit version for Windows NT, which allows your install program to port easily from 16-bit Windows. However, the toolkit's documentation is sketchy at best.

The Minimal Install

I investigated the Setup Toolkit by creating an installable version of a small application--in this case, Paul Yao's MIN, a minimum Windows program discussed in his book Windows 3.1 Power Programming Techniques (Bantam, 1992). The overall flow of my setup program is as follows:

  1. User inserts the single installation disk and then launches SETUP from Windows.
  2. SETUP reads the SETUP.LST file from the installation disk, creates a temporary working directory on the user's hard disk, and copies and decompresses the rest of the setup program into the working directory.
  3. SETUP invokes the Microsoft Test engine to interpret the customized setup script for the MIN sample.
  4. Script announces itself to the user and copies eight source files, plus one prebuilt executable file, to the C:\MIN directory.
  5. Script builds a Program Manager group with an icon for MIN.
In a nutshell, the steps in creating a MIN setup program are to customize the MSCUISTF.DLL dynamic link library, which contains all dialogs presented to the end user, then create an installation script named MIN.MST. This script is essentially a Basic program that will be executed by the Microsoft Test engine in order to perform the installation. (Note that the Microsoft Test program is not included, only its script engine; to debug scripts, you might find it helpful to obtain the complete Test package and work with it directly.) You next create a MIN.INF file that describes all the files that will be installed, on which installation disk they'll be found, to where they will be copied, and so on. Next, create a SETUP.LST file that directs the SETUP.EXE program to copy and decompress certain files from the first installation disk to a temporary directory on the user's system. Finally, you create images of the installation disks. A disk-layout program in the toolkit substantially automates this often-tedious process.

First, you use a tool such as Microsoft's Dialog Editor or Borland's Resource Workshop to modify the dialog templates contained in DIALOGS.RES (in the BLDCUI subdirectory). DIALOGS.RES contains templates for 16 different dialog boxes. I changed the icon located at the top-left corner of the WELCOME dialog by modifying the IDC_

SETUP entry (in DIALOGS.RC) that specifies the icon filename. Once the dialog has been changed, you build MSCUISTF

.DLL in the BLDCUI subdirectory, using the makefile from the toolkit. However, the makefile was designed for Microsoft C 6.x instead of Visual C++ (which I use). The linkers in these two packages are slightly incompatible, so I had to remove the /NOP link flag and add the /NOE option.

The next step is creating an installation script. The Toolkit provides three samples: SAMPLE1.MST, SAMPLE2.MST, and SAMPLE3.MST. These illustrate progressively more complex operations that can be done with the Setup Toolkit. I took SAMPLE1 and removed a great deal of code to end up with MIN.MST, shown in Listing One (page 86). As mentioned, this is just a Basic program to be executed by the language engine included with Microsoft Test. The very first line in MIN.MST includes SETUPAPI.INC. This file contains many useful functions, described further in the Toolkit documentation. The next several lines of the setup script declare manifest constants, global variables (such as DEST$, which contains the name of the installation directory on the user's machine), and local functions (Install and MakePath). Obviously, a more complicated setup script would have more code in this section of the file.

Immediately after the INIT label, is a section containing initialization code. The SetBitMap and SetTitle functions establish the background and title for the main frame window of the setup program. In the example, the bitmap originates as BITMAP.DIB in the BLDCUI subdirectory and contains a "Microsoft Setup" logo on a shaded blue background. We'll change this later. The GetSymbolValue function retrieves a string from a symbol table that can also be accessed by the C programs in the DLLs associated with the setup program. The setup script wants to determine the pathname of the MIN.INF file. If SETUP was invoked with a command-line argument, STF_SRCINFPATH is that pathname. Otherwise, the script assumes that SETUP copied and decompressed MIN.INF into the current working directory, the name of which is the value of the STF_CWDDIR symbol. The last step of initialization is to call ReadInfFile to read the .INF file into memory so that other setup helper functions can access it.

By this time, the user has seen both a dialog announcing that setup has been initialized (generated by SETUP.EXE under control of options in the SETUP.LST file) and a frame window with the title and background bitmap established by the INIT section of code. The WELCOME section of code calls the UIStartDlg function to display the WELCOME dialog from the resources in MSCUISTF.DLL. This is the dialog I customized earlier. The first two arguments to UIStartDlg let you specify a DLL and a dialog resource integer ID within that DLL. The third argument names a dialog function exported by the DLL. The fourth and fifth arguments specify the dialog and dialog function that FInfoDlgProc will use to react to a Help request. (FInfoDlgProc is one of the generic dialog functions in bldcui\dlgprocs.c--it responds to the Continue, Exit, and Help buttons in the WELCOME dialog.) If the user dismisses the WELCOME dialog by pressing Continue, the setup script regains control and calls the UIPop function to remove the dialog from the screen.

If the user elects to continue with setup, the setup script calls its own internal Install function to do the actual work of installing the product. The CreateDir function creates the installation directory (C:\MIN) if it doesn't already exist. AddSectionFilesToCopyList and CopyFilesInCopyList work together to actually install files from the installation diskettes. The "copy list" is a list of files to be installed. You build this list from pieces of the .INF file. For example, for MIN

.CUR, MIN.DEF, and other entries in the [Files] section be added to the copy list, you write AddSectionFilesToCopyList "Files", SrcDir$, DEST$.

Once you've built the copy list, call CopyFilesInCopyList to actually install them. This function first sorts the copy list by source disk so that the user doesn't have to insert disks that were previously used and removed. Then it prompts the user through the disk sequence and copies the files to their respective destinations. It also updates the thermometer-style progress bar to show the percentage of completion.

After installing all the necessary files, Install creates a Program Manager group and adds an icon for the MIN sample program. This is accomplished via functions in SETUPAPI.INC that use DDE messages to communicate with the Program Manager. Having written C code to do the same thing, I can attest to how much easier these functions are to use. CreateProgmanGroup creates a group named "Samples." There is an undocumented limit of 24 characters for the group name, by the way. ShowProgmanGroup displays the group so the user can watch as icons are added to it. CreateProgmanItem adds an individual icon to the group.

Laying Out the Disks

The setup program requires two auxiliary files. The first, SETUP.LST, is used by SETUP.EXE in order to initialize the setup program on the user's machine. Although you have to create SETUP.LST before you lay out your disks, I'll defer talking about it. The second file, which has an extension of .INF, lists all the files to be installed, their destinations on the user's hard disk, attributes of the files, and so on. You could build this file by hand, and the toolkit manual gives you instructions on how to do this. It's easier, however, to use the disk-layout tools Microsoft provides instead. You do this in two steps. First, you run the DSKLAYT tool to specify the files which will be installed. Then you run the DSKLAYT2 tool to build actual disk images and the .INF file. Both programs are in the DISKLAY subdirectory of the toolkit.

DSKLAYT is a Windows program that works with the files that make up your product, letting you specify layout-time and install-time options for each of your product files. A layout-time option is one that affects how you build the install-disk images. An install-time option takes effect when the SETUP program is actually running on the end user's machine. In the case of MIN.EXE, I specified the same handling for all my files: Files can be placed on any diskette (in this instance, there was only one); every file is a "vital" file (that is, it must be successfully copied to avoid an install-time error); all files are stored in compressed form on the installation diskette; none of the files possess a meaningful version resource; all files should show their real date and time; and finally, all files have read/write permission on the user's disk (otherwise it's harder to delete them later).

DSKLAYT makes some undocumented assumptions about how you've setup your development environment, and it will bite you if you do not fulfill those assumptions. You'll want to place all of the files that belong on your installation disks (and no others) in a single top-level directory (which I call the "layout working directory") on your disk. You can have a subdirectory hierarchy, but keep the names of your subdirectories short; the list box DSKLAYT gives you for file selection doesn't scroll horizontally. You can have extraneous files in this directory tree, but you'll have to specifically exclude them from the installation disks. Finally, copy the files you need from the setup-toolkit directory (see Table 1) into your layout working directory before you run DSKLAYT.

After specifying file properties, the resulting layout is saved in the DISKLAY subdirectory (in my case, with the filename MIN.LYT). Then, make DISKLAY the current directory (in a DOS box) and run the DSKLAYT2 utility to build an image of my one-and-only installation disk like this dsklayt2 min.lyt ..\min.inf /d \setup /f /k 144.

DSKLAYT2 creates an .INF file (MIN

.INF in the parent directory) and builds images of the installation disks in \SETUP\DISK1, \SETUP\DISK2, and so on. The /f option tells DSKLAYT2 to overwrite the .INF file. The /k 144 option tells it to layout 1.44-Mbyte, high-density, 3.5-inch disks.

There's a chicken-and-egg problem that DSKLAYT2 deals with quite nicely. The .INF file belongs on the first disk. You can't build the .INF file, though, until you know how files are going to be distributed across the installation disk set (because one of the parameters in each file description is the disk number on which the file is stored). But you can't lay out the first disk until you know how long the .INF file is. DSKLAYT2 solves this problem by first building and measuring a dummy .INF file and then automatically copying the finished .INF file (in compressed form) onto the first disk image. This automatic behavior is the reason you don't need to mention your .INF file when you run DSKLAYT.

Unfortunately, the toolkit's automation breaks down at this point in two respects. The first has to do with the .INF file. Remember that you need to tell DSKLAYT about all of the setup-program files, in addition to your product files. This is so DSKLAYT2 will place the setup program on the first disk, where it belongs. DSKLAYT2 also puts the setup-program files into the .INF file, however, and this causes them to be installed onto the end user's computer. Of course, you may want the setup program installed on the user's machine--to make it easier for the user to modify the installation. In the MIN example, however, I didn't really want to clutter up the user's disk with 11 extraneous files. To avoid having setup install itself, therefore, I had to hand edit MIN.INF and compress it by hand into the disk image. Although this is only a minor headache, it must be done every time DSKLAYT2 is used to layout a new set of disk images.

The second breakdown of automation concerns the SETUP.LST file. This file must be present (in uncompressed form) on the first setup disk. It tells SETUP.EXE how to launch the Test engine against the right setup script. It also lists the files which should be copied from the first disk into the temporary working directory that SETUP.EXE will create. Example 1 shows the SETUP.LST file for this example.

If you look at the [Files] section of SETUP.LST, you might wonder about the files whose names end with an underscore. The trailing underscore denotes files which are compressed on the setup disk. You are required to type the filenames in this way. One would think DSKLAYT2 could build this portion of SETUP.LST, since it knows which files are compressed and which aren't, but that's not the case.

Customizing the Installation

The example so far results in a workable but rudimentary setup program. You can customize it to be more eye-catching. One way is to change the background screen. Recall that SetBitmap is responsible for this, and we merely need to give it the name of a DLL and a resource identifier within that DLL. In my case, I created a monochrome bitmap using Windows Paintbrush, then added 2 BITMAP MIN.BMP to DIALOGS.RC in the BLDCUI subdirectory. After rebuilding MSCUISTF

.DLL, I changed the SetBitmap call in the setup script to refer to bitmap 2 instead of 1.

Another worthwhile enhancement is to let the user choose where product files are installed. This can be accomplished with the setup-script code in Listing Two (page 86), which introduces another setup API function--IsDirWritable()--the purpose of which should be clear. The handling of the REACTIVATE return from the dialog is, however, anything but clear. This return occurs when the user switches focus to the setup program after having been away from it for a while. Some (not all) of the dialog functions in the toolkit then return this special value. You need to respond by redisplaying the dialog box, as shown in the code fragment.

Version Information

With Windows 3.1, Microsoft introduced a standard for controlling software versions on end-user machines. This standard addresses the situation in which several products from one company share components such as DLLs, font files, device drivers, and so on; alternatively, there may be products from multiple software vendors that rely on a third-party DLL or device driver. For example, vendors of 32-bit Windows applications will likely redistribute Microsoft's Win32s components.

You plainly only want one copy of such shared files installed on the end user's machine. Further, if your product relies on the latest version of a particular file, you don't want some other vendor's install program to wipe out the copy you installed with some previous version. Yet, without some way of checking version levels, somebody else's install program might do just that--simply because that program was built before the newer version was available.

Microsoft's answer is to promulgate a standard way of describing software versions within a program's resources and to provide a set of version-checking APIs packaged in the redistributable file VER.DLL. If you're writing C programs, your routines will use VER.DLL directly. With the Setup Toolkit, however, you don't need VER.DLL; the toolkit incorporates the necessary code in another way.

An example of how to version-stamp your application is shown in Listing Three (page 86). This shows a portion of an RC file which you'd customize for your application.

Once the version information is present in your executable files, users can query the database with a utility like MSD. The more important use of the version information, of course, happens at the time an install program decides whether and where to install the file. If you specify the "Overwrite Older" option in DSKLAYT and use the CopyFilesInCopyList API to copy files, the toolkit will automatically check version information to avoid overwriting a later version of a file. The user is never informed that your older version is being skipped. If you need finer control, you can use the GetVersionOfFile() API to interrogate a version resource.

Additional Features

The Setup Toolkit provides other features for enhancing your installation programs that we can only touch on here. Do you want to provide a private profile file to record installation options and user preferences? Just use CreateIniKeyValue to create and modify it. How about imprinting the installed copy with the name of the user? There's a StampResource subroutine in the toolkit that may help. Will your product open an especially large number of files? Use GetConfigNumFiles to verify that the user's CONFIG.SYS has specified a large-enough FILES parameter. Is your product an OLE server? Use the extensive set of registry functions to interrogate and modify the Windows Registration Database concerning your product.

And finally, do you need to accomplish a function that isn't already provided for by a Basic function in the Toolkit? You can call many DLL functions directly from Basic simply by declaring them. For example, to call the WinExec() function in the Kernel portion of the Windows API, you would first declare it as follows: DECLARE FUNCTION WinExec LIB "KERNEL.EXE" (cmd$, show%) AS INTEGER. The ability to call functions and subroutines within DLLs also allows you to write your own DLLs, of course, thereby extending the capabilities of your install script to include anything possible in Windows.

Example 1: Contents of SETUP.LST.

[Params]
    WndTitle   = Windows Power Programming Sample Setup
    WndMess    = Initializing Setup...
    TmpDirSize = 500
    TmpDirName = ~msstfqf.t
    CmdLine    = _mstest min.mst /C "/S %s %s"
    DrvModName = DSHELL

[Files]
    min.ms_      = min.mst
    min.in_      = min.inf
    setupapi.in_ = setupapi.inc
    mscomstf.dl_ = mscomstf.dll
    msinsstf.dl_ = msinsstf.dll
    msuilstf.dl_ = msuilstf.dll
    msshlstf.dl_ = msshlstf.dll
    mscuistf.dl_ = mscuistf.dll
    msdetstf.dl_ = msdetstf.dll
    _mstest.ex_  = _mstest.exe

Table 1: Toolkit files which must be present on your installation disk (in addition to your application's files). The toolkit files must reside on Disk #1.

File        Compress?
---------------------
setup.exe         No
setup.lst         No
_mstest.exe      Yes
mscomstf.dll     Yes
mscuistf.dll     Yes
msdetstf.dll     Yes
msinsstf.dll     Yes
msshlstf.dll     Yes
msuilstf.dll     Yes
setupapi.inc     Yes
min.mst          Yes

[LISTING ONE]


'-----------------------------------------------------
'$INCLUDE 'setupapi.inc'

CONST WELCOME  = 100
CONST APPHELP  = 900

GLOBAL DEST$

DECLARE SUB Install
DECLARE FUNCTION MakePath (szDir$, szFile$) AS STRING
'-----------------------------------------------------
INIT:
    CUIDLL$ = "mscuistf.dll"
    HELPPROC$ = "FHelpDlgProc"

    SetBitmap CUIDLL$, 1
    SetTitle "Windows Power Programming Samples"
    szInf$ = GetSymbolValue("STF_SRCINFPATH")
    IF szInf$ = "" THEN
    szInf$ = GetSymbolValue("STF_CWDDIR") + "MIN.INF"
    END IF
    ReadInfFile szInf$
    DEST$ = "C:\MIN"
WELCOME:
    sz$ = UIStartDlg(CUIDLL$, WELCOME, "FInfoDlgProc", APPHELP, HELPPROC$)
    IF sz$ = "CONTINUE" THEN
    UIPop 1
    ELSE
    GOTO QUIT
    END IF
    Install
QUIT:
    END
'-----------------------------------------------------
SUB Install STATIC
    SrcDir$ = GetSymbolValue("STF_SRCDIR")
    CreateDir DEST$, cmoNone
    AddSectionFilesToCopyList "Files", SrcDir$, DEST$
    CopyFilesInCopyList
    CreateProgmanGroup "Samples", "", cmoNone
    ShowProgmanGroup  "Samples", 1, cmoNone
    CreateProgmanItem "Samples", "Minimum Windows Program",
                MakePath(DEST$,"min.exe"), "", cmoOverwrite
END SUB
'-----------------------------------------------------
FUNCTION MakePath (szDir$, szFile$) STATIC AS STRING
    IF szDir$ = "" THEN
    MakePath = szFile$
    ELSEIF szFile$ = "" THEN
    MakePath = szDir$
    ELSEIF MID$(szDir$, LEN(szDir$), 1) = "\" THEN
    MakePath = szDir$ + szFile$
    ELSE
    MakePath = szDir$ + "\" + szFile$
    END IF
END FUNCTION


[LISTING TWO]



'-----------------------------------------------------
GETPATH:
    SetSymbolValue "EditTextIn", DEST$
    SetSymbolValue "EditFocus", "END"
'-----------------------------------------------------
GETPATHL1:
    sz$ = UIStartDlg(CUIDLL$, DESTPATH, "FEditDlgProc", APPHELP, HELPPROC$)
    DEST$ = GetSymbolValue("EditTextOut")
    IF sz$ = "CONTINUE" THEN

    IF IsDirWritable(DEST$) = 0 THEN
        GOSUB BADPATH
        GOTO GETPATHL1
    END IF
    UIPop 1
    ELSEIF sz$ = "REACTIVATE" THEN
    GOTO GETPATHL1
    ELSE
    GOTO QUIT
    END IF
'-----------------------------------------------------

BADPATH:
    sz$ = UIStartDlg(CUIDLL$, BADPATH, "FInfo0DlgProc", 0, "")
    IF sz$ = "REACTIVATE" THEN
    GOTO BADPATH
    END IF
    UIPop 1
    RETURN


[LISTING THREE]



#include <windows.h>
#include <ver.h>

VS_VERSION_INFO    VERSIONINFO
FILEVERSION        1,0
PRODUCTVERSION     2,10
FILEFLAGSMASK      VS_FFI_FILEFLAGSMASK
FILEFLAGS         (VS_FF_PRERELEASE | VS_FF_DEBUG)
FILEOS             VOS_DOS_WINDOWS16
FILETYPE           VFT_DLL
FILESUBTYPE        VFT2_UNKNOWN
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
    BLOCK "040904E4"
    BEGIN
        VALUE "CompanyName",      "Your Company Name\0"
        VALUE "FileDescription",  "Your File Description\0"
        VALUE "FileVersion",      "1.0\0"
        VALUE "InternalName",     "Name from .DEF file\0"
        VALUE "LegalCopyright",   "Copyright \251 1993 ...\0"
        VALUE "LegalTrademarks",  "...\0"
        VALUE "ProductName",      "Name of your product\0"
        VALUE "ProductVersion",   "2.1\0"
    END
    END
    BLOCK "VarFileInfo"
    BEGIN
    VALUE "Translation", 0x0409, 1252
    END
END


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.