2010-01-16 21 views
11

Ich schrieb die folgenden zwei Funktionen, und rufen Sie die zweite ("CallAndWait") von JavaScript in Windows Script Host ausgeführt. Meine allgemeine Absicht ist es, ein Befehlszeilenprogramm von einem anderen aufzurufen. Das heißt, ich führe das anfängliche Scripting mit cscript aus und versuche dann, etwas anderes (Ant) von diesem Skript auszuführen.Erfassen der Ausgabe von WshShell.Exec mit Windows Script Host

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

} 

Leider, wenn ich mein Programm ausführen, bekomme ich kein sofortiges Feedback über das, was das aufgerufene Programm tut. Stattdessen scheint die Ausgabe in An und Ab zu kommen, manchmal wartet sie, bis das ursprüngliche Programm beendet ist, und manchmal scheint es, dass sie festgefahren ist. Was ich wirklich machen möchte, ist, dass der erzeugte Prozess tatsächlich den gleichen StdOut wie der aufrufende Prozess teilt, aber ich sehe keinen Weg, dies zu tun. Nur Einstellung oExec.StdOut = WScript.StdOut funktioniert nicht.

Gibt es eine alternative Möglichkeit, Prozesse zu spawnen, die die StdOut & StdErr des Startprozesses teilen? Ich habe versucht, mit "WshShell.Run()", aber das gibt mir eine "Erlaubnis verweigert" -Fehler. Das ist problematisch, weil ich meine Kunden nicht sagen müssen, um zu ändern, wie ihre Windows-Umgebung konfiguriert ist, nur um mein Programm auszuführen.

Was kann ich tun?

+0

Ich glaube nicht, dass es eine Möglichkeit, dies zu tun ist, ich habe für eine Weile selbst gesucht. Meine "Lösung" ist eine Funktion, die einen Befehl vorsetzt, der bei jedem Aufruf eines Befehls die erforderlichen Umgebungsvariablen setzt, wie in WShell.Exec ("% COMSPEC% setenvvars.bat & actual_program.exe"). Ziemlich klobig. – Roel

Antwort

1

Ja, die Exec-Funktion scheint gebrochen zu werden, wenn es um Terminal-Ausgabe kommt.

ich habe eine ähnliche Funktion function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());}, die ich in einer Schleife aufrufen ähnlich wie bei Ihnen Nicht sicher, ob die Überprüfung auf EOF und das Lesen von Zeile für Zeile besser oder schlechter ist

+0

Danke, ich habe das gleiche Problem. Dieser Ansatz scheint der einzige zu sein, der funktioniert. – Roel

2

Zuerst ist Ihre Schleife in t unterbrochen Hat es immer versucht, zuerst von oExec.StdOut zu lesen. Wenn es keine tatsächliche Ausgabe gibt, wird es hängen bleiben, bis es ist. Sie werden keine StdErr Ausgabe sehen, bis StdOut.atEndOfStream wahr wird (wahrscheinlich wenn das Kind beendet wird). Leider gibt es in der Skript-Engine kein Konzept für nicht blockierende E/A. Das bedeutet, dass Sie read aufrufen und sofort zurückkehren müssen, wenn sich keine Daten im Puffer befinden. Daher gibt es wahrscheinlich keine Möglichkeit, diese Schleife wie gewünscht zu arbeiten. Zweitens bietet WShell.Run keine Eigenschaften oder Methoden für den Zugriff auf die Standard-E/A des untergeordneten Prozesses. Er erstellt das untergeordnete Element in einem separaten Fenster, das bis auf den Rückkehrcode vollständig vom übergeordneten Element isoliert ist. Wenn Sie jedoch nur die Ausgabe des untergeordneten Elements sehen können, ist dies möglicherweise akzeptabel. Sie können auch mit dem Kind interagieren (Eingabe), aber nur durch das neue Fenster (siehe SendKeys).

Wie für die Verwendung von ReadAll(), wäre dies noch schlimmer, da es alle Eingaben aus dem Stream vor der Rückkehr sammelt, so dass Sie überhaupt nichts sehen würden, bis der Stream geschlossen wurde. Ich habe keine Ahnung, warum die example die ReadAll in einer Schleife platziert, die eine Zeichenfolge erstellt, sollte eine einzige if (!WScript.StdIn.AtEndOfStream) ausreichen, um Ausnahmen zu vermeiden.

Eine andere Alternative könnte die Verwendung der Prozesserzeugungsmethoden in WMI sein. Wie die Standard-E/A gehandhabt wird, ist nicht klar und es scheint keine Möglichkeit zu bestehen, bestimmte Ströme als StdIn/Out/Err zuzuteilen. Die einzige Hoffnung wäre, dass das Kind diese vom Elternteil erbt, aber das ist es was du willst, oder? (Dieser Kommentar basiert auf einer Idee und ein wenig Forschung, aber keine tatsächlichen Tests.)

Grundsätzlich ist das Skript-System nicht für komplizierte Interprozesskommunikation/Synchronisation ausgelegt.

Hinweis: Tests, die die obigen Angaben bestätigen, wurden unter Windows XP Sp2 mit der Skriptversion 5.6 durchgeführt. Bezug auf Strom (5.8) Handbücher schlägt keine Änderung vor.

3

Eine andere Technik, die in dieser Situation hilfreich sein kann, besteht darin, den Standardfehlerdatenstrom des Befehls zur Standardausgabe umzuleiten. Tun Sie dies, indem Sie "% comspec%/c" an den Anfang und "2> & 1" an das Ende der execStr-Zeichenfolge hinzufügen. Das heißt, der Befehl man von Lauf ändern:

zzz 

zu:

%comspec% /c zzz 2>&1 

Die "2> & 1" ist ein Redirect Befehl, der die StdErr Ausgang (Dateideskriptor 2) bewirkt, daß in den StdOut-Stream geschrieben (Dateideskriptor 1). Sie müssen den Teil "% comspec%/c" einbeziehen, da dies der Befehlsinterpreter ist, der die Befehlszeilenumleitung versteht. Siehe http://technet.microsoft.com/en-us/library/ee156605.aspx
Die Verwendung von "% comspec%" anstelle von "cmd" bietet Portabilität für eine größere Anzahl von Windows-Versionen. Wenn Ihr Befehl Zeichenfolgenargumente in Anführungszeichen enthält, kann es schwierig sein, sie richtig zu machen: Die Spezifikation dafür, wie cmd behandelt Anführungszeichen nach "/ c" scheint unvollständig zu sein.

Damit muss Ihr Skript nur den StdOut-Stream lesen und erhält sowohl Standardausgabe als auch Standardfehler. Ich habe dies mit "net stop wuauserv" verwendet, die nach Erfolg auf stdOut schreibt (wenn der Dienst läuft) und StdErr bei Fehler (wenn der Dienst bereits gestoppt ist).

+0

Siehe meinen Kommentar zu Bens Antwort über die Umleitung und die Verwendung von Anführungszeichen für die Argumente (kann durchgeführt werden, wenn die ausführbare Datei keine Anführungszeichen hat) –

11

Sie können auf diese Weise nicht von StdErr und StdOut in der Skript-Engine lesen, da es keine nicht blockierende IO wie Code Master Bob sagt. Wenn der aufgerufene Prozess den Puffer (etwa 4KB) auf StdErr ausfüllt, während Sie versuchen, von StdOut zu lesen, oder umgekehrt, dann werden Sie Deadlock/Hang. Sie werden verhungern, während Sie auf StdOut warten, und es wird Sie blockieren, wenn Sie darauf warten, dass Sie von StdErr lesen.

Die praktische Lösung ist StdErr zu StdOut wie folgt umgeleitet werden:

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

Mit anderen Worten, was zu Createprocess übergeben wird, ist dies:

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

Das ruft cmd.exe, die interpretiert die Befehlszeile. /S /C ruft eine spezielle Parsing-Regel auf, so dass das erste und das letzte Zitat abgezogen werden und der Rest wie er ist und von CMD.EXE ausgeführt wird. So CMD.EXE führt dies:

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

Die Beschwörung 2>&1 leitet prog.exe ‚s StdErr zu StdOut. CMD.EXE wird den Exit-Code propagieren.

Sie können jetzt erfolgreich sein, indem Sie von StdOut lesen und StdErr ignorieren.

Der Nachteil ist, dass die Ausgänge StdErr und StdOut zusammengemischt werden. Solange sie erkennbar sind, können Sie wahrscheinlich damit arbeiten.

1

Möglicherweise haben Sie das Deadlock-Problem, das auf dieser Microsoft Support-Site beschrieben wird, gefunden.

Ein Vorschlag ist, immer beide von stdout und stderr zu lesen. Sie könnten readAllFromAny zu ändern:

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; 
} 
Verwandte Themen