Zippen mit Access

Lies diesen Artikel und viele weitere mit einem kostenlosen, einwöchigen Testzugang.

Sie werden immer mal wieder auf die Aufgabe stoßen, automatisiert Zip-Dateien zu erstellen, Daten in Zip-Dateien zu speichern oder Daten aus Zip-Dateien zu extrahieren. Dazu benötigen Sie optimalerweise VBA-Code ohne viel Schnickschnack wie externe Bibliotheken et cetera. Windows liefert glücklicherweise in aktuelleren Versionen Möglichkeiten, Zip-Dateien auch per VBA zu erstellen, zu füllen und zu lesen. Diese sind zwar nicht so einfach zu finden, aber wir haben Ihnen eine Auswahl wichtiger Funktionen zusammengestellt.

Grundlegende Technik

Wenn wir die Windows-Funktionen zum Verwenden von Zip-Dateien nutzen wollen, haben wir nur eine eingeschränkte Menge an Möglichkeiten zur Verfügung. Diese sollten jedoch für den Großteil der denkbaren Einsatzfälle ausreichend sein.

Benötigen Sie spezielle Techniken etwa zum Hinzufügen eines Kennworts zum Schutz des Inhalts der Zip-Datei, müssen Sie auf fertige Bibliotheken zurückgreifen, die möglicherweise kostenpflichtig sind und gegebenenfalls auch erst beim Nutzer installiert werden müssen.

Die hier vorgestellten Techniken nutzen allein die vom Betriebssystem bereitgestellten Funktionen.

In einem halbwegs frischen System, also ohne zusätzliche Einträge in den Kontextmenüs, sieht die eingebaute Funktion zum Erstellen von Zip-Dateien wie in Bild 1 aus.

Erstellen einer Zip-Datei mit Windows-Bordmitteln

Bild 1: Erstellen einer Zip-Datei mit Windows-Bordmitteln

Zip-Datei erstellen

Es gibt keine eingebaute Funktion, um eine leere Zip-Datei zu erstellen. Wie können wir dieses Problem umgehen Dazu gibt es zumindest zwei Möglichkeiten:

  • Wir legen eine neue, leere Zip-Datei mit einem geeigneten Programm wie WinZip oder WinRar an, speichern diese in einem Anlage-Feld der Datenbank und exportieren dieses bei Bedarf als leere Zip-Datei in das Zielverzeichnis.
  • Wir schauen uns an, wie eine leere Zip-Datei aufgebaut ist, und bauen diese einfach nach.

Ersteres würde zumindest noch eine weitere Tabelle mit dem Anlage-Feld erforderlich machen, was wir an dieser Stelle für übertriebenen Aufwand halten. Also erstellen wir lieber mit WinZip oder WinRar eine leere Zip-Datei und betrachten diese in einem Texteditor.

Das Ergebnis sieht dann etwa wie in Bild 2 aus. Was soll schon schieflaufen, wenn wir einfach eine leere Textdatei mit den eingebauten VBA-Befehlen erzeugen und die hier enthaltenen Zeichen einfügen Also machen wir uns an die Arbeit. Die Prozedur aus Listing 1 erwartet den Namen der zu erstellenden Zip-Datei und legt diese dann an. Dazu erstellt sie eine String-Variable namens strZipinhalt und füllt sie genau mit dem Inhalt der Datei, die wir oben probehalber erzeugt und im Texteditor angezeigt haben. Die Zeichen in der verwendeten Vorlage werden vom Texteditor im hexadezimalen Format angezeigt. Wir müssen diese, bevor wir die Zeichen mit der Funktion Chr() ermitteln, in das Dezimalformat umwandeln. Dies erledigen wir durch Voranstellen der Zeichenkette &H. Die achtzehn Nullen fügen wir mit der Funktion String() hinzu, der wir als ersten Parameter die Anzahl der zu liefernden Zeichen und als zweiten den Code für das gewünschte Zeichen übergeben.

Aussehen einer leeren Zip-Datei

Bild 2: Aussehen einer leeren Zip-Datei

