8214566: --win-dir-chooser does not prompt if destination folder is not empty JDK-8200758-branch
authorherrick
Mon, 11 Mar 2019 13:24:47 -0400
branchJDK-8200758-branch
changeset 57254 c1b92a014e89
parent 57253 f0e513137db2
child 57255 f686bda3b831
8214566: --win-dir-chooser does not prompt if destination folder is not empty Submitten-by: almatvee Reviewed-by: herrick
make/lib/Lib-jdk.jpackage.gmk
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.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
src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs
src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp
--- a/make/lib/Lib-jdk.jpackage.gmk	Mon Mar 11 13:21:49 2019 -0400
+++ b/make/lib/Lib-jdk.jpackage.gmk	Mon Mar 11 13:24:47 2019 -0400
@@ -67,3 +67,21 @@
 
 endif
 
+# Build Wix custom action helper
+# Output library in resources dir, and symbols in the object dir
+ifeq ($(OPENJDK_TARGET_OS), windows)
+
+  $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \
+      NAME := wixhelper, \
+      OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+      SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \
+      OPTIMIZATION := LOW, \
+      CFLAGS := $(CXXFLAGS_JDKLIB), \
+      CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+      LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK), \
+      LIBS := $(LIBCXX), \
+      LIBS_windows := msi.lib Shlwapi.lib User32.lib, \
+  ))
+
+  TARGETS += $(BUILD_LIB_WIXHELPER)
+endif
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java	Mon Mar 11 13:21:49 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java	Mon Mar 11 13:24:47 2019 -0400
@@ -592,6 +592,15 @@
 
         data.put("UI_BLOCK", getUIBlock(params));
 
+        // Add CA to check install dir
+        if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
+            data.put("CA_BLOCK", CA_BLOCK);
+            data.put("INVALID_INSTALL_DIR_DLG_BLOCK", INVALID_INSTALL_DIR_DLG_BLOCK);
+        } else {
+            data.put("CA_BLOCK", "");
+            data.put("INVALID_INSTALL_DIR_DLG_BLOCK", "");
+        }
+
         List<Map<String, ? super Object>> secondaryLaunchers =
                 SECONDARY_LAUNCHERS.fetchFrom(params);
 
@@ -635,6 +644,28 @@
     private int compId;
     private final static String LAUNCHER_ID = "LauncherId";
 
