cannot get OutputStream content from sqlcmd process when running with java ProcessBuilder - java

Need to run sqlcmd to control local-connections to the database.
When i try to read the OutputStream to get the sql "prompt", the read() method simply never returns.
tried running the process with ProcessBuilder and Runtime.getRuntime().exec()
also tried running under "cmd /c"
when running sqlcmd from the command line, i get a prompt like "1>". expected to see that in the OutputStream ...
this is my code - i simply call start with "sqlcmd"
code example:
package org.example;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(start("sqlcmd"));
System.out.println(send("select * from spt_monitor\ngo\n"));
}
public static Process process = null;
public static String start(String command) throws Exception {
if(process != null && process.isAlive()) {
throw new Exception("another session already exists");
} else {
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectErrorStream(true);
process = builder.start();
return "";//response();
}
}
public static String send(String command) throws Exception {
if(process == null || !process.isAlive()) {
throw new Exception("no local session exists");
} else {
process.getOutputStream().write(command.getBytes());
process.getOutputStream().flush();
return response();
}
}
private static String response() throws IOException {
StringBuilder sb = new StringBuilder();
char[] array = new char[1024];
System.out.println("reading output...");
try (InputStreamReader isr = new InputStreamReader(process.getInputStream())) {
int len;
do {
len = isr.read(array);
if(len > 0)
sb.append(String.valueOf(array));
} while (len > 0);
}
return sb.toString();
}
}

This hopefully will give a clearer explanation as to why your code does not finish as you expect. You have started sub-process sqlcmd, and sqlcmd outputs a prompt, receives your command(s), processes each command and outputs another prompt.
The sqlcmd process is waiting for more commands but you have not sent more after send(). At this point though your response() call is stuck in a blocking loop trying to read the character AFTER the last prompt. This means that response() will never exit normally - even though it has processed some of the commands and appended to a StringBuilder.
To see this more clearly, fix a small bug in response() with incorrect size of the array in String.valueOf() and add a System.out to tell you what has been read so far by response():
private static String response() throws IOException {
StringBuilder sb = new StringBuilder();
char[] array = new char[1024];
System.out.println("reading output...");
try (InputStreamReader isr = new InputStreamReader(process.getInputStream())) {
int len;
do {
len = isr.read(array);
if(len > 0) {
String r = String.valueOf(array, 0, len);
System.out.println("response() has read a value: "+r);
sb.append(r);
}
} while (len > 0);
}
return sb.toString();
}
Re-run and you should see that isr.read(array) HAS worked and should print out the results of select * from spt_monitor but will block on next or following isr.read(array) call when the prompt has been printed - and response() never exits because no more commands are received to process.
There is no more STDOUT to process unless you send sqlcmd an exit command OR terminate the STDIN which causes the sqlcmd process to end - both of which would mean that response() could exit normally.
You should be able to make response() work by terminating the STDIN by adding process.getOutputStream().close() at the end of send() or after calling send(). However that means that sqlcmd will exit.

Related

Restrict Powershell from printing input commands to output stream

I am using Java ProcessStream to write input commands to powershell on my local machine. Now, the problem is that along with the output, I am seeing input commands as well. How can I restrict input command from being shown on the output.
Below is the code to reproduce the same:
public class Example {
public static boolean isAlive(Process o) {
try {
p.exitValue();
return false;
} catch (Exception e) {return true;}
}
public stati void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("powershell");
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream out = process.getInputStream();
OutputStream in = process.getOutputStream();
String bufferString = "echo hello \n";
byte[] buffer = bufferString.getBytes();
int n = buffer.length;
in.write(buffer, 0, n);
in.flush();
while(isAlive(process)) {
int no = out.available();
if (no >0) {
int m = out.read(buffer, 0, Math.min(no, buffer.length));
System.out.println(new String(buffer, 0, m));
}
}
}
}
OUTPUT:
PS C://> echo hello
hello
I need only "hello" in output.
The following works for me (but the java program never terminates and I have to kill it).
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class Example {
public static boolean isAlive(Process p) {
try {
p.exitValue();
return false;
}
catch (Exception e) {
return true;
}
}
public static void main(String[] args) throws IOException {
ProcessBuilder builder = new ProcessBuilder("powershell", "-NoLogo");
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream is = process.getInputStream();
try (InputStreamReader isr = new InputStreamReader(is);
BufferedReader out = new BufferedReader(isr)) {
OutputStream in = process.getOutputStream();
String bufferString = "echo hello" + System.lineSeparator();
byte[] buffer = bufferString.getBytes();
in.write(buffer);
in.flush();
while (isAlive(process)) {
String line = out.readLine();
if (!line.startsWith("PS")) {
System.out.println(line);
}
}
}
}
}
The output produced by the above code is simply:
hello
#talex mentioned the following in his comment to your question:
it is impossible to get rid of PS C://> part
This is not true since you can customize the PowerShell prompt but you wrote that you don't want to see the echo hello in the output and that is not possible and therefore, as #talex mentioned:
you have to filter your input stream yourself
Not displaying echo hello means not displaying the entered command. Imagine that you open a PowerShell window and don't see the command you entered but after you hit ENTER you want to see the output. So I don't think that there is a way to not display echo hello and therefore, in the above code, I don't print lines that start with the PowerShell prompt.
You can find details about launching PowerShell in about_PowerShell_exe

