minpic01.jpg

Dr. Tobias Weltner

Aufgedeckt

Skriptbefehle von Windows ausspionieren

Der Windows Script Host hat sich als universelles Werkzeug erwiesen, wenn es gilt, Tätigkeiten am PC zu automatisieren. Sogar manche fehlende Funktion lässt sich mit einem kleinen, selbst geschriebenen Skript leicht nachrüsten - so man weiß, welche Funktionen die installierten Windows-Komponenten bieten. Mit ein wenig Hintergrundwissen und dem richtigen Werkzeug nimmt man diese Hürde leicht.

Unterthema: Windows Script Host sicher installieren
Unterthema: Gängige Active Directory Service Interfaces
Unterthema: Listing

Skriptsprachen, wie das VBScript des Windows Script Host, besitzen von Haus aus nur sehr eingeschränkte Fähigkeiten. Bringt man Skripten aber bei, fremde Programme anzuzapfen, dann können Zehnzeiler plötzlich Benutzerkonten anlegen, Daten sichern oder sogar Texte laut vorlesen. Damit ergeben sich ganz neue Einsatzmöglichkeiten. Bislang scheiterten solche Vorhaben meist an der mangelhaften oder fehlenden Dokumentation. Kaum ein Softwarehersteller verrät, welche internen Programmfunktionen es in seinen Programmen gibt und wie sie von Skripten aus anzusprechen sind. Selbst Windows hält seine internen Funktionen geheim.

minpic02.jpg

Der Scripting Spy zeigt alle Komponenten an, auf die Skripte zugreifen können.

Deshalb setzen Skriptautoren zumeist die wenigen gut dokumentierten Skriptobjekte ein. Entsprechend bescheiden war das Einsatzfeld der Skripte bisher. Mit dem Freeware-Tool `Scripting Spy´ [1] wandelt sich das Bild. Damit lassen sich Betriebssystem und Applikationen elegant erforschen. Das Tool durchleuchtet wie ein Röntgengerät beliebige Programme und DLLs (Dynamic Link Library) und listet alle Funktionen auf, die Skripte von außen fernsteuern können.

Schleuserbande

Aber wie können Skripte überhaupt ohne weiteres auf fremde Programmfunktionen zugreifen? Oberstes Ziel der Hersteller von Office, Photoshop & Co war es sicherlich nie, fremde Programme ihre Funktionen nutzen zu lassen. Verhindern können sie es indes nicht. Grund dafür ist die Programmarchitektur: Moderne Programme sind keine monolithischen Blöcke mehr, sondern bestehen aus vielen Komponenten, die über das so genannte Component Object Model (COM) miteinander kommunizieren. Skripte klinken sich in die Kommunikation, die normalerweise zwischen den Programmmodulen abläuft, und bitten diese für ihre eigenen Zwecke um Amtshilfe. Die Programminnereien bemerken überhaupt nicht, dass ein Unbefugter anklopft.

VBScript verwendet den Befehl CreateObject, um sich eines fremden Programmmoduls zu bemächtigen. Diese Funktion will mit dem so genannten Programmatic Identifier des gewünschten Moduls gefüttert werden. Solch eine ProgID besteht in aller Regel aus zwei durch einen Punkt getrennten Begriffen wie "Scripting.FileSystemObject" oder "WScript.Network". Der erste Begriff bezeichnet die Objektfamilie, der zweite das konkrete darin befindliche Objekt.

minpic03.jpg

Die meisten DLLs verraten, wie man ihre internen Funktionen benutzen kann.

Die Skriptfunktion CreateObject schlägt daraufhin in der Windows-Registry im Schlüssel HKEY_CLASSES_ROOT die ProgID nach. Der Unterschlüssel ClsID liefert die 32-Bit-Klassen-ID des gewünschten Objekts. Alle Details zu diesem Objekt finden sich dann unter HKEY_CLASSES_ROOT\ClsId\{Klassen-ID}. Bei dieser Recherche erfährt CreateObject dann, dass das "Scripting.FileSystemObject" in der DLL `SCRRUN.DLL´ wohnt. Diese Bibliothek lädt der Scripting Host dann in seinen Objektkontext und liefert eine Referenz darauf zurück. Der Anwender merkt davon nichts, das passiert automatisch im Hintergrund, eigene Recherchen in der Registry sind unnötig. Weist man die von CreateObject erhaltene Referenz mit set einer Variablen zu, kann man die internen Funktionen des Objekts wie eine Script-Befehlserweiterung nutzen.

Helfershelfer

Was sich in der Theorie kompliziert anhört, ist in der Praxis ganz leicht. Obwohl VBScript zum Beispiel selbst keine Befehle mitbringt, um auf das lokale Dateisystem zuzugreifen, genügt ein Rückgriff auf das "Scripting.FileSystemObject", um mit Skripten Dateien zu lesen, zu schreiben oder hin und her zu kopieren. Ganz genauso funktioniert das mit all den anderen DLLs in einem Windows-System, die COM-Objekte veröffentlichen.

Eines ist klar: Ohne ein magisches Zauberbuch lässt sich dieser Wirrwarr nicht durchschauen. Woher soll man denn sonst überhaupt wissen, dass es ein Objekt namens "Scripting.FileSystemObject" gibt und welche Befehle es anzubieten hat? Welche Objekte und Befehle gibt es sonst noch?

Der Scripting Spy weiß Antwort auf diese Fragen. Das Programm analysiert die so genannten TypeLibraries, die bei der Programmentwicklung quasi als Abfallprodukt entstehen. Normalerweise bieten sie Programmierern kontextsensitive Hilfe, werden in aller Regel aber auch mit den fertigen Produkten ausgeliefert und installiert oder sind gleich in den DLLs enthalten.

