Creating a System Registry Editor

To illustrate how the Windows API Registry functions can be used to add, retrieve, update, and delete key values, the program that was started earlier in this chapter will be built upon so it can be used to edit INI files and the Registry. To keep things simple, only individual key values will be accessible.

To begin modifying the sample program, first add the following declarations and constants to the INIEdit module (Listing 32.12):

Listing 32.12. - Function declarations and constants to be added to the INIEdit module.

Public Declare Function RegCloseKey Lib "advapi32" _
    (ByVal hKey As Long) As Long
Public Declare Function RegCreateKey Lib "advapi32" _
    Alias "RegCreateKeyA" (ByVal hKey As Long, _
    ByVal lpSubKey As String, phkResult As Long) As Long
Public Declare Function RegDeleteValue Lib "advapi32" _
    Alias "RegDeleteValueA" (ByVal hKey As Long, _
    ByVal lpValueName As String) As Long
Public Declare Function RegOpenKeyEx Lib "advapi32" _
    Alias "RegOpenKeyExA" (ByVal hKey As Long, _
    ByVal lpSubKey As String, ByVal ulOptions As Long, _
    ByVal samDesired As Long, phkResult As Long) As Long
Public Declare Function RegQueryValueEx Lib "advapi32" _
    Alias "RegQueryValueExA" (ByVal hKey As Long, _
    ByVal lpValueName As String, ByVal lpReserved As Long, _
    lpType As Long, lpData As Any, lpcbData As Long) As Long
Public Declare Function RegSetValueEx Lib "advapi32" _
    Alias "RegSetValueExA" (ByVal hKey As Long, _
    ByVal lpValueName As String, ByVal Reserved As Long, _
    ByVal dwType As Long, lpData As Any, _
    ByVal cbData As Long) As Long
Public Const REG_NONE = 0
Public Const REG_SZ = 1
Public Const REG_EXPAND_SZ = 2
Public Const REG_BINARY = 3
Public Const REG_DWORD = 4
Public Const REG_DWORD_LITTLE_ENDIAN = 4
Public Const REG_DWORD_BIG_ENDIAN = 5
Public Const REG_LINK = 6
Public Const REG_MULTI_SZ = 7
Public Const REG_RESOURCE_LIST = 8
The first procedure that needs to be added to the program is a function called GetKeyHandle (Listing 32.13). This function uses the RegOpenKeyEx API function to open a subkey and get its handle, a unique number that can be used to reference the subkey.

Listing 32.13. - The GetKeyHandle function, which opens a subkey and returns its unique handle.

Private Function GetSubkeyHandle(sSubkey As String) As Long
Dim intSubkeyCount As Integer
Dim intSubkeyLoop As Integer
Dim intSubkeyPtr As Integer
Dim lonHandle(1 To 32) As Long
Dim lonLastHandle As Long
Dim lonStatus As Long
Dim strKeyPath As String
Dim strSubkeys(1 To 32) As String
Dim strTempKeys() As String
' Trim any spaces from left and right sides of the
' subkey path.
strKeyPath = Trim$(sSubkey)
' Use VB's Split function to break the key's path
' into subkeys.
strTempKeys = Split(strKeyPath, "\")
' Transfer the elements of the strTempKeys()
' array to strSubkeys(), but leave out any blank
' elements.
For intSubkeyLoop = 0 To UBound(strTempKeys)
    If strTempKeys(intSubkeyLoop) <> "" Then
        intSubkeyCount = intSubkeyCount + 1
        If intSubkeyCount <= 32 Then
            strSubkeys(intSubkeyCount) = strTempKeys(intSubkeyLoop)
        Else
            ' Error - too many subkeys in path.
            GetSubkeyHandle = -1
            Exit Function
        End If
    End If
Next intSubkeyLoop
For intSubkeyPtr = 1 To intSubkeyCount - 1
    ' For the first subkey, use the defined
    ' constants - this is the root key.
    If intSubkeyPtr = 1 Then
        Select Case UCase$(strSubkeys(1))
            Case "HKEY_CLASSES_ROOT":
                lonLastHandle = &H80000000
            Case "HKEY_CURRENT_USER":
                lonLastHandle = &H80000001
            Case "HKEY_LOCAL_MACHINE"
                lonLastHandle = &H80000002
            Case "HKEY_USERS":
                lonLastHandle = &H80000003
            Case "HKEY_PERFORMANCE_DATA":
               lonLastHandle = &H80000004
            Case "HKEY_CURRENT_CONFIG&quo;:
                lonLastHandle = &H80000005
            Case Else:
                GetSubkeyHandle = -1
                Exit Function
        End Select
    End If
    ' Open the next subkey.
    lonStatus = RegOpenKeyEx(lonLastHandle, _strSubkeys(intSubkeyPtr + 1), 0, &H3F, _ 
_ lonHandle(intSubkeyPtr)) 
' A non-zero value returned by the 
' RegOpenKey function indicates an error. 
If lonStatus Then '
 Error in path. Close any previously 
' opened subkeys. 
For intSubkeyLoop="2" To (intSubkeyPtr 1) 
lonStatus="RegCloseKey(lonHandle(intSubkeyLoop))"
 Next intSubkeyLoop GetSubkeyHandle="-1" 
Exit Function 
End If 
lonLastHandle="lonHandle(intSubkeyPtr)"
 Next intSubkeyPtr 
' Return the value of the last subkey handle. 
GetSubkeyHandle="lonLastHandle" 
'Close all subkeys but the last one. 
For intSubkeyLoop="1" To (intSubkeyCount 2) 
lonStatus="RegCloseKey(lonHandle(intSubkeyLoop))" 
Next intSubkeyLoop 
End Function 

The object of this function is to return the handle of the last subkey in the path, which is provided by the user via the txtSectionName text box. Keep in mind that a path can be a long series of subkeys, such as HKEY_CURRENT_USER\Software\Test App or something even longer. It is within the last subkey that the individual keys and their values are contained. So it is that last subkey that has to be opened.

Unfortunately, that's not an easy task. To access the last subkey in the path, all of the preceding subkeys have to be opened, one at a time. So first you would open the HKEY_CURRENT_USER root key, then the Software subkey, then finally the Test App subkey.

The first thing that needs to be done is to make sure there are no spaces surrounding the subkey path. Then the path is broken down into its various subkeys using VB's Split function, which was just introduced in VB6. This handy function saves a bit of coding! It uses the backslash ("\") delimiter of the path to break down the txtSectionName string into individual items, which are stored in the strTempKeys array.

Because empty subkey names would be invalid, the strTempKeys array is transferred to the strSubkeys array and any empty items are weeded out. The strSubkeys array is only dimensioned for 32 elements, so a path with more subkeys than that will cause the function to fail.

Now that the subkey path has been broken down, the function loops through the subkeys and opens each one. To see how that is done, take a look at the call to the RegOpenKeyEx API function:

lonStatus = RegOpenKeyEx(lonLastHandle, _
        strSubkeys(intSubkeyPtr + 1), 0, &H3F, _
 _      lonHandle(intSubkeyPtr))
The first argument to the function is the handle of the last subkey that was opened (lonLastHandle). Because this is not available for the first subkey in the path, a Select...Case statement is used to assign a value to lonLastHandle based on the first subkey, which must be one of the Registry's six root keys. If it's not, the path is invalid and the GetSubkeyHandle function fails.

The second argument to the RegOpenKeyEx function is the name of the subkey to be opened. That's easy, it's the next subkey in the strSubkeys array. The third argument is unused and is set to zero. The next argument is a Long value that specifies the operations that can be performed on the subkey. In this case, a value of &H3F is used to provide full access to the subkey. The final argument is assigned the handle of the subkey that is opened, which will be stored in the lonHandle array.

If for some reason the RegOpenKeyEx function is not successful, then the GetSubkeyHandle function fails. But first, any currently open subkeys need to be closed with the RegCloseKey API function. You always want to make sure that you close any subkeys that you open.

If all of the calls to the RegOpenKeyEx function are successful, then the GetSubkeyHandle function returns the handle of the last subkey that was opened. All of the handles that preceded it are closed before the function ends, leaving only the last subkey open.

This is a pretty complicated routine, but if you break it down into its parts it may be easier to understand. Navigating along a subkey path is one of the more difficult aspects of Registry API programming.

The next procedure to add (Listing 32.14) is a far more simple one. It closes a subkey when given its handle.

Listing 32.14. - The CloseSubkey function, which closes an open subkey.

Private Function CloseSubkey(lHandle As Long) As Long
Dim lonStatus As Long
' Close the key with the handle specified by the
' lHandle argument.
lonStatus = RegCloseKey(lHandle)
' Return zero if successful, -1 if not successful.
CloseSubkey = 0
If lonStatus Then CloseSubkey = -1
End Function
Once again, the RegCloseKey API function is used to close a subkey. The CloseSubkey function will be used in the next procedure to be added, the RegRetrieve function (Listing 32.15).

Listing 32.15. - The RegRetrieve function, which is used to retrieve a key value from the Registry.

Private Function RegRetrieve() As String
Dim lonSubkeyHandle As Long
Dim lonStatus As Long
Dim lonValueType As Long
Dim lonValueSize As Long
' Use the GetSubkeyHandle function to determine the
' handle of the subkey given in txtSectionName.
lonSubkeyHandle = GetSubkeyHandle(txtSectionName.Text)
If lonSubkeyHandle = -1 Then
    ' Subkey could not be located.
    RegRetrieve = "Error - Subkey could not be located"
    Exit Function
End If
' Use the RegQueryValueEx API function to retrieve
' the value of the key given in txtKeyName.
lonValueType = REG_SZ
lonValueSize = 256
lonStatus = RegQueryValueEx(lonSubkeyHandle, _
    Trim$(txtKeyName.Text), 0, lonValueType, _
    ByVal gstrKeyValue, lonValueSize)
If lonStatus <> 0 Then
    ' Key could not be located.
    RegRetrieve = "Error - Key could not be located"
Else
    RegRetrieve = "Key retrieved successfully"
    txtValue.Text = Left$(gstrKeyValue, lonValueSize)
End If
    
' Close the open subkey with the CloseSubKey
' function.
lonStatus = CloseSubkey(lonSubkeyHandle)
End Function
If you remember back to the cmdRetrieve_Click event that was added earlier in this chapter, the RegRetrieve function is called when the Retrieve button is clicked and the Registry option button is selected. This function does basically the same thing that the INIRetrieve function does, except instead of retrieving a key from an INI file, it retrieves it from the Registry.

This function assumes that the txtSectionName text box contains the name of the subkey path and the txtKeyName text box contains the name of the key value to be retrieved. To get to the key, the subkey path has to be followed and the last subkey has to be opened. This is accomplished with the GetSubkeyHandle function discussed earlier.

After the subkey handle has been provided, the RegQueryValueEx API function is used to obtain the key value. If successful, the function places the value in the specified buffer, which in this case is gstrKeyValue.

The lonValueType variable is set to REG_SZ, a constant that has a value of 1. This variable is passed to the RegQueryValueEx function to specify the type of value to be retrieved. The sample program will only deal with string values for simplicity's sake, but there are actually a number of different value types you can retrieve from a key, including binary data and long integers. The possible values are shown in Table 32.8.

After the RegQueryValueEx function has been called, the status of the operation is determined and will be returned by the RegRetrieve function. If successful, the value in gstrKeyValue is displayed in the txtValue text box. Before the function ends, the subkey that was opened is closed because it is no longer needed.

The next procedure to be added is the RegDelete function (Listing 32.16). It's used to delete a key and its value.

Listing 32.16. - The RegDelete function, which deletes key values.

Private Function RegDelete() As String
Dim lonSubkeyHandle As Long
Dim lonStatus As Long
' Use the GetSubkeyHandle function to determine the
' handle of the subkey given in txtSectionName.
lonSubkeyHandle = GetSubkeyHandle(txtSectionName.Text)
If lonSubkeyHandle = -1 Then
    ' Subkey could not be located.
    RegDelete = "Error - Subkey could not be located"
    Exit Function
End If
' Use the RegDeleteKey API function to delete the
' key.
lonStatus = RegDeleteValue(lonSubkeyHandle, _
    txtKeyName.Text)
' Check status of the operation.
If lonStatus <> 0 Then
    RegDelete = "Error - Key could not be deleted"
Else
    RegDelete = "Key deleted sucessfully"
End If
' Close the open subkey with the CloseSubKey
' function.
lonStatus = CloseSubkey(lonSubkeyHandle)
End Function
Once again, the GetSubkeyHandle function is used to open the last subkey in the path given in the txtSectionName text box. Then the RegDeleteValue API function is used to delete the key value. The subkey is then closed because it is no longer needed.

The next two procedures, RegUpdate and RegAdd, are almost identical (Listing 32.17). That's because the same API function (RegSetValueEx) is used to add or update a key value.

Listing 32.17. - The RegUpdate and RegAdd functions, which both use the RegSetValueEx API function to add or update a key value.

Private Function RegUpdate() As String
Dim lonSubkeyHandle As Long
Dim lonStatus As Long
' Use the GetSubkeyHandle function to determine the
' handle of the subkey given in txtSectionName.
lonSubkeyHandle = GetSubkeyHandle(txtSectionName.Text)
If lonSubkeyHandle = -1 Then
    ' Subkey could not be located.
    RegUpdate = "Error - Subkey could not be located"
    Exit Function
End If
' Use the RegSetValueEx API function to update the
' key's value.
lonStatus = RegSetValueEx(lonSubkeyHandle, _
    txtKeyName.Text, 0, REG_SZ, ByVal txtValue.Text, _
    Len(txtValue.Text))
' Check status of the operation.
If lonStatus <> 0 Then
    RegUpdate = "Error - Key could not be updated"
Else
    RegUpdate = "Key updated sucessfully"
End If
' Close the open subkey with the CloseSubKey
' function.
lonStatus = CloseSubkey(lonSubkeyHandle)
End Function
Private Function RegAdd() As String
Dim lonSubkeyHandle As Long
Dim lonStatus As Long
' Use the GetSubkeyHandle function to determine the
' handle of the subkey given in txtSectionName.
lonSubkeyHandle = GetSubkeyHandle(txtSectionName.Text)
If lonSubkeyHandle = -1 Then
    ' Subkey could not be located.
    RegAdd = "Error - Subkey could not be located"
    Exit Function
End If
' Use the RegSetValueEx API function to create a
' new key and set its value.
lonStatus = RegSetValueEx(lonSubkeyHandle, _
    txtKeyName.Text, 0, REG_SZ, ByVal txtValue.Text, _
    Len(txtValue.Text))
' Check status of the operation.
If lonStatus <> 0 Then
    RegAdd = "Error - Key could not be added"
Else
    RegAdd = "Key added sucessfully"
End If
' Close the open subkey with the CloseSubKey
' function.
lonStatus = CloseSubkey(lonSubkeyHandle)
End Function
In both procedures, the GetSubkeyHandle function is used to get the handle of the last subkey in the path. Then the RegSetValueEx API function is called. The REG_SZ constant is used again here to indicate that the value to be added or updated is of type string.

The final bit of code to be added is for the optType option buttons, which are used to select whether an INI file or the System Registry is to be accessed. The optType_Click event (Listing 32.18) enables and disables command buttons and text boxes depending on which option is selected.

Listing 32.18. - The optType_Click event, which is triggered when the user selects either the INI File or Registry option buttons.

Private Sub optType_Click(Index As Integer)
' Enable/disable options depending on the state
' of the option buttons for INI files/Registry.
If Index = 1 Then
    txtSectionName.Enabled = True
    txtKeyName.Enabled = True
    txtValue.Enabled = True
    cmdRefresh.Enabled = False
    mnuFile.Enabled = False
    lstFileContents.Enabled = False
Else
    cmdRefresh.Enabled = True
    If dlgGetFilename.FileName = "*.ini" Then
        txtSectionName.Enabled = False
        txtKeyName.Enabled = False
        txtValue.Enabled = False
        cmdAdd.Enabled = False
        cmdRetrieve.Enabled = False
        cmdUpdate.Enabled = False
        cmdDelete.Enabled = False
        cmdRefresh.Enabled = False
    End If
    cmdRefresh.Enabled = True
    mnuFile.Enabled = True
    lstFileContents.Enabled = True
End If
End Sub
The only thing left to do is make a few changes to the user interface. Change the caption of lblSectionName to Section Name/Subkey Path because the text box it labels now serves a dual purpose. Also, change the optType(1) option button's Enabled property to True so the Registry option can now be selected.

Make sure you save the program before running it, because an errant API function can cause Visual Basic to crash. Also, heed this warning: Back up your System Registry before fooling around with modifying and deleting keys! One wrong move can have serious repercussions!

To use the Registry editing functions of the program, first select the Registry option button. Then enter the path of a valid subkey in the Subkey Path text box, remembering that it must begin with one of the six root keys (see fig. 32.6). Enter a key name and a value in the appropriate text boxes. Then use the Add, Retrieve, Update, or Delete buttons to perform operations on the Registry. You can use the RegEdit program to build subkeys that you can experiment with. You can also use that program to visualize any changes that you make.

Figure 32.6

The INI File/System Registry Editor dialog box. To manipulate keys, put the subkey path in the top text box and the key name in the middle text box.

Top Home