Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MSI Packages Manager

0.00/5 (No votes)
11 Aug 2004 1  
This article shows how to process one or many MSI packages just by providing a configuration file. It gives also many useful hints and tricks that can be used in other projects.

sample screenshot

Index

Introduction

By reading the article title, we think that I will present the great MSI technology (Windows Installer). In fact, that's not the subject of the article at all. This article presents an easy way to manage any MSI package before, during and after MSI processing. It has no relation with MSI custom actions and different steps in product installation process as we can see in different market products as Visual Studio setup and deployment, InstallShield Studio, Wise Installer etc.

It may happen that we cannot do all things we want in MSI packages with respect to the actions taking place before and after installation. The only thing you need to use immediately in the program presented in this article is a configuration file which I will explain in the next section.

In the same time, the article presents many useful and ready to use hints on:

  • how to create threads in UI programming and disable our UI to be closed (by Atl+F4, for example),
  • how to launch silently Win32 or console programs, and DOS commands with/without parameters,
  • how to destroy a whole directory or to delete only files with some pattern in one or many directories,
  • how to design easily a non deterministic progress bar (see the image above) when we cannot evaluate exactly the operation length in terms of time or count,
  • how to realize a gradient color brush on any rectangle of any control (see the image above) by simply calling one function.

Before I go into details, I have to give credit to some people who have contributed free source in CodeProject, and from them I took many good hints and nice ideas to construct some of the article functions:

  • Patrik Svensson for his article entitled A Gradient Static control from which I took the idea of Gradient fill.
  • Robert Edward Caldecott for his article WTL Button menu Class from which I took the idea of using Marlett font in designing non deterministic progress bar.

The idea of testing if Windows Installer is present by looking into the registry is not mine, and I don't have the author's name. In spite that this method works for all Windows OS (except Windows 95), it is more appropriate, at least for Windows 2000/XP/2003, to test if Windows Installer service is installed on the target system. The fact that the file msiexec.exe is present in System32 directory does not prove at all that Windows Installer is alive.

How to use it

MsiMgr.exe Configuration_File

Where Configuration_File is the configuration file containing the package version and description, log file path, and actions that should be processed by MsiMgr.

Examples:

MsiMgr C:\MyFile.ini
MsiMgr C:\My directory\MyFile.ini

Even if the configuration file path contains spaces, don't add quotes, otherwise it doesn't work. I will explain in details in the next section the way the configuration file should be organized, and the rules should be respected in each of the included lines.

How the program is organized

Configuration file has the .INI file format. The following diagram shows the different sections and the general rule of included lines in each section.

sample screenshot

Each line in the configuration file has the following scheme except the SETTINGS section, in which we indicate the package version, description and logfile path as we will see below:

FILE = [PARAMTERS], VERB, [CONDITION, CONDITION_DATA], [MESSGE_TO_DISPLAY]

Parameters between [] are optional. Of course, the VERB and the FILE are obligatory. If FILE or CONDITION_DATA is a path containing spaces, make sure to enclose the string between two quotes".

This line means:

Apply the verb VERB to FILE+PARAMTERS only if CONDITION applied on CONDITION_DATA is true or if there is no condition, and display MESSGE_TO_DISPLAY during the line process.

Where:

Table 1: Typical line elements table

FILE

is the file to process depending on the verb value

PARAMTERS

Parameters to pass to File during the verb application

VERB

is the verb to process

CONDITION

is the condition

CONDITION_DATA

is the condition data on which condition should be applied

MESSGE_TO_DISPLAY

is the message to display on the UI during the verb execution

The tables below show the values the parameters VERB and CONDITION can have. Note that the VERB applies to FILE, and the CONDITION applies to CONDITION_DATA. Note also that neither the verb values nor the condition values are case sensitive.

Table 2: VERB values table

DelFile

Delete file

DelDir

Delete directory

DelKey

Delete registry key

DelKeyValue

