Process.Start() and PATH environment variable

15
我有一个简单的C#应用程序,它尝试启动“jconsole.exe”,而此文件位于我的机器上的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);
      }
    }
  }
}

如果我的PATH环境变量被设置为

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

它完美地运行。但是,如果设置了PATH环境变量为

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

(注意 "c:" 和 "programs" 之间的两个反斜杠),这会导致 win32 异常。

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)

有趣的是,在我运行.NET程序并得到异常的同一命令提示符中,我可以简单地键入"jconsole.exe",程序就会启动。Windows似乎没有问题地在PATH中找到了具有双反斜杠的可执行文件,但Process.Start()却出现了问题。

为什么PATH中的额外反斜杠会引起问题,以及如何解决这个问题?我不知道我想要调用的可执行文件将在运行时位于哪里,因此我宁愿依赖于PATH变量。


1
有两种方法可以启动一个EXE,你正在测试这两种方法。你的应用程序使用ShellExecuteEx(),命令行解释器使用CreateProcess()。你可以尝试修改ProcessStartInfo.UseShellExecute属性。不必过于担心它们如何不同地解释PATH环境变量,你知道如何解决这个问题。 - Hans Passant
3个回答

17

我不太确定为什么会出现这个问题,不过我可以想到一个在我的机器上有效的解决方案:

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);
}
我找到了一段话,启发了我解决这个问题。从Process.Start的文档中得知:

如果您在系统中使用引号声明了一个路径变量,则必须在启动该位置下找到的任何进程时完全限定该路径。否则,系统将无法找到该路径。例如,如果c:\mypath不在您的路径中,并且您使用引号添加它:path=%path%;"c:\mypath",则在启动c:\mypath中的任何进程时,必须完全限定该进程。

我理解为,即使PATH变量包含了Windows能够使用的有效路径,Process.Start也无法利用它,需要使用完全限定的路径.

感谢你在文档中突出显示的段落,Amith。我一开始理解为它只影响带引号的路径条目,但我喜欢你的概括说法,即您不能信任Process.Start()正确使用PATH环境变量。有趣的是,我尝试将我的PATH设置为c:\ windows \ system32; c:\ windows;“c:\ programs \ jdk16 \ bin”,而Process.Start()能够在没有任何额外帮助的情况下找到jconsole.exe。这似乎与文档所说的关于Process.Start()使用PATH的说法相矛盾,因此现在我真的不信任它。 :) - Reg Domaratzki
2
我建议使用System.IO.Path.PathSeparator来实现多平台支持。在macOS上,路径分隔符是“:”。 - robinryf

4

如果您首先创建一个ProcessStartInfo,那么您就可以解决它。

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

// Manipulate dictionary...

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

你需要自己找出如何操作 PATH 变量让它对你起作用。但这应该解决你在 PATH 变量方面可能遇到的任何问题。


1
在更改 EnvironmentVariables property 属性后,必须将UseShellExecute property属性设置为 false。但是,如果 UseShellExecute 为 false,则必须为 FileName property 指定完全限定路径,这有点违背了更改路径的目的。 - Reg Domaratzki
然而,在“UseShellExecute”页面的示例中,他们没有给出完全限定的“FileName”,而是使用了“UseShellExecute = false;”。我也尝试将其设置为false,并调用只能在我的PATH中找到的exe文件,它就会启动。 - Chrono
我还建议您将Process.StartInfo.WorkingDirectory设置为应用程序使用的环境路径。 - Lenor
请注意,dictionary.Replace(@"\\", @"\") 会破坏 UNC 路径,如 %PATH% 中使用的 \\PCNAME\Public - AntonK

4

被接受的答案是不正确的。

cmd.exe会首先查找具有可执行扩展名的应用程序。
因此,当您在C:\Ruby\bin\中拥有pumapuma.bat文件时,puma.bat将优先于puma

如果您从c:\redmine启动c:\ruby\bin\puma.bat,它将使用当前工作目录c:\ruby\bin启动puma,您的Web应用程序将正常工作。
但是,如果您直接启动c:\ruby\bin\puma,它将使用c:\redmine中的当前工作目录启动puma,并随后失败。

因此,更正后的版本看起来差不多如下:

// 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接