Don't use managed code to write your custom actions!
After posting a brief article about my opinions regarding script-based custom actions, I started thinking more about what I consider an even bigger personal pet peeve of mine - managed code custom actions.
I want to caveat everything I am about to say by explaining that in general, I think managed code is great and not just because I work at Microsoft :-) It offers developers the power of modern object-oriented languages while allowing for rapid development for small and large scale projects. In fact, I have basically spent my entire career trying to make it as easy as possible for everyone to deploy the .NET Framework as part of OEM-manufactured machines or managed applications - and yes I know there is still a lot of work for us to do in this space.
But I also believe that managed code is not the best choice for certain types of development, and setup is one of those places. The biggest concern I have with managed code being used in custom action code is the additional dependency that it introduces to setup. If the underlying .NET Framework runtime is damaged it can lead to unpredictable results in a setup that would not occur if the custom actions were written as a native code DLL (or better yet, were eliminated in favor of standard Windows Installer actions).
I have also seen setup issues related to .NET Framework runtime migration policy. The policy of migrating managed apps to use the latest version of the .NET Framework on the machine (which is on by default in .NET 1.1 and 2.0) can cause your custom actions to run on top of a different version of the .NET Framework than you originally tested and shipped with.
We used some managed executables during setup in previous versions of the .NET Framework. Then in future versions of the .NET Framework we decided to set the default runtime migration policy so that any managed code running on the system would use the most recent version of the .NET Framework by default. In general, the .NET Framework is side-by-side compatible and apps written against one version will continue to run correctly against future versions. However, for specific functionality such as parsing configuration files or calling APIs that have had breaking changes introduced for security reasons, this may not hold true. It is possible to include a config file with a managed app to lock it in so it will require a specific version of the .NET Framework to run correctly. However, if you neglect to include a config file (like we have done in the past) that can potentially cause issues if/when your managed code is migrated to run on top of a later version of the runtime. Also, if you author a managed DLL custom action you leave your fate in the hands of Windows Installer, because it will use the latest version of the .NET Framework on the machine in all cases (as opposed to a custom action that calls an installed EXE which can also have a config file installed next to it).
As a side note here, if you install the .NET Framework 1.1 you will notice a couple of files installed to the v1.0.3705 folder instead of the v1.1.4322 folder. A couple of those files are intended to fix policy migration issues like the ones I just described. You will also see the .NET Framework 2.0 setup install a few files to the v1.0.3705 and the v1.1.4322 folder for the same reason.
So, in summary, I strongly encourage you to not use managed code in your product setup. I realize that some teams do this here at Microsoft, but please don't use our bad (in my opinion) examples to justify doing so in your own setup....