C# Process.StartInfo.FileName 是否接受长文件名?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/616898/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 10:25:09  来源:igfitidea点击:

Does Process.StartInfo.FileName accept long file names?

c#

提问by Thushan

Looks like it's not.

好像不是。

If I convert the file name to its short value, then Process.Start() works.

如果我将文件名转换为其短值,则 Process.Start() 工作。

Process runScripts = new Process();
runScripts.StartInfo.FileName = @"C:\long file path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();

The above code fails. But...

上面的代码失败了。但...

Process runScripts = new Process();
runScripts.StartInfo.FileName = @"C:\short\file\path\run.cmd";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();

succeeds.

成功。

I managed to get around this by converting the long path name to a short path name. But I am a bit surprised to find this. Any reasons or background info on this?

我设法通过将长路径名转换为短路径名来解决这个问题。但我有点惊讶地发现这一点。任何原因或背景信息?

Thanks.

谢谢。

Update 1
Microsoft .NET Framework Version 2.0.50727

更新 1
Microsoft .NET Framework 版本 2.0.50727

采纳答案by Milan Gardian

To reproduce your problem, I used the following program:

为了重现您的问题,我使用了以下程序:

// file test.cs
using System;
using System.ComponentModel;
using System.Diagnostics;

public class Test
{
    public static int Main()
    {
        string error;
        try {
            ProcessStartInfo i = new ProcessStartInfo();
            i.FileName = @"C:\long file path\run.cmd";
            i.WindowStyle = ProcessWindowStyle.Hidden;
            i.UseShellExecute = true;
            i.RedirectStandardOutput = false;
            using (Process p = Process.Start(i)) {
                error = "No process object was returned from Process.Start";
                if (p != null) {
                    p.WaitForExit();
                    if (p.ExitCode == 0) {
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.WriteLine("OK");
                        Console.ResetColor();
                        return 0;
                    }
                    error = "Process exit code was " + p.ExitCode;
                }
            }
        }
        catch (Win32Exception ex) {
            error = "(Win32Exception) " + ex.Message;
        }
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("Whooops: " + error);
        Console.ResetColor();
        return 1;
    }
}

The code starts a new process (as per your code sample) and reports the different ways in which it may fail to execute. You can compile the program from command line:

代码启动一个新进程(根据您的代码示例)并报告它可能无法执行的不同方式。您可以从命令行编译程序:

c:\windows\Microsoft.NET\Framework\v2.0.50727\csc test.cs

(assuming test.cs is in the current directory; it will create test.exe in the same directory as test.cs)

(假设 test.cs 在当前目录中;它将在与 test.cs 相同的目录中创建 test.exe)

As expected, when "C:\long file path\run.cmd" does not exist, the program fails with:

正如预期的那样,当“C:\long file path\run.cmd”不存在时,程序会失败:

Whooops: (Win32Exception) The system cannot find the file specified

Now let's create a directory "C:\long file path" and put a very simple run.cmd in there:

现在让我们创建一个目录“C:\long file path”并在其中放置一个非常简单的 run.cmd:

rem file run.cmd
echo I ran at %Time% > "%~dp0\run.out.txt"

However, at this point I was unable to reproduce your failure. Once the above run.cmd is in place, test.exe runs successfully (i.e. run.cmd is executed correctly - you can verify this by looking for a newly created file "C:\long file path\run.out.txt".

但是,此时我无法重现您的失败。一旦上面的 run.cmd 就位,test.exe 就会成功运行(即 run.cmd 被正确执行 - 您可以通过查找新创建的文件“C:\long file path\run.out.txt”来验证这一点。

I ran test.exe on Vista x64 (my main dev machine), Windows XP SP3 x86 (virtual machine), Windows Server 2008 x64 (virtual machine) and it works everywhere.

我在 Vista x64(我的主要开发机器)、Windows XP SP3 x86(虚拟机)、Windows Server 2008 x64(虚拟机)上运行 test.exe,它在任何地方都可以运行。

Could you try running the above code in your environment and report back whether it is failing for you? This way we will at least establish the same testing context (same .NET program will be trying to run the same batch file in the same location for you as it does for me).

您能否尝试在您的环境中运行上述代码并报告它是否对您失败?这样我们至少会建立相同的测试上下文(相同的 .NET 程序将尝试在相同的位置为您运行相同的批处理文件,就像对我一样)。

回答by Alex Reitbort

Try

尝试

runScripts.StartInfo.FileName = @"""C:\long file path\run.cmd""";

Although I was sure it was done automatically for you by Process class. Are you sure you supplying the correct path?

虽然我确信它是由 Process 类自动为你完成的。你确定你提供了正确的路径?

回答by ageektrapped

Try this:

尝试这个:

Process runScripts = new Process();
runScripts.StartInfo.FileName = @"""C:\long file path\run.cmd""";
runScripts.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
runScripts.StartInfo.UseShellExecute = true;
runScripts.StartInfo.RedirectStandardOutput = false;
runScripts.Start();

I.e. use a quoted string for FileName when FileName has spaces.

即,当 FileName 有空格时,对 FileName 使用带引号的字符串。

回答by sisve

I am unable to reproduce the behavior you're describing. The following code works for me.

我无法重现您所描述的行为。以下代码对我有用。

string path = @"X:\Temp\long file path\run.cmd";
Process p = new Process();
p.StartInfo.FileName = path;

// Works with both true (default) and false.
p.StartInfo.UseShellExecute = true;
p.Start();

edit

编辑

I am able to reproduce with the following code

我可以使用以下代码重现

  string path = @"X:\Temp\long file path";
  string file = "run.cmd";

  Process p = new Process();
  p.StartInfo.FileName = file;
  p.StartInfo.WorkingDirectory = path;
  p.StartInfo.UseShellExecute = false; // only fails with false.
  p.Start();
  return;

This show (with Process Monitor) that ConsoleApplication.vshost.exe tries to find run.cmd in project\bin\Release, System32, System, Windows, System32\Wbem, and then further into some (I guess) path variables. It works, however, if I set UseShellExecute = true.

这表明(使用进程监视器)ConsoleApplication.vshost.exe 试图在 project\bin\Release、System32、System、Windows、System32\Wbem 中找到 run.cmd,然后进一步进入一些(我猜)路径变量。但是,如果我设置 UseShellExecute = true,它会起作用。

回答by Jhonny D. Cano -Leftware-

It's curious, i reproduce the behavior, effectively, it doesn't execute as you stated, but modifying this, it worked:

很好奇,我有效地重现了该行为,它不会像您所说的那样执行,但是修改它,它起作用了:

    System.Diagnostics.Process runScripts = new System.Diagnostics.Process();
    runScripts.StartInfo.FileName = @"run.cmd";
    // new
    runScripts.StartInfo.WorkingDirectory = @"C:\long file path";
    runScripts.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    runScripts.StartInfo.UseShellExecute = true;
    runScripts.StartInfo.RedirectStandardOutput = false;
    runScripts.Start();

回答by leiflundgren

I have a hypothesis. Win32 has a limitation that strings used to descibe filenames cannot be longer then MAX_PATH (260 bytes) including terminating '\0'.

我有一个假设。Win32 有一个限制,即用于描述文件名的字符串不能长于 MAX_PATH(260 字节),包括终止“\0”。

Maybe that problem has leaked into C#?

也许这个问题已经泄露到 C# 中?

(The tests I've done indicates that, but I cannot confirm which bugs me.)

(我所做的测试表明了这一点,但我无法确认哪些错误是我的问题。)

So, try to prefix your path with "\\?\". (backslash, backslash, questionmark, backslash) I.e.

因此,尝试使用“\\?\”作为路径前缀。(反斜杠,反斜杠,问号,反斜杠)即

runScripts.StartInfo.FileName = @"\?\C:\long file path\run.cmd";

For more details on MAX_PATH: http://msdn.microsoft.com/en-us/library/aa365247.aspx

有关 MAX_PATH 的更多详细信息:http: //msdn.microsoft.com/en-us/library/aa365247.aspx

/Leif

/莱夫

回答by Chris S

(Update)

(更新)

I think I found the problem from reverse engineering the Process.Start method a bit more. I was along the right lines, it fails without having a WorkingDirectory set.

我想我从对 Process.Start 方法的逆向工程中发现了更多问题。我是在正确的路线上,它在没有设置 WorkingDirectory 的情况下失败。

So in other words CreateProcess needs a valid workingdirectory, not .NET (it should really deduce it from the filename but obviously doesn't)

因此,换句话说 CreateProcess 需要一个有效的工作目录,而不是 .NET(它应该真正从文件名中推断出来,但显然不是)

Here's the proof:

这是证据:

namespace SoTest
{
    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SECURITY_ATTRIBUTES lpProcessAttributes, SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, STARTUPINFO lpStartupInfo, PROCESS_INFORMATION lpProcessInformation);

        static void Main(string[] args)
        {
            Process process = new Process();
            process.StartInfo.FileName = @"C:\my test folder\my test.bat";

            StringBuilder cmdLine = new StringBuilder();
            cmdLine.Append(process.StartInfo.FileName);
            STARTUPINFO lpStartupInfo = new STARTUPINFO();
            PROCESS_INFORMATION lpProcessInformation = new PROCESS_INFORMATION();

            // This fails
            //string workingDirectory = process.StartInfo.WorkingDirectory;
            string workingDirectory = @"C:\my test folder\";

            CreateProcess(null, cmdLine, null, null, true, 0, IntPtr.Zero, workingDirectory, lpStartupInfo, lpProcessInformation);
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public int dwProcessId;
        public int dwThreadId;

        public PROCESS_INFORMATION()
        {
            this.hProcess = IntPtr.Zero;
            this.hThread = IntPtr.Zero;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class SECURITY_ATTRIBUTES
    {
        public int nLength;
        public long lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class STARTUPINFO
    {
        public int cb;
        public IntPtr lpReserved;
        public IntPtr lpDesktop;
        public IntPtr lpTitle;
        public int dwX;
        public int dwY;
        public int dwXSize;
        public int dwYSize;
        public int dwXCountChars;
        public int dwYCountChars;
        public int dwFillAttribute;
        public int dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;

        public STARTUPINFO()
        {
            this.lpReserved = IntPtr.Zero;
            this.lpDesktop = IntPtr.Zero;
            this.lpTitle = IntPtr.Zero;
            this.lpReserved2 = IntPtr.Zero;
            this.hStdInput = IntPtr.Zero;
            this.hStdOutput = IntPtr.Zero;
            this.hStdError = IntPtr.Zero;
            this.cb = Marshal.SizeOf(this);
        }
    }       
}

I've removed SafeFileHandle from the original code as it wasn't needed for what we're doing. Also no start flags were set, but these are needed for a windowless version.

我已经从原始代码中删除了 SafeFileHandle,因为我们正在做的事情不需要它。也没有设置开始标志,但这些是无窗口版本所需要的。

回答by Igor Brejc

Your code can fail for various reasons which are unrelated to the long/short path issue. You should add the exact exception description (including the call stack) to your question.

您的代码可能由于与长/短路径问题无关的各种原因而失败。您应该将确切的异常描述(包括调用堆栈)添加到您的问题中。

回答by ini18

I managed to actually run it by using following behavior.

我设法通过使用以下行为来实际运行它。

    private string StartProcessAndGetResult(string executableFile, string arguments)
    //NOTE executable file should be passed as string with a full path
    //For example: C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe
    //Without """ and @ signs
    {
        var result = String.Empty;
        var workingDirectory = Path.GetDirectoryName(executableFile);


        var processStartInfo = new ProcessStartInfo(executableFile, arguments)
                                   {
                                       WorkingDirectory = workingDirectory,
                                       UseShellExecute = false,
                                       ErrorDialog = false,
                                       CreateNoWindow = true,
                                       RedirectStandardOutput = true
                                   };
        var process = Process.Start(processStartInfo);
        if (process != null)
        {
            using (var streamReader = process.StandardOutput)
            {
                result = streamReader.ReadToEnd();
            }
        }
        return result;
    }

The solution with @ or taking the string to quotes (""") did not wokred in my case.

在我的情况下,带有 @ 或将字符串带引号 (""") 的解决方案并没有奏效。