windows 当我从 CreateProcess 运行 NETSH 但它在命令提示符下工作正常时得到“系统找不到指定的文件”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8166840/
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-09-15 18:29:29  来源:igfitidea点击:

Got "The system cannot find the file specified" when I run NETSH from CreateProcess but it works ok on Command Prompt?

windowsdelphicreateprocessnetsh

提问by Joshua

I have an NTservice that calls a console program written in Delphi 7, let's call it failover.exethat in turn calls NETSHusing a procedure I found:

我有一个NT调用用 Delphi 7 编写的控制台程序的服务,让我们调用它failover.exe,然后NETSH使用我找到的过程调用它:

procedure ExecConsoleApp(CommandLine: ansistring; Output, Errors: TStringList); 

Note: ExecConsoleApp uses CreateProcess, see the following link for full code: http://www.delphisources.ru/pages/faq/base/createprocess_console.html

注:ExecConsoleApp 使用 CreateProcess,完整代码见以下链接:http: //www.delphisources.ru/pages/faq/base/createprocess_console.html

I would pass the following to CommandLine before calling ExecConsoleApp:

我会在调用之前将以下内容传递给命令行ExecConsoleApp

cmd.exe /c "C:\Windows\system32\netsh.exe interface delete address "Wireless Network Connection" 192.168.0.36" 

ExecConsoleAppwill return an error:

ExecConsoleApp将返回错误:

The system cannot find the file specified

该系统找不到指定的文件

But if I were to run it in Command Prompt, it runs perfectly.

但是如果我在命令提示符下运行它,它运行得很好。

The strange thing is that I remembered it working on the first attempt on that 2003 Server, but after that, it failed regardless of the number of times I tried. In one of the attempt, I've also tried assigning logon as administrator user to the service but to no avail. Neither does fiddling with file security help.

奇怪的是,我记得它是在 2003 服务器上的第一次尝试时工作的,但在那之后,无论我尝试了多少次,它都失败了。在其中一次尝试中,我还尝试以管理员用户身份登录该服务,但无济于事。摆弄文件安全性也没有帮助。

I don't have a Win 2003 server to test with in office, but I have tested it on XP and Win7 and ExecConsoleAppworks perfectly, although on XP, I had to amend ExecConsoleAppto execute from system32\wbemin order for it work work:

我没有 Win 2003 服务器可用于在办公室进行测试,但我已经在 XP 和 Win7 上对其进行了测试,并且ExecConsoleApp运行良好,尽管在 XP 上,我必须修改ExecConsoleAppsystem32\wbem使其正常工作:

 Res := CreateProcess(nil, PChar(CommandLine), nil, nil, True,
  // **** Attention: Amended by to point current directory to system32\wbem, this is to solve an error returned by netsh.exe if not done otherwise.
 //   CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, @env, nil, si, pi);
   CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, @env, pchar(GetSystemPath(WindRoot) + 'system32\wbem'), si, pi);

I've researched for a day but no clues, hope someone can help. Thanks.

我已经研究了一天,但没有任何线索,希望有人可以提供帮助。谢谢。

Additional remarks -