Die Prozedur ermittelt dann mit der Funktion FreeFile eine Dateinummer für den Zugriff auf die neu zu erstellende Datei. Diese öffnen wir mit der Open-Methode, der wir den Namen der zu erstellenden Datei folgen lassen (strZipdatei). Der öffnungsmodus ist For Binary Access Write, die Dateinummer lautet schließlich #intDateinummer. Die Put-Methode schreibt den Inhalt der Variablen strZipinhalt in die mit #intDateinummer geöffnete Datei, die Close-Methode schließt diese wieder.

Ein beispielhafter Aufruf für diese Prozedur sieht etwa wie folgt aus:

Public Sub Test_ZipErstellen()
     On Error Resume Next
     Kill CurrentProject.Path & "\testvba.zip"
     On Error GoTo 0
     ZipErstellen CurrentProject.Path & "\testvba.zip"
End Sub

Die so erzeugte Datei hat genau den gleichen Inhalt wie die oben mit WinZip erzeugte Datei im Texteditor und kann somit auch etwa mit WinZip oder WinRar geöffnet werden. Außerdem, und das ist ja unser erklärtes Ziel, können wir diese neu erstellte Zip-Datei nun mit den von uns gewünschten Dateien füllen.

Zip-Datei füllen

Der zweite Schritt besteht nun darin, eine Datei zu einer Zip-Datei hinzuzufügen. Dazu verwenden wir die Funktion Zippen aus Listing 2. Die Datei erwartet zwei Parameter:

Public Function Zippen(strZipdatei As String, strDatei As String) As Boolean
     Dim objZip As Object
     Dim objShell As Object
     Dim intCount As Integer
     Set objShell = CreateObject("Shell.Application")
     Set objZip = objShell.Namespace((strZipdatei))
     If objZip Is Nothing Then
         ZipErstellen strZipdatei
         Set objZip = objShell.Namespace(CVar(strZipdatei))
     End If
     intCount = objZip.items.Count
     ''''objZip.CopyHere strDatei
     objZip.CopyHere (strDatei) ''''Klammer Pflicht, da sonst kein zippen
     Do
         Call Sleep(100)
     Loop While Not ZipdateiGeschlossen(strZipdatei)
     If objZip.items.Count > intCount Then
         Zippen = True
     End If
End Function

Listing 2: Funktion zum Hinzufügen einer Datei zu einer Zip-Datei

  • strZipdatei ist der Pfad zur Zip-Datei,
  • strDatei ist der Pfad zu der hinzuzufügenden Datei.

Die Funktion deklariert zwei Objektvariablen namens objZip und objShell sowie eine Integer-Variable namens intCount. objShell füllen wir mit einem neuen Objekt auf Basis der Klasse Shell.Application wie bereits in der Prozedur ZipErstellen. objZip erhält einen Verweis auf das Namespace-Objekt auf Basis der Zip-Datei.

Diesen holen wir über die Namespace-Eigenschaft des Shell.Application-Objekts. Danach prüft die Funktion, ob objZip einen Verweis enthält. Dies ist nicht der Fall, wenn die mit strZipdatei übergebene Datei keine Zip-Datei ist. In diesem Fall erstellt die Funktion die Zip-Datei mit der bereits vorgestellten Prozedur ZipErstellen neu und erneuert dann den in objZip gespeicherten Verweis auf diese Datei.

Nun folgt ein Schritt, der für die überprüfung des Erfolgs wichtig ist. Mit der Eigenschaft Count der items-Auflistung des Namespace-Objekts mit der Zip-Datei ermitteln wir die Anzahl der in der Zip-Datei enthaltenen Dateien beziehungsweise Elemente (hier werden auch Ordner mitgezählt). Bei einer frisch angelegten Zip-Datei sollte dies den Wert 0 liefern.

Anschließend würden wir normalerweise einfach die CopyHere-Methode des Namespace-Objekts mit der Zip-Datei aufrufen und als Parameter den Namen der hinzuzufügenden Datei übergeben:

objZip.CopyHere strDatei

Dies ist auf dem Testsystem jedoch zuverlässig schiefgegangen. Nach einigen Recherchen stellte sich heraus, dass man den Dateinamen in Klammern einfassen muss, damit es gelingt:

objZip.CopyHere (strDatei)

