8221333: Replace Inno Setup with custom MSI wrapper for .exe bundler JDK-8200758-branch
authorherrick
Mon, 17 Jun 2019 14:57:23 -0400
branchJDK-8200758-branch
changeset 57411 4a80a72a891d
parent 57410 0bb3283aafe0
child 57412 02034583f4dc
8221333: Replace Inno Setup with custom MSI wrapper for .exe bundler Submitted-by: asemenyuk Reviewed-by: herrick, almatvee
make/lib/Lib-jdk.jpackage.gmk
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java
src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp
--- a/make/lib/Lib-jdk.jpackage.gmk	Fri Jun 14 21:20:00 2019 -0400
+++ b/make/lib/Lib-jdk.jpackage.gmk	Mon Jun 17 14:57:23 2019 -0400
@@ -65,12 +65,8 @@
 
   TARGETS += $(BUILD_LIB_JPACKAGE)
 
-endif
-
-# Build Wix custom action helper
-# Output library in resources dir, and symbols in the object dir
-ifeq ($(call isTargetOs, windows), true)
-
+  # Build Wix custom action helper
+  # Output library in resources dir, and symbols in the object dir
   $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \
       NAME := wixhelper, \
       OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
@@ -84,4 +80,20 @@
   ))
 
   TARGETS += $(BUILD_LIB_WIXHELPER)
+
+  $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_MSIWRAPPER, \
+      NAME := msiwrapper, \
+      OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+      SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/msiwrapper, \
+      SRC := $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/msiwrapper, \
+      EXTRA_FILES := $(addprefix $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/libjpackage/, \
+          FileUtils.cpp Log.cpp WinSysInfo.cpp tstrings.cpp WinErrorHandling.cpp ErrorHandling.cpp), \
+      CFLAGS := $(CXXFLAGS_JDKEXE) \
+          $(addprefix -I$(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/, msiwrapper libjpackage), \
+      CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+      LDFLAGS := $(LDFLAGS_JDKEXE), \
+      LIBS := $(LIBCXX), \
+  ))
+
+  TARGETS += $(BUILD_JPACKAGE_MSIWRAPPER)
 endif
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java	Fri Jun 14 21:20:00 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java	Mon Jun 17 14:57:23 2019 -0400
@@ -22,109 +22,45 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-
 package jdk.jpackage.internal;
 
 import java.io.*;
-import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.text.MessageFormat;
 import java.util.*;
 
