Rekursives VBS für das Dateisystem

[Include Datei] · [Schreibschutz entfernen] · [Tote Linke löschen] · [Dateiliste ausgeben] · [Download der Skripte]


Die folgenden Beispiele stellen Anwendungen eines Skript-Baukastens dar, der für verschiedene Aufgaben im Zusammenhang mit dem Dateisystem auf einfache Weise umfunktioniert werden kann.

Die besonderen Merkmale der folgenden Skriptbeispiele sind:

  • Rekursion: Damit die Operationen der Skripte auf alle Dateien in einem Verzeichnisbaum angewendet werden können, verfahren sie nach dem Muster "Durchsuche das Verzeichnis nach Dateien und bearbeite sie. Dann suche nach Unterverzeichnissen und rufe Dich für jedes Unterverzeichnis selbst auf". Die Skripte sind also rekursiv, die darin enthaltene Prozedur ‘DateiSystemDurchsuchen’ ruft sich jedesmal selbst auf, wenn sie in einem Verzeichnis weitere Unterverzeichnisse entdeckt.

  • Include-Dateien: Was bei großen Compilern möglich ist, geht auch bei VBS mit einem kleinen Trick: Gleichartige Programmteile werden in eine eigene Datei ausgelagert, die beim Kompilieren und/oder Aufruf geöffnet und geladen wird. So werden Skripts übersichtlicher, weil allgemeine Funktionen in einer zentralen Datei stecken. Jede Verbesserung und Fehlerbereinigung kommt so allen Skripts zu Gute, die auf die zentrale Datei zugreifen, im Skript stehen nur noch die speziellen Funktionen, welche die Besonderheit des Skripts ausmachen.
    Das funktioniert leider nicht bei allen Versionen von Windows. Daher wird hier nur der Weg beschrieben.

  • Funktionen: Die eigentlichen Befehle, die bestimmen, was das Skript nun mit einzelnen Dateien zu tun hat, wurden in einer eigene Prozedur am Ende des Skripts zusammengefasst. Diese Prozedur heißt ‘Bearbeiten’ und mit ihr wird festgelegt, was mit einer gefundenen Datei bzw. Ordner zu tun ist. Natürlich kann das ganze Skript beschleunigt werden, wenn man diese Prozedur in die Funktion ‘DateiSystemDurchsuchen’ integriert und optimal an die jeweilige Funktion des Skripts anpasst. Doch damit sinkt die Wiederverwendbarkeit und Übersichtlichkeit und Zuverlässigkeit des Codes, was wegen der beschränkten Debugging-Funktionen bei VBS doppelt schmerzt.

Die Skripte können auf mehrere Arten aufgerufen werden:

  • Drag & Drop: Man markiert im Explorer eine oder mehrere Dateien und/oder Verzeichnisse und zieht sie auf das Skript

  • Senden an: Im Ordner ‘\Windows\SendTo’ kann eine Verknüpfung zum Skript angelegt werden. Dadurch steht das Skript im Kontextmenü des Explorers unter ‘Senden an...’ zur Verfügung.
    Hinweis: Der Ordner ‘\Windows\SendTo’ ist versteckt, und kann nur angezeigt werden, wenn man im Explorer im Menü ‘Extras -> Ordneroptionen’ (je nach Windows-Version) die Registerkarte ‘Ansicht’ wählt und bei ‘Versteckte Dateien’ die Option ‘Alle Dateien anzeigen’ wählt.

  • Dialog: Falls keine Argumente übergeben wurden, öffnet das Skript beim Aufrufen ein Windows-Dialogfenster, in dem eine Datei oder ein Verzeichnis ausgewählt werden kann.

Die grünen Texte im unten angegebenen Quellcode sind Kommentare, die bei der Eingabe ausgelassen werden können. Alle Skripte wurden zum Download in der Datei wsh3.cab gespeichert.

Die Include-Datei
Option Explicit
On Error Resume Next

Dim
FS, Liste, Nr, Verzeichnis, Objekt