Delete registry key value

ExecFile

Execute file by passing parameters PARAMTERS if any

UnIns

Uninstall MSI package

Ins

Install MSI package

Table 3: CONDITION values table

IF_EXIST

If file/directory exists

IF_NOT_EXIST

If file/directory not exists

IF_KEY_EXIST

If key exists. In this case, the CONDITIONS_DATA should end with backslash sign to differ from a key value.

IF_KEY_NOT_EXIST

If key not exists. In this case, the CONDITIONS_DATA should end with backslash.

IF_KEY_VALUE_EXIST

If key value exists. In this case, the CONDITIONS_DATA should not end with backslash.

IF_KEY_VALUE_NOT_EXIST

If key value not exists.

Here is an example of a configuration file to make the entire concept clearer. It depends on your requirements and imagination to fill the configuration file.

[SETTINGS]
;Package version
Version=1.0.0.1
;Package description
Description=this is install test
;Logs will be generated in C:\Mylog.log
logfile=C:\Mylog.log
;Wait at most 5 minutes for processes execution
ExecTimeout=300000

[PREINSTALL]
;This line will delete directory C:\Test !
c:\Test=,deldir,,,Deleting directory C:\test...
;This line will delete directory C:\Temp only if directory C:\Test1 exist!
C:\Temp\=,delfile,IF_EXIST,C:\Test1, deleting all *.dll in C:\Temp directory...
;This line will delete directory C:\Temp1 
             only if file C:\Test2\MyFlagFile.001 exist!
C:\Temp1=,delfile,IF_EXIST,C:\Test2\MyFlagFile.001, 
         deleting all *.dll in C:\Temp directory...
;This line will unregister the service whose path 
             C:\Windows\system32\MyService.exe if this one exist
C:\Windows\system32\MyService.exe=/Unregister,ExecFile,IF_EXIST,
   C:\Windows\system32\MyService.exe,Unregistring the service Myservice...
;This line will delete the service binary 
             C:\Windows\system32\MyService.exe if this one exist
C:\Windows\system32\MyService.exe=/Unregister,DelFile,IF_EXIST,
   C:\Windows\system32\MyService.exe,deleting the service Myservice...
;This line will launch NotePad (works only on WinXP/2000/2003), 
                for Win9X replace cmd by Command
cmd /C %windir%\notepad.exe=,ExecFile,,,Launching NotePad...

[UNINSTALL]
;This section and the next one apply only to MSI packages
;(that's why we don't need to specify msi engine/service msiexec.exe)
;This line will uninstall the product with code indicated on the left 
;by hiding the cancel button and generating the msi log in C:\MSIlog.log
{125F0499-6759-11D5-A54F-0090278A1BB8}=/QB+! /LC:\MSIlog.log,
                   UnIns,,,Uninsall my product in progress...

[INSTALL]
;Install c:\Packages\MyPackage\MyMsi.msi with the MSI paramter 
                        "ALLUSERS=1 /QB" without any condition  
c:\Packages\MyPackage\MyMsi.msi=ALLUSERS=1 /QB,INS,,,
          Installing MyMsi in prgress. Please wait... 

[POSTINSTALL]
;The same rules apply to this section as those of PREINSTALL section

Note: When you specify as a VERB ExecFile, the execution waits, by default, for the process termination at most the value of ExecTimeout specified in SETTINGS section in milliseconds. This timeout may be adjusted for each package by evaluating, in pessimistic case, the most long operation even if the target machines can have less or more CPU performance.

Using the code

The project is a Win32 MFC application. The main window contains only one child dialog. The remaining parts of the UI around the dialog are just decoration that have been accomplished using some GDI functions that I will explain later.

By making a correspondence between the tables above (Tables 1, 2, and 3 above), and the program source code, they correspond respectively to:

enum VerbFlag {
    DelFile,     //delete file

    DelDir,      //delete directory

    DelKey,      //delete reg key