-import static jdk.jpackage.internal.WindowsBundlerParam.*;
+public class WinExeBundler extends AbstractBundler {
 
-public class WinExeBundler extends AbstractBundler {
+    static {
+        System.loadLibrary("jpackage");
+    }
 
     private static final ResourceBundle I18N = ResourceBundle.getBundle(
             "jdk.jpackage.internal.resources.WinResources");
 
-    public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
-            new WindowsBundlerParam<>(
-            "win.app.bundler",
-            WinAppBundler.class,
-            params -> new WinAppBundler(),
-            null);
-
-    public static final BundlerParamInfo<File> EXE_IMAGE_DIR =
-            new WindowsBundlerParam<>(
-            "win.exe.imageDir",
-            File.class,
-            params -> {
-                File imagesRoot = IMAGES_ROOT.fetchFrom(params);
-                if (!imagesRoot.exists()) imagesRoot.mkdirs();
-                return new File(imagesRoot, "win-exe.image");
-            },
-            (s, p) -> null);
-
-    public static final BundlerParamInfo<File> WIN_APP_IMAGE =
-            new WindowsBundlerParam<>(
-            "win.app.image",
-            File.class,
-            null,
-            (s, p) -> null);
-
-    public static final BundlerParamInfo<UUID> UPGRADE_UUID =
-            new WindowsBundlerParam<>(
-            Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
-            UUID.class,
-            params -> UUID.randomUUID(),
-            (s, p) -> UUID.fromString(s));
+    public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER
+            = new WindowsBundlerParam<>(
+                    "win.app.bundler",
+                    WinAppBundler.class,
+                    params -> new WinAppBundler(),
+                    null);
 
-    public static final StandardBundlerParam<Boolean> EXE_SYSTEM_WIDE  =
-            new StandardBundlerParam<>(
-            Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(),
-            Boolean.class,
-            params -> true, // default to system wide
-            (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null
-                    : Boolean.valueOf(s)
-            );
-    public static final StandardBundlerParam<String> PRODUCT_VERSION =
-            new StandardBundlerParam<>(
-                    "win.msi.productVersion",
-                    String.class,
-                    VERSION::fetchFrom,
-                    (s, p) -> s
-            );
-
-    public static final StandardBundlerParam<Boolean> MENU_HINT =
-        new WindowsBundlerParam<>(
-                Arguments.CLIOptions.WIN_MENU_HINT.getId(),
-                Boolean.class,
-                params -> false,
-                (s, p) -> (s == null ||
-                        "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
-        );
+    public static final BundlerParamInfo<File> EXE_IMAGE_DIR
+            = new WindowsBundlerParam<>(
+                    "win.exe.imageDir",
+                    File.class,
+                    params -> {
+                        File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+                        if (!imagesRoot.exists()) {
+                            imagesRoot.mkdirs();
+                        }
+                        return new File(imagesRoot, "win-exe.image");
+                    },
+                    (s, p) -> null);
 
-    public static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
-        new WindowsBundlerParam<>(
-                Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(),
-                Boolean.class,
-                params -> false,
-                (s, p) -> (s == null ||
-                       "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
-        );
-
-    private final static String DEFAULT_EXE_PROJECT_TEMPLATE = "template.iss";
-    private final static String DEFAULT_JRE_EXE_TEMPLATE = "template.jre.iss";
-    private static final String TOOL_INNO_SETUP_COMPILER = "iscc.exe";
-
-    public static final BundlerParamInfo<String>
-            TOOL_INNO_SETUP_COMPILER_EXECUTABLE = new WindowsBundlerParam<>(
-            "win.exe.iscc.exe",
-            String.class,
-            params -> {
-                for (String dirString : (System.getenv("PATH")
-                        + ";C:\\Program Files (x86)\\Inno Setup 5;"
-                        + "C:\\Program Files\\Inno Setup 5").split(";")) {
-                    File f = new File(dirString.replace("\"", ""),
-                            TOOL_INNO_SETUP_COMPILER);
-                    if (f.isFile()) {
-                        return f.toString();
-                    }
-                }
-                return null;
-            },
-            null);
+    private final static String EXE_WRAPPER_NAME = "msiwrapper.exe";
 
     @Override
     public String getName() {
@@ -148,24 +84,7 @@
 
     @Override
     public Collection<BundlerParamInfo<?>> getBundleParameters() {
-        Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>();
-        results.addAll(WinAppBundler.getAppBundleParameters());
-        results.addAll(getExeBundleParameters());
-        return results;
-    }
-
-    public static Collection<BundlerParamInfo<?>> getExeBundleParameters() {
-        return Arrays.asList(
-                DESCRIPTION,
-                COPYRIGHT,
-                LICENSE_FILE,
-                MENU_GROUP,
-                MENU_HINT,
-                SHORTCUT_HINT,
-                EXE_SYSTEM_WIDE,
-                VENDOR,
-                INSTALLDIR_CHOOSER
-        );
+        return new WinMsiBundler().getBundleParameters();
     }
 
     @Override
@@ -179,687 +98,60 @@
         return (Platform.getPlatform() == Platform.WINDOWS);
     }
 
-    private static String findToolVersion(String toolName) {
-        try {
-            if (toolName == null || "".equals(toolName)) return null;
-
-            ProcessBuilder pb = new ProcessBuilder(
-                    toolName,
-                    "/?");
-            VersionExtractor ve =
-                    new VersionExtractor("Inno Setup (\\d+.?\\d*)");
-            IOUtils.exec(pb, true, ve);
-            // not interested in the output
-            String version = ve.getVersion();
-            Log.verbose(MessageFormat.format(
-                    getString("message.tool-version"), toolName, version));
-            return version;
-        } catch (Exception e) {
-            if (Log.isDebug()) {
-                Log.verbose(e);
-            }
-            return null;
-        }
-    }
-
     @Override
     public boolean validate(Map<String, ? super Object> params)
             throws UnsupportedPlatformException, ConfigException {
-        try {
-            if (params == null) throw new ConfigException(
-                      getString("error.parameters-null"),
-                      getString("error.parameters-null.advice"));
-
-            // run basic validation to ensure requirements are met
-            // we are not interested in return code, only possible exception
-            APP_BUNDLER.fetchFrom(params).validate(params);
-
-            // make sure some key values don't have newlines
-            for (BundlerParamInfo<String> pi : Arrays.asList(
-                    APP_NAME,
-                    COPYRIGHT,
-                    DESCRIPTION,
-                    MENU_GROUP,
-                    VENDOR,
-                    VERSION)
-            ) {
-                String v = pi.fetchFrom(params);
-                if (v.contains("\n") | v.contains("\r")) {
-                    throw new ConfigException("Parmeter '" + pi.getID() +
-                            "' cannot contain a newline.",
-                            " Change the value of '" + pi.getID() +
-                            " so that it does not contain any newlines");
-                }
-            }
-
-            // exe bundlers trim the copyright to 100 characters,
-            // tell them this will happen
-            if (COPYRIGHT.fetchFrom(params).length() > 100) {
-                throw new ConfigException(
-                        getString("error.copyright-is-too-long"),
-                        getString("error.copyright-is-too-long.advice"));
-            }
-
-            String innoVersion = findToolVersion(
-                    TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params));
-
-            //Inno Setup 5+ is required
-            String minVersion = "5.0";
-
-            if (VersionExtractor.isLessThan(innoVersion, minVersion)) {
-                Log.error(MessageFormat.format(
-                        getString("message.tool-wrong-version"),
-                        TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion));
-                throw new ConfigException(
-                        getString("error.iscc-not-found"),
-                        getString("error.iscc-not-found.advice"));
-            }
-
-            /********* validate bundle parameters *************/
-
-            // only one mime type per association, at least one file extension
-            List<Map<String, ? super Object>> associations =
-                    FILE_ASSOCIATIONS.fetchFrom(params);
-            if (associations != null) {
-                for (int i = 0; i < associations.size(); i++) {
-                    Map<String, ? super Object> assoc = associations.get(i);
-                    List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
-                    if (mimes.size() > 1) {
-                        throw new ConfigException(MessageFormat.format(
-                                getString("error.too-many-content-"
-                                + "types-for-file-association"), i),
-                                getString("error.too-many-content-"
-                                + "types-for-file-association.advice"));
-                    }
-                }
-            }
-
-            return true;
-        } catch (RuntimeException re) {
-            if (re.getCause() instanceof ConfigException) {
-                throw (ConfigException) re.getCause();
-            } else {
-                throw new ConfigException(re);
-            }
-        }
-    }
-
-    private boolean prepareProto(Map<String, ? super Object> params)
-                throws PackagerException, IOException {
-        File appImage = StandardBundlerParam.getPredefinedAppImage(params);
-        File appDir = null;
-
-        // we either have an application image or need to build one
-        if (appImage != null) {
-            appDir = new File(EXE_IMAGE_DIR.fetchFrom(params),
-                APP_NAME.fetchFrom(params));
-            // copy everything from appImage dir into appDir/name
-            IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
-        } else {
-            appDir = APP_BUNDLER.fetchFrom(params).doBundle(params,
-                    EXE_IMAGE_DIR.fetchFrom(params), true);
-        }
-
-        if (appDir == null) {
-            return false;
-        }
-
-        params.put(WIN_APP_IMAGE.getID(), appDir);
-
-        String licenseFile = LICENSE_FILE.fetchFrom(params);
-        if (licenseFile != null) {
-            // need to copy license file to the working directory and convert to rtf if needed
-            File lfile = new File(licenseFile);
-            File destFile = new File(CONFIG_ROOT.fetchFrom(params),
-                    lfile.getName());
-            IOUtils.copyFile(lfile, destFile);
-            ensureByMutationFileIsRTF(destFile);
-        }
-
-        // copy file association icons
-        List<Map<String, ? super Object>> fileAssociations =
-                FILE_ASSOCIATIONS.fetchFrom(params);
-
-        for (Map<String, ? super Object> fa : fileAssociations) {
-            File icon = FA_ICON.fetchFrom(fa);
-            if (icon == null) {
-                continue;
-            }
-
-            File faIconFile = new File(appDir, icon.getName());
-
-            if (icon.exists()) {
-                try {
-                    IOUtils.copyFile(icon, faIconFile);
-                } catch (IOException e) {
-                    Log.verbose(e);
-                }
-            }
-        }
-
-        return true;
+        return new WinMsiBundler().validate(params);
     }
 
     public File bundle(Map<String, ? super Object> params, File outdir)
             throws PackagerException {
 
-        IOUtils.writableOutputDir(outdir.toPath());
-
-        String tempDirectory = WindowsDefender.getUserTempDirectory();
-        if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
-            tempDirectory = TEMP_ROOT.fetchFrom(params).getAbsolutePath();
-        }
-        if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
-                tempDirectory)) {
-            Log.error(MessageFormat.format(
-                    getString("message.potential.windows.defender.issue"),
-                    tempDirectory));
-        }
-
-        // validate we have valid tools before continuing
-        String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params);
-        if (iscc == null || !new File(iscc).isFile()) {
-            Log.verbose(getString("error.iscc-not-found"));
-            Log.verbose(MessageFormat.format(
-                    getString("message.iscc-file-string"), iscc));
-            throw new PackagerException("error.iscc-not-found");
-        }
+        File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params);
 
