Registry per VBA, 32- und 64-Bit

Die Registry von Windows ist für den einen oder anderen ein Buch mit sieben Siegeln. Tatsache ist: Dort landen manche wichtigen Informationen, die Sie gegebenenfalls einmal mit VBA auslesen wollen, oder Sie wollen dafür sorgen, dass per VBA bestimmte Elemente in der Registry angelegt werden. Wir stellen einige Routinen vor, die Ihnen die Arbeit mit der Registry erleichtern. Gleichzeitig liefern wir den Code in 64-Bit-kompatibler Form.

Per VBA mit der Registry arbeiten

Wann und wo Sie auf eine Anforderung stoßen, die mit dem Lesen oder Schreiben von Registry-Werten per VBA zu tun hat, lassen wir einmal dahingestellt. Wichtig ist allein, dass Sie nach der Lektüre dieses Beitrags auf den Ernstfall vorbereitet sind!

Daher schauen wir uns in diesem Beitrag nicht nur einen Satz von Funktionen an, welche wiederum die API von Windows für den lesenden und schreibenden Zugriff auf die Registry nutzen, sondern liefern auch noch eine Reihe von Beispielen für die Anwendung dieser Funktionen.

Unser konkreter Anlass, uns mit dem Thema VBA-Zugriff auf die Registry auseinanderzusetzen, war der Beitrag Optionen per VBA für Access 2019 (www.access-im-unternehmen.de/1320). Hier haben wir untersucht, welche Einstellungen des Dialogs Access-Optionen in der Registry landen. Um schnell prüfen zu können, ob sich nach dem Ändern einer Option unter Access einer der Einträge in der Registry geändert hat, haben wir eine Tabelle erstellt, die alle bereits in der Registry vorhandenen Werte enthält. Diese Werte haben wir initial einmal aus dem entsprechenden Bereich der Registry ausgelesen.

Anschließend haben wir nach jeder Änderung eine Prozedur durchlaufen lassen, welche alle Werte der Registry mit den bestehenden Werten in der Tabelle abgeglichen und eventuelle Unterschiede oder gar neue Werte im Direktbereich ausgegeben hat.

Access-Einstellungen in der Registry

Den Bereich der Registry, der die Access-Optionen enthält, findet sich unter HKEY_CURRENT_USER im Ordner SOFTWARE\Microsoft\Office\16.0\Access\Settings. Der Screenshot aus Bild 1 zeigt einige der Einstellungen.

Dieser Bereich der Registry speichert die meisten der Access-Optionen.

Bild 1: Dieser Bereich der Registry speichert die meisten der Access-Optionen.

Auf diesen Ordner wollen wir uns in diesem Beitrag konzentrieren – wie lesen die Informationen aus, legen neue Werte an, erstellen Unterordner und so weiter. Vorher schauen wir uns allerdings noch die dazu notwendigen Funktionen im Modul mdlRegistry an.

Das Modul mdlRegistry und die Registry-Funktionen

Da das Modul die Deklaration von API-Funktion enthält sowie dafür vorgesehene Wrapperfunktionen, benötigen wir auch einige Enum– und Const-Elemente. Diese sehen wie folgt aus:

Public Enum Key
     HKEY_CLASSES_ROOT = &H80000000
     HKEY_CURRENT_USER = &H80000001
     HKEY_LOCAL_MACHINE = &H80000002
     HKEY_USERS = &H80000003
End Enum
Public Enum DataType
     dtString = 1
     dtNumber = 4
End Enum
Global Const ERROR_NONE = 0
Global Const ERROR_BADDB = 1
Global Const ERROR_BADKEY = 2
Global Const ERROR_CANTOPEN = 3
Global Const ERROR_CANTREAD = 4
Global Const ERROR_CANTWRITE = 5
Global Const ERROR_OUTOFMEMORY = 6
Global Const ERROR_INVALID_PARAMETER = 7
Global Const ERROR_ACCESS_DENIED = 8
Global Const ERROR_INVALID_PARAMETERS = 87
Global Const ERROR_NO_MORE_ITEMS = 259
Global Const KEY_ALL_ACCESS = &H3F
Global Const REG_OPTION_NON_VOLATILE = 0

