Pieter van der Westhuizen

How to create WiX custom actions

The WiX Toolset provides a number of standard actions that are sufficient for most installers. If, however, you need more flexibility over the installation process the WiX toolset provides custom actions.

In this example, we’ll write a simple custom action that will prompt the user to enter their product registration information. If this information is valid the installation will continue, if not, the installer will exit.

The WiX setup project

In my last article where we discussed how to implement a WiX installer upgrade, we’ve created a WiX installer project for a simple Windows Forms application. For this example, we’ll re-use the same setup and Windows forms project. To recap, our Product.wxs file was as follows:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="https://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" Name="Win App" Language="1033" Version="1.0.0.0" Manufacturer="WinApp Software Inc." UpgradeCode="a9b1d837-9b09-491b-bd81-b794560745a4">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
 
    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
 
    <Media Id="1" Cabinet="WinApp.cab" EmbedCab="yes" />
 
    <Feature Id="Executable" Level="1">
      <ComponentRef Id="Executable" />
      <ComponentRef Id="Documentation" />
      <ComponentGroupRef Id="RegistryGroup" />
    </Feature>
 
    <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
    <UIRef Id="WixUI_InstallDir" />
 
  </Product>
 
  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLDIR" Name="Win App">
          <Component Id="Executable" Guid="7B289C8E-6F5B-4A7B-A9A1-E78A89239714">
            <File Id="WinAppExe" Name="WinApp.exe" Source="..\WinApp\bin\Debug\Winapp.exe" Vital="yes" />
            <RemoveFolder Id="INSTALLDIR" On="uninstall" />
          </Component>
          <Component Id="Documentation" Guid="E4AA4677-6DDA-4746-A956-0A636DBB2995">
            <File Id="HelpRtf" Name="Help.rtf" Source="..\WinApp\bin\Debug\Help.rtf" Vital="yes" />
            <RemoveFile Id="RemoveReadMe" Name="ReadMe.txt" On="both"/>
          </Component>
        </Directory>
      </Directory>
    </Directory>
 
    <ComponentGroup Id="RegistryGroup">
      <Component Id="_69CB4E7B_273F_4510_B885_4633736280D0" Guid="2EC2BF60-997B-44BB-BA82-C5760FB48A19" Transitive="no" Directory="TARGETDIR">
        <RegistryKey Root="HKLM" Key="Software" ForceCreateOnInstall="no" ForceDeleteOnUninstall="no" />
      </Component>
      <Component Id="_A159385C_39DE_404D_833B_6F83828512C0" Guid="1D85B1A4-ABDD-4EB5-8E70-82C609462AEB" Transitive="no" Directory="TARGETDIR">
        <RegistryKey Root="HKLM" Key="Software\WinApp Software" ForceCreateOnInstall="no" ForceDeleteOnUninstall="no" />
      </Component>
      <Component Id="_AAF14A16_5774_4861_AD86_C21F01857F59" Guid="E5F8A3A2-209A-4297-8B01-F7BB4FC6603B" Transitive="no" Directory="TARGETDIR">
        <RegistryValue Root="HKLM" Key="Software\WinApp Software" Type="string" Name="License" Value="Free" />
      </Component>
    </ComponentGroup>
 
  </Fragment>
 
</Wix>

Creating the WiX custom action

In order to create a WiX custom action, we first need to add a new C# Custom Action project to our solution. You’ll find the project template for this under Windows Installer XML:

Adding a C# Custom Action Project template to the solution.

This project will create a custom action project that contains a CustomAction.config and CustomAction.cs file. For this example, we’ll focus primarily on the CustomAction.cs file.

Open the CustomAction.cs file and rename the CustomActionMethod1 method to ShowLicenseInfo and change its code to the following:

[CustomAction]
public static ActionResult ShowLicenseInfo(Session session)
{
    frmLicenseInfo frmInfo = new frmLicenseInfo();
    if (frmInfo.ShowDialog() == DialogResult.Cancel)
        return ActionResult.UserExit;
 
    return ActionResult.Success;
}

This code will cause the license information form to be shown once the custom action is invoked. If the user clicks the Cancel button on the license information form, the custom action will exit by returning ActionResult.UserExit. If the user enters the license information correctly and clicks the Next button, the custom action will return ActionResult.Success.

Since, we want to display a dialog to prompt the user to enter registration information, we first need to create the user interface. Start by adding a new Windows Form to your project.

Adding a new Windows Form to your project

The design of the form should resemble the following image:

The design of the Windows form

Select the form in the Visual Studio forms designer and set its CancelButton to the name of the Cancel button, in this case it is btnCancel. This will ensure that when the user clicks on the Cancel button the dialog result of the form will be returned as Cancel.

