Skip to Main Content

Java Programming

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Interested in getting your voice heard by members of the Developer Marketing team at Oracle? Check out this post for AppDev or this post for AI focus group information.

Interacting with Powershell from Java

909372Jan 6 2012 — edited Jan 10 2012
Hi,

I'm trying to run a Java application which creates a new powershell process on startup and then later on interacts with it multiple times. Calling powershell.exe and have it execute a single command and return the output works fine for me. The problem arises if I don't want the powershell process to immediately finish/exit but to stay open so I can write to its outputStream and receive results back from the inputStream.

---------------------
String input = "dir";

String[] commandList = {"powershell.exe", "-Command", "dir"};

ProcessBuilder pb = new ProcessBuilder(commandList);

Process p = pb.start();

if(input != null) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true);
writer.println(input);
writer.flush();
writer.close();

}

//p.getOutputStream().close();

Gobbler outGobbler = new Gobbler(p.getInputStream());
Gobbler errGobbler = new Gobbler(p.getErrorStream());
Thread outThread = new Thread(outGobbler);
Thread errThread = new Thread(errGobbler);
outThread.start();
errThread.start();

System.out.println("Waiting for the Gobbler threads to join...");

outThread.join();
errThread.join();

System.out.println("Waiting for the process to exit...");

int exitVal = p.waitFor();
System.out.println("\n****************************");
System.out.println("Command: " + "cmd.exe /c dir");
System.out.println("Exit Value = " + exitVal);
List<String> output = outGobbler.getOuput();
input = "";
for(String o: output) {
input += o;
}

System.out.println("Final Output:");
System.out.println(input);
----------------------

This code returns the result of the "dir" command from a powershell - fine. But as you can see, I'm trying to run a second "dir" command using
----------------------------
PrintWriter writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true);
writer.println(input);
writer.flush();
---------------------
This has no effect whatsoever - no second dir output is shown when I run my code. I've also experimented with a powershell.exe option to open the powershell but not close it immediately:
String[] commandList = {"powershell.exe", "-NoExit", "-Command", "dir"};

But then my code hangs, meaning the Gobbler's who consume the process's inputStream don't read anything - strangely enough: they don't even read the first line - there must be at least some output....

I've also tried to close the process's outputStream after writing the second "dir" command to it - didn't change anything.

But when I initially call the cmd.exe using the /k (keep open) switch:
String[] commandList = {"cmd.exe", "/k", "dir"};

I can then still write to that outputstream and invoke the second "dir" command and get the output of both "dir" commands from the inputstream fo that process.

Any help is highly appreciated.
Thanks
Kurt
This post has been answered by kordirko on Jan 8 2012
Jump to Answer

Comments

sabre150
I misunderstood the problem.
452196
Try :

Start gobblers before writing anthing.

User one Writer stack etc. for all commands, and don't close it until all commands are written (closing the writer will close the output stream and, hence, tell the PowerShell that you've finished with it).
909372
@sabre150: The challenge is to start a powershell from Java when my application starts and then keep that powershell open as long as my application runs (it's a server so it's supposed to always run). Then, when the client calls the server for some data, the server should write some commands to that existing powershell process and read the corresponding results and push them back to the client (also in Java).

Calling a powershell.exe process once, initially passing a long some commands and retrieving the result works fine. But when I want to send additional commands to that powershell and read the output it won't work.
909372
@malcolmmc: Not sure I've understood your advice correctly. What I've tested so far:
1. I've put the Gobblers (consumers) higher up in the code as you can see at the bottom of this reply -> result: no improvement
2. I've used the ProcessBuilder to create this process and redirected the erroStream to the inputStream - so I only had to use one Gobbler process for both streams -> result: no improvement
3. I did not close the bufferedWriter in my code -> result: no improvement

It seems that my code only works half the way when I close the outputStream - if I don't do that, I assume, the powershell process is blocking/waiting for more commands from that stream before processing and finally providing the results on the inpuStream. But still, if I close the outputStream I only get the result from the inital "dir" command (sent along to the process at the time it is initiated) - any additional commands written to the process's input stream (=outputStream) doesn't seem to be read and processed by the powershell process.

Any other ideas?

--------------------------

String[] cmds = {"powershell.exe", "dir"};
Runtime runtime = Runtime.getRuntime();
Process p = runtime.exec(cmds);

Gobbler outGobbler = new Gobbler(p.getInputStream());
Gobbler errGobbler = new Gobbler(p.getErrorStream());
Thread outThread = new Thread(outGobbler);
Thread errThread = new Thread(errGobbler);
outThread.start();
errThread.start();

String input = "dir";
if(input != null) {
PrintWriter writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true);
writer.println(input);
writer.flush();
//writer.close();

}

//p.getOutputStream().close();
kordirko
Have you tried to start powershell with arguments: -NoExit -Command - ?

Type powershell -help and read what it says:

>
<pre>-NoExit
Does not exit after running startup commands.
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, <font color="red">and then exits, unless
NoExit is specified..</font> The value of Command can be "-", a string. or a
script block.
<font color="red"> If the value of Command is "-", the command text is read from standard
input.</font>
</pre>
909372
yes, I have read the info.

When I perform a runtime.exec with "-NoExit" it does what it says: powershell is never exiting and thus my code is hanging. From what I've research so far, I guess, the powershell waits for the outputStream (its inputstream) to be closed until it performs the requested operations. But I can't close this stream since I want to further interact with powershell and, once closed, the stream can never be opened again.

