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.

Monday, February 11, 2013

Adding UI to WiX Installer with Visual Studio 2012


This is a continuation of my last post regarding 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”. In this post I want to move into some of the User Interface and adding a custom dialog to the install sequence.

First Steps

The first thing I did was add a Reference from my Wix Installer Project to WixUIExtension library. You can find this in the bin directory of the Wix Toolset installation folder. This will tell the linker (light) to link in that library when you build the installer like running light.exe –ext WixUIExtension Product.wixobj from the command line.
The tutorial had previously broken out the manual into a separate feature, so I broke out the manual into a separate ComponentGroup and broke out the Documentation feature from the MainProgram feature. We can give the user the option to install either or both of these features now. I added Title and Description fields for all the features. The Title is the name of the feature the user will see in the installer treeview. The Description will appear alongside the feature when the user clicks on it. As per the tutorial I also added a Display=”expand” attribute and ConfigurableDirectory=”INSTALLFOLDER” attribute to the Complete Feature. The first will have the tree item expanded by default to show the two component features. The second attribute will allow the user to select an install directory(??).
The tutorial sets the Level of the Documentation to 1000. Since I don’t plan on using the “Mondo” UI, which includes the Typical, Custom and Complete installation options, I don’t think this is really necessary but will leave it. Anything with level 1-3 will be installed in a “Typical” installation. Everything above 3 will be left out of a Typical install but can be installed in the Custom and Complete options.

<Feature Id="Complete" Title='Foobar 1.0' Description='The complete package.'
            Display='expand' Level='1' ConfigurableDirectory='INSTALLFOLDER'>
    <Feature Id='MainProgram' Title='Program' Description='The main executable.' Level='1'>
        <ComponentGroupRef Id="ProductComponents" />
        <ComponentGroupRef Id="Shortcuts" />
    </Feature>
    <Feature Id='Documentation' Title='Documentation' Description='The instruction manual.' Level='1000'>
        <ComponentGroupRef Id='DocumentationComponents' />
    </Feature>
</Feature>
...
<Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
        <Directory Id="ProgramFilesFolder">
            <Directory Id="INSTALLFOLDER" Name="WixTutorial" />
        </Directory>
        <Directory Id="ProgramMenuFolder" Name="Programs">
            <Directory Id="ProgramMenuDir" Name="Foobar 1.0" />
        </Directory>
        <Directory Id="DesktopFolder" Name="Desktop" />
    </Directory>
</Fragment>

UI Wizardry

The next two lines that I added as children to the Product element are all it takes to add some basic user interface to the installer. If you went through the tramontana tutorial you saw the Mondo set of Wix UI. That contains all the built-in UI features for Wix. The Feature Tree set contains most of those screens but always assumes a Custom installation and allows the user to select the features to be installed from the feature tree. The ErrorProgressText extension uses Wix localized error and progress text rather than the built-in Windows Installer messages.

<UIRef Id="WixUI_FeatureTree" />
<UIRef Id="WixUI_ErrorProgressText" />



In this setup scheme any feature that is not Level One will show as not being installed by default as our Documentation feature shows above. The figure also shows the “Browse. . .” button for the user to change the installation directory if desired.

New Link in the Chain

Now we want to add a custom dialog into our setup sequence. I start off by adding the new .wxs file by right-clicking on the Wix project and doing Add > New Item > WiX > WiX File. I give the new file the name UserRegistrationDlg.wxs. Now I have a Wix file with an empty fragment.

To this fragment I add a UI element with a single Dialog. The Dialog attributes are fairly self-explanatory. The [ProductName] variable will be replaced by the Name attribute of our Product element. The NoMinimize=”yes” attribute indicates that the user cannot minimize the dialog. The dialog is compsed of different Control elements of different types.  Without going too much into designing new dialogs, I want to point out the Edit type, MaskedEdit type, and PushButton controls the new dialog has. The Edit type allows the user to input text that is written to a property of the install session. In this case we have the USERNAME and COMPANYNAME properties. The MaskedEdit allows the user to input text in a specific format. We will specify the PIDTemplate in the main file. The PushButton controls publish events. Primarily they are navigating back and forth between different interface dialogs. Notice that the “Next” button will also publish an event “ValidateProductID”.  This event tells the installer to create a ProductID with the input from the PIDKEY property if the key fits the format specified by the template. Also a condition on moving to the Next dialog, CustomizeDlg, is that the ProductID must exist. Thus, the user will not be able to move to that dialog until they have input a proper PIDKEY. Later we’ll write a custom action to handle this validation.

xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
       <Fragment>
        <UI>
            <Dialog Id="UserRegistrationDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
                <Control Id="NameLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="&User Name:" />
                <Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="USERNAME" Text="{80}" />
                <Control Id="OrganizationLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="&Organization:" />
                <Control Id="OrganizationEdit" Type="Edit" X="45" Y="122" Width="220" Height="18" Property="COMPANYNAME" Text="{80}" />
                <Control Id="CDKeyLabel" Type="Text" X="45" Y="147" Width="50" Height="10" TabSkip="no">
                    <Text>CD &Key:</Text>
                </Control>
                <Control Id="CDKeyEdit" Type="MaskedEdit" X="45" Y="159" Width="250" Height="16" Property="PIDKEY" Text="[PIDTemplate]" />
                <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
                    <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
                </Control>
                <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
                    <Publish Event="ValidateProductID" Value="0">1</Publish>
                    <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
                    <Publish Event="NewDialog" Value="CustomizeDlg">ProductID</Publish>
                </Control>
                <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
                    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
                </Control>
                <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
                <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>Please enter your customer information</Text>
                </Control>
                <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
                <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
                    <Text>{\WixUI_Font_Title}Customer Information</Text>
                </Control>
                <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            </Dialog>
        </UI>
       </Fragment>
</Wix>

We next need to fit our dialog into the sequence of dialogs in our UI. It should go between the LicenseAgreementDlg, specified in our “Back” action, and the CustomizeDlg specified by the “Next” action. If you followed the tutorial you will notice that I’ve traded the CustomizeDlg in for the SetupTypeDlg that the tutorial uses. The CustomizeDlg is what the FeatureTree uses, as we don’t allow the user to change the setup type as they can with the Mondo UI.

In my product file I create a new UI container and move the references to WixUI_FeatureTree and WixUI_ErrorProgressText to it. I also add the DialogRef to the new dialog we created. Finally I add the Publish elements that establish where to place our new dialog in the UI sequence. The Order attribute gives a precedence to the publish events. Those with a higher number will be fired before lower ones. Since the WixUI_FeatureTree  set adds its own hooks to the buttons on the builtin dialogs, we need to add order to our publish events for them to be fired before the built-in events. The dialog reference (http://wix.sourceforge.net/manual-wix3/WixUI_dialog_reference_toc.htm) explains the different built-in dialogs and what is included in each UI set.

Within the Fragment I also include the PIDTemplate property as well as a property element for the PIDKEY property that tells the installer not to log its value (Hidden=’yes’). Our property tells the user to enter 3 digits in each of two fields. In the Product element above I reference this UI container. The MaskedEdit control and templates are explained here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa369797(v=vs.85).aspx.

. . .
    <UIRef Id="MyWixUI_FeatureTree" />
   
</Product>
. . .
<Fragment>
    <UI Id="MyWixUI_FeatureTree">
        <UIRef Id="WixUI_FeatureTree" />
        <UIRef Id="WixUI_ErrorProgressText" />
        <DialogRef Id="UserRegistrationDlg" />

        <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="UserRegistrationDlg" Order="3">LicenseAccepted = "1"</Publish>
        <Publish Dialog="CustomizeDlg" Control="Back" Event="NewDialog" Value="UserRegistrationDlg" Order="2">1</Publish>
    </UI>
    <Property Id="PIDTemplate"><![CDATA[12345<### ###>@@@@@]]></Property>
    <Property Id="PIDKEY" Hidden='yes' />
</Fragment>

And with that you should be able to rebuild the installer and run it. The Wix VisualStudio tool knows to compile and link both dialog files. You should see our new dialog after the license agreement and see the key validation working. Next we’ll walk through creating a custom action to do that validation.