javawindowsbatch-fileprocessbuilderpoc

Executing a Batch Script with ProcessBuilder


I'm trying to execute a Batch Script with ProcessBuilder and can't figure out why it's not working.

I built a small PoC to show my problem, in order for this to work you would need to create some folders on C Drive:

22840c1a

22840c1a\subfolder

Then copy your calc.exe into the subfolder. Next create start.bat inside of 22840c1a. Paste the following content into start.bat

@echo off
echo "Set WorkingDirectory"
cd /d C:\22840c1a\subfolder
echo "Start"
C:\22840c1a\subfolder\calc.exe

With that you're able to run the following unit test and reproduce the problem:

Update: Added working PoC

import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

class BatchExecutionTest {
    @Test
    void pocWorking() {
        execute(Arrays.asList("C:\\22840c1a\\start.bat"), "C:\\22840c1a");
    }
    @Test
    void pocNotWorking() {
        execute(Arrays.asList("start.bat"), "C:\\22840c1a");
    }
    @Test
    void pocWorkingCmd1() {
        execute(Arrays.asList("cmd", "/c", "C:\\22840c1a\\start.bat"), "C:\\22840c1a");
    }
    @Test
    void pocWorkingCmd2() {
        execute(Arrays.asList("cmd", "/c", "start.bat"), "C:\\22840c1a");
    }

    public static void execute(List<String> commands, String workingDirectory) {
        try {
            final ProcessBuilder pb = new ProcessBuilder(commands);
            pb.redirectErrorStream(true);
            pb.directory(new File(workingDirectory));
            pb.start();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }
}

If everything works as expected the Unit Test is not able to execute start.bat because the File could not be found. However you can run start.bat from cmd and its working fine. Why is ProcessBuilder not able to execute the Batch Script?


Solution

  • The ProcessBuilder command line launch using a relative path as you've used in pocNotWorking() isn't implemented as you think.

    ProcessBuilder is applying a normal search using Path of your JVM. It does not - rightly or wrongly - consider checking in proposed subprocess workingDirectory to resolve a relative pathname to the command.

    You can verify this by appending C:\22840c1a to the path BEFORE launching your JVM for the unit tests, and then I would expect all 4 tests to work:

    set Path=%Path%;C:\22840c1a
    

    I also tested pocWorking/pocNotWorking on Linux with a shell script start.sh and it gives consistent results to Windows when calling pocWorking/pocNotWorking. The relative path version won't run unless you adjust PATH beforehand so that start.sh is resolved in PATH rather than the working directory:

    export PATH=$PATH:/somepathto/22840c1a
    

    Note that you cannot fix this by attempting to edit path for the subprocess via pb.environment().set("Path", xxx), the path edit must be to the JVM parent process.

    Overall, it seems good practice to use absolute path to ProcessBuilder or resolve command as workingDirectory + File.separator + relativeExeName