Kommunikation mit Konsolenanwendungen in VB .NET

Veröffentlicht: 26. Jan 2003 | Aktualisiert: 18. Aug 2005

Von Mathias Schiffer

Mithilfe des .NET Frameworks können Sie auf die Kommunikationskanäle "Standard Input", "Standard Output" und "Standard Error" einer gestarteten Konsolenanwendung zugreifen, die für Eingaben, Ausgaben und Fehlermeldungen verwendet werden.

Beim Start einer Konsolenanwendung über ein Process-Objekt der Frameworkklasse System.Diagnostics.Process können Sie in der StartInfo-Struktur des Objekts diverse Angaben dazu machen, auf welche Weise die Fremdanwendung gestartet werden soll (vgl. MSDN Quickie "Prozesse starten, überwachen und beenden mit VB .NET").

Hier können Sie auch angeben, ob die von Konsolenanwendungen für Eingaben, Ausgaben und Fehlermeldungen genutzten Kommunikationskanäle "Standard Input", "Standard Output" und "Standard Error" umgeleitet werden sollen: Setzen Sie die StartInfo-Eigenschaften RedirectStandardInput, RedirectStandardOutput und RedirectStandardError hierfür einfach auf True. Zusätzlich ist erforderlich, die StartInfo-Eigenschaft UseShellExecute auf False zu setzen, da die Umleitung der Kommunikationskanäle zwingend auf die Verwendung der API-Funktion CreateProcess zum Start einer Anwendung angewiesen ist (Process.Start verwendet je nach Bedarf CreateProcess oder ShellExecute, um eine Anwendung oder eine Datei in einer zugehörigen Anwendung zu starten).

Als Beispiel für eine Kommandozeilenanwendung soll hier der Befehlszeileninterpreter verwendet werden (COMMAND.COM bzw. CMD.EXE), dessen Aufrufpfad sich über die Umgebungsvariable COMSPEC mithilfe der Environ-Funktion ermitteln lässt. Ziel der beispielhaften Kommunikation mit dieser Konsolenanwendung soll sein, Dateien und Verzeichnisse des Datenträgers C:\ zu ermitteln: Nachdem der Befehlszeileninterpreter gestartet wurde, soll also der Befehl "DIR C:\*.*" ausgeführt werden. Die daraufhin normalerweise im Konsolenfenster erfolgende Ausgabe soll von Visual Basic .NET in einer Stringvariablen entgegengenommen werden.

' Ein neues Process-Objekt erzeugen 
Dim Anwendung As System.Diagnostics.Process _ 
  = New System.Diagnostics.Process() 
' Die StartInfo-Struktur des Process- 
' Objekts mit Informationen befüllen 
With Anwendung.StartInfo 
  ' Befehlszeileninterpreter (COMMAND.COM/CMD.EXE): 
  .FileName = Environ("COMSPEC") 
  ' Kein Konsolenfenster erzeugen: 
  .CreateNoWindow = True 
  ' StandardInput, -Output und -Error umleiten: 
  .RedirectStandardInput = True 
  .RedirectStandardOutput = True 
  .RedirectStandardError = True 
  ' UseShellExecute MUSS für eine Umleitung 
  ' von StdIn, StdOut und StdErr FALSE sein: 
  .UseShellExecute = False 
End With 
' Den Befehlszeileninterpreter starten: 
Anwendung.Start()

Der Befehlszeileninterpreter wurde nun mit umgeleiteten Standard-Kommunikationskanälen gestartet. Um lesenden bzw. schreibenden Zugriff auf diese Kanäle zu erhalten, benötigen Sie für den "Standard Input" ein StreamWriter-Objekt. Die Kanäle "Standard Output" und "Standard Error" können Sie mit einem StreamReader-Objekt auslesen.

' StreamReader- und Writer-Objekte für den 
' Zugriff auf StdIn, StdOut und StdErr erzeugen 
' Über ein StreamWriter-Objekt in StandardInput schreiben 
Dim StdIn As System.IO.StreamWriter = Anwendung.StandardInput 
StdIn.AutoFlush = True ' Puffer automatisch flushen 
' Über ein StreamReader-Objekt aus StandardOutput lesen 
Dim StdOut As System.IO.StreamReader = Anwendung.StandardOutput 
' Über ein StreamReader-Objekt aus StandardError lesen 
Dim StdErr As System.IO.StreamReader = Anwendung.StandardError

Nun können Sie die StreamReader-Methoden Write oder WriteLine verwenden, um Eingaben in die Konsolenanwendung vorzunehmen. Die Ausgaben der Kanäle können Sie auf Basis der erzeugten StreamWriter-Objekte über deren Funktionen Read, ReadLine oder ReadToEnd empfangen. Über die Funktion Peek können Sie ermitteln, wie viele Zeichen aktuell zur Abholung bereit liegen (bei leerem Puffer returniert Peek den Wert -1).

