Death of Windows XP? (Part 4)

The last post, Death of Windows XP? (Part 3), was supposed to be the last word on this topic that won’t die, but as usual, it isn’t. The hackers of the world have figured out a new an interesting way of getting around Microsoft’s plan to kill Windows XP. It turns out that you can continue to get updates if you’re willing to use a registry hack to convince Windows Update that your system is a different version of Windows that is almost like Windows XP Service Pack 3, but not quite. You can read the article, How to get security updates for Windows XP until April 2019, to get the required details.

The hack involves making Windows Update believe that you actually own a Point of Sale (POS) system that’s based on Windows XP. The POS version of Windows XP will continue to have support until April of 2019, when it appears that Windows XP will finally have to die unless something else comes along. It’s important to note that you must have Windows XP Service Pack 3 installed. Older versions of Windows XP aren’t able to use the hack successfully.

After reading quite a few articles on the topic and thinking through the way Microsoft has conducted business in the past, I can’t really recommend the registry hack. There are a number of problems with using it that could cause issues with your setup.

 

  • You have no way of knowing whether the updates will provide complete security support for a consumer version of Windows XP.
  • The updates aren’t specifically tested for the version of Windows XP that you’re using, so you could see odd errors pop up.
  • Microsoft could add code that will trash your copy of Windows XP (once it figures out how to do so).


There are probably other reasons not to use the hack, but these are the reasons that come to mind that are most important for my readers. As with most hacks, this one is dangerous and I do have a strong feeling that Microsoft will eventually find a way to make anyone using it sorry they did. The support period for Windows XP has ended unless you have the money to pay for corporate level support—it’s time to move on.

I most definitely won’t provide support to readers who use the hack. There isn’t any way I can create a test system that will cover all of the contingencies so that I could even think about providing you with any support. If you come to me with a book-related issue and have the hack installed, I won’t be able to provide you with any support. This may seem like a hard nosed attitude to take, but there simply isn’t any way I can support you.

 

Creating Links Between File Extensions and Batch Files

A couple of weeks ago I wrote a post entitled, “Adding Batch Files to the Windows Explorer New Context Menu” that describes how to create an entry on the New context menu for batch files. It’s a helpful way to create new batch files when you work with them regularly, as I do. Readers of both Administering Windows Server 2008 Server Core and Windows Command-Line Administration Instant Reference need this sort of information to work with batch files effectively. It wasn’t long afterward that a reader asked me about creating links between file extensions and batch files. For example, it might be necessary to use a batch file to launch certain applications that require more information than double clicking can provide.

This example is going to be extremely simple, but the principle that it demonstrates will work for every sort of file extension that you can think about. Fortunately, you don’t even need to use the Registry Editor (RegEdit) to make this change as you did when modifying the New menu. The example uses this simple batch file named ViewIt.BAT.

@Echo Processing %1
@Type %1 | More
@Pause

Notice that the batch file contains a %1 entry that accepts the filename and path the Windows sends to it. You only receive this single piece of information from Windows, but that should be enough for many situations. All you need to do then is create a reasonably smart batch file to perform whatever processing is necessary before interacting with the file. This batch file will interact with text (.TXT extension) files. However, the same steps work with any other file extension. In addition, this isn’t a one-time assignment—you can assign as many batch files as desired to a single file extension. Use these steps to make the assignment (I’m assuming you have already created the batch file).

  1. Right-click any text file in Windows Explorer and choose Open With from the context menu.
  2. Click Choose Default Program from the list of options. You see the Open With dialog box shown here.
    Link01
  3. Clear the Always Use the Select Program to Open this Kind of File option.
  4. Click Browse. You see the Open With dialog box.
  5. Locate and highlight the batch file you want to use to interact with the text file (or any other file for that matter) and click Open. You see the batch file added to the Open With dialog box.
  6. Click OK. You see the batch file executed on the selected file as shown here.
    Link02

At this point, you can right-click any file that has the appropriate extension and choose the batch file from the Open With menu. The batch file will receive the full path to the file as shown in this example. It can use the information as needed to configure the environment and perform other tasks, including user interaction. Let me know your thoughts on linking file extensions to batch files at John@JohnMuellerBooks.com.

 

Using Assoc and FType to Create a New Type

