Zataoca: Custom actions should be data driven.

As I noted a few months ago, the Windows Installer is a declarative installation engine not a procedural installation engine. That means if you are going to extend the installation engine (which is what custom actions essentially do) then you should follow the same principles the engine follows. Namely, your custom action should read data out of a table and act based on those declarative instructions.

Let me give an example. Let's say you want to protect some user input (maybe a password) using DPAPI.* Your first thought should be, "What are the inputs to my custom action?" Those inputs are the columns for your custom action's table. In this case, one column would contain the value to be encrypted (Formatted so the value of a Property could be encrypted).  Another column would provide the optional entropy value (that helps further randomize the protected output). A third column would specify whether the value will be protected for the current user or as the local machine account (limiting the set of users that can decrypt the data). A final column would specify the name of the Property to output the encrypted value to.

Then you would write your custom action to read each row out of the table with those for columns and call the ::CryptProtectData() API filling in the appropriate parameters. By acting on the data from the custom table, we call your custom action a "data driven custom action".

Those of you whose first instinct was to say, "Oh, I'll just write the code to get the value of a property called FOO and call the ::CryptProtectData() function with a bunch of parameters and write the value back out to a property called BAR." should go back an reevaluate all of the custom actions you've ever written. This type of custom action is what we call a "procedural custom action" and it is inferior to the data driven method. Unfortunately, to most developers procedural custom actions are also the most intuitive way to write code (because that's how most code is written).

Data driven custom actions are better than procedural custom actions for many reasons. as noted in the beginning, data driven custom actions follow the same declarative model that the rest of the Windows Installer follows. That means you fit into the rest of the installation system appropriately

One example is that data driven custom actions can be affected by transforms. You might think, "Oh, well I don't support administrative customizations so I don't care about transforms." That might be true but the most optimal form of patching uses transforms to affect the package. If your custom actions are not data driven, your transform must patch the custom action binary directly and that creates a much larger patch.

Data driven custom actions are also reusable. Another developer can use your custom action without modifying the code because the inputs all from from a table. The custom action library that comes with the WiX toolset works just like this. We write the actions to configure things like Users, FileShares, Web Sites, Virtual Directories, SQL Databases, etc and you can reuse those actions by providing your own data. Data driven custom actions paired with the extension model in WiX provides a very powerful platform for installation.

Data driven custom actions are also more transparent than procedural custom actions. Going back to the WiX toolset Web Site example. If you look at the tables in the MSI after adding a WebSite element it is very possible to predict what the installation is going to do. Procedural custom actions hide the data and the actions in the compiled code (you don't write script custom actions do you?). Admittedly, the data driven custom actions still have code in a black box but well written data driven custom actions can be quite transparent. Take a spin through the WiX custom action library if you don't believe me.

Now I wanted to say that *all* custom actions should be data driven from a table but there are some rare cases where it doesn't make sense. The custom action at the end of the install that launches your "readme.txt" through a ::ShellExecute() may just need a simple property passed to it (that's how the WixShellExec custom action works today).