Die Methode CopyHere ist eine asynchrone Methode, das heißt, sie wird ausgeführt, während der Code weiterläuft. Wenn wir also direkt danach mit der folgenden Anweisung prüfen würden, ob die Datei hinzugefügt wurde, kann es sein, dass das Hinzufügen noch nicht abgeschlossen wurde:

If objZip.items.Count > intCount Then

Also bauen wir eine Do…Loop-Schleife ein, die mit einer Hilfsfunktion prüft, ob die Datei bereits zur Zip-Datei hinzugefügt wurde. Mit jedem Durchlauf dieser Schleife rufen wir zunächst die Sleep-Funktion mit dem Wert 100 als Parameter auf, was eine Pause von einer Zehntelsekunde hervorruft. Die Sleep-Funktion deklarieren wir wie folgt in einem Standardmodul:

Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

Die Abbruchbedingung lautet so:

Loop While Not ZipdateiGeschlossen(strZipdatei)

Die hier angegebene Funktion ZipdateiGeschlossen erwartet den Namen der zu überprüfenden Datei als Parameter (s. Listing 3). Sie ermittelt eine Nummer für den schreibenden Zugriff auf die angegebene Datei und versucht dann, diese mit der Open-Anweisung zu öffnen.

Private Function ZipdateiGeschlossen(strZipdatei As String) As Boolean
     Dim intDateinummer As Integer
     intDateinummer = FreeFile
     On Error GoTo Ende_ZipdateiGeschlossen
     Open strZipdatei For Binary Access Read Lock Write As intDateinummer
     Close intDateinummer
     ZipdateiGeschlossen = True
Ende_ZipdateiGeschlossen:
End Function

Listing 3: Funktion zur Prüfung, ob eine bestimmte Datei aktuell geschlossen ist

Ist die Datei noch geöffnet, löst diese Anweisung einen Fehler aus und die Funktion springt zur Marke Ende_ZipdateiGeschlossen. Die Funktion liefert dann den Wert False zurück. Anderenfalls schließt die Funktion die Datei mit der Close-Anweisung wieder und stellt den Rückgabewert auf True ein.

Auf diese Weise wird die Do…Loop-Schleife so lange durchlaufen, bis die Zip-Datei nicht mehr durch die CopyHere-Anweisung in Beschlag genommen wird und die Datei hinzugefügt wurde. Danach können wir dann in der If…Then-Bedingung prüfen, ob sich die Anzahl der enthaltenen Dateien im Anschluss an das Hinzufügen geändert hat. Falls ja, wird der Rückgabewert der Funktion Zippen auf den Wert True eingestellt.

Die Funktion Zippen testen wir etwa mit folgender Routine:

Public Sub Test_Zippen_Datei_klein()
     Dim strDatei As String
     Dim strZipdatei As String
     strZipdatei = CurrentProject.Path & "\testvba.zip"
     strDatei = CurrentProject.Path & "\test.txt"
     MsgBox "Zippen erfolgreich " _
         & Zippen(strZipdatei, strDatei)
End Sub

Dies stellt die Pfadangaben zur Zip-Datei und zur hinzuzufügenden Datei zusammen und übergibt diese an die Funktion Zippen. Das Ergebnis wird dann in einer Meldung ausgegeben.

Verzeichnisse zippen

Mit der Funktion Zippen können Sie auch komplette Verzeichnisse zippen. Dazu übergeben Sie als zweiten Parameter einfach den Pfad zu dem zu zippenden Verzeichnis:

Public Sub Test_Zippen_Folder()
     Dim strDatei As String
     Dim strZipdatei As String
     strZipdatei = CurrentProject.Path & "\testvba.zip"
     strDatei = CurrentProject.Path & "\test\"
     MsgBox "Zippen erfolgreich " _
         & Zippen(strZipdatei, strDatei)
End Sub

Nachdem Sie diese beiden Test-Routinen aufgerufen haben, erhalten Sie eine Zip-Datei, die mit WinRar geöffnet etwa wie in Bild 3 aussieht.

Zip-Datei mit einigen per VBA hinzugefügten Elementen

Bild 3: Zip-Datei mit einigen per VBA hinzugefügten Elementen

