Wenn man allein mit einer Access-Anwendung arbeitet, die man währenddessen weiterentwickelt, ist man immer auf dem aktuellsten Stand. Wer eine Anwendung auf die Arbeitsplätze seiner eigenen Mitarbeiter oder sogar die der Mitarbeiter eines Kunden installiert hat, muss schon ein wenig Aufwand betreiben, wenn jeder immer die aktuellste Version der Anwendung vorfinden soll. Die alte Version muss entfernt und die neue installiert werden. Hier kann man die Benutzer informieren, sodass diese die notwendigen Schritte manuell durchführen oder man erledigt dies selbst. Praktischer wäre es jedoch, wenn die Anwendung selbst erkennen würde, wenn es eine neue Version gibt, und dann selbstständig ein Update durchführt. Das geht natürlich nicht, weil sie sich nicht selbst löschen und durch eine neue Version ersetzen kann, während sie läuft. Also benötigen wir einen kleinen Workaround: Wir stellen dem Öffnen der eigentlichen Anwendung eine weitere Access-Anwendung voran, die auf Updates prüft und diese durchführt und erst dann die eigentliche Anwendung aufruft. Alle notwendigen Schritte erläutern wir in diesem Beitrag.
Varianten von Frontend-Updatern
Eine solche vorgeschaltete Anwendung nennt man auch Frontend-Updater. Dabei muss es sich nicht zwingend um eine Access-Anwendung handeln – wenn man die notwendigen Programmierkenntnisse und Tools handelt, kann man dies auch in Form einer Exe-Datei auf Basis VB6, VB.NET oder twinBASIC erledigen.
Die Aufgabe bleibt jedoch gleich: Der Frontend-Updater soll vor der eigentlichen Access-Anwendung geöffnet werden und prüfen, ob die derzeit auf dem Rechner befindliche Frontend-Datenbank die aktuellste verfügbare Version ist. Ist diese Bedingung erfüllt, kann die Frontend-Datenbank geöffnet werden. Falls nicht, soll die vorhandene Frontend-Datenbank gelöscht (oder zumindest umbenannt) werden und die neue Version der Frontend-Datenbank soll stattdessen in das entsprechende Verzeichnis kopiert werden. Danach wird entweder die vorhandene oder die neu hinzugefügte Frontend-Datenbank gestartet.
Hier ergeben sich einige technische Herausforderungen:
- Wie finden wir heraus, ob sich die aktuellste Version auf dem aktuellen Rechner befindet?
- Wenn nicht – wie kopieren wir die aktuellere Version auf den Rechner und löschen die vorhandene Version oder benennen diese um?
- Wie können wir von einer Access-Datenbank aus eine andere Access-Datenbank starten?
- Welche Voraussetzungen sind für ein solches Vorgehen überhaupt erforderlich?
Voraussetzungen für ein automatisches Update des Frontends
Ein Update kann man nicht in jedem Fall ohne Weiteres über die vorhandene Version kopieren und dann einfach weiterarbeiten. Die erste Voraussetzung ist, dass die Daten, die von den Frontends aus bearbeitet werden, sich in einer Backend-Datenbank befinden. Das sollte aber im Mehrbenutzerbetrieb ohnehin der Fall sein. Gegebenenfalls befinden sich die Tabellen mit den Geschäftsdaten sogar in einer SQL Server-Datenbank.
Die zweite Voraussetzung ist, dass sich auch keine benutzerdefinierten Daten in der Frontend-Datenbank befinden. Es kommt beispielsweise oft vor, dass der Benutzer Optionen für die Anwendung selbst einstellen kann. So findet er beim Öffnen der Anwendung auf seinem Desktop-Rechner immer die Konfiguration vor, die er selbst eingestellt hat. Solche Daten speichert man üblicherweise in einer oder mehreren Optionen-Tabellen in der Frontend-Datenbank.
Um dies zu umgehen, gibt es beispielsweise die folgenden Alternativen:
- Die Einstellungen für den jeweiligen Benutzer werden in einer Optionentabelle im Backend auf dem Server gespeichert, wobei dort noch ein Feld hinzuzufügen ist, dass kenntlich macht, zu welchem Benutzer die Einstellungen gehören.
- Die Einstellungen werden in einem weiteren Backend gespeichert, das sich im gleichen Verzeichnis wie das Frontend des Benutzers befindet. Auf diese Weise können wir das Frontend austauschen, ohne dass die Einstellungen des Benutzers verlorengehen. Wir müssen nur sicherstellen, dass sich eine neue Version des Frontends automatisch wieder mit dem Backend mit den Optionentabellen verknüpft.
- Oder wir speichern Optionen gar nicht in einer Tabelle, sondern in der Registry in dem für den aktuellen Benutzer für VBA-Anwendungen vorgesehenen Bereich. Wie das gelingt, zeigen wir beispielsweise im Beitrag Optionen einfach in der Registry speichern (www.access-im-unternehmen.de/1512).
Information über die aktuelle Version speichern
Wenn wir vergleichen wollen, ob die auf dem Server liegende Version des Datenbank-Frontends aktueller ist, als die vorliegende Version, müssen wir irgendwo in den Datenbankdateien die Versionsnummer speichern.
Zunächst einmal schlagen wir vor, hier einfach eine durchlaufende Nummer zu verwenden. Auf diese Weise können wir am einfachsten vergleichen, welche Version die aktuellere ist.
Dann benötigen wir einen geeigneten Ort für diese Information. Dazu gibt es mindestens die folgenden beiden Vorschläge:
- Speichern der Versionsnummer in einer Optionentabelle der Frontends
- Speichern der Versionsnummer in einer Eigenschaft der Access-Datenbank
Beide Informationen können wir einfach per VBA abfragen und manuell oder per Code setzen. Später beschreiben wir die technische Umsetzung.
Prüfen, ob die vorliegende Version die aktuellste Version ist
Wenn sich sowohl im vorliegenden Frontend als auch in der Version auf dem Server die Information über die Version entweder in einer Tabelle oder in einer Eigenschaft befindet, können wir diese einfach einlesen und vergleichen. In beiden Fällen müssen wir von der Frontend-Updater-Anwendung aus per VBA auf die jeweilige Version zugreifen. Wenn wir, wie oben vorgeschlagen, eine einfache Versionsnummer wählen, können wir diese auch ganz einfach vergleichen und entscheiden, ob die Version vom Server die aktuelle Version ersetzen soll.
Umbenennen/Löschen der alten Version und Kopieren der neuen Version
Wenn wir herausgefunden haben, dass sich auf dem Server eine aktuellere Version der Frontend-Datenbank befindet, sind die folgenden Schritte durchzuführen:
- Löschen oder Umbenennen der vorliegenden Version, wobei wir das Umbenennen bevorzugen würden. Auf diese Weise könnten wir die vorliegende Version wiederherstellen, wenn das Update aus irgendeinem Grund nicht funktioniert. Beim Umbenennen könnten wir einfach die aktuelle Version hinten an den Dateinamen anhängen. Das Umbenennen oder auch das Löschen lässt sich per VBA realisieren.
- Kopieren der neueren Version vom Server auf den aktuellen Rechner. Dies gelingt ebenfalls mit einem einfachen VBA-Befehl.
Starten der eigentlichen Datenbank von der Updater-Anwendung aus
Schließlich fehlt noch der Schritt, der bei jedem Start durchgeführt werden muss: Die Frontend-Updater-Datenbank muss die eigentliche Datenbank starten, unabhängig davon, ob diese zuvor aktualisiert wurde oder nicht.
Testszenario
Zum Testen verwenden wir zwei gleiche Datenbanken, die lediglich die Tabelle mit der Versionsnummer enthalten. Die zu prüfende und gegebenenfalls zu ersetzende Datenbank liegt in einem Verzeichnis mit der Frontend-Updater-Datenbank. Statt eines Servers verwenden wir ein Verzeichnis namens Server, das sich im gleichen Verzeichnis wie die beiden zuvor genannten Datenbankdateien befindet.
Die Struktur sieht also wie folgt aus:
Frontend.accdb FrontendUpdater.accdb Server Frontend.accdb (neuere Version)
Die Frontent.accdb-Datenbanken enthalten eine Tabelle namens tblVersion mit einem einzigen Feld mit dem Datentyp Zahl und dem Namen Version.
Nun bearbeiten wir die Datenbank FrontendUpdater.accdb, damit diese die notwendige Prüfung durchführt, gegebenenfalls das Frontend ersetzt und dieses dann startet.
Code beim Starten automatisch ausführen
Die Voraussetzung für eine automatische Prüfung beim Start der Datenbank FrontendUpdater.accdb ist, dass diese beim Starten den entsprechenden Code ausführt. Das können wir am einfachsten mit einem AutoExec-Makro realisieren, der wir den Namen der zu startenden VBA-Funktion für die Makroaktion AusführenCode übergeben (siehe Bild 1).
Bild 1: AutoExec-Makro, das beim Start automatisch ausgeführt wird
Die Funktion StartDb können wir testhalber zunächst wie folgt formulieren:
Public Function StartDb() MsgBox "Start" End Function
Starten wir die Datenbank nun neu, sollte das Meldungsfenster erscheinen.
Version des vorliegenden Frontends einlesen
Mit der Funktion GetVersion aus Listing 1, der wir den Pfad zu der zu untersuchenden Datenbankdatei übergeben, erstellen wir ein Database-Objekt auf Basis der mit OpenDatabase geöffneten Datenbankdatei. Für diese öffnen wir ein Recordset auf Basis der Tabelle tblVersion. Enthält diese zumindest einen Datensatz, lesen wir daraus die Version aus und geben diese als Funktionswert zurück. Anderenfalls geben wir eine entsprechende Meldung aus.
Public Function GetVersion(strFrontend As String) As Long Dim db As DAO.Database Dim rst As DAO.Recordset Set db = OpenDatabase(strFrontend) Set rst = db.OpenRecordset("SELECT Version FROM tblVersion") If Not rst.EOF Then GetVersion = rst!Version Else MsgBox "Keine Versionsnummer in ''" & strFrontend & "'' gefunden." End If End Function
Listing 1: Einlesen der Version, längere Variante
Die zweite Version von GetVersion ist noch ein wenig kürzer (siehe Listing 2). Hier integrieren wir den Namen der zu untersuchenden Datenbank in die OpenRecordset-Methode. Dadurch sparen wir das Initialisieren des Database-Objekts für die Frontend-Datenbank.
Public Function GetVersion1(strFrontend As String) Dim rst As DAO.Recordset Set rst = CurrentDb.OpenRecordset("SELECT Version FROM tblVersion IN ''" & strFrontend & "''") If Not rst.EOF Then GetVersion1 = rst!Version Else MsgBox "Keine Versionsnummer in ''" & strFrontend & "'' gefunden." End If End Function
Listing 2: Einlesen der Version, kürzere Variante
Egal, welche Version wir wählen – wir können damit sowohl die Version des vorliegenden Frontends als auch die der Version vom Server ermitteln und dann in per If…Then-Bedingung ermitteln, welche Aktion durchzuführen ist. Dazu nutzen wir die neue Version der Prozedur StartDb aus Listing 3.
Public Function StartDb() Dim strCurrent As String Dim strServer As String Dim lngVersionCurrent As Long Dim lngVersionServer As Long strCurrent = CurrentProject.Path & "\Frontend.accdb" strServer = CurrentProject.Path & "\Server\Frontend.accdb" lngVersionCurrent = GetVersion(strCurrent) lngVersionServer = GetVersion1(strServer) If lngVersionCurrent < lngVersionServer Then MsgBox "Muss von Version ''" & lngVersionCurrent & "'' auf Version ''" & lngVersionServer & "'' aktualisiert werden." Else MsgBox "Keine Aktualisierung nötig." End If End Function
Listing 3: Vergleichen der Version und Ausgabe der zu treffenden Maßnahme
Sie schreibt die Pfade zu den zu untersuchenden Frontends in die Variablen strCurrent und strServer. Dann nutzt sie die Funktion GetVersion, um die Versionen zu den beiden Dateien zu ermitteln und in die Variablen lngVersionCurrent und lngVersionServer zu schreiben. Schließlich prüft sie, ob das Frontend vom Server eine größere Versionsnummer hat als das vorliegende und gibt eine entsprechende Meldung aus.
Bevor wir uns ansehen, wie wir das Frontend ersetzen, erstellen wir noch schnell eine kleine Hilfsprozedur, mit der wir die Versionsstände der Frontends jeweils auf 1 und 2 einstellen. So brauchen wir diese nicht immer manuell einzustellen:
Public Function SetVersions() Dim strFrontend As String strFrontend = CurrentProject.Path & "\Frontend.accdb" CurrentDb.Execute "UPDATE tblVersion IN ''" _ & strFrontend & "'' SET Version = 1", dbFailOnError strFrontend = CurrentProject.Path _ & "\Server\Frontend.accdb" CurrentDb.Execute "UPDATE tblVersion IN ''" _ & strFrontend & "'' SET Version = 2", dbFailOnError End Function
Ersetzen der vorliegenden Version durch die neue Version
In der Funktion StartDb ersetzen wir die Anweisung im If-Teil der If…Then-Bedingung wie folgt:
Public Function StartDb() ... If lngVersionCurrent < lngVersionServer Then ReplaceFrontend strCurrent, strServer, _ lngVersionCurrent, lngVersionServer Else ... End If End Function
Wir rufen also eine Prozedur namens ReplaceFrontend auf, der wir den Pfad zu dem aktuellen Frontend, den Pfad zum neuen Frontend sowie die Versionen des aktuellen und des neuen Frontends als Parameter übergeben (siehe Listing 4).
Public Sub ReplaceFrontend(strCurrent As String, strServer As String, lngVersionCurrent As Long, lngVersionServer As Long) Dim strCopy As String MsgBox "Muss von Version ''" & lngVersionCurrent & "'' auf Version ''" & lngVersionServer & "'' aktualisiert werden." strCopy = Replace(strCurrent, ".accdb", "_" & lngVersionCurrent & ".accdb") On Error Resume Next Kill strCopy On Error GoTo 0 Name strCurrent As strCopy FileCopy strServer, strCurrent End Sub
Listing 4: Sichern der aktuellen Version und ersetzen durch die neue Version
Wir haben die Meldung über das notwendige Ersetzen des Frontends aus StartDb in diese Prozedur übertragen. Dann ermitteln wir den Namen der Sicherheitskopie, der aus dem Namen der aktuellen Version und dem Zusatz der bisherige Version entspricht. Aus Frontend.accdb wird für die Version 1 beispielsweise Frontend_1.accdb.
Wir löschen eine eventuell bereits vorhandene Datei dieses Namens mit der Kill-Anweisung (dies bei deaktivierter Fehlerbehandlung, falls keine Datei diesen Namens vorhanden ist) und benennen die aktuelle Version mit der Name-Anweisung in den soeben ermittelten Dateinamen um. Dann können wir die FileCopy-Anweisung nutzen, um das Frontend vom Server auf den Client zu übertragen.
Starten des aktuellen Frontends
Nachdem wir entweder herausgefunden haben, dass wir bereits mit der aktuellsten Version des Frontends arbeiten oder diese auf den aktuellen Stand gebracht haben, können wir dieses Starten.
Das erledigen wir mit der folgenden Prozedur:
Public Sub StartFrontend(strFrontend As String) Dim objAccess As Access.Application Set objAccess = _ CreateObject("Access.Application") With objAccess .OpenCurrentDatabase strFrontend .Visible = True .UserControl = True End With End Sub
Man könnte meinen, dass es auch ein einfacher Aufruf der Shell-Methode wie folgt tun würde:
Shell "MSAccess.exe " & strFrontend
Allerdings ruft dieser nur die angegebene Datenbankdatei auf, aber holt diese nicht in den Vordergrund. Das heißt, dass der Benutzer sie nicht direkt sieht, was verwirrend sein könnte. Also wählen wir den obigen Ansatz. Hier erstellen wir eine neue Access-Instanz mit dem Typ Access.Application. Mit dieser öffnen wir mit OpenCurrentDatabase die mit strFrontend übergebene Datenbankdatei. Dann machen wir diese Instanz sichtbar und aktivieren sie durch das Setzen der Eigenschaft UserControl auf den Wert True.
Frontend-Updater-Datenbank unsichtbar öffnen
Aktuell starten wir die Frontend-Updater-Datenbank FrontendUpdater.accdb noch durch einen Doppelklick. Der Nachteil ist, dass dadurch mehr oder weniger kurz das Access-Fenster angezeigt wird, bevor die eigentliche Anwendung gestartet wird. Das ist an sich kein Problem, sieht aber nicht schön aus.
Abhilfe können wir mit einem kleinen VB-Skript schaffen, das wir in einer Datei namens Start.vbs hinterlegen (siehe Bild 2).
Bild 2: VB-Skript zum Starten der Frontend-Updater-Datenbank
In VB-Skript können wir beim Deklarieren keine Dateitypen angeben, daher sieht das Folgende etwas ungewohnt aus:
Dim fso Dim strFolder Dim objAccess Dim strFrontendUpdater Set fso = CreateObject("Scripting.FileSystemObject") strFolder = fso.GetParentFolderName(WScript.ScriptFullName) strFrontendUpdater = strFolder & "\FrontendUpdater.accdb" Set objAccess = CreateObject("Access.Application") objAccess.OpenCurrentDatabase strFrontendUpdater
Im ersten Teil ermitteln wir mit dem FileSystemObject und seiner Funktion GetParentFolderName das Verzeichnis des Skripts, das sich im gleichen Verzeichnis wie die Datei FrontendUpdater.accdb befinden sollte. Damit setzen wir in strFrontendUpdater den vollständigen Pfad zur zu öffnenden Datenbankdatei zusammen.
Wir erstellen dann eine Access-Instanz und öffnen mit OpenCurrentDatabase die Datenbankdatei aus strFrontendUpdater. Da wir hier nicht die Eigenschaft Visible auf True einstellen, wird das Access-Fenster gar nicht erst eingeblendet und.
So kann das AutoExec-Makro unsichtbar die Funktion StartDb aufrufen, die den Update-Prozess und den Start des Frontends erledigt.
Der Benutzer soll jetzt also nicht mehr die Datei Frontend.accdb starten, sondern Start.vbs.
Sicherstellen, dass das Frontend nicht geöffnet ist
Es könnte ein Problem auftauchen, wenn das vorliegende Frontend bereits geöffnet ist, wenn wir die Frontend-Update-Datenbank aufrufen. Wenn wir versuchen, eine geöffnete Access-Datenbank umzubenennen, erhalten wir die Fehlermeldung aus Bild 3. Die Fehlermeldung wird durch die folgende Anweisung ausgelöst:
Bild 3: Fehlermeldung, wenn das Frontend bereits geöffnet ist
Name strCurrent As strCopy
Hier haben wir nun beispielsweise die folgenden Möglichkeiten:
- Wir prüfen vorher, ob das Frontend bereits geöffnet ist, geben eine Meldung aus, dass dieses zunächst geschlossen werden muss und beenden den Frontend-Updater.
- Wir reagieren erst bei Auftreten des Fehlers und weisen den Benutzer darauf hin, dass ein Update nicht möglich ist, weil die Frontend-Datenbank bereits geöffnet ist.
Wir stellen beide Varianten vor.
Prüfen, ob das Frontend bereits geöffnet ist
Um zu prüfen, ob eine Datenbankdatei bereits in Benutzung ist, können wir einen einfachen Test durchführen. Wir versuchen dabei, diese Datenbank für den binären Zugriff exklusive zu öffnen. Das gelingt nur, wenn die Datenbankdatei noch gar nicht geöffnet ist.
Diese Aufgabe übernimmt die Funktion IsDatabaseInUse aus Listing 5 für uns. Dieser übergeben wir als Parameter den Pfad zu der zu untersuchenden Datenbank. Dann versucht die Funktion, die Datenbank mit der Open-Anweisung exklusiv zum Schreiben zu öffnen.
Function IsDatabaseInUse(strDBPath As String) As Boolean Dim intFile As Integer On Error Resume Next intFile = FreeFile Open strDBPath For Binary Access Read Write Lock Read Write As intFile If Err.Number <> 0 Then IsDatabaseInUse = True Err.Clear Else IsDatabaseInUse = False Close intFile End If End Function
Listing 5: Prüfen, ob eine Datenbank bereits in Verwendung ist
Wenn die Datenbank bereits geöffnet ist, führt das zu einem Fehler. In diesem Fall geben wir den Wert True zurück. Anderenfalls geben wir den Wert zurück und beenden den schreibenden Zugriff.
Diese Funktion integrieren wir direkt in die beim Start aufgerufene Funktion StartDb (siehe Listing 6). Hier finden wir zunächst heraus, ob das Frontend überhaupt aktualisiert werden soll.
Public Function StartDb() ... If lngVersionCurrent < lngVersionServer Then If IsDatabaseInUse(strCurrent) = True Then MsgBox "Die Datenbank muss aktualisiert werden. Sie ist jedoch noch geöffnet. Bitte schließen " _ & "Sie die Datenbank und starten Sie erneut.", vbOKOnly + vbCritical, "Datenbank schließen" DoCmd.Quit Else ReplaceFrontend strCurrent, strServer, lngVersionCurrent, lngVersionServer End If Else MsgBox "Keine Aktualisierung nötig." & vbCrLf & "Version aktuell: " & lngVersionCurrent & vbCrLf _ & "Version Server: " & lngVersionServer End If StartFrontend strCurrent DoCmd.Quit End Function
Listing 6: Einbinden der Prüfung, ob die Datenbank bereits geöffnet ist
Falls ja, folgt als nächster Schritt die Prüfung, ob die Datenbank geöffnet ist. In diesem Fall geben wir eine entsprechende Meldung aus. Wenn wir die Frontend-Updater-Datenbank mit dem oben vorgestellten Skript gestartet haben, erscheint diese Meldung völlig ohne Anzeige des übrigen Access-Fensters.
Nach dem Anzeigen der Meldung wird die Frontend-Updater-Datenbank beendet. Anderenfalls wird das Update wie geplant durchgeführt und das Frontend anschließend gestartet.
Downloads zu diesem Beitrag
Enthaltene Beispieldateien:
Frontend.zip