Neben diesen Elementen benötigen wir die Deklarationen der eigentlichen API-Funktionen. Dabei handelt es sich um 13 Funktionen, deren Deklaration Sie in Listing 1 finden.

Declare PtrSafe Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
Declare PtrSafe Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, _
     ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, _
     ByVal samDesired As Long, ByVal lpSecurityAttributes As Long, phkResult As Long, lpdwDisposition As Long) As Long
Declare PtrSafe Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, _
     ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
Declare PtrSafe Function RegQueryValueExString Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, _
     ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, ByVal lpData As String, lpcbData As Long) As Long
Declare PtrSafe Function RegQueryValueExLong Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, _
     ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, lpData As Long, lpcbData As Long) As Long
Declare PtrSafe Function RegQueryValueExNULL Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, _
     ByVal lpValueName As String, ByVal lpReserved As Long, lpType As Long, ByVal lpData As Long, lpcbData As Long) As Long
Declare PtrSafe Function RegSetValueExString Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, _
     ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, ByVal lpValue As String, _
     ByVal cbData As Long) As Long
Declare PtrSafe Function RegSetValueExLong Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, _
     ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpValue As Long, ByVal cbData As Long) _
     As Long
Declare PtrSafe Function RegDeleteKey& Lib "advapi32.dll" Alias "RegDeleteKeyA" (ByVal hKey As Long, _
     ByVal lpSubKey As String)
Declare PtrSafe Function RegDeleteValue& Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, _
     ByVal lpValueName As String)
Declare PtrSafe Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" (ByVal hKey As Long, _
     ByVal lpSubKey As String, phkResult As Long) As Long
Declare PtrSafe Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, _
     ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, ByVal lpReserved As Long, _
     ByVal lpClass As String, lpcbClass As Long, lpftLastWriteTime As Any) As Long
Declare PtrSafe Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" (ByVal hKey As Long, _
     ByVal dwIndex As Long, ByVal lpValueName As String, lpcbValueName As Long, ByVal lpReserved As Long, _
     lpType As Long, lpData As Byte, lpcbData As Long) As Long

Listing 1: Die benötigten API-Funktionen

Wrapperfunktion zum Auslesen von Werten

Die erste Wrapperfunktion soll das Auslesen eines Wertes für einen Eintrag eines Schlüssels/Unterschlüssels ermöglich. Diese Funktion erwartet drei Parameter:

  • lngKey: Einen der Werte der Enum-Auflistung Key, zum Beispiel HKEY_CURRENT_USER
  • strKeyName: Den Unterschlüssel, zum Beispiel SOFTWARE\Microsoft\Office\16.0\Access\Settings
  • strValueName: Den Namen des zu ermittelnden Elements, zum Beispiel Form Template

Die Funktion nutzt die API-Funktion RegOpenKeyEx, um den angegebenen Schlüssel zu öffnen. Dann liest sie mit RegQueryValueEx das gewünschte Element aus. Der Parameter varValue nimmt den gesuchten Wert auf.

Dieser wird in den folgenden Schritten noch untersucht und von nicht erwünschten Zeichen befreit. Schließlich wird dieser Wert zurückgegeben und der Registry-Schlüssel mit der API-Funktion RegCloseKey wieder geschlossen (siehe Listing 2)

Public Function QueryValue(lngKey As Key, strKeyName As String, strValueName As String)
     Dim lngRetVal As Long
     Dim hKey As Long
     Dim varValue As Variant
     lngRetVal = RegOpenKeyEx(lngKey, strKeyName, 0, KEY_ALL_ACCESS, hKey)
     lngRetVal = QueryValueEx(hKey, strValueName, varValue)
     varValue = Trim(varValue)
     If varValue <> "" Then
         If Asc(Right(varValue, 1)) > 127 Then
             varValue = Left(varValue, Len(varValue) - 1)
         End If
     End If
     If varValue <> "" Then
         If Asc(Right(varValue, 1)) < 21 Then
             varValue = Left(varValue, Len(varValue) - 1)
         End If
     End If
     QueryValue = varValue
     RegCloseKey hKey
