Visual Basic: The Registry Made Easy

VBA Hacker

A Classy Way to Bypass the Awkward Registry API

By Romke Soldaat

Compared with the bad old INI files, the Windows registry is not only a safer place for system and application settings, it also offers more ways to store different types of data, and lets you organize your settings in a hierarchical manner. But, unlike the straightforward functions that deal with INI files, the registry API is more complex. You can’t just read and write data with a single function call – you must open a key before you can access it, and close the key when you’re done with it. That’s why Microsoft gave Visual Basic programmers a set of one-step routines that simplify their lives: SaveSetting, GetSetting, GetAllSettings, and DeleteSetting. When you use these statements and functions, you don’t have to worry about opening and closing keys; VB takes care of that in the background. The only problem is these routines are too limited for power users.

VB restricts registry access to the "HKEY_CURRENT_USERSoftwareVB and VBA Program Settings" key. There’s no way you can use VB to retrieve a value from any other location. Apart from that, VB functions can only cope with string settings. SaveSetting stores numbers as strings, whether you like it or not. If you use GetSetting to retrieve a numeric (i.e. DWORD) value, you’re punished with an "Invalid procedure call or argument" error. If you use GetAllSettings, and one or more values under the specified key isn’t a string, your code crashes with the same error. And if you call DeleteSetting to delete a key that contains subkeys, again you’re accused of being an ignorant programmer. That’s bad.

Wrapping the Win API

These VB limitations force serious developers to stick with the Windows registry API. Fortunately, you can avoid the complexity of these functions by wrapping the nitty-gritty into a class module. Once the class is part of your application, you can create an object, access its properties and methods, and forget about API functions altogether.

I’ve explored many Web sites and books for registry classes and libraries, but didn’t find a single one that came close to my demands. Most limit themselves to getting and setting string values, just like VB. A few allow for the storage of numeric values, but none I’ve seen let you deal with environment variables or arrays. And all of them make the mistake of allowing you to overwrite values with new ones of a different data type.

Most "solutions" (including the ones produced by Microsoft!) were built on the premise that everything in the registry is accessible for any operation. However, under Windows NT/ 2000, parts of the registry may have restricted access rights. So if you simply want to read a value, you shouldn’t ask for full access. You might not get any.

Therefore, I decided to write my own RegOp (short for Registry Operation) class. It’s included in the download file (see end of article for details). This article explains how it works, and how you can use it in your applications.

First, however, let’s look at how the registry API works. An extensive description of each API function and its parameters would go beyond the scope of this article, but you can find them on the MSDN CD-ROM, and on the Microsoft Web site. Refer to Listing One for the declarations of the functions involved in the class.

Registry Basics

The Windows registry is a database that contains settings for the operating system and installed applications. These settings are stored under a number of root keys. The RegOp class supports the three roots you’re most likely to use; see FIGURE 1.

Key name
Abbr.
Contents
Value

HKEY_CLASSES_ROOT
HKCR
Names and properties of registered file types and information about ActiveX components.
&H80000000

HKEY_CURRENT_USER
HKCU
Configuration information for the user profile of the currently logged on user.
&H80000001

HKEY_LOCAL_MACHINE
HKLM
Configuration data for the local computer and global software settings.
&H80000002

FIGURE 1: Registry roots supported by the RegOp class.

Under each key, the registry contains a tree of subkeys, and each subkey can hold values, identified by unique value names. Each subkey can also hold one unnamed "default" value (which must be a string). Values can be stored in a number of formats. FIGURE 2 provides an overview of the data types supported by the RegOp class. There are other data types, but they’re irrelevant for VB programmers, so I’ll ignore them here.

Data types

Used for storage of

Constant value

REG_SZ

A fixed-length text string. Boolean (True or False) values and other short text values usually have this data type.

1

REG_EXPAND_SZ

A variable-length text string that can include variables that are resolved when an application or service uses the data.

2

REG_DWORD

Data represented by a number that is 4 bytes (32 bits) long. Also suitable for Boolean (1 or 0) values.

4

REG_MULTI_SZ

Multiple text strings formatted as an array of null-terminated strings, and terminated by two null characters.

7

FIGURE 2: Registry data types supported by RegOp.

Registry ethics. If you have access to all parts of the registry, you probably don’t want to use the "SoftwareVB and VBA Program Settings" key under HKCU. The commonly accepted rule is that you create a key with your company name under the "Software" key in both HKLM and HKCU. For each application you create a separate key under your company key. The HKLM branch is used for application-specific information (usually created at setup), and user-specific settings go under HKCU. Here’s the blueprint:

HKEY_LOCAL_MACHINESoftwareMy CompanyMy Application
HKEY_CURRENT_USERSoftwareMy CompanyMy Application
Opening and Closing Keys

Before you can access a registry key, you must open it. Two API functions let you do this: RegOpenKeyEx and RegCreateKeyEx. The class uses RegCreateKeyEx, because this function automatically creates a key if it doesn’t exist, and then opens the key. If the key already exists, the function simply opens it.

Each of these functions gives you a handle to the open key. You need this handle to access the information under the key. Once you’re done, you pass the handle to the RegCloseKey function.

Access rights and security. Under Windows NT and 2000, a system administrator may restrict access to parts of the registry. The level of restriction is determined by a combination of parameters. If the current Windows version is NT 4.0 or 2000, the RegOp class only asks for access rights that are relevant for the desired operation: KEY_READ to query and enumerate values, KEY_WRITE to create keys and values, and KEY_ALL_ACCESS to delete values or keys. The values of these constants are shown in Listing One.

Setting, Getting, and Deleting Values

To write a value to the registry, use the RegSetValueEx function. Specify the value name, the data type (one of the values shown in FIGURE 2), the data itself, and a value that specifies the size (in bytes) of the data you’re storing. When you save a DWORD value, you specify the data by reference; in all other cases you do it by value.

The RegQueryValueEx function retrieves a value. If you know the data type of the value, you can specify the type directly and retrieve the value from a buffer you’ve created. If you’re not sure about the data type, call the function first to receive the data type in a variable, then call the function again using this variable as the parameter that specifies the data type. The RegOp class, however, bypasses RegQueryValueEx. The first time you query a value, the class retrieves all values under the specified key at once, using the RegEnumValue function. (More about that later.)

It takes a simple call to the RegDeleteValue function to get rid of a specified value. There are no complicated parameters involved.

It’s a bit trickier to delete an entire key. The RegDeleteKey function works fine under Windows 95/98, but under Windows NT/2000 this function won’t delete a key if it contains subkeys. The standard solution is to recursively delete a key. In other words, delete the last branch that doesn’t have subkeys, and then go up the tree, deleting every branch on your way until you get to the key you want to delete. That’s a cumbersome process. The RegOp class takes a different approach by calling the SHDeleteKey key in the Shlwapi.dll file. This function deletes a key and all its values and subkeys, even under Windows NT/2000. There’s a small sticking point: SHDeleteKey works only under Windows NT 4.0, if Internet Explorer 4.0 is also installed (Shlwapi.dll comes standard with Windows 2000). The class checks to see if Shlwapi (short for Shell Lightweight API) is available. If so, it uses the SHDeleteKey function; if not, the SHDeleteKey function is called.

Enumerations

VB’s GetAllSettings function enumerates settings under a specified key, and returns a two-dimensional variant array containing the value names and their associated data. As I mentioned earlier, the function can only access a small portion of the registry, and fails as soon as it finds a value that isn’t a string. But we should be able to do one better.

The registry API comes with two enumeration functions: RegEnumKeyEx enumerates all subkeys of a key, RegEnumValue enumerates all values. Both functions have a dwIndex parameter. The common way to enumerate keys or values is to start with a zero dwIndex value, and increment this value until there are no more subkeys or values. The class does things differently. Before any enumerations take place, it calls the RegQueryInfoKey function, which returns a host of details about a key, including the number of subkeys and values. This allows the class to use a simple For structure to get all keys and values.

Unlike RegEnumKeyEx, RegEnumValue returns more than just names; it also gives you the data associated with each value name, and the size (in bytes) of the data. The RegOp class calls RegEnumValue the first time you query a value, and creates a mini-database of value names and data in a Dictionary object. The next time you query a value under the same key, the class doesn’t have to go back to the registry, but can pull the value directly out of the database.

What’s a Dictionary?

In their infinite wisdom, our friends in Redmond decided to create something better than a Collection object – and they named it Dictionary. So, if you’re an Office programmer, you now have two types of Dictionary objects at your disposal: the spelling Dictionary objects that are part of Word, and the Dictionary object, exposed by VBScript (the Microsoft Visual Basic Scripting Edition). Confused? You’re not the only one!

The VBScript Dictionary object has nothing to do with spelling or grammar. In short, Dictionary is a high-performance data storage object that can contain sets of pairs, each consisting of an item (which can be any data type), and a unique key (usually a string). Unlike a Collection, a Dictionary object exposes an Exists method (so you never have to guess), a CompareMode property (so you can make case-sensitive or case-insensitive searches), and a RemoveAll method.

There’s no space here to discuss all the ins and outs of the Dictionary object, so I’ll limit myself to the basics as they’re used in the RegOp class. The class uses Dictionary as the database that contains the names and data of all values under a key. To create a Dictionary object, you call the CreateObject function. In the following example, the object variable is named ValueList:

Set ValueList = CreateObject("Scripting.Dictionary") 

To add pairs of keys and items, you can use the Add method, but you don’t have to. If you assign a value to a key that doesn’t exist, the Dictionary object creates the key automatically. The following instructions have the same result:

ValueList.Add "MyValueName", "MyValue"   ValueList("MyValueName") = "MyValue" 

The advantage of the second syntax is that it allows you to replace the item associated with an existing key (the first line will generate an error if there is already a "MyValueName" key).

Once you’ve added your key/item pairs to Dictionary, the Count property tells you how many pairs are stored. The Keys and Item methods return arrays containing all existing keys and items, respectively.

Since Dictionary indexes all data alphabetically by key names, you can make very fast searches and retrievals, as shown here:

If ValueList.Exists("MyValueName") Then_   MsgBox ValueList("MyValueName") 

As I said before, the RegOp class uses Dictionary to store all pairs of value names and values when the first query comes in. From that point onwards, all subsequent values are retrieved from Dictionary. This may be overkill in cases where you want to retrieve only a single value, but it’s a serious performance booster if you need more (or all) values from the same registry key.

Using the RegOp Class

The RegOp class is designed to be easy to use. Once the class module is added to your project, you can create an object with the following instructions:

With New RegOp    ' ... properties and methods go here.
End With

In the first line, the object is created, and the Class_Initialize routine in the module is executed. In the last line, the object is deleted, forcing the Class_Terminate routine to run.

Specifying the registry address. The minimal information the class needs is the desired location in the registry. This is determined by two properties: Root and Key. These properties must be specified before you use any other property or method. Root is an enumeration value you can pick from a list. The options are listed in FIGURE 1. If you don’t specify a value, the class defaults to HKEY_CURRENT_USER. Key is required, and must be a string. Here are two examples:

' Accessing the Printers key under HKLM.
.Root = HKEY_LOCAL_MACHINE
.Key = "SystemCurrentControlSetControlPrintPrinters"   'Accessing the FrontPage key under HKCU
'(no need to specify a Root value).
.Key = "SoftwareMicrosoftFrontPage" 

Setting a value. Once the Root and Key properties are set, you use the Value property to store a value. The name of the value must be specified as an argument, the value itself can be a string, a number, a date, or even an array (more about arrays later). The class analyzes the data type of the value, and stores it in the appropriate format. If you omit the value name, the default value of the specified key is set. In that case the value is saved as a string. Here are some examples:

' Creating named values.
.Value("Name") = "Romke"         ' Stored as REG_SZ
.Value("Age") = 52               ' Stored as REG_DWORD
.Value("Windows") = "%windir%"   ' Stored as REG_EXPAND_SZ
.Value("Last Use") = Now         ' Stored as REG_SZ
' Setting the default value of a key:
.Value = "ABC"                   ' Stored as REG_SZ

Retrieving a value. Evaluate the Value property to obtain the data associated with a value name. The value name must be specified as an argument; if omitted, the default value of the key is returned. The data is returned as a Variant:

' Retrieving a named value.
MyName = .Value("Name")
' Retrieving the default value of a key.
DefVal = .Value

Deleting a value. Use the DeleteValue method to remove a name/value pair. The value name must be specified as an argument; if omitted, the default value of the key is returned. If you call this method as a function, the return value is True if the deletion was successful, or False if not. For example:

' Deleting a named value.
.DeleteValue("Name")
' Deleting the default value of a key.
.DeleteValue
' Using DeleteValue as a function.
If .DeleteValue("Name") = False Then   MsgBox "Could not delete value"
End If

Deleting a key. To delete a key and all its descendants, use the DeleteKey method. This method attempts to delete the last component of the key specified with the Key property. If you call this method as a function, the return value is True if the deletion was successful, or False if not. In the following examples, the "My App" key (including all its values and subkeys) is deleted:

.Key = "SoftwareMy CompanyMy App"
.DeleteKey
' Using DeleteKey as a function.
If .DeleteKey = True Then   MsgBox "Key successfully deleted"
End If

Obtaining a list of keys. The AllKeys property returns a Variant array of all subkeys under the key specified with the Key property. If there are no subkeys, AllKeys returns Empty. FIGURE 3 demonstrates how you can use this property to get a list of all local printers. The listing in FIGURE 4 uses a different approach to get the same information.

Sub GetAllPrinters()   With New RegOp     .Root = HKEY_LOCAL_MACHINE     .Key = _       "SystemCurrentControlSetControlPrintPrinters"      Dim AllPrinters As Variant, i As Integer, Msg As String     AllPrinters = .AllKeys      If Not IsEmpty(AllPrinters) Then        For i = LBound(AllPrinters) To UBound(AllPrinters)         Msg = Msg & AllPrinters(i) & vbCr        Next       MsgBox Msg      End If   End With
End Sub

FIGURE 3: Getting a list of all locally installed printers. The IsEmpty function is called to test for valid entries, and the For loop uses LBound and UBound to obtain the boundaries of the variant array.

Sub GetAllPrinters2()   With New RegOp     .Root = HKEY_LOCAL_MACHINE     .Key = _       "SystemCurrentControlSetControlPrintPrinters"      Dim MyPrinter As Variant      If .KeyCount > 0 Then        For Each MyPrinter In .AllKeys         Msg = Msg & MyPrinter & vbCr        Next       MsgBox Msg      End If   End With
End Sub

FIGURE 4: An alternative for the listing in FIGURE 3. The KeyCount property is evaluated, and a For Each loop is used to enumerate the printers.

Obtaining a list of values. The AllValues property returns a Variant array of all value names and their associated data under a key. If there are no values, AllValues returns Empty. FIGURE 5 demonstrates how you can use this property to retrieve all user settings of Word 2000.

Sub GetWordOptions()   With New RegOp      Dim AllSettings As Variant, i As Integer, Msg As String     .Key = "SoftwareMicrosoftOffice9.0WordOptions"     AllSettings = .AllValues      If Not IsEmpty(AllSettings) Then        For i = LBound(AllSettings, 1) To_                UBound(AllSettings, 1)         Msg = Msg & AllSettings(i, 0) & " = " & _               AllSettings(i, 1) & vbCr        Next        MsgBox Msg      End If   End With
End Sub

FIGURE 5: Getting all Word settings for the current user. Since these values are stored under HKEY_CURRENT_USER, no Root property needs to be specified.

Storing and Retrieving Arrays

The registry API allows for an interesting data type, called REG_MULTI_SZ. This format lets you store multiple text strings under a single value name. The array is stored in a space-saving binary format. The RegOp class fully supports REG_MULTI_SZ values.

The Windows concept of an "array" differs from what VB considers an array. Windows wants the substrings to be separated by null characters, and expects two null characters at the end of the string. In other words, we’re dealing with a single string, not a real array.

The class uses the Join function to create the string that contains all the elements of a specified array, and adds two null characters at the end, like this:

MultiString = _   Join(OriginalArray, vbNullChar) & String$(2, 0) 

