- Rationale
- Command Line Escaping
- Program Resolution
- A Simpler World: macOS & Linux
- Windows
- Observations & Notes
- How does
a
resolve from cwd? - How does
a.<ext>
resolve from cwd? - How does
a.<ext>
resolve from cwd when also on PATH? - How does
a
resolve from cwd when also on PATH? - How does
.\a
resolve from cwd? - How does
.\a.<ext>
resolve from cwd? - We expect absolute path of
a.<ext>
to work fine, do they? - How does
a
with workdir resolve from cwd, workdir,PATH
? - How does
a.<ext>
with workdir resolve from cwd, workdir,PATH
? - How does
.\a
resolve from cwd, workdir? - How does
.\a.<ext>
with workdir resolve from cwd, workdir? - We expect absolute path of
a.<ext>
with workdir to work fine, do they?
- Running Tests
- Manual Tests
As a Clojure developer I use Java’s ProcessBuilder to spawn scripts and command line applications. I’d like to better understand how it behaves.
Windows command line escaping is what initially confused me (and still does), more recently I’ve become interested in program resolution.
Initial notes, have barely started to understand. May return to study this more someday.
The command line escaping curiosity is borne from a Babashka script I was writing for rewrite-cljc and discussions continued with @borkdude’s babashka/process library. In my rewrite-cljc Babashka script, I wanted to pass the following clj-kondo config on the command line:
{:output {:include-files ["^src" "^test" "^script"]}}
After a few attempts to make this work on Windows, I gave up and moved the config from the command line to a file.
For Windows developers who have embraced WSL2, maybe all these Windows escaping rules less of a concern? I am still curious.
I find I am often confused by the command escaping rules when for Java’s ProcessBuilder on the Windows platform. Although it is Windows that has me confused, I’ll also explore command escaping rules when invoking bash scripts and native executables on Linux and macOS.
There are likely different/better ways to what I’ve discovered here. I’ll update this work as I learn more.
I’ve created little executables that echo the args they have received.
Example output of such a script called with args one
two
three
:
argc: 3
arg1: one
arg2: two
arg3: three
A variable in the equation is how these args get displayed.
Does the method of display affect the output?
I think, but am not yet entirely confident, that I’ve got the right incantation to tell .bat
and .cmd
files not to interpret on echo
, but a side effect is that args will be display "with double quotes"
For Windows I explore:
-
.ps1
- Powershell -
.bat
- Batch file -
.cmd
- Cmd file -
.exe
- Native exe
Note that batch and cmd script variants are identical and expected to behave the same. I’m including them both to verify that assumption is true.
For Linux and macOS:
-
.sh
- Bash script -
Native exe
A ptest.clj
Clojure program launches these scripts with various args and is where I experiment with escaping rules.
It might be more prudent, as I move forward, to make this a unit test.
Windows command line parsing, it turns out, is complex. Here are some references to work through those complexities:
There have been many Java bugs filed against Windows behavior, here are a couple:
The Java team seem have concluded that:
Unfortunately, the Windows API maps most directly to an interface where Runtime.exec(String) passes the arg string directly to the OS, but that method is specified to break up its arguments using StringTokenizer, which is surprisingly rarely what you want.
It’s very hard to make changes in this area, because - they change the behavior, and so are incompatible - the current behavior (how to pass strings to the OS) is unspecified, and so is spec-compliant.
If there’s any way at all that the current behavior can be relied upon, then we really can’t change it.
Which I’ll paraphrase to: we can’t generalize to a gong-show. I sympathize and empathize with the Java developers who had to contend with this. But I do wonder if modern Microsoft has stabilized here.
From OpenJDK source, we have:
For command line escaping we wonder what is happening with the following players:
-
Java
-
The Windows shell, if involved
-
Windows createProcessW
-
Windows OS
That’s a lot of wondering.
In the following sections I’ll use:
-
Command shell - to mean the shell invoked by
cmd.exe
-
PowerShell - to mean the shell invoked by
powershell.exe
Observe the following behavior under the Command shell:
>echo "Ar"g"um"e"n"t" W"it"h Sp"aces""
"Ar"g"um"e"n"t" W"it"h Sp"aces""
>exe-test.exe "Ar"g"um"e"n"t" W"it"h Sp"aces""
argc: 1
arg1: Argument With Spaces
This can seem baffling to the uninitiated.
-
On Windows, ProcessBuilder will invoke
.bat.
,.cmd
and.exe
files directly, but a Powershell.ps1
script needs to be explicitly invoked viapowershell
executable.
-
What’s with the caret? Some docs say the OS deals with this escape character. True?
-
The escape rules for
.bat
and.cmd
are on the complex side. I can’t say I understand how to properly escape for these targets yet. -
Escaping requirements depend on what, if anything, finally ends up being called by script/exe.
-
Does the shell that Java is invoked from have any affect?
-
UTF8 in command lines?
How are programs found by ProcessBuilder on Windows? How does this compare with how programs are found when running PowerShell and CMD shells? Windows has suprised me in the past, so I’m going to explore cases that might seem obvious to those already familiar with how Windows works.
We’ll test using 3 distinct folders:
-
cwd
- we’ll always launch from this folder -
workdir
- we’ll optionally specify this as thedirectory
workdir for ProcessBuilder tests -
on-path
- we’ll add this to our PATH
The above folders are populated with variants of print-dirs
.
In the tables below, we’ll describe what we’ve load up under the "Scenario" column, for example:
cwd [ps1] workdir [bat cmd com exe ps1] on-path [bat cmd com exe ps1]
For easier to read results we’ll rename print-dirs
to a
.
Example output from running a
:
exepath: Z:\lread\info-process-builder\resolve-test\scenario\on-path\a.com workdir: Z:\lread\info-process-builder\resolve-test\scenario\cwd
We’ll shorten this output in our tables below to the easier to scan:
on-path\a.com cwd
For comparison, let’s first have a look at how Linux behaves (we can assume macOS behaves the same).
Scenario | Bash | ProcessBuilder | |
---|---|---|---|
|
cwd [sh] workdir [sh] on-path [sh] |
on-path/a.sh cwd |
on-path/a.sh cwd |
|
cwd [sh] workdir [sh] on-path [sh] |
cwd/a.sh cwd |
cwd/a.ash cwd |
|
cwd [sh] workdir [sh] on-path [sh] |
workdir/a.sh cwd |
workdir/a.sh cwd |
And when specifying a workdir:
Scenario | ProcessBuilder | |
---|---|---|
|
cwd [sh] workdir [sh] on-path [sh] |
on-path/a.sh workdir |
|
cwd [sh] workdir [sh] on-path [sh] |
workdir/a.sh workdir |
|
cwd [sh] workdir [sh] on-path [sh] |
cwd/a.sh workdir |
Oh such sweet, consistent, and clearly understandable simplicity.
-
If program has no path prefix,
PATH
is searched. -
If program has a relative path prefix, program is resolved against:
-
cwd
-
or, if specified via ProcessBuilder,
directory
workdir
-
-
If program has an absolute path, the absolute path is used.
Our scenario folders are populated with copies .com
,.exe
,.bat
,.cmd
,.ps1
variants of print-dirs
.
Note that .com
is obscure these days, and I don’t know how to build a proper one, so print-dirs.com
is actually just a copy of print-dirs.exe
.
Source for print-dirs.exe
is in print-dirs.go
which can be built on any OS via:
GOOS=windows GOARCH=amd64 go build -o print-dirs.exe print-dirs.go
Or if you want to build via docker (and apply tricks for smaller .exe)
docker run --rm \
-v "$PWD":/src \
-w /src \
devopsworks/golang-upx:latest \
bash -c 'GOOS=windows GOARCH=amd64 \
go build -ldflags "-s -w" -o print-dirs.exe print-dirs.go &&
upx --ultra-brute print-dirs.exe'
As mentioned above, we rename print-dirs
to a
when populating scenarios for briefer results.
All tests run on Windows 10 with all updates applied as of 2024-07-18.
Program extension auto-resolution (in order):
-
PowerShell:
.ps1
,.com
,.exe
,.bat
,.cmd
-
CMD Shell:
.com
,.exe
,.bat
,.cmd
-
ProcessBuilder:
.exe
Directory auto-resolution (in order):
-
Powershell:
PATH
-
CMD Shell: cwd,
PATH
-
ProcessBuilder:
-
no workdir: cwd,
PATH
-
with workdir:
-
.com
,.exe
: cwd,PATH
(resolving to cwd seems odd because we’ve specified a workdir) -
.bat
,.cmd
: workdir,PATH
(but maybe intended to bePATH
, not sure, buggy)
-
-
When invoking .\a.ps1
from a CMD shell, notepad launches with file contents.
This kind of mucks up our testing, so I temporarily changed Windows to point to fake-notepad.exe
.
-
Registry key:
HKEY_CLASSES_ROOT\Microsoft.PowerShellScript.1\Shell\Open\Command
-
Old Registry value:
"C:\Windows\System32\notepad.exe" "%1"
Impact of host shell on ProcessBuilder:
-
I saw no difference between launching ProcessBuilder tests from CMD Shell or PowerShell.
Impact of JDK version on ProcessBuilder:
-
I repeated tests on JDK versions 8, 11, 17, 21 and 22 and saw no difference.
Nothing terribly suprising here.
-
Both CMD Shell and ProcessBuilder resolve
a
from the current working directory. -
PowerShell, like linux/macOS does not automatically resolve from current working directory.
-
CMD Shell, of course, does not resolve to
a.ps1
. -
Perhaps a bit suprising is that ProcessBuilder only resolves to
a.exe
.
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.com cwd |
<not found> |
cwd\a.exe cwd |
|
cwd: [bat cmd exe ps1] workdir: [] on-path: [] |
cwd\a.exe cwd |
<not found> |
cwd\a.exe cwd |
|
cwd: [bat cmd ps1] workdir: [] on-path: [] |
cwd\a.bat cwd |
<not found> |
<not found> |
|
cwd: [cmd ps1] workdir: [] on-path: [] |
cwd\a.cmd cwd |
<not found> |
<not found> |
|
cwd: [ps1] workdir: [] on-path: [] |
<not found> |
<not found> |
<not found> |
Nothing terribly suprising here:
-
CMD Shell cannot run
.ps1
files, and neither can ProcessBuilder. -
PowerShell, like linux/macOS does not automatically resolve from current working directory.
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
<opened notepad with cwd\a.ps1> |
<not found> |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.com cwd |
<not found> |
cwd\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.exe cwd |
<not found> |
cwd\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.bat cwd |
<not found> |
cwd\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.cmd cwd |
<not found> |
cwd\a.cmd cwd |
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
<opened notepad with cwd\a.ps1> |
on-path\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.com cwd |
on-path\a.com cwd |
cwd\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.exe cwd |
on-path\a.exe cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.bat cwd |
on-path\a.bat cwd |
cwd\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.cmd cwd |
on-path\a.cmd cwd |
cwd\a.cmd cwd |
|
cwd: [] workdir: [] on-path: [bat cmd com exe ps1] |
<opened notepad with on-path\a.ps1> |
on-path\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [] workdir: [] on-path: [bat cmd com exe ps1] |
on-path\a.com cwd |
on-path\a.com cwd |
on-path\a.com cwd |
|
cwd: [] workdir: [] on-path: [bat cmd com exe ps1] |
on-path\a.exe cwd |
on-path\a.exe cwd |
on-path\a.exe cwd |
|
cwd: [] workdir: [] on-path: [bat cmd com exe ps1] |
on-path\a.bat cwd |
on-path\a.bat cwd |
on-path\a.bat cwd |
|
cwd: [] workdir: [] on-path: [bat cmd com exe ps1] |
on-path\a.cmd cwd |
on-path\a.cmd cwd |
on-path\a.cmd cwd |
Nothing terribly suprising:
-
We see again that ProcessBuilder only resolves
a.exe
-
We see again that ProcessBuilder and CMD Shell prefer cwd over
PATH
-
As expected, PowerShell resolves to
a.ps1
-
We see again that PowerShell behaves more like macOS/Linux.
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.com cwd |
on-path\a.ps1 cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd exe ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.exe cwd |
on-path\a.ps1 cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.bat cwd |
on-path\a.ps1 cwd |
on-path\a.exe cwd |
|
cwd: [cmd ps1] workdir: [] on-path: [bat cmd com exe ps1] |
cwd\a.cmd cwd |
on-path\a.ps1 cwd |
on-path\a.exe cwd |
|
cwd: [ps1] workdir: [] on-path: [bat cmd com exe ps1] |
on-path\a.com cwd |
on-path\a.ps1 cwd |
on-path\a.exe cwd |
|
cwd: [ps1] workdir: [] on-path: [bat cmd com exe] |
on-path\a.com cwd |
on-path\a.com cwd |
on-path\a.exe cwd |
|
cwd: [ps1] workdir: [] on-path: [bat cmd exe] |
on-path\a.exe cwd |
on-path\a.exe cwd |
on-path\a.exe cwd |
|
cwd: [ps1] workdir: [] on-path: [bat cmd] |
on-path\a.bat cwd |
on-path\a.bat cwd |
<not found> |
|
cwd: [ps1] workdir: [] on-path: [cmd] |
on-path\a.cmd cwd |
on-path\a.cmd cwd |
<not found> |
|
cwd: [ps1] workdir: [] on-path: [] |
<not found> |
<not found> |
<not found> |
Nothing terribly surprising:
-
We’ve already learned that ProcessBuilder only resolves to
.exe
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.com cwd |
cwd\a.ps1 cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd com exe] workdir: [] on-path: [] |
cwd\a.com cwd |
cwd\a.com cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd exe] workdir: [] on-path: [] |
cwd\a.exe cwd |
cwd\a.exe cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd] workdir: [] on-path: [] |
cwd\a.bat cwd |
cwd\a.bat cwd |
<not found> |
|
cwd: [cmd] workdir: [] on-path: [] |
cwd\a.cmd cwd |
cwd\a.cmd cwd |
<not found> |
No real suprises:
-
It is expected that CMD Shell cannot run
.ps1
scripts. -
ProcessBuilder cannot run
.ps1
scripts directly either.
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
<opened notepad with cwd\a.ps1> |
cwd\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.com cwd |
cwd\a.com cwd |
cwd\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.exe cwd |
cwd\a.exe cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.bat cwd |
cwd\a.bat cwd |
cwd\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [] on-path: [] |
cwd\a.cmd cwd |
cwd\a.cmd cwd |
cwd\a.cmd cwd |
Yes they do (note that I abbreviated absolute paths with …
for easier reading).
-
We did not expect CMD Shell to run a
.ps1
-
We’ve already learned that PowerShell does not run
.ps1
scripts directly.
Scenario | Cmd Shell | PowerShell | ProcessBuilder | |
---|---|---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<opened notepad with cwd\a.ps1> |
cwd\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.com cwd |
cwd\a.com cwd |
cwd\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe cwd |
cwd\a.exe cwd |
cwd\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.bat cwd |
cwd\a.bat cwd |
cwd\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.cmd cwd |
cwd\a.cmd cwd |
cwd\a.cmd cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<opened notepad with workdir\a.ps1> |
workdir\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.com cwd |
workdir\a.com cwd |
workdir\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.exe cwd |
workdir\a.exe cwd |
workdir\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.bat cwd |
workdir\a.bat cwd |
workdir\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.cmd cwd |
workdir\a.cmd cwd |
workdir\a.cmd cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<opened notepad with on-path\a.ps1> |
on-path\a.ps1 cwd |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.com cwd |
on-path\a.com cwd |
on-path\a.com cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.exe cwd |
on-path\a.exe cwd |
on-path\a.exe cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.bat cwd |
on-path\a.bat cwd |
on-path\a.bat cwd |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.cmd cwd |
on-path\a.cmd cwd |
on-path\a.cmd cwd |
Specifying a workdir only applies to ProcessBuilder so we leave out comparisons with CMD Shell and PowerShell.
This matches up with what we learned so far:
-
ProcessBuilder prefers cwd (overidden here with workdir) then
PATH
-
ProcessBuilder only resolves to
.exe
Scenario | ProcessBuilder | |
---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe workdir |
|
cwd: [bat cmd com ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.exe workdir |
|
cwd: [bat cmd com ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com ps1] |
<not found> |
This has some real suprises, see comments in the table.
Scenario | ProcessBuilder | |
---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.com workdir Since we have specified a workdir, it is surprising that this resolves to cwd. Less suprising would have been resolution to workdir, or PATH. |
|
cwd: [bat cmd exe] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.com workdir |
|
cwd: [bat cmd exe] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe workdir Since we have specified a workdir, it is surprising that this resolves to cwd.
Less suprising would have been resolution to workdir, or PATH.
Consistent with |
|
cwd: [bat cmd] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.exe workdir |
|
cwd: [bat cmd] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.bat workdir Inconsistent with |
|
cwd: [bat cmd] workdir: [cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.bat workdir Ok, favors workdir then PATH. |
|
cwd: [cmd] workdir: [cmd] on-path: [cmd] |
workdir\a.cmd workdir Same behavior as |
|
cwd: [cmd] workdir: [] on-path: [cmd] |
on-path\a.cmd workdir Same behavior as |
No surprises, we’ve already learned:
-
ProcessBuilder only resolves to
.exe
Scenario | ProcessBuilder | |
---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe workdir |
|
cwd: [bat cmd com ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not found> |
Some surprises here, see comments in table.
Scenario | ProcessBuilder | |
---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.com workdir Consistent with |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe workdir Consistent with |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.bat workdir Consistent with |
|
cwd: [cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not found> Happened to notice this, so showing what seems like a bug in ProcessBuilder.
(Since workdir is preferred why is the absence of |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.cmd workdir |
|
cwd: [bat com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not found> Happened to notice this, so showing what seems like a bug in ProcessBuilder.
(Since workdir is preferred why is the absence of |
Yes they do (note that I abbreviated absolute paths with …
for easier reading).
-
We’ve already learned that PowerShell does not run
.ps1
scripts directly.
Scenario | ProcessBuilder | |
---|---|---|
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.com workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.exe workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.bat workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
cwd\a.cmd workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.com workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.exe workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.bat workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
workdir\a.cmd workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
<not a valid Win32 application> |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.com workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.exe workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.bat workdir |
|
cwd: [bat cmd com exe ps1] workdir: [bat cmd com exe ps1] on-path: [bat cmd com exe ps1] |
on-path\a.cmd workdir |
These tests only explore behaviour they do not verify behaviour against some expectations.
From resolve-test
dir, gen test scripts with:
bb gen_resolve_tests.clj
Run CMD Shell tests from a CMD shell via:
-
run-cmd-tests.bat
- CMD shell tests -
run-pb-cmd-tests.bat
- Run ProcessBuilder tests (from CMD)
Run Powershell tests from a Powershell via:
-
run-pwr-tests.ps1
- PowerShell tests -
run-pb-pwr-tests.ps1
- Run ProcessBuilder tests (from PowerShell)
Why do we run ProcessBuilder from both CMD and PowerShell? I was curious to see any effects on resolving if the launching shell was different. (I found no differences)
The run-pb-*
tests should be run against each jdk version you are interested in testing against.
(I found no differences on jdk8, 11, 17 and 21)
All raw output is saved under results
.
Other source files:
-
PBResolveTest.java
- Our launcher for ProcessBuilder -
scenario.clj
- A bb script to setup our test scenario -
print-dirs.*
- Our target programs to launch (copied toa.*
) when loaded into scenario dirs -
fake-notepad.go
- A dummy for notepad.exe -
java-version.clj
- A wee script to print out java version in short form -
compare_results.clj
- Compares the ProcessBuilder test results (across shells and JDKs), they should all be the same. -
summarize.clj
- Summarizes the results in toresults/summary.adoc
. I pasted these results into this doc and annotated with observations.
If you want to poke around here’s some tips.
We assume you are starting from the resolve-test
dir.
Fire up CMD Shell. Alter the PATH
cd scenario
rmdir /S /Q cwd workdir on-path
mkdir cwd workdir on-path
set PATH=%CD%\on-path;%PATH%
cd cwd
Setup a scenario via:
> bb ..\..\scenario.clj set --cwd=bat,com,cmd,ps1 --workdir=exe,ps1 --on-path=exe
cwd [bat cmd com ps1]
workdir [exe ps1]
on-path [exe]
And launch a
however you see fit:
> a
exepath: Z:\lread\info-process-builder\resolve-test\scenario\cwd\a.com
workdir: Z:\lread\info-process-builder\resolve-test\scenario\cwd
> a.exe
exepath: Z:\lread\info-process-builder\resolve-test\scenario\on-path\a.exe
workdir: Z:\lread\info-process-builder\resolve-test\scenario\cwd
Fire up a PowerShell.
cd scenario
mkdir cwd
$env:PATH = "$PWD\on-path;$env:PATH"
cd cwd
Run scenarios and tests as described in CMD Shell above.
Recompile java sources:
javac PBResolveTest.java
Setup your PATH as directed above for CMD Shell or PowerShell above.
Setup scenario as described in CMD Shell above.
Run ProcessBuilder tests from scenario\cwd
like so:
> java -cp ..\.. PBResolveTest a
cwd: Z:\lread\info-process-builder\resolve-test\scenario\cwd
args: [a]
running: a dir: null
exepath: Z:\lread\info-process-builder\resolve-test\scenario\on-path\a.exe
workdir: Z:\lread\info-process-builder\resolve-test\scenario\cwd
> java -c ..\.. PBResolveTest a.exe
cwd: Z:\lread\info-process-builder\resolve-test\scenario\cwd
args: [a.exe]
running: a.exe dir: null
exepath: Z:\lread\info-process-builder\resolve-test\scenario\on-path\a.exe
workdir: Z:\lread\info-process-builder\resolve-test\scenario\cwd
Optionally specify a workdir as the 2nd arg:
>java -cp ..\.. PBResolveTest a.exe ..\workdir
cwd: Z:\lread\info-process-builder\resolve-test\scenario\cwd
args: [a.exe, ..\workdir]
running: a.exe dir: ..\workdir
exepath: Z:\lread\info-process-builder\resolve-test\scenario\on-path\a.exe
workdir: Z:\lread\info-process-builder\resolve-test\scenario\workdir