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

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.

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.

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

Click on "C++ File (.cpp)"

In the Name text box type: GetHardwareProfile

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.

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.