End Function

Listing 2: Die Wrapperfunktion QueryValue

Wrapperfunktion zum Auslesen aller Einträge eines Schlüssels

Die Wrapperfunktion EnumKeyValues ermittelt alle Einträge der mit den Parametern übergebenen Schlüssel/Unterschlüssel-Kombination.

Die Funktion erwartet diese beiden Parameter:

  • lngKey: Einen der Werte der Enum-Auflistung Key, zum Beispiel HKEY_CURRENT_USER
  • strKeyName: Den Unterschlüssel, zum Beispiel SOFTWARE\Microsoft\Office\16.0\Access\Settings

Die Funktion öffnet wieder mit RegOpenKey den Schlüssel. Dann startet sie eine Do…Loop-Schleife, die dann endet, wenn die Funktion RegEnumValue den Wert 0 zurückgibt. Diese erhält in der Schleife wiederum einen Index als zweiten Parameter, der mit jedem Schleifendurchlauf erhöht wird, sowie als dritten Parameter eine mit 255 Leerzeichen vorbelegte Zeichenkette. Diese wird, sofern der aktuelle Index aus lngIndex ein gültiges Ergebnis hergibt, mit dem Namen des Eintrags gefüllt. Der Inhalt von strSave wird dann mit der Funktion StripTerminator von vbNullChar-Zeichen befreit und an das Array strKeys angehängt. Dieses gibt die Funktion schließlich als Ergebnis zurück:

Public Function EnumKeyValues(lngKey As Key, _
         strSubkey As String) As String()
     Dim hKey As Long
     Dim lngIndex As Long
     Dim strSave As String
     Dim strKeys() As String
     RegOpenKey lngKey, strSubkey, hKey
     Do
         strSave = String(255, 0)
         If RegEnumValue(hKey, lngIndex, strSave, 255, _
                 0, ByVal 0&, ByVal 0&, ByVal 0&) <> 0 Then
             Exit Do
         Else
             ReDim Preserve strKeys(lngIndex)
             strKeys(lngIndex) = StripTerminator(strSave)
             lngIndex = lngIndex + 1
         End If
     Loop
     RegCloseKey hKey
     EnumKeyValues = strKeys
End Function

Die Hilfsfunktion StripTerminator

Die in der vorher vorgestellten Funktion verwendete Hilfsfunktion StripTerminator befreit die übergebene Zeichenkette von Vorkommen des Zeichens vbNullChar. Die API-Funktion RegEnumValue ersetzt in der String-Variablen strSave die 255 Leerzeichen durch das Ergebnis und füllt die übrigen Zeichen mit dem Zeichen vbNullChar auf. Diese wollen wir entfernen und verwenden dazu die Funktion StripTerminator. Die Funktion erwartet die zu untersuchende Zeichenkette als Parameter und gibt die überarbeitete Zeichenkette zurück. Sie sucht im ersten Schritt nach dem ersten Vorkommen von vbNullChar und geht davon aus, dass hier das eigentliche Ergebnis beendet ist. Die Suche erledigt sie mit der Funktion InStr, welche die Position des ersten Vorkommens zurückliefert. Ist das Ergebnis größer als 0, wurde ein vbNullChar-Zeichen gefunden. Dann schneidet die Funktion alle Zeichen von diesem Zeichen an ab und trägt nur die davor liegenden Zeichen in den Rückgabewert der Funktion ein. Andernfalls erhält dieser einfach die übergebene Zeichenkette als Ergebnis:

Private Function StripTerminator(strInput As String) _
         As String
     Dim lngPosNull As Long
     lngPosNull = InStr(1, strInput, vbNullChar)
     If lngPosNull > 0 Then
         StripTerminator = Left$(strInput, lngPosNull - 1)
     Else
         StripTerminator = strInput
     End If
