Home > Artikel > Ausgabe 10/2016 > Datei- und Verzeichnisoperationen mit der Shell

Datei- und Verzeichnisoperationen mit der Shell

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Zu den häufigsten Aufgaben außerhalb einer Access-Datenbankanwendung gehört sicher der Zugriff auf Dateien und Verzeichnisse. Ob Sie nun externe Daten exportieren oder importieren, Dokumente fernsteuern, oder Eigenschaften von Dateien ermitteln möchten, all dies erfordert Navigation in Verzeichnissen und das Ansprechen von Dateien. Und das möglicherweise auch noch innerhalb einer Netzwerkstruktur. Die VBA-Grundfunktionen machen es Ihnen da nicht immer leicht. Über die Shell von Windows kommen Sie jedoch spielend zum Ziel.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1610_ShellObjekte.accdb

VBA-Funktionen für Dateien und Verzeichnisse

Die Klasse FileSystem der VBA-Bibliothek gibt im Objektkatalog eine nur dürftige Liste von Methoden aus, über die verschiedene Datei- und Verzeichnisoperationen ausgeführt werden können. Dieses altehrwürdige Ensemble stammt noch aus Zeiten, in denen Visual Basic eine gewichtige Rolle spielte, und das war Mitte der 90er-Jahre, als noch erheblich geringere Anforderungen gestellt wurden. Seitdem hat sich am Befehlsbestand nichts geändert und die Funktionen weisen leider immer noch Mängel auf.

Die Liste ist schnell beschrieben. Zu den Verzeichnisfunktionen gehören diese:

CurDir ermittelt das aktuelle Verzeichnis. ChDir wechselt zu einem anderen, ChDrive zu einem anderen Laufwerk. Derlei braucht heute niemand mehr – es spielte eher unter DOS eine Rolle. MkDir erzeugt ein neues Verzeichnis mit dem im Parameter angegebenen. Leider funktioniert das nur dann, wenn das oberste Verzeichnis bereits existiert. Das neue kann nur eine Ebene tiefer angelegt werden. Ähnliches gilt für RmDir, was ein Verzeichnis löscht. Die Anweisung klappt nicht, wenn ein Verzeichnis nicht leer ist.

Und das war's auch schon! Die Dateimethoden beschränken sich nun auf folgende:

Kill löscht die angegebene Datei. FileCopy kopiert eine Datei in den im zweiten Parameter angegebenen Ordner. Verschieben geht nicht. Es ist nur durch die Kombination aus FileCopy und anschließendem Kill zu realisieren. FileLen gibt die Dateigröße zurück, FileDateTime das Erstelldatum, GetAttr die Zugriffsattribute.

Lesend und schreibend greifen Sie über die Open-Anweisung auf eine Datei zu. Das ist wohl von allen der wichtigste Befehl. Und schließlich lassen sich über Dir sowohl Dateien, wie auch Verzeichnisse, enumerieren oder nach ihrer Existenz befragen.

Mindestens zwei Kritikpunkte sind für sämtliche dieser Dateisystemfunktionen hervorzubringen. Zum einen scheitern sie häufig an Objekten, deren Namen in Unicode formatiert sind und Sonderzeichen enthalten. Hier ruft VBA schlicht einen Fehler aus. Zum anderen – und das ist viel schwerwiegender! – kommen sie nicht mit Netzwerkpfaden klar. Auf die Dateien eines Server in der Syntax \\serverxy\c\... besteht kein Zugriff. Hier wird dann ein nicht existierendes Verzeichnis bemängelt oder eine falsche Dateinummer gemeldet. Behelfen können Sie sich da dann nur mit zusätzlich eingerichteten Netzlaufwerken.

Einst führten diese Beschränkungen zu einer Menge Programmierlösungen unter Verwendung von Windows-API-Funktionen, die im Internet fröhliche Urstände feierten. Das aber ist inzwischen obsolet.

Die Windows-Shell-Bibliothek