-        File imageDir = EXE_IMAGE_DIR.fetchFrom(params);
-        try {
-            imageDir.mkdirs();
-
-            boolean menuShortcut = MENU_HINT.fetchFrom(params);
-            boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params);
-            if (!menuShortcut && !desktopShortcut) {
-                // both can not be false - user will not find the app
-                Log.verbose(getString("message.one-shortcut-required"));
-                params.put(MENU_HINT.getID(), true);
-            }
+        // Write msi to temporary directory.
+        File msi = new WinMsiBundler().bundle(params, exeImageDir);
 
-            if (prepareProto(params) && prepareProjectConfig(params)) {
-                File configScript = getConfig_Script(params);
-                if (configScript.exists()) {
-                    Log.verbose(MessageFormat.format(
-                            getString("message.running-wsh-script"),
-                            configScript.getAbsolutePath()));
-                    IOUtils.run("wscript", configScript);
-                }
-                return buildEXE(params, outdir);
-            }
-            return null;
+        try {
+            return buildEXE(msi, outdir);
         } catch (IOException ex) {
             Log.verbose(ex);
             throw new PackagerException(ex);
         }
     }
 
-    // name of post-image script
-    private File getConfig_Script(Map<String, ? super Object> params) {
-        return new File(EXE_IMAGE_DIR.fetchFrom(params),
-                APP_NAME.fetchFrom(params) + "-post-image.wsf");
-    }
-
-    private String getAppIdentifier(Map<String, ? super Object> params) {
-        String nm = UPGRADE_UUID.fetchFrom(params).toString();
-
-        // limitation of innosetup
-        if (nm.length() > 126) {
-            Log.error(getString("message-truncating-id"));
-            nm = nm.substring(0, 126);
-        }
-
-        return nm;
-    }
-
-    private String getLicenseFile(Map<String, ? super Object> params) {
-        String licenseFile = LICENSE_FILE.fetchFrom(params);
-        if (licenseFile != null) {
-            File lfile = new File(licenseFile);
-            File destFile = new File(CONFIG_ROOT.fetchFrom(params),
-                    lfile.getName());
-            String filePath = destFile.getAbsolutePath();
-            if (filePath.contains(" ")) {
-                return "\"" + filePath + "\"";
-            } else {
-                return filePath;
-            }
-        }
-
-        return null;
-    }
-
-    void validateValueAndPut(Map<String, String> data, String key,
-                BundlerParamInfo<String> param,
-                Map<String, ? super Object> params) throws IOException {
-        String value = param.fetchFrom(params);
-        if (value.contains("\r") || value.contains("\n")) {
-            throw new IOException("Configuration Parameter " +
-                     param.getID() + " cannot contain multiple lines of text");
-        }
-        data.put(key, innosetupEscape(value));
-    }
-
-    private String innosetupEscape(String value) {
-        if (value == null) {
-            return "";
-        }
-
-        if (value.contains("\"") || !value.trim().equals(value)) {
-            value = "\"" + value.replace("\"", "\"\"") + "\"";
-        }
-        return value;
-    }
-
-    boolean prepareMainProjectFile(Map<String, ? super Object> params)
+    private File buildEXE(File msi, File outdir)
             throws IOException {
-        Map<String, String> data = new HashMap<>();
-        data.put("PRODUCT_APP_IDENTIFIER",
-                innosetupEscape(getAppIdentifier(params)));
-
-        validateValueAndPut(data, "INSTALL_DIR", WINDOWS_INSTALL_DIR, params);
-        validateValueAndPut(data, "INSTALLER_NAME", APP_NAME, params);
-        validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params);
-        validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params);
-        validateValueAndPut(data, "INSTALLER_FILE_NAME",
-                INSTALLER_FILE_NAME, params);
-
-        data.put("LAUNCHER_NAME",
-                innosetupEscape(WinAppBundler.getAppName(params)));
-
-        data.put("APPLICATION_LAUNCHER_FILENAME",
-                innosetupEscape(WinAppBundler.getLauncherName(params)));
 