Die bisher beschriebene Version der Funktion Zippen haben wir unter dem Namen Zippen_Version1 im Modul mdlZippen abgelegt.

Dateien eines Verzeichnisses einzeln zippen

Nun möchten Sie möglicherweise nicht das komplette Verzeichnis zippen, sondern nur die in diesem Verzeichnis enthaltenen Dateien. Das können Sie erledigen, indem Sie die Funktion Zippen entsprechend anpassen.

Die neue Version sieht nun wie in Listing 4 aus. Sie erwartet zwei Parameter mehr als die vorherige Version, nämlich lngAnzahl und bolOhneVerzeichnis. Beide sind nur in Zusammenhang mit der übergabe eines Verzeichnisses mit dem Parameter strDateiVerzeichnis interessant, da nur dann mehrere Dateien verarbeitet werden.

Public Function Zippen(strZipdatei As String, ByVal strDateiVerzeichnis As String, _
         Optional lngAnzahl As Long, Optional bolOhneVerzeichnis As Boolean = False) As Boolean
     Dim objZip As Object, objShell As Object
     Dim intCount As Integer, strDatei As String, strVerzeichnis As String
     Set objShell = CreateObject("Shell.Application")
     Set objZip = objShell.NameSpace((strZipdatei))
     If objZip Is Nothing Then
         ZipErstellen strZipdatei
         Set objZip = objShell.NameSpace(CVar(strZipdatei))
     End If
     intCount = objZip.items.Count
     If IstVerzeichnis(strDateiVerzeichnis) And bolOhneVerzeichnis Then
         strVerzeichnis = Trim(strDateiVerzeichnis)
         If Not Right(strVerzeichnis, 1) = "\" Then
             strVerzeichnis = strVerzeichnis & "\"
         End If
         strDatei = Dir(strVerzeichnis)
         Do While Len(strDatei) > 0
             objZip.CopyHere (strVerzeichnis & strDatei)
             Do
                 Call Sleep(100)
             Loop While Not ZipdateiGeschlossen(strZipdatei)
             strDatei = Dir()
         Loop
     Else
         objZip.CopyHere (strDateiVerzeichnis)
         Do
             Call Sleep(100)
         Loop While Not ZipdateiGeschlossen(strZipdatei)
     End If
     lngAnzahl = objZip.items.Count - intCount
     If lngAnzahl Then
         Zippen = True
     End If
End Function

Listing 4: Zippen-Funktion, die auch einzelne Elemente eines Verzeichnisses zippt – mit Rückgabe der Anzahl

Die Funktion verwendet außerdem drei zusätzliche Variablen: intCount nimmt die Anzahl der hinzufügten Elemente auf. strDatei soll, wenn es sich beim Inhalt des Parameters strDateiVerzeichnis um ein Verzeichnis handelt, zum Durchlaufen der darin enthaltenen Dateien dienen. strVerzeichnis nimmt entsprechend das Verzeichnis aus strDateiVerzeichnis auf – dies nur, um Verwechslungen zu vermeiden. Die Zip-Datei wird wie gehabt referenziert beziehungsweise, sofern noch nicht vorhanden, erstellt. Die Variable intCount speichert nach wie vor die Anzahl der Elemente des Zip-Archivs vor dem Hinzufügen der Elemente.

Dann prüft die Hilfsfunktion IstVerzeichnis, ob es sich bei dem mit strDateiVerzeichnis übergebenen Pfad um ein Verzeichnis handelt. Ist dies der Fall und gleichzeitig der Parameter bolOhneVerzeichnis auf True eingestellt, wird der erste Teil der If…Then-Bedingung abgearbeitet.

Innerhalb dieses Abschnitts trägt die Funktion den Wert von strDateiVerzeichnis in die Variable strVerzeichnis ein. Dann prüft sie, ob strVerzeichnis ein abschließendes Backslash-Zeichen (\) enthält, und fügt dieses gegebenenfalls noch hinzu. Schließlich liest die Prozedur mit der Dir-Funktion den Namen des ersten Elements des in strVerzeichnis angegebenen Verzeichnisses in die Variable strDatei ein.

