How to make JPackage installer display message after successful installation?

158 Views Asked by At

I have made a Windows installer for a JavaFX + Spring Boot application using the answer to:

However, the installer just quit after finishing its job without any notification.

My question is: How to make it display a message after successful/failed installation?

1

There are 1 best solutions below

0
jewelsea On

This is a Windows only solution.

You need to override the default jpackage WIX resources and customize the installer configuration.

Make sure you read the documentation on overriding jpackage resources before attempting this.

Launch your application after installation is complete

We can't customize the UI of the WIX installer at all (when jpackage from JDK 21 is used), as far as I can tell. The reason why is explained later in the answer.

But, we can customize the WIX installation configuration so that it will launch our application with a custom parameter after installation is complete. And then we can provide our own UI (in JavaFX) to handle the post installation tasks in any way we wish.

To do this, set a resource directory for jpackage, by uncommenting this line from the jpackage-maven-plugin section of the pom.xml in the answer referenced in the question.

<resourcedir>${project.basedir}/config/jpackage/resources</resourcedir>

Set a value for the temp directory that jpackage uses so that it persists after an execution:

<temp>${project.build.directory}/jpackage-temp</temp>

Then when jpackage runs, temporary files will be output to the build directory and you can view them there after a build.

Running maven in debug mode and jpackage in verbose mode is pretty much mandatory for developing this solution, as it will display the jpackage commands and the WIX commands in the build output that way, together with any errors that jpackage or WIX detects.

config/jpackage/resources/main.wxs

Create this new file which defines the Wix project configuration that jpackage will use for your application.

The file was created by copying the generated resource file from target/jpackage-temp/config/main.wxs to config/jpackage/resources/main.wxs. And then customizing it to launch the installed application on installation.

