Google Analytics

Tuesday, February 12, 2013

Custom Actions with WiX and Visual Studio 2012


Events and Actions

This is the third part of a series on using Wix 3.7 with Visual Studio 2012. Previously I walked through the Wix tutorial (http://wix.tramontana.co.hu/tutorial/ ) from “Getting Started” through “Putting it to Use” and then through the User Interface section from “First Steps” through “New Link in the Chain” to add some user interface dialogs including a custom dialog to our installer. Now I want to walk through creating a custom action to our install sequence.

Queueing Up

The Windows Installer runs through a series of actions specified in the .msi database. The tutorial mentions using the .msi editor Orca to investigate the sequence. Orca is a must-have tool for doing installer development. WiX has four tags to change the sequence of the events, suppress events and add additional events including custom ones: AdminUISequence, AdminExecuteSequence, InstallUISequence and InstallExecuteSequence. Changing this sequence is beyond the scope of what I want to do for now.

Extra Actions

Custom Actions need to be added as a child of the Product element in Wix. Additionally if you want the action to run within the install sequence you need to add it to one of the appropriate tags mentioned in Queueing Up. The tutorial describes using custom actions to do things like execute applications, set properties and display errors.  There are also a number of additional standard actions that can be queued up using a tag like InstallExecuteSequence.

What’s Not in the Book

I am going to have a user action that references a custom action I write. Following the tutorial’s lead, this action is going to check the validity of the product key entered by the user.  To add the code file for this we right-click the solution and Add > New Project.  Under Windows Installer XML select the C# Custom Action Project. We’ll call it CheckPID.



Wix adds the code file and stubs out the custom action. It also adds a config file with the necessary supportedRuntime tags included. I add my code to retrieve the PIDKEY property from the installer session. Then it writes the PIDACCEPTED value back to the session based on whether the PIDKEY that the user inputs stats with a “1” or not: the value is 1 if it does and 0 if it does not. Then our action is always going to return ActionResult.Success. If we returned a failure the installer would give the user an error and shut down. We want them to be able to stay at the dialog and insert the correct key.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;

namespace CheckPID
{
    public class CustomActions
    {
        [CustomAction]
        public static ActionResult CheckPID(Session session)
        {
            string Pid = session["PIDKEY"];
            session["PIDACCEPTED"] = Pid.StartsWith("1") ? "1" : "0";
            return ActionResult.Success;
        }
    }
}


We also need to add a reference from the Wix project to our CheckPID project. Right-click on the References item under the Wix project and do Add Reference and then select our new project from the Projects tab.

In the Product.wxs file I added a new Fragment that contains the CustomAction tag and the binary reference to the DLL to use. The SourceFile attribute of the Binary element is the source file where the action is. The CustomAction element then references the Binary element by Id. The DllEntry attribute of the CustomAction is the method of the binary that will be called.

<Fragment>
    <CustomAction Id='CheckingPID' BinaryKey='CheckPID.CA' DllEntry='CheckPID' />
    <Binary Id='CheckPID.CA' SourceFile='..\CheckPID\bin\Debug\CheckPID.CA.dll' />
</Fragment>

How to Manage

Notice above that the DLL we are referencing is not the expected CheckPID.dll. Wix uses the MakeSfxCA (for Make self-extracting custom action) utility to put a wrapper around the assembly our C# code creates to have a managed custom action. If you use the Wix Custom Action project as described above Wix will handle making that call to MakeSfxCA. If you just create a C# code library file you will need to add a post-build call to create that wrapper (http://wix.tramontana.co.hu/tutorial/events-and-actions/how-to-manage).

Control Your Controls

In the UserRegistrationDlg I replaced the Publish events with new ones.

<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
    <Publish Event="DoAction" Value="CheckingPID">1</Publish>
    <Publish Event="SpawnDialog" Value="InvalidPidDlg">PIDACCEPTED = "0"</Publish>
    <Publish Event="NewDialog" Value="SetupTypeDlg">PIDACCEPTED = "1"</Publish>
</Control>

The first Event DoAction has a value that refers back to the Id of our CustomAction. Then we check the PIDACCEPTED value that we set in our code. If the product Id is not accepted we are going to spawn a new dialog, InvalidPidDlg. If it is accepted we’ll advance to the CustomizeDlg dialog as before. Note that SpawnDialog creates a child dialog to the current dialog. NewDialog navigates to a new dialog.
As before when I added the User Registration dialog I add a new Wix .wxs file to the Wix project named InvalidPidDlg.wxs. To this file I added a UI container with the new dialog. The dialog contains the Error text to convey to the user, an exclamation point icon and a PushButton that will close the dialog sending us back to the parent UserRegistrationDlg dialog for us to re-enter the key. I also added the binary reference to the exclamation icon to include. You can get Exclam.ico from the tutorial files download (http://wix.tramontana.co.hu/system/files/samples/SampleAskKey.zip) or create your own.

xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Fragment>
    <UI>
      <Dialog Id="InvalidPidDlg" Width="260" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
        <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24"
          ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="Exclam.ico" />
        <Control Id="Return" Type="PushButton" X="100" Y="62" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Return">
          <Publish Event="EndDialog" Value="Return">1</Publish>
        </Control>
        <Control Id="Text" Type="Text" X="48" Y="10" Width="194" Height="45" TabSkip="no">
          <Text>
            The user key you entered is invalid.
            Please, enter the key printed on the label
            of the jewel case of the installation CD.
          </Text>
        </Control>
      </Dialog>
    </UI>

    <Binary Id="Exclam.ico" SourceFile="Exclam.ico" />
  </Fragment>
</Wix>

In Conclusion

You should be able to build and run the installer now. Submitting a product key that starts with anything other than a ‘1’ will display the Invalid PID dialog.  As I use WiX for our real-world cases I will post anything interesting or sticking points that come up.

5 comments:

Unknown said...

Hello,
I have a problem that when the custom action has a reference to another X.DLL (reference created inside visual studio 2012, custom action project)then during the installation, the error is always: X.DLL FileNotFound Exception. Do you have any suggestion ?

Ben Rice said...

Try these suggestions. I haven't tried doing that yet, but I would like to know if either of these or some other solution worked for you.

http://stackoverflow.com/questions/1591700/how-to-execute-a-wix-custom-action-dll-file-with-dependencies

http://stackoverflow.com/questions/4549498/wix-installer-custom-actions-ca-which-require-an-external-library-cant-find-i

Unknown said...

Hi, Ben

I am trying to run your sample and got error:

Error 4 ICE17: PushButton: 'Back' of Dialog: 'SetupTypeDlg' does not have an event defined in the ControlEvent table. It is a 'Do Nothing' button. C:\src\wix38\src\ext\UIExtension\wixlib\SetupTypeDlg.wxs 25 1 KevinSetupTest

Do you have any suggestion?
Thanks

Unknown said...

Hi,

I need to show Wpf screen while installing, for e.g: when you install WIX3.7 you can see an UI, and please provide your source code , it will be very usefull.Kindly try to give an article about BootStrapper application, Thanks a lot for your article

Anonymous said...

HI, I have tried to call to CustomActions and work fine. My Problem is that in the CustomActions method, i want to call a web service. I made a reference to it, but it does not work. How can I make a call to a Web Service? thanks