End Function

Wrapperfunktion zum Auslesen aller Unterschlüssel eines Schlüssels

Wenn Sie alle Schlüssel unterhalb eines anderen Schlüssels auslesen möchten, können Sie dazu die Wrapperfunktion EnumRegKeys nutzen (siehe Listing 3). Diese Funktion erwartet die gleichen Parameter wie die Funktion EnumKeyValues.

Public Function EnumRegKeys(lngKey As Key, strKeyName As String) As String()
     Dim hKey As Long
     Dim lngIndex As Long
     Dim strSave As String
     Dim strKeys() As String
    RegOpenKey lngKey, strKeyName, hKey
     Do
         strSave = String(255, 0)
         If RegEnumKeyEx(hKey, lngIndex, strSave, 255, 0, vbNullString, ByVal 0&, ByVal 0&) <> 0 Then
             ReDim Preserve strKeys(lngIndex)
             strKeys(lngIndex) = StripTerminator(strSave)
             lngIndex = lngIndex + 1
         Else
             Exit Do
         End If
     Loop
     RegCloseKey hKey
     EnumRegKeys = strKeys
End Function

Listing 3: Die Wrapperfunktion zum Auslesen aller untergeordneten Schlüssel

Sie öffnet mit der API-Funktion RegOpenKey den zu untersuchenden Schlüssel. Dann durchläuft sie eine Do Loop-Schleife solange, bis kein weiterer untergeordneter Schlüssel mehr gefunden werden kann.

Dabei füllt sie die Variable strSave, die mit dem jeweiligen Namen des Unterschlüssels gefüllt werden soll, mit 255 Leerzeichen. Dann prüft sie in einer If…Then-Bedingung das Ergebnis der API-Funktion RegEnumKeyEx.

Ist dieses 0, wurde für den Index mit dem Wert aus lngIndex ein Unterschlüssel gefunden.

Dann vergrößert die Funktion im If-Teil der Bedingung das Array strKeys für die Ergebnisse auf den aktuellen Index aus lngIndex und fügt den Namen des mit strSave ermittelten Unterschlüssels an der neuen Position in das Array ein. Zuvor entfernt die Funktion StripTerminator wieder die überzähligen vbNullChar-Zeichen aus strSave.

Liefert RegEnumKeyEx für den aktuellen Index den Wert 0 zurück, ist kein weiterer Unterschlüssel mehr vorhanden und der untersuchte Schlüssel wird mit RegCloseKey geschlossen.

Wrapperfunktion zum Erzeugen eines neuen Schlüssels

Einen neuen Schlüssel anzulegen bedeutet in der Sprache der Registry, dass Sie einen neuen Ordner im linken Bereich erstellen wollen. Das gelingt viel einfacher als bei Ordnern im Dateisystem.

Sie brauchen einfach nur den Hauptschlüssel festzulegen und dann den Unterschlüssel zu definieren – egal, ob dieser noch übergeordnete Schlüssel hat. Sie können damit auch in einem Schritt mehrere verschachtelte Schlüssel anlegen, also beispielsweise Test1\Test2\Test3.

Schauen wir uns die Funktion an, die dies erlaubt – siehe Listing 4. Die Funktion erwartet zwei Parameter:

Public Function CreateNewKey(lngKey As Key, strNewSubkey As String)
     Dim hNewKey As Long
     Dim lngRetVal As Long
     lngRetVal = RegCreateKeyEx(lngKey, strNewSubkey, 0&, vbNullString, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, _
         0&, hNewKey, lngRetVal)
     RegCloseKey hNewKey
End Function

Listing 4: Die Wrapperfunktion zum Erstellen eines Wertes

  • lngKey: der Hauptschlüssel, also ein Element der Enum-Auflistung Key wie HKEY_CURRENT_USER
  • strNewSubkey: der neu anzulegenden Schlüssel