This creates a Windows-friendly string, ready to be stored as a REG_MULTI_SZ value. When this value is retrieved from the registry, the trailing double null characters are removed, and the Split function is used to recreate the array:

OriginalArray = Left$(MultiString, InStr( _   MultiString, String$(2, 0)) - 1)
OriginalArray = Split(OriginalArray, vbNullChar) 

So much for the class internals. Fortunately, you don’t have to worry about it; all you have to do is specify an array as the data for a value, and the rest is taken care of.

How do you use this in your own applications? That depends on your needs and creativity. If your application needs to store several related values for later use, the REG_MULTI_SZ data type saves space in the registry and can speed up data retrieval. For example, if your application has a dialog box (a.k.a. UserForm), you may want to save the dialog box position and size so you can recreate the same view the next time it’s displayed. Currently, you probably store the position and size of the dialog as four separate values:

.Value("Left") = Me.Left
.Value("Top") = Me.Top
.Value("Width") = Me.Width
.Value("Height") = Me.Height

Instead, you can use the Array function, and store all values as a single setting:

.Value("Display") = _   Array(Me.Left, Me.Top, Me.Width, Me.Height) 

Here’s another example. If your software needs to keep MRU (Most Recently Used) file lists, you could save them in separate values, with names like "File1", "File2", etc. But you’re saving space and time if you create an array of file names, and store the array as a single value:

Dim MRUFile(3) As String  MRUFile(0) = "Report.doc"
MRUFile(1) = "Budget.xls"
MRUFile(2) = "Customers.mdb"
MRUFile(3) = "Meeting.ppt"
With New RegOp   .Key = "SoftwareRomkeMy App"   .Value("MRU") = MRUFile
End With

The RegOp class can return REG_MULTI_SZ data in two ways. By default, the class returns the value as a single string, in which the substrings are separated by null characters (the terminating double null characters are removed). If you specify ReturnMultiStringsAsArrays as part of the Options property, the data is returned as an array. The following code demonstrates how you can obtain the array elements, regardless of the Options setting:

Dim MRUFile As Variant, i As Long  MRUFile = .Value("MRU")
If Not IsArray(MRUFile) Then   MRUFile = Split(MRUFile, vbNullChar)
End If
For i = LBound(MRUFile) To UBound(MRUFile)    Debug.Print MRUFile(i)
Next
Conclusion

The purpose of this series is to demonstrate that there is a lot more to VBA programming than you may think. With a bit of hacking, you can greatly enhance your Office applications, and facilitate your work as a developer. In this installment, I showed you how you can wrap the complex Windows registry API in a class module, so you only have to deal with some simple properties and methods (summarized in FIGURE 6). Watch this space for more VBA hacks!

Properties & Methods

   Description

Root

Write-only property. Optional Long. Specifies the registry root containing the path specified with the Key property. Must be one of the values in FIGURE 1. If omitted, HKEY_CURRENT_USER is assumed.

Key

Write-only property. Required string. Specifies the full path to the subkey where registry operations take place. This property must be specified before any of the other properties or methods are used.

Value ( [ValueName])

Read/write property. Variant. Saves or returns the data associated with ValueName under the subkey specified by the Key property. If ValueName is omitted, the default value for the key is saved or returned.

DeleteValue ( [ValueName])

Method. Deletes the value specified with ValueName. If ValueName is omitted, the default value for the key is deleted. When called as a function, DeleteValue returns True if the deletion was successful.

DeleteKey

Method. Deletes the last subkey component (including all its subkeys and values) of the path specified with the Key property. When called as a function, DeleteKey returns True if the deletion was successful.

ValueCount

Read-only property. Long. Returns the number of values under the subkey specified with the Key property.

KeyCount

Read-only property. Long. Returns the number of subkeys under the subkey specified with the Key property.

AllValues

Read-only property. Returns a two-dimensional Variant array of all value names and their values under the key specified with the Key property. If there are no values, AllValues returns Empty, and ValueCount returns zero.

AllKeys

Read-only property. Returns a Variant array of all subkeys under the key specified with the Key property. If there are no subkeys, AllKeys returns Empty, and KeyCount returns zero.

Options

Optional write-only property. Long. Lets you change the format in which values are set or returned. This property must be specified before the first Value property is set or evaluated.

•          Use any combination of the following enumeration values: StoreNumbersAsStrings. Saves Integer and Long values as REG_SZ values rather than REG_DWORD values. (Note: any numeric value other than Integer or Long is always stored as a REG_SZ value.)

•          ReturnMultiStringsAsArrays. If the data type is REG_MULTI_SZ, the data is converted into a variant array. You can then use the VB IsArray, LBound, and UBound functions to process the array. If this value isn’t set, the data is returned as a single string, in which substrings are separated by null characters. Use the VB Split function to extract individual substrings.

•          ExpandEnvironmentStrings. If the data type is REG_EXPAND_SZ, any substring that refers to an environment-variable (e.g. "%windir%") is replaced with its current value.

•          ShowErrorMessages. Displays a message box when an error occurs. If not set, errors are displayed in the Immediate window.

FIGURE 6: A summary of RegOp properties and methods.

Dutchman Romke Soldaat was hired by Microsoft in 1988 to co-found the Microsoft International Product Group in Dublin, Ireland. That same year he started working with the prototypes of WinWord, writing his first macros long before the rest of the world. In 1992 he left Microsoft, and created a number of successful add-ons for Office. Living in Italy, he divides his time between writing articles for this magazine, enjoying the Mediterranean climate, and steering his Land Rover through the world’s most deserted areas. Romke can be contacted at mailto:[email protected].

