If you’ve not read it, please read my original post on this subject before continuing.

The java uninstaller I posted works a treat most of the time, but not always.  We have a number of machines with Java v1.8 Update 74 on that refuse to uninstall it – even via Programs & Features the Windows installer progress box just sits there.

Process Explorer shows something interesting at this point:
Java Issue

The Windows Installer process, MsiExec.exe, has launched Java Web Start which has then opened a process called JP2Launcher.exe.  I’ve no idea what that is trying to do.  I tried a Fiddler trace of it and it caught nothing.  Whatever it is doing, killing the process allows the uninstall of Java to then continue successfully.

As a result, I’ve made an extended version of my Java Uninstaller script that will kill any instances of this process shortly after they appear.  Here it is:

Rem Remove all versions of Java x86 and x64

On Error Resume Next

Const HKEY_LOCAL_MACHINE = &H80000002
Const WINDOW_HIDDEN = 0
Const WAIT_ON_RETURN = True
Const wbemFlagReturnWhenComplete = 0

Const intJP2KillDelay = 5

Dim objSWbemServices

Set objWshShell = WScript.CreateObject("WScript.Shell")
Set objStdReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
Rem Need Debug privileges to terminate processes we do not own.
Set objSWbemServices = GetObject("winmgmts:{impersonationLevel=impersonate,(Debug)}!\\.\root\cimv2")

Rem MsiExec.exe sometimes launches JavaWS.exe which launches JP2Launcher.exe and blocks uninstall until the latter is killed.
Set objWMISink = WScript.CreateObject("WbemScripting.SWbemSink","SINK_")
strWQL = "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'JP2Launcher.exe'"
objSWbemServices.ExecNotificationQueryAsync objWMISink, strWQL

For i = 1 To 2
	If i = 1 Then
		strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Uninstall"
	Else
		strKeyPath = "Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
	End If

	objStdReg.EnumKey HKEY_LOCAL_MACHINE, strKeyPath, arrSubKeys
	If Not IsNull(arrSubKeys) Then
		For Each strSubKey In arrSubKeys
			Rem Ensure is a GUID key
			If Left(strSubKey, 1) = "{" Then
				strSubKeyPath = strKeyPath & "\" & strSubKey
				objStdReg.GetStringValue HKEY_LOCAL_MACHINE, strSubKeyPath, "DisplayName", strDisplayName
				If Not IsNull(strDisplayName) Then
					intJavaIndex = InStr(LCase(strDisplayName), "java")
					If intJavaIndex > 0 Then
						intUpdateIndex = InStr(LCase(strDisplayName), "update")
						Rem Effectively make the check to be for "*java*update*"
						If intUpdateIndex > intJavaIndex Then
							strCmd = "MsiExec.exe /X " & strSubKey & " /quiet /norestart"
							intRC = objWshShell.Run(strCmd, WINDOW_HIDDEN, WAIT_ON_RETURN)
						End If
					End If
				End If
			End If
		Next
	End If
Next

objWMISink.Cancel
Set objWMISink = Nothing
WScript.Quit

Sub SINK_OnObjectReady(objNextObject, objWbemAsyncContext)
	Rem Kill JP2Launcher.exe processes after a delay (in case it's meant to launch)

	On Error Resume Next

	intPID = objNextObject.TargetInstance.ProcessID
	strPath = objNextObject.TargetInstance.ExecutablePath
	WScript.Sleep intJP2KillDelay * 1000

	Rem Check that process is still running but can't assume the PID has not been reused
	strWQL = "SELECT * FROM Win32_Process WHERE ProcessID = " & intPID
	Set colProcesses = objSWbemServices.ExecQuery(strWQL,, wbemFlagReturnWhenComplete)
	If colProcesses.Count = 0 Then
		rem Already closed
		Exit Sub
	End If
	For Each objProcess In colProcesses
		Rem Will only ever be one match with a given PID!
		If objProcess.ExecutablePath <> strPath Then
			rem Is a different process
			Exit Sub
		End If
	Next

	objNextObject.TargetInstance.Terminate
End Sub

The extra code watches for the creation of a process called JP2Launcher.exe and then kills it after a short delay.  For a comprehensive explanation of how this works, I direct you to this excellent article here.

Firstly, an SWbemSink object is created to determine what code is called when events we’re interested in are raised.  You’ll see I specify the prefix “SINK_” that is to be used in the names of the subroutines called:

Set objWMISink = WScript.CreateObject("WbemScripting.SWbemSink","SINK_")

Next I construct a WQL query that every 3 seconds, looks to see if an event has been raised for the creation of a new instance of a Win32_Process whose name is JP2Launcher.exe:

strWQL = "SELECT * FROM __InstanceCreationEvent WITHIN 3 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'JP2Launcher.exe'"

Given that description, you can work out how to alter the syntax of that query to meet your requirements.  The query and the Sink object are then passed as parameters to a special WMI call called an Asynchronous (i.e. Runs in the background) Notification Query:

objSWbemServices.ExecNotificationQueryAsync objWMISink, strWQL

If and when a matching process is instantiated, this generates an event known as “OnObjectReady” and this, along with the prefix I specified earlier, causes the sink object to call the subroutine SINK_OnObjectReady.

Note the syntax for getting at the properties of the new Win32_Process from inside that subroutine:

intPID = objNextObject.TargetInstance.ProcessID
intPID = objNextObject.TargetInstance.ProcessID
strPath = objNextObject.TargetInstance.ExecutablePath

After a short delay (in case there are circumstances where this process should legitimately appear and then soon disappear during uninstall), the process is killed by calling its Terminate method:

objNextObject.TargetInstance.Terminate

You’ll see in the code I’ve added in an extra check first to see if the process with the same ProcessID still exists and whether it’s pointing at the same executable before I kill it.  This is in case the process had closed and then by chance a new process had been created with the same ID.  I suspect that even if this were the case, that the call to Terminate would just throw an error as the original Process object would be gone and it wouldn’t try and touch the new one, but since I can’t really test this, I’m just being careful!

One last thing to note is that the WMI object used has to have Debug privileges to be able to kill processes launched under a different user context:

Set objSWbemServices = GetObject("winmgmts:{impersonationLevel=impersonate,(Debug)}!\\.\root\cimv2")
Advertisements