8232281: jpackage is not always reporting an error when no main class specified JDK-8200758-branch
authorherrick
Wed, 23 Oct 2019 10:37:54 -0400
branchJDK-8200758-branch
changeset 58762 0fe62353385b
parent 58761 88e2753a2334
child 58763 bc43733cd5cf
8232281: jpackage is not always reporting an error when no main class specified Reviewed-by: asemenyuk, asemenuk
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties
src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties
src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties
src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java
src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java
src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java
src/jdk.jpackage/share/classes/jdk/jpackage/internal/ScriptRunner.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourcesBuilder.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java
test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java
test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java
test/jdk/tools/jpackage/linux/ShortcutHintTest.java
test/jdk/tools/jpackage/share/FileAssociationsTest.java
test/jdk/tools/jpackage/share/RuntimePackageTest.java
test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java
test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java
test/jdk/tools/jpackage/windows/WinScriptTest.java
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java	Wed Oct 23 10:37:54 2019 -0400
@@ -69,7 +69,8 @@
                 .setCategory(I18N.getString("resource.menu-icon"))
                 .setExternal(customIconFile);
         desktopFileResource = createResource("template.desktop", params)
-                .setCategory(I18N.getString("resource.menu-shortcut-descriptor"));
+                .setCategory(I18N.getString("resource.menu-shortcut-descriptor"))
+                .setPublicName(APP_NAME.fetchFrom(params) + ".desktop");
 
         // XDG recommends to use vendor prefix in desktop file names as xdg
         // commands copy files to system directories.
@@ -89,8 +90,8 @@
             // - custom icon specified
             //
             desktopFile = new DesktopFile(desktopFileName);
