

Listing 1
/*****************************************************/
/* install.c                                         */
/* -- Copy the install executable to a hard disk     */
/*    with enough free space.                        */
/* -- Then fork install and exit.                    */
/*****************************************************/

/*****************************************************/
/* Header files.                                     */
/*****************************************************/
#include <windows.h>
#include <io.h>

/*****************************************************/
/* Constants.                                        */
/*****************************************************/
/* Maximum length of a DOS path. */
#define cbPathMax   128

/* Control ID of StaticText that replaces */
/* PushButton. */
#define didInit     0x1000

/* Replace with the name of your "real" install */
/* program. */
#define szInstall   "realinst.exe"

/* (Optional) name of the installation script file. */
#define szScript     "setup.inf"

/*****************************************************/
/* Globals.                                          */
/*****************************************************/
/* Put the name of your application here. */
char    szCaption[] = "MyApp Installation";

/* Call name for the main window. */
char    szClass[]   = "HcInst20";

char    szHello[]   =   /* Initial message to user. */
    "Welcome to the MyApp installation program.  This"
    " program will install MyApp onto your computer."
    "  Please wait while the installation process"
    " begins.";

BOOL    fProceed;   /* Has USER hit Proceed button? */
HWND    hwndMain;   /* Main window handle. */

/*****************************************************/
/* Prototypes.                                       */
/*****************************************************/
/* Main window procedure. */
LONG   CALLBACK AppWndProc(HWND, unsigned, unsigned,
                           LONG);

/* Remove the filename from a path. */
VOID            GetBaseName(PSTR);

/* Make a copy of a file. */
BOOL            FCopyFile(PSTR, PSTR);

/* Get and process a messge and dispatch it. */
BOOL            FGetMessage(VOID);

/* Invoke our hand crafted dialog. */
BOOL            FPseudoDialog(HANDLE);

/*****************************************************/
/* Routines.                                         */
/*****************************************************/

int FAR PASCAL
WinMain(HANDLE hins, HANDLE hinsPrev,
  LPSTR lszCommand, int wShowWindow)
/*****************************************************/
/* -- Entry point.                                   */
/*****************************************************/
    {
    /* Required buffer for OpenFile. */
    OFSTRUCT    ofs;

    /* Name of install executable on floppy. */
    char        szSrc[cbPathMax];

    /* Name of install executable on hard disk. */
    char        szDest[cbPathMax];

    /* Name of information file (optional). */
    char        szInf[cbPathMax];

    /* Install program invokation line. */
    char        szExec[cbPathMax * 2 + 1];

    /* Hard drive to install to. */
    BYTE        chDrive;

    /* Describes our main window class. */
    WNDCLASS    wcs;

    /* Make sure there is not another instance running. */
    if (hinsPrev != NULL)
        {
        MessageBox(NULL,
          "MyApp installation is already running.",
          szCaption, MB_OK | MB_ICONSTOP);
        return FALSE;
        }

    /* Register a class for the main window. */
    wcs.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcs.hIcon = NULL;
    wcs.lpszMenuName = NULL;
    wcs.lpszClassName = szClass;
    wcs.hbrBackground = COLOR_WINDOW + 1;
    wcs.hInstance = hins;
    wcs.style = CS_SAVEBITS;
    wcs.lpfnWndProc = AppWndProc;
    wcs.cbWndExtra = 0;
    wcs.cbClsExtra = 0;

    if (!RegisterClass(&wcs))
        return FALSE;

    /* Create the greeting dialog (our main window). */
    if (!FPseudoDialog(hins))
        {
        MessageBox(NULL, "Out of memory.", szCaption,
          MB_OK | MB_ICONSTOP | MB_SYSTEMMODAL);
        goto WinMainExit;
        }

    /* Give other apps a chance to paint. */
    if (!FGetMessage())
        goto WinMainExit;

    /* Greet the user. */
    ShowWindow(hwndMain, wShowWindow);
    UpdateWindow(hwndMain);

    /* Find out where we are being installed from. */
    /* Easiest way is to ask for name of this module */
    /* since Windows will return a full path. */
    GetModuleFileName(hins, szSrc, sizeof szSrc);
    GetBaseName(szSrc);

    /* Create a path to the real install program. */
    lstrcat(szSrc, szInstall);

    /* Find a fixed disk to temporarilly hold a */
    /* copy of the real install program. */
    if ((chDrive = GetTempDrive(0)) == 'A' ||
      chDrive == 'B')
        {
        MessageBox(NULL,
          "You must have a network or fixed disk to"
          " install MyApp.", szCaption,
          MB_OK | MB_ICONSTOP);
        goto WinMainExit;
        }

    /* Find a temp directory on the hard disk, and */
    /* copy the real install program there. */
    GetTempFileName(chDrive, "HYP", 0, szDest);
    if (!FCopyFile(szSrc, szDest))
        goto WinMainExit;

    /* Optional.  Copy an installation script file */
    /* to same temp directory on the hard disk. */
    GetBaseName(szSrc);
    lstrcat(szSrc, szScript);
    GetTempFileName(chDrive, "XYZ", 0, szInf);
    if (!FCopyFile(szSrc, szInf))
        goto WinMainExit;

    /* Give the user a chance to read the greeting */
    /* before invoking the real install program and */
    /* hiding the greeting.  We require the user to */
    /* hit Proceed to inform us he is ready. */
    while (!fProceed)
        if (!FGetMessage())
            goto WinMainExit;

    /* Remove the greeting window, but don't destroy */
    /* it, since it is needed to perform process */
    /* control. */
    ShowWindow(hwndMain, SW_HIDE);

    /* Create a buffer to hold the path to the real */
    /* install program on the hard disk, and its */
    /* command-line arguments.  We supply the path */
    /* to the disk/directory holding the files to */ 
    /* install, and the full path of the install */
    /* script on the hard disk. */
    GetBaseName(szSrc);
    wsprintf(szExec, "%s %s %s",
      (LPSTR)szDest, (LPSTR)szSrc, (LPSTR)szInf);

    /* Flush all discardable segments, just in case */
    /* there is an open file handle for one of them. */
    /* This is actually a pretty paranoid thing to */
    /* do, but paranoia is an asset here! */
    GlobalCompact(GlobalCompact(0));

    /* Invoke the real install program. */
    if (WinExec(szExec, wShowWindow) < 32)
        {
        /* This is bad. */
        MessageBox(NULL,
          "Fatal error encountered while installing",
          szCaption, MB_OK | MB_ICONSTOP);
        goto WinMainExit;
        }

    /* Wait around until the real install program is */
    /* done.  We can tell when it dies by polling */
    /* for its module handle. */
    for (;;)
        {
        if (!FGetMessage())
            goto WinMainExit;

        if (GetModuleHandle(szDest) == NULL)
            break;
        }

    /* More paranoia here.  Make sure we yield to the */
    /* dying app just in case its not completely dead */
    /* yet. */
    FGetMessage();

WinMainExit:
    /* Clean up.  Get rid of the temporary files on */
    /* the hard disk and kill the main window. */
    if (OpenFile(szDest, &ofs, OF_EXIST) != -1)
        unlink(szDest);

    if (OpenFile(szInf, &ofs, OF_EXIST) != -1)
        unlink(szInf);

    if (hwndMain != NULL)
        DestroyWindow(hwndMain);
    }

BOOL
FGetMessage(VOID)
/*****************************************************/
/* -- Extract and process a message.                 */
/* -- Return false if it is the quit message.        */
/*****************************************************/
    {
    MSG msg;

    if (hwndMain == NULL)
        return FALSE;   /* No receiver! */

    if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        return TRUE;    /* No oustanding events. */

    if (msg.message == WM_QUIT)
        return FALSE;

    /* Even though we don't have a dialog, we can */
    /* still use IsDialogMessage() to translate */
    /* keyboard input for navigating controls. */
    if (IsDialogMessage(hwndMain, &msg))
        return TRUE;

    TranslateMessage(&msg);
    DispatchMessage(&msg);
    return TRUE;
    }

BOOL
FCopyFile(PSTR szSrc, PSTR szDest)
/*****************************************************/
/* -- Copy source file to destination.               */
/* -- Get messages during the copy, so system can    */
/*    still respond to user.                         */
/*****************************************************/
    {
    int         nfdSrc  = -1;
    int         nfdDest = -1;
    char        szT[cbPathMax];
    BOOL        fVal    = FALSE;
    OFSTRUCT    ofs;

    /* Open the source file. */
    if ((nfdSrc = OpenFile(szSrc, &ofs, OF_READ)) ==
      -1)
        {
        wsprintf(szT,
          "Unable to open installation file %s",
          (LPSTR)szSrc);
        MessageBox(NULL, szT, szCaption,
          MB_OK | MB_ICONSTOP);
        goto FCopyFileExit;
        }

    /* Create the target file. */
    if ((nfdDest = OpenFile(szDest, &ofs, OF_CREATE))
      == -1)
        {
        wsprintf(szT,
          "Error creating installation file %s",
          (LPSTR)szDest);
        MessageBox(NULL, szT, szCaption,
          MB_OK | MB_ICONSTOP);
        goto FCopyFileExit;
        }

    /* Copy source to destination. */
    for (;;)
        {
        int     cb;         /* # bytes copied. */
        BYTE    rgb[1024];  /* Copy buffer. */

        if (!FGetMessage())
            goto FCopyFileExit;

        if ((cb = (int)_lread(nfdSrc, rgb,
            sizeof rgb)) > 0 &&
          (int)_lwrite(nfdDest, rgb, cb) != cb)
            {
            wsprintf(szT,
              "Insufficient disk space available for"
              " installation file %s", (LPSTR)szDest);
            MessageBox(NULL, szT, szCaption,
              MB_OK | MB_ICONSTOP);
            goto FCopyFileExit;
            }

        /* Have we copied it all? */
        if (cb == 0)
            {
            fVal = TRUE;
            break;
            }

        if (cb < 0)
            {
            wsprintf(szT,
              "Error reading installation file %s",
              (LPSTR)szSrc);
            MessageBox(NULL, szT, szCaption,
              MB_OK | MB_ICONSTOP);
            goto FCopyFileExit;
            }
        }

FCopyFileExit:
    /* Clean up.  Close open file handles. */
    if (nfdSrc != -1)
        _lclose(nfdSrc);
    if (nfdDest != -1)
        _lclose(nfdDest);
    return fVal;
    }