Set Liste    = WScript.Arguments
Set FS       = CreateObject("Scripting.FileSystemObject")


If Liste.Count > 0 Then
    ' Then wird ausgeführt, wenn aus dem Explorer Dateien
    ' mit Drag & Drop auf diese Skript-Datei gezogen wurden
    For Nr = 0 To Liste.Count - 1
        DateiSystemDurchsuchen Liste(Nr)
    Next
Else
    ' Else wird ausgeführt, wenn dieses Skript gestartet
    ' wurde, ohne dass Argumente übergeben wurden

    ' Ruft einen Dialog von Windows auf, mit dem eine
    ' Datei oder ein Ordner ausgewählt werden kann

    Set Verzeichnis = CreateObject("Shell.Application") _
        .BrowseForFolder(0, "Datei oder Verzeichnis wählen" _
        , &H4011, 17)

    ' Skript beenden, falls im Dialog die Schaltfläche
    ' Abbrechen gedrückt wurde

    If TypeName(Verzeichnis) = "Nothing" Then WScript.Quit

    Objekt = LCase(Left(TypeName(Verzeichnis.ParentFolder), 6))

    If (Objekt LIKE "folder*") Then
        For Each Objekt In Verzeichnis.ParentFolder.Items
            If Objekt.Name = Verzeichnis.Title Then Exit For
        Next
    End If

    DateiSystemDurchsuchen Objekt.Path
End If

' *** Ende des Scripts

' ---------------------------------------------------------- '
' Rekursives Unterprogramm um das Dateisystem zu durchsuchen
' ---------------------------------------------------------- '


Private Sub DateiSystemDurchsuchen(Pfad)
    Dim Ordner, UnterOrdner, Datei

    If FS.FolderExists(Pfad) Then
        ' Then: Falls Ordner übergeben wurde
        Set Ordner = FS.GetFolder(Pfad)

        ' Papierkorb nicht bearbeiten
        If LCase(Ordner.Name) = "recycled" Then Exit Sub

        ' Funktion Bearbeiten() für Ordner aufrufen
        If Not Bearbeiten(Ordner, False) Then Exit Sub

        ' Alle Dateien im Ordner bearbeiten
        For Each Datei In Ordner.Files
            ' Funktion Bearbeiten() für Dateien aufrufen
            If Not Bearbeiten(Datei, True) Then Exit For
        Next

        ' Alle Unterordner rekursiv bearbeiten
        For Each UnterOrdner In Ordner.SubFolders
            ' Einstieg In die Rekursion
            DateiSystemDurchsuchen UnterOrdner
        Next

    ElseIf FS.FileExists(Pfad) Then
        ' Else: Falls eine einzelne Datei übergeben wurde
        Bearbeiten FS.GetFile(Pfad), True
    End If
End Sub

Die Funktionen zur Dateisuche und -verwaltung werden in allen folgenden Beispielen benutzt, daher wurden sie ausgeleagert. Am Besten speichert man den folgenden Code in eine Datei namens ‘#DateiSystem.inc’. Die Datei sollte nicht als *.vbs gespeichert werden, damit sie nicht versehentlich ausgeführt wird. Die hier wiedergegebene Datei ist harmlos, es könnte jedoch sein, daß Sie irgend wann einmal eine gefährliche Include-Datei entwickeln, die nur unter der Kontrolle eines anderen Skripts laufen darf.

Diese Datei bringt im wesentlichen zwei wesentliche Funktionen mit:

  • Sie fragt ab, ob dem Skript irgend welche Startoptionen übergeben wurden - falls nicht, öffnet es ein Dialogfenster, in dem Verzeichnisse und Dateien ausgewählt werden können.

  • Es durchsucht das Dateisystem nach Deteien und Orndern und ruft eine vom Anwender konstruierte Funktion namens ‘Bearbeiten’ für jede Datei und jedes Verzeichnis auf, welche das Programm in dieser Include-Datei gefunden hat.

