Setup Factory for Windows Installer

How To: Add a Serial Number Validation Screen to Your Project

This article will show you how to collect a user's name, company name and/or serial number from them during the installation process. It will also demonstrate how to use a custom action DLL to validate the serial number according to your needs.

Before we start it is important to note that serial number collection and validation during a Windows Installer installation is not a highly secure process. Since Windows Installer files are just databases that are editable with commonly available tools and APIs, bypassing serial number security is not difficult for a person knowledgeable about the MSI file format. Ultimately, your application itself should have protection measures in it to ensure license management and serial number validation. However, it is still an added level of protection as well as a convenience to have this information collected during your installation routine.

This tutorial assumes that you have a project already underway that you want to add the dialog to.

1. Add the CollectUserInformationDlg dialog

- Select Project > Dialogs from the menu.

- Click the Add button and add the "CollectUserInformationDlg" dialog.

- Move the dialog into the correct position in your dialog sequence (usually before the License Agreement dialog).

- Click OK to accept the changes.

2. Edit the PIDTemplate.

- Next you will need to edit the PIDTemplate (Product ID Template) that will determine the format of the serial number and affects how it is displayed to the user.

- Select Project > Settings from the menu and then select the Properties tab.

- Edit the PIDTemplate property to reflect the formatting that you require for your serial number. For a description of how the string should be formatted, see the "MaskedEdit Control" topic in the MSDN online documentation (see link at the bottom of this page).

- Click OK to accept the changes to the PIDTemplate property.

At this point you are done if all that you want to do is collect the serial number from the user without actual validation. Windows Installer will validate that the key the user enters matched the pattern that you specified in the PIDTemplate property. During installation, the accepted key will be stored in the ProductID MSI property (as well as the PIDKEY property). You could also use a Registry action to write it out to the Registry for your program to retrieve later.

You only need to go onto the following steps if you want to perform custom validation on the serial number.

3. Create a custom action DLL that validates the serial number.

This step requires you to know how to create and code a C/C++ Windows DLL. Here is an example of a C++ function that validates the serial number entered to ensure that the first four digits are "1234":

UINT __stdcall ValidateSerial(MSIHANDLE hInstall)
{
     TCHAR szPIDKEY[255];
     DWORD dwLen = sizeof(szPIDKEY)/sizeof(szPIDKEY[0]);

    // Get the "PIDKEY" property
    UINT res = MsiGetProperty(hInstall, _T("PIDKEY"), szPIDKEY, &dwLen);
    if(res != ERROR_SUCCESS)
    {
        //fail the installation
        return 1;
    }

    int nUILevel = 5; // Assume full UI
     TCHAR szUILevel[10];
    DWORD dwUILevelStringLength = sizeof(szUILevel)/sizeof(szUILevel[0]);;
    res = MsiGetProperty(hInstall, _T("UILevel"), szUILevel, &dwUILevelStringLength);
    if(res == ERROR_SUCCESS)
    {
        nUILevel = _ttoi(szUILevel);
    }
    bool bSerialIsValid = false;

    // Validate the serial number szPIDKEY according to your algorithm
    // put the result in bSerialIsValid
    // For demonstration purposes, the validation will pass is the
    // szPIDKEY values is greater than 5 characters in length and the first
    // four characters are "1234"
    if(lstrlen(szPIDKEY) > 5)
    {
        TCHAR szFirstFourNumbers[5];
        memset((void*)szFirstFourNumbers,0,sizeof(szFirstFourNumbers));
        lstrcpyn(szFirstFourNumbers,szPIDKEY,5);
        if(lstrcmp(szFirstFourNumbers,_T("1234")) == 0)
        {
            bSerialIsValid = true;
        }
    }

    TCHAR szPropertyValue[10];
    if(bSerialIsValid)
    {
        lstrcpy(szPropertyValue,_T("TRUE"));
    } else
    {
        if(nUILevel > 2)
        {
            // Show if not completely silent installation
            MessageBox(0, _T("Invalid Serial Number"), _T("Error"), MB_ICONSTOP);
        } else
        {
            // Log the failure out
            PMSIHANDLE hRecord = MsiCreateRecord(1);
            MsiRecordSetString(hRecord, 0, "Log: [1]");
            MsiRecordSetString(hRecord, 1, "Serial number validation failed.");
            MsiProcessMessage(hInstall, INSTALLMESSAGE_INFO, hRecord);
        }
        lstrcpy(szPropertyValue,_T("FALSE"));
    }

    res = MsiSetProperty(hInstall, _T("VALID_SERIAL"), szPropertyValue);
    if(res != ERROR_SUCCESS)
    {
        return 1;
    }
    // Return OK even if the validation failed. This way it sets
    // the property and allows the installer to react accordingly.
    return 0;
}