VOID
GetBaseName(PSTR sz)
/*****************************************************/
/* -- Strip a file name (if any) from a path.        */
/*****************************************************/
    {
    PSTR    pch;

    if (!sz || sz[0] == '\0')
        return;

    /* Start at end of string and work to the front. */
    /* We are done when first delimeter is met. */
    for (pch = sz + lstrlen(sz) - 1; pch >= sz; pch--)
        if (*pch == ':' || *pch == '/' || *pch == '\\')
            break;

    pch[1] = '\0';  /* Null terminate. */
    }

BOOL
FPseudoDialog(HANDLE hins)
/*****************************************************/
/* -- Create something that looks like a dialog.     */
/* -- The dialog has a mutli-line StaticText         */
/*    message, and Proceed and Cancel PushButtons.   */
/* -- It also has a StaticText to replace the        */
/*    PushButtons if the user pushed Proceed, to let */
/*    him know something is happening.               */
/* -- We can't use a real dialog else Windows will   */
/*    leave this .exe open and we hit the copy       */
/*    problem.                                       */
/*****************************************************/
    {
    HDC     hdc;        /* Output surface. */
    RECT    rect;       /* Text output rectangle. */
    RECT    rectWindow; /* Dialog window rectangle. */
    BOOL    fOk;        /* Error free? */
    LONG    l;          /* System font unit cell. */
    int     yButton;    /* Button top location. */
    int     dxScreen, dyScreen; /* Size of screen. */
    int     dxChar, dyChar;     /* System font size. */
    int     dxButton, dyButton; /* PushButton size. */
    int     dxClient, dyClient; /* Client area size. */
    int     dxBorder, dyBorder; /* Border size. */

    if ((hdc = GetDC(NULL)) == NULL)
        goto FPseudoDialogExit;

    fOk = FALSE;    /* Guilty 'till proven innocent. */

    /* Find the dimensions of the system font.  This */
    /* method is easier than GetTextMetrics(). */
    l = GetDialogBaseUnits();
    dxChar = LOWORD(l);
    dyChar = HIWORD(l);

    /* Find dimensions of screen, so we can center */
    /* dialog. */
    dxScreen = GetSystemMetrics(SM_CXSCREEN);
    dyScreen = GetSystemMetrics(SM_CYSCREEN);

    /* Make client area half the width of the */
    /* screen. */
    dxClient = dxScreen / 2;

    /* Compute the size of the client area based on */
    /* the length of the text and the client width, */
    /* plus space for a pair of buttons at the */
    /* bottom. */
    dyButton = (dyChar * 14) / 8;

    /* Given the initial width of the client area, */
    /* determine the height to accomodate all the */
    /* text to display. */
    rect.left = 0;
    rect.right = dxClient;
    rect.top = 0;
    rect.bottom = dyScreen;
    DrawText(hdc, szHello, -1, &rect,
        DT_CALCRECT | DT_WORDBREAK);
    /* Rect now contains the size of the StaticText */
    /* that will display the greeting. */

    /* Adjust the client rect to have a white space */
    /* one character wide around the text.  Adjust */
    /* the height to accomodate the PushButtons. */
    dxClient = dxChar * 2 + rect.right - rect.left;
    dyClient = rect.bottom + (dyChar * 2) + dyButton;

    /* Postion the client area, then inflate to */
    /* accomodate the border of the main window. */
    rectWindow.left = (dxScreen - dxClient) / 2;
    rectWindow.right = rectWindow.left + dxClient;
    rectWindow.top = (dyScreen - dyClient) / 2;
    rectWindow.bottom = rectWindow.top + dyClient;
    dxBorder = GetSystemMetrics(SM_CXDLGFRAME);
    dyBorder = GetSystemMetrics(SM_CYDLGFRAME);
    rectWindow.left -= dxBorder;
    rectWindow.right += dxBorder;
    rectWindow.top -= dyBorder;
    rectWindow.bottom += dyBorder;

    /* Create the main "dialog" window. */
    if ((hwndMain = CreateWindow(
      szClass,
      NULL,
      WS_DLGFRAME | WS_POPUP,
      rectWindow.left,
      rectWindow.top,
      rectWindow.right - rectWindow.left,
      rectWindow.bottom - rectWindow.top,
      NULL,
      NULL,
      hins,
      NULL)) == NULL)
        goto FPseudoDialogExit;

    /* Create the multi-line StaticText to hold the */
    /* greeting. */
    if (CreateWindow(
      "static",
      szHello,
      WS_CHILD | WS_VISIBLE,
      dxChar,
      dyChar / 2,
      rect.right,
      rect.bottom,
      hwndMain,
      -1,
      hins,
      NULL) == NULL)
        goto FPseudoDialogExit;

    /* Size the PushButtons to be the width of the */
    /* text, but allowing a one character wide gap */
    /* between them.  Postion them one character */
    /* below the multi-line StaticText. */
    dxButton = (rect.right / 2) - dxChar;
    yButton = rect.bottom + (dyChar * 3) / 2;

    /* Create the PushButtons. */
    if (CreateWindow(
      "button",
      "&Proceed",
      WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        BS_DEFPUSHBUTTON,
      dxChar,
      yButton,
      dxButton,
      dyButton,
      hwndMain,
      IDOK,
      hins,
      NULL) == NULL)
        goto FPseudoDialogExit;

    if (CreateWindow(
      "button",
      "&Cancel",
      WS_CHILD | WS_VISIBLE | WS_TABSTOP |
        BS_PUSHBUTTON,
      dxButton + (3 * dxChar),
      yButton,
      dxButton,
      dyButton,
      hwndMain,
      IDCANCEL,
      hins,
      NULL) == NULL)
        goto FPseudoDialogExit;

    /* Create the StaticText to replace the */
    /* PushButtons if the user hits Proceed. */
    if (CreateWindow(
      "static",
      "Initializing...",
      WS_CHILD | SS_CENTER,
      dxChar,
      yButton,
      (dxButton + dxChar) * 2,
      dyButton,
      hwndMain,
      didInit,
      hins,
      NULL) == NULL)
        goto FPseudoDialogExit;

    fOk = TRUE;

FPseudoDialogExit:
    /* Clean up. */
    if (!fOk && hwndMain != NULL)
        {
        /* There was an error, so get rid of main */
        /* window. */
        DestroyWindow(hwndMain);
        hwndMain = NULL;
        }

    if (hdc != NULL)
        ReleaseDC(NULL, hdc);

    return fOk;
    }