Switch to the forms’ code-behind and add the following to the forms’ constructor:

public frmLicenseInfo()
{
    InitializeComponent();
    Application.EnableVisualStyles();
    this.TopMost = true;
}

The above code will enable visual styles for the form as well as make it the top-most form. If we do not set the TopMost property to true, the form will be hidden behind the standard WiX UI.

Now, double-click the Next button to generate a Click event handler for it and add the following code:

private void btnNext_Click(object sender, EventArgs e)
{
    bool valid = false;
    if (!String.IsNullOrEmpty(txtName.Text) && !String.IsNullOrEmpty(txtKey.Text))
        valid = VerifyLicenseInfo(txtName.Text, txtKey.Text);
 
    if (!valid)
    {
        MessageBox.Show("You license information does not appear to be valid. Please try again.", "Invalid info");
    }
    else
    {
        this.DialogResult = DialogResult.Yes;
    }
}

The code will first check whether the registration information has been completed and then call the VerifyLicenseInfo method. This method is used to validate the registration information and can either connect to a licensing server or run a license key algorithm to make sure the user has entered a valid product key.

Adding the custom action to the WiX source file (.wxs)

Next, we need to add the necessary XML mark-up to our Product.wxs file to show the license information form. Add the following to your Product.wxs file below the Product element:

<Fragment>
  <Binary Id="CustomActionBinary" SourceFile="$(var.MyCustomAction.TargetDir)$(var.MyCustomAction.TargetName).CA.dll"/>
  <CustomAction Id="LicenseInfoCustomAction" BinaryKey="CustomActionBinary" DllEntry="ShowLicenseInfo" Execute="immediate" Return="check"/>
</Fragment>

The Binary element must point to the DLL file that is generated by our custom action project. You’ll notice that we add .CA.dll to the end of the custom action projects’ target name. This is because the custom action project generates two DLL files. The DLL that ends in .CA.dll contains a format that is callable from the MSI engine and this is the file we need to reference.

Before you can reference it in the WiX source file, we first need to add a reference to the custom action project in our solution. To do this, right click on the References folder in the WiX setup project and select Add Reference…

In the Add Reference dialog, select the Projects tab and add the MyCustomAction project to the list of selected projects and components.

Adding the MyCustomAction project to the list of selected projects and components

The CustomAction element is used to specify the custom action we’ve created. The BinaryKey attribute points to the id of the Binary element we’ve added earlier and the DllEntry attribute should be the name of the method in the custom action that ought to be invoked, in this case set it to ShowLicenseInfo.

Finally, add the following inside the Product element:

<InstallExecuteSequence>
  <Custom Action='LicenseInfoCustomAction' Before='InstallFinalize'>NOT Installed</Custom>
</InstallExecuteSequence>

The above code specified that the custom action should be run before the InstallFinalize installation step. By setting the element value to NOT Installed, we told the WiX installer to only run the installation when it is not already installed, thus it will not run when the user uninstalls the application.

The complete listing for the Product.wxs file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="https://schemas.microsoft.com/wix/2006/wi">
  <Product Id="*" Name="Win App" Language="1033" Version="2.0.0.0" Manufacturer="WinApp Software Inc." UpgradeCode="a9b1d837-9b09-491b-bd81-b794560745a4">
    <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
 
    <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
 
    <Media Id="1" Cabinet="WinApp.cab" EmbedCab="yes" />
 
    <Feature Id="Executable" Level="1">
      <ComponentRef Id="Executable" />
      <ComponentRef Id="Documentation" />
      <ComponentGroupRef Id="RegistryGroup" />
    </Feature>
 
    <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
    <UIRef Id="WixUI_InstallDir" />
 
    <InstallExecuteSequence>
      <Custom Action='LicenseInfoCustomAction' Before='InstallFinalize'>NOT Installed</Custom>
    </InstallExecuteSequence>
 
  </Product>
 
  <Fragment>
    <Binary Id="CustomActionBinary" SourceFile="$(var.MyCustomAction.TargetDir)$(var.MyCustomAction.TargetName).CA.dll"/>
    <CustomAction Id="LicenseInfoCustomAction" BinaryKey="CustomActionBinary" DllEntry="ShowLicenseInfo" Execute="immediate" Return="check"/>
  </Fragment>
 
  <Fragment>
    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLDIR" Name="Win App">
          <Component Id="Executable" Guid="7B289C8E-6F5B-4A7B-A9A1-E78A89239714">
            <File Id="WinAppExe" Name="WinApp.exe" Source="..\WinApp\bin\Debug\Winapp.exe" Vital="yes" />
            <RemoveFolder Id="INSTALLDIR" On="uninstall" />
          </Component>
          <Component Id="Documentation" Guid="E4AA4677-6DDA-4746-A956-0A636DBB2995">
            <File Id="HelpRtf" Name="Help.rtf" Source="..\WinApp\bin\Debug\Help.rtf" Vital="yes" />
            <RemoveFile Id="RemoveReadMe" Name="ReadMe.txt" On="both"/>
          </Component>
        </Directory>
      </Directory>
    </Directory>
 
    <ComponentGroup Id="RegistryGroup">
      <Component Id="_69CB4E7B_273F_4510_B885_4633736280D0" Guid="2EC2BF60-997B-44BB-BA82-C5760FB48A19" Transitive="no" Directory="TARGETDIR">
        <RegistryKey Root="HKLM" Key="Software" ForceCreateOnInstall="no" ForceDeleteOnUninstall="no" />
      </Component>
      <Component Id="_A159385C_39DE_404D_833B_6F83828512C0" Guid="1D85B1A4-ABDD-4EB5-8E70-82C609462AEB" Transitive="no" Directory="TARGETDIR">
        <RegistryKey Root="HKLM" Key="Software\WinApp Software" ForceCreateOnInstall="no" ForceDeleteOnUninstall="no" />
      </Component>
      <Component Id="_AAF14A16_5774_4861_AD86_C21F01857F59" Guid="E5F8A3A2-209A-4297-8B01-F7BB4FC6603B" Transitive="no" Directory="TARGETDIR">
        <RegistryValue Root="HKLM" Key="Software\WinApp Software" Type="string" Name="License" Value="Free" />
      </Component>
    </ComponentGroup>
 
  </Fragment>
 
