We experienced the same after a Java or Windows7 fixpack update. I don’t remember which.
The apparent behavior is that a DOS command executed from Java does not properly terminate to Java, if it has started another process.
My solution to the problem is the (Groovy) class below. I hope you can use it.
Cheers, Jesper
package dk.jyskebank.tools.system.impl
import java.util.regex.Matcher
import java.io.File;
import java.util.regex.Pattern
import dk.jyskebank.tools.system.ExecResult;
/**
* Runs script on Windows 7 where CMD does not appear to return until all child processes have completed.
*
* For WAS6 startServer.bat this means that the script will launch the server, and then hang.
* This code works around this by looking for a special marker in the output, which is used to
* communicate exit value - and the order to kill the process.
*/
class Win7ScriptRunner {
private static final int PROCESS_OK = 0
private static final String TERMINATION_MARKER = "#### WIN7 PROCESS EXIT %ERRORLEVEL% ####"
private static final Pattern TERMINATION_MARKER_PATTERN = Pattern.compile("(?s)#### WIN7 PROCESS EXIT (-?\d+) ####.*")
private boolean processSucceeded = true
private int exitValue = 0
private boolean terminatedByMarker = false
private boolean echoOutput = false
/**
* Executes DOS script with magic marker exit handling.
* Note that stdout and stderr output is merged.
*
* @param processCurrentDirectory directory to run process in.
* @param args command arguments.
* @param messageOnError message to print if script failed.
*
* @return execution result.
*/
public static ExecResult executeShellCommandAbortOnError(File processCurrentDirectory, List<String> args, String messageOnError) {
return new Win7ScriptRunner().executeScriptAbortOnError(processCurrentDirectory, args, messageOnError)
}
/**
* Executes DOS script with magic marker exit handling.
*
* @param processCurrentDirectory directory to run process in.
* @param args command arguments.
* @param messageOnError message to print if script failed.
*
* @return integer result from script
*/
public static int executeShellCommandIntResult(File processCurrentDirectory, List<String> args, String messageOnError) {
return new Win7ScriptRunner().executeScriptIntResult(processCurrentDirectory, args, messageOnError)
}
private int executeScriptIntResult(File processCurrentDirectory, List<String> args, String messageOnError) {
echoOutput = true
executeScriptAbortOnError(processCurrentDirectory, args, messageOnError)
return exitValue
}
public ExecResult executeScriptAbortOnError(File processCurrentDirectory, List<String> args, String messageOnError) {
List<String> cmdWrapped = getWindowsShellExecutionPrefix() + args + [ "&", "echo ${TERMINATION_MARKER}"]
File wrapperScript = makeWrapperScript(args)
Process proc = new ProcessBuilder(wrapperScript.getAbsolutePath()).directory(processCurrentDirectory).redirectErrorStream(true).start()
InputStreamReader isr = new InputStreamReader(new BufferedInputStream(proc.getInputStream()))
String output = consumeProcessOutputLookingForTermintationMarker(isr)
if (terminatedByMarker) {
proc.destroy()
} else {
recordExitValue(proc.exitValue())
}
wrapperScript.delete()
return new ExecResult(processSucceeded, output, "", cmdWrapped)
}
private File makeWrapperScript(List<String> args) {
File wrapper = File.createTempFile("win7.wrapper.", ".bat")
wrapper << """@echo off
@call ${args.join(' ')}
@echo ${TERMINATION_MARKER}
"""
return wrapper
}
private String consumeProcessOutputLookingForTermintationMarker(Reader reader) {
StringBuilder sb = new StringBuilder()
char[] buffer = new char[1024]
String builtString = ""
while (true) {
int readCount = reader.read(buffer, 0, buffer.length)
if (readCount == -1){
return builtString
}
sb.append(buffer, 0, readCount)
builtString = sb.toString()
String tailString = getTailLinePreservingNewline(builtString)
Matcher m = TERMINATION_MARKER_PATTERN.matcher(tailString)
if (m.matches()) {
recordExitValue(Integer.parseInt(m[0][1]))
terminatedByMarker = true
return builtString.replace(tailString, "")
}
if (echoOutput) {
System.out.print(new String(buffer, 0, readCount))
}
}
return sb.toString()
}
private void recordExitValue(int exitValue) {
this.exitValue = exitValue
processSucceeded = (exitValue == PROCESS_OK)
}
private String getTailLinePreservingNewline(String str) {
String tailString = str.split("\n").last()
if (str.endsWith("\n")) {
tailString += "\n"
}
return tailString
}
private getWindowsShellExecutionPrefix() {
return [ System.getenv('ComSpec'), "/c" ]
}
public static final void main(String[] args) {
int res = executeShellCommandIntResult(new File(System.getProperty("user.dir")), args as List, "Failed to run script: ${args}")
System.exit(res)
}
}
package dk.jyskebank.tools.system
/**
* Represents execution result.
* The boolean value of the object itself reflects the execution success/failure.
*/
class ExecResult {
final boolean executionResult
final String stdOutput
final String errOutput
final List<String> command
ExecResult(boolean executionResult, String stdOutput, String errOutput, List<String> command) {
this.executionResult = executionResult
this.stdOutput = stdOutput
this.errOutput = errOutput
this.command = command
}
boolean asBoolean() {
return executionResult
}
}