On page 74 of Windows Command-Line Administration Instant Reference, I describe how to add an Open command to an existing file type, txtfile. The txtfile type already appears in the registry, so adding a new command to it is relatively straightforward. However, what happens if you want to create an entirely new type—one that doesn’t currently exist in the Registry?

Before you can do anything, you need to open an Administrator command prompt because Vista, Windows 7, and anything else newer won’t let you make the required registry changes with a standard account. Follow these steps in order to open an administrator command prompt.

 

  1. Choose Start > All Programs > Accessories.
  2. Right click the Command Prompt icon and choose Run As Administrator from the context menu. You’ll see a User Account Control dialog box.
  3. Click Yes. You’ll see a new command prompt open. However, instead of the usual title bar entry, you’ll see Administrator: Command Prompt. In addition, instead of opening to your personal user folder, the prompt will display C:\Windows\system32>. If you don’t see these differences, then you haven’t opened an administrator command prompt.


Let’s say you want to include a new file extension and it’s associated type. For example, you might want to create a .RIN file extension and associate it with a type of RINFile. Once you create this association, you may want to use Notepad to open the file. In order to perform this task, you need to use two different utilities as shown in the following steps.

 

  1. Type Assoc .RIN=RINFile and press Enter. You’ll see, “.RIN=RINFile” appear at the command line. This command creates a .RIN file extension entry in the Registry as shown here.
    AssocFType01
  2. Type FType RINFile=%SystemRoot%\Notepad.exe %1 and press Enter. Notice that you don’t enclose the command in double quotesit will fail if you do. You’ll see, “RINFile=C:\Windows\Notepad.exe %1” (or something similar) appear at the command line. This command creates the RINFile association in the Registry as shown here.
    AssocFType02
  3. Create a new .RIN file on your hard drive. You don’t have to do anything with it, just create the file.
  4. Double click the new .RIN file. Windows will open the file using Notepad.


This technique works with any file extension and association you want to create. In fact, you could easily create a batch file to patch user configurations where the file associations have become damaged in some way. The big thing to remember is that this is always a two-step process when the file extension doesn’t already exist or the association is new. Use the Assoc utility to create a link between any file extension and it’s association and the FType utility to create the association itself. Let me know if you have any questions about this technique at John@JohnMuellerBooks.com.

 

Exploring the GrabAPicture Application (Part 7)

Last week, in the Exploring the GrabAPicture Application (Part 6) post, we discussed the use of the SystemParametersInfo() function call. Of course, this call is part of the Windows API, so you have to use P/Invoke to access it. The previous post doesn’t really tell you how to use the SystemParametersInfo() call—that’s the topic of this post.

Wallpaper is defined by three elements in the GrabAPicture application: Name, Location, and Style. The Name property is easyit can be any string. So, you don’t have to work really hard with it. The Location property is the most complex because a location can’t be anywhere there is a graphic that the application can grabeither local or remote. The Style property is still complex because of the way Windows stores information, but it’s not absurdly difficult because Windows only supports so many styles. The following code looks at the Style property in the WinWallpaper class first.

Public Property Style() As Styles
   Get
      ' Contains the registry key that holds the
      ' desktop values.
      Dim StyleKey As RegistryKey
 
      ' Get the desktop key.
      StyleKey = _
         Registry.CurrentUser.OpenSubKey( _
            "Control Panel\Desktop", False)
 
      ' Obtain the current value.
      If StyleKey.GetValue("WallpaperStyle") = 2 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Stretched
      End If
      If StyleKey.GetValue("WallpaperStyle") = 1 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Centered
      End If
      If StyleKey.GetValue("WallpaperStyle") = 1 And _
         StyleKey.GetValue("TileWallpaper") = 1 Then
         Return Styles.Tiled
      End If
 
      ' This value should only show up if someone has
      ' tampered with the registry and set the values
      ' incorrectly.
      Return Styles.Unknown
   End Get
   Set(ByVal Value As Styles)
      ' Because Styles.Unknown signifies an error
      ' condition, the developer can't provide it as an
      ' input value.
      If Value = Styles.Unknown Then
         Throw New ArgumentException( _
            "Cannot use Styles.Unknown as an input argument")
      End If
 
      ' Contains the registry key that holds the
      ' desktop values.
      Dim StyleKey As RegistryKey
 
      ' Get the desktop key.
      StyleKey = _
         Registry.CurrentUser.OpenSubKey( _
            "Control Panel\Desktop", True)
 
      ' Select one of the other input values and set the
      ' registry keys as needed.
      Select Case Value
         Case Styles.Stretched
            StyleKey.SetValue("WallpaperStyle", "2")
            StyleKey.SetValue("TileWallpaper", "0")
         Case Styles.Centered
            StyleKey.SetValue("WallpaperStyle", "1")
            StyleKey.SetValue("TileWallpaper", "0")
         Case Styles.Tiled
            StyleKey.SetValue("WallpaperStyle", "1")
            StyleKey.SetValue("TileWallpaper", "1")
      End Select
   End Set