Es lohnt sich uneingeschränkt, auf diese COM-Bibliothek, die grundsätzlich jedes Windows mit sich bringt, ein Auge zu werfen, denn sie ermöglicht einfache Lösungen, die auf andere Weise viel umständlicher zu erreichen wären. Sie hängen sie in ihr VBA-Projekt ein, indem Sie zunächst einen Verweis auf Microsoft Shell Controls And Automation setzen. Verantwortlich für die Bibliothek ist die System-Dll shell32. So lautet auch der Name der Bibliothek, wenn Sie sie dann unter VBA ansprechend möchten. Es gibt genau ein Objekt in ihr, das die Zentrale für alle weiteren Vorgänge darstellt, nämlich das Shell-Objekt.

Sie erzeugen das Objekt über den New-Operator:

Dim oShell As Shell32.Shell

Set oShell = New Shell32.Shell

Nun lassen sich über dieses Objekt allerlei Dinge anstellen, wie ein Blick in den Objektkatalog zeigt, nachdem Sie im Kombinationsfeld links oben Shell32 auswählten und in der Klassenliste Shell markierten. Wir lassen hier jedoch alle Methoden unbeachtet, bis auf eine sehr mächtige, die sich NameSpace nennt.

NameSpace

Die Funktion gibt zu dem im Parameter übergebenen Ausdruck ein Verzeichnisobjekt zurück. Dieses ist vom in der Verweisbibliothek definierten Typ Folder, mit dem sich anschließend weitere Operationen ausführen lassen. Als Parameter für NameSpace kommen sowohl Strings, wie auch Zahlen infrage, wie wir gleich sehen werden.

Ein kleines Bespiel macht alles deutlicher:

Dim oShell As New Shell32.Shell

Dim oFld As Shell32.Folder

Set oFld = _

oShell.NameSpace("c:\windows")

Debug.Print oFld.Title

Im VBA-Direktfenster wird bei Ausführung dieses Schnipsels der Text Windows ausgegeben. Das Folder-Objekt oFld hat diesen Titel, der nicht mit dem Pfad zu verwechseln ist. Den erhält man erst über einen Umweg:

Debug.Print oFld.Self.Path

> c:\windows

Das ist soweit noch nicht sonderlich aufregend. Die Besonderheit der Windows-Shell liegt jedoch darin begründet, dass sie all ihre Objekte wie Verzeichnisse und Dateien behandeln kann, also auch Objekte außerhalb des Dateisystems. Der Explorer zeigt das deutlich. Deshalb kann man NameSpace auch unterschiedliche Werte für den Parameter zuweisen, eben jene, die auch der Explorer verarbeiten kann. Hier einige Beispiele für mögliche Parameter:

c:\windows\

nas\public\images

3

http://www.access-im-unternehmen.de

ftp://ftp.rz.uni-wuerzburg.de/pub

e:\dokumente\alles.zip

::{21EC2020-3AEA-1069-A2DD- _

08002B30309D}

Da kann die Dir-Funktion von VBA wohl nicht mithalten! Die erste Zeile bezieht sich auf den lokalen Pfad von Windows. Die zweite ist eine ein Serverpad im Netzwerk – für die Shell kein Problem! Die dritte Zeile übergäbe einfach eine Zahl. NameSpace nimmt dann an, dass es sich um einen der im System verankerten sogenannten Special Folders handelt, die jeweils eine eindeutige ID besitzen. Das sind vorgegebene Windows-Spezialordner, wie etwa der Desktop, welcher über den Parameterwert 0 anzusprechen wäre. Mit der 3 hingegen erhalten Sie als Folder.Title die Bezeichnung Systemsteuerung, denn auch die ist ein virtueller Ordner. Und was wäre deren Pfad? Folder.Self.Path gibt dies aus:

::{21EC2020-3AEA-1069-A2DD- _

08002B30309D}

Es handelt sich hier um einen GUID-String, da es ja, im Gegensatz zum Desktop, keinen physischen Ordner für die Systemsteuerung gibt. Geben Sie diesen String einmal spaßeshalber in die Adresszeile des Explorers ein. Sie gelangen damit tatsächlich zur Systemsteuerung!