Der Windows-Papierkorb wird von diesem Skript automatisch von der Bearbeitung ausgeschlossen.

Damit man diese Include-Datei in einem eigenen VB-Skript nutzen kann, müssen folgende Voraussetzungen gegeben sein:

  • Im Kopf der VBS-Datei muß der Aufruf der Include-Datei stehen

  • In den folgenden Beispielen wird angenommen, daß die Include-Datei ‘#DateiSystem.inc’ heisst und im gleichen Verzeichnis wie das aufrufende VBS steht.

  • Das VBS muß eine Funktion namens ‘Bearbeiten’ mit zwei Argumenten enthalten.

Die letzten beiden Punkte können natürlich durch Anpassung des Skripts geändert werden.

Die Funktion ‘Bearbeiten’ muss in der VBS-Datei stehen und wird von der Include-Datei immer dann aufgerufen, wenn eine Datei oder ein Verzeichnis gefunden wurde. Die Funktion ‘Bearbeiten’ muss mit zwei Argumenten und einem Rückgabewert erstellt werden:

Bearbeiten(Objekt, IstDatei)

  1. Das erste Argument ‘Objekt’ entspricht dem Dateisystemobjekt der gefundenen Datei

  2. Das zweite Argument ‘IstDatei’ ist True oder False, je nach dem, ob es sich beim ersten Argument um ein Verzeichnis oder eine Datei handelt.

  3. Der Rückgabewert entscheidet, ob die Bearbeitung fortgesetzt wird. Wenn der Rückgabewert True ist, wird die Bearbeitung fortgesetzt und das Dateisystem weiter durchsucht. Wenn der Rückgabewert False ist, kommt es darauf an, ob ‘Objekt’ eine Datei oder ein Verzeichnis ist: Bei einem Verzeichnis, werden weder dessen Unterverzeichnisse noch dessen Dateien durchsucht. Bei einer Datei wird die Suche abgebrochen.

Das Verhalten von Punkt 3 ist ein wenig erklärungsbedürftig:

  • Nehmen wir an, Sie durchsuchen Laufwerk C: nach Dateien vom Typ *.WAV. Nun wollen Sie aber die Dateien im Verzeichnis ‘C:\Windows\’ und seinen Unterverzeichnissen gerade nicht suchen. Also prüft Ihre Funktion ‘Bearbeiten’, ob das übergebene Objekt ein Verzeichnis ist und auch noch ‘C:\Windows\’ heißt. Falls ja, ist der Rückgabewert von BearbeitenFalse’ und der Ordner ‘C:\Windows\’ und seine Unterordner werden nicht mehr durchsucht - alle anderen Ordner, wie z.B. ‘C:\Programme\’ oder ‘C:\Eigene Dateien\’ werden aber weiterhin durchsucht.

  • Wenn Sie nach der Datei ‘MSCREATE.DIR’ suchen wollen, um sie zu löschen, dann prüfen Sie, ob das Objekt eine Datei ist und ‘MSCREATE.DIR’ heisst. Falls ja, löschen Sie die Datei. Da keine zweite Datei namens ‘MSCREATE.DIR’ im gleichen Verzeichnis existieren kann, erhält die Funktion Bearbeiten den Rückgabewert ‘False’, so dass in dem Verzeichnis keine weitere Datei mehr bearbeitet wird - das beschleunigt die Arbeit des Skripts. Unterordner werden aber weiterhin durchsucht.

' Aktueller Pfad dieses Skripts
SkriptPfad = WScript.ScriptFullName
SkriptPfad = Left(SkriptPfad, Len(SkriptPfad) - Len(WScript.ScriptName))

' Include-Datei zur Dateisystemverwaltung laden
Execute CreateObject("Scripting.FileSystemObject") _
      .OpenTextFile(SkriptPfad & "#DateiSystem.inc").ReadAll

So ruft ein normales VBS die Include-Datei ‘#DateiSystem.inc’ auf, wenn sie im gleichen Verzeichnis wie das VBS liegt:

Private Function Bearbeiten(Objekt, IstDatei)
...
    Bearbeiten = ...
...

End Function

So muß die Funktion aufgebaut werden, die von der Include-Datei ‘#DateiSystem.inc’ mit Dateinamen und Verzeichnissen gefüttert wird:

OrdnerListe = ""

Private Function Bearbeiten(Objekt, IstDatei)
  If IstDatei Then
    Bearbeiten = False          
  Else
    OrdnerListe = OrdnerListe & Objekt.Name & vbNewLine
    Bearbeiten = True
  End If
End Function

Dies ist allerdings die Minimalausstattung. Man kann die Struktur auch ein wenig anpassen. Die folgende Funktion bearbeitet nur Ordner und sammelt deren Namen in der Variablen ‘OrdnerListe’:

Doch nun einige Anwendungsfälle für die Include-Datei:
Schreibschutz entfernen
Anzahl = 0

' -------- Include-Datei laden -------- '
' Aktueller Pfad dieses Skripts
SkriptPfad = WScript.ScriptFullName
SkriptPfad = Left(SkriptPfad, Len(SkriptPfad) _
                      - Len(WScript.ScriptName))
' Include-Datei zur Dateisystemverwaltung laden
Execute CreateObject("Scripting.FileSystemObject") _
    .OpenTextFile(SkriptPfad & "#DateiSystem.inc").ReadAll
' -------- Ende Include-Datei laden -------- '

' Zeigt 5 Sekunden lang die Anzahl der bearbeiteten Dateien
CreateObject("WScript.Shell").PopUp Anzahl &  _
    " Dateien und Ordner bearbeitet", 5, "Schreibschutz entfernen"


'*** Ende des Skripts

' Hier wird festgelegt, wie Dateien bearbeitet werden sollen.
Private Function Bearbeiten(Datei, IstDatei)
    ' Schreibschutz-Attribut einer/s Datei/Ordners löschen
    Datei.Attributes = Datei.Attributes And Not 1
    Anzahl = Anzahl + 1
    Bearbeiten = True
End Function

Wer Dateien von einer CD-ROM auf die Festplatte kopiert, der übernimmt auch das Schreibschutz-Attribut von Dateien und Ordnern. Dies ist besonders lästig, wenn man kopierte Dateien bearbeiten will, weil z.B. Datenbanken schon beim Öffnen wegen dem Schreibschutz eine Warnmeldung ausgeben.
Das folgende Skript entfernt von einem Verzeichnis, den untergeordneten Verzeichnissen und allen Dateien den Schreibschutz.

Das Skript zeigt zum Abschluss eine Meldung, die angibt, wieviele Dateien bearbeitet wurden. Diese Meldung verschwindet nach 5 Sekunden automatisch, so dass das Skript beispielsweise auch von einem Systempflegeprogramm aufgerufen werden kann, ohne daß es den Prozess des aufrufenden Skripts anhält.

Der Code And Not 1 löscht das Schreibschutz-Attribut der Datei. Ersetzt man die 1 durch andere Werte, kann man auch andere Attribute zurücksetzen:
And Not  2 = Versteckte Datei (Hidden)
And Not  4 = Systemdatei
And Not 32 = Archiv-Attribut löschen
Die Attribute kann man auch addieren: 34 = 32 + 2 setzt das Archiv- und das Schreibschutz-Attribut zurück.

Um das Schreibschutz-Attribut zu setzen vwerwendet man Or 1 statt And Not 1. Sinngemäß lauten die Werte für die anderen Attribute:
Or  2 = Versteckte Datei (Hidden)
Or  4 = Systemdatei
Or 32 = Archiv-Attribut setzen

Wie man sieht, ist die Datei dank Include angenehm kurz.

Tote Links löschen
Set WshShell = CreateObject("WScript.Shell")
Anzahl = 0

' -------- Include-Datei laden -------- '
' Aktueller Pfad dieses Skripts
SkriptPfad = WScript.ScriptFullName
SkriptPfad = Left(SkriptPfad, Len(SkriptPfad) _
                      - Len(WScript.ScriptName))
