ISWIX, LLC View Christopher Painter's profile on LinkedIn profile for Christopher Painter at Stack Overflow, Q&A for professional and enthusiast programmers

February 19, 2009

MSI Tip: How to Reuse a CustomAction for Deferred and Rollback

Neil Sleightholm recently asked how to tell if your in a rollback custom action using C#/DTF. The answer is simple.

Let's suppose we want to export one custom action method and make it multipurposed. In other words, if we call into while deferred execution we should do one thing but if we call into it for rollback execution we should do another.

How would we do that? The answer is in the BOOL MsiGetMode( __in MSIHANDLE hInstall, __in MSIRUNMODE iRunMode) Windows Installer function exposed by the Session.GetMode( InstallRunMode installRunMode ) DTF method.

Consider the following WiX Code:

    <Binary Id="CustomActionModeTest"
            SourceFile="PATH_TO_DLL.CA.dll "
    </Binary>
 
    <CustomAction Id="RollbackCA"
                  BinaryKey="CustomActionModeTest"
                  DllEntry="CustomAction1"
                  Execute="rollback"
                  Impersonate="yes">
    </CustomAction>
    <CustomAction Id="DeferredCA"
                  BinaryKey="CustomActionModeTest"
                  DllEntry="CustomAction1"
                  Execute="deferred"
                  Impersonate="yes">
    </CustomAction>
 
    <InstallExecuteSequence>
      <Custom Action="RollbackCA" 
              After="InstallInitialize">
      </Custom>
      <Custom Action="DeferredCA"
              After="RollbackCA">
      </Custom>
    </InstallExecuteSequence>



You'll notice we have 2 custom actions pointing to the same exported function in the binary table. Also notice that one custom action is scheduled as deferred and the other rollback. The deferred is scheduled right after the rollback. This results in Windows Installer scheduling RollbackCA after InstallInitialize and DeferredCA after RollbackCA.

Now let's look at the code inside of that Custom Action.

using System;
using System.Windows.Forms;
using Microsoft.Deployment.WindowsInstaller;
 
namespace CustomActionModeTest
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult CustomAction1(Session session)
        {
            ActionResult result = ActionResult.Success;
 
            if (session.GetMode(InstallRunMode.Scheduled))
            {
                MessageBox.Show("I'm not in rollback, let's cause one to occur!");
                result = ActionResult.Failure;
            }
            
            if (session.GetMode(InstallRunMode.Rollback))
            {
                MessageBox.Show("We are now in a rollback." );
            }
 
            
            return result;
        }
    }
}


At runtime, RollbackCA is skipped and DeferredCA is executed. The CustomAction1 method will be executed by the DeferredCA and it will detect that it's scheduled in deferred mode and display a message saying that it's not in rollback and that it'll cause one. It does so by returning a failure to Windows Installer. Now MSI starts walking the script backwards executing any rollback CA it finds. This causes it to run RollbackCA and once again we are inside of the CustomAction1 method.

This time we will evaluate that we are in rollback, display a message stating so and quit gracefully. At this point the product is not installed.

This pattern can be extended so that CustomAction1 also handles immediate execution, implicit scheduling of the deferred custom actions and CustomActionData serialization/deserialization. But I'll leave that for another blog.

3 comments:

Neil Sleightholm said...

Thanks for this it helps a bit but I was really trying to see how I could use the same CA for install and uninstall. For your other reply I think the only way to do this is to pass in the REMOVE property.

Christopher Painter said...

I'll show how to do that tonight when I get some more free time.

The Reeds said...

Thanks, this is helpful. As far as best practices go, if I need to track some information or backup files so that, should a rollback occur, I can undo whatever I just did, is there a common place to put those? For example, if I store values in my C# custom action class, I probably can't bank on those values being present whenever the rollback invokes my class. Should I put those in a temporary MSI table perhaps? If my custom action installs widgets from a Widgets table, maybe I should just create and populate a WidgetsRollback table on-the-fly...