Begin Listing One – RegOp.cls
Option Explicit  DefStr S
DefLng H-I, L, N
DefVar V
DefBool B  Private Type OSVERSIONINFO   dwOSVersionInfoSize As Long   dwMajorVersion As Long   dwMinorVersion As Long   dwBuildNumber As Long   dwPlatformId As Long   szCSDVersion As String * 128
End Type   Private Type SECURITY_ATTRIBUTES   nLength As Long   lpSecurityDescriptor As Long   bInheritHandle As Long
End Type   ' RegCreateKeyEx creates the specified key. If the key
' already exists, the function opens it. The phkResult
' parameter receives the key handle.
Private Declare 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, _   lpSecurityAttributes As SECURITY_ATTRIBUTES, _   phkResult As Long, lpdwDisposition As Long) As Long   'RegCloseKey releases a handle to the specified key.
'(Key handles should not be left open any longer than
'necessary.)
Private Declare Function RegCloseKey Lib "advapi32.dll" ( _    ByVal hCurKey As Long) As Long  ' RegQueryInfoKey retrieves information about the specified
'key, such as the number of subkeys and values, the length
'of the longest value and key name, and the size of the
'longest data component among the key's values.

Private Declare Function RegQueryInfoKey _    Lib "advapi32.dll" Alias "RegQueryInfoKeyA" ( _    ByVal hCurKey As Long, ByVal lpClass As String, _   lpcbClass As Long, ByVal lpReserved As Long, _   lpcSubKeys As Long, lpcbMaxSubKeyLen As Long, _   lpcbMaxClassLen As Long, lpcValues As Long, _   lpcbMaxValueNameLen As Long, lpcbMaxValueLen As Long, _   lpcbSecurityDescriptor As Long, _   lpftLastWriteTime As Long) As Long   'RegEnumKeyEx enumerates subkeys of the specified open
'key. Retrieves the name (and its length) of each subkey.
Private Declare Function RegEnumKeyEx Lib "advapi32.dll" _    Alias "RegEnumKeyExA" (ByVal hCurKey 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 Long) As Long   'RegEnumValue enumerates the values for the specified open   'key. Retrieves the name (and its length) of each value,
'and the type, content and size of the data.
Private Declare Function RegEnumValue Lib "advapi32.dll" _    Alias "RegEnumValueA" (ByVal hCurKey As Long, _    ByVal dwIndex As Long, ByVal lpValueName As String, _   lpcbValueName As Long, ByVal lpReserved As Long, _   lpType As Long, lpData As Any, lpcbData As Long) As Long   'RegQueryValueEx retrieves the type, content and data for
' a specified value name. Note that if you declare the
' lpData parameter as String, you must pass it By Value.
Private Declare Function RegQueryValueEx _    Lib "advapi32.dll" Alias "RegQueryValueExA" ( _    ByVal hCurKey As Long, ByVal lpValueName As String, _    ByVal lpReserved As Long, lpType As Long, _   lpData As Any, lpcbData As Long) As Long   'RegSetValueEx sets the data and type of a specified
' value under a key.
Private Declare Function RegSetValueEx Lib "advapi32.dll" _    Alias "RegSetValueExA" (ByVal hCurKey As Long, ByVal _   lpValueName As String, ByVal Reserved As Long, _    ByVal dwType As Long, lpData As Any, _    ByVal cbData As Long) As Long   'RegDeleteValue removes a named value from specified key.
Private Declare Function RegDeleteValue _    Lib "advapi32.dll" Alias "RegDeleteValueA" ( _    ByVal hCurKey As Long, ByVal lpValueName As String) _    As Long   'RegDeleteKey deletes a subkey. Under Win 95/98, also
'deletes all subkeys and values. Under Windows NT/2000,
'the subkey to be deleted must not have subkeys. The class
'attempts to use SHDeleteKey (see below) before using
'RegDeleteKey.
Private Declare Function RegDeleteKey Lib "advapi32.dll" _    Alias "RegDeleteKeyA" (ByVal hKey As Long, _    ByVal lpSubKey As String) As Long   'SHDeleteKey deletes a subkey and all its descendants.
'Under Windows NT 4.0, Internet Explorer 4.0 or later
'is required.
Private Declare Function SHDeleteKey Lib "Shlwapi" _    Alias "SHDeleteKeyA" (ByVal hKey As Long, _    ByVal lpSubKey As String) As Long  Private Declare Function LoadLibrary Lib "Kernel32" _    Alias "LoadLibraryA" (ByVal lpLibFileName As String) _    As Long   Private Declare Function FreeLibrary Lib "Kernel32" ( _    ByVal hLibModule As Long) As Long   Private Declare Function ExpandEnvStrings Lib "Kernel32" _    Alias "ExpandEnvironmentStringsA" ( _    ByVal lpSrc As String, ByVal lpDst As String, _    ByVal nSize As Long) As Long   Private Declare Function GetVersionEx Lib "Kernel32" _    Alias "GetVersionExA" ( _   lpVersionInformation As OSVERSIONINFO) As Long   Private Const REG_SZ = 1
Private Const REG_EXPAND_SZ = 2
Private Const REG_DWORD = 4
Private Const REG_DWORD_LITTLE_ENDIAN = REG_DWORD
Private Const REG_MULTI_SZ = 7   ' The following values are only relevant under WinNT/2K,
' and are ignored by Win9x.
Private Const STANDARD_RIGHTS_READ = &H20000
Private Const STANDARD_RIGHTS_WRITE = &H20000
Private Const STANDARD_RIGHTS_ALL = &H1F0000
Private Const KEY_CREATE_LINK = &H20
Private Const KEY_QUERY_VALUE = &H1
Private Const KEY_ENUMERATE_SUB_KEYS = &H8
Private Const KEY_NOTIFY = &H10
Private Const KEY_SET_VALUE = &H2
Private Const KEY_CREATE_SUB_KEY = &H4
Private Const SYNCHRONIZE = &H100000   ' Access right to query and enumerate values.
Private Const KEY_READ = ((STANDARD_RIGHTS_READ Or _   KEY_QUERY_VALUE Or KEY_ENUMERATE_SUB_KEYS Or _   KEY_NOTIFY) And (Not SYNCHRONIZE))
'Access right to create values and keys.
Private Const KEY_WRITE = ((STANDARD_RIGHTS_WRITE Or _   KEY_SET_VALUE Or KEY_CREATE_SUB_KEY) And _    (Not SYNCHRONIZE))
'Access right to create/delete values and keys.
Private Const KEY_ALL_ACCESS = ((STANDARD_RIGHTS_ALL Or _   KEY_QUERY_VALUE Or KEY_SET_VALUE Or _   KEY_CREATE_SUB_KEY Or KEY_ENUMERATE_SUB_KEYS Or _   KEY_NOTIFY Or KEY_CREATE_LINK) And (Not SYNCHRONIZE))   Private lRequiredAccess Private lPreviousAccess  'Return values for all registry functions.
Private Const ERROR_SUCCESS = 0   'Property variables.
Private lRoot 'default is HKEY_LOCAL_MACHINE
Private lOptions
Private strKeyName
Private strValueName
Private vData   'Variables set in GetKeyHandle.
Private hCurKey
Private nSubKeys
Private nValues
Private lMaxSubKeyLen
Private lMaxValueNameLen
Private lMaxValueLen   Private bIsWinNT   Public Enum RegOptions ' variable: lOptions   StoreNumbersAsStrings = 1   ReturnMultiStringsAsArrays = 2   ExpandEnvironmentStrings = 4   ShowErrorMessages = 8
End Enum   Public Enum RegRoot ' variable: lRoot   HKEY_CLASSES_ROOT = &H80000000   HKEY_CURRENT_USER = &H80000001   ' default   HKEY_LOCAL_MACHINE = &H80000002
End Enum   'Message constants.
Private Const ERROR_NO_KEY As String = _   "No Key name specified!"
Private Const ERROR_NO_HANDLE = _   "Could not open Registry Key!"
Private Const ERR_MSG_NO_OVERWRITE As String = _   "Existing value has unsupported data type " & _   "and will not be overwritten"
Private Const RETURN_UNSUPPORTED As String = _   "(unsupported data format)"   Private ValueList As Object   Property Let Root(lProp As RegRoot)   ' Don't accept an invalid Root value.   Select Case lProp     Case HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, _           HKEY_LOCAL_MACHINE        ' All is well.     Case Else       lRoot = HKEY_CURRENT_USER   End Select   If lProp <> lRoot Then     lRoot = lProp      If Len(strKeyName) Then       GetKeyHandle lRoot, strKeyName      End If   End If   lRoot = lProp
End Property   Property Let Key(strProp)   ' Don't accept an empty key name.   If Len(strProp) = 0 Then Exit Property   If Len(strKeyName) = 0 Then ' first time     strKeyName = strProp   ElseIf StrComp(strProp, strKeyName, _                  vbTextCompare) <> 0 Then     strKeyName = strProp     GetKeyHandle lRoot, strKeyName   Else   End If
End Property   Property Let Options(lProp As RegOptions)   ' Don't accept an invalid Options value.   Select Case lProp     Case 0 To 15: lOptions = lProp     Case Else:    End Select
End Property   Property Let Value(Optional ValueName As String, vValue)   If IsEmpty(vValue) Then      Exit Property    Else     vData = vValue    End If   If bIsWinNT Then lRequiredAccess = KEY_WRITE Or KEY_READ   If PropertiesOK Then      ' First see if this is an existing value, and,      ' if so, what data type we have here.      Dim strBuffer, lBuffer, lType      If RegQueryValueEx(hCurKey, ValueName, 0, lType, _         ByVal strBuffer, lBuffer) = ERROR_SUCCESS Then        ' Make sure our new value is the same data type.        Select Case lType          Case REG_SZ, REG_EXPAND_SZ ' existing string           vData = CStr(vData)          Case REG_DWORD, REG_DWORD_LITTLE_ENDIAN            ' existing long           vData = CLng(vData)          Case REG_MULTI_SZ ' existing array           vData = CVar(vData)          Case Else           ShowErrMsg ERR_MSG_NO_OVERWRITE            Exit Property        End Select      End If      If (lOptions And StoreNumbersAsStrings) Then        If IsNumeric(vData) Then vData = CStr(vData)      End If      ' If nameless "(default)" value:      If Len(ValueName) = 0 Then vData = CStr(vData)      ' Look at the data type of vData, and store it      ' in the appropriate registry format.      If VarType(vData) And vbArray Then   ' 8192        Dim sTemp As String        ' REG_MULTI_SZ values must end with 2 null characters.       sTemp = Join(vData, vbNullChar) & String$(2, 0)        Call RegSetValueEx(hCurKey, ValueName, 0, _         REG_MULTI_SZ, ByVal sTemp, Len(sTemp))      Else        Select Case VarType(vData)          Case vbInteger, vbLong            Call RegSetValueEx(hCurKey, ValueName, 0, _             REG_DWORD, CLng(vData), 4)          Case vbString            If ContainsEnvString(CStr(vData)) Then              Call RegSetValueEx(hCurKey, ValueName, 0, _               REG_EXPAND_SZ, ByVal CStr(vData), _               Len(vData) + 1)            Else              Call RegSetValueEx(hCurKey, ValueName, 0, _               REG_SZ, ByVal CStr(vData), Len(vData) + 1)            End If          Case Else ' Store any other data type as string.            Call RegSetValueEx(hCurKey, ValueName, 0, _             REG_SZ, ByVal CStr(vData), Len(vData) + 1)        End Select      End If      ' Update Value Count.      Call RegQueryInfoKey(hCurKey, vbNullString, 0, 0, 0, _        0, 0, nValues, 0, 0, 0, 0)      ' Clear the values database.     ValueList.RemoveAll   End If
End Property  Property Get Value(Optional ValueName As String) As Variant   With ValueList      If .Count = 0 Then FillDataList      If .Exists(ValueName) Then Value = .Item(ValueName)   End With
End Property   Property Get AllValues()As Variant   If bIsWinNT Then lRequiredAccess = KEY_READ   If PropertiesOK Then      If nValues = 0 Then Exit Property      With ValueList        If .Count = 0 Then FillDataList        If .Count Then          Dim i, vKeys, vItems         vKeys = .Keys         vItems = .items          ReDim vTemp(.Count - 1, 1)          For i = 0 To .Count - 1           vTemp(i, 0) = vKeys(i)           vTemp(i, 1) = vItems(i)          Next         AllValues = vTemp        End If      End With   End If
End Property   Property Get AllKeys()As Variant   If bIsWinNT Then lRequiredAccess = KEY_READ   If PropertiesOK Then      If nSubKeys = 0 Then Exit Property      Dim i: ReDim vTemp(nSubKeys - 1)      For i = 0 To nSubKeys - 1       strKeyName = String$(lMaxSubKeyLen + 1, 0)        If RegEnumKeyEx(hCurKey, i, strKeyName, _          lMaxSubKeyLen + 1, 0, vbNullString, 0, 0) = _          ERROR_SUCCESS Then         vTemp(i) = TrimNull(strKeyName)        End If      Next     AllKeys = vTemp   End If
End Property  Function DeleteValue(Optional ValueName As String) _    As Boolean    If bIsWinNT Then lRequiredAccess = KEY_ALL_ACCESS   If PropertiesOK Then     DeleteValue = (RegDeleteValue(hCurKey, ValueName) = _       ERROR_SUCCESS)      If DeleteValue Then        Call RegQueryInfoKey(hCurKey, vbNullString, 0, 0, _         0, 0, 0, nValues, 0, 0, 0, 0)       ValueList.RemoveAll      End If   End If
End Function   Function DeleteKey()As Boolean   If Len(strKeyName) = 0 Then     ShowErrMsg ERROR_NO_KEY      Exit Function   End If   Dim n, strLastKey   n = InStrRev(strKeyName, "")   If n > 0 And n < Len(strKeyName) Then     strLastKey = Mid$(strKeyName, n + 1)     strKeyName = Left$(strKeyName, n - 1)      If bIsWinNT Then lRequiredAccess = KEY_ALL_ACCESS      Call GetKeyHandle(lRoot, strKeyName)      If hCurKey = 0 Then Exit Function      If ShlwapiInstalled Then        ' This should always work.       DeleteKey = (SHDeleteKey(hCurKey, strLastKey) = _         ERROR_SUCCESS)      Else        ' This will only work under Win95/98.       DeleteKey = (RegDeleteKey(hCurKey, strLastKey) = _         ERROR_SUCCESS)      End If      If DeleteKey Then        Call RegQueryInfoKey(hCurKey, vbNullString, 0, 0, _         nSubKeys, 0, 0, 0, 0, 0, 0, 0)       ValueList.RemoveAll      End If   End If
End Function   Property Get ValueCount()As Long   If PropertiesOK Then ValueCount = nValues
End Property  Property Get KeyCount()As Long   If PropertiesOK Then KeyCount = nSubKeys
End Property   Private Function PropertiesOK()As Boolean   If Len(strKeyName) = 0 Then     ShowErrMsg ERROR_NO_KEY      Exit Function   End If   If lPreviousAccess Then      If lRequiredAccess <> lPreviousAccess Then _       CloseCurrentKey   End If   If hCurKey = 0 Then Call GetKeyHandle(lRoot, strKeyName)   If hCurKey = 0 Then     ShowErrMsg ERROR_NO_HANDLE      Exit Function   End If   PropertiesOK = True
End Function   Private Sub Class_Initialize()   lRoot = HKEY_CURRENT_USER   bIsWinNT = IsWinNT   If bIsWinNT Then lRequiredAccess = KEY_READ   On Error Resume Next   Set ValueList = CreateObject("Scripting.Dictionary")   If IsObject(ValueList) Then     ValueList.CompareMode = vbTextCompare   Else      End   End If
End Sub  Private Sub Class_Terminate()   CloseCurrentKey   Set ValueList = Nothing
End Sub  Private Sub CloseCurrentKey()   If hCurKey Then      Call RegCloseKey(hCurKey)     hCurKey = 0   End If
End Sub  Private Sub GetKeyHandle(lKey, strKey)   CloseCurrentKey   If lKey = 0 Then lKey = HKEY_CURRENT_USER   Dim SA As SECURITY_ATTRIBUTES   Call RegCreateKeyEx(lKey, strKey, 0, vbNull, 0, _     lRequiredAccess, SA, hCurKey, 0)    If hCurKey Then      Call RegQueryInfoKey(hCurKey, vbNullString, 0, 0, _       nSubKeys, lMaxSubKeyLen, 0, nValues, _       lMaxValueNameLen, lMaxValueLen, 0, 0)     ValueList.RemoveAll     lPreviousAccess = lRequiredAccess    End If
End Sub  Private Function TrimNull(ByVal strIn) As String   TrimNull = Left$(strIn, InStr(strIn, vbNullChar) - 1)
End Function   Private Function TrimDoubleNull(ByVal strIn) As String   If Len(strIn) Then _     TrimDoubleNull = _       Left$(strIn, InStr(strIn, String$(2, 0)) - 1)
End Function   Private Function ExpandString(strIn) As String   Dim nChars, strBuff, nBuffSize   nBuffSize = 1024   strBuff = String$(nBuffSize, 0)   nChars = ExpandEnvStrings(strIn, strBuff, nBuffSize)   If nChars Then ExpandString = Left$(strBuff, nChars - 1)
End Function  Private Function ShlwapiInstalled()As Boolean   Dim hLib As Long   hLib = LoadLibrary("Shlwapi")   If hLib Then     ShlwapiInstalled = True     FreeLibrary hLib   End If
End Function  Private Function ContainsEnvString(ByVal strTest) _    As Boolean    Const PCT As String = "%"   ' See if there is a percent sign.   Dim n As Long:
n = InStr(strTest, PCT)   If n = 0 Then Exit Function   ' See if there is a second percent sign.   If n = InStrRev(strTest, PCT) Then Exit Function   ' Now we have a potential environment string.   Dim Env As String, EnvSplit()As String   Dim i As Long   For i = 1 To 100     Env = Environ(i)      If Len(Env) Then       EnvSplit = Split(Env, "=")        If InStr(1, strTest, PCT & EnvSplit(0) & PCT, _                vbTextCompare) Then         ContainsEnvString = True          Exit For        End If      Else        Exit For      End If   Next
End Function  Private Sub ShowErrMsg(strMsg)   If (lOptions And ShowErrorMessages) Then     MsgBox strMsg, vbExclamation, "Registry Error"   Else      Debug.Print strMsg   End If
End Sub   Private Function IsWinNT()   ' Returns True if the OS is Windows NT/2000.   Const VER_PLATFORM_WIN32_NT As Long = 2   Dim osvi As OSVERSIONINFO   osvi.dwOSVersionInfoSize = Len(osvi)   GetVersionEx osvi   IsWinNT = (osvi.dwPlatformId = VER_PLATFORM_WIN32_NT)
End Function   Private Sub FillDataList(Optional Key As String)   If Len(Key) Then strKeyName = Key   If Len(strKeyName) = 0 Then _     ShowErrMsg ERROR_NO_KEY: Exit Sub   If bIsWinNT Then lRequiredAccess = KEY_READ   If PropertiesOK Then      If nValues = 0 Then Exit Sub     ValueList.RemoveAll      Dim i, lValuename, lType, lBuffer, strValue, strBuffer      For i = 0 To nValues - 1       lValuename = lMaxValueNameLen + 1       strValue = String$(lValuename, 0)       lBuffer = lMaxValueLen + 1       strBuffer = String$(lBuffer, 0)        If RegEnumValue(hCurKey, i, strValue, lValuename, _          0, lType, ByVal strBuffer, lBuffer) = _          ERROR_SUCCESS Then         strValue = TrimNull(strValue)          Select Case lType            Case REG_SZ             ValueList(strValue) = TrimNull(strBuffer)            Case REG_EXPAND_SZ              If (lOptions And ExpandEnvironmentStrings) Then               ValueList(strValue) = _                 ExpandString(TrimNull(strBuffer))              Else               ValueList(strValue) = TrimNull(strBuffer)              End If            Case REG_MULTI_SZ              If (lOptions And _                 ReturnMultiStringsAsArrays) Then               ValueList(strValue) = Split( _                 TrimDoubleNull(strBuffer), vbNullChar)              Else               ValueList(strValue) = _                 TrimDoubleNull(strBuffer)              End If            Case REG_DWORD, REG_DWORD_LITTLE_ENDIAN              Dim nBuffer              If RegEnumValue(hCurKey, i, strValue, _                Len(strValue) + 1, 0, REG_DWORD, nBuffer, _                4) = ERROR_SUCCESS Then               ValueList(strValue) = nBuffer              End If            Case Else             ValueList(strValue) = RETURN_UNSUPPORTED          End Select        End If      Next   End If

End Sub

 

Reference: http://msdn.microsoft.com/en-us/library/aa155731%28office.10%29.aspx

2 thoughts on “Visual Basic: The Registry Made Easy

  1. I am trying to get this code to work using access 2007 and I’m getting hung up on these variables “l” variables. ie. Private lRequiredAccess Private lPreviousAccess Not sure I understand why. Thanks

Leave a Reply

Your email address will not be published. Required fields are marked *