' Include-Datei zur Dateisystemverwaltung laden
Execute CreateObject("Scripting.FileSystemObject") _
    .OpenTextFile(SkriptPfad & "#DateiSystem.inc").ReadAll
' -------- Ende Include-Datei laden -------- '

WshShell.PopUp Anzahl & " Links gelöscht", 5, "Tote Links"

'*** Ende des Skripts


' Diese Prozedur prüft, ob ein Link existiert
Private Function Bearbeiten(Objekt, IstDatei)
    Bearbeiten = True  ' In jedem Falle weitermachen

    Dim Quelle, LaufWerk, Typ

    ' Funktion verlassen, wenn ein Ordner übergeben wurde
    If Not IstDatei Then Exit Function

    ' Funktion verlassen, wenn die Datei kein Link ist
    If LCase(FS.GetExtensionName(Objekt)) <> "lnk" _
        Then Exit Function

    ' Auf welche Datei/Verzeichnis zeigt der Link ?
    Quelle = WshShell.CreateShortcut(Objekt).TargetPath

    ' Funktion verlassen, wenn die Datei / Ordner, auf
    ' die der Link zeigt, existiert
    If FS.FileExists(Quelle)  Then Exit Function
    If FS.FolderExists(Quelle) Then Exit Function

    ' Laufwerksbuchstaben bestimmen
    LaufWerk = FS.GetDriveName(Quelle)

    ' Existiert wenigstens das Laufwerk ?
    If FS.DriveExists(LaufWerk) Then
        ' Laufwerkstyp bestimmen
        Typ = FS.Drives(CStr(LaufWerk)).DriveType

        ' Falls der Link auf eine Datei/Ordner auf einer
        ' lokalen Festplatte (2) oder ein Netzlaufwerk (3)
        ' verweist, dann löschen
        If (Typ = 2) Or (Typ = 3) Then
            FS.DeleteFile Objekt
            Anzahl = Anzahl + 1
        End If
    End If
End Function

Seit Windows 95 gibt es Links im Dateisystem. Auch wenn die vielen nützlichen Möglichkeiten eines Links kaum in das Bewußtsein normaler Benutzer dringt, gibt es eine Reihe von automatischen Funktionen, die Links anlegen. Gelegentlich verschwindet die Datei, auf die ein Link zeigt, und der Link wird überflüssig, manchmal sogar störend. Tote Links entstehen z.B. unter folgenden Umständen:

  • Im Startmenü unter ‘Dokumente’ wird über die zuletzt bearbeiteten Dateien Buch geführt. Wenn man eine Datei nach dem Öffnen löscht, bleibt sie im Menü Dokumente erhalten

  • Manche Programme sind nicht in der Lage, bei der Deinstallation verschobene oder umbenannte Einträge im Startmenü zu finden und zu entfernen.

Das Skript durchsucht die angegebenen Verzeichnisse auf Links. Beim Löschen wird nach folgendem Schema vorgegangen:

  • Wenn der Link auf ein Laufwerk verweist, das nicht vorhanden ist, dann wird er nicht gelöscht, denn es könnte sein, daß Sie sich gerade lokal angemeldet haben, das Netzwerk nicht verfügbar ist oder eine Festplatte nicht in ihrem Wechselrahmen steckt.

  • Wenn der Link auf einen Wechseldatenträger (CD-ROM, Floppy, DVD, Iomega ZIP ...) verweist, wird er ebenfalls nicht gelöscht, denn das Skript kann nicht feststellen, ob gerade genau das Medium im Laufwerk liegt, auf welches der Link verweist. Diese Art von Links wird z.B. gerne für Lexika auf CD-ROM im Startmenü angelegt.

Der Link wird also nur gelöscht, wenn er auf ein existierendes Netzlaufwerk oder eine Festplatte verweist und die zugehörige Datei nicht zu finden ist