Die Funktion ruft ohne Umschweife die API-Funktion RegCreateKeyEx auf, der sie den ersten und den zweiten Parameter neben einigen anderen Werten als Funktionsparameter übergibt.

Im Gegensatz zu den ersten Wrapperfunktionen taucht hier kein Aufruf der Funktion RegOpenKey auf, was nicht nötig ist, da wir ja erst einen Schlüssel erstellen wollen.

Dennoch wird beim Anlegen der Schlüssel “geöffnet”, was dazu führt, dass dieser auch geschlossen werden muss.

Die API-Funktion RegCreateKeyEx liefert daher mit dem Parameter hNewKey ein Handle auf den neuen Schlüssel zurück, das dann mit RegCloseKey zum Schließen verwendet werden kann.

Wrapperfunktion zum Erzeugen eines neuen Eintrags mit Wert

Nun wollen wir einem bestehenden Schlüssel einen neuen Eintrag mit Wert hinzufügen. Dazu nutzen wir eine weitere Wrapperfunktion namens AddKeyAndValue (siehe Listing 5). Diese erwartet neben den bereits bekannten Parametern drei weitere Parameter:

Public Function AddKeyAndValue(lngKey As Key, strSubkey As String, strValueName As String, strValue As String, _
         lngValueType As DataType)
     Dim lngRetVal As Long
     Dim hKey As Long
     lngRetVal = RegOpenKeyEx(lngKey, strSubkey, 0, KEY_ALL_ACCESS, hKey)
     lngRetVal = SetValueEx(hKey, strValueName, lngValueType, strValue)
     RegCloseKey hKey
End Function 

Listing 5: Die Wrapperfunktion zum Anlegen eines neuen Eintrags mit Wert

  • strValueName: Name des neuen Eintrags
  • strValue: Wert des neuen Eintrags
  • lngValueType: Datentyp des neuen Eintrag aus der Enum-Auflistung DataType

Damit ausgestattet öffnet die Funktion mit der API-Funktion RegOpenKeyEx erst einmal den mit den ersten beiden Parametern angegebenen Unterschlüssel und speichert das Handle auf diesen Schlüssel in der Variablen hKey. Danach ruft es eine weitere API-Funktion namens SetValueEx auf. Diese erhält Handle, Eintrag, Wert und Datentyp und legt den neuen Eintrag an.

Wrapperfunktion zum Einstellen eines Wertes für einen vorhandenen Eintrag

Vielleicht möchten Sie auch einen vorhandenen Eintrag mit einem neuen Wert versehen. Dabei unterstützt uns die Wrapperfunktion SetValue (siehe Listing 6).

Public Function SetValue(ByVal lngKey As Long, strSubkey As String, strValueName As String, varValue As Variant, _
         lngDataType As DataType) As Long
     Dim lngValue As Long
     Dim strValue As String
     Dim lngRetVal As Long
     Dim hkey As Long
     lngRetVal = RegOpenKeyEx(lngKey, strSubkey, 0, KEY_ALL_ACCESS, hkey)
     Select Case lngDataType
         Case dtString
             strValue = varValue
             SetValue = RegSetValueExString(hkey, strValueName, 0&, lngDataType, strValue, Len(strValue))
         Case dtNumber
             lngValue = varValue
             SetValue = RegSetValueExLong(hkey, strValueName, 0&, lngDataType, lngValue, 4)
     End Select
     RegCloseKey hKey
End Function

Listing 6: Die Wrapperfunktion zum Einstellen eines Wertes für einen vorhandenen Eintrag

Diese erwartet die gleichen Parameter wie die Funktion AddKeyAndValue. Aus den beiden Parametern für den Haupt- und den Unterschlüssel ermittelt sie zunächst mit der API-Funktion RegOpenKeyEx ein Handle auf diesen Schlüssel.

Dann untersucht sie den mit dem letzten Parameter übergebenen Datentyp in einer Select Case-Bedingung. Im Fall des String-Datentyps schreibt Sie den Inhalt des Parameters varValue in die Variable strValue.

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

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar