Scythe Studio website employs cookies to improve your user experience (Read more)

How tu customise installer behaviour with Qt Installer Framework

In previous posts of this series, we briefly touched on the topic of scripting, while describing how to add custom pages to the installer UI. In this entry we will go much deeper. You will be able to learn what types of scripts are available and how to create them in the Qt Installer Framework – all of that with some handy examples. Let’s go!

 

API overview

Before doing the scripting itself, let’s explain what is the Scripting API and how it works.

In the Qt Installer Framework, Scripting API is basically a set of JavaSctipt objects. Scripts created with this API use files with .qs extension, which are executed by QJSEngine. You can use scripts to execute certain actions or modify the installer behaviour during runtime, for example by adding custom pages depending on which components were selected for installation. In the case of installers, there are two main types of scripts: controller scripts and component scripts.

Controller scripts are used for interacting with certain parts of the installer’s UI or functionality. For instance, you can modify existing pages or simulate user clicks in order to automate some parts of the installation. For each installer, there can be only a single controller script that is pointed in the config.xml file.

Component scripts are used to handle behaviour for selected component in the installer, so each component can have its own script. Because of that, each installer can contain several component scripts. They are executed when the component is selected for installation. The component script you want to use have to be pointed in the package.xml file.

Controller script basics

First, let’s talk a little bit about scripts structure in Qt Installer Framework. Both controller and component scripts share almost the same structure – the only difference between them is based on what you can do inside the script and the name of the constructor.

A minimal valid script needs to contain at least a constructor. For beginning we can create script.qs file. In order to make this file be recognized as proper script we have to put the empty constructor in it. In the case of the controller script, it is the following constructor:

function Controller()
{
}

Now let’s implement some simple function to modify installer behaviour. Let’s say that we want to skip some pages in the installer automatically – for example, the introduction page. One of the simple solutions to do this is to invoke a click on the „Next” button when the selected page appears. To do so, we can use Controller.prototype.IntroductionPageCallback. The script should look as follows:

function Controller()
{
}

Controller.prototype.IntroductionPageCallback = function()
{
    gui.clickButton(buttons.NextButton);
}

In this script, we used two global objects that are accessible in controller scripts. The first one is the gui which is JavaScript global object that allows for interaction with the installer UI. The buttons, as the name implies, allow us to use buttons placed in installer pages. Besides those objects, each page has a set of its own methods, buttons and widgets. For example on component selection page you can use functions like selectAll() or selectComponent(id) in order to manage component selection. We won’t describe all of them in this post as it would take too long. If you want to get familiar with all available options, take a look at the documentation.

Now in order to use script in your installer, open configuration file for your installer and simply add the following tag to it:

<ControlScript>script.qs</ControlScript>

Don’t forget to place script.qs inside the configuration directory! Now your first controller script is hooked up – you can generate a new installer and give it a try. If you don’t remember how to do it, take a look at the first post of this series.

Component script basics

Now we have a functional controller script. Let’s create the basic component script. Just like in the case of controller script you have to start with a constructor – in this case, its name is Component. As a simple example of a component script, let’s use the one from previous post. Its purpose is to add custom page to the installer if the component is selected. To implement it we can use addWizardPage function:

function Component()
{
    installer.addWizardPage(component, "MyOtherPage", QInstaller.ReadyForInstallation);
}

A function that adds new page takes three parameters. First is the component which contains the registered name of the widget class you want to add – in this case, a global component object referencing the current Component was passed. The next argument is the name of the registered widget class you want to use. Last is the index of the existing page before which new page will be placed. In this case, CustomPage would be placed before the ReadyForInstallation page.

In case to make this script working you also have to add a page .ui file that will contain page design in the package directory while also register this file in package.xml. Besides that you also need to point the script in the package.xml to make it execute:

<Script>installscript.qs</Script>
<UserInterfaces>
    <UserInterface>CustomPage.ui</UserInterface>
</UserInterfaces>

If you want to learn more about customizing installer UI, take a look at following post.

Adding a shortcut 

As you are now familiar with the basics of writing scripts, we can now move on to more advanced use cases with examples. Let’s say we have a Windows app installer and we want to create an app shortcut on the desktop after finishing an installation. To implement such functionality we can use the component script as different executables can be used for different packages.

All we need to do is to add custom operation that will be executed when the component is installed. To do so we can use Component.prototype.createOperations hook with addOperation function:

function Component()
{
    // default constructor
}

Component.prototype.createOperations = function()
{
    try {
        // call the base create operations function
        component.createOperations();
        // check if we are on widnows machine
        if (installer.value("os") == "win") {
            try {
                // look for user profile directory
                var userProfile = installer.environmentVariable("USERPROFILE");
                installer.setValue("UserProfile", userProfile);
                component.addOperation("CreateShortcut", "@TargetDir@\\win64\\application.exe", "@UserProfile@\\Desktop\\My\ App.lnk");                
            } catch (e) {
                // Do nothing if key doesn't exist
            }
        }
    } catch (e) {
        print(e);
    }
}

Recursive installation 

There might be a case that despite libraries and other files that you can simply place in the deployment folder there might be some external tools that are needed to make your app working on the end device. A most common example of such a case are Microsoft Visual C++ Redistributable packages containing C and C++ runtime, that are needed on the device if you compiled an app using an MVSC kit.

You can simply place runtime .dll files in the deployment folder however, there is a catch. Antivirus software doesn’t really like using C and C++ runtime libraries that were not installed with Microsoft installer, as modified runtime can do some nasty things in the user’s computer. Using this approach you can make your app be recognized as malicious, and you never want such a thing to happen. What should you do?

Solution is quite simple – perform recursive installation of redistributable packages. It may sound fancy and hard to implement, but it all comes down to check if there are existing VC runtime on the device and, if not, executing redistributable packages installer from Microsoft. Let’s take a look at how it is done.

The first thing you need to do is download a redistributable packages installer from the Microsoft website. After that place it in data folder of the package.

Now let’s go with the scripting. As we are working with a single component, we need a component script. First, we need to crate a custom function that will allow us to check for existing VC runtime and execute installer if it is missing:

Component.prototype.installVCRedist = function()
{
    var registryVC2017x64 = installer.execute("reg", new Array("QUERY", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", "/v", "Installed"))[0];

    if (registryVC2017x64)
    {
        QMessageBox.information("vcRedist.install", "Install VS Redistributables", "The application requires Visual Studio 2017 Redistributables. Please follow the steps to install it now.", QMessageBox.OK);
        var dir = installer.value("TargetDir");
        installer.execute(dir + "/VC_redist.x64.exe", "/norestart", "/passive");
    }
}

In this function, we first check system registers to verify if there is VC runtime installed. If the installation is needed, a message box with proper information is shown. After clicking ok, the installer is executed. We are using the „TargetDir” path as the VC runtime installer is placed in the same place as the root directory of the installed app.

All is left to do is to execute the function after installation of the component is finished. To do so we can use signal and slots system that is also available in the script engine:

function Component()
{
    // constructor
    installer.installationFinished.connect(this, Component.prototype.installVCRedist);
}

Summary 

In this post, you learned what is Scripting API in Qt Installer Framework and how to use it.  Using this fresh knowledge you can now play and experiment with the installer scripts as the possibilities for applying them are endless. There are many more popular use cases for scripts than those presented in this post. In future posts we will present more of them, so stay tuned.

If you like this post subscribe to our newsletter and stay up to date.

Latest posts