From MSI to WiX, Part 22 – DLL Custom Actions – Introduction


Today I am starting a mini series on writing C++ custom actions.


Let’s start with wizard-generated custom action project.  Start Visual Studio and select “C++ Custom Action Project”.  Set “CAIntro” as the name of the project.


Before we will start discussing what is in the generated code, let’s talk about what Windows Installer is expecting from dll in order to be a good behaving MSI custom action dll.


First of all, dll must provide the usual dll entry point – DllMain function.  On process attach event we want to save HINSTANCE parameter in case later we will need to call some API function which requires it.


Second, we need to add custom action function.  The signature of custom action entry point function is:


UINT __stdcall CustomActionEntryPoint(MSIHANDLE hInstall)


{


    return ERROR_SUCCESS;


}


As you already know, valid return values from DLL custom action are:




  • ERROR_FUNCTION_NOT_CALLED


  • ERROR_SUCCESS


  • ERROR_INSTALL_USER_EXIT


  • ERROR_INSTALL_FAILURE


  • ERROR_NO_MORE_ITEMS

Input parameter hInstall, passed by Windows Installer to custom action, is an installation handle.  There are few functions we can call from custom action which require this parameter and we will discuss them later in this post and in subsequent posts as well.


Third, and last, we need to export custom action function in the definition file:


LIBRARY “CAIntro”


 


EXPORTS


 


CustomActionEntryPoint


Now, let’s take a look at the generated code.  Here is the content of CustomAction.cpp file:


#include “stdafx.h”


 



UINT __stdcall CustomAction1(MSIHANDLE hInstall)


{


    HRESULT hr = S_OK;


    UINT er = ERROR_SUCCESS;


 


    hr = WcaInitialize(hInstall, “CustomAction1”);


    ExitOnFailure(hr, “Failed to initialize”);


 


    WcaLog(LOGMSG_STANDARD, “Initialized.”);


 


    // TODO: Add your custom action code here.


 


 


LExit:


    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;


    return WcaFinalize(er);


}


 


 


// DllMain – Initialize and cleanup WiX custom action utils.


extern “C” BOOL WINAPI DllMain(


    __in HINSTANCE hInst,


    __in ULONG ulReason,


    __in LPVOID


    )


{


    switch(ulReason)


    {


    case DLL_PROCESS_ATTACH:


        WcaGlobalInitialize(hInst);


        break;


 


    case DLL_PROCESS_DETACH:


        WcaGlobalFinalize();


        break;


    }


 


    return TRUE;


}


WiX comes with its own API library which provides a set of convenient wrappers for Windows Installer API.  We’ll discuss them later in greater details.  For now, let’s make some changes to the project.




  1. Add new source file to the project.  Right-click on “Source Files” and select Add->New Item..


  2. Click on “C++ File (.cpp)”


  3. In the Name text box type: GetHardwareProfile


  4. Click Add button

Now, cut the whole body of CustomAction1 function from CustomAction.cpp and paste it into GetHardwareProfile.cpp.  Also, copy #include statement from CustomAction.cpp to GetHardwareProfile.cpp.


In GetHardwareProfile.cpp, rename CustomAction1 to GetHardwareProfile.  Don’t forget to change it in the WcaInitialize as well.  Open CustomAction.def and change CustomAction1 to GetHardwareProfile.


For this sample we will use GetCurrentHwProfile windows API to set 3 properties: HWPROFILENAME, HWPROFILEGUID, and HWDOCKINFO.  Here is the full source code for our custom action:


#include “stdafx.h”


 


UINT __stdcall GetHardwareProfile(MSIHANDLE hInstall)


{


    HRESULT hr = S_OK;


    UINT er = ERROR_SUCCESS;


 


    hr = WcaInitialize(hInstall, “GetHardwareProfile”);


    ExitOnFailure(hr, “Failed to initialize”);


 


    WcaLog(LOGMSG_STANDARD, “Initialized.”);


 


    // TODO: Add your custom action code here.


    LPWSTR pwzPropertyValue = NULL;


    HW_PROFILE_INFO hwProfInfo;


 


    if (::GetCurrentHwProfile(&hwProfInfo))


    {


        hr = WcaSetProperty(L“HWPROFILENAME”, hwProfInfo.szHwProfileName);


        ExitOnFailure(hr, “Failed to set HWPROFILENAME property value”);


 


        hr = WcaSetProperty(L“HWPROFILEGUID”, hwProfInfo.szHwProfileGuid);


        ExitOnFailure(hr, “Failed to set HWPROFILEGUID property value”);


 


        hr = WcaSetIntProperty(L“HWDOCKINFO”, hwProfInfo.dwDockInfo);


        ExitOnFailure(hr, “Failed to set HWDOCKINFO property value”);


    }


    else


    {


        hr = HRESULT_FROM_WIN32(::GetLastError());


        ExitOnFailure(hr, “Failed to call GetCurrentHwProfile”);


    }


 


LExit:


    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;


    return WcaFinalize(er);


}


 