Selbst Internet-URLs kann NameSpace verarbeiten, wie die beiden folgenden Zeilen der Beispielparameter zeigen. Sowohl das HTTP-Protokoll, wie auch das FTP-Protokoll, werden klaglos angenommen. Hier kommen ebenfalls gültige Folder-Objekte zustande!

Noch krasser ist die Übergabe der ZIP-Datei alles.zip. Denn auch der Explorer behandelt ZIP-Archive wie Ordner. Deshalb ist ein ZIP-Archiv automatisch auch bei NameSpace ein gültiges Ordner-Objekt.

Hier wird schon deutlich, dass die Möglichkeiten der Shell-Objekte weit über das hinausgehen, was VBA an Bordmitteln zu bieten hat.

FolderExists

Aus dem bisher Erläuterten geht die einfachste Anwendung hervor, nämlich der Test, ob ein Verzeichnis denn existiert. Übergeben Sie NameSpace nämlich einen ungültigen String oder eine ungültige Zahl, so ereignet sich kein Fehler. Stattdessen ist das zurückgegeben Folder-Objekt einfach leer, hat also quasi den Wert Nothing. Das lässt sich nun leicht überprüfen, wie die Routine FolderExists in Listing 1 zeigt. Ihr übergeben Sie das fragliche Verzeichnis als Parameter. In der Objektvariablen oFld erhalten Sie aus NameSpace das Folder-Objekt. Ist dieses nicht Nothing, so existiert das Verzeichnis, womit der Rückgabewert der Funktion auf True gesetzt wird. Wohlgemerkt: Nicht nur lokale Verzeichnisse können mit dieser Funktion befragt werden, sondern ebenso Netzwerkpfade und Internetpfade!

Function FolderExists(sFolder As String) As Boolean

     Dim oFld As Shell32.Folder

     

     Set oFld = oShell.NameSpace(sFolder)

     If Not oFld Is Nothing Then FolderExists = True

End Function

Listing 1: Überprüfen der Existenz eines Verzeichnisses

Nutzt man die Shell-Funktionen in mehreren Prozeduren, so ist es übrigens hilfreich, statt dem dauernden Neuerzeugen des Shell-Objekts für dieses eine gesonderte Funktion zu schreiben. Auch FolderExists verwendet eine solche (oShell), wie sie in Listing 2 abgebildet ist. Hier ist im Modulkopf die globale Variable m_Shell deklariert. In der Prozedur wird erst getestet, ob deren Inhalt leer ist. In diesem Fall wird per New eine neue Shell-Instanz erzeugt und ihr zugewiesen. Die Funktion gibt schließlich den Wert der Objektvariablen m_Shell zurück.

Public m_Shell As Shell32.Shell

Public Function oShell() As Shell32.Shell

     If m_Shell Is Nothing Then Set m_Shell = New Shell32.Shell

     Set oShell = m_Shell

End Function

Listing 2: Funktion zum Zurückgeben des Shell-Objekts

Special Folders

Es gibt keine genaue Aufstellung von Microsoft, welche Zahlenwerte für NameSpace welchem Spezialverzeichnis zuzuordnen sind. Das variiert je nach Betriebssystem und Sprache. Bei den gebräuchlichsten stimmen sie allerdings überein.

Den Desktop erreichen Sie immer über die 0, das Startmenü über 22, oder das Systemverzeichnis über 37. Diese Werte sind im Windows-SDK in den Header-Dateien deklariert. Doch machen Sie für Ihr System doch einfach selbst die Aufstellung und verwenden die Routine StoreSpecialFolders im Modul mdlSpcFolders der Beispieldatenbank!

Die in Listing 3 dargestellte Prozedur speichert alle möglichen Werte in der Tabelle tblShellFolders, die zuerst über die DELETE-Anweisung geleert wird. Dann durchläuft eine Schleife auf den Zähler i die Werte von 0 bis 255 und testet NameSpace damit.