-            iconFile = new DesktopFile(String.format("%s.png",
-                    APP_NAME.fetchFrom(params)));
+            iconFile = new DesktopFile(APP_NAME.fetchFrom(params)
+                    + IOUtils.getSuffix(Path.of(DEFAULT_ICON)));
         } else {
             desktopFile = null;
             iconFile = null;
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -26,14 +26,8 @@
 package jdk.jpackage.internal;
 
 import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-import java.util.ResourceBundle;
+import java.util.*;
 
 import static jdk.jpackage.internal.StandardBundlerParam.*;
 
@@ -85,10 +79,7 @@
     public boolean validate(Map<String, ? super Object> params)
             throws ConfigException {
         try {
-            if (params == null) throw new ConfigException(
-                    I18N.getString("error.parameters-null"),
-                    I18N.getString("error.parameters-null.advice"));
-
+            Objects.requireNonNull(params);
             return doValidate(params);
         } catch (RuntimeException re) {
             if (re.getCause() instanceof ConfigException) {
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java	Wed Oct 23 10:37:54 2019 -0400
@@ -169,7 +169,8 @@
             throws IOException {
 
         Path iconTarget = appLayout.destktopIntegrationDirectory().resolve(
-                APP_NAME.fetchFrom(params) + ".png");
+                APP_NAME.fetchFrom(params) + IOUtils.getSuffix(Path.of(
+                DEFAULT_ICON)));
 
         createResource(DEFAULT_ICON, params)
                 .setCategory("icon")
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -365,9 +365,12 @@
         debianFiles.add(new DebianFile(
                 configDir.resolve("postrm"),
                 "resource.deb-postrm-script").setExecutable());
-        debianFiles.add(new DebianFile(
-                getConfig_CopyrightFile(params).toPath(),
-                "resource.copyright-file"));
+
+        if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+            debianFiles.add(new DebianFile(
+                    getConfig_CopyrightFile(params).toPath(),
+                    "resource.copyright-file"));
+        }
 
         for (DebianFile debianFile : debianFiles) {
             debianFile.create(data, params);
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -29,6 +29,7 @@
 import java.nio.file.Path;
 import java.text.MessageFormat;
 import java.util.*;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -99,18 +100,25 @@
 
         PlatformPackage thePackage = createMetaPackage(params);
 
+        Function<File, ApplicationLayout> initAppImageLayout = imageRoot -> {
+            ApplicationLayout layout = appImageLayout(params);
+            layout.pathGroup().setPath(new Object(),
+                    AppImageFile.getPathInAppImage(Path.of("")));
+            return layout.resolveAt(imageRoot.toPath());
+        };
+
         try {
             File appImage = StandardBundlerParam.getPredefinedAppImage(params);
 
             // we either have an application image or need to build one
             if (appImage != null) {
-                appImageLayout(params).resolveAt(appImage.toPath()).copy(
+                initAppImageLayout.apply(appImage).copy(
                         thePackage.sourceApplicationLayout());
             } else {
                 appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
                         thePackage.sourceRoot().toFile(), true);
-                ApplicationLayout srcAppLayout = appImageLayout(params).resolveAt(
-                        appImage.toPath());
+                ApplicationLayout srcAppLayout = initAppImageLayout.apply(
+                        appImage);
                 if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) {
                     // Application image points to run-time image.
                     // Copy it.
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -40,9 +40,6 @@
 resource.menu-icon=menu icon
 resource.rpm-spec-file=RPM spec file
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-
 error.tool-not-found.advice=Please install required packages
 error.tool-old-version.advice=Please install required packages
 
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -40,9 +40,6 @@
 resource.menu-icon=menu icon
 resource.rpm-spec-file=RPM spec file
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-
 error.tool-not-found.advice=Please install required packages
 error.tool-old-version.advice=Please install required packages
 
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -40,9 +40,6 @@
 resource.menu-icon=menu icon
 resource.rpm-spec-file=RPM spec file
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-
 error.tool-not-found.advice=Please install required packages
 error.tool-old-version.advice=Please install required packages
 
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -28,11 +28,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.ResourceBundle;
+import java.util.*;
 
 import static jdk.jpackage.internal.StandardBundlerParam.*;
 import static jdk.jpackage.internal.MacAppBundler.*;
@@ -235,11 +231,7 @@
     public boolean validate(Map<String, ? super Object> params)
             throws ConfigException {
         try {
-            if (params == null) {
-                throw new ConfigException(
-                        I18N.getString("error.parameters-null"),
-                        I18N.getString("error.parameters-null.advice"));
-            }
+            Objects.requireNonNull(params);
 
             // hdiutil is always available so there's no need to test for
             // availability.
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -429,9 +429,7 @@
     public boolean validate(Map<String, ? super Object> params)
             throws ConfigException {
         try {
-            if (params == null) throw new ConfigException(
-                    I18N.getString("error.parameters-null"),
-                    I18N.getString("error.parameters-null.advice"));
+            Objects.requireNonNull(params);
 
             //run basic validation to ensure requirements are met
             //we are not interested in return code, only possible exception
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -31,12 +31,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.ResourceBundle;
+import java.util.*;
 
 import static jdk.jpackage.internal.StandardBundlerParam.*;
 import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
@@ -502,9 +497,7 @@
     public boolean validate(Map<String, ? super Object> params)
             throws ConfigException {
         try {
-            if (params == null) throw new ConfigException(
-                    I18N.getString("error.parameters-null"),
-                    I18N.getString("error.parameters-null.advice"));
+            Objects.requireNonNull(params);
 
             // run basic validation to ensure requirements are met
             // we are not interested in return code, only possible exception
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -33,8 +33,6 @@
 error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
 error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
 error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map.
 error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
 error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
 error.no-app-signing-key=No Mac App Store App Signing Key
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -33,8 +33,6 @@
 error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
 error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
 error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map.
 error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
 error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
 error.no-app-signing-key=No Mac App Store App Signing Key
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -33,8 +33,6 @@
 error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
 error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
 error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map.
 error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
 error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
 error.no-app-signing-key=No Mac App Store App Signing Key
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java	Wed Oct 23 10:37:54 2019 -0400
@@ -51,7 +51,7 @@
     private final String launcherName;
     private final List<String> addLauncherNames;
 
-    public final static String FILENAME = ".jpackage.xml";
+    private final static String FILENAME = ".jpackage.xml";
 
     private final static Map<Platform, String> PLATFORM_LABELS = Map.of(
             Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
@@ -91,13 +91,21 @@
     }
 
     /**
+     * Returns path to application image info file.
+     * @param appImageDir - path to application image
+     */
+    public static Path getPathInAppImage(Path appImageDir) {
+        return appImageDir.resolve(FILENAME);
+    }
+
+    /**
      * Saves file with application image info in application image.
      * @param appImageDir - path to application image
      * @throws IOException
      */
-    static void save(Path appImage, Map<String, Object> params)
+    static void save(Path appImageDir, Map<String, Object> params)
             throws IOException {
-        IOUtils.createXml(appImage.resolve(FILENAME), xml -> {
+        IOUtils.createXml(getPathInAppImage(appImageDir), xml -> {
             xml.writeStartElement("jpackage-state");
             xml.writeAttribute("version", getVersion());
             xml.writeAttribute("platform", getPlatform());
@@ -126,7 +134,7 @@
      */
     static AppImageFile load(Path appImageDir) throws IOException {
         try {
-            Path path = appImageDir.resolve(FILENAME);
+            Path path = getPathInAppImage(appImageDir);
             DocumentBuilderFactory dbf =
                     DocumentBuilderFactory.newDefaultInstance();
             dbf.setFeature(
@@ -177,7 +185,7 @@
 
     /**
      * Returns list of launcher names configured for the application.
-     * The first item in the returned list is man launcher name.
+     * The first item in the returned list is main launcher name.
      * Following items in the list are names of additional launchers.
      */
     static List<String> getLauncherNames(Path appImageDir,
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java	Wed Oct 23 10:37:54 2019 -0400
@@ -240,6 +240,11 @@
         return parent != null ? parent.resolve(filename) : Path.of(filename);
     }
 
+    public static String getSuffix(Path path) {
+        String filename = replaceSuffix(path.getFileName(), null).toString();
+        return path.getFileName().toString().substring(filename.length());
+    }
+
     @FunctionalInterface
     public static interface XmlConsumer {
         void accept(XMLStreamWriter xml) throws IOException, XMLStreamException;
@@ -248,7 +253,7 @@
     public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
             IOException {
         XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
-        try (Writer w = new BufferedWriter(new FileWriter(dstFile.toFile()))) {
+        try (Writer w = Files.newBufferedWriter(dstFile)) {
             // Wrap with pretty print proxy
             XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(
                     XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java	Wed Oct 23 10:37:54 2019 -0400
@@ -81,10 +81,6 @@
         return this;
     }
 
-    String getCategory() {
-        return category;
-    }
-
     OverridableResource setResourceDir(Path v) {
         resourceDir = v;
         return this;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ScriptRunner.java	Wed Oct 23 10:37:54 2019 -0400
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import static jdk.jpackage.internal.OverridableResource.createResource;
+import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME;
+import static jdk.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
+
+/**
+ * Runs custom script from resource directory.
+ */
+class ScriptRunner {
+    ScriptRunner() {
+        environment = new ProcessBuilder().environment();
+    }
+
+    ScriptRunner setResourceCategoryId(String v) {
+        resourceCategoryId = v;
+        return this;
+    }
+
+    ScriptRunner setDirectory(Path v) {
+        directory = v;
+        return this;
+    }
+
+    ScriptRunner setScriptNameSuffix(String v) {
+        scriptNameSuffix = v;
+        return this;
+    }
+
+    ScriptRunner addEnvironment(Map<String, String> v) {
+        environment.putAll(v);
+        return this;
+    }
+
+    ScriptRunner setEnvironmentVariable(String envVarName, String envVarValue) {
+        Objects.requireNonNull(envVarName);
+        if (envVarValue == null) {
+            environment.remove(envVarName);
+        } else {
+            environment.put(envVarName, envVarValue);
+        }
+        return this;
+    }
+
+    public void run(Map<String, ? super Object> params) throws IOException {
+        String scriptName = String.format("%s-%s%s", APP_NAME.fetchFrom(params),
+                scriptNameSuffix, scriptSuffix());
+        Path scriptPath = CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+                scriptName);
+        createResource(null, params)
+                .setCategory(I18N.getString(resourceCategoryId))
+                .saveToFile(scriptPath);
+        if (!Files.exists(scriptPath)) {
+            return;
+        }
+
+        ProcessBuilder pb = new ProcessBuilder(shell(),
+                scriptPath.toAbsolutePath().toString());
+        Map<String, String> workEnvironment = pb.environment();
+        workEnvironment.clear();
+        workEnvironment.putAll(environment);
+
+        if (directory != null) {
+            pb.directory(directory.toFile());
+        }
+
+        Executor.of(pb).executeExpectSuccess();
+    }
+
+    private static String shell() {
+        if (Platform.isWindows()) {
+            return "cscript";
+        }
+        return Optional.ofNullable(System.getenv("SHELL")).orElseGet(() -> "sh");
+    }
+
+    private static String scriptSuffix() {
+        if (Platform.isWindows()) {
+            return ".wsf";
+        }
+        return ".sh";
+    }
+
+    private String scriptNameSuffix;
+    private String resourceCategoryId;
+    private Path directory;
+    private Map<String, String> environment;
+}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -28,10 +28,7 @@
 import java.io.File;
 import java.nio.file.Path;
 import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-import java.util.ResourceBundle;
+import java.util.*;
 
 import static jdk.jpackage.internal.WindowsBundlerParam.*;
 import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE;
@@ -60,10 +57,7 @@
     public boolean validate(Map<String, ? super Object> params)
             throws ConfigException {
         try {
-            if (params == null) throw new ConfigException(
-                    I18N.getString("error.parameters-null"),
-                    I18N.getString("error.parameters-null.advice"));
-
+            Objects.requireNonNull(params);
             return doValidate(params);
         } catch (RuntimeException re) {
             if (re.getCause() instanceof ConfigException) {
@@ -80,21 +74,6 @@
             throws ConfigException {
 
         imageBundleValidation(p);
-
-        if (StandardBundlerParam.getPredefinedAppImage(p) != null) {
-            return true;
-        }
-
-        // Make sure that jpackage.exe exists.
-        File tool = new File(
-                System.getProperty("java.home") + "\\bin\\jpackage.exe");
-
-        if (!tool.exists()) {
-            throw new ConfigException(
-                    I18N.getString("error.no-windows-resources"),
-                    I18N.getString("error.no-windows-resources.advice"));
-        }
-
         return true;
     }
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -110,6 +110,13 @@
         File msi = msiBundler.bundle(params, exeImageDir);
 
         try {
+            new ScriptRunner()
+            .setDirectory(msi.toPath().getParent())
+            .setResourceCategoryId("resource.post-msi-script")
+            .setScriptNameSuffix("post-msi")
+            .setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
+            .run(params);
+
             return buildEXE(msi, outdir);
         } catch (IOException ex) {
             Log.verbose(ex);
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java	Wed Oct 23 10:37:54 2019 -0400
@@ -335,11 +335,6 @@
         try {
             Files.createDirectories(imageDir);
 
-            Path postImageScript = imageDir.resolve(APP_NAME.fetchFrom(params) + "-post-image.wsf");
-            createResource(null, params)
-                    .setCategory(I18N.getString("resource.post-install-script"))
-                    .saveToFile(postImageScript);
-
             prepareProto(params);
 
             wixSourcesBuilder
@@ -349,12 +344,13 @@
 
             Map<String, String> wixVars = prepareMainProjectFile(params);
 
-            if (Files.exists(postImageScript)) {
-                Log.verbose(MessageFormat.format(I18N.getString(
-                        "message.running-wsh-script"),
-                        postImageScript.toAbsolutePath()));
-                Executor.of("wscript", postImageScript.toString()).executeExpectSuccess();
-            }
+            new ScriptRunner()
+            .setDirectory(imageDir)
+            .setResourceCategoryId("resource.post-app-image-script")
+            .setScriptNameSuffix("post-image")
+            .setEnvironmentVariable("JpAppImageDir", imageDir.toAbsolutePath().toString())
+            .run(params);
+
             return buildMSI(params, wixVars, outdir);
         } catch (IOException ex) {
             Log.verbose(ex);
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourcesBuilder.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixSourcesBuilder.java	Wed Oct 23 10:37:54 2019 -0400
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
+import java.text.MessageFormat;
 import java.util.*;
 import java.util.function.*;
 import java.util.stream.Collectors;
@@ -75,7 +76,8 @@
             // Don't want AppImageFile.FILENAME in installed application.
             // Register it with app image at a role without a match in installed
             // app layout to exclude it from layout transformation.
-            layout.pathGroup().setPath(new Object(), Path.of(AppImageFile.FILENAME));
+            layout.pathGroup().setPath(new Object(),
+                    AppImageFile.getPathInAppImage(Path.of("")));
 
             // Want absolute paths to source files in generated WiX sources.
             // This is to handle scenario if sources would be processed from
@@ -136,7 +138,8 @@
 
     void logWixFeatures() {
         if (wixVersion.compareTo("3.6") >= 0) {
-            Log.verbose(I18N.getString("message.use-wix36-features"));
+            Log.verbose(MessageFormat.format(I18N.getString(
+                    "message.use-wix36-features"), wixVersion));
         }
     }
 
@@ -148,6 +151,10 @@
             fa.iconPath = null;
         }
 
+        if (fa.iconPath != null) {
+            fa.iconPath = fa.iconPath.toAbsolutePath();
+        }
+
         // Filter out empty extensions.
         fa.extensions = fa.extensions.stream().filter(Predicate.not(
                 String::isEmpty)).collect(Collectors.toList());
@@ -277,6 +284,14 @@
             return cfg.isFile;
         }
 
+        static void startElement(XMLStreamWriter xml, String componentId,
+                String componentGuid) throws XMLStreamException, IOException {
+            xml.writeStartElement("Component");
+            xml.writeAttribute("Win64", "yes");
+            xml.writeAttribute("Id", componentId);
+            xml.writeAttribute("Guid", componentGuid);
+        }
+
         private static final class Config {
             Config withRegistryKeyPath() {
                 withRegistryKeyPath = true;
@@ -329,9 +344,8 @@
         xml.writeAttribute("Id", Id.Folder.of(directoryRefPath));
 
         final String componentId = "c" + role.idOf(path);
-        xml.writeStartElement("Component");
-        xml.writeAttribute("Id", componentId);
-        xml.writeAttribute("Guid", String.format("{%s}", role.guidOf(path)));
+        Component.startElement(xml, componentId, String.format("{%s}",
+                role.guidOf(path)));
 
         boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath();
         if (isRegistryKeyPath) {
@@ -716,9 +730,7 @@
 
         xml.writeStartElement("DirectoryRef");
         xml.writeAttribute("Id", INSTALLDIR.toString());
-        xml.writeStartElement("Component");
-        xml.writeAttribute("Id", componentId);
-        xml.writeAttribute("Guid", "*");
+        Component.startElement(xml, componentId, "*");
 
         addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> {
             // The following code converts a path to value to be saved in registry.
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java	Wed Oct 23 10:37:54 2019 -0400
@@ -26,10 +26,7 @@
 package jdk.jpackage.internal;
 
 import java.io.IOException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.PathMatcher;
+import java.nio.file.*;
 import java.text.MessageFormat;
 import java.util.*;
 import java.util.function.Supplier;
@@ -119,16 +116,39 @@
 
     private final static String MINIMAL_VERSION = "3.0";
 
-    private final static Path PROGRAM_FILES = Path.of("C:\\Program Files");
-    private final static Path PROGRAM_FILES_X86 = Path.of("C:\\Program Files (x86)");
+    static Path getSystemDir(String envVar, String knownDir) {
+        return Optional
+                .ofNullable(getEnvVariableAsPath(envVar))
+                .orElseGet(() -> Optional
+                        .ofNullable(getEnvVariableAsPath("SystemDrive"))
+                        .orElseGet(() -> Path.of("C:")).resolve(knownDir));
+    }
+
+    private static Path getEnvVariableAsPath(String envVar) {
+        String path = System.getenv(envVar);
+        if (path != null) {
+            try {
+                return Path.of(path);
+            } catch (InvalidPathException ex) {
+                Log.error(MessageFormat.format(I18N.getString(
+                        "error.invalid-envvar"), envVar));
+                return null;
+            }
+        }
+        return null;
+    }
 
     private static List<Path> findWixInstallDirs() {
         PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher(
                 "glob:WiX Toolset v*");
 
+        Path programFiles = getSystemDir("ProgramFiles", "Program Files");
+        Path programFilesX86 = getSystemDir("ProgramFiles(X86)",
+                "Program Files (x86)");
+
         // Returns list of WiX install directories ordered by WiX version number.
         // Newer versions go first.
-        return Stream.of(PROGRAM_FILES, PROGRAM_FILES_X86).map(path -> {
+        return Stream.of(programFiles, programFilesX86).map(path -> {
             List<Path> result;
             try (var paths = Files.walk(path, 1)) {
                 result = paths.collect(Collectors.toList());
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -32,16 +32,10 @@
 
 resource.executable-properties-template=Template for creating executable properties file
 resource.setup-icon=setup dialog icon
-resource.post-install-script=script to run after application image is populated
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
 resource.wxl-file-name=MsiInstallerStrings_en.wxl
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-error.no-windows-resources=This copy of the JDK does not support Windows
-error.no-windows-resources.advice=Please use the Oracle JDK for Windows
-error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
-error.copyright-is-too-long=The copyright string is too long for InnoSetup
-error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
 error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
 error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
 error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
@@ -50,28 +44,21 @@
 error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
 error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
 error.version-string-part-not-number=Failed to convert version component to int
-error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
 error.version-swap=Failed to update version information for {0}
-error.version-compare=Error: Failed to compare version {0} with {1.
+error.invalid-envvar=Invalid value of {0} environment variable
 
 message.result-dir=Result application bundle: {0}.
 message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
-message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
 message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
-message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
 message.outputting-to-location=Generating EXE for installer to: {0}.
 message.output-location=Installer (.exe) saved to: {0}
 message.tool-version=Detected [{0}] version [{1}].
-message.running-wsh-script=Running WSH script on application image [{0}].
 message.creating-association-with-null-extension=Creating association with null extension.
 message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
 message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
-message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
-message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
 message.generated-product-guid=Generated product GUID: {0}.
 message.preparing-msi-config=Preparing MSI config: {0}.
 message.generating-msi=Generating MSI: {0}.
-message.light-file-string=WiX light tool set to {0}.
-message.candle-file-string=WiX candle tool set to {0}.
 message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -32,16 +32,10 @@
 
 resource.executable-properties-template=Template for creating executable properties file
 resource.setup-icon=setup dialog icon
-resource.post-install-script=script to run after application image is populated
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
 resource.wxl-file-name=MsiInstallerStrings_en.wxl
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-error.no-windows-resources=This copy of the JDK does not support Windows
-error.no-windows-resources.advice=Please use the Oracle JDK for Windows
-error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
-error.copyright-is-too-long=The copyright string is too long for InnoSetup
-error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
 error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
 error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
 error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
@@ -50,28 +44,21 @@
 error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
 error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
 error.version-string-part-not-number=Failed to convert version component to int
-error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
 error.version-swap=Failed to update version information for {0}
-error.version-compare=Error: Failed to compare version {0} with {1.
+error.invalid-envvar=Invalid value of {0} environment variable
 
 message.result-dir=Result application bundle: {0}.
 message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
-message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
 message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
-message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
 message.outputting-to-location=Generating EXE for installer to: {0}.
 message.output-location=Installer (.exe) saved to: {0}
 message.tool-version=Detected [{0}] version [{1}].
-message.running-wsh-script=Running WSH script on application image [{0}].
 message.creating-association-with-null-extension=Creating association with null extension.
 message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
 message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
-message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
-message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
 message.generated-product-guid=Generated product GUID: {0}.
 message.preparing-msi-config=Preparing MSI config: {0}.
 message.generating-msi=Generating MSI: {0}.
-message.light-file-string=WiX light tool set to {0}.
-message.candle-file-string=WiX candle tool set to {0}.
 message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties	Wed Oct 23 10:10:34 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties	Wed Oct 23 10:37:54 2019 -0400
@@ -32,16 +32,10 @@
 
 resource.executable-properties-template=Template for creating executable properties file
 resource.setup-icon=setup dialog icon
-resource.post-install-script=script to run after application image is populated
+resource.post-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
 resource.wxl-file-name=MsiInstallerStrings_en.wxl
 
-error.parameters-null=Parameters map is null
-error.parameters-null.advice=Pass in a non-null parameters map
-error.no-windows-resources=This copy of the JDK does not support Windows
-error.no-windows-resources.advice=Please use the Oracle JDK for Windows
-error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
-error.copyright-is-too-long=The copyright string is too long for InnoSetup
-error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
 error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
 error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH.
 error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
@@ -50,28 +44,21 @@
 error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
 error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
 error.version-string-part-not-number=Failed to convert version component to int
-error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
 error.version-swap=Failed to update version information for {0}
-error.version-compare=Error: Failed to compare version {0} with {1.
+error.invalid-envvar=Invalid value of {0} environment variable
 
 message.result-dir=Result application bundle: {0}.
 message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
-message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
 message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
-message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
 message.outputting-to-location=Generating EXE for installer to: {0}.
 message.output-location=Installer (.exe) saved to: {0}
 message.tool-version=Detected [{0}] version [{1}].
-message.running-wsh-script=Running WSH script on application image [{0}].
 message.creating-association-with-null-extension=Creating association with null extension.
 message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
 message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
-message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
-message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
 message.generated-product-guid=Generated product GUID: {0}.
 message.preparing-msi-config=Preparing MSI config: {0}.
 message.generating-msi=Generating MSI: {0}.
-message.light-file-string=WiX light tool set to {0}.
-message.candle-file-string=WiX candle tool set to {0}.
 message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
 
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java	Wed Oct 23 10:37:54 2019 -0400
@@ -619,7 +619,7 @@
     }
 
     public void verifyIsOfType(PackageType ... types) {
-        final Set<PackageType> typesSet = Set.of(types);
+        final var typesSet = Stream.of(types).collect(Collectors.toSet());
         if (!hasArgument("--package-type")) {
             if (!isImagePackageType()) {
                 if (TKit.isLinux() && typesSet.equals(PackageType.LINUX)) {
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java	Wed Oct 23 10:37:54 2019 -0400
@@ -255,7 +255,8 @@
         Function<List<String>, String> verifier = (lines) -> {
             // Lookup for xdg commands
             return lines.stream().filter(line -> {
-                Set<String> words = Set.of(line.split("\\s+"));
+                Set<String> words = Stream.of(line.split("\\s+")).collect(
+                        Collectors.toSet());
                 return words.contains("xdg-desktop-menu") || words.contains(
                         "xdg-mime") || words.contains("xdg-icon-resource");
             }).findFirst().orElse(null);
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -351,8 +351,8 @@
                 }
             }
 
-            TKit.assertPathExists(cmd.appInstallationDirectory().resolve(
-                    AppImageFile.FILENAME), false);
+            TKit.assertPathExists(AppImageFile.getPathInAppImage(
+                    cmd.appInstallationDirectory()), false);
 
             installVerifiers.stream().forEach(v -> v.accept(cmd));
         }
--- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Wed Oct 23 10:37:54 2019 -0400
@@ -593,18 +593,32 @@
     }
 
     public static void assertTrue(boolean actual, String msg) {
+        assertTrue(actual, msg, null);
+    }
+
+    public static void assertFalse(boolean actual, String msg) {
+        assertFalse(actual, msg, null);
+    }
+
+    public static void assertTrue(boolean actual, String msg, Runnable onFail) {
         currentTest.notifyAssert();
         if (!actual) {
-            error(concatMessages("Unexpected FALSE", msg));
+            if (onFail != null) {
+                onFail.run();
+            }
+            error(concatMessages("Failed", msg));
         }
 
         traceAssert(String.format("assertTrue(): %s", msg));
     }
 
-    public static void assertFalse(boolean actual, String msg) {
+    public static void assertFalse(boolean actual, String msg, Runnable onFail) {
         currentTest.notifyAssert();
         if (actual) {
-            error(concatMessages("Unexpected TRUE", msg));
+            if (onFail != null) {
+                onFail.run();
+            }
+            error(concatMessages("Failed", msg));
         }
 
         traceAssert(String.format("assertFalse(): %s", msg));
--- a/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -154,7 +154,7 @@
 
     private AppImageFile createFromXml(String... xmlData) throws IOException {
         Path directory = tempFolder.getRoot().toPath();
-        Path path = directory.resolve(AppImageFile.FILENAME);
+        Path path = AppImageFile.getPathInAppImage(directory);
         path.toFile().mkdirs();
         Files.delete(path);
 
--- a/test/jdk/tools/jpackage/linux/ShortcutHintTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -21,13 +21,18 @@
  * questions.
  */
 
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.Map;
 import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
 import jdk.jpackage.test.FileAssociations;
 import jdk.jpackage.test.PackageType;
 import jdk.jpackage.test.PackageTest;
 import jdk.jpackage.test.TKit;
 import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.*;
 
 /**
  * Test --linux-shortcut parameter. Output of the test should be
@@ -139,4 +144,42 @@
                     TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString()));
         }).run();
     }
+
+    /**
+     * .desktop file from resource dir.
+     */
+    @Test
+    public static void testDesktopFileFromResourceDir() {
+        final String expectedVersionString = "Version=12345678";
+        TKit.withTempDirectory("resources", tempDir -> {
+            createTest().addInitializer(cmd -> {
+                cmd.setFakeRuntime();
+
+                cmd.addArgument("--linux-shortcut");
+                cmd.addArguments("--resource-dir", tempDir);
+
+                // Create custom .desktop file in resource directory
+                TKit.createTextFile(tempDir.resolve(cmd.name() + ".desktop"),
+                        List.of(
+                                "[Desktop Entry]",
+                                "Name=APPLICATION_NAME",
+                                "Exec=APPLICATION_LAUNCHER",
+                                "Terminal=false",
+                                "Type=Application",
+                                "Categories=DEPLOY_BUNDLE_CATEGORY",
+                                expectedVersionString
+                        ));
+            })
+            .addInstallVerifier(cmd -> {
+                Path desktopFile = cmd.appLayout().destktopIntegrationDirectory().resolve(
+                        String.format("%s-%s.desktop",
+                                LinuxHelper.getPackageName(cmd), cmd.name()));
+                TKit.assertFileExists(desktopFile);
+                TKit.assertTextStream(expectedVersionString)
+                        .label(String.format("[%s] file", desktopFile))
+                        .predicate(String::equals)
+                        .apply(Files.readAllLines(desktopFile).stream());
+            }).run();
+        });
+    }
 }
--- a/test/jdk/tools/jpackage/share/FileAssociationsTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/share/FileAssociationsTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -71,6 +71,8 @@
         Path icon = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon"
                 + TKit.ICON_SUFFIX));
 
+        icon = TKit.createRelativePathCopy(icon);
+
         applyFileAssociations(packageTest,
                 new FileAssociations("jptest2").setFilename("fa2").setIcon(icon));
         packageTest.run();
--- a/test/jdk/tools/jpackage/share/RuntimePackageTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -21,11 +21,15 @@
  * questions.
  */
 
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.HashSet;
 import java.util.Optional;
-import jdk.jpackage.test.TKit;
-import jdk.jpackage.test.PackageTest;
-import jdk.jpackage.test.JPackageCommand;
+import java.util.Set;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Annotations.Test;
 
 /**
  * Test --runtime-image parameter.
@@ -49,23 +53,55 @@
  * @comment Temporary disable for Linux and OSX until functionality implemented
  * @requires (os.family != "mac")
  * @modules jdk.jpackage/jdk.jpackage.internal
- * @run main/othervm/timeout=720 -Xmx512m RuntimePackageTest
+ * @compile RuntimePackageTest.java
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=RuntimePackageTest
  */
 public class RuntimePackageTest {
 
-    public static void main(String[] args) {
-        TKit.run(args, () -> {
-            new PackageTest()
-            .addInitializer(cmd -> {
-                cmd.addArguments("--runtime-image", Optional.ofNullable(
-                        JPackageCommand.DEFAULT_RUNTIME_IMAGE).orElse(Path.of(
-                                System.getProperty("java.home"))));
-                // Remove --input parameter from jpackage command line as we don't
-                // create input directory in the test and jpackage fails
-                // if --input references non existant directory.
-                cmd.removeArgumentWithValue("--input");
-            })
-            .run();
+    @Test
+    public static void test() {
+        new PackageTest()
+        .addInitializer(cmd -> {
+            cmd.addArguments("--runtime-image", Optional.ofNullable(
+                    JPackageCommand.DEFAULT_RUNTIME_IMAGE).orElse(Path.of(
+                            System.getProperty("java.home"))));
+            // Remove --input parameter from jpackage command line as we don't
+            // create input directory in the test and jpackage fails
+            // if --input references non existant directory.
+            cmd.removeArgumentWithValue("--input");
+        })
+        .addInstallVerifier(cmd -> {
+            Set<Path> srcRuntime = listFiles(Path.of(cmd.getArgumentValue("--runtime-image")));
+            Set<Path> dstRuntime = listFiles(cmd.appRuntimeDirectory());
+
+            Set<Path> intersection = new HashSet<>(srcRuntime);
+            intersection.retainAll(dstRuntime);
+
+            srcRuntime.removeAll(intersection);
+            dstRuntime.removeAll(intersection);
+
+            assertFileListEmpty(srcRuntime, "Missing");
+            assertFileListEmpty(dstRuntime, "Unexpected");
+        })
+        .run();
+    }
+
+    private static Set<Path> listFiles(Path root) throws IOException {
+        try (var files = Files.walk(root)) {
+            return files.map(root::relativize).collect(Collectors.toSet());
+        }
+    }
+
+    private static void assertFileListEmpty(Set<Path> paths, String msg) {
+        TKit.assertTrue(paths.isEmpty(), String.format(
+                "Check there are no %s files in installed image",
+                msg.toLowerCase()), () -> {
+            String msg2 = String.format("%s %d files", msg, paths.size());
+            TKit.trace(msg2 + ":");
+            paths.stream().map(Path::toString).sorted().forEachOrdered(
+                    TKit::trace);
+            TKit.trace("Done");
         });
     }
 }
--- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -44,7 +44,7 @@
  * @build jdk.jpackage.test.*
  * @modules jdk.jpackage/jdk.jpackage.internal
  * @compile BasicTest.java
- * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
  *  --jpt-run=jdk.jpackage.tests.BasicTest
  */
 
@@ -116,6 +116,7 @@
     }
 
     @Test
+    @SuppressWarnings("unchecked")
     public void testVerbose() {
         JPackageCommand cmd = JPackageCommand.helloAppImage()
                 .setFakeRuntime().executePrerequisiteActions();
@@ -139,12 +140,19 @@
 
         TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
         List<String> nonVerboseOutput = cmd.createExecutor().executeAndGetOutput();
+        List<String>[] verboseOutput = (List<String>[])new List<?>[1];
 
-        TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
-        List<String> verboseOutput = cmd.createExecutor().addArgument(
-                "--verbose").executeAndGetOutput();
+        // Directory clean up is not 100% reliable on Windows because of
+        // antivirus software that can lock .exe files. Setup
+        // diffreent output directory instead of cleaning the default one for
+        // verbose jpackage run.
+        TKit.withTempDirectory("verbose-output", tempDir -> {
+            cmd.setArgumentValue("--dest", tempDir);
+            verboseOutput[0] = cmd.createExecutor().addArgument(
+                    "--verbose").executeAndGetOutput();
+        });
 
-        TKit.assertTrue(nonVerboseOutput.size() < verboseOutput.size(),
+        TKit.assertTrue(nonVerboseOutput.size() < verboseOutput[0].size(),
                 "Check verbose output is longer than regular");
 
         expectedVerboseOutputStrings.forEach(str -> {
@@ -155,7 +163,7 @@
 
         expectedVerboseOutputStrings.forEach(str -> {
             TKit.assertTextStream(str).label("verbose output")
-                    .apply(verboseOutput.stream());
+                    .apply(verboseOutput[0].stream());
         });
     }
 
--- a/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java	Wed Oct 23 10:10:34 2019 -0400
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -52,7 +52,6 @@
  * @compile MainClassTest.java
  * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
  *  --jpt-run=jdk.jpackage.tests.MainClassTest
- *  --jpt-space-subst=_
  */
 
 public final class MainClassTest {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinScriptTest.java	Wed Oct 23 10:37:54 2019 -0400
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import jdk.jpackage.internal.IOUtils;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test usage of scripts from resource dir
+ * @summary jpackage with
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @compile WinScriptTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinScriptTest
+ */
+
+public class WinScriptTest {
+
+    public WinScriptTest(PackageType type) {
+        this.packageType = type;
+
+        test = new PackageTest()
+        .forTypes(type)
+        .configureHelloApp()
+        .addInitializer(cmd -> {
+            cmd.setFakeRuntime().saveConsoleOutput(true);
+        });
+    }
+
+    @Parameters
+    public static List<Object[]> data() {
+        return List.of(new Object[][]{
+            {PackageType.WIN_MSI},
+            {PackageType.WIN_EXE}
+        });
+    }
+
+    @Test
+    @Parameter("0")
+    @Parameter("10")
+    public void test(int wsfExitCode) {
+        final ScriptData appImageScriptData;
+        if (wsfExitCode != 0 && packageType == PackageType.WIN_EXE) {
+            appImageScriptData = new ScriptData(PackageType.WIN_MSI, 0);
+        } else {
+            appImageScriptData = new ScriptData(PackageType.WIN_MSI, wsfExitCode);
+        }
+
+        final ScriptData msiScriptData = new ScriptData(PackageType.WIN_EXE, wsfExitCode);
+
+        test.setExpectedExitCode(wsfExitCode == 0 ? 0 : 1);
+        TKit.withTempDirectory("resources", tempDir -> {
+            test.addInitializer(cmd -> {
+                cmd.addArguments("--resource-dir", tempDir);
+
+                appImageScriptData.createScript(cmd);
+                msiScriptData.createScript(cmd);
+            });
+
+            if (packageType == PackageType.WIN_MSI) {
+                test.addBundleVerifier((cmd, result) -> {
+                    appImageScriptData.assertJPackageOutput(result.getOutput());
+                });
+            }
+
+            if (packageType == PackageType.WIN_EXE) {
+                test.addBundleVerifier((cmd, result) -> {
+                    appImageScriptData.assertJPackageOutput(result.getOutput());
+                    msiScriptData.assertJPackageOutput(result.getOutput());
+                });
+            }
+
+            test.run();
+        });
+    }
+
+    private static class ScriptData {
+        ScriptData(PackageType scriptType, int wsfExitCode) {
+            if (scriptType == PackageType.WIN_MSI) {
+                echoText = "post app image wsf";
+                envVarName = "JpAppImageDir";
+                scriptSuffixName = "post-image";
+            } else {
+                echoText = "post msi wsf";
+                envVarName = "JpMsiFile";
+                scriptSuffixName = "post-msi";
+            }
+            this.wsfExitCode = wsfExitCode;
+        }
+
+        void assertJPackageOutput(List<String> output) {
+            TKit.assertTextStream(String.format("jp: %s", echoText))
+                    .predicate(String::equals)
+                    .apply(output.stream());
+
+            String cwdPattern = String.format("jp: CWD(%s)=", envVarName);
+            TKit.assertTextStream(cwdPattern)
+                    .predicate(String::startsWith)
+                    .apply(output.stream());
+            String cwd = output.stream().filter(line -> line.startsWith(
+                    cwdPattern)).findFirst().get().substring(cwdPattern.length());
+
+            String envVarPattern = String.format("jp: %s=", envVarName);
+            TKit.assertTextStream(envVarPattern)
+                    .predicate(String::startsWith)
+                    .apply(output.stream());
+            String envVar = output.stream().filter(line -> line.startsWith(
+                    envVarPattern)).findFirst().get().substring(envVarPattern.length());
+
+            TKit.assertTrue(envVar.startsWith(cwd), String.format(
+                    "Check value of %s environment variable [%s] starts with the current directory [%s] set for %s script",
+                    envVarName, envVar, cwd, echoText));
+        }
+
+        void createScript(JPackageCommand cmd) throws IOException {
+           IOUtils.createXml(Path.of(cmd.getArgumentValue("--resource-dir"),
+                    String.format("%s-%s.wsf", cmd.name(), scriptSuffixName)), xml -> {
+                xml.writeStartElement("job");
+                xml.writeAttribute("id", "main");
+                xml.writeStartElement("script");
+                xml.writeAttribute("language", "JScript");
+                xml.writeCData(String.join("\n", List.of(
+                    "var shell = new ActiveXObject('WScript.Shell')",
+                    "WScript.Echo('jp: " + envVarName + "=' + shell.ExpandEnvironmentStrings('%" + envVarName + "%'))",
+                    "WScript.Echo('jp: CWD(" + envVarName + ")=' + shell.CurrentDirectory)",
+                    String.format("WScript.Echo('jp: %s')", echoText),
+                    String.format("WScript.Quit(%d)", wsfExitCode)
+                )));
+                xml.writeEndElement();
+                xml.writeEndElement();
+            });
+        }
+
+        private final int wsfExitCode;
+        private final String scriptSuffixName;
+        private final String echoText;
+        private final String envVarName;
+    }
+
+    private final PackageType packageType;
+    private PackageTest test;
+}