补充说明——

  1. Server is 32 bit Win2k3.

  2. Tried domain administrator, doesn't work.

  3. Code snippets:

    Procedure ExecConsoleApp(CommandLine: ansistring; Output, Errors: TStringList);
      var
        sa: TSECURITYATTRIBUTES;
        si: TSTARTUPINFO;
        pi: TPROCESSINFORMATION;
        hPipeOutputRead: THANDLE;
        hPipeOutputWrite: THANDLE;
        hPipeErrorsRead: THANDLE;
        hPipeErrorsWrite: THANDLE;
        Res, bTest: boolean;
        env: array[0..100] of char;
        szBuffer: array[0..256] of char;
        dwNumberOfBytesRead: DWORD;
        Stream: TMemoryStream;
      begin
        sa.nLength := sizeof(sa);
        sa.bInheritHandle := True;
        sa.lpSecurityDescriptor := nil;
        CreatePipe(hPipeOutputRead, hPipeOutputWrite, @sa, 0);
        CreatePipe(hPipeErrorsRead, hPipeErrorsWrite, @sa, 0);
        ZeroMemory(@env, SizeOf(env));
        ZeroMemory(@si, SizeOf(si));
        ZeroMemory(@pi, SizeOf(pi));
        si.cb := SizeOf(si);
        si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
        si.wShowWindow := SW_HIDE;
        si.hStdInput := 0;
        si.hStdOutput := hPipeOutputWrite;
        si.hStdError := hPipeErrorsWrite;
    
      (* Remember that if you want to execute an app with no parameters you nil the
         second parameter and use the first, you can also leave it as is with no
         problems.                                                                 *)
        Res := CreateProcess(nil, PChar(CommandLine), nil, nil, True,
        CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, @env, nil, si, pi);
    
    
        // Procedure will exit if CreateProcess fail
        if not Res then
        begin
          CloseHandle(hPipeOutputRead);
          CloseHandle(hPipeOutputWrite);
          CloseHandle(hPipeErrorsRead);
          CloseHandle(hPipeErrorsWrite);
          Exit;
        end;
        CloseHandle(hPipeOutputWrite);
        CloseHandle(hPipeErrorsWrite);
    
        //Read output pipe
        Stream := TMemoryStream.Create;
        try
          while True do
          begin
            bTest := ReadFile(hPipeOutputRead, szBuffer, 256, dwNumberOfBytesRead, nil);
            if not bTest then
            begin
              break;
            end;
            OemToAnsi(szBuffer, szBuffer);
            Stream.Write(szBuffer, dwNumberOfBytesRead);
          end;
          Stream.Position := 0;
          Output.LoadFromStream(Stream);
        finally
          Stream.Free;
        end;
    
        //Read error pipe
        Stream := TMemoryStream.Create;
        try
          while True do
          begin
            bTest := ReadFile(hPipeErrorsRead, szBuffer, 256, dwNumberOfBytesRead, nil);
            if not bTest then
            begin
              break;
            end;
            OemToAnsi(szBuffer, szBuffer);
            Stream.Write(szBuffer, dwNumberOfBytesRead);
          end;
          Stream.Position := 0;
          Errors.LoadFromStream(Stream);
        finally
          Stream.Free;
        end;
    
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hProcess);
        CloseHandle(hPipeOutputRead);
        CloseHandle(hPipeErrorsRead);
      end;
    
    
      cmdstring :=
        'cmd.exe /c "' + GetSystemPath(WindRoot) + 'system32\netsh.exe interface ' +
        ip + ' delete address "' + NetworkInterfaceName + '" ' + VirtualFailoverIPAddress + '"';
    
      logstr('cmdstring: ' + cmdstring);
      ExecConsoleApp(cmdstring, OutP, ErrorP);
    
      if OutP.Text <> '' then
      begin
        logstr('Delete IP Result: ' + OutP.Text);
      end
      else
      begin
        logstr('Delete IP Error: ' + ErrorP.Text);
      end;
    
  4. Tried running netsh.exe directly instead of "cmd.exe /c C:\Windows\system32\netsh.exe...", and got the same "The system cannot find the file specified." error. I also accidentally discovered that if I were to issue a wrong netsh command, netsh will actually return an error, e.g.

  1. 服务器是 32 位 Win2k3。

  2. 试过域管理员,不起作用。

  3. 代码片段:

    Procedure ExecConsoleApp(CommandLine: ansistring; Output, Errors: TStringList);
      var
        sa: TSECURITYATTRIBUTES;
        si: TSTARTUPINFO;
        pi: TPROCESSINFORMATION;
        hPipeOutputRead: THANDLE;
        hPipeOutputWrite: THANDLE;
        hPipeErrorsRead: THANDLE;
        hPipeErrorsWrite: THANDLE;
        Res, bTest: boolean;
        env: array[0..100] of char;
        szBuffer: array[0..256] of char;
        dwNumberOfBytesRead: DWORD;
        Stream: TMemoryStream;
      begin
        sa.nLength := sizeof(sa);
        sa.bInheritHandle := True;
        sa.lpSecurityDescriptor := nil;
        CreatePipe(hPipeOutputRead, hPipeOutputWrite, @sa, 0);
        CreatePipe(hPipeErrorsRead, hPipeErrorsWrite, @sa, 0);
        ZeroMemory(@env, SizeOf(env));
        ZeroMemory(@si, SizeOf(si));
        ZeroMemory(@pi, SizeOf(pi));
        si.cb := SizeOf(si);
        si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
        si.wShowWindow := SW_HIDE;
        si.hStdInput := 0;
        si.hStdOutput := hPipeOutputWrite;
        si.hStdError := hPipeErrorsWrite;
    
      (* Remember that if you want to execute an app with no parameters you nil the
         second parameter and use the first, you can also leave it as is with no
         problems.                                                                 *)
        Res := CreateProcess(nil, PChar(CommandLine), nil, nil, True,
        CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, @env, nil, si, pi);
    
    
        // Procedure will exit if CreateProcess fail
        if not Res then
        begin
          CloseHandle(hPipeOutputRead);
          CloseHandle(hPipeOutputWrite);
          CloseHandle(hPipeErrorsRead);
          CloseHandle(hPipeErrorsWrite);
          Exit;
        end;
        CloseHandle(hPipeOutputWrite);
        CloseHandle(hPipeErrorsWrite);
    
        //Read output pipe
        Stream := TMemoryStream.Create;
        try
          while True do
          begin
            bTest := ReadFile(hPipeOutputRead, szBuffer, 256, dwNumberOfBytesRead, nil);
            if not bTest then
            begin
              break;
            end;
            OemToAnsi(szBuffer, szBuffer);
            Stream.Write(szBuffer, dwNumberOfBytesRead);
          end;
          Stream.Position := 0;
          Output.LoadFromStream(Stream);
        finally
          Stream.Free;
        end;
    
        //Read error pipe
        Stream := TMemoryStream.Create;
        try
          while True do
          begin
            bTest := ReadFile(hPipeErrorsRead, szBuffer, 256, dwNumberOfBytesRead, nil);
            if not bTest then
            begin
              break;
            end;
            OemToAnsi(szBuffer, szBuffer);
            Stream.Write(szBuffer, dwNumberOfBytesRead);
          end;
          Stream.Position := 0;
          Errors.LoadFromStream(Stream);
        finally
          Stream.Free;
        end;
    
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hProcess);
        CloseHandle(hPipeOutputRead);
        CloseHandle(hPipeErrorsRead);
      end;
    
    
      cmdstring :=
        'cmd.exe /c "' + GetSystemPath(WindRoot) + 'system32\netsh.exe interface ' +
        ip + ' delete address "' + NetworkInterfaceName + '" ' + VirtualFailoverIPAddress + '"';
    
      logstr('cmdstring: ' + cmdstring);
      ExecConsoleApp(cmdstring, OutP, ErrorP);
    
      if OutP.Text <> '' then
      begin
        logstr('Delete IP Result: ' + OutP.Text);
      end
      else
      begin
        logstr('Delete IP Error: ' + ErrorP.Text);
      end;
    
  4. 尝试直接运行 netsh.exe 而不是“cmd.exe /c C:\Windows\system32\netsh.exe...”,得到同样的“系统找不到指定的文件”。错误。我还偶然发现,如果我发出错误的 netsh 命令,netsh 实际上会返回一个错误,例如

