JDK-8156895 : ent msi does not have double-click support
  • Type: Bug
  • Component: install
  • Sub-Component: install
  • Affected Version: 7,8
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2016-05-12
  • Updated: 2016-10-13
  • Resolved: 2016-06-03
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
8u112Fixed 9 b124Fixed
Steps to reproduce:

1. Install the enterprise msi by double clicking on it
2. Note that it will get a 1603 error
PIT VERIFIED with JDK9 b123 client PIT: /net/jre.us.oracle.com/onestop/jdk/9/jdk9-client/122_2016-06-08-0301_5133/bundles. Please see the related list for new issues

>>Webrev comments: >>- make/msi/common-properties.wxi: >> no change Actually, line 13 changed. ALLUSERS is no longer needed because we have InstallScope="perMachine" in the jre.xml now, so Wix sets it automatically.

Webrev comments: - make/msi/common-properties.wxi: no change - uninstaller.cpp: configData.GetStringValue(std::nothrow, INSTALL_DIR) should be replaced with configData.GetUtf8Value(INSTALL_DIR) as we don't want to silently ignore case when INSTALL_DIR is missing on the command line. Could you please publish your change as Crucible review. It is more convenient for commenting.

Yes, need to pass extra arg to Bundle ctor. Sorry for confusion. Currently baseimage checksum is calculated by JavaEnvironment, but as you can't use it, you need to pass it on uninstaller.exe command line. Something like this: jre.xml: <?ifdef IsEnterprise ?> <CustomAction Id="uninstallexe" BinaryKey="$(var.UninstallerExeBinaryKey)" ExeCommand="ProductCode=[ProductCode] [SILENT]" Return="ignore" /> <?else?> <CustomAction Id="uninstallexe" BinaryKey="$(var.UninstallerExeBinaryKey)" ExeCommand="ProductCode=[ProductCode] [SILENT]" BaseimageChecksumSHA256=[BaseimageChecksumSHA256] Return="ignore" /> <?endif?> We also need corresponding id in InstallConfigData class for BaseimageChecksumSHA256 msi property.

Here is the current webrev for JDK9 for this change: http://oklahoma.us.oracle.com/~wharnois/8156895/ Everything is working correctly for all users now (standard, admin, system), except for the uninstaller is not removing the base image during uninstall when it was a consumer install. You mentioned: "In jdk9 you still need to pass another argument to InstalledJava ctor - baseimage checksum. For enterprise JRE which doesn't depend on baseimage there is special value for this checksum. " I'm not sure what you mean by this. I do not see anything about baseimages in the InstallJava class. This is stored in the Bundle class now right? Are you saying to pass the checksum to the Bundle ctor in the first arg to the InstallJava ctor? InstalledJava(Bundle(JRE) ?

When I move the custom action to deferred/impersonate="no" and placed it right after InstallInitialize in the sequence, the files are still in the JRE image when it gets run. So we are good now. That was a false alarm.

We can't call RegisterDeploy(FALSE) after MSI removes files from Java home. Seems like simple plan to change order of uninstaller.exe call doesn't work. I'm not sure, but probably we should run uninstaller.exe twice: once to do clean up actions with impersonate="yes" and the second time impersonate="no" deferred="yes". This is major rework.

Solution was to move the custom action to immediately after InstallInitialize, so it now gets run as deferred (and therefore as system user), but still after the actual files are removed. Was still getting the RegisterDeploy(FALSE) error, but that was because it still didn't know what INSTALLDIR was. Additionally, we need to pass INSTALLDIR="[INSTALLDIR]\" to the uninstaller.exe. Otherwise, it will not know where Java is installed and won't be able to load msvc*/deploy.dll properly.

Ran into a new issue with the uninstaller. Now that we launch uninstaller.exe as deferred, it tries to call RegisterDeploy(FALSE) to uninstall deploy. It will try to load msvc*.dll and deploy.dll, but the msi has already removed them. So we will need to come up with a solution for this.

Minor changes to the line: const InstalledJava thisProduct = InstalledJava(Bundle(JRE), configData.GetStringValue(std::nothrow, INSTALL_DIR), Guid(configData.GetUtf8Value(PRODUCT_CODE)), InstalledJava::unknownMode()); Seems to work well, but I ran into another issue down the line.

>>I don't see any recent changes in jre.xml in I meant in my local repo. Sorry that wasn't clear.

