Захват вывода из WshShell.Exec с использованием Windows Script Host

Я написал следующие две функции и вызвал второй ( "callAndWait" ) из JavaScript, запущенного внутри Windows Script Host. Мое общее намерение - вызывать одну командную строку из другой. То есть, я запускаю начальный скриптинг с помощью cscript, а затем пытаюсь запустить что-то еще (Ant) из этого script.

function readAllFromAny(oExec)
{
     if (!oExec.StdOut.AtEndOfStream)
          return oExec.StdOut.ReadLine();

     if (!oExec.StdErr.AtEndOfStream)
          return "STDERR: " + oExec.StdErr.ReadLine();

     return -1;
}

// Execute a command line function....
function callAndWait(execStr) {
 var oExec = WshShell.Exec(execStr);
  while (oExec.Status == 0)
 {
  WScript.Sleep(100);
  var output;
  while ( (output = readAllFromAny(oExec)) != -1) {
   WScript.StdOut.WriteLine(output);
  }
 }

}

К сожалению, когда я запускаю свою программу, я не получаю немедленную обратную связь о том, что делает вызываемая программа. Вместо этого вывод, кажется, приходит в припадки и начинается, иногда до тех пор, пока оригинальная программа не закончится, а иногда, похоже, зашла в тупик. То, что я действительно хочу сделать, - это то, что порожденный процесс фактически использует тот же StdOut, что и вызывающий процесс, но я не вижу способа сделать это. Просто установка oExec.StdOut = WScript.StdOut не работает.

Есть ли альтернативный способ создания процессов, которые будут совместно использовать StdOut и StdErr процесса запуска? Я попытался использовать "WshShell.Run(), но это дает мне ошибку" отказа в разрешении ". Это проблематично, потому что я не хочу, чтобы мои клиенты изменяли, как их среда Windows настроена только для запуска моей программы.

Что я могу сделать?

Ответ 1

Вы не можете читать из StdErr и StdOut в движке script таким образом, поскольку нет неблокирующего ввода-вывода, как говорит Code Master Bob. Если вызываемый процесс заполняет буфер (около 4 КБ) на StdErr, когда вы пытаетесь прочитать из StdOut или наоборот, тогда вы будете блокировать/зависать. Вы будете голодать, ожидая StdOut, и он будет блокировать вас, чтобы вы читали StdErr.

Практическое решение - перенаправить StdErr на StdOut следующим образом:

sCommandLine = """c:\Path\To\prog.exe"" Argument1 argument2"
Dim oExec
Set oExec = WshShell.Exec("CMD /S /C "" " & sCommandLine & " 2>&1 """)

Другими словами, то, что передается CreateProcess, следующее:

CMD /S /C " "c:\Path\To\prog.exe" Argument1 argument2 2>&1 "

Это вызывает CMD.EXE, который интерпретирует командную строку. /S /C вызывает специальное правило синтаксического анализа, так что первая и последняя цитаты удаляются, а остаток используется as-is и выполняется CMD.EXE. Итак, CMD.EXE выполняет следующее:

"c:\Path\To\prog.exe" Argument1 argument2 2>&1

Заклинание 2>&1 перенаправляет prog.exe StdErr на StdOut. CMD.EXE будет распространять код выхода.

Теперь вы можете добиться успеха, прочитав StdOut и игнорируя StdErr.

Недостатком является то, что выходные данные StdErr и StdOut смешиваются. Пока они распознаются, вы, вероятно, можете работать с этим.

Ответ 2

Другим методом, который может помочь в этой ситуации, является перенаправление стандартного потока ошибок команды для сопровождения стандартного вывода. Сделайте это, добавив "% comspec%/c" в начало и "2 > & 1" в конец строки execStr. То есть, измените команду, с которой вы работаете:

zzz

в

%comspec% /c zzz 2>&1 

"2 > & 1" - это команда перенаправления, которая заставляет вывод StdErr (дескриптор файла 2) записываться в поток StdOut (дескриптор файла 1). Вам нужно включить часть% comspec%/c, потому что это интерпретатор команд, который понимает о перенаправлении командной строки. См. http://technet.microsoft.com/en-us/library/ee156605.aspx
Использование "% comspec%" вместо "cmd" обеспечивает переносимость более широкого диапазона версий Windows. Если ваша команда содержит цитированные строковые аргументы, может быть сложно получить их право: спецификация того, как cmd обрабатывает кавычки после "/c", кажется неполным.

При этом вашему script нужно только прочитать поток StdOut и получить как стандартный вывод, так и стандартную ошибку. Я использовал это с "net stop wuauserv", который пишет StdOut об успехе (если служба работает) и StdErr при сбое (если служба уже остановлена).

Ответ 3

Во-первых, ваш цикл разбит на то, что он всегда пытается сначала прочитать oExec.StdOut. Если фактического выхода нет, он будет висеть до тех пор, пока не появится. Вы не увидите какой-либо вывод StdErr до тех пор, пока StdOut.atEndOfStream не станет истинным (возможно, когда ребенок закончит). К сожалению, в блоке script нет понятия неблокирующего ввода-вывода. Это означает вызов read и его немедленное возвращение, если в буфере нет данных. Таким образом, вероятно, нет способа заставить этот цикл работать так, как вы хотите. Во-вторых, WShell.Run не предоставляет никаких свойств или методов для доступа к стандартным ввода-вывода дочернего процесса. Он создает дочерний элемент в отдельном окне, полностью изолированном от родителя, за исключением кода возврата. Однако, если все, что вы хотите, чтобы иметь возможность видеть результат от ребенка, тогда это может быть приемлемым. Вы также сможете взаимодействовать с дочерним элементом (ввод), но только через новое окно (см. SendKeys).

Что касается использования ReadAll(), это будет еще хуже, поскольку он собирает все входные данные из потока перед возвратом, чтобы вы вообще ничего не увидели до тех пор, пока поток не был закрыт. Я не знаю, почему пример помещает ReadAll в цикл, который строит строку, один if (!WScript.StdIn.AtEndOfStream) должен быть достаточным, чтобы избежать исключений.

Другой альтернативой может быть использование методов создания процесса в WMI. Как обрабатывается стандартный ввод-вывод, неясно, и, похоже, нет никакого способа выделить определенные потоки как StdIn/Out/Err. Единственная надежда была бы в том, что ребенок наследует их от родителя, но что вы хотите, не так ли? (Этот комментарий основан на идее и немного исследований, но не на самом деле.)

В принципе, скриптовая система не предназначена для сложной межпроцессной связи/синхронизации.

Примечание. Тесты, подтверждающие вышеизложенное, были выполнены в Windows XP Sp2 с использованием script версии 5.6. Ссылка на текущие (5.8) руководства не предлагает никаких изменений.

Ответ 4

Да, функция Exec, кажется, сломана, когда дело доходит до вывода терминала.

Я использую аналогичную функцию function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());}, которую я вызываю в цикле, подобном вашему. Не уверен, что проверка EOF и чтение строк за строкой лучше или хуже.

Ответ 5

Возможно, вы столкнулись с проблемой тупика, описанной на этом сайте поддержки Microsoft.

Одно из предложений - всегда читать как от stdout, так и от stderr. Вы можете изменить readAllFromAny на:

function readAllFromAny(oExec)
{
  var output = "";

  if (!oExec.StdOut.AtEndOfStream)
    output = output + oExec.StdOut.ReadLine();

  if (!oExec.StdErr.AtEndOfStream)
    output = output + "STDERR: " + oExec.StdErr.ReadLine();

  return output ? output : -1;
}