Revenera logo

If you’re getting started with Custom Actions in InstallShield, you may have noticed in short order that sequencing a custom action after InstallFiles strangely fails to find files being installed by the software installation.  Some research leads you down the path of switching the custom action to Deferred, or potentially Deferred in System Context (to ensure proper UAC elevation).  But all of a sudden INSTALLDIR or other property values have disappeared!

There’s quite a bit of confusion the first time a developer tries to use a property in a Deferred custom action, and finds that for some reason they are not able to access it via MsiGetProperty().  After hours of head scratching, one might end up coming across a help document or two:

Obtaining Context Information for Deferred Execution Custom Actions

Q104413: PRB: Deferred Execution Custom Action Cannot Retrieve a Property Value

These documents are pretty clear about using the CustomActionData property, but the suggestion for the SetProperty custom action is a little peculiar.  At the very least, the first few times I read through those documents I didn’t quite get what it was suggesting.  Basically, it’s required to set a property that matches the Name of the custom action you’re passing the values to.  Which admittedly sounds strange, which is perhaps the bigger reason why people get this wrong when they are first exposed to it.

That said, the easiest thing to do is to illustrate how the two custom actions relate to each other:

CaryBlog1
You’ll note in the screenshot that I’ve set up a delimited list of Property-Value pairs that are formatted when the property value is set.  Parsing this is easier in some custom action languages than others, though.  I’ve pasted below a function I wrote in InstallScript that eases the pain in getting some properties out of CustomActionData once you’ve got it into your script:

function string GetCADataProp(CAData, propName)

NUMBER nResultA,nResultB,nCountPairs,nCountKeyVals;

string PropVal,tmpKey,tmpVal;

LIST propPairs, propValKey;

begin

 

propPairs = ListCreate(STRINGLIST);

propValKey = ListCreate(STRINGLIST);

StrGetTokens(propPairs,CAData,”;”);

 

nResultA = ListGetFirstString (propPairs , tmpKey );

 

while (nResultA != END_OF_LIST)

ListDestroy(propValKey);

propValKey = ListCreate(STRINGLIST);

StrGetTokens(propValKey,tmpKey,”=”);

 

nResultB = ListGetFirstString (propValKey,tmpVal);

while(nResultB != END_OF_LIST)

 

if(tmpVal==propName) then

nResultB = ListGetNextString(propValKey,PropVal);

return PropVal;

endif;

nResultB = ListGetNextString(propValKey,PropVal);

 

endwhile;

nResultA = ListGetNextString(propPairs , tmpKey );

endwhile;

 

//If didn’t break out sooner in this function

PropVal = “Not Found!”;

return PropVal;

InstallShield icon

InstallShield

Create native MSIX packages, build clean installs, and build installations in the cloud with InstallShield from Revenera.

end;

This works by actually requesting a property by name instead of assuming a particular order in the delimited list of values in CustomActionData, like some examples suggest.

Last, but not least, don’t forget to sequence your SetProperty action early in the Execute Sequence!  Preferably after CostFinalize, since this will make sure that Directory Table entries like INSTALLDIR have been resolved.  And, for that matter, if you have a large number of custom actions to pass values to, there’s no reason why you can’t have a single script custom action set all of these properties in one go; as long as they are set to the names of the custom actions, it will pass the values through via CustomActionData.

I hope this saves you a little head scratching.