JavaからPowerShellを実行するとコマンド(プロセス)が終了しない場合の対応

JavaからWindows PowerShellスクリプトを実行しようとすると、Process#waitFor()がいつまで経っても終わらなくて(powershellプロセス自体も残ったまま)ハマったので、それを回避する方法についてまとめる。

以下は、JavaからC:\test.ps1というPowerShellスクリプトを実行する場合の例。

import java.io.*;

public class Example {
    public static void main(String[] args) throws Exception {
        ProcessBuilder pb = new ProcessBuilder(new String[]{"powershell", "-Command", "C:\test.ps1"});
        pb.redirectErrorStream(true);
        
        Process p = pb.start();
        p.getOutputStream().close();
        
        System.out.println("start");
        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
        try {
            String line = null;
            while((line = br.readLine()) != null) {
                System.out.println(" -> " + line);
            }
        } finally {
            try {
                br.close();
            } catch (IOException ignore) {
            }
        }
        
        int r = p.waitFor();
        System.out.println("exit: " + r);
    }    
}

ProcessBuilder#start()の後に、Process#getOutputStream()で取得するサブプロセスの出力ストリームをcloseしているのがポイント(9行目)。これが抜けているとずっと実行が終わらない現象が起こる。呼び出し位置も重要で、少なくとも出力をreadLineしているwhileループよりも前で、このcloseを呼んでいないとダメみたい。

PowerShellでないコマンド実行の場合には上記の現象が起こらないのでややこしい…。

追記

いろいろ調べると、そもそもPowerShellの不具合のようなことが書かれていた。

http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected

"-inputformat none"をつけて実行すると回避できるみたい。"-inputformat none"相当のことをJava側でなんとかしようとすると、上記のようにさっさとストリームをcloseすることにつながるのか…。ただここで設定する"none"って隠しオプションみたいなので、本当に応急処置でしかないような感じだよなぁ。