+    private static final String CA_BLOCK =
+            "<Binary Id=\"CustomActionDLL\" SourceFile=\"wixhelper.dll\" />\n" +
+            "<CustomAction Id=\"CHECK_INSTALLDIR\" BinaryKey=\"CustomActionDLL\" " +
+            "DllEntry=\"CheckInstallDir\" />";
+
+    private static final String INVALID_INSTALL_DIR_DLG_BLOCK =
+            "<Dialog Id=\"InvalidInstallDir\" Width=\"300\" Height=\"85\" " +
+            "Title=\"[ProductName] Setup\" NoMinimize=\"yes\">\n" +
+            "<Control Id=\"InvalidInstallDirYes\" Type=\"PushButton\" X=\"100\" Y=\"55\" " +
+            "Width=\"50\" Height=\"15\" Default=\"no\" Cancel=\"no\" Text=\"Yes\">\n" +
+            "<Publish Event=\"NewDialog\" Value=\"VerifyReadyDlg\">1</Publish>\n" +
+            "</Control>\n" +
+            "<Control Id=\"InvalidInstallDirNo\" Type=\"PushButton\" X=\"150\" Y=\"55\" " +
+            "Width=\"50\" Height=\"15\" Default=\"yes\" Cancel=\"yes\" Text=\"No\">\n" +
+            "<Publish Event=\"NewDialog\" Value=\"InstallDirDlg\">1</Publish>\n" +
+            "</Control>\n" +
+            "<Control Id=\"Text\" Type=\"Text\" X=\"25\" Y=\"15\" Width=\"250\" Height=\"30\" " +
+            "TabSkip=\"no\">\n" +
+            "<Text>" + I18N.getString("message.install.dir.exist") + "</Text>\n" +
+            "</Control>\n" +
+            "</Dialog>";
+
     /**
      * Overrides the dialog sequence in built-in dialog set "WixUI_InstallDir"
      * to exclude license dialog
@@ -642,12 +673,21 @@
     private static final String TWEAK_FOR_EXCLUDING_LICENSE =
               "     <Publish Dialog=\"WelcomeDlg\" Control=\"Next\""
             + "              Event=\"NewDialog\" Value=\"InstallDirDlg\""
-            + " Order=\"2\"> 1"
-            + "     </Publish>\n"
+            + " Order=\"2\">1</Publish>\n"
             + "     <Publish Dialog=\"InstallDirDlg\" Control=\"Back\""
             + "              Event=\"NewDialog\" Value=\"WelcomeDlg\""
-            + " Order=\"2\"> 1"
-            + "     </Publish>\n";
+            + " Order=\"2\">1</Publish>\n";
+
+    private static final String CHECK_INSTALL_DLG_CTRL =
+              "     <Publish Dialog=\"InstallDirDlg\" Control=\"Next\""
+            + "              Event=\"DoAction\" Value=\"CHECK_INSTALLDIR\""
+            + " Order=\"3\">1</Publish>\n"
+            + "     <Publish Dialog=\"InstallDirDlg\" Control=\"Next\""
+            + "              Event=\"NewDialog\" Value=\"InvalidInstallDir\""
+            + " Order=\"5\">INSTALLDIR_VALID=\"0\"</Publish>\n"
+            + "     <Publish Dialog=\"InstallDirDlg\" Control=\"Next\""
+            + "              Event=\"NewDialog\" Value=\"VerifyReadyDlg\""
+            + " Order=\"5\">INSTALLDIR_VALID=\"1\"</Publish>\n";
 
     // Required upgrade element for installers which support major upgrade (when user
     // specifies --win-upgrade-uuid). We will allow downgrades.
@@ -671,23 +711,28 @@
      * WixUI_InstallDir for installdir dialog only or for both
      * installdir/license dialogs
      */
-    private String getUIBlock(Map<String, ? super Object> params) {
-        String uiBlock = "     <UI/>\n"; // UI-less element
+    private String getUIBlock(Map<String, ? super Object> params) throws IOException {
+        String uiBlock = ""; // UI-less element
+
+        // Copy CA dll to include with installer
+        if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
+            File helper = new File(CONFIG_ROOT.fetchFrom(params), "wixhelper.dll");
+            try (InputStream is_lib = getResourceAsStream("wixhelper.dll")) {
+                Files.copy(is_lib, helper.toPath());
+            }
+        }
 
         if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
             boolean enableTweakForExcludingLicense =
                     (getLicenseFile(params) == null);
-            uiBlock = "     <UI>\n"
-                    + "     <Property Id=\"WIXUI_INSTALLDIR\""
+            uiBlock = "     <Property Id=\"WIXUI_INSTALLDIR\""
                     + " Value=\"APPLICATIONFOLDER\" />\n"
                     + "     <UIRef Id=\"WixUI_InstallDir\" />\n"
                     + (enableTweakForExcludingLicense ?
                             TWEAK_FOR_EXCLUDING_LICENSE : "")
-                    +"     </UI>\n";
+                    + CHECK_INSTALL_DLG_CTRL;
         } else if (getLicenseFile(params) != null) {
-            uiBlock = "     <UI>\n"
-                    + "     <UIRef Id=\"WixUI_Minimal\" />\n"
-                    + "     </UI>\n";
+            uiBlock = "     <UIRef Id=\"WixUI_Minimal\" />\n";
         }
 
         return uiBlock;
@@ -1076,6 +1121,13 @@
             commandLine.add("-ext");
             commandLine.add("WixUIExtension.dll");
         }