netsh interface ip delete address "LocalArea Connection" 10.40.201.65

netsh interface ip 删除地址“LocalArea Connection” 10.40.201.65

Invalid interface LocalArea Connection specified.

指定的接口本地连接无效。

The following is returned if i correct the typo "LocalArea" to "Local Area". netsh interface ip delete address "Local Area Connection" 10.40.201.65

如果我将错字“LocalArea”更正为“Local Area”,则返回以下内容。netsh 接口 ip 删除地址“本地连接” 10.40.201.65

The system cannot find the file specified.

该系统找不到指定的文件。

Again, I must repeat that the same command works perfectly fine if I issue it via Command Prompt instead of from my application.

同样,我必须重申,如果我通过命令提示符而不是从我的应用程序发出相同的命令,则它可以正常工作。

回答by Jens Mühlenhoff

Have you tried this?

你试过这个吗?

if not CreateProcess(PChar('C:\Windows\system32\netsh.exe'), PChar(Arguments), ...) then
begin
  // Do somehting with `GetLastError`
end;

Of course it would be better to detect the path of C:\Windows\system32at runtime as this could be on another driver or in another directory.

当然,最好C:\Windows\system32在运行时检测路径,因为这可能位于另一个驱动程序或另一个目录中。

When you run it this way you can get an error message from Windows using the GetLastErrorcall right after CreateProcess.

当您以这种方式运行它时,您可以GetLastError在 CreateProcess 之后立即使用调用从 Windows 获取错误消息。

The ExecConsoleAppprocedure is flawed, because it doesn't return the GetLastErroror even any indication that CreateProcessfailed.

ExecConsoleApp过程是有缺陷的,因为它没有返回失败的GetLastError指示,甚至没有返回任何指示CreateProcess

You should fix this first. Maybe add raise EExecConsoleAppCreateProcessFailed.Create(SysErrorMessage(GetLastError))before Exitto the code.

你应该先解决这个问题。也许在代码中添加raise EExecConsoleAppCreateProcessFailed.Create(SysErrorMessage(GetLastError))之前Exit

You shouldn't use cmd.exe /cas a prefix. It's redundant and it makes error diagnostics more difficult. GetLastErrormight not reflect the correct error code, because you're delegating the creation of the acutal netsh.exeprocess to cmd.

您不应该cmd.exe /c用作前缀。它是多余的,并且使错误诊断更加困难。GetLastError可能不会反映正确的错误代码,因为您将实际netsh.exe过程的创建委托给cmd.

回答by Harry Johnston

The "cannot find the file specified" error may also occur if an implicitly loaded DLL required by the executable is not available. In this situation, that is the most likely cause - some essential DLL is not being found when netsh.exe is being run in a non-interactive context.

如果可执行文件所需的隐式加载的 DLL 不可用,也可能会出现“找不到指定的文件”错误。在这种情况下,这是最可能的原因 - 当 netsh.exe 在非交互式上下文中运行时,未找到某些重要的 DLL。

Use Process Monitor (available for download from Microsoft's web site) to record the file system operations that are taking place during the attempt. Look for file not found errors either in the context of your service process or in the context of the netsh.exe process.

使用进程监视器(可从 Microsoft 网站下载)记录尝试期间发生的文件系统操作。在您的服务进程上下文或 netsh.exe 进程上下文中查找文件未找到错误。