LONG FAR PASCAL
AppWndProc(HWND hwnd, unsigned wm, unsigned wParam,
  LONG lParam)
/*****************************************************/
/* -- Pseudo dialog's window proc.                   */
/*****************************************************/
    {
    switch (wm)
        {
    default:
        break;

    case WM_SETFOCUS:
        /* Set focus to Proceed button if main */
        /* window gets focus. */
        SetFocus(GetDlgItem(hwnd, IDOK));
        break;

    case WM_COMMAND:
        switch (wParam)
            {
        default:
            return
              DefWindowProc(hwnd, wm, wParam, lParam);

        case IDOK:
            fProceed = TRUE;

            /* User hit proceed.  Replace */
            /* PushButtons with StaticText so he */
            /* knows something is happenning. */
            ShowWindow(GetDlgItem(hwnd, IDOK), SW_HIDE);
            ShowWindow(GetDlgItem(hwnd, IDCANCEL),
              SW_HIDE);
            ShowWindow(GetDlgItem(hwnd, didInit),
              SW_SHOWNA);

            /* Change class cursor to hourglass, so */
            /* it will show only this app is busy. */
            SetClassWord(hwnd, GCW_HCURSOR,
              LoadCursor(NULL, IDC_WAIT));
            break;

        case IDCANCEL:
            DestroyWindow(hwnd);
            break;
            }   /* End switch wParam. */
    return 0L;  /* End case WM_COMMAND. */

    case WM_DESTROY:
        hwndMain = NULL;
        break;
        }       /* End switch wm. */

    return DefWindowProc(hwnd, wm, wParam, lParam);
    }