+
+        // Only needed if we using CA dll, so Wix can find it
+        if (enableInstalldirUI) {
+            commandLine.add("-b");
+            commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+        }
+
         commandLine.add("-out");
         commandLine.add(msiOut.getAbsolutePath());
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties	Mon Mar 11 13:21:49 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties	Mon Mar 11 13:24:47 2019 -0400
@@ -129,4 +129,5 @@
 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.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway?
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties	Mon Mar 11 13:21:49 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties	Mon Mar 11 13:24:47 2019 -0400
@@ -129,4 +129,5 @@
 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.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway?
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties	Mon Mar 11 13:21:49 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties	Mon Mar 11 13:24:47 2019 -0400
@@ -129,4 +129,5 @@
 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.install.dir.exist=The folder [APPLICATIONFOLDER] already exist. Whould you like to install to that folder anyway?
 
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs	Mon Mar 11 13:21:49 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/template.wxs	Mon Mar 11 13:24:47 2019 -0400
@@ -1,48 +1,52 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
-     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
-    <Product Id="PRODUCT_GUID" Name="APPLICATION_NAME"
-             Language="1033" Version="APPLICATION_VERSION"
-             Manufacturer="APPLICATION_VENDOR"
-             UpgradeCode="PRODUCT_UPGRADE_GUID">
-        <Package Description="APPLICATION_DESCRIPTION" Comments="None"
-                 InstallerVersion="200" Compressed="yes"
-                 InstallScope="INSTALL_SCOPE" Platform="PLATFORM"/>
-        <Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
-UPGRADE_BLOCK
-
-        <!-- We use RemoveFolderEx to ensure application folder is fully
-             removed on uninstall. Including files created outside of MSI
-             after application had been installed (e.g. on AU or user state).
-
-             Hovewer, RemoveFolderEx is only available in WiX 3.6,
-             we will comment it out if we running older WiX.
-
-             RemoveFolderEx requires that we "remember" the path for uninstall.
-             Read the path value and set the APPLICATIONFOLDER property with the value.
-        -->
-        <Property Id="APPLICATIONFOLDER">
-            <RegistrySearch Key="SOFTWARE\APPLICATION_VENDOR\APPLICATION_NAME"
-                            Root="REGISTRY_ROOT" Type="raw"
-                            Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
-        </Property>
-        <DirectoryRef Id="APPLICATIONFOLDER">
-            <Component Id="CleanupMainApplicationFolder" Guid="*" Win64="WIN64">
-                <RegistryValue Root="REGISTRY_ROOT"
-                                   Key="SOFTWARE\APPLICATION_VENDOR\APPLICATION_NAME"
-                                   Name="Path" Type="string" Value="[APPLICATIONFOLDER]"
-                                   KeyPath="yes" />
-                <!-- We need to use APPLICATIONFOLDER variable here or RemoveFolderEx
-                     will not remove on "install". But only if WiX 3.6 is used. -->
-                WIX36_ONLY_START
-                  <util:RemoveFolderEx On="uninstall" Property="APPLICATIONFOLDER" />
-                WIX36_ONLY_END
-            </Component>
-        </DirectoryRef>
-        <?include bundle.wxi ?>
-UI_BLOCK
-        <Icon Id="DesktopIcon.exe" SourceFile="APPLICATION_ICON" />
-        <Icon Id="StartMenuIcon.exe" SourceFile="APPLICATION_ICON" />
-SECONDARY_LAUNCHER_ICONS
-    </Product>
-</Wix>
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
+     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
+    <Product Id="PRODUCT_GUID" Name="APPLICATION_NAME"
+             Language="1033" Version="APPLICATION_VERSION"
+             Manufacturer="APPLICATION_VENDOR"
+             UpgradeCode="PRODUCT_UPGRADE_GUID">
+        <Package Description="APPLICATION_DESCRIPTION" Comments="None"
+                 InstallerVersion="200" Compressed="yes"
+                 InstallScope="INSTALL_SCOPE" Platform="PLATFORM"/>
+        <Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
+        UPGRADE_BLOCK
+
+        <!-- We use RemoveFolderEx to ensure application folder is fully
+             removed on uninstall. Including files created outside of MSI
+             after application had been installed (e.g. on AU or user state).
+
+             Hovewer, RemoveFolderEx is only available in WiX 3.6,
+             we will comment it out if we running older WiX.
+
+             RemoveFolderEx requires that we "remember" the path for uninstall.
+             Read the path value and set the APPLICATIONFOLDER property with the value.
+        -->
+        <Property Id="APPLICATIONFOLDER">
+            <RegistrySearch Key="SOFTWARE\APPLICATION_VENDOR\APPLICATION_NAME"
+                            Root="REGISTRY_ROOT" Type="raw"
+                            Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
+        </Property>
+        <DirectoryRef Id="APPLICATIONFOLDER">
+            <Component Id="CleanupMainApplicationFolder" Guid="*" Win64="WIN64">
+                <RegistryValue Root="REGISTRY_ROOT"
+                               Key="SOFTWARE\APPLICATION_VENDOR\APPLICATION_NAME"
+                               Name="Path" Type="string" Value="[APPLICATIONFOLDER]"
+                               KeyPath="yes" />
+                <!-- We need to use APPLICATIONFOLDER variable here or RemoveFolderEx
+                     will not remove on "install". But only if WiX 3.6 is used. -->
+                WIX36_ONLY_START
+                  <util:RemoveFolderEx On="uninstall" Property="APPLICATIONFOLDER" />
+                WIX36_ONLY_END
+            </Component>
+        </DirectoryRef>
+        <?include bundle.wxi ?>
+        CA_BLOCK
+        <UI>
+            INVALID_INSTALL_DIR_DLG_BLOCK
+            UI_BLOCK
+        </UI>
+        <Icon Id="DesktopIcon.exe" SourceFile="APPLICATION_ICON" />
+        <Icon Id="StartMenuIcon.exe" SourceFile="APPLICATION_ICON" />
+        SECONDARY_LAUNCHER_ICONS
+    </Product>
+</Wix>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp	Mon Mar 11 13:24:47 2019 -0400
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#include <Windows.h>
+#include <msiquery.h>
+#include <shlwapi.h>
+
+extern "C" {
+
+#ifdef JP_EXPORT_FUNCTION
+#error Unexpected JP_EXPORT_FUNCTION define
+#endif
+#define JP_EXPORT_FUNCTION comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
+
+    BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
+            LPVOID lpvReserved) {
+        return TRUE;
+    }
+
+    BOOL DirectoryExist(TCHAR *szValue) {
+        DWORD attr = GetFileAttributes(szValue);
+        if (attr == INVALID_FILE_ATTRIBUTES) {
+            return FALSE;
+        }
+
+        if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+            return TRUE;
+        }
+
+        return FALSE;
+    }
+
+    UINT __stdcall CheckInstallDir(MSIHANDLE hInstall) {
+        #pragma JP_EXPORT_FUNCTION
+
+        TCHAR *szValue = NULL;
+        DWORD cchSize = 0;
+
+        UINT result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"), TEXT(""), &cchSize);
+        if (result == ERROR_MORE_DATA) {
+            cchSize = cchSize + 1; // NULL termination
+            szValue = new TCHAR[cchSize];
+            if (szValue) {
+                result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"), szValue, &cchSize);
+            } else {
+                return ERROR_INSTALL_FAILURE;
+            }
+        }
+
+        if (result != ERROR_SUCCESS) {
+            delete [] szValue;
+            return ERROR_INSTALL_FAILURE;
+        }
+
+        if (DirectoryExist(szValue)) {
+            if (PathIsDirectoryEmpty(szValue)) {
+                MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+            } else {
+                MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("0"));
+            }
+        } else {
+            MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+        }
+
+        delete [] szValue;
+
+        return ERROR_SUCCESS;
+    }
+}