Sub StoreSpecialFolders()

     Dim fld As Folder

     Dim i As Long

     Dim sPath As String

     Dim rs As DAO.Recordset

     

     CurrentDb.Execute "DELETE FROM tblShellFolders"

     Set rs = CurrentDb.OpenRecordset("SELECT * FROM tblShellFolders", dbOpenDynaset)

     On Error Resume Next

     For i = 0 To 255

         Set fld = oShell.NameSpace(i)

         Err.Clear

         sPath = fld.Self.Path

         If Err.Number = 0 Then

             rs.AddNew

             rs!SFID = i

             rs![Name] = fld.Title

             rs!Path = sPath

             rs.Update

         End If

     Next i

     rs.Close

End Function

Listing 3: Alle Windows-Spezialordner kommen in die Tabelle tblShellFolders

Die Variable sPath nimmt den Pfad des so ermittelten Ordners fld entgegen. Statt des Überprüfens auf Nothing für das Folder-Objekt wird hier ein On Error Resume Next eingesetzt, denn bei nicht existierendem Ordner erzeugt Self.Path einen Fehler. Ist die Fehlernummer aber 0, so wird dem Recordset rs ein Datensatz hinzugefügt. Das Feld SFID nimmt dabei den Zahlenwert für NameSpace auf, Name seinen Titel, und Path den vollen Verzeichnispfad.

Nach dem Durchlaufen der Prozedur können Sie die Tabelle in der Datenblattansicht begutachten. Bild 1 gibt einen Ausschnitt der 56 Spezialordner wieder. In Bild 2 sind diese über den Spaltenfilter auf den Textbeginn '::' reduziert. Die Ansicht gibt damit alle virtuellen Ordner von Windows aus.

Die Windows-Spezialordner sind in der Tabelle tblShellFolders abgespeichert

Bild 1: Die Windows-Spezialordner sind in der Tabelle tblShellFolders abgespeichert

Hier ist die Tabelle nach allen Ordnerpfaden gefiltert, die mit :: beginnen

Bild 2: Hier ist die Tabelle nach allen Ordnerpfaden gefiltert, die mit :: beginnen

Sie haben nun die Auswahl, wie Sie zum Ordner für Windows gelangen:

oShell.NameSpace(36).Self.Path

oder

Environ("Windows")

Beides gibt dasselbe aus. Environ ist eine Funktion, die seltsamerweise nicht in der Klasse FileSystem von VBA untergebracht ist. Sie gibt aber keine Spezialordner zurück, sondern die Umgebungsvariablen von Windows. Die enthalten aber erheblich weniger vorgegebene Orte, als NameSpace auszugeben vermag.

FolderItems

Bisher beschäftigten wir uns nur mit Verzeichnisobjekten. Ein Folder-Objekt ist aber nur der Ausgangspunkt für weitere Elemente. Seine wichtigste Eigenschaft ist die Aufzählung Items vom Typ FolderItems. Das sind die Elemente, die der Ordner enthält.

Man kann diese Elemente in einer For-Each-Schleife enumerieren:

Dim oFld As Shell32.Folder

Dim oItm As Shell32.FolderItem

Set oFld = NameSpace("c:\windows")

For Each oItm In oFld.Items

     Debug.Print oItm.Name

Next oItm

In diesem Beispiel werden alle Elemente des Windows-Verzeichnisses ausgegeben. Die Items-Auflistung des Folders wird durchlaufen und zu jedem FolderItem-Objekt oItm darin dessen Name erhalten.

Ähnlich, wie dies auch bei der Funktion Dir der Fall ist, können diese Elemente vom Typ Datei oder Verzeichnis sein. Hier kommen noch virtuelle Elemente hinzu, denn die Systemsteuerung etwa enthält weder Dateien, noch Ordner. Zum Glück ist die Unterscheidung einfach:

oItm.IsFileSystem

Hier kommt ein Wahr heraus, wenn es sich um ein Element des Dateisystems handelt. Ob das dann eine Datei oder ein Verzeichnis ist, gibt eine weitere Funktion wieder:

oItm. IsFolder

Bei Dateien wäre die Rückgabe False. Und für die gibt es noch eine zusätzliche Spezialität:

oItm.IsLink

Das gibt immer dann True zurück, wenn es sich um eine Verknüpfung handelt. Der Desktop etwa ist voll davon.

Ein FolderItem kennt daneben noch einige weitere Methoden. Die Dateigröße etwa ermitteln Sie über oItm.Size, das Datum der letzten Änderung mit oItm.ModifyDate und den Typ des Elements über oItm.Type. Letzteres gibt einen Text aus, wie etwa Dateiordner. Wie weitere Eigenschaften des Elements erhalten werden können, folgt später.

Natürlich können Sie ein Element eines Ordners auch gezielt ansprechen. Dafür kommen zwei Methoden in Betracht. Die eine benutzt wieder die Items-Auflistung:

Set oFld = NameSpace("c:\windows")

Set oItm = oFld.Items.Item( _

"notepad.exe")

Debug.Print oItm.Name

Sicherer jedoch ist eine spezielle Methode des Folder-Objekts selbst, weil sie fehlerrobuster ist:

Set oFld = NameSpace("c:\windows")

Set oItm = oFld.ParseName( _

                     "notepad.exe")

Debug.Print oItm.Name

ParseName nämlich kann nicht nur Elemente des aktuellen Ordners wiedergeben, sondern auch untergeordnete:

Set oFld = NameSpace("c:\windows")

Set oItm = oFld.ParseName( _

            "system32\drivers\etc")

Debug.Print oItm.Name

> 'etc'

Hier werden also mehrfache Unterordner ermittelt.

Mit diesem Wissen im Gepäck können Sie sich auch an die Behandlung von Dateien machen.

FileExists

Ob eine Datei existiert, kann die Funktion FileExists in Listing 4 herausfinden. Wieder wird hier ein Folder-Objekt oFld erhalten, aus dem es sich, so es nicht Nothing ist, über ParseName ein FolderItem oItm holt. Ist dieses Objekt gültig, abermals durch Vergleich mit Nothing ermittelt, so existiert die Datei:

Function FileExists(sFile As String) As Boolean

     Dim sFld As String

     Dim sFil As String

     Dim oFld As Shell32.Folder

     Dim oItm As Shell32.FolderItem

     

     GetFileFolder sFile, sFld, sFil

     Set oFld = oShell.NameSpace(sFld)

     If Not oFld Is Nothing Then

         Set oItm = oFld.ParseName(sFil)

         If Not oItm Is Nothing Then FileExists = True

     End If

End Function

Listing 4: Funktion zum Prüfen der Existenz einer Datei

  FileExists("c:\windows\notepad.exe")

> Wahr

Die Sache ist allerdings nicht ganz so einfach, wie es beim ersten Blick auf die Prozedur den Anschein hat. Denn übergeben wird ihr ja ein voller Dateipfad. Aus diesem muss erst der Verzeichnispfad extrahiert werden, um ihn an die NameSpace-Methode zu geben. Das übernimmt eine zusätzlich Hilfsfunktion GetFileFolder (Listing 5). In sExpression kommt der Dateipfad hinein. Andererseits werden die String-Variablen für den zurückgegebenen Ordner und den Dateinamen in sFolder und sFile spezifiziert. Als Charakteristikum für die Trennung zwischen Pfad- und Dateiname hält das letzte Backslash her, welches über InstrRev ermittelt wird. Das macht man in der Regel so, doch hier sind ja auch noch URLs für den Internetzugriff zu berücksichtigen, die stattdessen Slashs enthalten. Das wird in der eingeschobenen Bedingung ausgewertet. Sind gar keine Slashs oder Backslashs vorhanden, so bleibt die Rückgabe leer.

Sub GetFileFolder(sExpression As String, sFolder As String, sFile As String)

     Dim n As Long

     

     n = InStrRev(sExpression, "\")

     If n = 0 Then

         n = InStrRev(sExpression, "/")

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!