The Windows Installer works in two phases: the acquisition (or immediate) phase and the execution (or deferred) phase. The user enters required data (e.g. the installation target path) in the acquisition phase and then the data is used to actually perform the installation in the execution phase. So simple this sounds, actually doing this with custom actions is not that straight forward as you might think.
Custom Actions in the immediate phase can access (read and write) session properties. The following example is a custom action that sets the session property USERDATA to the value “I am data”.
[CustomAction] public static ActionResult Acquire(Session session) { session["USERDATA"] = "I am data"; return ActionResult.Success; }
In the deferred phase, we cannot access the session properties any more. Trying to execute “session[“USERDATA”]” will result in an exception. In the deferred phase, there is one single property that can be read (read only, no writing). That property is called CustomActionMetadata. In the deferred phase, the following line will execute without any complaints:
[CustomAction] public static ActionResult Execute(Session session) { var data = session["CustomActionData"]; return ActionResult.Success; }
So the question is how to get the session property USERDATA into the custom action’s property CustomActionData. To answer that, let’s first look at how to schedule a custom action in each phase. First, we declare the assembly that contains our custom actions as well as the two custom actions themselves:
<Binary Id="CustomActions" SourceFile="$(var.MyCustomActions.TargetDir)\MyCustomActions.CA.dll" /> <CustomAction Id="Acquire" BinaryKey="CustomActions" DllEntry="Acquire" Execute="immediate" Return="check" /> <CustomAction Id="Execute" BinaryKey="CustomActions" DllEntry="Execute" Execute="deferred" Return="check" />
Then, we schedule the immediate custom action:
<InstallUISequence> <Custom Action="Acquire" After="LaunchConditions" /> </InstallUISequence>
So far this is all pretty simple. All we need to do now is to schedule the deferred custom action and send it the session properties. To send the session properties, we declare a new custom action using the Property attribute:
<CustomAction Id="PrepareExecute" Property="Exeucte" Value="USERDATA=[USERDATA]" />
Notice that the value of the Property attribute equals the name of the deferred custom action. This tells MSI to copy the value into the CustomActionMetadata property of the deferred custom action. In the example above, only the property USERDATA is copied. If more properties should be copied, the key-value-pairs must be separated by a semicolon. Example:
<CustomAction Id="PrepareExecute" Property="Exeucte" Value="USERDATA=[USERDATA];ANOTHERDATA=[USERDATA2];MOREDATA=[USERDATA3]" />
It is not necessary to use upper casing like in the example above, but remember that the naming is case sensitive. So if your session property’s name is all upper case, the name inside the square brackets must be all upper case. Also note that the square brackets are indeed needed to tell MSI to insert the value of the property. If you omit the square brackets, MSI copies the name itself (e.g. “USERDATA”).
No that we have got all pieces together, this is how to schedule the two last custom actions:
<InstallExecuteSequence> <Custom Action="PrepareExecute" Before="Execute" /> <Custom Action="Execute" After="InstallInitialize" /> </InstallExecuteSequence>
Notice how we make sure that the custom action for copying the data is scheduled before the actual deferred custom action.
Inside the deferred custom action, a call to “session[“CustomActionMetadata”]” will return the semicolon-concatenated key-value-pairs. Also, the Session object has a property called CustomActionMetadata that can be used as following:
[CustomAction] public static ActionResult Execute(Session session) { string userData = session.CustomActionData["USERDATA"]; return ActionResult.Success; }