MAKEMSI quickly and reliably creates MSI files in a non-programmatic way
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
[Bottom][Contents][Prev]: DllCa[Next]: /DllCa-C
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
\->Source Code->Commands->DllCa-C

The "DllCa-C" Command

This command creates a DLL based custom action from C (or C++) based source code.

This command will (takes about a second on my computer):

  1. Provide some standard routines that you can use.
  2. Compile the source (while adding some msi logging).
  3. Link the objects into a DLL.
  4. Add the created DLL to the "Binary" table.
  5. Optionally compress the generated DLL (with "UPX.DLL").


  6. Takes care of the entry point decorators.
  7. Creates a batch file which you can rerun to quickly allow you to test fixes (if the DLL build fails) without going though the whole MAKEMSI process and causing delays.

The "/DllCa-C" command is used to mark the end of the source code and your code defines any executable code in functions, each functions code is specified between DllCaEntry and /DllCaEntry commands. The entry and exit is automatically logged for you.

C++ code will be much larger and I have not tested it, let me know how it goes.

A small complete custom action DLL follows:

<$DllCa-C Binary="Pause.dll">
    //============================================================================
    <$DllCaEntry "Pause">
    //============================================================================
    {
        //--- Never use a message box like this in production code (prevents silent install) ---
        MessageBox(NULL, "Paused at your request (can be useful for debugging)", "Paused: <$ProdInfo.ProductName> (<$ProductVersion>)", MB_OK);

        //--- Return successful to Windows Installer -------------------------
        return(0);
    }
    <$/DllCaEntry>
<$/DllCa-C>

The code above created a DLL containing the custom action, you would typically use one or more "DllCa" commands to schedule each entry point (indicate when and under what conditions it should be executed) as shown below:

;--- Now call the entry point where and when desired ------------------------
<$DllCa Binary="Pause.dll" Seq="CostFinalize-" Entry=^<$DllCaEntry? "Pause">^ Type="Immediate" Condition="<$DLLCA_CONDITION_INSTALL_ONLY>">

As per the example above, it is highly recommended that you always use the "DllCaEntry?" command to refer to the DLL entry points.

Parameters

This command takes these parameters:

