Property does not exist or empty when accessed from deferred custom action

Problem

Property is created, but when deferred custom action tries to get its value - property either does not exist or its value is empty.

Sample

Let's test it with immediate custom action first to make sure that property has a value:

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

<Wix xmlns="https://schemas.microsoft.com/wix/2003/01/wi">

  <Product Id="{94A35E02-D48F-48F1-AC1A-23F62489BBF4}"

           Name="Minimal Windows Installer Sample"

           Language="1033"

           Codepage="1252"

           Version="1.0.0"

           Manufacturer="Acme Corporation"

           UpgradeCode="{74069BA4-1D1C-4252-A074-B2EC0C746403}">

    <Package Id="{4C159FD5-135E-42DE-B36E-22DDDCBA3DCF}"

             Description="Minimal Windows Installer Sample"

             Comments="This installer database contains the logic and data required to install [ProductName]."

             InstallerVersion="200"

             Languages="1033"

             SummaryCodepage="1252"

             Platforms="Intel"

             ReadOnly="no"

             Compressed="yes"

             AdminImage="no"

             Keywords="Installer"

             ShortNames ="no"

             Manufacturer="Acme Corporation" />

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

    <Directory Id="TARGETDIR" Name="SourceDir">

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLDIR" Name="Minimal" LongName="MinimalInstallation">

          <Component Id="Component1"

                     Guid="{A77C5B06-132D-4884-8E17-EA10A83C812D}">

            <File Id="ReadMe" DiskId="1" Name="Readme.txt" Source="Readme.txt" Vital="yes" KeyPath="yes" />

          </Component>

        </Directory>

      </Directory>

    </Directory>

    <Property Id="TESTPROPERTY" Value="Hello" />

    <CustomAction Id="ShowProperty" Script="vbscript" Execute="deferred">

      <![CDATA[

      MsgBox Session.Property("TESTPROPERTY")

      ]]>

    </CustomAction>

    <InstallExecuteSequence>

      <Custom Action="ShowProperty" Before="InstallFinalize">Not Installed</Custom>

    </InstallExecuteSequence>

    <Feature Id="Feature1"

             Title="Feature1 title"

             Description="Feature1 description"

             Level="1"

             ConfigurableDirectory="INSTALLDIR" >

      <ComponentRef Id="Component1" />

    </Feature>

  </Product>

</Wix>

As you can see I explicitly set the value of the TESTPROPERTY property.  Custom Action Type 38 shows the message box with the value of this property.  Build MSI and test it.

Now, let's make our custom action ShowProperty deferred:

    <CustomAction Id="ShowProperty" Script="vbscript"

                 Execute="deferred">

      <![CDATA[

      MsgBox Session.Property("TESTPROPERTY")

      ]]>

    </CustomAction>

Let's test it again.  As you can see, the message box is empty.

Why?

As you can see in this article on MSDN, deferred custom action can access only three property:

Also, this article is saying that we need to add custom action to set the property with the same name as deferred custom action to the value of property we want to use in the deferred custom action.  Defereed custom action must access CustomActionData property.  Here is updated version:

    <Property Id="TESTPROPERTY" Value="Hello" />

    <CustomAction Id="ShowProperty" Script="vbscript" Execute="deferred">

      <![CDATA[

      MsgBox Session.Property("CustomActionData")

      ]]>

    </CustomAction>

    <CustomAction Id="SetPropertyForShowProperty"

   Property="ShowProperty" Value="[TESTPROPERTY]" />

    <InstallExecuteSequence>

      <Custom Action="SetPropertyForShowProperty" Before="InstallInitialize">Not Installed</Custom>

      <Custom Action="ShowProperty" Before="InstallFinalize">Not Installed</Custom>

    </InstallExecuteSequence>

I've added new immediate Custom Action Type 51 to set the property with the name ShowProperty (same name as deferred custom action) and scheduled it in the InstallExecuteSequence table just before InstallInitialize standard action.  Custom Action ShowProperty is using CustomActionData property now.

Build the MSI and make sure that it is working now.

What if I need more than one property?

If we need to access more than one property, we must pack them into CustomActionData.  For example, if we know for sure that our properties will not have semi-colon in their value, we can concatenate property values and separate values using semi-colon.  Here is an example:

    <Property Id="TESTPROPERTY" Value="Hello" />

    <Property Id="TESTPROPERTY2" Value="World" />

    <CustomAction Id="ShowProperty" Script="vbscript" Execute="deferred">

      <![CDATA[

      Dim properties

      properties = Split(Session.Property("CustomActionData"), ";", -1, 1)

      MsgBox properties(0) & ", " & properties(1) & "!"

      ]]>

    </CustomAction>

    <CustomAction Id="SetPropertyForShowProperty"

                  Property="ShowProperty"

                  Value="[TESTPROPERTY];[TESTPROPERTY2]" />

    <InstallExecuteSequence>

      <Custom Action="SetPropertyForShowProperty" Before="InstallInitialize">Not Installed</Custom>

      <Custom Action="ShowProperty" Before="InstallFinalize">Not Installed</Custom>

    </InstallExecuteSequence>