In the code above, it stores "TRUE" or "FALSE" in a property called "VALID_SERIAL". This source code is available in a Visual C++ 2005 project in the file "MSISerialValidateSample.zip" located in the "Samples\Source Code" sub-folder of the Setup Factory for Windows Installer installation folder. The compiled DLL that can be used for example purposes is called "MSISerialValidateSample.dll" and is located in the "Custom Actions" sub-folder of the Setup Factory for Windows Installer installation folder.

4. Add the custom action DLL to your project.

- Select Actions > Custom Actions from the menu.

- Add a new Call DLL action.

- On the Settings tab of the Call DLL dialog, select "Internal binary" as the Location and locate the DLL on your system in the Source file field. If you want to use the sample DLL that comes with Setup Factory, enter the following for Source file: "$(var.SetupFactoryFolder)\Custom Actions\MSISerialValidateSample.dll" (no quotes).

- Enter the DLL function name in the field in the option section. If you are using the sample DLL, use "ValidateSerial" (no quotes)

- On the Attributes tab of the Call DLL dialog, change the ID field to be "CustomValidateSerial" (you can call it anything you want, actually).

- Change the Scheduling option to be "Immediate - Once Per Process". This is because we only want to run the action once - either in the UI sequence or in the execute sequence (if the UI is not displayed).

- In the Timing section, create a new Custom Action Timing using the following values:

- Sequence: InstallExecuteSequence

- Condition: NOT Installed

- Execute: Before

- Relative action: CostInitialize

- The timing options above will ensure that the serial check is done even if the installer is run with the /quiet option. In this case the user running the installer will have to provide the serial number on the command line (i.e. "PIDKEY=1234-5678-9012-3456").

- Click OK to accept the new custom action.

5. Add a Terminate Installation custom action to abort the installation if the validation fails.

- Select Actions > Custom Actions from the menu.

- Add a new Terminate Installation action.

- On the Settings tab of the Terminate Installation dialog, enter the message that you want to appear and/or have written to the log file if validation fails in the Message field. For example: "Invalid serial number." (no quotes).

- On the Attributes tab of the Terminate Installation dialog, change the ID field to be "ExitIfNotValidSerial" (you can call it anything you want, actually).

- Change the Scheduling option to be "Immediate".

- In the Timing section, create a new Custom Action Timing using the following values:

- Sequence: InstallExecuteSequence

- Condition: VALID_SERIAL="FALSE" and NOT Installed

- Execute: After

- Relative action: CustomValidateSerial

- The timing options above will ensure that the installation aborts if the VALID_SERIAL property is not set to "TRUE".

- Click OK to accept the new custom action.

6. Add the custom action call from the user interface.

- Select Project > Dialogs from the menu.

- Select the "CollectUserInformationDlg" dialog.

- Select the Published Events tab.

- Click the Add button to add a new published event. Enter the following values:

- Type: Event

- Event: DoAction

- Arguments: CustomValidateSerial

- Condition: ProductID

- Set explicit order: Unchecked

- This will call our custom action function as long as ProductID was set by the ValidateProductID event above.

- Move this new event above the NewDialog event. It should now be the third of four events listed.

- Double-click the NewDialog event (fourth in the list).

- Change the Condition to: ProductID AND VALID_SERIAL="TRUE"

- This will ensure that the next dialog is opened only if the serial number validation passed.

- Click OK on each dialog to save these changes until you are back at the main design environment. You should now save your project.

At this point you can build and test your installer. The dialog that collects the serial number should not allow you to continue until you enter a valid serial number.

You can see a complete sample project of the above steps. It is called "CollectUserInfo_Sample.sufproj" and is located in the "Samples\Projects" sub-folder of the Setup Factory for Windows Installer installation folder.

More Information

MSDN Online: MaskedEdit Control

MSDN Online: PIDTemplate Property

MSDN Online: ProductID Property

MSDN Online: ValidateProductID Action

MSDN Online: ValidateProductID ControlEvent