Захват вывода из WshShell.Exec с помощью Windows Script Host - PullRequest
11 голосов
/ 16 января 2010

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

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 только для запуска моей программы.

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

Ответы [ 5 ]

11 голосов
/ 30 января 2012

Вы не можете таким образом читать из StdErr и StdOut в обработчике сценариев, так как нет неблокирующего ввода-вывода, как говорит мастер кода Боб. Если вызываемый процесс заполняет буфер (около 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 вызывает специальное правило синтаксического анализа, так что первая и последняя кавычки удаляются, а оставшаяся часть используется как есть и выполняется 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 смешиваются вместе. Пока они узнаваемы, вы, вероятно, можете работать с этим.

4 голосов
/ 18 января 2012

Другой метод, который может помочь в этой ситуации, - перенаправить стандартный поток ошибок команды, чтобы сопровождать стандартный вывод. Сделайте это, добавив «% 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", кажется неполной.

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

2 голосов
/ 18 октября 2011

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

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

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

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

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

1 голос
/ 05 февраля 2016

Возможно, вы столкнулись с проблемой взаимоблокировки, описанной на этом сайте поддержки 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;
}
1 голос
/ 23 января 2010

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

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

...