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.
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.
Dann ruft sie die API-Funktion RegSetValueExString auf und übergibt alle relevanten Informationen. Das Ergebnis landet im Rückgabewert der Wrapperfunktion.
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