    DelKeyValue, //delete reg key value

    ExecFile,    //Execute file

    UnIns,       //UnInstall Msi package

    Ins,         //Install Msi package

};

enum ConditionFlag{
    IF_EXIST,                 //If file exist

    IF_NOT_EXIST,             //If file not exist

    IF_KEY_EXIST,             //if key exist

    IF_KEY_NOT_EXIST,         //if key not exist

    IF_KEY_VALUE_EXIST,       //if key value exist

    IF_KEY_VALUE_NOT_EXIST,   //if key value not exist

};

struct InstallStruct {
    CString Data;      //file/dir/registry on which the verb is applied

    CString Parameter; //Parameters execution for Data if anny

    int Verb;          //the verb (see VerbFlag)

    int Flag;          //the flag condition (see ConditionFlag)

    CString FlagFile;  //Flag file/dir/key etc to be tested by the falg condition

    CString Message;   //Message to be displayed

};

The main program thread MainThread responsible for processing all sections is as follows:

//    Program Main thread 

DWORD WINAPI MainThread(LPVOID lp)
{
    DWORD    ExitCode=0;
    BOOL     ret[4];

    for (int i= 0;i<4; i++) ret[i]=TRUE;

    //    We go to next section only if the current

    //    section has returned with success

    ret[0]=PreInstall(hdlg,ExitCode);
    if (ret[0]==TRUE) {
        ret[1]=UnInstallPackage(hdlg,ExitCode);
        if (ret[1]==TRUE) {
            ret[2]=InstallPackage(hdlg,ExitCode);
            if (ret[2]==TRUE)
                ret[3]=PostInstall(hdlg,ExitCode);
        }
    }

    CString Msg;
    if ( (ret[0]&ret[1]&ret[2]&ret[3])!=0) {
        Msg=_T("\nMSI package processing has finished with success.");
        UpdateMessage(hdlg,Msg);
    }
    else {
        Msg=_T("\nThe MSI package processing has failed!");
        UpdateMessage(hdlg,Msg);
        //    Don't call MessageBox with respect to the main window

        //    (the attached main thread), it will not work!

        //    Instead, call MessageBox with respect to the dialog

        MessageBox(hdlg, Msg, TITLE, MB_OK|MB_ICONEXCLAMATION|MB_SYSTEMMODAL);
    }

    WriteToLogFile(LOG_FILE,Msg.GetBuffer(0));Msg.ReleaseBuffer();
    Sleep(2000);

    //    Destroy our unique dialog

    SendMessage(hdlg,WM_DESTROY,0,0);

    //    Tell to the main thread associated with

    //    our frame window that we are done

    DONE=1;

    return ExitCode;
    
}

The decoration around the dialog is made by a call to the function below:

//    Purpose:     Make a filled rectangle by a gradient color

//    hWnd:        Handle to the control on which the gradient will be made

//    FirstColor:  First gradient color

//    SecondColor: Second gradient color

//    TextOut:     Text will be drawn

//    TextColor:   Text color

//    TextFormat:  Text format

//    FontName:    font name used in the text output

//    FontSize:    font size used in the text output

//    Rect:        Rectangle on which the gradient rectangle will take place

//    Direction:   Gradient direction (Vertical:GRADIENT_FILL_RECT_V, 

//                                     Horizontal:GRADIENT_FILL_RECT_H)

//    CleanUp:     Tell that we can free the library msimg32.dll

//            (this should be TRUE only in the last call to this function)

//    For exmaples on how to call this function, see below