-        data.put("APPLICATION_DESKTOP_SHORTCUT",
-                SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse");
-        data.put("APPLICATION_MENU_SHORTCUT",
-                MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse");
-        validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params);
-        validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params);
-
-        data.put("APPLICATION_LICENSE_FILE",
-                innosetupEscape(getLicenseFile(params)));
-        data.put("DISABLE_DIR_PAGE",
-                INSTALLDIR_CHOOSER.fetchFrom(params) ? "No" : "Yes");
-
-        Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params);
-
-        if (isSystemWide) {
-            data.put("APPLICATION_INSTALL_ROOT", "{pf}");
-            data.put("APPLICATION_INSTALL_PRIVILEGE", "admin");
-        } else {
-            data.put("APPLICATION_INSTALL_ROOT", "{localappdata}");
-            data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest");
-        }
-
-        data.put("ARCHITECTURE_BIT_MODE", "x64");
-
-        validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params);
-
-        validateValueAndPut(data, "APPLICATION_DESCRIPTION",
-                DESCRIPTION, params);
-
-        data.put("APPLICATION_SERVICE", "returnFalse");
-        data.put("APPLICATION_NOT_SERVICE", "returnFalse");
-        data.put("APPLICATION_APP_CDS_INSTALL", "returnFalse");
-        data.put("START_ON_INSTALL", "");
-        data.put("STOP_ON_UNINSTALL", "");
-        data.put("RUN_AT_STARTUP", "");
-
-        String imagePathString = WIN_APP_IMAGE
-                .fetchFrom(params).toPath().toAbsolutePath().toString();
-        data.put("APPLICATION_IMAGE", innosetupEscape(imagePathString));
-        Log.verbose("setting APPLICATION_IMAGE to " +
-                innosetupEscape(imagePathString) + " for InnoSetup");
-
-        StringBuilder addLaunchersCfg = new StringBuilder();
-        for (Map<String, ? super Object>
-                launcher : ADD_LAUNCHERS.fetchFrom(params)) {
-            String application_name = APP_NAME.fetchFrom(launcher);
-            if (MENU_HINT.fetchFrom(launcher)) {
-                // Name: "{group}\APPLICATION_NAME";
-                // Filename: "{app}\APPLICATION_NAME.exe";
-                // IconFilename: "{app}\APPLICATION_NAME.ico"
-                addLaunchersCfg.append("Name: \"{group}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append("\"; Filename: \"{app}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append(".ico\"\r\n");
-            }
-            if (SHORTCUT_HINT.fetchFrom(launcher)) {
-                // Name: "{commondesktop}\APPLICATION_NAME";
-                // Filename: "{app}\APPLICATION_NAME.exe";
-                // IconFilename: "{app}\APPLICATION_NAME.ico"
-                addLaunchersCfg.append("Name: \"{commondesktop}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append("\"; Filename: \"{app}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append(".exe\";  IconFilename: \"{app}\\");
-                addLaunchersCfg.append(application_name);
-                addLaunchersCfg.append(".ico\"\r\n");
-            }
-        }
-        data.put("ADD_LAUNCHERS", addLaunchersCfg.toString());
-
-        StringBuilder registryEntries = new StringBuilder();
-        String regName = APP_REGISTRY_NAME.fetchFrom(params);
-        List<Map<String, ? super Object>> fetchFrom =
-                FILE_ASSOCIATIONS.fetchFrom(params);
-        for (int i = 0; i < fetchFrom.size(); i++) {
-            Map<String, ? super Object> fileAssociation = fetchFrom.get(i);
-            String description = FA_DESCRIPTION.fetchFrom(fileAssociation);
-            File icon = FA_ICON.fetchFrom(fileAssociation);
-
-            List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation);
-            String entryName = regName + "File";
-            if (i > 0) {
-                entryName += "." + i;
-            }
+        Log.verbose(MessageFormat.format(
+                getString("message.outputting-to-location"),
+                outdir.getAbsolutePath()));
 
-            if (extensions == null) {
-                Log.verbose(getString(
-                        "message.creating-association-with-null-extension"));
-            } else {
-                for (String ext : extensions) {
-                    if (isSystemWide) {
-                        // "Root: HKCR; Subkey: \".myp\";
-                        // ValueType: string; ValueName: \"\";
-                        // ValueData: \"MyProgramFile\";
-                        // Flags: uninsdeletevalue"
-                        registryEntries.append("Root: HKCR; Subkey: \".")
-                                .append(ext)
-                                .append("\"; ValueType: string;"
-                                + " ValueName: \"\"; ValueData: \"")
-                                .append(entryName)
-                                .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n");
-                    } else {
-                        registryEntries.append(
-                                "Root: HKCU; Subkey: \"Software\\Classes\\.")
-                                .append(ext)
-                                .append("\"; ValueType: string;"
-                                + " ValueName: \"\"; ValueData: \"")
-                                .append(entryName)
-                                .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n");
-                    }
-                }
-            }
-
-            if (extensions != null && !extensions.isEmpty()) {
-                String ext = extensions.get(0);
-                List<String> mimeTypes =
-                        FA_CONTENT_TYPE.fetchFrom(fileAssociation);
-                for (String mime : mimeTypes) {
-                    if (isSystemWide) {
-                        // "Root: HKCR;
-                        // Subkey: HKCR\\Mime\\Database\\
-                        //         Content Type\\application/chaos;
-                        // ValueType: string;
-                        // ValueName: Extension;
-                        // ValueData: .chaos;
-                        // Flags: uninsdeletevalue"
-                        registryEntries.append("Root: HKCR; Subkey: " +
-                                 "\"Mime\\Database\\Content Type\\")
-                            .append(mime)
-                            .append("\"; ValueType: string; ValueName: " +
-                                 "\"Extension\"; ValueData: \".")
-                            .append(ext)
-                            .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n");
-                    } else {
-                        registryEntries.append(
-                                "Root: HKCU; Subkey: \"Software\\" +
-                                "Classes\\Mime\\Database\\Content Type\\")
-                                .append(mime)
-                                .append("\"; ValueType: string; " +
-                                "ValueName: \"Extension\"; ValueData: \".")
-                                .append(ext)
-                                .append("\"; Flags: uninsdeletevalue uninsdeletekeyifempty\r\n");
-                    }
-                }
-            }
-
-            if (isSystemWide) {
-                // "Root: HKCR;
-                // Subkey: \"MyProgramFile\";
-                // ValueType: string;
-                // ValueName: \"\";
-                // ValueData: \"My Program File\";
-                // Flags: uninsdeletekey"
-                registryEntries.append("Root: HKCR; Subkey: \"")
-                    .append(entryName)
-                    .append(
-                    "\"; ValueType: string; ValueName: \"\"; ValueData: \"")
-                    .append(removeQuotes(description))
-                    .append("\"; Flags: uninsdeletekey\r\n");
-            } else {
-                registryEntries.append(
-                    "Root: HKCU; Subkey: \"Software\\Classes\\")
-                    .append(entryName)
-                    .append(
-                    "\"; ValueType: string; ValueName: \"\"; ValueData: \"")
-                    .append(removeQuotes(description))
-                    .append("\"; Flags: uninsdeletekey\r\n");
-            }
-
-            if (icon != null && icon.exists()) {
-                if (isSystemWide) {
-                    // "Root: HKCR;
-                    // Subkey: \"MyProgramFile\\DefaultIcon\";
-                    // ValueType: string;
-                    // ValueName: \"\";
-                    // ValueData: \"{app}\\MYPROG.EXE,0\"\n" +
-                    registryEntries.append("Root: HKCR; Subkey: \"")
-                            .append(entryName)
-                            .append("\\DefaultIcon\"; ValueType: string; " +
-                            "ValueName: \"\"; ValueData: \"{app}\\")
-                            .append(icon.getName())
-                            .append("\"\r\n");
-                } else {
-                    registryEntries.append(
-                            "Root: HKCU; Subkey: \"Software\\Classes\\")
-                            .append(entryName)
-                            .append("\\DefaultIcon\"; ValueType: string; " +
-                            "ValueName: \"\"; ValueData: \"{app}\\")
-                            .append(icon.getName())
-                            .append("\"\r\n");
-                }
-            }
+        // Copy template msi wrapper next to msi file
+        String exePath = msi.getAbsolutePath();
+        exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
+        try (InputStream is = getResourceAsStream(EXE_WRAPPER_NAME)) {
+            Files.copy(is, Path.of(exePath));
+        }
+        // Embed msi in msi wrapper exe.
+        embedMSI(exePath, msi.getAbsolutePath());
 
-            if (isSystemWide) {
-                // "Root: HKCR;
-                // Subkey: \"MyProgramFile\\shell\\open\\command\";
-                // ValueType: string;
-                // ValueName: \"\";
-                // ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n"
-                registryEntries.append("Root: HKCR; Subkey: \"")
-                        .append(entryName)
-                        .append("\\shell\\open\\command\"; ValueType: " +
-                        "string; ValueName: \"\"; ValueData: \"\"\"{app}\\")
-                        .append(APP_NAME.fetchFrom(params))
-                        .append("\"\" \"\"%1\"\"\"\r\n");
-            } else {
-                registryEntries.append(
-                        "Root: HKCU; Subkey: \"Software\\Classes\\")
-                        .append(entryName)
-                        .append("\\shell\\open\\command\"; ValueType: " +
-                        "string; ValueName: \"\"; ValueData: \"\"\"{app}\\")
-                        .append(APP_NAME.fetchFrom(params))
-                        .append("\"\" \"\"%1\"\"\"\r\n");
-            }
-        }
-        if (registryEntries.length() > 0) {
-            data.put("FILE_ASSOCIATIONS",
-                    "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" +
-                    registryEntries.toString());
-        } else {
-            data.put("FILE_ASSOCIATIONS", "");
-        }
-
-        String iss = StandardBundlerParam.isRuntimeInstaller(params) ?
-                DEFAULT_JRE_EXE_TEMPLATE : DEFAULT_EXE_PROJECT_TEMPLATE;
-
-        try (Writer w = Files.newBufferedWriter(
-                getConfig_ExeProjectFile(params).toPath())) {
-
-            String content = preprocessTextResource(
-                    getConfig_ExeProjectFile(params).getName(),
-                    getString("resource.inno-setup-project-file"),
-                    iss, data, VERBOSE.fetchFrom(params),
-                    RESOURCE_DIR.fetchFrom(params));
-            w.write(content);
-        }
-        return true;
-    }
+        Path dstExePath = Paths.get(outdir.getAbsolutePath(), Path.of(exePath).getFileName().toString());
+        Files.deleteIfExists(dstExePath);
 