End Property

The Style property defines which style to use to display an image on the Desktop. The current version of the GetAPicture program is compatible with all versions of Windows, including Windows XP. A later post will show how to upgrade this application so it also allows use with Windows 7-specific features (Fill and Fit). As you can see, the get portion of this property begins by accessing the HKEY_CURRENT_USER\Control Panel\Desktop key of the registry as shown here.

GrabAPicture0701

Eventually, you’ll use three values from this key: TileWallpaper, Wallpaper, and WallpaperStyle. For now, all you need to know about is TileWallpaper (used to define the wallpaper as Tiled) and WallpaperStyle (used for all of the other settings). The code examines the registry settings using the GetValue() method and returns a Styles enumeration value that reflects the current settings. If the code can’t recognize a setting, then it returns Styles.Unknown.

When it comes time to set the wallpaper style, the application verifies that the style is known and then uses two calls to SetValue() to configure the TitleWallpaper and WallpaperStyle settings in the registry. As you might imagine, updating the rudimentary functionality of the code won’t be hard, but to ensure that the code works properly with all versions of Windows, you need to add version checks. This is the part that will interest you the most in the future post.

The WallpaperURI property code is a lot more complex than you might imagine for a very good reason. If you simply try to set the wallpaper location using the registry values, you won’t see any change on screen. The system won’t know to perform the update. In order to see the update and ensure that the change is reflected in any running applications, you must use the SystemParametersInfo() call as shown in the following code.

Public Property WallpaperURI() As Uri
   Get
      ' Create a variable to hold the result.
      Dim Result As New StringBuilder(120)
 
      ' Get the wallpaper URI.
      If SystemParametersInfo( _
         Convert.ToUInt32(SPI_GETDESKWALLPAPER), _
         Convert.ToUInt32(Result.Capacity), _
         Result, _
         Convert.ToUInt32(0)) Then
 
         ' Return the result as a URI.
         Return New Uri(Result.ToString())
      Else
         ' Throw an exception if there is an error.
         Throw New Exception( _
            "Error Calling SystemParametersInfo: " + _
            Marshal.GetLastWin32Error().ToString())
      End If
   End Get
   Set(ByVal Value As Uri)
      ' The usable, local path, location of the
      ' wallpaper.
      Dim Location As String
 
      ' A connection to the wallpaper online.
      Dim GetData As Stream
 
      ' The online image.
      Dim Img As Image
 
      ' Wallpaper on a Web page requires somewhat
      ' different processing than local wallpaper.
      If Value.Scheme.ToUpper() = "HTTP" Then
 
         ' Create a new path to save the image
         ' locally so it can be placed on the desktop.
         Location = Path.GetTempPath() + "WallPaper.BMP"
 
         ' The Web connection could fail, so use a Try
         ' block to obtain the file.
         Try
 
            ' Obtain a connection to the wallpaper.
            GetData = _
               New WebClient().OpenRead(Value.ToString())
 
            ' Read the wallpaper data.
            Img = Image.FromStream(GetData)
 
            ' Save the image.
            Img.Save(Location, Imaging.ImageFormat.Bmp)
 
         Catch ex As WebException
            ' The Web connection has failed.
            ' Use the existing Wallpaper.BMP
            ' file if it exists.
            If File.Exists(Location) Then
 
               ' Read the existing file instead.
               Img = Image.FromFile(Location)
 
            Else
               ' Otherwise, use the local path instead.
               Location = Value.LocalPath
 
               ' Verify the wallpaper is a BMP.
               If Not Location.ToUpper().EndsWith("BMP") Then
 
                  ' Load the file from disk.
                  Img = Image.FromFile(Location)
 
                  ' Create a new path to save the image
                  ' locally so it can be placed on the desktop.
                  Location = Path.GetTempPath() + "WallPaper.BMP"
 
                  ' Save the image.
                  Img.Save(Location, Imaging.ImageFormat.Bmp)
               End If
            End If
         End Try
      Else
         ' Assume that the wallpaper is local.
         Location = Value.LocalPath
 
         ' Verify the wallpaper is a BMP.
         If Not Location.ToUpper().EndsWith("BMP") Then
 
            ' Load the file from disk.
            Img = Image.FromFile(Location)
 
            ' Create a new path to save the image
            ' locally so it can be placed on the desktop.
            Location = Path.GetTempPath() + "WallPaper.BMP"
 
            ' Save the image.
            Img.Save(Location, Imaging.ImageFormat.Bmp)
         End If
      End If
 
 
      If SystemParametersInfo( _
         Convert.ToUInt32(SPI_SETDESKWALLPAPER), _
         Convert.ToUInt32(Location.Length), _
         New StringBuilder(Location), _
         Convert.ToUInt32(SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE)) Then
      Else
         ' Throw an exception if there is an error.
         Throw New Exception( _
            "Error Calling SystemParametersInfo: " + _
            Marshal.GetLastWin32Error().ToString())
      End If
   End Set
End Property

Working with a Windows API function requires that you do some special coding. Otherwise, the call will never work. It’s important to remember that the Windows API works mainly with numbers and not with other data types. If you want to work with a string, then the string parameter takes the form of a pointer to memory in the caller’s address space in most cases. Of course, Visual Basic doesn’t provide a means to do this directly. Creating a StringBuilder object tells Visual Basic to marshal the string as a pointer to the Windows API. In this case, the code creates a StringBuilder capable of holding 120 characters, which is normally enough to hold the location of the current wallpaper on disk.

 

PInvoke comes in handy for all sorts of purposes in Visual Basic. This application shows just one example. You can find another great example of working with PInvoke on Rod Stephen’s site with the “Remove the X Close button from a form’s system menu in Visual Basic .NET” example. I haven’t seen this particular use of PInvoke explained anywhere else, so you’ll definitely want to take a look. Of course, you can get all the information you need for working with PInvoke in my book, “.NET Framework Solutions: In Search of the Lost Win32 API.”

The call to SystemParametersInfo() comes next. Normally, you’d make a call that could fail in a try…catch block. However, a Windows API call won’t work that way. In this case, the call returns True when the call succeeds and False when it doesn’t (individual Windows API calls behave in different ways, so make sure you understand how errors are presented by individual call). When the call returns True, the property returns a new Uri containing the result provided by SystemParametersInfo(). Otherwise, the code throws an Exception. Unfortunately, you don’t know what happened. The only way to get this information is to call Marshal.GetLastWin32Error() and that information is only available when you set the value of the SetLastError property of the <DllImport> attribute (explained in the previous post) to True.

 

Some readers will take me to task for not providing a more specific exception, especially since I pound on this particular failing in many applications in my books. Because you don’t know the nature of the exception, you can’t provide anything better than an general Exception in most cases. The only way around this is to create code that laboriously tracks every sort of potential error returned by the Windows API and creates an appropriate exception for it. This may be a good topic for another post in the future, but definitely not the point of this post.

Setting the wallpaper location is complicated by the fact that the wallpaper can be either local or remote. The code pursues the remote route first. The example assumes that the remote wallpaper will be on an HTTP site. You’d need to modify the code to accommodate other schemes such as HTTPS. This might make a good future post, but let’s get into a common HTTP remote location first.

The first step is to create a copy of the wallpaper on the local drive in the user’s temporary folder. A call to Path.GetTempPath() obtains this information. The remote wallpaper is always stored in a file named Wallpaper.BMP (I chose the BMP format for compatibility purposes).

The code performs a number of tasks in a try…catch block. First, it attempts to read the file containing the image from the remote location into GetData (of type Stream). If this is successful (the image exists and you can read it), the code converts the stream into an Image object, Img. The image can be of any type that Image supports:

 

  • BMP
  • GIF
  • JPEG
  • PNG
  • TIFF