It will only launch the application if the application has never been installed or is installed and is being upgraded to a new version. If the same version of the application is already installed, the installer won't launch your application because the installer doesn't do anything in that case.

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

  <?ifdef JpIsSystemWide ?>
    <?define JpInstallScope="perMachine"?>
  <?else?>
    <?define JpInstallScope="perUser"?>
  <?endif?>

  <?define JpProductLanguage=1033 ?>
  <?define JpInstallerVersion=200 ?>
  <?define JpCompressedMsi=yes ?>

  <?include $(var.JpConfigDir)/overrides.wxi ?>

  <?ifdef JpAllowUpgrades ?>
    <?define JpUpgradeVersionOnlyDetectUpgrade="no"?>
  <?else?>
    <?define JpUpgradeVersionOnlyDetectUpgrade="yes"?>
  <?endif?>
  <?ifdef JpAllowDowngrades ?>
    <?define JpUpgradeVersionOnlyDetectDowngrade="no"?>
  <?else?>
    <?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
  <?endif?>

  <Product
    Id="$(var.JpProductCode)"
    Name="$(var.JpAppName)"
    Language="$(var.JpProductLanguage)"
    Version="$(var.JpAppVersion)"
    Manufacturer="$(var.JpAppVendor)"
    UpgradeCode="$(var.JpProductUpgradeCode)">

    <Package
      Description="$(var.JpAppDescription)"
      Manufacturer="$(var.JpAppVendor)"
      InstallerVersion="$(var.JpInstallerVersion)"
      Compressed="$(var.JpCompressedMsi)"
      InstallScope="$(var.JpInstallScope)" Platform="x64"
    />

    <Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />

    <Upgrade Id="$(var.JpProductUpgradeCode)">
      <UpgradeVersion
        OnlyDetect="$(var.JpUpgradeVersionOnlyDetectUpgrade)"
        Property="JP_UPGRADABLE_FOUND"
        Maximum="$(var.JpAppVersion)"
        MigrateFeatures="yes"
        IncludeMaximum="$(var.JpUpgradeVersionOnlyDetectUpgrade)" />
      <UpgradeVersion
        OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)"
        Property="JP_DOWNGRADABLE_FOUND"
        Minimum="$(var.JpAppVersion)"
        MigrateFeatures="yes"
        IncludeMinimum="$(var.JpUpgradeVersionOnlyDetectDowngrade)" />
    </Upgrade>

    <?ifndef JpAllowUpgrades ?>
    <CustomAction Id="JpDisallowUpgrade" Error="!(loc.DisallowUpgradeErrorMessage)" />
    <?endif?>
    <?ifndef JpAllowDowngrades ?>
    <CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
    <?endif?>

    <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>

    <CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />

    <!-- Standard required root -->
    <Directory Id="TARGETDIR" Name="SourceDir"/>

    <Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
      <ComponentGroupRef Id="Shortcuts"/>
      <ComponentGroupRef Id="Files"/>
      <ComponentGroupRef Id="FileAssociations"/>
    </Feature>

    <CustomAction Id="JpSetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />
    <CustomAction Id="JpSetARPCOMMENTS" Property="ARPCOMMENTS" Value="$(var.JpAppDescription)" />
    <CustomAction Id="JpSetARPCONTACT" Property="ARPCONTACT" Value="$(var.JpAppVendor)" />
    <CustomAction Id="JpSetARPSIZE" Property="ARPSIZE" Value="$(var.JpAppSizeKb)" />

    <?ifdef JpHelpURL ?>
      <CustomAction Id="JpSetARPHELPLINK" Property="ARPHELPLINK" Value="$(var.JpHelpURL)" />
    <?endif?>

    <?ifdef JpAboutURL ?>
      <CustomAction Id="JpSetARPURLINFOABOUT" Property="ARPURLINFOABOUT" Value="$(var.JpAboutURL)" />
    <?endif?>

    <?ifdef JpUpdateURL ?>
      <CustomAction Id="JpSetARPURLUPDATEINFO" Property="ARPURLUPDATEINFO" Value="$(var.JpUpdateURL)" />
    <?endif?>

    <?ifdef JpIcon ?>
    <Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
    <Icon Id="JpARPPRODUCTICON" SourceFile="$(var.JpIcon)"/>
    <?endif?>

    <CustomAction Id="LaunchApplication"
             ExeCommand='&quot;[INSTALLDIR]wininstalled.exe&quot; --start-mode=installed'
             Directory="INSTALLDIR"
             Impersonate="yes"
             Return="asyncNoWait"/>

    <InstallExecuteSequence>
      <Custom Action="JpSetARPINSTALLLOCATION" After="CostFinalize">Not Installed</Custom>
      <Custom Action="JpSetARPCOMMENTS" After="CostFinalize">Not Installed</Custom>
      <Custom Action="JpSetARPCONTACT" After="CostFinalize">Not Installed</Custom>
      <Custom Action="JpSetARPSIZE" After="CostFinalize">Not Installed</Custom>
      <?ifdef JpHelpURL ?>
        <Custom Action="JpSetARPHELPLINK" After="CostFinalize">Not Installed</Custom>
      <?endif?>
      <?ifdef JpAboutURL ?>
        <Custom Action="JpSetARPURLINFOABOUT" After="CostFinalize">Not Installed</Custom>
      <?endif?>
      <?ifdef JpUpdateURL ?>
        <Custom Action="JpSetARPURLUPDATEINFO" After="CostFinalize">Not Installed</Custom>
      <?endif?>

      <?ifndef JpAllowUpgrades ?>
      <Custom Action="JpDisallowUpgrade" After="JpFindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
      <?endif?>
      <?ifndef JpAllowDowngrades ?>
      <Custom Action="JpDisallowDowngrade" After="JpFindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
      <?endif?>
      <RemoveExistingProducts Before="CostInitialize"/>
      <Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
      <Custom Action="LaunchApplication" After="InstallFinalize">Not Installed</Custom>
    </InstallExecuteSequence>

    <InstallUISequence>
      <Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
    </InstallUISequence>

  </Product>
</Wix>

These lines were added to the WIX project file under the Product section (wininstalled is the name of the application, you should adjust it fit your application name)

<CustomAction Id="LaunchApplication"
         ExeCommand='&quot;[INSTALLDIR]wininstalled.exe&quot; --start-mode=installed'
         Directory="INSTALLDIR"
         Impersonate="yes"
         Return="asyncNoWait"/>