I don't see any recent changes in jre.xml in http://closedjdk.us.oracle.com/jdk8u/jdk8u-cpu/install, so I don't quite follow what changes have been done to jre msi behaior. Still I can suggest the following replacement for je::getByProductCode call in uninstaller.exe: --- const InstalledJava thisProduct = InstalledJava(Bundle(JRE), configData.GetUtf8Value(INSTALLDIR), Guid(configData.GetUtf8Value(PRODUCT_CODE)), InstalledJava::unknownMode()); --- This is quick and dirty fix. Known issues: - JRE mode (consumer/static) is not detected properly, just hardcoded; - In jdk9 you still need to pass another argument to InstalledJava ctor - baseimage checksum. For enterprise JRE which doesn't depend on baseimage there is special value for this checksum. I suggest to give this change a try. If it works well we can think about how to make this fix more accurate.

The install changes are working fine now for all users (system, admin, standard). But there is a new problem with the uninstaller for standard user. If the msi is launched from the standard user, it will install fine. But when the uninstaller runs the uninstaller.exe program, it will fail. This is because the uninstallerexe custom action is still set to impersonate="yes", which says to run it as the original invoker of the msi. So even during uninstall, it will launch the uninstaller.exe as the original invoker of the user that installed the msi. Therefore it runs it as standard user, which does not have access to the uninstaller.exe. The correct thing to do is to set impersonate="no" and launch it as deferred instead of immediate. However, this presents a new problem with the uninstaller.cpp changes introduced in 8u60. In 8u25, we launched the UninstallJRE() function like this: UninstallJRE(VERSION, DURING_JRE_UNINSTALL); The latest 8 and 9 launches it like this: UninstallJRE(installedJavas, thisProduct, configData); This product is determined by 60 const InstalledJava thisProduct = je::getByProductCode(installedJavas, 61 Guid(configData.GetUtf8Value(PRODUCT_CODE))); Which now queries the MSI database to find the jre version and javahome, etc. But because the custom action is now deferred, it gets run after the msi is uninstalled. This version of the JRE is no longer in the msi database, so this code cannot find thisproduct. So we need a solution to this problem. One idea is to go back to the old way of launching UninstallJRE. We still wouldn't know the JavaHome because the registry would be removed at this point. So we could have the msi pass INSTALLDIR to the uninstaller.exe via arg.

The reason for the 1603 error is because of the installer.exe custom action. This custom action is set to "deferred in the system context" and impersonate="yes". The impersonate="yes" part tells the installer to run it as the original invoker of the msi. This is just fine when it's run from a cmd.exe that was launched by the system or admin user. But it is not ok when launched by a non-admin, because it does not have execute rights to launch installer.exe in the PF/Java/jre directory. Double clicking on an msi runs non-elevated, which is essentially the same as non-admin. According to msi/wix documentation, Custom actions that need elevated privileges should have impersonate="no". This makes the custom action run as the Windows Installer service, which has system privileges. So the first part of the solution is to set impersonate="no". While this initially seems like an easy solution, it appears that the custom action is actually run with a few less security tokens than advertised. I've read that other developers have noticed this. It can write to all registry and install files anywhere, but it is missing some security tokens like SE_CREATE_SYMBOLIC_LINK_NAME. So before pushing the change, I noticed that it was failing when creating the java.exe/javaw.exe symlinks. I tried using the adjusttokenprivileges() windows api to add the token, but that failed too. It appears that everything else that installer.exe is working fine except for the symlink problem. Background: the reason we use symlinks is to avoid filesinuse situations when java.exe/javaw.exe files are locked up. Symlinks do not get locked. Potential solutions to maintain the filesinuse protection. Solution 1: Don't use symlinks, but use lightweight wrapper java.exe/javaw.exe files that simply run the real java.exe's and exit out. There would be a slim chance of running into FIU issues. Solution 2: Use the real exe's instead of symlinks. Randomize the javapath directory name so that it's always unique, and will never create a filesinuse situation. Update the ENV property to point to the new location. It's pretty much a hidden directory anyways, so most people wouldn't notice. Attempt to remove the old javapath, but schedule a remove-on-reboot if locked (do not ask for a system restart). Solution 3: Use Junctions like we already do with winXP. Or we could simply do nothing and say that we don't support non-admin msi installs (or double click). Looks like we will go with #3. We can't go with #1 due to java.exe being a console app and needing to wait and return error codes.