-    private final static String removeQuotes(String s) {
-        if (s.length() > 2 && s.startsWith("\"") && s.endsWith("\"")) {
-            // special case for '"XXX"' return 'XXX' not '-XXX-'
-            // note '"' and '""' are excluded from this special case
-            s = s.substring(1, s.length() - 1);
-        }
-        // if there interior double quotes replace them with '-'
-        return s.replaceAll("\"", "-");
-    }
-
-    private final static String DEFAULT_INNO_SETUP_ICON =
-            "icon_inno_setup.bmp";
-
-    private boolean prepareProjectConfig(Map<String, ? super Object> params)
-            throws IOException {
-        prepareMainProjectFile(params);
-
-        // prepare installer icon
-        File iconTarget = getConfig_SmallInnoSetupIcon(params);
-        fetchResource(iconTarget.getName(),
-                getString("resource.setup-icon"),
-                DEFAULT_INNO_SETUP_ICON,
-                iconTarget,
-                VERBOSE.fetchFrom(params),
-                RESOURCE_DIR.fetchFrom(params));
-
-        fetchResource(getConfig_Script(params).getName(),
-                getString("resource.post-install-script"),
-                (String) null,
-                getConfig_Script(params),
-                VERBOSE.fetchFrom(params),
-                RESOURCE_DIR.fetchFrom(params));
-        return true;
-    }
-
-    private File getConfig_SmallInnoSetupIcon(
-            Map<String, ? super Object> params) {
-        return new File(EXE_IMAGE_DIR.fetchFrom(params),
-                APP_NAME.fetchFrom(params) + "-setup-icon.bmp");
-    }
-
-    private File getConfig_ExeProjectFile(Map<String, ? super Object> params) {
-        return new File(EXE_IMAGE_DIR.fetchFrom(params),
-                APP_NAME.fetchFrom(params) + ".iss");
-    }
-
-
-    private File buildEXE(Map<String, ? super Object> params, File outdir)
-             throws IOException {
-        Log.verbose(MessageFormat.format(
-             getString("message.outputting-to-location"),
-             outdir.getAbsolutePath()));
-
-        outdir.mkdirs();
-
-        // run Inno Setup
-        ProcessBuilder pb = new ProcessBuilder(
-                TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params),
-                "/q",    // turn off inno setup output
-                "/o"+outdir.getAbsolutePath(),
-                getConfig_ExeProjectFile(params).getAbsolutePath());
-        pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params));
-        IOUtils.exec(pb);
+        Files.copy(Path.of(exePath), dstExePath);
 
         Log.verbose(MessageFormat.format(
                 getString("message.output-location"),
                 outdir.getAbsolutePath()));
 