void    MakeGradient(HWND hWnd, COLORREF FirstColor, COLORREF SecondColor,
        CString &TextOut, COLORREF TextColor, UINT    TextFormat,
        CString &FontName, int FontSize,
        CRect &Rect, DWORD Direction, BOOL CleanUp)
{
    static HINSTANCE    h_msimg32;
    if (h_msimg32==NULL) h_msimg32= LoadLibrary( "msimg32.dll" );
    
    CDC    dc;
    HDC    hDC=GetWindowDC(hWnd);
    dc.Attach(hDC);

    CFont Font;
    int nHeight = -1-MulDiv(FontSize, GetDeviceCaps(hDC, LOGPIXELSY), 72);
    int nWidth  = -1-MulDiv(FontSize, GetDeviceCaps(hDC, LOGPIXELSX), 72);
    Font.CreateFont(nHeight, nWidth,
        0, 0, FW_DONTCARE, FALSE, FALSE, 0,
        DEFAULT_CHARSET, OUT_CHARACTER_PRECIS,
        CLIP_CHARACTER_PRECIS, ANTIALIASED_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE,
        FontName );

    // Select the newly created font into

    // the device context

    CFont *pOldFont = dc.SelectObject( &Font );


    if (h_msimg32) {
        typedef UINT (CALLBACK* LPFNDLLFUNC1)
               (HDC,CONST PTRIVERTEX,DWORD,
               CONST PVOID,DWORD,DWORD);
        LPFNDLLFUNC1 dllfunc_GradientFill = 
               ((LPFNDLLFUNC1) GetProcAddress( h_msimg32, 
               "GradientFill" ));

        if (Rect.IsRectNull()) {
            GetClientRect(hWnd, &Rect);
            Rect.top-=1;
            Rect.bottom+=2;
            Rect.right+=2;
            Rect.left-=1;
        }

        TRIVERTEX rcVertex[2];

        //    Define vertex array

        rcVertex[0].x=Rect.left;
        rcVertex[0].y=Rect.top;

        // color values from 0x0000 to 0xff00(255)

        rcVertex[0].Red=GetRValue(FirstColor)<<8;

        rcVertex[0].Green=GetGValue(FirstColor)<<8;
        rcVertex[0].Blue=GetBValue(FirstColor)<<8;
        rcVertex[0].Alpha=0x0000;
        rcVertex[1].x=Rect.right;
        rcVertex[1].y=Rect.bottom;
        rcVertex[1].Red=GetRValue(SecondColor)<<8;
        rcVertex[1].Green=GetGValue(SecondColor)<<8;
        rcVertex[1].Blue=GetBValue(SecondColor)<<8;
        rcVertex[1].Alpha=0;

        GRADIENT_RECT rect;
        rect.UpperLeft=0;
        rect.LowerRight=1;


        // fill the area

        dllfunc_GradientFill(dc, rcVertex, 2, &rect, 1, Direction);
        if ( (h_msimg32!=NULL) && (CleanUp==TRUE) ) {
            h_msimg32=NULL;
            FreeLibrary(h_msimg32);
        }
    }

    //    Draw text

    SetTextColor(dc, TextColor);
    SetBkMode(dc, TRANSPARENT);
    DrawText(dc, TextOut, -1, &Rect, TextFormat);

    //    Draw the contour

    Rect.DeflateRect(-1,-1, -1, -1 );
    dc.DrawEdge(&Rect,EDGE_BUMP,BF_RECT);

    //    Select the old font back into the device context.

    dc.SelectObject( pOldFont );
    dc.Detach();

}

To make the progress bar, a timer is set in the InitInstance function. Then the function above is called in the main frame window procedure as a response to WM_TIMER message by using a string table and Marlett font as follows:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
                         WPARAM wParam, LPARAM lParam)
{
    ..............
    static int i;
    .................
    static CString str[22]={
        {"gggg11111111111111111111111111111111111111111111"},
        {"111gggg11111111111111111111111111111111111111111"},
        {"11111gggg111111111111111111111111111111111111111"},
        {"1111111gggg1111111111111111111111111111111111111"},
        {"111111111gggg11111111111111111111111111111111111"},
        {"11111111111gggg111111111111111111111111111111111"},
        {"1111111111111gggg1111111111111111111111111111111"},
        {"111111111111111gggg11111111111111111111111111111"},
        {"11111111111111111gggg111111111111111111111111111"},
        {"1111111111111111111gggg1111111111111111111111111"},
        {"111111111111111111111gggg11111111111111111111111"},
        {"11111111111111111111111gggg111111111111111111111"},
        {"1111111111111111111111111gggg1111111111111111111"},
        {"111111111111111111111111111gggg11111111111111111"},
        {"11111111111111111111111111111gggg111111111111111"},
        {"1111111111111111111111111111111gggg1111111111111"},
        {"111111111111111111111111111111111gggg11111111111"},
        {"11111111111111111111111111111111111gggg111111111"},
        {"1111111111111111111111111111111111111gggg1111111"},
        {"111111111111111111111111111111111111111gggg11111"},
        {"11111111111111111111111111111111111111111gggg111"},
        {"1111111111111111111111111111111111111111111gggg1"}
    };

    CRect        rect;

    switch (message)
    {
        ..................
        case WM_TIMER:
            // If DONE then Kill our timer and exit by destroying our window

            if (DONE==1) {
                KillTimer(hWnd, 1);
                DestroyWindow(hWnd);
                return 0;
            }

            //    Progress animation is done here

            GetClientRect(GetDlgItem(hdlg,IDC_STATIC_PROGRESS),&rect);
            MakeGradient(GetDlgItem(hdlg,IDC_STATIC_PROGRESS), 
                 RGB(128, 128, 128), RGB(255, 255, 255),
                 str[i], RGB(119, 60, 0), 
                 DT_CENTER|DT_VCENTER, CString("Marlett"),
                 10, rect, GRADIENT_FILL_RECT_H,FALSE);
            i+=1;
            i=i%18;

            //    Create our unique thread as long as static

            //    variable thread handle h is null

            //    It sould be done only once

            if (h==NULL)
                h=CreateThread(NULL,0,MainThread, &hWnd, NULL,&ID);

            break;
        .............
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
  
  
   return 0;
}

How the program processes each line in each section?

The lines are processed in the same order as they are in the configuration file. Each line at one time is represented by a global variable.

InstallStruct Install; //Global Install variable

and it is processed by following the steps:

  1. Get the FILE which will be stored in Install.Data.
  2. Get the Data value (right-hand of =), say strArr, of type CStringArray.
  3. Split strArr into Parameter, Verb, Flag (CONDITION), FlagFile (CONDITION_DATA), and Message by calling the function:
    // Split string str and return the slices in the CString array SplitArray
    
    // This function works even if there is nothing beteween separators such
    
    // as if str="toto,tata,,,mama,,"
    
    void SplitString(CString &str, char Sep, CStringArray &SplitArray)
  4. Get VERB, CONDITION, CONDITION_DATA, and MESSGE_TO_DISPLAY values. Since the VERB and CONDITION can have different values, two functions are used to get their values:
    int GetVerb(CStringArray strArr)
    int GetCondition(CStringArray strArr)
  5. Call the function corresponding to the current section as indicated in the table:
SECTION Corresponding function
PREINSTALL BOOL PreInstall(HWND hWnd, DWORD &ExitCode)
UNINSTALL BOOL UnInstallPackage(HWND hWnd, DWORD &ExitCode)
INSTALL BOOL InstallPackage(HWND hWnd, DWORD &ExitCode)
POSTINSTALL BOOL PostInstall(HWND hWnd, DWORD &ExitCode)

I will not explain how these functions work, but as I mentioned in the Introduction, I will give some intermediate functions used indirectly, in the next section. These functions may be used in any program to achieve common tasks.

How to's

  • How to create threads in UI programming and disable our UI to be closed normally (by Atl+F4 for example):
    ATOM MyRegisterClass(HINSTANCE hInstance) 
    { 
      WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX);
      wcex.style = CS_NOCLOSE; //This prevent the use of Alt+F4 ...........
    
    }

    and see the thread creation MainThread above.

  • How to launch silently Windows or console programs and DOS commands with/without parameters:
    //////////////////////////////////////////////////////////////
    
    // Purpose:
    
    //   -Create a process with module name: ModuleName, 
    
    //                          command line: CommandLine 
    
    //   -and wait for the end of the process or not: WaitOrNot 
    
    //   -You can fix a TimeOut (milliseconds),
    
    //       otherwise pass -1 (INFINITE)
    
    //   -You can specify if the UI associeted
    
    //       to the process will be visible 
    
    //       by passing Show parameter
    
    //       (TRUE:Visible, FALSE:Not visible)
    
    //   -[OUT] ExitCode is the process exit code (very usefull)
    
    // Returns:    creation status (TRUE or FALSE)
    
    //////////////////////////////////////////////////////////////
    
    BOOL    CreateProc(char *ModuleName, char *CommandLine, BOOL WaitOrNot, 
            DWORD TimeOut, BOOL Show, DWORD &ExitCode)
    {
        STARTUPINFO        startup_info;
        PROCESS_INFORMATION    process_info;
        BOOL        status;
        DWORD        dwexitcode = 0;
    
        if ( strcmp(CommandLine, "")==0 && strcmp(ModuleName, "")==0 )
            return TRUE;
    
        ZeroMemory(&startup_info, sizeof(STARTUPINFO));
        startup_info.cb        = sizeof ( startup_info );
        startup_info.dwFlags    = STARTF_USESHOWWINDOW; 
        startup_info.wShowWindow    = SW_SHOWNORMAL?(Show==TRUE):SW_HIDE;
    
        status = CreateProcess((LPTSTR)
                (strcmp(ModuleName,"")==0)?NULL:ModuleName, 
                (LPTSTR)CommandLine, 
                NULL, NULL, FALSE, 
                0 , NULL, NULL,
                &startup_info, &process_info );
    
        if ( status ) {
            if ( WaitOrNot ) 
                if (TimeOut>0)
                  WaitForSingleObject ( process_info.hProcess, TimeOut );
                else 
                  WaitForSingleObject ( process_info.hProcess, INFINITE );
        }
    
        if (GetExitCodeProcess(process_info.hProcess, &dwexitcode) != 0) {
            ExitCode = dwexitcode;
            CloseHandle ( process_info.hThread );
            CloseHandle ( process_info.hProcess );
        }
    
        if (status == 0 )    return    FALSE; 
        else return    TRUE;
    }

    Example: If you want to execute a DOS command or a console program silently, do the following:

    //Get Command prompt (...\cmd.exe for WINNT/XP/2002/2003 
    
    //and Command.com for Win9X/ME)
    
    char *Cmd=getenv("COMSPEC");
    char cmdline[255]; 
    sprintf(cmdline,"%s /C Dir *.*/s>C:\\Out.txt",Cmd);
    DWORD ExitCode=0;
    //This line will produce the output file C:\Out.txt 
    
    //with all files in the current directory and all sub-directorie
    
    BOOL ret=CreateProc("", cmdline, TRUE /*Wait*/, 
            10000 /*10 seconds as wait 
            timeout*/, FALSE /*Hide console window*/, ExitCode);
    //...Proceed the program sequence 
    
    //with respect to ret and ExitCode
    
    //Open now with NotePad our file C:\Out.txt    
    
     char
    Windir[MAX_PATH];
    if (GetWindowsDirectory(Windir,MAX_PATH)>0){
        char modName[MAX_PATH];
        sprintf(modName,"%s\\Notepad.exe",Windir);
        sprintf(cmdline,"%s C:\\Out.txt",modName);
        ret=CreateProc("", cmdline, FALSE /*don't wait*/, 
                0 /*put any int number*/, 
                TRUE /*Show NotePad*/, ExitCode);
    }
  • How to destroy a whole directory or to delete only files with some pattern, in one or many directories:

    CAUTION: Beware! Using the functions below without paying attention can be dangerous for your data. If you want to test them, specify a directory with non pertinent data.

    Deleting a directory and optionally sub-directories and root directory can be done by simply calling the function:

    /////////////////////////////////////////////////////////////
    
    //    Purpose:    delete all directory files and optionally
    
    //              subdirectories by recursive method
    
    //    Parameters:
    
    //      TheDir        : the directory to empty
    
    //      DelSubDirs    : tell to delete sub-directories or not
    
    //      DelRootDir    : tell to delete root (TheDir) directory or not
    
    //    Return:        TRUE if success
    
    /////////////////////////////////////////////////////////////
    
    BOOL    ClearDirEx( CString &TheDir, BOOL DelSubDirs, BOOL DelRootDir)
    {
        CString Pattern="*.*";
        BOOL ret= ClearDir(TheDir.GetBuffer(MAX_PATH), 
                  DelSubDirs, DelRootDir, Pattern.GetBuffer(3));
        TheDir.ReleaseBuffer();
        Pattern.ReleaseBuffer();
        return ret;
    }

    Deleting files having some extension in a directory and optionally in all sub-directories can be done by calling the function:

    ////////////////////////////////////////////////////////////
    
    //    Purpose:    delete all files with extension Pattern 
    
    //                and optionally include allsubdirectories
    
    //    Parameters:
    
    //      TheDir  : the basic directory where where files are to be deleted
    
    //      Pattern : delete only files with this extension pattern
    
    //      IncludeSubDirs : tell to delete all sub-directory files too
    
    //    Return:        TRUE if success
    
    ////////////////////////////////////////////////////////////
    
    BOOL    DeleteFilesEx( CString &TheDir, 
            CString &Pattern, BOOL IncludeSubDirs)
    {
        BOOL ret= ClearDir(TheDir.GetBuffer(MAX_PATH), 
                  IncludeSubDirs, FALSE, Pattern.GetBuffer(3));
        TheDir.ReleaseBuffer();
        Pattern.ReleaseBuffer();
        return ret;
    }

    The common function called in the two functions above in different ways is:

    BOOL ClearDir(char *TheDir, BOOL DelSubDirs, BOOL DelRootDir, char *Pattern)
    {
        BOOL            bFound=TRUE;
        WIN32_FIND_DATA FileData;
        HANDLE            hSearch;
        static char        *CurDir=TheDir;
        char            TempDir[MAX_PATH];
    
    
        if (TheDir==NULL)    return TRUE;
    
        //    Of course, if we want to delete root directory then
    
        //    all sub-directories should be deleted too
    
        if (DelRootDir==TRUE) DelSubDirs=TRUE;
    
        sprintf(TempDir, TheDir);
        strcat(TempDir,"\\");
        strcat(TempDir, "*.*");
        static char *revPat=_strrev(Pattern);
    
        hSearch = FindFirstFile((LPCTSTR)TempDir, &FileData);
    
        while ( bFound ) {
            if (hSearch != INVALID_HANDLE_VALUE) {
                GetFileAttributes( (LPCTSTR)FileData.cFileName );
    
                if ( FileData.dwFileAttributes & 
                   FILE_ATTRIBUTE_DIRECTORY ) { //Directory data
                 if ( strcmp((char*)FileData.cFileName, "." )!=0 &&
                   strcmp((char*)FileData.cFileName,"..")!=0 ) 
                 { //There is some more files
    
                    sprintf(TempDir, TheDir);
                    strcat(TempDir, "\\");
                    strcat(TempDir,(char*)FileData.cFileName);
                    if ( ClearDir(TempDir, DelSubDirs, 
                            DelRootDir, revPat)==FALSE)
                       return FALSE;
                 }
                } else {    //file data
                   if ( SetCurrentDirectory(TheDir)!=0 ) {
                     if (SetFileAttributes((LPCTSTR)FileData.cFileName,
                        FILE_ATTRIBUTE_ARCHIVE)!=0 &&
                        SetFileAttributes((LPCTSTR)FileData.cFileName,
                        FILE_ATTRIBUTE_NORMAL)!=0 ) {
                          if (strcmp(Pattern,"*.*")==0) {
                            if ( DeleteFile((LPCTSTR)FileData.cFileName)==0)
                              // cannot delete file, return FALSE
                              return FALSE;
                          }
                          else {
                            char *revFile=_strrev(FileData.cFileName);
                            if (_strnicmp(revFile,revPat,strlen(Pattern))==0){
                              revFile=_strrev(FileData.cFileName);
                              if ( DeleteFile((LPCTSTR)revFile)==0)
                                return FALSE;
                            }
                          }
                     }
                   }
                }
                bFound=FindNextFile(hSearch, &FileData);
            } else return TRUE;
        }
    
    
        FindClose(hSearch);
        SetCurrentDirectory("\\");
        if ( (DelRootDir==TRUE) && (stricmp(TheDir,CurDir)==0) ) {
            //    We cannot remove local or network drive !!!
            if ( strlen(TheDir)>2 && IsDriveLetter(TheDir)==FALSE ) {
                if ( RemoveDirectory(TheDir)==0)
                    return FALSE;
            } else return TRUE;
        }
        else if ( (stricmp(Pattern,"*.*")==0) && (DelSubDirs==TRUE) 
                                 && (stricmp(TheDir,CurDir)!=0) ) {
            if ( RemoveDirectory(TheDir)==0)
                return FALSE;
        }
    
        return TRUE;
    }
  • How to design easily a non deterministic progress bar (see the image above) when we cannot evaluate exactly the operation length in terms of time or count:

    The answer to this question consists of the following steps:

    1. Put on your dialog a Static control, for example. (It can be, in fact, another kind of control like Edit Box or Button, but the Static control is more appropriate.) This will be our progress bar.
    2. At the beginning of the long task (long copy, search etc.), set a timer by specifying a timer procedure if you don't already have a global window procedure related to your UI.
    3. In the timer procedure or the global one (in the WM_TIMER message handling), make a call to the function responsible for the progress bar update such as calling the function: MakeGradient(...). In this article, the call is made in the WM_TIMER message handling. The trick to display something like a progress bar is the Marlett font for this control. I have used the digit 1 (1) for the empty squares, and the g (g) character for the filled squares, moving towards in a cyclic time. In fact, you can display anything you want in other existing fonts on the target machine too; the basic idea remains the same. The common length of our strings in the table (str in the above code) can be fixed dynamically with respect to the control width and in order to almost hold the whole string.
    4. Once the operation has reached its end, kill the timer.
  • How to realize a gradient color brush (Alpha-blend) on any rectangle of any control (see the image above) by simply calling one function:

    As indicated before, all the decoration around the main frame window and the progress bar painting is done by a call to one function, namely MakeGradient(...) with many parameters to personalize the appearance of the control. This function can be called on any window (Static, button, Edit etc.) by providing a good handle to the window to get a device context handle, a rectangle on which the gradient can be done, and other information as colors, text, font name, font size etc. It's up to your imagination to use this function in your projects. Note that this function works only on Windows 98/2000 and up, since the Microsoft library msimg32.dll (Extension component for Windows GDI) is not available on Windows 95 and NT 4.0. I am at least sure that it works on Win 98/2000/XP. To see details about functions that can be called from this library, you can examine the header file WINGDI.H, they are prototyped there.

Conclusion

This article has shown how we can drive install/uninstall one or more MSI packages with all operations that can be done before, during, and after, by just providing a configuration file. Feel free to make any suggestion, criticism, or improvement.

History

Initial version 1.0.0.1 - August 8, 2004.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here