The final step is to save the image, using Img.Save() to Wallpaper.BMP in the BMP format. If there is an exception in any of these steps, the application automatically recovers in one of two ways. First, it looks for an example Wallpaper.BMP and uses it when the file exists. Second, if this first technique fails, then the application looks at the current value for the local wallpaper selection and uses it instead. In this case, the application uses the Image.FromFile() method to obtain a copy of the image and saves it to Wallpaper.BMP.

When the wallpaper is local, the code first checks to see if the wallpaper is already in BMP format. If it is, the application simply places the information in Location. Otherwise, the application reads the file using Image.FromFile() and saves the image in BMP format to Wallpaper.BMP.

No matter how the information gets there, eventually Location contains a path and filename for the file you want to display on the Desktop. At this point, the code calls SystemParametersInfo() to set the wallpaper, which indirectly sets the Wallpaper value in the registry. The call also notifies Windows that it needs to update the Desktop and any applications that are currently running that they need to repaint their displays. The user sees the new wallpaper displayed on screen.

At this point, you have a good idea of how the WinWallpaper class works. In the next post, you learn about the GrabAPictureSettings class, which is used to interact with the settings you’ve learned about in the past several posts. Please let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 8).

 

A Visual Studio Quick Guide to Accessibility (Part 1)

One of the most important accessibility aids that also applies to common users is the keyboard accelerator (or keyboard shortcut). In fact, this issue figures prominently in both C# Design and Development and Accessibility for Everybody: Understanding the Section 508 Accessibility Requirements. Just why Microsoft decided to turn this feature off in Windows 7 is a complete mystery to me. All of the pertinent examples in Professional Windows 7 Development Guide include keyboard accelerators, but you can’t see them. I’ve received a number of queries about this problem and decided that this two-part post on accessibility for Visual Studio developers is really necessary.

First, let’s discuss the keyboard accelerator from a programming perspective. A keyboard accelerator is the underline you see on a button, label, menu, or other control. You press Alt+Letter to perform the equivalent of a click or selection with the associated control. For example, most people know that you press Alt+F to access the File menu in an application that has keyboard accelerators properly implemented.

To create a keyboard accelerator, the developer precedes the letter or number with an ampersand (the & character). For example, to make the File menu respond to Alt+F, the developer would type &File in the development environment. I’ve always strongly encouraged the use of keyboard accelerators as a must have for any application because many keyboardists are seriously hindered by an application that lacks them. In fact, you’ll find the keyboard accelerators used in the vast majority of my books, even for examples that aren’t related to programming in any way.

Second, some developers who feel as I do about keyboard accelerators are upset that adding them to applications no longer works (apparently). Windows 7 hides the keyboard accelerators for some odd reason known only to Microsoft. The Ribbon interface provides an equivalent when the developer provides it, but we’re talking about all of the applications (the vast majority) that don’t use the Ribbon interface. It turns out that you must manually turn the keyboard accelerator support back on. Follow this steps to accomplish the task manually:

 

  1. Open the Control Panel, followed by the Ease of Access applet. Click the Ease of Access Enter link. You’ll see the list of options shown here:Accessibility0101
  2. Click Make the Keyboard Easier to Use. You’ll see a list of options for making the keyboard easier to use. Near the bottom of the list you’ll see the Underline Keyboard Shortcuts and Access Keys option shown here.
    Accessibility0102
  3. Check this option and then click OK. The system will now display the underlines as before.

One of the biggest questions I had while working through this is whether there is a method for turning this feature on or off at the command line. There isn’t any WMIC command that I’ve found to make the change (nor any other command for that matter), so I’m currently trying to discover the associated registry keys. I’ve found one so far. The On value in the HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Preference key must be changed to 1. However, that value alone doesn’t make the change work, so there are more keys and values to find. If anyone has some clues to provide me about this particular need, please let me know at John@JohnMuellerBooks.com. In the meantime, I’ll continue looking for the elusive registry updates required to automate this change.

 

Accessing Windows Functions

There is currently an example of how to access the registry on page 290 (Chapter 13) of VBA for Dummies. It will still work when working with the 32-bit version of Office 2010 and VBA 7.0albeit unreliably. This example won’t work with the 64-bit version of Office 2010 and could, in fact, cause problems in some cases (although I haven’t personally been able to produce something to show you, the fact remains that pointers are inherently dangerous and should be used with caution).

