15

I have the following trivial C# application that simply attempts to launch "jconsole.exe", which on my machine is located in C:\Programs\jdk16\bin.

using System;
using System.Diagnostics;

namespace dnet {
  public class dnet {
    static void Main( string[] args ) {
      try {
        Process.Start("jconsole.exe");
        Console.WriteLine("Success!");
      } catch (Exception e) {
        Console.WriteLine("{0} Exception caught.", e);
      }
    }
  }
}

If my PATH environment variable is set to

c:\windows;c:\windows\sytem32;c:\programs\jdk16\bin

it works perfectly. However, if the PATH environment variable is set to

c:\windows;c:\windows\sytem32;c:\\programs\jdk16\bin

(note the two backslashes between "c:" and "programs"), it fails with a win32 exception.

System.ComponentModel.Win32Exception (0x80004005): The system cannot find the file specified
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at dnet.dnet.Main(String[] args)

Interestingly, in the same command prompt where I run the .NET program and get the exception, I can simply type "jconsole.exe", and the program will start. Windows appears to have no trouble finding the executable with the double backslash in the PATH, but Process.Start() does.

Why is the extra backslash in the PATH causing problems, and how I can get around the problem? I don't know where the executable I want to call will be located at runtime, so I'd rather rely on the PATH variable.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Reg Domaratzki
  • 895
  • 2
  • 7
  • 11
  • 1
    There are two ways to start an EXE, you are testing both ways. Your app uses ShellExecuteEx(), the command line interpreter uses CreateProcess(). You can play with ProcessStartInfo.UseShellExecute property. There isn't much point in worrying about how they interpret the PATH environment variable differently, you know how to fix the problem. – Hans Passant Sep 12 '12 at 17:41

3 Answers3

15

Not quite sure why the problem occurs. Though, I can think of one solution that works on my machine:

var enviromentPath = System.Environment.GetEnvironmentVariable("PATH");

Console.WriteLine(enviromentPath);
var paths = enviromentPath.Split(';');
var exePath = paths.Select(x => Path.Combine(x, "mongo.exe"))
                   .Where(x => File.Exists(x))
                   .FirstOrDefault();

Console.WriteLine(exePath);

if (string.IsNullOrWhiteSpace(exePath) == false)
{
    Process.Start(exePath);
}

I did find one para which gave me the idea for this solution. From the documentation for Process.Start

If you have a path variable declared in your system using quotes, you must fully qualify that path when starting any process found in that location. Otherwise, the system will not find the path. For example, if c:\mypath is not in your path, and you add it using quotation marks: path = %path%;"c:\mypath", you must fully qualify any process in c:\mypath when starting it.

The way I read it, even though the PATH variable contained a valid path that Windows is able to use, Process.Start is unable to use it and needs the fully qualified path .

Rodja
  • 7,998
  • 8
  • 48
  • 55
Amith George
  • 5,806
  • 2
  • 35
  • 53
  • Thanks for highlighting the paragraph from the docs Amith. I'd interpreted it as only affecting entries in the path with quotes, but I like your generalization that you can't trust Process.Start() to use the PATH environment variable properly. As an interesting aside, I tried setting my PATH to `c:\windows\system32;c:\windows;"c:\programs\jdk16\bin"`, and Process.Start() was able to find jconsole.exe without any extra help. This seems contrary to what the docs say, about Process.Start() using the PATH, so I really don't trust it now. :) – Reg Domaratzki Sep 12 '12 at 18:39
  • 2
    I recommend to use [System.IO.Path.PathSeparator](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.pathseparator) to have multi platform support. On macos the path separator is ":". – robinryf Jan 05 '20 at 14:50
4

You can solve it if you first create a ProcessStartInfo.

ProcessStartInfo psi = new ProcessStartInfo("jconsole.exe");
StringDictionary dictionary = psi.EnvironmentVariables;

// Manipulate dictionary...

psi.EnvironmentVariables["PATH"] = dictionary.Replace(@"\\", @"\");
Process.Start(psi);

You'll have to find out yourself how to manipulate the PATH to let it work for you. But this should solve any issues you might have with your PATH variable.

Chrono
  • 1,433
  • 1
  • 16
  • 33
  • 1
    After changing the [EnvironmentVariables property](http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.environmentvariables.aspx) , you have to have set the [UseShellExecute property](http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.useshellexecute.aspx) to false. However, if UseShellExecute is false, I must specify the fully qualified path for the [FileName property](http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.filename), which sort of defeats the purpose of changing the path. – Reg Domaratzki Sep 12 '12 at 19:01
  • Yet in the very example on the `UseShellExecute` page they do not give a fully qualified `FileName` and use `UseShellExecute = false;`. I've also tried setting it to false and just calling an exe that can only be found in my PATH and it just starts. – Chrono Sep 12 '12 at 19:39
  • i would also recommand to set your `Process.StartInfo.WorkingDirectory` with the environment path the your .exe should using – Lenor Jul 02 '20 at 15:27
  • Note that `dictionary.Replace(@"\\", @"\")` will corrupt UNC-paths like `\\PCNAME\Public`, if someone uses them in `%PATH%` – AntonK Apr 17 '23 at 11:15
4

The accepted answer is incorrect.

cmd.exe will find applications with executable extensions first.
So when you have the files puma and puma.bat in C:\Ruby\bin\, then puma.bat will take precedence over puma.

If you start c:\ruby\bin\puma.bat from c:\redmine, it will start puma with current working directory c:\ruby\bin, and your web application will work.
However, if you start c:\ruby\bin\puma directly, it will start puma with the current working directory in c:\redmine and will subsequently fail.

So a corrected version looks more or less like this:

// FindAppInPathDirectories("ruby.exe");
public string FindAppInPathDirectories(string app)
{
    string enviromentPath = System.Environment.GetEnvironmentVariable("PATH");
    string[] paths = enviromentPath.Split(';');

    foreach (string thisPath in paths)
    {
        string thisFile = System.IO.Path.Combine(thisPath, app);
        string[] executableExtensions = new string[] { ".exe", ".com", ".bat", ".sh", ".vbs", ".vbscript", ".vbe", ".js", ".rb", ".cmd", ".cpl", ".ws", ".wsf", ".msc", ".gadget" };

        foreach (string extension in executableExtensions)
        {
            string fullFile = thisFile + extension;

            try
            {
                if (System.IO.File.Exists(fullFile))
                    return fullFile;
            }
            catch (System.Exception ex)
            {
                Log("{0}:\r\n{1}",
                     System.DateTime.Now.ToString(m_Configuration.DateTimeFormat, System.Globalization.CultureInfo.InvariantCulture)
                    , "Error trying to check existence of file \"" + fullFile + "\""
                );

                Log("Exception details:");
                Log(" - Exception type: {0}", ex.GetType().FullName);
                Log(" - Exception Message:");
                Log(ex.Message);
                Log(" - Exception Stacktrace:");
                Log(ex.StackTrace);
            } // End Catch

        } // Next extension

    } // Next thisPath


    foreach (string thisPath in paths)
    {
        string thisFile = System.IO.Path.Combine(thisPath, app);

        try
        {
            if (System.IO.File.Exists(thisFile))
                return thisFile;
        }
        catch (System.Exception ex)
        {
            Log("{0}:\r\n{1}",
                 System.DateTime.Now.ToString(m_Configuration.DateTimeFormat, System.Globalization.CultureInfo.InvariantCulture)
                , "Error trying to check existence of file \"" + thisFile + "\""
            );

            Log("Exception details:");
            Log(" - Exception type: {0}", ex.GetType().FullName);
            Log(" - Exception Message:");
            Log(ex.Message);
            Log(" - Exception Stacktrace:");
            Log(ex.StackTrace);
        } // End Catch

    } // Next thisPath

    return app;
} // End Function FindAppInPathDirectories
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442