Der Scripting Spy analysiert zuerst das ganze System und findet dabei alle COM-Objekte, die in der Registry vermerkt sind. Anschließend sucht das Tool nach den TypeLibraries und verbindet die gefundenen Informationen mit den COM-Objekten. Dieser Vorgang läuft direkt nach dem Programmstart ab und dauert nur wenige Minuten. Da der Spy die gefundenen Informationen speichert, muss man sich nur beim ersten Programmstart gedulden. Später lädt er seine Daten sozusagen aus der Konserve. Installiert ein Anwender neue Programme oder löscht welche, muss man die Spy-Daten mit der Funktion `Alles neu erkennen´ aktualisieren.

Das Ergebnis seiner Suche präsentiert der Scripting Spy auf der Registerkarte `Komponenten´. Hier finden sich alle COM-Objekte, die möglicherweise automatisierbar sind. Weil manche DLLs und Module gleich mehrere COM-Objekte beherbergen, zeigt die Liste nicht nur Einzelobjekte, sondern auch Ordner. Öffnet man zum Beispiel den Ordner `Scripting´, erkennt man, dass die DLL `scrrun.dll´ nicht nur das bereits erwähnte "Scripting.FileSystemObject" beherbergt, sondern noch "Scripting.Dictionary" und "Scripting.Encoder".

Im rechten Fensterbereich verrät der Spy allerhand nützliche Details über die gerade ausgewählte Komponente, etwa den Namen der DLL und die entscheidenden Schlüssel in der Registry. Wer auf eigene Faust weiterforschen möchte, braucht nur auf eine der gelb unterlegten Kategorien zu klicken. Ein Klick auf ProgID öffnet zum Beispiel den Registrierungseditor und markiert automatisch den angegebenen Schlüssel. So kann man seine eigenen Forschungen direkt in der Registry fortsetzen. Aber Vorsicht: Man sollte tunlichst von jeglichen manuellen Änderungen an dieser sensiblen Datenbank Abstand nehmen. Zu schnell bringt man sonst Windows aus dem Tritt. Ein Klick auf `Server´ öffnet das Verzeichnis im Windows Explorer, in dem die DLL liegt. Drückt man auf den Rechtspfeil, wird die Datei sogar zuvorkommend markiert.

In der Komponentenliste bewertet der Spy den potenziellen Nutzwert eines COM-Objekts farbig: Grün markierte Objekte sind interessant und verdienen eine ausführlichere Untersuchung. Wird die Option `vorselektiert´ gewählt, dann bereinigt der Spy die Liste automatisch und zeigt nur noch grüne Objekte an. Rote Objekte lassen sich nicht über CreateObject anlegen, sind also in aller Regel nutzlos. Diesen Objekten fehlt schlicht der ProgID-Eintrag in der Registry. Gelbe Objekte liefern keine von Skripten nutzbaren Funktionen und sind deshalb ebenfalls weitgehend uninteressant.

Reduziert man die Komponentenliste auf `Anwendungen´, dann zeigt sie nur noch EXE-Dateien an, die in einem eigenen Prozess laufen. Auf diese Art ist es ein Leichtes, die ProgID einer automatisierbaren Applikation wie WinWord ("Word.Application") oder Corel Photopaint ("Corel.Photopaint") herauszufinden.

Autopsie

Richtig interessant wird es, wenn man mit dem Spy in Komponenten hineinschaut. Dazu genügt es, eine solche in der Auswahlliste zu doppelklicken. Das Tool wechselt dann automatisch auf die Registerkarte `Innenleben´, analysiert das DLL-TypeLibrary-Duo und stellt die gefundenen Informationen in zwei Listen dar. In der oberen Liste führt es alle Objekte der DLL auf, in der darunter liegenden Liste die Funktionen und Befehle des ausgewählten Objekts. Mehr Informationen braucht man nicht, um Funktionen aus fremden DLLs in eigenen Skripten einzusetzen. Der Scripting Spy liefert sie frei Haus.

Zu den entdeckten Funktionen verrät der Scripting Spy außerdem eine kurze Funktionsbeschreibung - eine enorme Arbeitserleichterung für Skriptentwickler. Ist für die DLL eine Hilfe-Datei vermerkt, dann sieht man am rechten Rand zusätzlich die Schaltfläche `Hilfe´, über die sich kontextsensitive Hilfe direkt zum ausgewählten Befehl aufrufen lässt - vorausgesetzt, die Hilfe-Datei ist tatsächlich installiert.

minpic04.jpg

Viele DLL-Entwickler liefern Hilfe-Dateien für Skriptentwickler mit.

Leider fehlen diese Dateien häufig und sind nur in Developer Editionen von Windows enthalten oder beim Microsoft Developer Network (MSDN) erhältlich. Der Spy nennt in solchen Fällen zumindest den Namen der Hilfe-Datei, zum Beispiel `VBENLR98.CHM´ im Falle des "Scripting.FileSystemObject", sodass man diese Datei im lokalen Dateisystem und im Internet suchen kann. Kann die Datei aufgespürt werden, dann genügt es, sie in den Hilfe-Ordner innerhalb des Windows-Ordners zu kopieren, also nach %WINDIR%\Help. Wichtig ist, bei CHM-Dateien auch die korrespondierenden CHI-Indexdateien mitzukopieren. Erst danach steht die kontextsensitive Hilfe im Scripting Spy zur Verfügung.

Man kann nicht nur DLLs aus der Liste auswählen. Ebenso gut lassen sich DLL-Namen auch direkt ins Feld `TypeLibrary´ eintragen, zum Beispiel `VBSCRIPT.DLL´ (enthält die VBScript-Befehle) oder `WSCRIPT.EXE´ (die speziellen Befehle des Windows Script Host, die über das Objekt `wscript´ immer und ohne vorheriges CreateObject nutzbar sind). Allerdings funktioniert dieser Weg nur, wenn die ausgewählte DLL auch TypeLibrary-Informationen enthält. Leider fehlen bei vielen nicht-automatisierbaren DLLs diese Daten.

Objekte verstehen

Für Programmieranfänger mag die objektorientierte Arbeitsweise von Skripten zu Beginn etwas schwierig zu durchschauen sein. Im Grunde funktioniert der objektorientierte Ansatz aber wie ein Baukastensystem, und es kommt darauf an, die richtigen Bausteine aneinander zu reihen. Greift ein Skript über CreateObject und die ProgID auf eine Komponente zu, dann ist das Resultat ein Verweis auf dieses Objekt. Den Namen des erhaltenen Objekts ermittelt die Funktion TypeName: Im Falle von "Scripting.FileSystemObject" heißt das Objekt "FileSystemObject" (siehe `typename.vbs´).

Möchte man sich über die Funktionen eines Objekt informieren, dann genügt es, diese im Innenleben-Register auszusuchen. Jedes Objekt besteht mindestens aus einem Interface, das erscheint, wenn man auf das Plus-Symbol vor dem Objekt klickt. Beim "Scripting.FileSystemObject" heißt es in der neuesten WSH-Version `IFileSystem3´ und liefert die gewünschten Funktionen.

Alle Funktionen eines Objekts lassen sich über die Referenz ansprechen, die CreateObject liefert: An die Referenz muss man lediglich einen Punkt und dann den Namen der gewünschten Funktion anfügen. Das Beispiel `dateilister.vbs´ verdeutlicht diese Technik.

Viele Komponenten bestehen meist aus mehr als nur einem Objekt. "Scripting.FileSystemObject" zum Beispiel repräsentiert einen Ordner mit Hilfe des Objekts Folder. Dieses Objekt besitzt keine ProgID und lässt sich also auch nicht über CreateObject anlegen. Diese so genannten Binnenobjekte sind in aller Regel das Resultat von Funktionen, die über das Basisobjekt ansprechbar sind. So liefert GetFolder das Folder-Objekt zurück. Möchte man sehen, welche Informationen das Folder-Objekt einem Ordner entlocken kann, dann schlägt man im Innenleben-Register in der oberen Liste das Folder-Objekt nach. Wieder findet sich ein Interface, das in diesem Fall `IFolder´ heißt.

So wird schnell klar, dass die Size-Funktion die Gesamtgröße eines Ordners errechnen kann. Das Beispiel `ordnergroesse.vbs´ zeigt, wie diese Funktion angesprochen wird. Die Größenberechnung kann durchaus einige Augenblicke dauern. Das Skript verdeutlicht auch, dass Objektreferenzen immer mit set einer Variablen zugewiesen werden müssen, normale Werte dagegen nicht. Wie man dank objektorientierter Schreibweise dieses Beispiel anders formulieren kann - wenn auch nicht übersichtlicher -, zeigt das Skript `ordnergroessekurz.vbs´.

Dateien filtern

Eine besondere Rolle in VBScript spielen so genannte Collections: Das sind Sammlungen, die eine variable Anzahl an Objekten aufnehmen können. Beispielsweise liefert die Funktion Files des Folder-Objekts eine Collection (`IFileCollection´) als Ergebnis, die alle Dateien enthält, die in einem Ordner lagern. Jede Datei wird in dieser Liste über ein File-Objekt repräsentiert, und wenn man sich das Fileobjekt und sein IFile-Interface anschaut, entdeckt man, wie viel Informationen über Dateien verfügbar sind. Collections lassen sich mit VBScript elegant über eine for-each...next-Schleife auslesen. `kleinedateienfinden.vbs´ zeigt, wie man mit Hilfe einer Collection eine Liste aller Dateien anlegen kann, die kleiner als 10 KByte sind.

Das Beispiel öffnet nicht irgendeinen Ordner, sondern über die Funktion GetSpecialFolder den offiziellen Windows-Ordner. Die Kennzahl dafür lautet 0. Die Kennzahl 1 öffnet den System-Ordner und könnte so wie in `dllversionen.vbs´ dazu dienen, die Versionen von DLL-Dateien zu protokollieren. Über die Kennzahl 2 erhält man Zugriff auf das TEMP-Verzeichnis. Das Skript `tempreiniger.vbs´ nutzt dies und entfernt alle temporären Dateien, die älter als fünf Tage sind. Die Altersgrenze kann man leicht an die eigenen Bedürfnisse anpassen.

Weil das automatisierte Löschen von Dateien immer heikel ist, setzt das Skript eine Sicherung ein und reinigt den Temp-Ordner nur, wenn der Ordnername das Wort `Temp´ enthält. Es ist leicht vorstellbar, wie schnell dieses nützliche Skript Schaden anrichtet, wenn die Kennziffer des GetSpecialFolder-Befehls versehentlich auf 0 gesetzt und so der Windows-Ordner pulverisiert wird. Zusätzlich ist die Zeile mit dem eigentlichen Löschbefehl (datei.delete) auskommentiert. Schalten Sie diese Zeile erst nach ausgiebiger Prüfung von Hand durch Entfernen des `REM´-Befehls scharf. Automatisierte Löschaktionen sind nützlich, aber extrem riskant und setzen immer ein sorgfältigst geprüftes Skript voraus.

Abenteuerland

Der Hintereingang, durch den Skripte ans Innenleben eines COM-Objekts gelangen, ist immer das IDispatch-Interface, also die ProgID, die der Scripting Spy in seinem Komponenten-Register zeigt und die man an CreateObject verfüttert. Diese Hintertür führt immer nur zu den Grundfunktionen eines Objekts. Unterobjekte wie File stellt die DLL nur über Funktionen bereit, sie lassen sich nicht direkt via CreateObject ansprechen.

Wie greift man dann aber auf eine Datei zu, etwa um sie umzubenennen? Diese Frage kann der Scripting Spy ebenfalls beantworten: Dazu wählt man das gewünschte Objekt (IFile) in der oberen Liste des Innenleben-Registers aus. Ein Klick auf die Schaltfläche `Referenzen´ erstellt eine Liste mit den Befehlen, die dieses Objekt zur Verfügung stellt. So liefert die Grundfunktion GetFile aus dem FileSystemObject direkt ein Dateiobjekt zurück. Doppelklickt man auf GetFile, dann schaltet der Spy sofort zu diesem Befehl um. Die Funktion erfordert nur den Namen der gewünschten Datei. `umbenennen.vbs´ zeigt, wie es geht. Um ein Skript fehlertolerant zu machen, empfiehlt es sich mittels "FileSystemObject.FileExist" zu überprüfen, ob die gewünschte Datei überhaupt existiert, ehe man auf sie zugreift (umbenennen2.vbs).

Das, was das "Scripting.FileSystemObject" als Spezialist für das Dateisystem leistet, erledigen andere COM-Objekte auf ihrem jeweiligen Spezialgebiet. Die randvoll gefüllte Komponentenliste lädt zum Experimentieren ein. Dabei stößt man unweigerlich immer wieder auf versteckte Juwelen. So versteckt sich in Windows 2000 eine komplette (leider englischsprachige) Sprachausgabe. `Labertasche.vbs´ zeigt, wie Windows 2000 sich mit Ihnen unterhalten kann.

An der Hand

Zum Entwickeln von Skripten benötigt man keine komplizierte Entwicklungsumgebung. Ein beliebiger Texteditor, etwa das zu Windows gehörende Notepad, genügt vollauf. Auf kontextsensitive Hilfestellung bei der Programmierung muss man dann aber verzichten. Deshalb enthält der Scripting Spy auf dem Register `Skript erstellen´ einen einfachen Skripteditor, der mehr kann, als nur Text zu erfassen.

Gibt man hier in der ersten Zeile das COM-Objekt an, mit dem man arbeiten will, dann leistet der Spy dafür Hilfestellung. Vielleicht möchte man den Inhalt einer Datei wie `BOOT.INI´, `AUTOEXEC.BAT´ oder `CONFIG.SYS´ auslesen, weiß aber gerade nicht, wie das funktionieren könnte. Die Vermutung liegt nahe, dass das "Scripting.FileSystemObject" die nötigen Funktionen mitbringt. Also gibt man als erste Skriptzeile

set fs = CreateObject(:"Scripting.FileSystemObject:")
ein. Jetzt kann der Spy helfen: Geben Sie ins Quicksearch-Feld `open´ ein, und Sie erhalten eine Liste mit Funktionen aus dem "Scripting.FileSystemObject", die das Stichwort enthalten. OpenTextFile klingt viel versprechend. Klickt man diese Funktion an, dann bietet der Spy an, den Befehl ins Skript zu integrieren - nicht nur die Funktionssyntax, sondern auch alle notwendigen Konstanten. Wird nun noch der Name der gewünschten Datei eingefügt, dann ist das Skript bereits fertig (siehe `dateilesen.vbs´). Über die Schaltfläche `Ausführen´ startet der Scripting Spy das Skript auch gleich. Für größere Projekte bietet sich der kommerzielle Editor `Primalscript´ [2] an.

minpic05.jpg

Im Windows Script Debugger lassen sich Skripte schrittweise ausführen - ideal für die Fehlersuche.

Wie bei der Entwicklung in einer traditionellen Programmiersprache, etwa C++, sollte man auch Skripte vor ihrem Einsatz sorgfältig auf Fehler überprüfen. Hat man den Windows Script Debugger installiert (bei Windows 2000 über Systemsteuerung - Software - Windows-Komponenten hinzufügen/entfernen, sonst Download unter [3]), dann lassen sich Skripte übersichtlich Schritt für Schritt ausführen und genau beobachten. Im Scripting Spy klickt man dazu nicht auf `Ausführen´, sondern auf `Debuggen´. Ein schreibgeschütztes Codefenster zeigt das aktuelle Skript an. Mit F8 kann man sich Schritt für Schritt durch das Skript arbeiten. Über `Ansicht - Befehlsfenster´ öffnet man ein Zusatzfenster, in dem man zu jedem Zeitpunkt Variableninhalte abfragen oder Unterfunktionen aufrufen kann. Ist der Debugger nicht (richtig) installiert, dann startet die Schaltfläche `Debuggen´ wie `Ausführen´ nur das Skript.

Anwendungen fernsteuern

Fast alle modernen Anwendungen lassen sich wegen ihres COM-Modells per Skript fernsteuern. Welche Anwendungen solche Möglichkeiten bieten, enthüllt der Scripting Spy auf seinem Komponenten-Register, indem man die Option `Anwendungen´ aktiviert. Komponenten, die von einem eigenständigen Programm ausgeführt werden, bezeichnet man im Fachjargon als LocalServer.

Microsoft Word lässt sich beispielsweise gut von Skripten missbrauchen, um flexible Ausgabefenster zu erhalten, etwa für Zwischenergebnisse. Das Skript `wordausgabe.vbs´ zeigt, wie man die Liste der Netzwerkdrucker und Netzlaufwerke direkt in ein Word-Dokument schreibt. Die in diesem Skript definierten Prozeduren `Print´ und `PrintLine´ lassen sich leicht in eigenen Projekten einsetzen.

Auch komplexere Aufgaben bewältigt man so. `wordeigenedateien.vbs´ dokumentiert einen beliebigen Dateiordner samt Unterordner und verwendet dazu die Tabellenfunktion von Word und die eingebaute Sortierung.

Ein guter Ansatz zum Lernen und für eigene Skriptexperimente mit Programmen sind Makros, die man mit einer Anwendung aufnimmt. So verwendet beispielsweise Word für Makros die Skriptsprache Visual Basic for Applications (VBA), die VBScript sehr ähnelt. Auch Corel Photopaint setzt für Makros auf diesen Dialekt. Das nützliche Beispiel `grafikkonverter.vbs´ wandelt alle BMP-Bilder eines Verzeichnisses sowohl ins TIFF- als auch ins JPEG-Format um. Besonders auffällig bei diesem Skript ist der umständliche Aufruf der Funktionen FileOpen und ImageConver des Objekts "CorelPhotoPaint.Automation". Leider gibt es bei VBScript keine einheitliche Syntax für Befehle. Die Entwickler von COM-Objekten haben hierfür völlig freie Hand, weshalb manchmal die Benutzerfreundlichkeit auf der Strecke bleibt.

Benutzerkonten verwalten

Besonders mächtige Möglichkeiten bietet das Windows Scripting, seit sich über Systemkomponenten wie Active Directory Service Interfaces (ADSI) und Windows Management Instrumentation (WMI) leistungsfähige interne Windows-Verwaltungsfunktionen ansprechen lassen. Da ADSI und WMI - sofern installiert - als Teil des Betriebssystems stets laufen, kommt hier nicht CreateObject zum Einsatz, sondern GetObject.

minpic06.jpg

Per VBScript lassen sich sogar Benutzerkonten und ihr Alter übersichtlich in Excel dokumentieren.

ADSI ist fester Bestandteil von Windows 2000 und lässt sich bei allen anderen 32-Bit-Windows-Systemen kostenlos über [4] nachrüsten. Das Beispiel `adsistart.vbs´ zeigt, ob ADSI auf Ihrem Rechner einsatzbereit ist, und welche Informationsquellen zur Verfügung stehen.

Möchte man zum Beispiel alle Benutzerkonten eines NT- oder Windows-2000-Systems ermitteln, um herauszufinden, wie lange Kennwörter bereits in Benutzung sind, und wann sich Benutzer zum letzten Mal angemeldet haben, dann ist `benutzerkonten.vbs´ eine Lösung. Das erweiterte Beispiel `exceladsi.vbs´ zeigt, wie man die ermittelten Kontendaten übersichtlich in eine Excel-Tabelle ausgibt.

Der Scripting Spy zeigt auf dem ADSI-Register alle Eigenschaften und Methoden des Directory Service an. Dazu scannt das Programm das ADSI-Objektmodell. Bei Windows-2000-Domänencontrollern mit Active Directory kann die LDAP:-Untersuchung sehr lange dauern, lässt sich aber abbrechen. Zu den Objekten des "WinNT"-Providers gehört das User-Objekt, das unter anderem den LastLogin bereithält - eine Information, die Windows über die Bedienoberfläche nicht verrät. Die zu Grunde liegenden COM-Interfaces macht der Spy ebenfalls sichtbar (Schaltfläche `Standard-Interface anzeigen´). Hier wird schnell deutlich, dass man über ADSI sogar Konten anlegen, löschen und Kennwörter ändern kann. Vielfältige Beispielskripte finden sich in [5].

minpic07.jpg

Per Windows Management Instrumentation (WMI) erfragen Skripte Informationen über die angeschlossene Rechnerhardware.

WMI erteilt dem Skripter ultimative Kontrolle über das Betriebssystem. Fast alles lässt sich damit inventarisieren und fernsteuern. Einstiegspunkt ist der so genannte WMI-Dienst "winmgmts". Über ihn verbinden sich Skripte mit WMI und können dann Abfragen starten. Wie man alle Details über die in einem Rechner vorhandenen Grafikkarten per WMI erfahren kann, zeigt das Beispiel `grafikkarte.vbs´.

Es fragt die Klasse `win32_videocontroller´ mit der SQL-ähnlichen Sprache WQL ab. Das Ergebnis enthält alle Details rund um die Grafikkarte. Die undokumentierte Funktion getObjectText_ liefert alle Informationen, die das Ergebnis enthält, auf einen Schlag. Um eine bestimmte Teilinformation zu erhalten, zum Beispiel die aktuelle Auflösung, ersetzt man getObjectText_ durch den Namen der gewünschten Eigenschaft (etwa VideoModeDescription).

WMI hält solch eine Fülle an Klassen und Informationen bereit, dass es eine Weile dauert, bis man sich damit vertraut gemacht hat. Die `Suchen´-Funktion des Scripting Spy kann dabei helfen: Alle Klassen rund um Prozesse offenbart eine Suche nach `process´ mit den Optionen `Modul´, `enthaltend´ und `in der WMI´. So findet man beispielsweise auch die Klasse `Win32_Processor´, die alle wichtigen Informationen rund um die CPU kennt, etwa Taktfrequenz und Cache-Größe. Mit geringen Änderungen analysiert das Grafikkartenskript den Prozessor (`prozessor.vbs´).

minpic08.jpg

Der Spy dokumentiert die Struktur des Active Directory Service Interfaces sowie Details zu den Objekten.

Ein Skript kann per WMI nicht nur Informationen erfragen, sondern sogar handfest selbst ins Geschehen eingreifen. `notepadkiller.vbs´ ermittelt alle laufenden Prozesse und beendet selektiv alle Instanzen von Notepad. Der Prozess-Herauswurf geschieht allerdings nach der Holzhammer-Methode, eine Sicherheitsabfrage spart sich WMI. Ungesicherte Daten gehen dabei verloren, die sonst obligatorische Nachfrage `Aktuelle Änderungen speichern?´ bleibt aus.

Eine Suche mit dem Stichwort `network´ nach WMI-Modulen fördert ebenfalls reiche Beute zu Tage. Die Klasse `Win32_NetworkAdapterConfiguration´ enthält unzählige Methoden, um skriptgesteuert IP-Adressen zu ändern oder DHCP ein- und auszuschalten. Diese ausgefeilte Netzwerkkonfiguration funktioniert allerdings erst in Windows 2000 vollständig. WMI gehört bei Windows 2000 und Windows ME zum Lieferumfang. Bei allen übrigen Windows-Versionen lässt es sich kostenlos nachrüsten [5, 6].

Beinahe unbemerkt hat sich das Windows Scripting zu einem mächtigen Werkzeug entwickelt. Mit der Möglichkeit, interne Programmfunktionen fernzusteuern oder via ADSI und WMI direkt ins System einzugreifen, lassen sich beinahe alle Aufgaben automatisieren. Die fehlende Dokumentation der vorhandenen Funktionen liefert der Scripting Spy frei Haus. Er ist sozusagen die Taschenlampe des Höhlenforschers. (adb)

Literatur
[1] Scripting Spy 2.1, www.heise.de/ct/ftp/01/13/204

[2] Frank Groth, Prima skripten, c't 8/2001, S. 84, www.sapien.com

[3] Windows Script Debugger, http://msdn.microsoft.com/scripting/

[4] Active Directory Service Interfaces (ADSI) Nachrüstpack: http://www.microsoft.com/adsi

[5] Weltner, T., `Scripting für Administratoren´, MS Press, ISBN 3-86063-633-2

[6] Windows Management Instrumentation (WMI) SDK, http://msdn.microsoft.com/code/sample.asp?url=/msdn-files/027/001/566/msdncompositedoc.xml

[7] Windows Script Host, www.microsoft.com/msdownload/vbscript/scripting.asp?id=11

Kasten 1


Windows Script Host sicher installieren

Der Windows Script Host (WSH), früher auch als Sripting Host bezeichnet, ist bei Windows ME und Windows 2000 bereits Teil des Betriebssystems. Bei älteren Windows-Versionen fehlt er entweder oder liegt nur in einer alten Fassung bei. Mit dem WSH-Installer von Microsofts Webseite [7] bringt man die aktuelle Variante auf den Rechner, ganz gleich, ob zuvor schon ein WSH installiert war oder nicht. Bei Windows NT und Windows 95 muss zuvor außerdem mindestens der Internet Explorer 4.01 installiert sein.

Teil des WSH sind die Skriptsprachen VBScript (eine Untermenge von VisualBasic) und das in Webdesigner-Kreisen bekannte JScript (JavaScript). Während JavaScript auf HTML-Seiten den Standard darstellt, ist das bei serverseitigen Skripten nicht der Fall. Hier dominiert VBScript, und nicht nur, weil es leichter zu erlernen ist. Erfahrungen, die man mit VBScript sammelt, lassen sich fast unverändert auch in Visual Basic nutzen. Über VBScript lernt man also schnell und mit verwertbaren Praxisergebnissen eine Programmiersprache, mit der später auch `richtige´ EXE-Programme geschrieben werden können.

Damit der WSH Skriptdateien tatsächlich ausführt, ist es wichtig, dass der Dateiname auf die richtige Dateiextension (`.vbs´) endet. Aufgepasst: Texteditoren hängen häufig ungefragt noch ein `.txt´ an den eingegebenen Dateinamen an. Hat man im Windows Explorer die Dateierweiterungen über die Ordneroptionen ausgeblendet, merkt man dies nicht sofort.

Wie gefährlich ist der Windows Script Host?

Alle Funktionen, die der WSH für Sie ausführt, sind nahtlos in das Windows-Sicherheitskonzept eingebettet, das heißt, Skripte dürfen nur das, wozu auch ein Anwender berechtigt ist. Im Falle von Windows 9x heißt dies allerdings, dass ein Skript alles darf, also beträchtlichen Schaden anrichten kann. Problematisch wird es, wenn man unbekannte Skripte (etwa per E-Mail-Anhang erhaltene) unüberlegt ausführt. Dies sollte man ebenso wie bei EXE-Dateien vermeiden. Erst unter Windows NT, 2000 und dem kommenden XP gebieten Benutzerrechte Skripten Einhalt. Ohne Administratorrechte kann man bei diesen Systemen mit Skripten keine verbotenen Dinge tun.

In der Version 5.6 unterstützt der Windows Script Host signierte Skripte. Damit sind Änderungen an Skripten ausgeschlossen und ihre Herkunft kann eindeutig bestimmt werden. Das macht es leicht, Freund von Feind zu unterscheiden. Bis dahin sollte man darauf verzichten, Skripte aus E-Mail-Anhängen auszuführen.

Kasten 2


Gängige Active Directory Service Interfaces

ADSI-Provider Bedeutung
ADs:oberste Hierarchieebene liefert alle Provider
WinNT:Zugriff auf NT-Benutzerkonten, Dienste, Freigaben
LDAP:Zugriff auf LDAP:-Verzeichnisse wie Exchange und Windows 2000 Active Directory
NDS:Zugriff auf Novell Directory Service
NWCOMPAT:Zugriff auf NetWare 3.0
IIS:Zugriff auf den Internet Information Server Webserver

Kasten 3


Listing

Alle Listings finden Sie auf dem Heise-Server zum Download.

rem (C) Tobias Weltner, c't 13/2001

rem Skript "tempreiniger.vbs"
rem Löscht temporäre Dateien
set fs = CreateObject("Scripting.FileSystemObject")
set tempfolder = fs.GetSpecialFolder(2)

rem Sicherheitsabfrage
if Instr(lcase(tempfolder.name), "temp")=0 then
  MsgBox "Ihr Temp-Ordner hat einen untypischen Namen: " & tempfolder.name
  WScript.Quit
end if

for each datei in tempfolder.files
  alter = DateDiff("d", datei.DateCreated, date)
  if alter>5 then
    reinigung = reinigung + datei.size
    anzahl = anzahl + 1
    rem datei.delete
  end if
next

gewinn = FormatNumber(reinigung/1024^2,1) & " MB."
MsgBox anzahl & " Dateien älter als 5 Tage gelöscht, Speichergewinn " & _
              gewinn


rem Skript "grafikkonverter.vbs"
rem Wandelt mit Corel PhotoPaint BMP-Dateien
rem ins TIF- und JPG-Format um
Set cp = CreateObject("CorelPhotoPaint.Automation.7")
Set fs = CreateObject("Scripting.FileSystemObject")
Set wshshell = CreateObject("WScript.Shell")

protokoll = ConvertAllBMPinFolder("%WINDIR%", "C:\KONVERTIERUNGEN")

wshshell.Run "notepad.exe """ & protokoll & """"
cp.SetVisible True

Function ConvertAllBMPinFolder(path, newfolder)
   Path = wshshell.ExpandEnvironmentStrings(Path)
   newfolder = wshshell.ExpandEnvironmentStrings(newfolder)
   
   rem wenn Quellordner nicht existiert, Fehler auslösen:
   If Not fs.FolderExists(Path) Then
      Err.Raise 10000, "Ordner existiert nicht", Path & " nicht gefunden!"
   End If
   
   rem wenn Zielordner nicht existiert, anlegen:
   If Not fs.FolderExists(newfolder) Then fs.CreateFolder newfolder
   
   rem Protokolldatei anlegen
   protokoll = newfolder & "\protokoll.txt"
   Set logbuch = fs.CreateTextFile(protokoll, True)
   
   rem Zugriff auf Quellordner:
   Set folder = fs.GetFolder(Path)
   rem alle Dateien im Ordner auflisten:
   For each file in folder.Files
      rem Dateiextension in Kleinbuchstaben:
      ext = LCase(fs.GetExtensionName(file.Name))
      
      rem ist die Datei ein BMP-Bild?
      If ext = "bmp" Then
         zielname = newfolder & "\" & fs.GetBaseName(file.Name)
         logbuch.WriteLine ConvertToTIF(file.Path, zielname & ".tif")
         logbuch.WriteLine ConvertToJPG(file.Path, zielname & ".jpg")
      End If
   Next
   
   logbuch.Close
   ConvertAllBMPinFolder = protokoll
End Function

Function ConvertToTIF(path, newpath)
   with cp
   .FileOpen Path, 0, 0, 0, 0, 0, 1, 1
   .ImageConvert 2, 1, 0, 0, 0, 0, 0, 0
   .FileSave newpath, 772, 1
   
   .FileClose
   
   ConvertToTIF = Path & " wurde nach " & newpath & " konvertiert."
End with
End Function

Function ConvertToJPG(path, newpath)
   with cp
   .FileOpen Path, 0, 0, 0, 0, 0, 1, 1
   
   If .GetDocumentType<>3 Then
      .FileSave newpath, 774, 0
      ConvertToJPG = Path & " wurde nach " & newpath & " konvertiert."
   Else
      ConvertToJPG = "WARNUNG: " & Path &_  
      " konnte nicht konvertiert werden (S/W-Bild)."
   End If
   .FileClose
   End with
End Function

rem Skript "benutzerkonten.vbs"
rem Ermittelt alle Kennwörter eines Rechners
rem Name des Computers ermitteln:
Set net = CreateObject("WScript.Network")
ComputerName = 
     InputBox("Bitte geben Sie den Computernamen ein!",,net.ComputerName)

rem Verbindung aufnehmen
On Error Resume Next
Set computer = GetObject("WinNT://" & ComputerName & ",computer")
If Err.number<>0 Then
   MsgBox "Keine Verbindung zum Konto möglich/ADSI nicht installiert."
   WScript.Quit
End If
On Error Goto 0

rem nur Benutzerkonten anzeigen
computer.Filter = Array("User")

For each user in computer
   On Error Resume Next
   rem Alter des Kontos ermitteln:
   alter = user.Get("PasswordAge")
   If Err.number = 0 Then
     rem Zeitangabe ist in Sekunden, in Tage verwandeln
      tage = Fix(alter/60/60/24)
   Else
      tage = "[neu, unbenutzt]"
   End If
   err.clear
   rem Letzte Benutzung herausfinden
   alter = user.Get("LastLogin")
   If Err.number = 0 Then
      rem DateDiff ermittelt den Unterschied in Tagen zwischen zwei Daten
      rem now liefert die aktuelle Systemzeit
      benutzung = alter & " (vor " & DateDiff("d", alter, now) & " Tagen)."
   Else
      benutzung = "[noch nie benutzt]"
   End If

   On Error Goto 0
   
   list = list & user.Name & " Alter: " & tage & _
          " Tage, zuletzt benutzt: " & benutzung & vbCr
Next

WScript.Echo list


rem Skript "exceladsi.vbs
rem Gibt alle Kennwörter eines Rechners übersichtlich 
rem in einer Excel-Tabelle aus
Set fs = CreateObject("Scripting.FileSystemObject")
Set wshshell = CreateObject("WScript.Shell")
Set net = CreateObject("WScript.Network")

rem welcher Computer soll untersucht werden?
computer = net.ComputerName
computer = InputBox("Bitte geben Sie den Computernamen an!",,computer)

Set obj = GetObject("WinNT://" & computer & ",computer")

rem Excel Spreadsheet vorbereiten
Set objXL = CreateObject("Excel.Application")
objXL.Visible = True
objXL.Workbooks.Add
objXL.Columns(1).ColumnWidth = 30
objXL.Columns(2).ColumnWidth = 20
objXL.Columns(3).ColumnWidth = 50

objXL.Cells(1, 1).Value = "Kontoname"
objXL.Cells(1, 2).Value = "Kennwortalter"
objXL.Cells(1, 3).Value = "Letzte Benutzung"

objXL.Range("A1:C1").Select
objXL.Selection.Font.Bold = True
objXL.Selection.Interior.ColorIndex = 1
objXL.Selection.Interior.Pattern = 1      'lSolid
objXL.Selection.Font.ColorIndex = 2

rem Daten beginnen in zweiter Zeile (erste Zeile ist Überschrift)
intIndex = 2

rem nur Gruppen-Objekte anzeigen
obj.Filter = Array("User")

For each benutzer in obj
   GetInfo(benutzer)
Next

MsgBox "Fertig.",vbInformation + vbSystemModal

Sub GetInfo(user)
   On Error Resume Next
   rem Alter des Kontos ermitteln:
   alter = user.Get("PasswordAge")
   If Err.number = 0 Then
      rem Zeitangabe ist in Sekunden, in Tage verwandeln
      tage = Fix(alter/60/60/24)
   Else
      tage = "[neu, unbenutzt]"
   End If
   err.clear
   rem Letzte Benutzung herausfinden
   alter = user.Get("LastLogin")
   If Err.number = 0 Then
      rem DateDiff ermittelt den Unterschied in Tagen zwischen zwei Daten
      rem now liefert die aktuelle Systemzeit
      benutzung = alter & " (vor " & DateDiff("d", alter, now) & " Tagen)."
   Else
      benutzung = "[noch nie benutzt]"
   End If

   On Error Goto 0

   objXL.Cells(intIndex, 1).Value = user.Name
   objXL.Cells(intIndex, 2).Value = tage
   objXL.Cells(intIndex, 3).Value = benutzung
   intIndex = intIndex + 1
End Sub


rem Skript "grafikkarte.vbs"
rem Ermittelt Informationen zu allen im
rem Rechner vorhandenen Grafikkarten
rem mit WMI verbinden
on error resume next
set wmi = GetObject("winmgmts:")
if err.number<>0 then
  MsgBox "WMI nicht installiert!"
  WScript.Quit
end if
on error goto 0

rem Anfrage formulieren
wql = "select * from win32_videocontroller"

rem Absenden
set ergebnis = wmi.ExecQuery(wql)

rem Auswerten
for each grafikkarte in ergebnis
  rem alle Informationen anzeigen
  WScript.Echo grafikkarte.getObjectText_
  rem bestimmte Infos herausgreifen
  MsgBox "Aktuelle Auflösung: " & _
         grafikkarte.VideoModeDescription
next


rem Skript "adsistart.vbs"
rem Ermittelt die vorhandenen Active Directory
rem Service Interfaces (ADSI)
rem versuchen, mit ADSI eine Verbindung aufzunehmen:
On Error Resume Next
Set adsi = GetObject("ADs:")
If Not Err.number = 0 Then
   MsgBox "ADSI ist nicht einsatzbereit."
Else
   On Error Goto 0
   For each provider in adsi
      list = list & provider.Name & vbCr
   Next
   MsgBox "ADSI bietet auf diesem Computer " _
   & "die folgenden Provider:" & vbCr & list
End If


rem Skript "notepadkiller.vbs"
rem Beendet alle Instanzen von Notepad
rem mit WMI verbinden
on error resume next
set wmi = GetObject("winmgmts:")
if err.number<>0 then
  MsgBox "WMI nicht installiert!"
  WScript.Quit
end if
on error goto 0

rem Anfrage formulieren
wql = "select * from win32_process"

rem Absenden
set ergebnis = wmi.ExecQuery(wql)

rem Auswerten
for each objekt in ergebnis
  liste = liste & objekt.name & " Prozess-ID: " & objekt.processID & vbCr
next

WScript.Echo "Liste aller Prozesse:" & vbCr & liste
MsgBox "Beende nun alle Notepad-Instanzen!"

wql = "select * from win32_process where name='notepad.exe'"
set ergebnis = wmi.ExecQuery(wql)

counter = 0
for each notepad in ergebnis
  counter = counter + 1
  notepad.Terminate 0
next

MsgBox "Habe " & counter & " Notepads geschlossen!"

Zu diesem Artikel existieren Programmbeispiele
1301_204.zip

1301_204.txt