When I use the "-Command" option and add some command afterwards (like "dir") and I close the outputStream everything works fine meaning my code doesn't hang, the powershell process will be closed and I see the result from the "dir" command - but that doesn't help :(

thanks again
Kurt
909372
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, <font color="red">and then exits, unless
NoExit is specified..</font> The value of Command can be "-", a string. or a
script block.
<font color="red"> If the value of Command is "-", the command text is read from standard
input.</font>
</pre>>
Let me be more specific on the second suggestion:
This was actually a really good hint (thanks!) - using "powershell.exe", "-Command", "-" as the process's start parameters actually gives me the possiblity to write to the process's inputStream again and again as often as I want - nice!
BUT: my Gobblers only read the output of all my commands when I perform a "p.getOutputStream().close();" - but once I do this, I can never re-open the outputStream, correct!? Or maybe there is another way of reading the inputStream - I use a bufferedReader which seems to only start reading when the outputStream closese...not sure why.
kordirko
Answer
user4491593 wrote:
BUT: my Gobblers only read the output of all my commands
Then why don't change your Gobbler code ? ;)
Test this, it's ugly and needs improvemens, but by now works fine on linux and windows:
public class Gobbler implements Runnable {

    private PrintStream out;
    private String message;

    private BufferedReader reader;

    public Gobbler(InputStream inputStream, PrintStream out) {
        this.reader = new BufferedReader(new InputStreamReader(inputStream));
               this.out = out;
        this.message = ( null != message ) ? message : "";
    }

    public void run() {
        String line;

        try {
            while (null != (line = this.reader.readLine())) {
                out.println(message + line);
            }
            this.reader.close();
        } catch (IOException e) {
            System.err.println("ERROR: " + e.getMessage());
        }
    }
}


public class PowerConsole {

    private ProcessBuilder pb;
    Process p;
    boolean closed = false;
    PrintWriter writer;

    PowerConsole(String[] commandList) {
        pb = new ProcessBuilder(commandList);
        try {
            p = pb.start();
        } catch (IOException ex) {
            throw new RuntimeException("Cannot execute PowerShell.exe", ex);
        }
        writer = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(p.getOutputStream())), true);
        Gobbler outGobbler = new Gobbler(p.getInputStream(), System.out);
        Gobbler errGobbler = new Gobbler(p.getErrorStream(), System.out);
        Thread outThread = new Thread(outGobbler);
        Thread errThread = new Thread(errGobbler);
        outThread.start();
        errThread.start();
    }

    public void execute(String command) {
        if (!closed) {
            writer.println(command);
            writer.flush();
        } else {
            throw new IllegalStateException("Power console has ben closed.");
        }
    }

    public void close() {
        try {
            execute("exit");
            p.waitFor();
        } catch (InterruptedException ex) {
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        /*   PowerConsole pc = new PowerConsole(new String[]{"/bin/bash"});
        
        PowerConsole pc = new PowerConsole(new String[]{"/bin/bash"});
        pc.execute("pwd");
        pc.execute("ls");
        pc.execute("cd /");
        pc.execute("ls -l");
        pc.execute("cd ~");
        pc.execute("find . -name 'test.*' -print");
        pc.close();
         */
        //      PowerConsole pc = new PowerConsole(new String[]{"cmd.exe"});
        PowerConsole pc = new PowerConsole(new String[]{"powershell.exe", "-NoExit", "-Command", "-"});
        System.out.println("========== Executing dir");
        pc.execute("dir"); 
        System.out.println("========== Executing cd\\");
        pc.execute("cd \\"); Thread.sleep(2000);
        System.out.println("========== Executing dir");
        pc.execute("dir"); Thread.sleep(2000);
        System.out.println("========== Executing cd \\temp");
        pc.execute("cd \\temp"); Thread.sleep(2000);
        System.out.println("========== Executing dir");
        pc.execute("dir"); Thread.sleep(2000);
        System.out.println("========== Executing cd \\bubba");
        pc.execute("cd \\bubba"); Thread.sleep(2000);
        System.out.println("========== Exiting .... bye.");
        pc.close();
    }
}
I tested this and there is still a little problem -look at the test below.
It seems that when thecommand
executed in the powershell prints only a one ot two lines,
powershell doesn't flush the output stream
.... but this rather problem of powershell, not the java code
I have not a clue how to force powershell to flush
it's output stream after each command.
C:\temp>java -jar PowerShell.jar
========== Executing dir


    Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2012-01-09     01:16       5290 PowerShell.jar


========== Executing cd\
========== Executing dir


    Directory: Microsoft.PowerShell.Core\FileSystem::C:\


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        2012-01-08     02:56            61587b977687a6e22fbe
d----        2011-12-14     03:19            Documents and Settings
d----        2011-12-15     00:05            oraclexe
d-r--        2012-01-08     03:44            Program Files
d----        2012-01-05     19:59            sqldeveloper
d----        2012-01-09     01:15            temp
d----        2012-01-09     01:13            WINDOWS
-a---        2011-12-14     03:12          0 AUTOEXEC.BAT
-a---        2011-12-14     03:12          0 CONFIG.SYS


========== Executing cd \temp
========== Executing dir


    Directory: Microsoft.PowerShell.Core\FileSystem::C:\temp


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2012-01-09     01:16       5290 PowerShell.jar


========== Executing cd \bubba
Set-Location : Cannot find path 'C:\bubba' because it does not exist.
At line:1 char:3
+ cd  <<<< \bubba
========== Exiting .... bye.

C:\temp>
Marked as Answer by 909372 · Sep 27 2020
909372
Hi kordirko,

that's it! you've found the solution - THANKS a lot!

I incorporated this into my code and at least when running this on a Windows server 2008 R2 the Powershell seems to flush all its output and the Gobblers read it - I haven't done full testing yet but I will close this thread for the moment as my problem seems solved.

thanks again, really great support!
Kurt
1 - 9
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Feb 7 2012
Added on Jan 6 2012
9 comments
12,246 views