Got Система не может найти указанный файл, когда я запускаю NETSH из CreateProcess, но он работает нормально в командной строке?

У меня есть служба NT, которая вызывает консольную программу, написанную на Delphi 7, назовем ее failover.exe, которая, в свою очередь, вызывает NETSH, используя найденную мной процедуру:

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

Примечание. ExecConsoleApp использует CreateProcess, полный код см. по следующей ссылке: http://www.delphisources.ru/pages/faq/base/createprocess_console.html

Я бы передал в CommandLine следующее перед вызовом ExecConsoleApp:

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

ExecConsoleApp вернет ошибку:

Система не может найти указанный файл

Но если бы я запускал его в командной строке, он работал бы отлично.

Странно то, что я помнил, что он работал с первой попытки на этом сервере 2003, но после этого он не работал независимо от того, сколько раз я пытался. В одной из попыток я также попытался назначить вход в систему как администратор для службы, но безрезультатно. Не помогает и возня с файловой безопасностью.

У меня нет сервера Win 2003 для тестирования в офисе, но я протестировал его на XP и Win7, и ExecConsoleApp работает отлично, хотя на XP мне пришлось изменить ExecConsoleApp, чтобы он выполнялся из system32\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);

Я исследовал в течение дня, но никаких подсказок, надеюсь, кто-то может помочь. Спасибо.

Дополнительные замечания -

  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 на самом деле вернет ошибку, например.

IP-адрес интерфейса netsh для удаления «Подключение по локальной сети» 10.40.201.65

Указан недопустимый интерфейс LocalArea Connection.

Следующее возвращается, если я исправлю опечатку «LocalArea» на «Локальная область». IP-адрес интерфейса netsh для удаления «Подключение по локальной сети» 10.40.201.65

Система не может найти указанный файл.

Опять же, я должен повторить, что та же команда отлично работает, если я ввожу ее через командную строку, а не из своего приложения.


person Joshua    schedule 17.11.2011    source источник
comment
Если это 64-разрядная система, существует ли c:\windows\syswow64\netsh.exe?   -  person Harry Johnston    schedule 18.11.2011
comment
@Harry: Хороший вопрос, если приложение OP является 32-битным, оно будет подвержено перенаправлению WOW64.   -  person Jens Mühlenhoff    schedule 18.11.2011
comment
@Harry: Это 32-битная Win2k3, старая машина. Я попробую без префикса cmd.exe /c, как предложил Йенс на клиентском сервере win2k3.   -  person Joshua    schedule 18.11.2011
comment
Пожалуйста, пожалуйста, покажите свой полный код. У CreateProcess много параметров. Если вы на самом деле не попытаетесь показать нам, что они из себя представляют, нам просто придется догадываться, что вы сделали неправильно. Почему вы проходите через среду? Как вы инициализировали si? Что такое WindRoot? Что такое CommandLine? Безусловно, лучше всего предоставить полное консольное приложение в виде одного файла .dpr, воспроизводящего вашу проблему. Как только вы это сделаете, я гарантирую, что мы сможем решить вашу проблему в два раза быстрее.   -  person David Heffernan    schedule 18.11.2011
comment
@ Дэвид, я добавил немного кода, подготовлю весь проект, как только смогу протестировать без префикса cmd.exe /c, предложенного Йенсом. Спасибо.   -  person Joshua    schedule 18.11.2011


Ответы (2)


Вы пробовали это?

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

Конечно, было бы лучше определить путь C:\Windows\system32 во время выполнения, так как он может находиться в другом драйвере или в другом каталоге.

Когда вы запускаете его таким образом, вы можете получить сообщение об ошибке от Windows, используя вызов GetLastError сразу после CreateProcess.

Процедура ExecConsoleApp ошибочна, потому что она не возвращает GetLastError или даже каких-либо указаний на то, что CreateProcess не удалось.

Вы должны исправить это в первую очередь. Может быть, добавить raise EExecConsoleAppCreateProcessFailed.Create(SysErrorMessage(GetLastError)) перед Exit в код.

Вы не должны использовать cmd.exe /c в качестве префикса. Это избыточно и затрудняет диагностику ошибок. GetLastError может не отражать правильный код ошибки, потому что вы делегируете создание фактического процесса netsh.exe cmd.