Damit steigt die Funktion in eine Do While-Schleife ein, die so lange läuft, wie die Länge der in strDatei gespeicherten Zeichenkette größer als 0 ist, die Variable also einen Wert enthält. Innerhalb der Schleife fügt die CopyHere-Methode nun die Datei mit dem aus strVerzeichnis und strDatei zusammengesetzten Pfad zu der mit objZip referenzierten Zip-Datei hinzu. Die Do…Loop-Schleife mit dem Ausdruck Not ZipdateiGeschlossen(strZipdatei) hält die weitere Ausführung der Funktion wieder so lange an, bis die Zip-Datei durch den asynchronen Befehl CopyHere fertig beschrieben wurde.

Danach ermittelt die Funktion über den einfachen Aufruf der Dir-Funktion die nächste Datei in dem angegebenen Verzeichnis. Warum kein Parameter Weil die Dir-Funktion so strukturiert ist, dass sie mit Verzeichnisangabe das erste Element dieses Verzeichnisses zurückliefert und ohne Parameter immer das jeweils folgende Element. Die Schleife beginnt nun von vorn, wo sie wieder prüft, ob strDatei noch mit einem Dateinamen gefüllt ist. Falls ja, werden auch die folgenden Dateien zur Zip-Datei hinzugefügt, anderenfalls ist die Schleife abgearbeitet.

Der Else-Teil der If…Then-Bedingung fügt wie bereits in der vorherigen Version der Zippen-Funktion die mit strDateiVerzeichnis angegebene Datei oder das Verzeichnis zur Zip-Datei hinzu.

Ob während des Aufrufs der Zippen-Funktion ein oder mehrere Dateien zur Zip-Datei hinzugefügt wurden, ermitteln wir wieder mit der Differenz aus objZip.items.Count und dem Wert aus der Variablen intCount. Diesmal landet das Ergebnis allerdings gleich im Rückgabeparameter lngAnzahl. Deren Inhalt überprüft dann auch die If…Then-Bedingung, welche den Rückgabewert der Funktion Zippen im Falle eines Wertes größer 0 auf True einstellt.

Die Hilfsfunktion IstVerzeichnis, mit der wir prüfen, ob es sich bei dem mit strDateiVerzeichnis übergebenen Ausdruck um eine Datei oder ein Verzeichnis handelt, sieht wie folgt aus:

Function IstVerzeichnis(ByVal strPfad As String) As Boolean
     On Error Resume Next
     IstVerzeichnis = _
         ((GetAttr(strPfad) And vbDirectory) = vbDirectory)
End Function

Sie liest den Wert der Funktion GetAttr für den Parameter strPfad ein und prüft, ob das Ergebnis die Konstante vbDirectory enthält. In diesem Fall handelt es sich um ein Verzeichnis und die Funktion liefert den Wert True zurück.

Das Ergebnis für ein Verzeichnis, das beispielsweise drei Textdateien enthält und mit folgender Routine aufgerufen wurde, sieht wie in Bild 4 aus:

Zip-Datei mit dem Inhalt eines Verzeichnis als einzelne Dateien

Bild 4: Zip-Datei mit dem Inhalt eines Verzeichnis als einzelne Dateien

Public Sub Test_Zippen_VerzeichnisDateienEinzeln_MitAnzahl()
     Dim strDatei As String
     Dim strZipdatei As String
     Dim lngAnzahl As Long
     Dim bolZippen As Boolean
     strZipdatei = CurrentProject.Path & "\testvba.zip"
     strDatei = CurrentProject.Path & "\test"
     bolZippen = Zippen(strZipdatei, strDatei, lngAnzahl, True)
     If bolZippen Then
         MsgBox "Zippen erfolgreich, Anzahl: " & lngAnzahl
     Else
         MsgBox "Zippen nicht erfolgreich"
     End If
End Sub

Elemente der Zip-Datei ermitteln

Nun wollen wir herausfinden, welche Dateien überhaupt in einer Zip-Datei enthalten sind. Im Hinblick auf eine spätere Anwendung, in der wir diese Dateien in einem Unterformular anzeigen wollen, speichern wir diese Daten direkt in einer Tabelle namens tblDateien.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

Testzugang

eine Woche kostenlosen Zugriff auf diesen und mehr als 1.000 weitere Artikel

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar