private static void GetExecutable(PlatformAdaptationLayer/*!*/ pal, string/*!*/ command, out string executable, out string arguments) {
command = command.Trim(' ');
if (command.Length == 0) {
throw RubyExceptions.CreateEINVAL(command);
}
// This seems to be quite complicated:
// 1) If the first part of the command is a shell command (DIR, ECHO, ...) or
// if the command contains unquoted <, > or | then
// it uses ENV['COMSPEC'] to execute the command: %COMSPEC% /c "COMMAND"
// 2) It looks for the shortest prefix of command that is separated by space from the rest of the command that is either
// a) An absolute path to an executable file.
// b) Try prepend paths from ENV['PATH']
// c) Try Environment.SpecialFolder.System.
// d) Try SHGetFolderPath(CSIDL_WINDOWS) - we can't get this from Environment.SpecialFolder, so we need to use
// ENV["SystemRoot"] environment variable.
// In each step it tries to append ".exe" or ".com" extension if the path doesn't exist.
//
// For example, if the command is `x/a b/x` and the directory structure is
// x\a b\x.exe
// x\a.exe
// it executes a.exe.
//
// MRI probably calls CreateProcess Win32 API with lpApplicationName it resolves as described above and
// lpCommandLine == command. System.Diagnostics.Process also uses this API with lpApplicationName == NULL and
// lpCommandLine == '"{ProcessStartInfo.FileName}" {ProcessStartInfo.Arguments}'.
//
// Although CreateProcess does all the searching for an executable if passed no lpApplicationName,
// we need to do it ourselves because it is slightly different in MRI (is that a bug?) and also because System.Diagnostics.Process
// quotes the FileName :(
//
string comspec = pal.GetEnvironmentVariable("COMSPEC");
if (!pal.FileExists(comspec)) {
comspec = null;
}
if (comspec != null && IndexOfUnquotedSpecialCharacter(command) >= 0) {
executable = comspec;
arguments = "/c \"" + command + "\"";
return;
}
int start = 0;
while (true) {
int next = command.IndexOf(' ', start);
executable = (next >= 0) ? command.Substring(0, next) : command;
arguments = (next >= 0) ? command.Substring(next + 1) : "";
if (start == 0 && comspec != null && IsShellCommand(executable)) {
executable = comspec;
arguments = "/c \"" + command + "\"";
return;
}
try {
foreach (var path in GetExecutableFiles(pal, executable)) {
if (pal.FileExists(path)) {
// We need to set the path we found as executable. Althought makes command line of the target process
// different from when called by MRI it will execute the right process. If we passed the original executable name
// CreateProcess might resolve it to a different executable.
executable = path;
return;
}
}
} catch (Exception e) {
if (next < 0) {
throw RubyExceptions.CreateENOENT(command, e);
}
}
if (next < 0) {
throw RubyExceptions.CreateENOENT(command);
}
start = next + 1;
while (start < command.Length && command[start] == ' ') {
start++;
}
}
}