Der Umfang der Befehle, die unter Windows 2000 und XP in der immer noch häufig so genannten DOS-Box (alias Eingabeaufforderung oder Konsolenfenster) zur Verfügung stehen, hat sich zwar seit Vor-Windows-Zeiten hier und da weiterentwickelt, zu einer Revolution ist es aber - zumindest in den Augen eingefleischter .BAT-Programmierer - nicht gekommen. Oder doch? Schon seit 1996 bietet Microsoft mit dem inzwischen in der Version 5.6 verfügbaren Windows Script Host (WSH) eine erhebliche Erweiterung der Möglichkeiten an und das auch noch kostenlos. Auf neueren Windows-Varianten ist diese Software schon vorinstalliert.
Vielleicht wäre der Bekanntheitsgrad dieser Innovation unter Batch-Jongleuren größer, wenn man WSH tatsächlich als Erweiterung des DOS-Befehlssatzes verstehen könnte. Aber Berührungspunkte mit der Vorläufertechnik sind auf den ersten Blick kaum vorhanden, schon weil WSH sich nicht als klassischer Kommandointerpreter mit Prompt und blinkendem Cursor präsentiert. Auch der Befehlsvorrat ist (zum Glück) ein völlig anderer, denn man hat es nun mit respektablen Programmiersprachen und nicht mit einem Sammelsurium schlecht konzipierter Konsolenbefehle zu tun. WSH kann Programmcode interpretieren, der wahlweise in Visual Basic-Script oder in JavaScript verfasst werden kann und der zwar noch interpretierend verarbeitet wird, aber in Punkto Funktionsumfang und Komfort einen erheblichen Schritt in Richtung echte Programmiersprachen vollzieht.Ich möchte aber mit diesem Artikel keine Einführung in WSH-Programmierung geben, sondern nur eine spezielle Anwendungsmöglichkeit vorstellen und zwar den Zugriff auf InterBase/Firebird-Datenbanken aus solchen Batch-Programmen. Die Flexibilität des WSH liegt nicht allein in dem Befehlsumfang der unterstützten Programmiersprachen, sondern vor allem in der Fähigkeit, automationsfähige COM-Objekte einzubinden und anzuwenden. Damit erlangen die an sich schlichten Scriptsprachen Zugriff auf einen riesigen Vorrat an Funktionalität. Von zwei Vertretern solcher COM-Objekte möchte ich in diesem Artikel Gebrauch machen: ADO (ActiveX Data Objects) für den Datenbankzugriff und FSO (File System Objects) für das Erstellen einfacher Textdateien. Diese Kombination verrät auch schon fast das Thema meines Anwendungsbeispiels.Grenzgänger
In der Entwicklung von Datenbankanwendungen stellt sich immer wieder die Aufgabe, Informationen aus der jeweils verwendeten Datenbank und ein individuelles Textdateiformat zu exportieren, um sie dann mit anderen Programmen wieder einzulesen und weiterzuverarbeiten. Auch wenn diese Vorgehensweise vielleicht nicht zu den modernsten Techniken zählt, ist sie trotzdem Alltag bei der Integration in eine vorhandene EDV-Landschaft. Und nicht selten greifen Entwickler zur Lösung solcher eigentlich trivialer Probleme auf ihren gewohnte Hochsprachencompiler zurück und schreiben schnell ein Programm. Dateischnittstellen werden aber oft und gerne geändert und erweitert und außerdem handelt es sich bei solchen Schnittstellen auch fast immer um einen Grenzübergang in die Welt eines anderen Entwicklers, der vielleicht irgendwann selbst gerne das eine oder andere Detail an dem Datenformat verändern möchte. Deswegen ist eine offene Scriptinglösung eine schöne Alternative zur in-Stein-gemeißelten Exportanwendung. Der Quellcode ist offen lesbar, einigermaßen leicht verständlich und kann ohne Entwicklungsumgebung (außer vielleicht Notepad) geändert werden. So könnte die Vorschrift für einfache Dateischnittstelle aussehen, mit der Lieferpositionen aus einem Warenwirtschaftssystem an ein fremdes Buchhaltungssystem übergeben werden:Datum: jjjj-mm-ttIdentnummer: 10 ZeichenWarenwert: 0000000.00Kundennummer: 10 ZeichenKundenname: 30 Zeichen
select s.ship_date, s.po_number, s.total_value, s.cust_no, c.customer from sales sinner join customer c on (c.cust_no=s.cust_no)where s.order_status = 'shipped'
Wahl des Wirts
Wie kommt nun das Script an die Daten? Zunächst noch ein paar Vorbemerkungen zum Windows Script Host (WSH). Hinter dieser Bezeichnung verbergen sich genau genommen zwei Host-Anwendungen, nämlich WScript.exe und CScript.exe, die beide die Ausführung von Programmen erlauben, die in VBScript (Dateiendung .vb) oder in JScript (Microsofts Implementations von JavaScript, Dateiendung .js) geschrieben sind. WScript führt das Script im Rahmen einer Windowsanwendung aus, wobei alle Ausgaben als Dialogboxen dargestellt werden. CScript ist ein klassisches Konsolenprogramm. Der Leistungsumfang dieser beiden Hosts ist fast identisch, der Sprachumfang ist definitiv gleich, weil sich beide Programme der selben Script Engine bedienen. Geben Sie an der Kommandozeile einmal cscript ein.C:> csriptMicrosoft (R) Windows Script Host, Version 5.6...
WScript.Echo("Hallo Welt");
Treiber heißt jetzt Dienstanbieter
Da der WSH es uns ermöglicht, automationsfähige ActiveX-Objekte einzubinden, kann man auch ohne weiteres ADO (ActiveX Data Objects) von JScript aus verwenden. ADO ist die anwenderfreundliche Objektarchitektur, die als Unterbau einen sogenannten OLE DB Provider verwendet, um zum Beispiel auf eine relationale Datenbank zuzugreifen. Um mit ADO-Objekten InterBase erreichen zu können, braucht man also einen passenden OLE DB Provider. ADO und ein Standardvorrat an OLE DB Providern werden mit dem von Microsoft kostenlos angebotenen Setupprogramm MDAC_TYP.EXE [2] installiert. Die aktuelle Version von MDAC (Microsoft Data Access Components) ist 2.7 SP1. Um festzustellen, welche Version bei Ihnen installiert ist, können Sie entweder den MDAC Component Checker [2] verwenden, oder einfach gleich das neueste MDAC_TYP.EXE ausführen.Allerdings bietet Microsoft keinen OLE DB Provider für InterBase an, was auch kaum überraschen dürfte. Vor einigen Monaten habe ich vier verschiedene Provider für InterBase bzw. Firebird vorgestellt [3]. Nur einer der Kandidaten, nämlich das Produkt mit dem schlichten Namen IB OLEDB von Ralph Curry [4], ist als Freeware verfügbar. Zwar hatte gerade dieser Treiber bei dem letzten Vergleichstest ziemlich schlecht abgeschnitten, aber inzwischen ist die Versionsnummer von 1.1 auf 1.6 geklettert (Abb. 1) und der Entwickler (bzw. sein Produkt) hat offenbar deutliche Fortschritte in Punkto Zuverlässigkeit gemacht. Inzwischen ist sogar der Quellcode als Open Source verfügbar [5]. Erfahrungen bezüglich der Langzeitstabilität dieses Treibers liegen zwar noch nicht vor, sind im Batch-Betrieb aber auch nicht von so großer Bedeutung. Zumindest für unseren ziemlich unkritischen Anwendungsfall dürfte dieser OLE DB Provider völlig ausreichen.
regsvr23 iboledb.dll
var conn = new ActiveXObject("ADODB.Connection");var rs = new ActiveXObject("ADODB.Recordset");
conn.Open("Provider=IbOleDb;Location=localhost;Data Source=C:\\Programme\\Borland\\InterBase6\\Examples\\v5\\Employee.gdb;User ID=SYSDBA;Password=masterkey;Extended Properties='Character Set=ISO8859_1'");
ADO auf einfachste Art
Nachdem die Verbindung aufgebaut ist, können wir das RecordSet-Objekt verwenden, um eine Datenmenge abzurufen, was mit den folgenden Codezeilen geschieht:rs.CursorLocation = 3; //adUseClientvar sql = "select s.ship_date, s.po_number, s.total_value, s.cust_no, c.customer from sales s inner join customer c on (c.cust_no=s.cust_no) where s.order_status = 'shipped'";rs.Open(sql, conn);
while (!rs.EOF) {WScript.Echo(rs(0).Value);rs.MoveNext();}
var conn = new ActiveXObject("ADODB.Connection");var rs = new ActiveXObject("ADODB.Recordset");conn.Open("Provider=IbOleDb;Location=localhost;Data Source=C:\\Programme\\Borland\\InterBase6\\Examples\\v5\\Employee.gdb;User ID=SYSDBA;Password=masterkey;Extended Properties='Character Set=ISO8859_1'");rs.CursorLocation = 3; //adUseClientvar sql = "select s.ship_date, s.po_number, s.total_value, s.cust_no, c.customer " +"from sales s inner join customer c on (c.cust_no=s.cust_no) " +"where s.order_status = 'shipped'";rs.Open(sql, conn);while (!rs.EOF) {WScript.Echo(rs(0).Value);rs.MoveNext();}rs.Close();conn.Close();
Reine Formsache
Nun ist es an der Zeit, sich um die Formatierung der Ausgabe Gedanken zu machen. Wenn Sie das bis jetzt entstandene Script laufen lassen, dann sehen Sie, dass das Datum aus dem Feld ship_date zum Beispiel als 31.05.1993 erscheint, was - je nach Einstellung des Gebietsschema - auch variieren kann. Benötigt wird aber das Format jjjj-mm-tt. JScript bietet leider nur eine recht spartanische Ausstattung mit Formatierungsfunktionen an. Daher muss man solche Probleme selbst mit ein paar Zeile Code lösen. Hier bietet die prototype Eigenschaft der JScript-Objekte einen interessanten Lösungsansatz. JScript arbeitet sehr weitgehend objektorientiert. Auch JScript-Datentypen wie String, Date oder Number sind Objekte, die jeweils typspezifische Eigenschaften und Methoden aufweisen. Ungewöhnlich ist dabei, dass man auch für vordefinierte Objekttypen neue Methoden einführen kann, indem man eine einfache Funktion implementiert und diese der Objekteigenschaft prototype unter einem frei wählbaren, neuen Methodennamen zuweist: Ein Beispiel:function str_ralign(size, fill_ch) {var s = this;while (s.length < size) s = fill_ch + s;return s;}String.prototype.ralign = str_ralign;function date_myFormat() {var s = this.getFullYear() + "-";s += (this.getMonth() + 1).toString().ralign(2, "0") + "-";s += this.getDate().toString().ralign(2, "0");return s;}Date.prototype.myFormat = date_myFormat;
while (!rs.EOF) {var d = new Date(rs(0).Value);WScript.Echo(d.myFormat());rs.MoveNext();}
function str_ralign(size, fill_ch) {var s = this;while (s.length < size) s = fill_ch + s;return s;}String.prototype.ralign = str_ralign;function date_myFormat() {var s = this.getFullYear() + "-";s += (this.getMonth() + 1).toString().ralign(2, "0") + "-";s += this.getDate().toString().ralign(2, "0");return s;}Date.prototype.myFormat = date_myFormat;var conn = new ActiveXObject("ADODB.Connection");var rs = new ActiveXObject("ADODB.Recordset");conn.Open("Provider=IbOleDb;Location=localhost;Data Source=C:\\Programme\\Borland\\InterBase6\\Examples\\v5\\Employee.gdb;User ID=SYSDBA;Password=masterkey;Extended Properties='Character Set=ISO8859_1'");rs.CursorLocation = 3; //adUseClientvar sql = "select s.ship_date, s.po_number, s.total_value, s.cust_no, c.customer " +"from sales s inner join customer c on (c.cust_no=s.cust_no) " +"where s.order_status = 'shipped'";rs.Open(sql, conn);while (!rs.EOF) {var d = new Date(rs(0).Value);WScript.Echo(d.myFormat());rs.MoveNext();}rs.Close();conn.Close();
Spätestens jetzt sollte ich auf die Debuggingmöglichkeiten hinweisen. Wenn Sie cscript.exe mit der Option //X starten, wird das Script in einem Debugger ausgeführt, sofern ein kompatibler Debugger installiert ist. Dies kann Visual Studio sein oder aber auch der etwas schlichtere Script Debugger, den Microsoft zum freien Download anbietet [1]. Abpictureung 3 zeigt diesen Gratis-Debugger im Einsatz.
var fso = new ActiveXObject("Scripting.FileSystemObject");var ts = fso.CreateTextFile("C:\\Temp\\Test.txt", true);ts.WriteLine("Eine Zeile Text");ts.Close();
// ---------------- Neue Methoden fuer String und Number Objekte ----------------function str_lalign(size, fill_ch) {var s = this;while (s.length < size) s += fill_ch;return s;}String.prototype.lalign = str_lalign;function str_ralign(size, fill_ch) {var s = this;while (s.length < size) s = fill_ch + s;return s;}String.prototype.ralign = str_ralign;function str_quoted( ) {var s = "";for (i=0; i<this.length; i++) {s += this.charAt(i);if (this.charAt(i) == "'") s += "'";}return "'" + s + "'";}String.prototype.quoted = str_quoted;function num_myFormat(size) {var s;if (this >= 0) s = this.toString();else {size--;s = (-this).toString();}var i = s.indexOf(".");if (i < 0) { s = s + ".00" } else while (s.length - i < 3) s = s + "0";s = s.ralign(size, "0");if (this < 0) s = "-" + s;return s;}Number.prototype.myFormat = num_myFormat;function date_myFormat() {var s = this.getFullYear() + "-";s += (this.getMonth() + 1).toString().ralign(2, "0") + "-";s += this.getDate().toString().ralign(2, "0");return s;}Date.prototype.myFormat = date_myFormat;// ---------------- Hauptprogramm ----------------var conn = new ActiveXObject("ADODB.Connection");var rs = new ActiveXObject("ADODB.Recordset");conn.Open("Provider=IbOleDb;Location=localhost;Data Source=C:\\Programme\\Borland\\InterBase6\\Examples\\v5\\Employee.gdb;User ID=SYSDBA;Password=masterkey;Extended Properties='Character Set=ISO8859_1'");var ExportFileName = "c:\\temp\\export.dat";var fso = new ActiveXObject("Scripting.FileSystemObject");var ts = fso.CreateTextFile(ExportFileName, true);conn.BeginTrans();try {rs.CursorLocation = 3; //adUseClientvar sql = "select s.po_number, s.ship_date, s.total_value, s.cust_no, c.customer " +"from sales s inner join customer c on (c.cust_no=s.cust_no) " +"where s.order_status = 'shipped'";rs.Open(sql, conn);while (!rs.EOF) {s = rs(0).Value.toString().lalign(10, " ");ts.Write(s);d = new Date(rs(1).Value);ts.Write(d.myFormat());n = new Number(rs(2).Value);ts.Write(n.myFormat(10));s = rs(3).Value.toString().lalign(10, " ");ts.Write(s);s = rs(4).Value.toString().lalign(30, " ");ts.WriteLine(s);s = rs(0).Value.toString();conn.Execute("delete from sales where po_number=" + s.quoted());rs.MoveNext;}WScript.Echo(rs.RecordCount.toString() + " Datensätze wurden exportiert");conn.CommitTrans();rs.Close();}catch(e) {conn.RollbackTrans();WScript.Echo("Rollback wegen Exception!");ts.Close();fso.DeleteFile(ExportFileName);throw e;}conn.Close();ts.Close();
In Listing 3 sind noch ein paar Neuerungen hinzugekommen: Ich habe die Formatierungsmethoden ausgebaut, um auch die Strings und den Währungswert in das geforderte Format bringen zu können. Außerdem ist der Datenbankzugriff noch in eine explizite Transaktion (BeginTrans, CommitTrans) gekapselt, begleitet von einem try..catch-Block, der im Falle einer Exception die Datenbankänderungen mit RollbackTrans zurücknimmt und auch die evtl. schon begonnene Exportdatei wieder löscht. Da Bewegungsdaten wie Lieferpositionen i.d.R. nicht immer wieder sondern nur einmalig exportiert werden sollen, wollte ich die Datensätze eigentlich mit einem Update-Statement als bereits exportiert kennzeichnen. Leider bietet sich in der Sales Tabelle dafür kein Feld an, deshalb habe ich mich einfach dazu entschlossen, jeden exportierten Datensatz mit einer Delete-Anweisung zu löschen. Zuverlässiger kann man ein mehrfaches Exportieren zwar kaum verhindern, aber so ganz praxisgerecht ist dieses Vorgehen natürlich nicht. Mir kam es aber darauf an, ein möglichst einfaches Beispiel zu liefern, daher hoffe ich, dass Sie diesen rüden Umgang mit den Demodaten verzeihen werden. Um zu verhindern, dass die Daten tatsächlich gelöscht werden, können Sie die CommitTrans Anweisung einfach in RollbackTrans abändern. Zum Löschen der exportierten Datensätze reicht es in diesem Beispiel übrigens nicht aus, die Delete-Methode des Recordsets aufzurufen, denn die Grundlage des Recordsets pictureet ein Select, der sich per JOIN-Syntax auf mehrere Tabellen bezieht. Daher ist für ADO nicht feststellbar, welche Datensätze aus welchen Tabellen zu löschen sind, und deswegen muss das Löschen in diesem Fall von Hand vorgenommen werden.Ein Makel an dieser Lösung bleibt, dass das Datenbankpasswort im ConnectString als Klartext sichtbar ist. In dem vorliegenden Beispiel habe ich sogar gegen zwei goldene Regeln verstoßen, nämlich das SYSDBA-Kennwort auf der Voreinstellung masterkey zu belassen, und diesen Super-Benutzer überhaupt für Anwendungszwecke zu verwenden. Sinnvoll wäre es natürlich, für den Datenexport einen anderen Datenbankbenutzer einzurichten, und diesem nur die erforderlichen Zugriffsrechte zu geben. Darüber hinaus sollte der Script-Quellcode nur für autorisierte Anwender einsehbar sein.
Fazit
Die vorgestellte Technik für den Zugriff auf Datenbanken kann weder in Punkto Komfort noch Performance mit den Möglichkeiten konkurrieren, die man mit Produkten wie Delphi, C-Builder o.ä. zur Verfügung hat. Der Charme dieses Ansatzes liegt in seiner Schlichtheit und in der Tatsache, dass ein Scriptprogramm auf jedem Anwendungsrechner modifizieren kann, auch wenn keine Entwicklungsumgebung installiert ist.Links und Literatur
- [1] www.microsoft.com/germany/scripting
- [2] www.microsoft.com/data
- [3] Der Entwickler: OLE DB-Provider im Überblick, 6.2002
- [4] www.oledb.net
- [5] sourceforge.net/project/showfiles.php?group_id=72859
- [6] msdn.microsoft.com
- [7] Andreas Kosch: ADO und Delphi, Software und Support Verlag