Wichtig für das VBS ist, dass die Variable WshShell vor der Include-Datei geladen wird, denn die Include-Datei ruft ja die Funktion Bearbeiten auf und dann muss das Objekt WScript.Shell bereits der Variablen WshShell zugewisesn sein.

Liste aller Dateien
Set FS = CreateObject("Scripting.FileSystemObject")

' Name und Pfad der Ausgabedatei im Temp-Ordner festlegen
DateiName = FS.GetSpecialFolder(2).Path & "\" & _
          FS.GetBaseName(FS.GetTempName) & ".html"
' Ausgabedatei erstellen / öffnen
Set AusgabeDatei = FS.CreateTextFile(DateiName, True)

' Dateikopf schreiben
With AusgabeDatei
    .Write "<HTML><HEAD><TITLE>"
    .Write "Dateiliste ausgeben" ' Titelleiste
    .Write "</TITLE></HEAD><BODY><H1>"
    .Write "Dateiliste ausgeben" ' Überschrift
    .Write "</H1><PRE>"
End With

Anzahl = 0  ' Zähler: Anzahl der Dateien
Bytes  = 0  ' Zähler: Anzahl der Bytes

' -------- Include-Datei laden -------- '
' Aktueller Pfad dieses Skripts
SkriptPfad = WScript.ScriptFullName
SkriptPfad = Left(SkriptPfad, Len(SkriptPfad) _
                      - Len(WScript.ScriptName))
' Include-Datei zur Dateisystemverwaltung laden
Execute CreateObject("Scripting.FileSystemObject") _
    .OpenTextFile(SkriptPfad & "#DateiSystem.inc").ReadAll
' -------- Ende Include-Datei laden -------- '

' Ende der Ausgabedatei schreiben
With AusgabeDatei
    .Write "</PRE><P>" & Anzahl & " Dateien gefunden"
    .Write "<BR>" & FormatNumber(Bytes \ 1024, , , , True)
    .Write " kB</BODY></HTML>"
    .Close
End With

' Ausgabedatei öffnen
CreateObject("WScript.Shell").Run DateiName, True

' ... warten, bis Datei angezeigt wird
WScript.Sleep 1000

' ... und dann unauffällig wieder löschen
FS.DeleteFile DateiName, True


'*** Ende des Skripts

' Funktion, die den Inhalt der Ausgabe bestimmt
Private Function Bearbeiten(Datei, IstDatei)
    If IstDatei Then
        AusgabeDatei.WriteLine Datei.ParentFolder & "\<B>" & Datei.Name & "</B>"
        Bytes = Bytes + Datei.Size
        Anzahl = Anzahl + 1
    End If
    Bearbeiten = True
End Function

Dieses Skript gibt eine Liste aller Dateien aus, die im angegebenen Verzeichnis und seinen Unterverzeichnissen gefunden wurden. Zur Ausgabe wird der Browser benutzt, der standardmäßig die Dateien mit der Endung *.HTML öffnet.

Um die Ausgabe von Pfad und Dateiname übersichtlicher zu gestalten, wird der Dateiname bei der Ausgabe in den Browser fett hervorgehoben.

Die Include-Datei darf dabei nicht zu früh geladen werden:

Zuerst muss die Ausgabedatei erzeugt werden, dann erst darf die Include-Datei beginnen, die Dateinamen über die Funktion Bearbeiten in die Ausgabedatei zu schreiben. Interessanterweise gibt es hier auch keine Fehlermeldung, obwohl die Variable FS sowohl in der Include-Datei, als auch in der VBS-Datei definiert wurde.

Damit das Skript nicht überflüssige HTML-Dateien zurückläßt, wird die erzeugte HTML-Datei gelöscht, nachdem der Browser sie dargestellt hat - der Browser zeigt also eine Datei, die es im Augenblick ihres Erscheinens schon nicht mehr gibt. Um die Daten in anderen Programmen zu verwenden kann man die Pfade aus dem Browserfenster in die Zwischenablage kopieren und in andere Programme übertragen.