</Wix>

After building the solution and running the MSI, you ought to see the license information form just before the installation finishes.

Thank you for reading. Until next time, keep coding!

Available downloads:

Sample WiX Custom Actions project

You may also be interested in:

31 Comments

  • Frank says:

    Hi Pieter,

    thanks for this tutorial. That will help me to develope “my” custom dialog during installation process. Your licenceInfo dialog result is the click of the cancel or next button. I’am planning a list of CheckBox items in “my” dialog to let the user decide, which application he wants to install. How can I get his decision from the dialog window into the WIX project? How can I handle this information in the WIX code?
    Do you have any suggestions?
    Rgs,
    Frank

  • Pieter van der Westhuizen says:

    Hi Frank,

    Thank you for reading!
    It sounds like you want the user to choose which feature to install. I wouldn’t recommend you use a custom action for this.
    Rather, have a look at the FeatureTree UI that the WiX toolset provides – this gives the user a UI to choose which feature to install. You can use this by adding each application you want to install as a feature in your installer.

    The UIRef in this case is : < UIRef Id=”WixUI_Advanced” />

    Hope this helps!

  • Loon says:

    Hi Pieter,

    I would like to ask how to do the feature tree and user able to select which feature to be un-installed instead of whole application.

    Thanks.

    Best Regards,
    Loon

  • Pieter van der Westhuizen says:

    Hi Loon,

    You can have a look at the Wix Feature Element And a pretty good blog post about Wix Feature conditions here

    I’ve not had to build a setup that allows removal of only certain features yet, although I’m sure it would be possible. This might not be a bad idea for a future blog post.

    Hope this helps!

  • Loon says:

    Hi Pieter,

    Thanks for your reply. Do you have any idea about how to do the software prerequisite check (e.g. .Net framework 4.0 required) then display a list of message within a message box. For example,

    You must install:
    .Net 4.0
    .Google Chrome
    etc

    Thanks.

  • Loon says:

    Besides, Hope you can create more interesting article about WiX Toolset.

    Thanks.

  • Pieter van der Westhuizen says:

    Hi Loon,

    You can use Wix to check prerequisites, for example the following checks whether the .Net 4.5 framework is installed:

    <PropertyRef Id=”NETFRAMEWORK40CLIENT”/>
    <Condition Message=”This application requires .NET Framework 4.5. Please install the .NET Framework then run this installer again.”>
    <![CDATA[Installed OR NETFRAMEWORK40CLIENT]]>
    </Condition>

    To display a list of prerequisites takes a bit more work, there is a whole discussion about it here

    Hope this helps!

  • Loon says:

    Hi Pieter,

    Thanks for your reply. However, do you have any ideas for create a custom UI Dialog
    to display list of prerequisite software that was not installed on target computer during set-up installation. (I don’t want use condition message and display the error message one by one).

    You may email to me for further discussion.

    Thanks, have a nice day.

    Best Regards,
    Loon

  • Pieter van der Westhuizen says:

    Hi Loon,

    Unfortunately, I’ve never had to display a list of prerequisites in a list box using WiX, so I’d have to do some research.
    However, maybe try some of the suggestions mentioned in this discussion : https://windows-installer-xml-wix-toolset.687559.n2.nabble.com/How-to-populate-a-list-box-in-a-msi-dialog-during-runtime-td3811329.html

  • Loon says:

    Hi Pieter,

    Thanks for your time and reply ! Looks like I have to put time to work out some prototype. Beside, do you have any contact email ? If yes, please drop me a message (tenghonloon@hotmail.com)

    Thanks, good day !

    Best Regard,
    Loon

  • Pieter van der Westhuizen says:

    Loon,

    I’ve sent you an email.

  • Siddique says:

    Thanks for the article. It is very useful article. Due to this article, I completed my installer project.

  • Joe Healy says:

    .ca.dll – omg . Thanks much…

  • Jack says:

    Hi Pieter,

    Thanks for your article.I have a question here, is it possible to install some other applications, like 7-zip or some.exe(non msi package) in some step of the wizard process? I want the installation to be behind one of the wizard dialogs, not after the “Finish Dialog” showing, the installation begin itself.

    Thanks & Regards
    Jack

  • Pieter van der Westhuizen says:

    Hi Jack,

    Have a look at the Quiet Execution Custom Action. From the documentation:

    The QtExec custom action allows you to run an arbitrary command line in an MSI-based setup in silent mode

    I think this might be what you’re looking for.

    Hope this helps!

  • Jack says:

    Pieter, appreciate for your reponse, I’ll have a try. Thanks again.

  • Srilakshmi says:

    Hi
    I want to get msi installation wizard inputs given from User using wizard of MSI.
    How will be able to get this given inputs and stop installing msi.

  • Andrei Smolin (Add-in Express Team) says:

    Hello Srilaksmi,

    Check the properties in the code of the forms the installer shows. The properties whose name is in uppercase are public and you can use them in custom actions. Below is an example of such a variable declared in the form Checkboxes (A):

  • Shreyas says:

    I am getting the following error. i know this is an old post but if you are still active here, I would really appreciate a reply.

    Error:
    The system cannot find the file ‘C:\Users\Shreyas\Desktop\AlignNET_FileUploadSrv\AlignNET_FileUploadSrv\CustomerInfoAction\bin\Debug\CustomerInfoAction.CA.dll’.

  • Andrei Smolin (Add-in Express Team) says:

    Hello Shreyas,

    We cannot help you with this issue as it obviously doesn’t relate to the demo project described in this blog. There are too many ways to get this error; I simply unable to list all possibilities.

  • Pablo says:

    Thank you for this great tutorial.

    I have create a Custom Action in the same way. It works fine but the problem is when I show my Window Form using “showDialog()”, this window is not modal which could be a problem in the installation process.
    I have checked your example and there is the same “problem”.

    Is there any way to fix it?

  • Marco says:

    Hi,
    I followed your tutorial but when I use the msbuild command to build it I had the error “Undefined preprocessor variable ‘$(var.MyCustomAction.TargetDir).
    How can I solve this?

    Thanks

  • Andrei Smolin (Add-in Express Team) says:

    Hello Marco,

    I build that project using Visual Studio or msbuild.exe with no problem. I start {path}\msbuild.exe {anotherPath}\WinAppSetup.wixproj and see that parameters passed to candle.exe specify many variables:

    {Path to WiX}\candle.exe [… skiped ….] -dMyCustomAction.TargetDir={path}\WixCustomActions\MyCustomAction\bin\Debug\ [… skiped ….] -dMyCustomAction.TargetName=MyCustomAction

    Find the complete msbuild output at https://temp.add-in-express.com/support/sample_msbuild_output.txt. To produce the full output, modify the main project; say, change its version.

    Hope this helps.

  • Dorababu says:

    How to copy bin directory to the installed directory

  • Dorababu says:

    And how can I use the own content instead of lorpesum

  • Andrei Smolin (Add-in Express Team) says:

    Hello Dorababu,

    Adding a file is a standard feature of an installation software product: you should check the manual; see https://wixtoolset.org/documentation/manual/.

    As to using a custom license instead of a dummy one, see https://stackoverflow.com/questions/41867565/wix-installer-product-end-user-agreement-shows-dummy-text.

  • Jobaidul Alam Jamali says:

    I downloaded the sample project, built the setup project through VS 2017 and ran the installer. Everything was fine except for the session value. In the registraioninfo.txt file, I don’t see the user name and email I provided during the installation time other than a comma (,). Has anything changed? Please help.

  • Andrei Smolin (Add-in Express Team) says:

    Hello Jobaidul,

    I can’t find that file.

  • kamal says:

    thanks for your helping mind and please show how to add pidkey custom action in wix toolset and show how to run that action please help me for this task.
    thank you ….

Post a comment

Have any questions? Ask us right now!