Beispielhaft sollen zunächst die ersten drei Zeilen ausgelesen (und damit aus dem "Standard Output"-Puffer entfernt) werden, die beim Öffnen eines Befehlszeileninterpreter-Konsolenfensters von Windows eingefügt werden:

Console1

Ausgabe nach Start des Befehlszeileninterpreters

Dim sTemp As String ' Hilfsvariable 
' Standardausgabe beim Starten des Befehlszeileninterpreters 
' auslesen und damit aus dem Output-Puffer entfernen: 
sTemp &= StdOut.ReadLine() & vbNewLine 
sTemp &= StdOut.ReadLine() & vbNewLine 
sTemp &= StdOut.ReadLine() & vbNewLine 
MessageBox.Show(sTemp, "Befehlszeileninterpreter")

Console2

Ergebnis des Auslesevorgangs in der MessageBox

Die Ausgabe des DIR-Befehls an den Interpreter erfolgt über die Methode WriteLine des StreamWriter-Objekts über den Kanal "Standard Input". Zuvor wird im Beispiel - aus rein kosmetischen Gründen - auf gleichem Wege ein ECHO OFF gesendet, um die Ausgabe des Arbeitspfades in der Befehlszeile zu unterdrücken:

' Echo-Ausgaben unterdrücken
StdIn.WriteLine("ECHO OFF")
  
' Einen DIR-Befehl über StdIn absetzen:
StdIn.WriteLine("DIR C:\*.*")
  
' Einen EXIT-Befehl über StdIn absetzen:
StdIn.WriteLine("EXIT")
 
' "ECHO OFF" und Befehlszeile "DIR C:\*.*"
' beim Auslesen ignorieren:
sTemp = StdOut.ReadLine()
sTemp = StdOut.ReadLine()

Der Befehlszeileninterpreter listet nun die Datei- und Verzeichnisübersicht für das angefragte Verzeichnis auf, die er stückweise in den Kanal "Standard Output" schreibt. Um das Ende dieser Ausgabe nicht programmiertechnisch ermitteln zu müssen, kann über StdIn bereits jetzt der Befehl EXIT gesendet werden, um die Konsolenanwendung zu schließen: Vor Abarbeitung dieses Befehls ist die Ausgabe des Kommandos DIR komplett abgeschlossen worden.

Mithilfe der Funktion ReadToEnd kann nun über das StreamReader-Objekt StdOut die Information aus dem Kanal "Standard Output" abgeholt werden, bevor alle Kanäle mit der Close-Anweisung wieder geschlossen werden.

' Konsolenausgabe auslesen: 
Dim sOutput As String  ' Konsolen-Ausgaben 
sOutput = StdOut.ReadToEnd 
' Streams schließen 
StdIn.Close() 
StdOut.Close() 
StdErr.Close()

Durch das Auslesen des Streams bis zu seinem aktuellen Ende ist auch die übergebene Anweisung EXIT noch Teil der in sOutput ausgelesenen Informationen. Dieser Teil kann nun noch entfernt werden. Weiterhin ist für Ausgabezwecke eine Umsetzung der ASCII-Ausgabe des Konsolenfensters in den Ansi-Zeichensatz vorzunehmen: Hier hilft die Klasse System.Text.Encoding durch eine Umwandlung in die Ansi-Codepage (Nr. 850).

' Abschließendes 'vbNewLine & "EXIT"' entfernen: 
sOutput = Strings.Left(sOutput, _ 
 sOutput.IndexOf(vbNewLine & "EXIT")) 
' Umwandlung der ASCII-Zeichen in Ansi-Codepage (Nr. 850): 
sOutput = System.Text.Encoding.GetEncoding(850).GetString( _ 
 System.Text.Encoding.Default.GetBytes(sOutput)) 
' Ausgabe des Ergebnisses: 
MessageBox.Show(sOutput, "Ausgabe der Konsolenanwendung")

Als letzter Schritt empfiehlt es sich sicherzustellen, dass die Konsolenanwendung durch den Befehl EXIT auch tatsächlich beendet wurde. Hierfür kann die HasExited-Eigenschaft des Process-Objekts herangezogen werden, um ggf. über seine Kill-Methode der Anwendung den sicheren Garaus zu machen. Abschließend wird nur noch das Process-Objekt geschlossen.

' Befehlszeileninterpreter ggf. "abschießen": 
If Not Anwendung.HasExited Then 
  Anwendung.Kill() 
End If 
' Ressourcen des Process-Objekts freigeben 
Anwendung.Close()