2010-01-16 11 views
11

Napisałem następujące dwie funkcje i wywołałem drugą ("callAndWait") z JavaScript działającego w Hosta skryptów systemu Windows. Moim ogólnym zamiarem jest wywołanie jednego programu linii poleceń od drugiego. To znaczy, uruchamiam początkowe skryptowanie przy użyciu cscript, a następnie próbuję uruchomić coś innego (Ant) z tego skryptu.Przechwytywanie danych wyjściowych z WshShell.Exec przy użyciu Hosta skryptów systemu Windows

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); 
    } 
} 

} 

Niestety, gdy uruchomię mój program, nie uzyskać natychmiastowe informacje zwrotne o to, co nazywa się program robi. Zamiast tego dane wyjściowe wydają się pasować i uruchamiają się, czasami czekając aż skończy się pierwotny program, a czasami wydaje się, że ma on zakleszczenie. To, co naprawdę chcę zrobić, to to, że proces odradzania faktycznie udostępnia ten sam StdOut jako proces wywołujący, ale nie widzę sposobu, aby to zrobić. Ustawienie parametru oExec.StdOut = WScript.StdOut nie działa.

Czy istnieje alternatywny sposób na odradzanie procesów, które będą udostępniać StdOut & StdErr procesu uruchamiania? Próbowałem użyć "WshShell.Run(), ale to daje mi błąd" odmowa uprawnień ". Jest to problematyczne, ponieważ nie chcę mówić moim klientom, aby zmienić sposób skonfigurowania ich środowiska Windows tylko po to, aby uruchomić mój program.

Co mogę zrobić?

+0

Nie sądzę, istnieje sposób, aby to zrobić, Szukałam przez chwilę sam. Moim "rozwiązaniem" jest posiadanie funkcji poprzedzającej polecenie, które ustawia wymagane zmienne środowiskowe za każdym razem, gdy wywoływane jest polecenie, tak jak w pliku WShell.Exec ("% COMSPEC% setenvvars.bat & actual_program.exe"). Całkiem niezgrabne. – Roel

Odpowiedz

1

Tak, funkcja Exec wydaje się być uszkodzony, jeśli chodzi o wyjście terminala.

Używam podobną funkcję function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());} że nazywam w pętli podobny do Ciebie Nie wiem czy sprawdzanie EOF i czytanie linii po linii jest lepsze czy gorsze

+0

Dzięki, mam ten sam problem. To podejście wydaje się być jedynym, które działa. – Roel

2

Po pierwsze, twoja pętla jest zepsuta wt kapelusz zawsze próbuje najpierw odczytać z oExec.StdOut. Jeśli nie ma rzeczywistego wyjścia, zawiesi się, dopóki nie pojawi się. Nie zobaczysz żadnego wyjścia o wartości StdErr do momentu, gdy StdOut.atEndOfStream stanie się prawdziwe (prawdopodobnie, gdy dziecko się zakończy). Niestety, nie ma koncepcji nie blokujących operacji we/wy w silniku skryptowym. Oznacza to wywołanie read i natychmiastowe zwrócenie go, jeśli w buforze nie ma danych. Tak więc prawdopodobnie nie ma sposobu, aby uruchomić tę pętlę, jak chcesz. Po drugie, WShell.Run nie zapewnia żadnych właściwości ani metod dostępu do standardowych operacji we/wy procesu potomnego. Tworzy dziecko w oddzielnym oknie, całkowicie odizolowane od rodzica, z wyjątkiem kodu powrotu. Jednakże, jeśli wszystko, co chcesz, to móc WIDZIĆ wyjście z dziecka, to może to być do przyjęcia. Będziesz także mógł wchodzić w interakcje z dzieckiem (input), ale tylko przez nowe okno (patrz SendKeys).

Jeśli chodzi o używanie ReadAll(), byłoby jeszcze gorzej, ponieważ zbiera wszystkie dane wejściowe ze strumienia przed powrotem, więc nie zobaczysz niczego, dopóki strumień nie zostanie zamknięty. Nie mam pojęcia, dlaczego example umieszcza ReadAll w pętli, która buduje ciąg, pojedynczy if (!WScript.StdIn.AtEndOfStream) powinny być wystarczające, aby uniknąć wyjątków.

Inną alternatywą może być użycie metod tworzenia procesów w usłudze WMI. Sposób obsługi standardowego wejścia/wyjścia nie jest jasny i nie istnieje żaden sposób przydzielania określonych strumieni jako StdIn/Out/Err. Jedyną nadzieją byłoby, że dziecko odziedziczy je po rodzicu, ale tego właśnie chcesz, prawda? (Ten komentarz oparty na pomyśle i niewielkich badaniach, ale bez faktycznego testowania.)

Zasadniczo, system skryptów nie jest przeznaczony do skomplikowanej komunikacji/synchronizacji międzyprocesowej.

Uwaga: Testy potwierdzające powyższe zostały wykonane w systemie Windows XP Sp2 przy użyciu skryptu w wersji 5.6. Odniesienie do prądu (5.8) podręczniki sugerują brak zmian.

3

Inną techniką, która może pomóc w tej sytuacji, jest przekierowanie standardowego strumienia błędów polecenia dołączonego do standardowego wyjścia. Należy to zrobić, dodając "% comspec%/c" na początku i "2> & 1" na końcu łańcucha execStr. Oznacza to, że zmiany polecenia uruchomieniu od:

zzz 

do:

%comspec% /c zzz 2>&1 

W "2> & 1" jest instrukcją przekierowanie co powoduje wyjście stderr (deskryptor pliku 2) być zapisywane do strumienia StdOut (deskryptor pliku 1). Należy dołączyć część "% comspec%/c", ponieważ jest to interpreter poleceń, który rozumie przekierowanie wiersza polecenia. Zobacz http://technet.microsoft.com/en-us/library/ee156605.aspx
Użycie "% comspec%" zamiast "cmd" daje przenośność szerszemu zakresowi wersji Windows. Jeśli twoje polecenie zawiera cytowane argumenty łańcuchowe, może to być trudne: specyfikacja tego, jak cmd obsługuje cytaty po "/ c" wydaje się być niekompletna.

Dzięki temu skrypt musi jedynie odczytać strumień StdOut, a otrzyma zarówno standardowy błąd wyjściowy, jak i standardowy. Użyłem tego z "net stop wuauserv", który zapisuje do StdOut po sukcesie (jeśli usługa jest uruchomiona) i StdErr po awarii (jeśli usługa jest już zatrzymana).

+0

Zobacz mój komentarz na temat odpowiedzi Bena na temat przekierowania i użycia cudzysłowów na argumentach (można zrobić, jeśli plik wykonywalny nie ma cytatów) –

11

Nie można odczytać z StdErr i StdOut w silniku skryptowym w ten sposób, ponieważ nie ma tam żadnych blokujących IO, jak mówi Mistrz kodu Bob. Jeśli wywoływany proces zapełni bufor (około 4 KB) na StdErr podczas próby odczytu ze StdOut lub odwrotnie, to zablokujesz/zawiesisz. Będziesz głodować podczas oczekiwania na StdOut, a to zablokuje czekanie na odczyt z StdErr.

Praktycznym rozwiązaniem jest przekierować stderr do stdout tak:

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

Innymi słowy, co zostanie przekazane do CreateProcess to:

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

To wywołuje CMD.EXE, który interpretuje wiersz poleceń. /S /C wywołuje specjalną regułę analizowania, dzięki czemu pierwsza i ostatnia wycena są usuwane, a pozostała część używana jest jako , jak to jest i wykonywana przez CMD.EXE. Więc CMD.EXE wykonuje to:

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

inkantację 2>&1 przekierowuje StdErr prog.exe „s na standardowe wyjście. CMD.EXE będzie propagować kod wyjścia.

Możesz teraz odnieść sukces, czytając ze StdOut i ignorując StdErr.

Wadą jest to, że wyjścia StdErr i StdOut mieszają się ze sobą. Dopóki są rozpoznawalne, prawdopodobnie możesz z tym pracować.

1

Być może problem dotyczy zakleszczenia opisanego w tej witrynie Microsoft Support.

Jedna z sugestii to zawsze przeczytać zarówno z stdout i stderr. Można zmienić readAllFromAny do:

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; 
} 
Powiązane problemy