Index: main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java	(revision 31644)
+++ main/trunk/gli/src/org/greenstone/gatherer/file/FileAssociationManager.java	(revision 31646)
@@ -117,5 +117,5 @@
 	    String [] browsers = new String [] {"mozilla", "netscape", "firefox"};
 	    for (int i=0; i<browsers.length; i++) {
-		if (isAvailable(browsers[i])) {
+		if (SafeProcess.isAvailable(browsers[i])) {
 		    command = browsers[i]+ " %1";
 		    break;
@@ -351,20 +351,3 @@
     }
 
-
-    protected boolean isAvailable(String program) {
-	try {
-	    // `which bla` does nothing, prompt is returned
-	    // `which grep` returns a line of output with the path to grep
-	    SafeProcess prcs = new SafeProcess("which " + program);
-	    prcs.runProcess();
-	    String output = prcs.getStdOutput();
-	    if(output.equals("")) {
-		return false;
-	    }
-	    //System.err.println("*** 'which " + program + "' returned: " + output);
-	    return true;
-	} catch (Exception exc) {
-	    return false;
-	}
-    }
 }
Index: main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java
===================================================================
--- main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java	(revision 31644)
+++ main/trunk/gli/src/org/greenstone/gatherer/util/SafeProcess.java	(revision 31646)
@@ -11,4 +11,12 @@
 import java.io.OutputStreamWriter;
 import java.util.Arrays;
+import java.util.Scanner;
+
+import com.sun.jna.*;
+import com.sun.jna.platform.win32.Kernel32;
+import com.sun.jna.platform.win32.WinNT;
+
+import java.lang.reflect.Field;
+//import java.lang.reflect.Method;
 
 import org.apache.log4j.*;
@@ -253,6 +261,5 @@
 
 	    if( this.forciblyTerminateProcess ) {
-		process.destroy(); // see runProcess() below
-		//killAnyPerl();
+		destroyProcess(process); // see runProcess() below		
 	    }
 		process = null;
@@ -299,5 +306,5 @@
 	    if(procErrHandler == null) {
 		errorGobbler // ReaderFromProcessOutputStream
-		    = new SafeProcess.InputStreamGobbler(process.getErrorStream(), splitStdErrorNewLines);
+		    = new SafeProcess.InputStreamGobbler(process.getErrorStream(), this.splitStdErrorNewLines);
 	    } else {
 		errorGobbler
@@ -308,5 +315,5 @@
 	    if(procOutHandler == null) {
 		outputGobbler
-		    = new SafeProcess.InputStreamGobbler(process.getInputStream(), splitStdOutputNewLines);
+		    = new SafeProcess.InputStreamGobbler(process.getInputStream(), this.splitStdOutputNewLines);
 	    } else {
 		outputGobbler
@@ -345,6 +352,5 @@
 	    if( this.forciblyTerminateProcess ) {
 		log("*** Going to call process.destroy 2");
-		process.destroy();
-		//killAnyPerl();
+		destroyProcess(process);				
 		log("*** Have called process.destroy 2");
 	    }
@@ -434,6 +440,5 @@
 	    if( this.forciblyTerminateProcess ) {
 		log("*** Going to call process.destroy 1");
-		process.destroy();
-		//killAnyPerl();
+		destroyProcess(process); 	
 		log("*** Have called process.destroy 1");
 	    }
@@ -454,9 +459,265 @@
 	// only on Windows do we need to still kill perl despite process.destroy()
 	try{
-		Runtime.getRuntime().exec("taskkill /f /im perl.exe");		
+		//Runtime.getRuntime().exec("taskkill /f /im perl.exe");
+		SafeProcess proc = new SafeProcess("taskkill /f /im perl.exe");
+			// /f Specifies to forcefully terminate the process(es).
+			//  /t Terminates the specified process and any child processes which were started by it.
+			// /im is the image name (the name of the program), see Image Name column in Win Task Manager.
+		int exitValue = proc.runBasicProcess(); // don't care about IOstreams
 	} catch(Exception e) {
 		log("@@@ Exception attempting to stop perl " + e.getMessage(), e);		
 	}
 }
+
+// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
+// http://www.golesny.de/p/code/javagetpid
+// Need to use JNA (Java Native Access), http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id
+// Got 4.1.0 from https://mvnrepository.com/artifact/net.java.dev.jna/jna/4.1.0 and https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform/4.1.0
+// (see also https://mvnrepository.com/artifact/net.java.dev.jna/jna), since github's GettingStarted doc talks about downloading jna.jar but never provides it. 
+// See https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
+public static long getProcessID(Process p)
+{
+	long pid = -1;
+	try
+	{
+		//for windows
+		if (p.getClass().getName().equals("java.lang.Win32Process") ||
+			   p.getClass().getName().equals("java.lang.ProcessImpl")) 
+		{
+			Field f = p.getClass().getDeclaredField("handle");
+			f.setAccessible(true);              
+			long handl = f.getLong(p);
+			Kernel32 kernel = Kernel32.INSTANCE;
+			WinNT.HANDLE hand = new WinNT.HANDLE();
+			hand.setPointer(Pointer.createConstant(handl));
+			pid = kernel.GetProcessId(hand);
+			f.setAccessible(false);
+		}
+		//for unix based operating systems
+		else if (p.getClass().getName().equals("java.lang.UNIXProcess")) 
+		{
+			Field f = p.getClass().getDeclaredField("pid");
+			f.setAccessible(true);
+			pid = f.getLong(p);
+			f.setAccessible(false);
+		}
+	}
+	catch(Exception ex)
+	{
+		log("SafeProcess.getProcessID(): Exception when attempting to get process ID for process " + ex.getMessage(), ex);	
+		pid = -1;
+	}
+	return pid;
+}
+
+// UNUSED - only kills toplevel process, same as p.destroy().
+// Call destroyProcess(p) instead to kill any childprocesses launched
+static void killProcessIfOnWindows(Process p) {
+	if(!Utility.isWindows()) return;
+	
+	// only on Windows do we need to still kill perl despite process.destroy()
+	// http://stackoverflow.com/questions/4750470/how-to-get-pid-of-process-ive-just-started-within-java-program
+	// http://www.golesny.de/p/code/javagetpid
+	long processID = getProcessID(p);
+	
+	SafeProcess.killWinProcessWithID(processID);	
+}
+
+
+// Eventually move to using https://github.com/flapdoodle-oss/de.flapdoodle.embed.process/blob/master/src/main/java/de/flapdoodle/embed/process/runtime/Processes.java ?
+// Works with Apache license, http://www.apache.org/licenses/LICENSE-2.0
+
+
+// stackoverflow.com/questions/1835885/send-ctrl-c-to-process-open-by-java	
+// (Taskkill command can kill all running perl. But what if we kill perl instances not launched by GS?)
+// stackoverflow.com/questions/912889/how-to-send-interrupt-key-sequence-to-a-java-process
+// Searching for: "forcibly terminate external process launched by Java on Windows"	
+static void killWinProcessWithID(long processID) {
+	
+	// check if we have taskkill or tskill
+	String cmd = null;
+	if (SafeProcess.isAvailable("wmic")) {
+		// https://isc.sans.edu/diary/Windows+Command-Line+Kung+Fu+with+WMIC/1229
+		cmd = "wmic process " + processID + " delete"; // like "kill -9" on Windows
+	} else if(SafeProcess.isAvailable("taskkill")) {
+		cmd = "taskkill /f /t /PID " + processID;
+	} else { //if(SafeProcess.isAvailable("tskill")) { can't check availability since "which tskill" doesn't ever succeed
+		cmd = "tskill " + processID; // https://ss64.com/nt/tskill.html
+	}
+	cmd = "tskill " + processID;
+	
+	try{		
+		// Despite the /T flag, the following still doesn't kill subprocesses. Taskkill is also not available
+		// universally. Try alternates tskill and pskill, though they too don't kill further processes launched by p.
+		log("Attempting to taskkil process with ID " + processID);
+		//Runtime.getRuntime().exec("taskkill /f /t /PID " + processID);
+		SafeProcess proc = new SafeProcess(cmd);
+			// /f Specifies to forcefully terminate the process(es).
+			//  /t Terminates the specified process and any child processes which were started by it.
+		int exitValue = proc.runProcess(); // no IOstreams for Taskkill, but for "wmic process pid delete" there is output that needs flushing
+			
+	} catch(Exception e) {
+		log("@@@ Exception attempting to stop perl " + e.getMessage(), e);		
+	}
+}
+
+// On linux and mac, p.destroy() suffices to kill processes launched by p as well.
+// On Windows we need to do more work, since processes launched by p remain around executing until termination otherwise.
+// e.g. full-import.pl may be terminated with p.destroy(), but it launches import.pl which is left running until termination.
+static void destroyProcess(Process p) {
+	//killProcessIfOnWindows(p); // doesn't work: only terminates first level process launched, same as p.destroy()
+	//p.destroy();
+	//killAnyPerl(); // kills any running perl, even those not launched by GS
+	
+	if(!Utility.isWindows()) {
+		p.destroy();
+		return;
+	}
+	
+	
+	
+	// We don't want to kill all perl running processes. Just the ones whose command we sort of know and can see in TaskManager
+	// under the "Command Line" column of the Processes tab.
+	// This is possible with wmic, available since xp. First check wmic is really present on this Windows machine
+	// https://superuser.com/questions/52159/kill-a-process-with-a-specific-command-line-from-command-line
+	if(!SafeProcess.isAvailable("wmic")) {
+		log("wmic used to kill subprocesses is not available. Unable to terminate subprocesses...");
+		log("Kill them manually from the TaskManager or they will proceed to run to termination");
+		
+		// At least we can get rid of the top level process we launched
+		p.destroy();
+		return;
+	}
+	
+	
+	// get the process id of the process we launched
+	long processID = SafeProcess.getProcessID(p);
+	
+	
+	// What we want to be able to do on Windows is to kill a process by its command line as seen in Task Manager for that process.
+	// full-import.pl launches import.pl, full-build will launch buildcol.pl and activate.pl.
+	// These tend to have the collection name still at the end, so we can terminate a process knowing the collection name, since
+	// we can use wildcards in our wmic termination command.
+	// However, lucene_passes.pl is launched by buildcol.pl and doesn't have the collection name in its launching cmd line.
+	
+	// Works beatufilly to kill any CommandLine (as seen in TaskManager) that matches
+	//C:>wmic PATH win32_process Where "CommandLine like '%-site%localsite%%lucene-jdbm-demo%'" Call Terminate
+	// https://codeslammer.wordpress.com/2009/02/21/wmic-wildcard-search-using-like-and/
+	// "WMIC Wildcard Search using like and %"
+	// All subsequent perl processes launched by Greenstone are launched with bin\windows\perl\bin\perl. So this works well:
+	// wmic PATH win32_process Where "CommandLine like '%bin%windows%perl%bin%perl%'" Call Terminate
+	// However do we want to terminate all perl processes launched by Greenstone, or only those that we can infer were spawned by
+	// the SafeProcess that we've been running?
+	
+	/*
+	// Works in terminal, but when run through GLI, GLI is terminated too...
+	try{		
+		
+		log("Attempting to use Windows to terminate sub processes");
+		Runtime.getRuntime().exec("wmic PATH win32_process Where \"CommandLine like '%bin%windows%perl%bin%perl%'\" Call Terminate");
+	} catch(Exception e) {
+		log("@@@ Exception attempting to stop perl " + e.getMessage(), e);		
+	}
+	*/
+	
+	// https://superuser.com/questions/851692/track-which-program-launches-a-certain-process
+	// Can get parent process id and process id, and then kill those with child process id
+	// http://stackoverflow.com/questions/7486717/finding-parent-process-id-on-windows
+	// Can get the pids of all childprocesses launched by parent process with parent pid.
+	// And vice versa:
+	// e.g. if you know the parent pid and want to know all the pids of the child processes spawned:
+	// wmic process where (parentprocessid=596) get processid
+	// if you know a child process id and want to know the parent's id:
+	// wmic process where (processid=180) get parentprocessid
+	
+	String[] child_pids = null;
+		
+		
+	log("Attempting to use Windows to terminate sub processes");
+	SafeProcess proc = new SafeProcess("wmic process where (parentprocessid="+processID+") get processid");
+	proc.setSplitStdOutputNewLines(true); // since this is windows, splits lines by \r\n
+	// Output looks like:
+	// ProcessId
+	// 6040
+	// 180
+	// 4948
+	// 1084
+	// 6384
+	// If no children, then STDERR output starts with the following, possibly succeeded by empty lines:
+	// No Instance(s) Available.
+	int exitValue = proc.runProcess(); // exitValue (%ERRORLEVEL%) is 0 either way.	
+	log("@@@@ Return value from proc: " + exitValue);
+	String stdOutput = proc.getStdOutput();
+	// http://stackoverflow.com/questions/691184/scanner-vs-stringtokenizer-vs-string-split
+		
+	log("@@@@ Got output:\n" + stdOutput);
+	if(stdOutput.indexOf("No Instance(s) Available.") == -1) {
+		//int start = stdOutput.indexOf("\n"); // skip first line, which is the column name "ProcessId"
+		//stdOutput = stdOutput.substring(start+1);		
+		//child_pids = stdOutput.split("/(\\r?\\n/)+"); // get rid of newline, so we should only have a list of Long numbers
+		
+		// Now we can get rid of the top level process
+		p.destroy();
+		// find all childprocesses for that pid and terminate them too:
+		Scanner sc = new Scanner(stdOutput);
+		while (sc.hasNext()) {
+		  if(!sc.hasNextLong()) {
+			  sc.next(); // discard the current token since it's not a Long
+		  } else {
+			long child_pid = sc.nextLong();
+			log("Attempting to terminate child pid: " + child_pid);
+			SafeProcess.killWinProcessWithID(child_pid);
+		  }
+		}
+		sc.close();
+	} else {
+		// No child process. Only the top level process needs to be got rid of:
+		p.destroy();
+		
+	}
+	
+	/*
+	
+	// Now we can get rid of the top level process
+	p.destroy();
+	
+	
+	// Then kill each subprocess.
+	
+	if(child_pids != null) {
+		for(int i = 0; i < child_pids.length; i++) {
+			log("Attempting to terminate child pid: |" + child_pids[i] + "|");
+			SafeProcess.killWinProcessWithID(Long.parseLong(child_pids[i].trim()));
+		}
+	}
+	*/
+	
+}
+
+
+// Run `which` on a program to find out if it is available. which.exe is included in winbin.
+// On Windows, can use where or which. GLI's file/FileAssociationManager.java used which, so we stick to the same.
+// where is not part of winbin. where is a system command on windows, but only since 2003, https://ss64.com/nt/where.html
+// There is no `where` on Linux/Mac, must use which for them.
+// On windows, "which tskill" fails but "which" succeeds on taskkill|wmic|browser names.
+public static boolean isAvailable(String program) {		
+	try {
+	    // On linux `which bla` does nothing, prompt is returned; on Windows, it prints "which: no bla in"
+	    // `which grep` returns a line of output with the path to grep. On windows too, the location of the program is printed
+	    SafeProcess prcs = new SafeProcess("which " + program);		
+	    prcs.runProcess();
+	    String output = prcs.getStdOutput();
+	    if(output.equals("")) {
+		return false;
+	    }
+	    //System.err.println("*** 'which " + program + "' returned: " + output);
+	    return true;
+	} catch (Exception exc) {
+	    return false;
+	}
+}	
+	
+// Google Java external process destroy kill subprocesses
+// https://zeroturnaround.com/rebellabs/how-to-deal-with-subprocesses-in-java/	
 	
 //******************** Inner class and interface definitions ********************//
@@ -544,8 +805,9 @@
     
     public InputStreamGobbler(InputStream is, boolean split_newlines)
-    {
+    {	
 	this(); // sets thread name
 	this.is = is;
 	this.split_newlines = split_newlines;
+	
     }
     