person Jens Mühlenhoff    schedule 17.11.2011
comment
Действительно, проблема может заключаться просто в жестко заданном пути (которого не существует на целевом компьютере). - person Ondrej Kelle; 17.11.2011
comment
@TOndrej - путь существует, потому что та же команда работает в командной строке. Я также удостоверился, что C:\Windows\system32\netsh.exe существует с помощью проводника Windows, и даже присвоил все права «Всем» для netsh.exe. Кроме того, это же приложение работает на XP. - person Joshua; 17.11.2011
comment
Ваша командная строка (обычно) запускается под вашей интерактивной учетной записью. Возможно, ваша служба работает под учетной записью с более ограниченным доступом. Обычно учетная запись локальной службы не имеет доступа к сети. - person Ondrej Kelle; 17.11.2011
comment
@TOndrej - Спасибо за быстрый ответ. Я не пробовал интерактивную учетную запись, но пытался использовать учетную запись администратора домена. Я попробую интерактивный кондиционер в следующий раз. - person Joshua; 17.11.2011
comment
@Jens Mühlenhoff - я использовал следующий код: - cmdstring := 'cmd.exe /c' + GetSystemPath(WindRoot) + 'интерфейс system32\netsh.exe' + ip + 'добавить адрес' + NetworkInterfaceName + ' ' + VirtualFailoverIPAddress + ' ' + SubNetMask + ' ' + DefaultGateway + ' ' + GatewayMetric + ''; ExecConsoleApp(cmdstring, OutP, ErrorP); - person Joshua; 17.11.2011
comment
@Jens Mühlenhoff - Когда вы запускаете его таким образом, вы можете получить сообщение об ошибке из Windows, используя вызов GetLastError сразу после CreateProcess. ›› ExecConsoleApp возвращает ошибку «Система не может найти указанный файл» codeCreateProcess(PChar('C:\Windows\system32\netsh.exe'), PChar(Arguments), ...)code ›› Я не пробовал, поскольку процедура ExecConsoleApp делает это по-другому: codeRes := CreateProcess(nil, PChar(CommandLine), nil, nil, True, CREATE_NEW_CONSOLE или NORMAL_PRIORITY_CLASS, env, nil, si, pi);code - person Joshua; 17.11.2011
comment
@Joshua: Вам следует отредактировать свой вопрос, а не публиковать длинные комментарии, поскольку их трудно читать. Я обновляю свой ответ, чтобы отразить некоторые из ваших комментариев. - person Jens Mühlenhoff; 18.11.2011
comment
@Jens - спасибо. Я пытался использовать разрыв строки ‹br/› и отступы, но, похоже, они не работают. Я попробую удалить cmd.exe /c, когда зайду на сайт клиента на следующей неделе. Хорошее предложение. - person Joshua; 18.11.2011
comment
Комментарии к SO не должны занимать несколько строк, вам рекомендуется отредактировать свой вопрос/ответ, чтобы сделать его лучше. Это тоже даст вам больше голосов ;). - person Jens Mühlenhoff; 18.11.2011
comment
Как я уже сказал, лучше всего получить наиболее точный код ошибки / сообщение об ошибке, чтобы вы могли понять, что не работает и почему :). - person Jens Mühlenhoff; 18.11.2011
comment
@Jens - я пробовал без cmd.exe /c, пожалуйста, проверьте мои комментарии в разделе «Дополнительные замечания», пункт 4. Сейчас я думаю, стоит ли исследовать возможность использования WMI API для выполнения того, что делает netsh. - person Joshua; 23.11.2011
comment
Кажется, это проблема Windows, я переключился на использование WMI API, чтобы делать то, что делает NETSH. Спасибо. - person Joshua; 07.03.2012

Ошибка «не удается найти указанный файл» также может возникнуть, если неявно загруженная DLL, необходимая для исполняемого файла, недоступна. В данной ситуации это наиболее вероятная причина — некоторые важные библиотеки DLL не найдены, когда netsh.exe запускается в неинтерактивном контексте.

Используйте Process Monitor (доступен для загрузки с веб-сайта Microsoft) для записи операций с файловой системой, происходящих во время попытки. Ищите ошибки «файл не найден» либо в контексте вашего сервисного процесса, либо в контексте процесса netsh.exe.

person Harry Johnston    schedule 19.11.2011
comment
@ Гарри, спасибо, я проверю. Кстати, знаете ли вы возможную причину того, почему библиотеки DLL не обнаруживаются при запуске в неинтерактивном контексте? Я вспомнил, что столкнулся с этим в другой системе при написании кода, поэтому я установил текущий каталог CreateProcess как «c:\windows\system32\wbem». Между тем, мне также нужно будет протестировать без cmd.exe /c - person Joshua; 19.11.2011
comment
@ Джошуа, я не уверен, почему это произошло, надеюсь, это станет яснее, когда вы определите рассматриваемую DLL (при условии, что это действительно причина вашей проблемы). Возможно, по какой-то причине переменная среды PATH не установлена ​​​​должным образом для вашего подпроцесса. - person Harry Johnston; 20.11.2011
comment
@ Джошуа, если подумать, возможно, стоит запустить cmd /c path в качестве подпроцесса вашей службы и посмотреть, что получится на выходе. - person Harry Johnston; 20.11.2011