And this line was added to the InstallExecuteSequence section:

<Custom Action="LaunchApplication" After="InstallFinalize">Not Installed</Custom>

What will happen now, is that, after the installation is complete, your application will be launched with a command line parameter --start-mode=installed.

HelloApplication.java

So, now we can modify the code of the application to recognize and act on this parameter. What you choose to do when the parameter is set is up to you. In this example what I do is pop up an alert dialog notifying that the application was installed.

The alert is setAlwaysOnTop(true) so that it will display on top of other windows. Otherwise, it won't show immediately, instead the application icon will be highlighted by the OS in the windows task bar indicating that the application is running and has UI to show and then the UI can be seen after clicking the app icon in the task bar.

In this example, after the user dismisses the installation notification dialog, the main application window is shown, but you could instead quit the application if you wish.

The code queries the startup parameter to see if it was invoked from the installer and only shows an alert in that case. If you start the application normally (for example by double clicking on the application icon on the desktop), then, as expected, no installation alert dialog will be displayed in that case.

This is a modified version of the application code that was generated by the Idea IDE during the steps to create a packaged application that are outlined in the answer referenced in the question.

package com.example.wininstalled;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    enum StartMode {
        installed,
        normal
    }

    @Override
    public void start(Stage stage) throws IOException {
        StartMode startMode = StartMode.valueOf(
                getParameters()
                        .getNamed()
                        .getOrDefault(
                                "start-mode",
                                StartMode.normal.toString()
                        )
        );

        if (startMode != StartMode.normal) {
            Alert alert = new Alert(Alert.AlertType.INFORMATION);
            alert.setTitle("WinInstalled JavaFX Application");
            alert.setHeaderText("Installation detected");
            alert.setContentText("The application was " + startMode + ".");
            alert.setOnShown(e ->
                    ((Stage) alert.getDialogPane().getScene().getWindow())
                            .setAlwaysOnTop(true)
            );
            alert.showAndWait();
        }

        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setTitle("Hello!");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The WIX Installer UI generated by jpackage is not customizable

One approach might be to modify the WIXUI_Minimal Dialog set to include an ExitDlg that displays a custom message after the installation is complete.

A dialog that displays a summary dialog after setup completes successfully. It can also optionally display a checkbox and custom text. For details about how to add a checkbox and custom text to this dialog, see Customizing Built-in WixUI Dialog Sets and How To: Run the Installed Application After Setup.

However, unfortunately jpackage generated packaging cannot use the standard WixUI dialog sets. This answer describes how these dialog sets are used:

The answer notes that you must have the following switch on the command line to the wix toolkit:

on the command line, add -ext WixUIExtension to the call to light.exe.

But jpackage does not place this extension on the command line, so customized Wix Installer UI cannot be created for applications built using jpackage. I guess adding support for that might be provided by a feature request. This could be accomplished by adding a switch for custom UI in the jpackage command and, if set, providing the appropriate parameters to the wix tools.

This is an example light command issued by jpackage, it uses the -ext WixUtilExtension, but not the -ext WixUIExtension:

C:\Program Files (x86)\WiX Toolset v3.11\bin\light.exe -nologo -spdb -ext WixUtilExtension -out C:\dev\wininstalled\target\jpackage-temp\images\win-exe.image\wininstalled-24.01.1204.3456.msi -b C:\dev\wininstalled\target\jpackage-temp\config -sice:ICE27 -sice:ICE91 -loc C:\dev\wininstalled\target\jpackage-temp\config\MsiInstallerStrings_de.wxl -loc C:\dev\wininstalled\target\jpackage-temp\config\MsiInstallerStrings_en.wxl -loc C:\dev\wininstalled\target\jpackage-temp\config\MsiInstallerStrings_ja.wxl -loc C:\dev\wininstalled\target\jpackage-temp\config\MsiInstallerStrings_zh_CN.wxl -cultures:en-us C:\dev\wininstalled\target\jpackage-temp\wixobj\main.wixobj C:\dev\wininstalled\target\jpackage-temp\wixobj\bundle.wixobj C:\dev\wininstalled\target\jpackage-temp\wixobj\ui.wixobj