-        // presume the result is the ".exe" file with the newest modified time
-        // not the best solution, but it is the most reliable
-        File result = null;
-        long lastModified = 0;
-        File[] list = outdir.listFiles();
-        if (list != null) {
-            for (File f : list) {
-                if (f.getName().endsWith(".exe") &&
-                        f.lastModified() > lastModified) {
-                    result = f;
-                    lastModified = f.lastModified();
-                }
-            }
-        }
-
-        return result;
-    }
-
-   public static void ensureByMutationFileIsRTF(File f) {
-        if (f == null || !f.isFile()) return;
-
-        try {
-            boolean existingLicenseIsRTF = false;
-
-            try (FileInputStream fin = new FileInputStream(f)) {
-                byte[] firstBits = new byte[7];
-
-                if (fin.read(firstBits) == firstBits.length) {
-                    String header = new String(firstBits);
-                    existingLicenseIsRTF = "{\\rtf1\\".equals(header);
-                }
-            }
-
-            if (!existingLicenseIsRTF) {
-                List<String> oldLicense = Files.readAllLines(f.toPath());
-                try (Writer w = Files.newBufferedWriter(
-                        f.toPath(), Charset.forName("Windows-1252"))) {
-                    w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033"
-                            + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n"
-                            + "\\viewkind4\\uc1\\pard\\sa200\\sl276"
-                            + "\\slmult1\\lang9\\fs20 ");
-                    oldLicense.forEach(l -> {
-                        try {
-                            for (char c : l.toCharArray()) {
-                                if (c < 0x10) {
-                                    w.write("\\'0");
-                                    w.write(Integer.toHexString(c));
-                                } else if (c > 0xff) {
-                                    w.write("\\ud");
-                                    w.write(Integer.toString(c));
-                                    w.write("?");
-                                } else if ((c < 0x20) || (c >= 0x80) ||
-                                        (c == 0x5C) || (c == 0x7B) ||
-                                        (c == 0x7D)) {
-                                    w.write("\\'");
-                                    w.write(Integer.toHexString(c));
-                                } else {
-                                    w.write(c);
-                                }
-                            }
-                            if (l.length() < 1) {
-                                w.write("\\par");
-                            } else {
-                                w.write(" ");
-                            }
-                            w.write("\r\n");
-                        } catch (IOException e) {
-                            Log.verbose(e);
-                        }
-                    });
-                    w.write("}\r\n");
-                }
-            }
-        } catch (IOException e) {
-            Log.verbose(e);
-        }
+        return dstExePath.toFile();
     }
 
     private static String getString(String key)
             throws MissingResourceException {
         return I18N.getString(key);
     }
+
+    private static native int embedMSI(String exePath, String msiPath);
 }
--- a/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp	Fri Jun 14 21:20:00 2019 -0400
+++ b/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp	Mon Jun 17 14:57:23 2019 -0400
@@ -28,6 +28,8 @@
 #include <string>
 #include <windows.h>
 
+#include "ResourceEditor.h"
+#include "WinErrorHandling.h"
 #include "IconSwap.h"
 #include "VersionInfoSwap.h"
 #include "Utils.h"
@@ -74,6 +76,31 @@
         return 1;
     }
 
+    /*
+     * Class:     jdk_jpackage_internal_WinExeBundler
+     * Method:    embedMSI
+     * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+     */
+    JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WinExeBundler_embedMSI(
+            JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) {
+
+        const wstring exePath = GetStringFromJString(pEnv, jexePath);
+        const wstring msiPath = GetStringFromJString(pEnv, jmsiPath);
+
+        JP_TRY;
+
+        ResourceEditor()
+            .id(L"msi")
+            .type(RT_RCDATA)
+            .apply(ResourceEditor::FileLock(exePath), msiPath);
+
+        return 0;
+
+        JP_CATCH_ALL;
+
+        return 1;
+    }
+
     BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
         return TRUE;
     }