However, any custom action that modifies system state (see #3 in my list of custom action classes) should definitely be a data driven custom action to participate in the installation transaction well. After I do an example of a data driven internal-only custom action (#1 from the list), I'll dive into system modification custom actions and further demonstrate why they should be data driven.

Anyway, that's a long blog entry that basically says, "If you have already failed and determined that you need a custom action, make sure you make it data driven."

 

* Yes, I am going to walk step by step through this example at some time in the future.

 

6 Comments

Comment by Ted on Friday, September 14, 2007 7:38 AM

All custom actions that alter system state should be deferred, right? I could be wrong, but deferred custom actions have no way to query the installer database, right? Combine those two facts with this blog post and every custom action that alters system state will need an immediate custom action that queries the database only to save the data into a global somewhere that is then accessible when its paired defered custom action later runs. That's a large amount of overhead for what is sometimes just a couple lines of code.

Comment by ibi on Saturday, September 15, 2007 2:54 AM

agreed. it just doesn't make sense to save some of the information of the installer database to, for example, the registry and read out the value(s) in a deferred custom action. that's one point which bugs me in MSI since almost eight years... :(

Comment by Christopher Painter on Saturday, September 15, 2007 6:20 AM

First I don't agree that writing a CA automatically means that you've failed as a setup developer. It may mean that if upon peer review you realize that there was a built-in way of accomplishing your goal. However if there is no built-in way, then either Windows Installer or your Tools vendor has failed you as a developer and a CA is the only way left to get the job.

As for CA's losing transparency, that is sometimes desired. Sometimes the developer wants to expose the entire mechansim, sometimes the implementation is hidden but the interface is exposed, and sometimes it's obfusacated on purpose. Script CA's are meant to be easy and transparent for people examining/transforming packages. It's not the developers fault that Script CA's are unreliable, it's MICROSOFT's fault. Saying they `suck` and criticizing developers who use them isn't the solution to the problem.

Finally I'd like to say data driven CA's is a good pattern, but it's not the only pattern. I would suggest that the good rule of thumb is when you want to write a CA to implement a pattern that is meant to be reusable or mutable. Any CA that makes changes to the system state is almost by definition both of those things because you rarely need to write to 1 registry value or 1 XML element. You also rarely want to hide the ability for someone to transform that behavior.

However a great many of simple CA's can be one shot deals where procedural is approriate and you don't need to waste tons of time ( and money ) overengineering the solution and/or exposing too much of the behavior.

Comment by Edison M. Castro on Wednesday, September 19, 2007 4:18 AM

I do not agree that writing custom actions indicate a failure of any kind. But I specially do not agree with you assesment of usage of script code to write custom actions. I am a very non-trusting individual when it comes to installing stuff on my machine. Usually when I get an MSI install from anywhere the first I do, is to decompile it to know what the install is doing and making sure that it is installing a backdoor, or possibly reformatting my drive.

When I said a DLL-based custom action written, I totally freak out. I just pissed the living daylights out of me the fact that I do not know what the stupid install is doing and there is nothing the system or I can do to protect my machine.


The instaler team has enhanced MSI for some while, but has not taken care of fixing some of the basic functionality of the product. Everytime I see posting referring to "this post by Rob Mensching", I completly go crazy.

1.- Robust code is difficult write in script.

This is utterly nonsense, bad code can be writing in any language, but specially C. If the developer of the custom action written in script can not do a simple try{}catch() on his code, changing to C would not make it any better.

2. Debugging script in the Windows Installer is difficult

Another stupid instaler team mistake and another request of mine. Has anyone on that team heard of "Active Scripting Host debug". Microsoft for some while sold us the idea of scripting debugging, I built several of them. doesn't that team care what their customer want?

3. Anti-virus products kill them

My anti-virus kills any new code I put in my machine. Many ways exist of fixing this, but the main one is: Just execute the stupid script, do not put it in the file system, get it directly from the binary table and execute it, simple isn't it?

4.- Make sure jscript.dll and vbscript.dll are correctly registered.

So many things that are supposedly part of the base system (ie, media player, etc). Aren't those part of the system too since they are required by and used by the former apps? Shouldn't the system (MSI, etc) make sure they are in the correct state before executing an action which requires it?

This is just the tip of the iceberg. I honestly think that script should be the ONLY allowable way of writing custom action, since they provide sort of a sandbox model to protect the end user from ill-intented code from the part of the producer of the MSI and for the most part (i could say allways) they provide all the functionality you may possible need, besides the fact that script is so easy to write and debug (with the right support, of course)

I will send another set of emails to the Windows Installer Wishes mailto:msiwish@microsoft.com asking for some fixes for the upcoming 4.5 release. Let's hope someone is actually listening............

Comment by gripper on Thursday, November 1, 2007 9:38 AM

I would love to write data driven custom actions, because I know the benefits, and it just seems the right thing to do. But then I am faced with cold hard reality that all my custom actions modify the system -- so they are scheduled as deferred (and support rollback and such).

As noted in a previous comment, all this extra overhead is now required (let alone, how much data can a property hold to be parsed by the CA in CustomActionData if your tables are quite large?)

Rob, how does an MSI Standard action do this seamlessly with the existing tables? Is it possible to have an immediate custom action write data directly to the MSI execution script, and then access this data from a deferred custom action? Can a custom action be written to write its own execution script when its immediate counterpart is launched, only to execute this script when running deferred?

I think us install monkeys can use a real scalable example (i.e. Beyond the HelloWorld-like sample). It would be extremely helpful.

Comment by Neelakandan on Monday, April 7, 2008 4:11 AM

I would like to write custom actions for my installer based on wix. How to write a custom actions that'll write the data to the msi tables instead of executing on the end-user system, which could be a problem, when roll back occurred. Give me a sample if possible.

Leave a comment
optional