The registry access code shown in Listing 13-5 is used by the AccessAnObject() macro in Listing 13-6 on page 292. This macro will work fine after you make changes to the RegistryFuncs module. Here’s the current RegistryFuncs code.

' This Windows API function opens a registry key.
Public Declare Function RegOpenKey _
    Lib "advapi32.dll" _
    Alias "RegOpenKeyA" (ByVal HKey As Long, _
                         ByVal lpSubKey As String, _
                         phkResult As Long) As Boolean
 
' Use this enumeration for the top level keys.
Public Enum ROOT_KEYS
    HKEY_CLASSES_ROOT = &H80000000
    HKEY_CURRENT_USER = &H80000001
    HKEY_LOCAL_MACHINE = &H80000002
    HKEY_USERS = &H80000003
    HKEY_PERFORMANCE_DATA = &H80000004
    HKEY_CURRENT_CONFIG = &H80000005
    HKEY_DYN_DATA = &H80000006
End Enum
 
' This Windows API function reads a value from a key.
Declare Function RegQueryValue _
    Lib "advapi32.dll" _
    Alias "RegQueryValueA" (ByVal HKey As Long, _
                            ByVal lpSubKey As String, _
                            ByVal lpValue As String, _
                            lpcbValue As Long) As Boolean
 
' This Windows API function closes a registry key.
Public Declare Function RegCloseKey _
    Lib "advapi32.dll" (ByVal HKey As Long) As Boolean

Notice that these functions all use a Long data type to hold the pointer returned from the function calls. A Long isn’t the same as a pointer, but because there weren’t any pointer types before VBA 7.0 a Long was the best you could hope to achieve. VBA 7.0 does support pointers and you can see the list of them in the Application Programming Interface Compatibility topic on MSDN.

The correct replacement for the pointers in this example is a LongPtr. The LongPtr type works equally well with both 32-bit and 64-bit versions of Office. Of course, earlier versions of VBA don’t support the LongPtr type, so you need some way to work in both environments, which was the purpose of the Checking the VBA Environment post. Consequently, the new code looks like this:

' Use this enumeration for the top level keys.
Public Enum ROOT_KEYS
    HKEY_CLASSES_ROOT = &H80000000
    HKEY_CURRENT_USER = &H80000001
    HKEY_LOCAL_MACHINE = &H80000002
    HKEY_USERS = &H80000003
    HKEY_PERFORMANCE_DATA = &H80000004
    HKEY_CURRENT_CONFIG = &H80000005
    HKEY_DYN_DATA = &H80000006
End Enum
     
#If VBA7 Then
 
    ' This code replaces the Long values used in
    ' previous versions of VBA with LongPtr values.
    ' This Windows API function opens a registry key.
    Public Declare Function RegOpenKey _
        Lib "advapi32.dll" _
        Alias "RegOpenKeyA" (ByVal HKey As LongPtr, _
                             ByVal lpSubKey As String, _
                             phkResult As Long) As Boolean
     
    ' This Windows API function reads a value from a key.
    Declare Function RegQueryValue _
        Lib "advapi32.dll" _
        Alias "RegQueryValueA" (ByVal HKey As LongPtr, _
                                ByVal lpSubKey As String, _
                                ByVal lpValue As String, _
                                lpcbValue As LongPtr) As Boolean
     
    ' This Windows API function closes a registry key.
    Public Declare Function RegCloseKey _
        Lib "advapi32.dll" (ByVal HKey As LongPtr) As Boolean
 
#Else
 
    ' This Windows API function opens a registry key.
    Public Declare Function RegOpenKey _
        Lib "advapi32.dll" _
        Alias "RegOpenKeyA" (ByVal HKey As Long, _
                             ByVal lpSubKey As String, _
                             phkResult As Long) As Boolean
     
    ' This Windows API function reads a value from a key.
    Declare Function RegQueryValue _
        Lib "advapi32.dll" _
        Alias "RegQueryValueA" (ByVal HKey As Long, _
                                ByVal lpSubKey As String, _
                                ByVal lpValue As String, _
                                lpcbValue As Long) As Boolean
     
    ' This Windows API function closes a registry key.
    Public Declare Function RegCloseKey _
        Lib "advapi32.dll" (ByVal HKey As Long) As Boolean
 
#End If

Please contact me if you have any trouble with this code at John@JohnMuellerBooks.com. Also, be sure to keep letting me know about your Office 2010 needs!