If you have problems compiling your source have a look in the "log" directory, it will have all the source and output files (including intermediate) as well as the redirected output. The directory also contains a btach file which you can double click on to try again without having to invoke the MAKEMSI build again. This will allow you to experiment to determine the cause of any issues (just don't forget that these files are cleared on every build).

DEBUGGING

The full C/C++ source and all intermediate files as well as a batch file as a debugging aid have been generated in the "log" directory (location configured with the "MAKEMSI_DLLCA-C_DIR" macro).

It is typically easier and faster to play with the files in this directory until you determine the issue (change the source code and use the batch file to build). Then duplicate any "fixes" in the MAKEMSI source.

CONFIGURATION

This command requires some configuration. The command tries to detect incorrect configuration and produce a meaningful message if its not correct:

You are required to have set up:

  1. You need to install a number of free MinGw packages. These are the names I installed (not sure if I needed all of these for a "minimal" installation):

    The above list does not include the "c++" "front end" you'd require to even begin playing with C++.

    The install process is basically to unpack everything into a common directory (which I suggest be "c:\MINGW").

  2. If you haven't put the "MinGw" "bin" directory into the "PATH" environment variable then you must set the "MAKEMSI_MINGW" environment variable.

  3. The free Windows Installer SDK as it provides the "MSI" headers ("MSIQUERY.H" etc) and library files required.

  4. The "INSTALLER_SDK_DIR" macro defines where the SDK you wish to use is located.

    Prior to Windows Installer 4.5 SDK, it would have been enough just to use this SDK and not need the complete platform sdk (for the purposes of this command), however it is no longer self contained (for example it refers to the platform sdk file "specstrings.h").

ALTERNATIVE COMPILER TOOLS

I suggest you start with "MinGw" to prove thing are "working" and then play with the generated batch file (in the "LOG" directory tree) to get it to work with your compiler, linker etc.

It should then be relatively simple to update the batch file generation so that MAKEMSI generates a batch file like that you just tested. It would be great if you could submit it to me so that others may also benefit.

COMPLETE EXAMPLE

Please see the "TryMeDllCustomAction.MM" example, and also the "Browse for File Dialog" tip.

ANOTHER EXAMPLE DLL

The following example creates a DLL which takes the name of a property containing a directory name and creates new properties with information about that directory (for example the name of the directory without a terminating slash):

<$DllCa-C Binary="GetSomeDirectoryDetails.dll">
    //============================================================================
    <$DllCaEntry "SetDirProperties">    ;;Note on entry directory may not exist...
    //============================================================================
    {
        //--- Never use a message box like this in production code (prevents silent install) ---
        MessageBox(NULL, "Started", "TITLE: <$ProdInfo.ProductName> (<$ProductVersion>)", MB_OK);

        //--- Get the Name of the property containing the path ---------------
        UINT  Rc;
        TCHAR PropertyName[_MAX_PATH] = {0};
        DWORD PropertyNameLng   = sizeof(PropertyName) / sizeof(TCHAR);
        Rc = MsiGetProperty(hInstall, TEXT("SetDirProperties"),  PropertyName, &PropertyNameLng);
        if  (Rc != ERROR_SUCCESS)
        {
            CaDebugv(PROGRESS_LOG, "MsiGetProperty() failed with RC = %u on \"SetDirProperties\" (contains passed parameter)", Rc);
            return(1603);
        }
        CaDebugv(PROGRESS_LOG, "We were told to process the property \"%s\".", PropertyName);

        //--- We now have the name of the property, get the path -------------
        TCHAR Path[300] = {0};
        DWORD PathLng   = sizeof(Path) / sizeof(TCHAR);
        Rc = MsiGetProperty(hInstall, PropertyName,  Path, &PathLng);
        if  (Rc != ERROR_SUCCESS)
        {
            CaDebugv(PROGRESS_LOG, "MsiGetProperty() failed with RC = %u", Rc);
            return(1603);
        }
        CaDebugv(PROGRESS_LOG, "The property contained \"%s\".", Path);

        //--- Now remove any terminating slash -------------------------------
        if  (Path[PathLng-1] == '\\')
            *(&Path[PathLng-1]) = '\0';
        CaDebugv(PROGRESS_LOG, "The value without a terminating slash is \"%s\".", Path);

        //--- Create a "_NTS" (No Terminating Slash) property ----------------
        TCHAR NewPropertyName[400];
        sprintf(NewPropertyName, "%s_NTS", PropertyName);
        Rc = MsiSetProperty(hInstall, NewPropertyName, Path);    ;;Will fail if CA type is "deferred"
        if  (Rc != ERROR_SUCCESS)
        {
            CaDebugv(PROGRESS_LOG, "MsiSetProperty() failed with RC = %u setting \"%s\".", Rc, NewPropertyName);
            return(1603);
        }

        ;--- Get the 8.3 (shortname) ----------------------------------------
        TCHAR ShortName[_MAX_PATH];
        ULONG ShortLen = GetShortPathName(Path, ShortName, _MAX_PATH);      //Note length "_MAX_PATH"?, samples I see take into account size (as I did above), I think this is wrong...
        if  (ShortLen != 0)
            CaDebugv(PROGRESS_LOG, "The short (8.3) name is \"%s\".", ShortName);
        else
        {
            CaDebugv(PROGRESS_LOG, "The short (8.3) name couldn't be determined (directory probably doesn't exist yet).");
            *ShortName = '\0';              ;;Maybe the API has taken care of this...
        }

        //--- Create a "_83" (short name) property ---------------------------
        sprintf(NewPropertyName, "%s_83", PropertyName);
        Rc = MsiSetProperty(hInstall, NewPropertyName, ShortName);      ;;Will fail if CA type is "deferred"
        if  (Rc != ERROR_SUCCESS)
        {
            CaDebugv(PROGRESS_LOG, "MsiSetProperty() failed with RC = %u setting \"%s\".", Rc, NewPropertyName);
            return(1603);
        }

        //--- Never use a message box like this in production code (prevents silent install) ---
        MessageBox(NULL, "Ended", "TITLE: <$ProdInfo.ProductName> (<$ProductVersion>)", MB_OK);

        //--- Return successful to Windows Installer -------------------------
        return(0);
    }
    <$/DllCaEntry>
<$/DllCa-C>


;--- Now call the entry point where and when desired ------------------------
<$DirectoryTree Key="INSTALLDIR" Dir="[ProgramFilesFolder]TryMe DLL (path stuff)" CHANGE="\" PrimaryFolder="Y">
<$propertyCa  "SetDirProperties" VALUE="INSTALLDIR" Seq="CostFinalize-" Condition="<$DLLCA_CONDITION_INSTALL_ONLY>">
<$DllCa Binary="GetSomeDirectoryDetails.dll" Seq="CostFinalize-" Entry=^<$DllCaEntry? "SetDirProperties">^ \
        Type="Immediate" Condition="<$DLLCA_CONDITION_INSTALL_ONLY>">

For a simpler (non-generic) version of the above see the "Remove Trailing Slashes from Directory Names" tip.

Main "DllCa-C" Options

Please see the "options for commands" section of the manual.

;----------------------------------------------------------------------------
;--- General Options --------------------------------------------------------
;----------------------------------------------------------------------------
#define? DEFAULT_DLLCA-C_DOCO                       Y                   ;;"N" = Don't add to doco
#define? DEFAULT_DLLCA-C_LANGUAGE                   C                   ;;C/C++
#define? DLLCA-C_USE_TOOLS                          MINGW               ;;See "DLLCA-C_COMPILE_BATCH_FILE_CONTENTS.MINGW" below
#define? DLLCA-C_BINARY_COMMENT                     This file generated by the "DLLCA-C" command at <??RxMmLocation>
#define? DLLCA-C_STACK_BUFFER_SIZE                  1000                ;;Too short and messages may get truncated (buffers can't overflow)
#define? DLLCA-C_LOG_PREFIX                         <?Space>   DLL(C)-><?Space>  ;;Make logged lines easy to find
#define? DLLCA-C_SOURCE_EXTN_FOR_C                  .c                  ;;Must be lower case C or compiler thinks its C++
#define? DLLCA-C_SOURCE_EXTN.FOR_C++                .cpp
#define? DLLCA-C_COMPRESS_DLL_COMMAND_LINE                              ;;If non-blank then complete UPX.EXE (or other tools) command line less the name of the DLL which will follow... Must return RC=0 if OK (wrap in batch file if required)
#define? DLLCA-C_BUILD_DLL_OUTPUT_COLOR             {GREEN}             ;;Color of redirected output


;----------------------------------------------------------------------------
;--- Stub Related -----------------------------------------------------------
;----------------------------------------------------------------------------
#define? DLLCA-C_STUB_ENTRY_USER                                        ;;User Code (debug loops etc)
#define? DLLCA-C_STUB_EXIT_USER                                         ;;User Code
#define? DLLCA-C_USER_FUNCTION_SUFFIX               _                   ;;MAKEMSI creates a logging stub and calls your RENAMED function. This value must be non-empty.
#(
    #define? DLLCA-C_STUB_ENTRY_LOG
    CaDebug(PROGRESS_LOG, "\r\n\r\n>>>> Starting DLL entry point: {$function}() - in \"{$Binary}\" -
                          version <$ProductVersion> of <$ProdInfo.ProductName>");
#)
#(
    #define? DLLCA-C_STUB_EXIT_LOG
    CaDebugv(PROGRESS_LOG, "<<<< Finished DLL entry point: {$function}() - RC = %lu\r\n\r\n", {$RcVar});
#)
#(  '<?NewLine>'
    ;--- Not normally overridden --------------------------------------------
    #define? DLLCA-C_STUB_ENTRY
    <$DLLCA-C_STUB_ENTRY_LOG  {$?}>
    <$DLLCA-C_STUB_ENTRY_USER {$?}>
#)
#(  '<?NewLine>'
    ;--- Not normally overridden --------------------------------------------
    #define? DLLCA-C_STUB_EXIT
    <$DLLCA-C_STUB_EXIT_USER {$?}>
    <$DLLCA-C_STUB_EXIT_LOG  {$?}>
#)



;----------------------------------------------------------------------------
;--- Export Decorators ------------------------------------------------------
;----------------------------------------------------------------------------
#define? DLLCA-C_EXPORT_DECORATORS_PREFIX                               ;;Don't know if ever required (better safe than sorry)
#define? DLLCA-C_EXPORT_DECORATORS_SUFFIX           @4                  ;;MinGx/GCC adds "@4" to end of name (you can actually configure the linker not to create...)


;----------------------------------------------------------------------------
;--- Constants --------------------------------------------------------------
;----------------------------------------------------------------------------
#define  DLLCA_BATCH_LABEL_ERROR_FOUND          ErrorFound
#define  DLLCA_BATCH_LABEL_OK_FINISH            OkFinish
#define  DLLCA_BATCH_FILE_ERROR_RC_TEXT_BEFORE  ERROR DETECTED: RC= "
#define  DLLCA_BATCH_FILE_ERROR_RC_TEXT_AFTER                       "


;----------------------------------------------------------------------------
;--- "MINGW" configuration (used by default) --------------------------------
;----------------------------------------------------------------------------
#define? DLLCA-C_MINGW_EXTRA_OPTIONS_COMPILE_KEEP_TEMP  -save-temps                         ;;Keeps preprocessed output (useful when debugging) and even generated assembler code.
#define? DLLCA_C_MINGW_INCLUDE_DIRECTIVES               -I "<$PLATFORM_SDK_INCLUDE_DIR>"    ;;You can use multiple "-I" stitches in whatever order you wish if you need to.
#(  '<?NewLine>'
    ;--- This is the "middle" part of a batch file --------------------------
    #define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS.MINGW

    <?NewLine>
    @rem **** MinGw "BIN" must be in the path ************************************
    if     "%MAKEMSI_MINGW%" == "" if exist "%HOMEDRIVE%\MinGw\bin\gcc.exe"    set MAKEMSI_MINGW=%HOMEDRIVE%\MinGw
    if     "%MAKEMSI_MINGW%" == "" if exist "%ProgramFiles%\MinGw\bin\gcc.exe" set MAKEMSI_MINGW=%ProgramFiles%\MinGw
    if not "%MAKEMSI_MINGW%" == "" set PATH=%PATH%;%MAKEMSI_MINGW%\bin

    <?NewLine>
    @rem **** Define some Intermediate files *************************************
    set OutObjFile=%FileBase%.o
    set OutResFile=%FileBase%(resources).o

    <?NewLine>
    @rem **** Removing old temporary files ***************************************
    @del "%OutObjFile%" >nul 2>&1
    @del "%OutResFile%" >nul 2>&1
    @del "%DllFile%"    >nul 2>&1
    <?NewLine>

    @echo *** COMPILING THE SOURCE CODE ***********************************
    setlocal
    cd "<$MAKEMSI_DLLCA-C_DIR>"
    cd /d "<$MAKEMSI_DLLCA-C_DIR>" 2>&1     ;;/d not supported everywhere
    set GCC_COMPILE_OPTIONS=-c -DBUILD_DLL <$DLLCA-C_MINGW_EXTRA_OPTIONS_COMPILE_KEEP_TEMP> <$DLLCA_C_MINGW_INCLUDE_DIRECTIVES>
    set Cmd=gcc.exe %GCC_COMPILE_OPTIONS% "%SrcCodeFile%" -o "%OutObjFile%"
    echo EXEC: %CMD%
    %CMD%
    @if errorlevel 1 goto <$DLLCA_BATCH_LABEL_ERROR_FOUND>
    endlocal
    <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_DISPLAY_SEPARATOR>

    <?NewLine>
    echo WindRes.exe is crap and doesn't handle spaces in filenames > "%OutResFile%"
    for %%x in ("%SrcRcFile%")  do set  SrcRcFile83=%%~fsx
    for %%x in ("%OutResFile%") do set OutResFile83=%%~fsx
    @del "%OutResFile%" >nul 2>&1

    <?NewLine>
    @echo *** COMPILING RESOURCES *****************************************
    set Cmd=WindRes.exe -o "%OutResFile%" "%SrcRcFile%"
    set Cmd=WindRes.exe -i "%SrcRcFile83%" -o "%OutResFile83%"
    echo EXEC: %CMD%
    %CMD%
    @if errorlevel 1 goto <$DLLCA_BATCH_LABEL_ERROR_FOUND>
    for %%x in ("%OutResFile%") do set OutResFileNP=%%~nxx
    set CMD=ren "%OutResFile83%" "%OutResFileNP%"
    echo WORKAROUND: %CMD%
    @%CMD% > nul

    <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_DISPLAY_SEPARATOR>

    @echo *** GENERATING THE DLL ******************************************
    set GCC_LINK_OPTIONS=<$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_GCC_LINK_OPTIONS>
    set Cmd=gcc.exe -o "%DllFile%" "%OutObjFile%" "%OutResFile%" %GCC_LINK_OPTIONS%
    echo EXEC: %CMD%
    %CMD%
    @if errorlevel 1 goto <$DLLCA_BATCH_LABEL_ERROR_FOUND>
    <?NewLine>
#)
#define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_LINK_LIBRARYPATH     <$PLATFORM_SDK_LIB_DIR>\msi.lib
#define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_GCC_LINK_OPTIONS     -shared -mwindows --library-path "<$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_LINK_LIBRARYPATH>"


;----------------------------------------------------------------------------
;--- Resource file (.rc) ----------------------------------------------------
;----------------------------------------------------------------------------
#define? DLLCA-C_RESOURCE_FILE
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_FILE_VERSION    <$ProductVersion>
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_PRODUCT_VERSION <$ProductVersion>
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_FILEOS          0x40000
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_BLOCK           "040904B0"
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_LegalCopyright
#define? DLLCA-C_RESOURCE_FILE_StringFileInfo_EXTRA_KEYWORD_VALUE_PAIRS
#DefineRexx '@@ConvertDottedToComma4'
    ;--- Converts "1.2.003" to "1, 2, 3, 0" etc -----------------------------
    parse value {$VerDotExp} with @@P1 '.' @@P2 '.' @@P3 '.' @@P4;
    if  @@P1 = '' then @@P1 = 0; else @@P1 = @@P1 + 0;
    if  @@P2 = '' then @@P2 = 0; else @@P2 = @@P2 + 0;
    if  @@P3 = '' then @@P3 = 0; else @@P3 = @@P3 + 0;
    if  @@P4 = '' then @@P4 = 0; else @@P4 = @@P4 + 0;
    {$VerCmaVar} = @@P1 || ', ' || @@P2 || ', ' || @@P3 || ', ' || @@P4;
#DefineRexx
#(  '<?NewLine>'
    #define? DLLCA-C_RESOURCE_FILE_StringFileInfo

    ;--- Get a version number with 4 "bits" and commas ----------------------
    #(
        #DefineRexx ''
            <$@@ConvertDottedToComma4 VerCmaVar='@@VerCmaFile'    VerDotExp=^'<$DLLCA-C_RESOURCE_FILE_StringFileInfo_FILE_VERSION>'^>;
            <$@@ConvertDottedToComma4 VerCmaVar='@@VerCmaProduct' VerDotExp=^'<$DLLCA-C_RESOURCE_FILE_StringFileInfo_PRODUCT_VERSION>'^>;
        #DefineRexx
    #)

    ;--- Generate the version info ------------------------------------------
    1 VERSIONINFO
    FILEVERSION    <??@@VerCmaFile>
    PRODUCTVERSION <??@@VerCmaProduct>
    FILEFLAGSMASK 0
    FILEOS <$DLLCA-C_RESOURCE_FILE_StringFileInfo_FILEOS>
    FILETYPE 1
    {
     BLOCK "StringFileInfo"
     {
      BLOCK <$DLLCA-C_RESOURCE_FILE_StringFileInfo_BLOCK>
      {
       VALUE "CompanyName",      "<$COMPANY_PROPERTY_MANUFACTURER>"
       VALUE "FileDescription",  "Built by MAKEMSI (at <?CompileTime>) as part of the creation of the <$ProdInfo.ProductName> MSI."
       VALUE "FileVersion",      "<$ProductVersion>"
       VALUE "LegalCopyright",   "<$DLLCA-C_RESOURCE_FILE_StringFileInfo_LegalCopyright>"
       VALUE "ProductName",      "<$ProdInfo.ProductName>"
       VALUE "ProductVersion",   "<$ProductVersion>"
       <$DLLCA-C_RESOURCE_FILE_StringFileInfo_EXTRA_KEYWORD_VALUE_PAIRS>
      }
     }
    }
#)


;----------------------------------------------------------------------------
;--- Contents of the Compile Batch file -------------------------------------
;----------------------------------------------------------------------------
#(  '<?NewLine>'
    #define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_START
    @echo off
    setlocal
    echo.
    set     FileBase=<??@@FilesNe>
    set  SrcCodeFile=<??@@SourceCodeFile>
    set    SrcRcFile=<??@@SrcRcFile>
    set      DllFile=<??@@DllFile>
    if     "%1" == "" set InvokedBy=USER
    if not "%1" == "" set InvokedBy=MAKEMSI
    <?NewLine><?NewLine><?NewLine>

    @REM *** Change Buffer size, unless told not too ****************************
    if  "%InvokedBy%" == "MAKEMSI" goto AlreadyDone
        set BuffSize=%MAKEMSI_MM_CONBUFFSIZE%
        if     "%BuffSize%" == ""   set BuffSize=32000
        if not "%BuffSize%" == "-"  ConSetBuffer.exe /H=%BuffSize%
    :AlreadyDone
    <?NewLine><?NewLine><?NewLine>
#)
#(  '<?NewLine>'
    #define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_END
    <?NewLine><?NewLine><?NewLine>

    ;--- Did the user want a compress step (UPX.EXE etc)? -------------------
    #if ['<$DLLCA-C_COMPRESS_DLL_COMMAND_LINE $$IsBlank>' = 'Y']
        @echo.
        @echo Compression not configured with the "DLLCA-C_COMPRESS_DLL_COMMAND_LINE" macro!
    #elseif
        <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_DISPLAY_SEPARATOR>
        @echo *** COMPRESSING THE SUCCESSFULLY GENERATED DLL ********************
        set Cmd=<$DLLCA-C_COMPRESS_DLL_COMMAND_LINE> "%DllFile%"
        echo EXEC: %CMD%
        %CMD%
        @if errorlevel 1 goto <$DLLCA_BATCH_LABEL_ERROR_FOUND>
    #endif
    <?NewLine>

    ;--- If user let us get to header its OK --------------------------------
    @rem ============
    :<$DLLCA_BATCH_LABEL_OK_FINISH>
    @rem ============
        set SetRc=echo GOOD (this will always work)
        goto ExitWithRc
        <?NewLine>

    @rem ============
    :<$DLLCA_BATCH_LABEL_ERROR_FOUND>
    @rem ============
        @echo.
        @echo !!!
        @echo !!! <$DLLCA_BATCH_FILE_ERROR_RC_TEXT_BEFORE>%errorlevel%<$DLLCA_BATCH_FILE_ERROR_RC_TEXT_AFTER>
        @echo !!!
        set SetRc=.\NoSuchExeToSetRc.EXE (this will never work)
        goto ExitWithRc
        <?NewLine>

    @rem ============
    :ExitWithRc
    @rem ============
    @echo.
    if  "%InvokedBy%" == "USER" @pause
    %SetRc% >nul 2>&1
#)
#(  '<?NewLine>'
    #define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_DISPLAY_SEPARATOR
    <?NewLine><?NewLine><?NewLine>
    @echo.
    @echo *+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+
    @echo.
    <?NewLine><?NewLine><?NewLine>
#)
#(  '<?NewLine>'
    #define? DLLCA-C_COMPILE_BATCH_FILE_CONTENTS

    ;--- Standard initialization code ---------------------------------------
    <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_START>

    ;--- Use the configured toolset -----------------------------------------
    #ifndef DLLCA-C_COMPILE_BATCH_FILE_CONTENTS.[DLLCA-C_USE_TOOLS]
            #error ^The toolset "<$DLLCA-C_USE_TOOLS>" hasn't been defined!^
    #endif
    <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS.[DLLCA-C_USE_TOOLS]>

    ;--- Standard final code ------------------------------------------------
    <$DLLCA-C_COMPILE_BATCH_FILE_CONTENTS_END>
#)

Example of enhancing the Entry Point's "STUB"

The following code enhances the generate stub created for functions defined with the "DllCaEntry" command to add these features:

  1. Nothing inserted if building a production MSI.

  2. Execution pauses before function started so you can examine environment.

  3. If the function returns success you are asked if you want to simulate an error to see how your MSI handles it (does it rollback etc).

  4. If the function returns an error, you are asked if you want to try again (probably after diagnosing and modifying the environment).
#if ['<$MmMode>' <> 'P']
    ;--- Not production so add some debug aids to DLL entry points ----------
    #( '<?NewLine>'
        #define DLLCA-C_STUB_ENTRY_USER

        //--- Pause Execution so user can examine the environment ------------
        MessageBox(NULL, "Function \"{$Function}()\" in <$ProdInfo.ProductName> version <$ProductVersion> is about to be executed.", "DLL FUNCTION WAIT!", MB_OK|MB_ICONINFORMATION);

        //--- Start Loop ---------------------------------------------------------
        BOOL LoopAgain;
        do
        {
            //--- Default is to exit the loop ------------------------------------
            LoopAgain = FALSE;
    #)
    #( '<?NewLine>'
        #define DLLCA-C_STUB_EXIT_USER

            //--- Check the return code ------------------------------------------
            if  ({$RcVar} == 0)
            {
                //--- Return Code indicates success (user want to simulate an error to test rollback etc?) ---
                if  (IDYES == MessageBox(NULL, "Function \"{$Function}()\" in <$ProdInfo.ProductName> version <$ProductVersion> succeeded.\n\nDo you want to simulate a failure to test rollback etc?", "DLL CA SUCCESSFUL!", MB_YESNO|MB_DEFBUTTON2|MB_ICONQUESTION))
                {
                    CaDebug(PROGRESS_DETAIL, "Custom Action succeeded, however user chose to simulate an error...");
                    {$RcVar}  = 1603;
                }
            }
            else
            {
                //--- Return Code indicates failure (user want to change environment and try again?) ---
                if  (IDYES == MessageBox(NULL, "Function \"{$Function}()\" in <$ProdInfo.ProductName> version <$ProductVersion> failed.\n\nDo you want to try it again?", "DLL CA FAILED!", MB_YESNO|MB_DEFBUTTON2|MB_ICONQUESTION))
                {
                    CaDebug(PROGRESS_DETAIL, "Custom Action failed, however user chose to simulate an error...");
                    LoopAgain = TRUE;
                }
            }

        //--- End the loop -------------------------------------------------------
        }   while (LoopAgain);
    #)
#endif


Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.Please email me any feedback, additional information or corrections.
See this page online (look for updates)

[Top][Contents][Prev]: DllCa[Next]: /DllCa-C


MAKEMSI© is (C)opyright Dennis Bareis 2003-2008 (All rights reserved).
Saturday May 28 2022 at 3:11pm
Visit MAKEMSI's Home Page
Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006, 2007, 2008 & 2009 for the Windows SDK (Windows Installer) area.