Reading output from command line

I'm really stuck here, I've read number of articles and answers on stackoverflow, but nothing solved my problem.
I've got the method which runs Selenium Server Hub from cmd.exe using batch file:
public static boolean startGrid() throws IOException {
ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "start", START_GRID_BAT_PATH);
builder.redirectErrorStream(true);
Process process = builder.start();
String out = getCmdOutput(process);
return out.contains("Selenium Grid hub is up and running");
}
The server is started successfully and is running in open cmd.
To make sure that server is up I use the following method to get the output from the cmd:
protected static String getCmdOutput(Process proc) throws java.io.IOException {
String res = "";
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(proc.getInputStream()));
String s = "";
while ((s = stdInput.readLine()) != null) {
res += s;
}
return res;
}
And here is where the problem starts - method hangs at the the line s = stdInput.readLine()) != null
It seems that it can't read any line from the cmd, although I can see that there is number of lines in the output produced by running server.
Any ideas what is wrong?
Maybe the output produced by the server doesn't contain any \r or \n characters but is simply line-wrapped.
Anyhow, you shouldn't rely on the line feed characters when reading the output.
The following example shows how to read binary data from the InputStream of the process non-blocking with java.nio, convert the bytes to chars and write the result to a StringWriter - which is preferrable to using string concatenation.
protected static String getCmdOutput(Process proc, Consumer<String> consumer)
final InputStream source = proc.getInputStream();
final StringWriter sink = new StringWriter();
final ByteBuffer buf = ByteBuffer.allocate(1024);
final CharBuffer chars = CharBuffer.allocate(1024);
final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
try(final ReadableByteChannel from = Channels.newChannel(source)) {
while (from.read(buf) != -1) {
buf.flip();
decoder.decode(buf, chars, false);
chars.flip();
sink.write(chars.array(), chars.position(), chars.remaining());
buf.compact();
chars.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
return sink.toString();
}
Edit:
As you are starting a process that is continuously producing output, the loop won't finish as long as the process is running. So to continue your parent process, you have to start a thread that is capturing and processing the output of the process continuously
For example
Consumer<String> consumer = ...;
Thread t = new Thread(() -> {
final InputStream source = proc.getInputStream();
final StringWriter sink = new StringWriter();
final ByteBuffer buf = ByteBuffer.allocate(1024);
final CharBuffer chars = CharBuffer.allocate(1024);
final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
try(final ReadableByteChannel from = Channels.newChannel(source)) {
while (from.read(buf) != -1) {
buf.flip();
decoder.decode(buf, chars, false);
chars.flip();
sink.write(chars.array(), chars.position(), chars.remaining());
forward(sink, consumer); //forward the captured content to a consumer
buf.compact();
chars.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
});
t.start();
private void forward(StringWriter sink, Consumer<String> consumer) {
StringBuffer buf = sink.getBuffer();
int pos = buf.lastIndexOf("\n");
//you may use other options for triggering the process here,
//i.e. on every invocation, on fixed length, etc
if(pos != -1){
consumer.accept(buf.substring(0, pos + 1));
buf.delete(0, pos + 1);
}
//or to send the entire buffer
/*
consumer.accept(sink.toString());
sink.getBuffer().delete(0, sink.getBuffer().length());
*/
}
Depending on your use case, you may not necessarily need a separate thread. It might be ok to process the child-process' output in the main thread and do what you want with the output in the consumer.
It doesn't hang, it just blocks waiting for a line to be read. And after the line has been read, it waits for the next. And so on.
Getting the output stream of a child process should be made in a separate thread, because the child process could in fact hang, than your main thread will hang, too.
An example:
given the instance methods
private static final long WAITTIME_FOR_CHILD = Duration.ofMinutes(5).toNanos();
private static final String START_SEQUENCE = "whatever";
private final Lock lock = new ReentrantLock();
private final Condition waitForStart = lock.newCondition();
private boolean started;
the code that starts the child process
// prepare child process
lock.lock();
try {
Process process = processBuilder.start();
// both standard and error output streams get redirected
pipe(process.getInputStream(), System.out, START_SEQUENCE);
pipe(process.getErrorStream(), System.err, START_SEQUENCE);
// loop because of 'spurious wakeups' - should happen only on Linux,
// but better take care of it
// wait until WAITTIME_FOR_CHILD (default 5 minutes, see above)
long waitTime = WAITTIME_FOR_CHILD;
while (!started) {
// we wait hier until the child process starts or timeout happens
// value <= 0 means timeout
waitTime = waitForStart.awaitNanos(waitTime);
if (waitTime <= 0) {
process.destroyForcibly();
throw new IOException("Prozess xxx couldn't be started");
}
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Error during start of xxx", e);
} finally {
lock.unlock();
}
private void getCmdOutput(InputStream in, PrintStream out, final String startSeq) {
startDaemon(() -> {
try (Scanner sc = new Scanner(in)) {
while (sc.hasNextLine()) {
String line = sc.nextLine();
// check your know output sequence
if (line.contains(startSeq)) {
lock.lock();
try {
if (!started) {
started = true;
// waiting for process start finished
waitForStart.signal();
}
} finally {
lock.unlock();
}
}
out.println(line);
}
}
});
}
private void startDaemon(Runnable task) {
Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
}
Try replacing while condition like this:
while ((s = stdInput.readLine()) != null && !s.equals("")) {
ProcessBuilder pb =
new ProcessBuilder("myCommand", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory(new File("myDir"));
File log = new File("log");
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(log));
Process p = pb.start();
assert pb.redirectInput() == Redirect.PIPE;
assert pb.redirectOutput().file() == log;
assert p.getInputStream().read() == -1;
You can redirect output to a file as oracle documentation offers in basic example:
https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html

Passing windows credentials programmatically

I have a VB script to which I need to pass username and password.
I want to run this VB script through Java code programmatically.
Is there a way that I can pass the Windows credentials to the VB script in Java programmatically?
You can have the credentials on the OS environment and read them from there:
String credentials = System.getenv().get("encrypted_credentials_or_something");
And then run your command from Java. However, Runtime.exec() won't work in some cases:
When the command is not on the System's PATH
When arguments are involved
When you want to have access to the process output
When you need to be able to kill the process
When you need to check if it terminated successfully or in error (status code != 0 - which is why you write System.exit(int) to terminate a Java application. The System.exit(1), for example, indicates abnormal termination)
That's why I created this utility class to execute external processes with arguments and everything. It works very well for me:
import java.io.*;
import java.util.*;
public class ExternalCommandHelper {
public static final void executeProcess(File directory, String command) throws Exception {
InputStreamReader in = null;
try {
//creates a ProcessBuilder with the command and its arguments
ProcessBuilder builder = new ProcessBuilder(extractCommandWithArguments(command));
//errors will be printed to the standard output
builder.redirectErrorStream(true);
//directory from where the command will be executed
builder.directory(directory);
//starts the process
Process pid = builder.start();
//gets the process output so you can print it if you want
in = new InputStreamReader(pid.getInputStream());
//simply prints the output while the process is being executed
BufferedReader reader = new BufferedReader(in);
String line = null;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int status = 0;
//waits for the process to finish. Expects status 0 no error. Throws exception if the status code is anything but 0.
if ((status = pid.waitFor()) != 0) {
throw new IllegalStateException("Error executing " + command + " in " + directory.getAbsolutePath() + ". Error code: " + status);
}
} finally {
if (in != null) {
in.close();
}
}
}
//Splits the command and arguments. A bit more reliable than using String.split()
private static String[] extractCommandWithArguments(String command) {
StringTokenizer st = new StringTokenizer(command);
String[] cmdWithArgs = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++) {
cmdWithArgs[i] = st.nextToken();
}
return cmdWithArgs;
}
}

How to execute sh file without wait result?

I have a problem, I tried run a sh file from java code to start a JBoss Server, so when I exec this sh file I want to know when it's started and print out my console. Here is my code:
public static void runStart() {
try {
String command = "./Run.sh";
String s = get_commandline_results(command);
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("done");
}
public static String get_commandline_results(String cmd)
throws IOException, InterruptedException, IllegalCommandException {
String result = "";
Process p = null;
p = Runtime.getRuntime().exec(String.format("bash -c %s", cmd));
final ProcessResultReader stderr = new ProcessResultReader(
p.getErrorStream(), "STDERR");
final ProcessResultReader stdout = new ProcessResultReader(
p.getInputStream(), "STDOUT");
System.out.println("starting...");
stderr.start();
stdout.start();
final int exitValue = p.waitFor();// It was delayed here because sh not complete
System.out.println("started!");// How to go to here?
if (exitValue == 0) {
result = stdout.toString();
} else {
result = stderr.toString();
}
return result;
}
}
class ProcessResultReader extends Thread {
final InputStream is;
final String type;
final StringBuilder sb;
ProcessResultReader(final InputStream is, String type) {
this.is = is;
this.type = type;
this.sb = new StringBuilder();
}
public void run() {
try {
final InputStreamReader isr = new InputStreamReader(is);
final BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null) {
this.sb.append(line).append("\n");
}
} catch (final IOException ioe) {
System.err.println(ioe.getMessage());
throw new RuntimeException(ioe);
}
}
#Override
public String toString() {
return this.sb.toString();
}
}
Thank for your help!
You have contradicting goals here: You want to continue without waiting for the script to terminate (i.e. you want JBoss running) but at the same time, you want to know the result. Short of building a time machine, this is impossible.
What you can do is you can start the script within another script start.sh:
#!/bin/bash
nohup base -c ./Run.sh > /dev/null &
Note: You can omit bash -c by making Run.sh executable.
This script will start Run.sh as a background process and exit immediately. But if JBoss can't start, you won't get an error since that will happen later.
One solution for this dilemma is a script that reads the log files of JBoss for a couple of seconds or until it sees a line of text that means "JBoss has started successfully".
To do that, use the start script and then in the Java code, start reading the log file of JBoss (you may need to wait for it to show up, first).
For this kind of trick, it's always good when you delete the old log files first, before you try to start JBoss. Otherwise, the Java program might read an old log file and continue while JBoss is still trying to start writing a new log.

runtime.exec sending EOF immediately to input?

This is my code to start a process in Windows via java (and gobble the output).
public static void main(String[] args) throws Exception {
String[] command = new String[3];
command[0] = "cmd";
command[1] = "/C";
command[2] = "test.exe";
final Process child = Runtime.getRuntime().exec(command);
new StreamGobbler(child.getInputStream(), "out").start();
new StreamGobbler(child.getErrorStream(), "err").start();
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(
child.getOutputStream()));
out.write("exit\r\n");
out.flush();
child.waitFor();
}
private static class StreamGobbler extends Thread {
private final InputStream inputStream;
private final String name;
public StreamGobbler(InputStream inputStream, String name) {
this.inputStream = inputStream;
this.name = name;
}
public void run() {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(
inputStream));
for (String s = in.readLine(); s != null; s = in.readLine()) {
System.out.println(name + ": " + s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Somehow the program in question (process) is recieving an EOF right away (as in right after I step pas the "exec" line) and thus throwing an error ( detected, invalid) message immediately after runtime.exec is called. I can run this program manually via command prompt without this issue, but have confirmed that sending a ctrl-z on windows is what causes this message.
Anyone know what could be causing this?
If it matters, I have tried running the process directly as "test.exe" instead of cmd /c test.exe, but when I do that I can't see the output via the inputStream. And when I do cmd test.exe without the /c, there is no difference.
Your code looks like it should work (with one caveat, see below).
I took your code verbatim and replaced test.ext with sort, which can read from piped stdin.
If I run the code as-is, it starts the sort command, which waits for input. It hangs at child.waitFor() because you don't close the output stream to indicate EOF. When I add the close() call, everything works correctly.
I suggest you look at test.exe and determine if it is capable of reading from piped stdin, or is expecting console input.
Get rid of "cmd" and "/c". At present you are feeding output to cmd.exe, not to test.exe.

Categories