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

3

7

I have an NT service that calls a console program written in Delphi 7, let's call it failover.exe that in turn calls NETSH using a procedure I found:

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

I would pass the following to CommandLine before calling ExecConsoleApp:

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

ExecConsoleApp will return an error:

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.

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

 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.

netsh interface ip delete address "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

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.

Hazy answered 17/11, 2011 at 12:7 Comment(5)
If this is a 64-bit system, does c:\windows\syswow64\netsh.exe exist?Spermine
@Harry: Good point, if OPs application is 32-Bit it will be subject to WOW64 redirection.Hadj
@Harry: It's 32 bit, Win2k3, an old machine. I'll be trying without the cmd.exe /c prefix as Jens has suggested on the client's win2k3 server.Hazy
Please please please show your full code. There are lots of parameters to CreateProcess. Unless you actually make the effort to show us what they are we simply have to guess at what you have done wrong. Why are you passing an environment? How did you initialise si? What is WindRoot? What is CommandLine? By far the best would be to supply a complete console app in the form of a single .dpr file that reproduced your problem. Once you do that I guarantee we'll be able to fix your problem in double quick time.Cumuliform
@ David, I've put up some code, will prepare the whole project once I could test without the cmd.exe /c prefix Jens has suggested. Thanks.Hazy
B
7

When running netsh interface portproxy delete v4tov4 ... I get this error if the thing I'm trying to delete does not exist. I also get this error if I try to do netsh interface portproxy delete v4tov4 all. Then only thing works for items that are present is if I specify both the port and the address: netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=80. If I just use the listenaddress or just the listenport I get the error The system cannot find the file specified. even though the item is present. Using process monitor it appears netsh.exe will pass invalid parameter to RegDeleteValue Win32 API if you use anything else but both those parameters.

Butternut answered 19/8, 2023 at 21:4 Comment(0)
T
2

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\system32 at runtime as this could be on another driver or in another directory.

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

The ExecConsoleApp procedure is flawed, because it doesn't return the GetLastError or even any indication that CreateProcess failed.

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

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

Tenderhearted answered 17/11, 2011 at 13:1 Comment(12)
Indeed, the problem might simply be the hardcoded path (which doesn't exist on the target computer).Pathogenic
@TOndrej - the path exists because the same command works on Command Prompt. I've also ensured that C:\Windows\system32\netsh.exe exists using Windows Explorer, and even assigned all rights to 'Everyone' for netsh.exe. Also, the same application works on XP.Hazy
Your Command Prompt (usually) runs under your interactive account. Your service might be running under a more restricted account. Usually, the "local service" account doesn't have access to the network.Pathogenic
@TOndrej - Thanks for your quick reply. I've not tried the interactive account but I did try using the domain administrator account. I'll try the interactive a/c the next time.Hazy
@Jens Mühlenhoff - I used the following code:- cmdstring := 'cmd.exe /c "' + GetSystemPath(WindRoot) + 'system32\netsh.exe interface ' + ip + ' add address "' + NetworkInterfaceName + '" ' + VirtualFailoverIPAddress + ' ' + SubNetMask + ' ' + DefaultGateway + ' ' + GatewayMetric + '"'; ExecConsoleApp(cmdstring, OutP, ErrorP);Hazy
@Jens Mühlenhoff - When you run it this way you can get an error message from Windows using the GetLastError call right after CreateProcess. >> ExecConsoleApp does return an error - 'The system cannot find the file specified' codeCreateProcess(PChar('C:\Windows\system32\netsh.exe'), PChar(Arguments), ...)code >> I've not tried this as Procedure ExecConsoleApp does it differently: codeRes := CreateProcess(nil, PChar(CommandLine), nil, nil, True, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, env, nil, si, pi);codeHazy
@Joshua: You should edit your question instead of posting long comments as they're hard to read. I'm updating my answer to reflect some of your comments.Hadj
@Jens - thanks. I tried to use <br/> linebreak, and indents but they don't seem to work. I'll try removing "cmd.exe /c" when I go to the client's site next week. Good suggestion.Hazy
Comments on SO are not meant to span multiple lines, you're encouraged to edit your question / answer to make it better. Will get you more votes on it too ;).Hadj
As I said it's best to get the most accurate error code / error message, so you can understand whats failing and why :).Hadj
@Jens - I've tried without cmd.exe /c, please check my comments under "Additional remarks, point 4". I'm now considering whether to explore the possibility of using WMI API to do what netsh is doing.Hazy
This seems to be a Windows problem, I've switched to using WMI API to do what NETSH does. Thanks.Hazy
S
0

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.

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.

Spermine answered 19/11, 2011 at 5:14 Comment(3)
@ Harry - thanks, let me check. By the way, do you know the possible reason as to why DLL are not found when run in non-interactive context? I remembered encountering this on another system while writing the code, that's why I set current directory of CreateProcess as 'c:\windows\system32\wbem'. Meanwhile, I'll also need to test without the cmd.exe /cHazy
@Joshua, I'm not sure why this would happen, hopefully it will be clearer once you identify the DLL in question (assuming this is in fact the cause of your problem). It might be that for some reason the PATH environment variable is not being set properly for your subprocess.Spermine
@Joshua, come to think of it, it might be worth running "cmd /c path" as a subprocess of your service and see what the output is.Spermine

© 2022 - 2024 — McMap. All rights reserved.