We will discuss all functions later, but for now I’ll provide just short description of what is going on in here:




  • WcaInitialize – initializes some WiX API internal variables.  It alse caches hInstall to be used later in Windows Installer API calls.


  • ExitOnFailure – If hr has an error code in it, logs message and passes control to LExit label.


  • WcaLog – logs message to the log file.


  • WcaSetProperty – set the string value to property.


  • WcaSetIntValue – set the integer value to property.


  • WcaFinalize – releases WiX API internal resources and returns final exit code.

And here is how we can call this custom action:


<?xml version=1.0 encoding=UTF-8?>


<Wix xmlns=http://schemas.microsoft.com/wix/2006/wi


     xmlns:util=http://schemas.microsoft.com/wix/UtilExtension>


  <Product Id=95872e2d-8175-4661-a087-5aa8ca2db230


           Name=CAIntroInstall


           Language=1033


           Version=1.0.0.0


           Manufacturer=CAIntroInstall


           UpgradeCode=8124a77f-4991-47e6-aa63-557df0e51d26>


    <Package InstallerVersion=200 Compressed=yes />


 


    <Media Id=1 Cabinet=media1.cab EmbedCab=yes />


 


    <Property Id=HWPROFILENAME Secure=yes />


    <Property Id=HWPROFILEGUID Secure=yes />


    <Property Id=HWDOCKINFO Secure=yes />


 


    <Binary Id=CAIntro SourceFile=..\bin\CAIntro.dll/>


 


    <CustomAction Id=HardwareProfile


                  BinaryKey=CAIntro


                  DllEntry=GetHardwareProfile


                  Execute=immediate


                  Return=check


                  HideTarget=no />


 


    <InstallExecuteSequence>


      <Custom Action=HardwareProfile After=AppSearch />


    </InstallExecuteSequence>


 


    <Directory Id=TARGETDIR Name=SourceDir>


      <Directory Id=ProgramFilesFolder>


        <Directory Id=INSTALLLOCATION Name=CAIntroInstall>


          <Component Id=ProductComponent Guid=6335fc54-7f5a-46ae-9309-6b7ab214a00d>


 


            <File Id=XmlFile


                 Name=test.xml


                 Source=test.xml


                 KeyPath=yes />


            <util:XmlFile Id=SetHWPROFILENAME


                          Action=setValue


                          ElementPath=//config/parameter[\[]@name=’ProfileName'[\]]/@value


                          Value=[HWPROFILENAME]


                          File=[#XmlFile] />


            <util:XmlFile Id=SetHWPROFILEGUID


                          Action=setValue


                          ElementPath=//config/parameter[\[]@name=’ProfileGuid'[\]]/@value


                          Value=[HWPROFILEGUID]


                          File=[#XmlFile] />


            <util:XmlFile Id=SetHWDOCKINFO


                          Action=setValue


                          ElementPath=//config/parameter[\[]@name=’DockInfo'[\]]/@value


                          Value=[HWDOCKINFO]


                          File=[#XmlFile] />


 


          </Component>


        </Directory>


      </Directory>


    </Directory>


 


    <Feature Id=ProductFeature Title=CAIntroInstall Level=1>


      <ComponentRef Id=ProductComponent />


    </Feature>


  </Product>


</Wix>



 


    Functions which require an installation handle


     Installation session information



    Features information



    Components information



    Properties



    Logging



    Execute action



    Evaluate condition



    Database handle



    Database functions


    Obtaining a record containg all primary keys for a table



    Get the status of database



     Create view



    View functions



     Record functions



     Handle functions



 


In the next post we will take a closer look at our sample.  We will discuss how to log a message to the log file and how to set property’s value using Windows Installer API and how we did it using WiX API.


 Source code for the sample is in attachment.


 

CAIntro.zip

Comments (1)

  1. Glenn Picher says:

    Thanks for the info – it’s useful.

    I don’t understand why there’s a long list of MSI functions needing an installation handle in the middle of the article, though.

    Also, it looks like there’s no need for the line of

    C++ code “LPWSTR pwzPropertyValue = NULL;”.