Team LiB   Previous Section   Next Section

8.1 The Registry API

The original Registry API is defined in winreg.h, part of Microsoft's Win32 Software Development Kit (SDK) for NT 4.0 and Windows 95. The current version is still part of the Win32 API, but now it lives in the Microsoft Developer Network (MSDN) Platform SDK. There are 28 distinct routines in the Registry API, though most of them actually have two variants: one that works with standard one-byte ASCII strings and another that handles Unicode strings. The ASCII versions have routine names that end in "A," such as RegCreateKeyA, while the Unicode versions end with a "W," as in RegCreateKeyW. Macros in winreg.h automatically map the correct variant to the routine name. When you call RegCreateKey, you automatically get the correct Unicode or ASCII variant depending on how your header files are set up. (Of course, in Visual Basic or Perl this distinction is moot.) The Registry stores strings in Unicode format, so when you call one of the ASCII variants, the Registry code takes care of converting one encoding to another.

As if this original set of functions wasn't enough, Microsoft has added a separate set of Registry-related API routines as part of the Internet Explorer 4.0/5.0 shell. These routines are delivered as part of the Shell Lightweight Utility API, and most of them are implemented in Version 4.71 and later of shlwapi.dll. All machines running Windows 2000 or 98 have this DLL (as of this writing, it's Version 5.00), while machines running Windows 95 or NT 4.0 have it if they also have Internet Explorer 4.0 or later. Some functions discussed later in the chapter are only available as part of Internet Explorer 5.0 or later; those functions are noted.

8.1.1 API Concepts and Conventions

If you've used any other set of Win32 API routines, you'll probably find the Registry API easy to digest. If you haven't, though, a brief review of some Win32 API fundamentals will help flatten your learning curve.

8.1.1.1 Input and output parameters

Each Registry routine described next has its own unique set of parameters. These parameters give you a way to tell the API routines what you want done and how to do it. It's important to make sure you specify the parameters completely and correctly. If you don't, you'll likely get ERROR_INVALID_PARAMETER back as an error; it's entirely possible that instead you might get a corrupted Registry and a crashed machine.

In general, the C/C++ declarations for the Registry routines use pointers both for input and output. For example, strings are always passed as pointers (surprise!), as are outputs for things like security attributes and newly opened HKEYs. The Perl and Visual Basic declarations use the type system appropriate for the language, as you'll see in the sections that cover each language.

8.1.1.2 Registry error codes

Every Registry API routine returns an error code as its value. These codes, all of which are defined in winerror.h, give you an easy way to test for success or failure of an operation. Table 8.1 lists the most commonly used codes. A few routines can return other error codes as noted, but these are the ones you're most likely to see. Your code should always test for all returned errors (not just these) and handle them properly if they should occur.

Table 8.1. Registry Error Codes

Error Code

Meaning

ERROR_SUCCESS

The requested operation succeeded.

ERROR_FILE_NOT_FOUND

The requested Registry key or path doesn't exist.

ERROR_ACCESS_DENIED

The permissions on the requested key don't allow you to access it.

ERROR_INVALID_HANDLE

The HKEY you passed in isn't a valid Registry handle.

ERROR_OUTOFMEMORY

There's not enough memory to read the data you requested.

ERROR_INVALID_PARAMETER

One or more parameters you supplied are invalid; you may have omitted values for a required parameter or supplied a bad value.

ERROR_BAD_PATHNAME

The path specified doesn't exist.

ERROR_LOCK_FAILED

The internal Registry locking mechanism failed. This is usually because you're making multiple requests of the Registry from within a single process or thread.

ERROR_MORE_DATA

The buffer you provided as a parameter is too small to contain all the available data.

ERROR_NO_MORE_ITEMS

There are no more keys or values to enumerate.

ERROR_BADKEY

The key handle you provided is bad.

ERROR_BADDB

The hive that holds the key or value you requested is corrupted.

ERROR_CANTOPEN

The requested key or value can't be opened.

ERROR_CANTREAD

The requested key or value can be opened but not read.

ERROR_CANTWRITE

You can't write data to the key or value you're trying to overwrite.

ERROR_REGISTRY_RECOVERED

One or more hive files was reconstructed.

ERROR_REGISTRY_CORRUPT

Something very bad has happened to one or more hive files.

ERROR_REGISTRY_IO_FAILED

The kernel tried to read, write, or flush cached Registry data from the corresponding hive but couldn't.

ERROR_NOT_REGISTRY_FILE

The hive file you tried to load isn't a hive file.

ERROR_KEY_DELETED

You're trying to modify a key that's been deleted.

8.1.1.3 Why some calls have names ending in "Ex"

Back in ancient times,[1] the original Windows 3.x API was the One True API application developers were counseled to use. As programmers did use the API, the inertia of a large installed base made it hard for Microsoft to change the way any of the original 3.x routines worked. Instead of changing the originals, the Win32 API added new routines where necessary and gave them new names ending with Ex. For example, RegOpenKey begat RegOpenKeyEx, which adds an options flag and a SAM access context--both of which are specific to Win32.

[1] Well, all right: around 1990.

In general, you should avoid using the original routines when an Ex equivalent exists. Most of the cool features of the Windows 2000 Registry (especially those related to security) aren't available with the "classic" API. In addition, it's possible that the old-style routines will stop being supported in future Windows versions. In a few cases it may make sense to use the old-style routine anyway; I've noted these exceptions where appropriate.

8.1.1.4 "Happy families are all alike"

The whole point behind the Win32 API is that you can write programs that use a single API. As long as you stick with that API, your code should run on any Win32-compliant platform, whether it's Win95 on Intel, WinNT on Alpha, Windows 2000 on Itanium, or WinCE on whatever CPU the HPC builder chose. You're not supposed to have to care which underlying operating system is present. While this is a wonderful theory, it sometimes breaks down in practice. For example, many of the routines described here have slightly different behavior under Windows CE.[2] More importantly, some routines don't work at all under Win95.

[2] MSDN and the Win32 SDK both document these differences, so I won't go into them here.

This may be too harsh an indictment. What really happens is that the routines don't fail, but they don't do what they're supposed to; they just return ERROR_SUCCESS. This means that your code still executes under Win9x, but it may not do what you intended it to. At present, there are only four routines that behave this way under Win9x: RegRestoreKey, RegGetKeySecurity, RegSetKeySecurity, and RegNotifyChangeKeyValue. If your application uses any of these routines, be forewarned: you won't get back the data you expect when your code is run under Win9x. Be sure to handle these cases gracefully (for example, checking whether the SECURITY_DESCRIPTOR returned by RegGetKeySecurity is valid before trying to use it).

The same is true for the shell APIs I mentioned earlier: none of those APIs are supported under Windows CE, and they may have slight functional differences between Windows 2000/NT and 95/98.

8.1.1.5 New and exciting datatypes

One of Windows 2000's biggest advantages over Win9x is its robust security architecture. Since the Win32 API is supposed to be common across Win9x, Windows 2000/NT, and Windows CE devices, you may have seen, and ignored, some of the Windows 2000-specific datatypes used in Registry API routines. These datatypes can be useful, so a quick introduction will help you get familiar with them. (Skip this section if you already know how to use these types.)

The Registry API uses many standard Windows datatypes such as DWORD and LPSTR. However, there are six datatypes that are fairly unfamiliar to most programmers who haven't yet written Windows 2000-specific code. Each is used in at least one Registry call.

HKEY

The initial letter of this type should tip you off to what it is. Microsoft uses Hungarian notation,[3] so the initial H means this datatype is a handle to something. An HKEY is an opaque handle to a Registry key; the handle actually points to a large table of key references, so it's not a handle in the pointer-to-a-pointer sense most programmers usually use.

[3] This notation gets its name from Charles Simonyi, the Microsoft developer who invented the scheme. As you might infer from his surname, he's Hungarian. Despite the fact that it's ugly and restrictive, it has caught on in Windows books, perhaps because Microsoft uses it exclusively in their header files and example code.

winreg.h includes definitions for the standard six root keys. Anywhere you can use an HKEY, you can use HKEY_LOCAL_MACHINE or one of the other predefined root key HKEYs.

REGSAM

REGSAM is really a DWORD in disguise; its values represent the permission you're requesting when you open or create a key. Legal values are shown in Table 8.2. You can use any of them when creating or opening a key, but you should limit what you ask for to what you actually need. In most cases, that means either KEY_READ or KEY_WRITE.

Table 8.2. REGSAM Access Mask Values

Value

Meaning

KEY_ALL_ACCESS

Combination of KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, and KEY_SET_VALUE access

KEY_CREATE_LINK

Grants permission to create a symbolic link to specified key

KEY_CREATE_SUB_KEY

Grants permission to create new subkeys

KEY_ENUMERATE_SUB_KEYS

Grants permission to enumerate subkeys of the parent key

KEY_EXECUTE

Grants permission to read subkeys and values

KEY_NOTIFY

Grants permission to request change notification on the parent key or its values

KEY_QUERY_VALUE

Grants permission to get subkey values and their contents

KEY_READ

Combination of KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY access

KEY_SET_VALUE

Permission to change subkey values

KEY_WRITE

Combination of KEY_SET_VALUE and KEY_CREATE_SUB_KEY access

SECURITY_INFORMATION

Windows 2000 allows you to read and write ACLs on Registry keys. However, you must specify exactly which ACL you want to view or change. The SECURITY_INFORMATION type handles this; it allows you to specify any of the values listed in Table 8.3 when calling RegGetKeySecurity or RegSetKeySecurity. The first four values in the table are valid for Windows NT 4.0 or 2000; the last four are Windows 2000-only.

Table 8.3. SECURITY_INFORMATION Values

Value

Meaning

OWNER_SECURITY_INFORMATION

Indicates that you want information about the owner identifier of an object.

GROUP_SECURITY_INFORMATION

Indicates you're requesting information about the primary group identifier of the object. Only objects connected with the POSIX subsystem have this information.

DACL_SECURITY_INFORMATION

Indicates that you want information about the discretionary ACL of the object.

SACL_SECURITY_INFORMATION

Indicates that you want information on the system ACL of the object.

PROTECTED_DACL_SECURITY_INFORMATION

Indicates that this DACL may not inherit ACE entries from its parent.

PROTECTED_SACL_SECURITY_INFORMATION

Indicates that this SACL may not inherit ACE entries from its parent.

UNPROTECTED_DACL_SECURITY_INFORMATION

Indicates that this DACL inherits ACE entries from its parent object.

UNPROTECTED_SACL_SECURITY_INFORMATION

Indicates that this SACL inherits ACE entries from its parent object.

SECURITY_DESCRIPTOR

Access control data is stored in SECURITY_DESCRIPTOR structures. Like HKEY, HWND, and other types, a SECURITY_DESCRIPTOR is opaque; there's no way to decipher exactly what it points to or contains without using the Win32 security API routines. (Actually, this is a fudge. Microsoft documents the structure but sternly warns developers against reading or modifying its fields.)

SECURITY_ATTRIBUTES

The SECURITY_ATTRIBUTES structure encapsulates a security descriptor and data needed to interpret it:

typedef struct _SECURITY_ATTRIBUTES {
    DWORD  nLength; 
    LPVOID lpSecurityDescriptor; 
    BOOL   bInheritHandle; 
} SECURITY_ATTRIBUTES;

The nLength member specifies the size of the security descriptor pointed to by lpSecurityDescriptor. The bInheritHandle member controls whether a child process spawned by the process that owns the SECURITY_ATTRIBUTES structure should also receive the owning process' security descriptor.

FILETIME

The FILETIME structure contains the access date and time for an object. Its format is a little odd:

typedef struct _FILETIME { 
    DWORD dwLowDateTime; 
    DWORD dwHighDateTime; 
} FILETIME;

Together, the two DWORDs represent the number of 100-nsec intervals since 1 January 1601. I have no idea what possessed Microsoft to use this particular date as the base of their time system. Fortunately, there are a number of routines for converting between FILETIME values and more useful formats; check out FileTimeToSystemTime for one example.

8.1.1.6 New routines = new datatypes

When Microsoft added the shell utility routines as part of IE 4.0, they also had to create some new datatypes to fully support those routines. Most of the shell utility routines provide functionality not included in the standard Win32 API set. However, the file association routines (AssocCreate, AssocQueryKey, AssocQuery-String, and AssocQueryStringByKey) bundle several Registry operations into a single function. These routines actually encapsulate the IQueryAssociations COM object; its purpose is to return the correct key and OLE class information from HKCR for a specific type of document file. By providing a standard way to do this (instead of requiring every developer to roll their own) Microsoft is trying to reduce the number of association-related frustrations foisted on end users. The new datatypes are:

ASSOCF

The ASSOCF structure holds flags that specifies what data you want back from a call to one of the association functions. Table 8.4 shows the flags and their values.

Table 8.4. ASSOCF Values

Value

Meaning

ASSOCF_INIT_BYEXENAME

Finds the association for the selected executable. When this flag is not set, the query routines return the association for the .exe filetype.

ASSOCF_OPEN_BYEXENAME

Identical to ASSOCF_INIT_BYEXENAME.

ASSOCF_INIT_DEFAULTTOSTAR

If no matching association is found under the selected root key, and this flag is set, checks the HKCR\* subkey for a match.

ASSOCF_INIT_DEFAULTTOFOLDER

If no matching association is found under the selected root key, and this flag is set, checks the HKCR\Folder subkey for a match.

ASSOCF_NOUSERSETTINGS

When set, directs the query code to search HKCR only, not HKCU\Software\Classes. By default, both keys are searched, and the user value is used if present.

ASSOCF_NOTRUNCATE

If the found value is too big for the supplied buffer, don't truncate it; instead, return the required buffer length and an error.

ASSOCF_VERIFY

Cross-checks the found association with the class factory or executable that owns the associated type. Imposes a performance penalty but provides extra safety.

ASSOCF_REMAPRUNDLL

Tells the query code to ignore the presence of the rundll.exe command in the supplied command string; this prevents the query code from returning association information for rundll.

ASSOCF_NOFIXUPS

Don't fix any errors found when ASSOCF_VERIFY is set. When set, this flag may cause your code to modify Registry data.

ASSOCF_IGNOREBASECLASS

Ignores the BaseClass value when searching for associations.

ASSOCKEY

The ASSOCKEY enumerated type tells the association routines what kind of key you want returned from your association query. You have to use this type in calls to AssocQueryKey to ensure that you get the desired key in return. See Table 8.5 for the enumeration's values.

typedef enum {
	ASSOCKEY_SHELLEXECCLASS = 1,
	ASSOCKEY_APP,
	ASSOCKEY_CLASS,
	ASSOCKEY_BASECLASS,
	} ASSOCKEY;

Table 8.5. ASSOCKEY Values

Value

Indicates you're asking for...

ASSOCKEY_SHELLEXECCLASS

A handle to a key that can be passed directly to the ShellExec( ) function

ASSOCKEY_APP

A handle to the Application key for the specified file class

ASSOCKEY_CLASS

A handle to the class key or ProgID

ASSOCKEY_BASECLASS

A handle to the class BaseClass key

ASSOCSTR

The ASSOCSTR enumerated type tells the association routines what type of string you want as the result of a query. For example, you can request the friendly name of an executable or document type, the command for a particular shell verb, and so on. Table 8.6 enumerates this type's values and their meanings.

typedef enum {
	ASSOCSTR_COMMAND,
	ASSOCSTR_EXECUTABLE,
	ASSOCSTR_FRIENDLYDOCNAME,
	ASSOCSTR_FRIENDLYAPPNAME,
	ASSOCSTR_NOOPEN,
	ASSOCSTR_SHELLNEWVALUE,
	ASSOCSTR_DDECOMMAND,
	ASSOCSTR_DDEIFEXEC,
	ASSOCSTR_DDEAPPLICATION,
	ASSOCSTR_DDETOPIC
	} ASSOCSTR;

Table 8.6. ASSOCSTR Flags

Value

Indicates you're asking for...

ASSOCSTR_COMMAND

The command string associated with the specified shell verb

ASSOCSTR_EXECUTABLE

The executable name from a shell verb command string (see the note for ASSOCF_REMAPRUNDLL)

ASSOCSTR_FRIENDLYDOCNAME

The friendly name of a document type

ASSOCSTR_FRIENDLYAPPNAME

The friendly name of an application

ASSOCSTR_NOOPEN

All information except the contents of the Open subkey

ASSOCSTR_SHELLNEWVALUE

Information from the ShellNew subkey

ASSOCSTR_DDECOMMAND

The template that forms DDE commands sent to this object

ASSOCSTR_DDEIFEXEC

The DDE command that creates a new instance of the selected object's factory

ASSOCSTR_DDEAPPLICATION

The application name needed to send DDE broadcasts to the application

ASSOCSTR_DDETOPIC

The topic name needed to send DDE broadcasts to the application

HUSKEY and PHUSKEY

HKEY is an opaque type that represents a handle to an open Registry key. HUSKEY is a little different. It's a handle that represents a user-specific key (as you'll see in the next section).

8.1.1.7 User-specific keys

Windows NT 3.1 introduced the concept of multiple user profiles to the Windows world. The idea was that each user could have her own group of personal settings that would automatically be loaded when she logged on. In Windows NT 3.51, Microsoft expanded this concept to cover domains, so that users could get their personal setting (or profile) information no matter where in the domain they logged on. However, some applications store their settings under HKCU, and others use HKLM. Compounding the problem, not all programs and components keep their setting data in the Registry. The introduction of user-specific class keys (see Section 2.1.2.4) makes things even more complicated, since some per-user settings may actually be inherited from HKCR.

To fix this problem, Microsoft has introduced the concept of user-specific keys (USK). The idea is that all settings for one user can be stored beneath that user's USK, which then conveniently becomes the user's profile, making the settings portable. Applications that use the shell utility API are encouraged to use the USK functions to store and retrieve user-specific data so that all the user's profile settings are stored in the same place.

8.1.1.8 An extremely brief example

Almost every C or C++ book includes an example based on the famous "Hello, World" example from Kernighan and Ritchie's The C Programming Language. Following that venerable tradition, Example 8.1 shows what a similar program that uses the Registry looks like.

Example 8.1. A Modern Variation of the Canonical "Hello, World" Program
#include <windows.h>
#include <winreg.h>
#include <stdio.h>

// Hello, World! for the Registry: gets this machine's name and prints
// it out.
void main(void)
{
	unsigned char 	pszName[MAX_PATH] = "";
	DWORD	 nNameLen = MAX_PATH;
	HKEY 	hkResult, hStartKey = HKEY_LOCAL_MACHINE;
	long 	nResult = ERROR_SUCCESS;
	
	nResult = RegOpenKeyEx(hStartKey, 
	      "SYSTEM\\CurrentControlSet\\Control\\ComputerName\\ActiveComputerName", 
				0L, KEY_READ, &hkResult);
	if (ERROR_SUCCESS == nResult)
	{
		nResult = RegQueryValueEx(hkResult, "ComputerName", 0, 0,
								  pszName, &nNameLen);
		if (ERROR_SUCCESS == nResult)
			printf("Hello, world, from %s!\n", pszName);
		else
			printf("I don't even know my own name.\n");
	}
	RegCloseKey(hkResult);
}

Throughout the C examples in this section, you'll notice that I've had to use double backslashes (\\) in Registry paths. That's because the C preprocessor treats a single backslash as a flag character that marks a special character sequence; to get one backslash in a string, you need to include two.

This example contains code to implement the three most basic--and most common--Registry operations:

  1. Open a key whose full path you know using RegOpenKey or RegOpenKeyEx, then retain the HKEY returned when the key is opened.

  2. Use that returned HKEY to get a value whose location and type you already know (in this case, HKLM\SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName).

  3. Do something with the retrieved value, and close the key opened in Step 1.

Almost all programs that use the Registry involve these three steps. Of course, in addition to (or instead of ) reading Registry data, you can write new data to a value or enumerate a sequence of keys or values to find one that matches what you're looking for. You'll learn how to do all these things in the following sections.

In the following sections, I present the API as Microsoft defined it: using C. The sections on programming with Perl and Visual Basic contain the correct definitions for those languages.

8.1.2 Opening and Closing Keys

In Chapter 1, I pointed out the organizational similarities between a filesystem and the Registry. These similarities are more than skin deep: they extend to the actual process of moving data into and out of the Registry. In general, the same rules apply when working with Registry keys and their values as with disk files.

First and foremost, you have to open a key when you want to use it, then close it when you're done. If you don't open a key, you can't get its values. If you don't close the key when done, other applications can't access it, and your changes aren't written out when you'd expect them to be. The API routines that open keys require two arguments: a path to the key you want to open and an open parent key. This may seem like a Catch-22: how can you open a key if you must already have an open key? The answer is simple: the root keys (HKLM, HKCC, HKCU, HKU, HKDD, and HKCR) are always open, so you can use them when you open any other key.

There are exceptions to the foregoing rule: some of the shell utility API routines don't have to open or close keys. For example, you can call SHRegCreateKey, which creates a new user-specific key underneath your choice of HKLM or HKCU, without opening either parent key. You even get back a handle that you can use with other shell API routines, all without opening or closing any other keys.

The next similarity involves access controls and rights. If you're accustomed to NTFS, Unix, or Novell filesystems, you know that files and directories can have permissions attached to them that govern who can open, modify, delete, and move things around. In ACLs, files also have rights, which the ACLs grant. One entry in the ACL might grant Administrator the right to read or write a file, while another might deny write access to members of the Domain Users group. Registry keys have these same controls and rights. As you'll learn in Chapter 9, you can keep your Registry secure by putting ACLs on security-sensitive keys. When you open a Registry key, you must specify what access you want to it: read, write, enumerate, and delete are all examples. Windows 2000 checks the access you request against the ACLs on the Registry key to decide whether or not you get access.

The best way to stay out of trouble when opening and closing keys is to remember to balance key openings with closings. Later in the chapter (in Section 8.3.2), you'll see a C++ class, StKey, that automates the cleanup process. Please be sure to close any keys you open even when errors or exceptions interrupt the normal flow of control in your code.

8.1.2.1 Opening keys

When you're ready to open a key, there are two different approaches you can take. The first one is to use the RegCreateKey or RegCreateKeyEx functions, which I'll talk about in a bit. They'll automatically open the key you specify or create it if it doesn't exist. The second method, which is probably better for most applications, is to open the key with RegOpenKeyEx or RegOpenKey. Why are these calls better? They fail when you try to open a key that doesn't exist, while the RegCreate functions will create a new key with no values in it. Imagine that you're calling a friend named Bill on the phone. If you call and are told "Bill's not here" by the person who answers, that's the equivalent of calling RegOpenKey routines on a nonexistent key. By contrast, calling Bill and being told "Bill's not here, but I'll pretend to be him" is more or less what happens when you call RegCreate. That may sometimes be desirable, but it's not a pleasant surprise if you're not expecting it.

The recommended way to open a key is with RegOpenKeyEx. You supply an open key, which may be a root key or a key you've already opened; the name of the full path to the key you want to open; and a mask describing what access you want to the newly opened key.

LONG RegOpenKeyEx(hKey, pszSubKey, dwOptions, samDesired, phkResult);

HKEY
hKey

Handle to any open key or root key.

LPCTSTR
pszSubKey

Name of the subkey of hKey you want to open; if NULL or empty, RegOpenKeyEx just opens an additional copy of hKey instead.

DWORD
dwOptions

Reserved; must be 0.

REGSAM
samDesired

Mask defining access rights you're asking for (just use either KEY_READ or KEY_WRITE).

PHKEY
phkResult

Pointer to the newly opened key; NULL if an error occurs.

The following code opens a key under HKLM for reading, then goes on to do some other processing (which I've omitted here). If you combine the root key and the value of pszSubKey, you'll see that the key being opened is HKLM\SOFTWARE\LJL\ArmorMail\Users; if I'd already had any key in that path open (for example, HKLM\SOFTWARE\LJL) I could have shortened the subkey name accordingly.

DWORD		result = ERROR_SUCCESS;
HKEY		firstKey;

// try to open the user list key; if we succeed, enumerate its subkeys
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\LJL\\ArmorMail\\
                      Users", 0L, KEY_READ, &firstKey);
if (ERROR_SUCCESS == result)
	...

If you try to open a key for access that the DACL on the key doesn't allow (for example, trying to open any of the HKLM\HARDWARE subkeys for write access from an unprivileged user account), you get ERROR_ACCESS_DENIED for your trouble. One of the "strongly recommended" criteria for getting the Win9x and Windows 2000 certification labels is that you should open keys with the privileges you need: don't ask for KEY_ALL_ACCESS when what you really need is KEY_READ. You should ask for write access only when you're ready to write data to the Registry; this reduces the risk that your code will accidentally damage the Registry while you're developing it.

If you're willing to use the default system security mask for key access, you can use the RegOpenKey function instead. It takes the same hKey, pszSubKey, and phkResult parameters as RegOpenKeyEx, but it doesn't accept a desired SAM mask.

LONG RegOpenKey(hKey, pszSubKey, phkResult);

HKEY
hKey

Handle to any open key or root key.

LPCTSTR
pszSubKey

Name of the subkey you want opened; if NULL or empty, RegOpenKey opens another copy of hKey.

PHKEY
phkResult

Pointer to the newly opened HKEY.

The only difference between RegOpenKey and RegOpenKeyEx is that the latter has two extra parameters. Apart from that, they function identically. One portability warning, though: as with the other Win 3.x Registry API calls, RegOpenKey is unsupported on Windows CE. If you're writing code you want to be portable, stick with the .Ex functions, tempting though the old ones may be.

8.1.2.2 Opening a key while impersonating another user

As it turns out, Windows NT and 2000 both cache the contents of HKCU for all threads in a process. This is a big efficiency win (which is why Microsoft did it), but if you're writing an application that uses multiple threads, it can pose a sticky problem if any of those threads has to impersonate another user. For example, let's say you're writing an antivirus utility. You want it to be able to scan memory and files owned by whichever users are present on the system, so you code it to spawn one thread for each interactive or network user. Guess what? The default behavior results in your application reading, and storing, settings only in HKCU, even if other users have set preferences in their own profiles. This problem is particularly acute for people who are writing management utilities that have to deal with users and services sharing a computer (or, worse, using Terminal Services).

There's a way to fix this when writing applications for Windows 2000: the RegOpenCurrentUser call opens the appropriate user-specific key for the thread that calls it. For example, if you have one thread running as Administrator and another running as RA\paulr, and each thread calls RegOpenCurrentUser, one thread gets HKU\Administrator and one gets HKU\paulr.[4]

[4] Actually, these names would be replaced by SIDs, but you get the idea.

LONG RegOpenCurrentUser(rDesiredPerms, phkResult);

REGSAM
rDesiredPerms

Permissions you want to have on the user-specific key.

PHKEY
phkResult

Pointer to the newly opened HKEY.

8.1.2.3 Opening the user's class data

In Windows 2000, the class information that used to live only in HKCR has been partitioned into two chunks: one that lives in HKCR and one that occupies the new, user-specific HKCU\Classes subkey. When you want data about OLE/ActiveX objects or class definitions (say, to find out which class factory to use to create a new object), how do you know where to look? Worse still, what if you're writing a multiuser or server-based application that needs to get the correct settings for whatever user is currently making a request? Oh, the horror.

The solution is a new, Windows 2000-only API call, RegOpenUserClassesRoot. This routine allows you to open a handle to the class data for a particular user. Windows 2000 automatically combines that user's HKCU\Classes key with the machine's HKCR data to present a single unified tree to your program.

LONG RegOpenUserClassesRoot(hToken, dwOptions, samDesired, phkResult);

HANDLE
hToken

Access token that identifies the user whose data you want.

DWORD
dwOptions

Reserved; must be 0.

REGSAM
samDesired

Mask defining access rights you're asking for (just use KEY_READ or KEY_WRITE).

PHKEY
phkResult

Pointer to the newly opened HKEY; NULL if an error occurs.

The dwOptions, samDesired, and phkResult parameters are all pretty straightforward, since they work the same as they do when calling RegOpenKeyEx. hToken takes a little more explaining: it's a process token like the one the system generates internally when you log on interactively. In fact, you can pass that same token to RegOpenUserClassesRoot, but normally you wouldn't need to, since you can get the active user's class data when running processes in that user's context. It's more likely that you'd need to get a token representing some user other than the current user. For example, in a multiuser server application, you'd probably want to retrieve each individual user's data by opening their class data key. There are six routines that can give you back a token of the type you need to call RegOpenUserClassesRoot: (see Table 8.7). Which of these routines you use will depends on what you're trying to do. Most of the time, though, you'll probably use either LogonUser, OpenProcessToken, or OpenThreadToken.

Table 8.7. API Routines That Can Give You a Token to Use with RegOpenUserClassesRoot

API routine

Use it when you want to...

LogonUser

Log a new user on to the local computer and run processes as that user.

CreateRestrictedToken

(Windows 2000 only) Create a new token with fewer privileges than some existing token.

DuplicateToken

Duplicate an existing token, keeping the same access privileges.

DuplicateTokenEx

Duplicate an existing token, creating either an exact duplicate or an impersonation token.

OpenProcessToken

Obtain a handle to the access token of an existing process.

OpenThreadToken

Obtain a handle to the access token of an existing thread.

8.1.2.4 Closing keys

There's only one way to close a handle to a key: RegCloseKey. You pass in the HKEY you want to close. If it's successfully closed, you get ERROR_SUCCESS back. Otherwise, you get an error that indicates what went wrong.

LONG RegCloseKey (hKey);

You can actually call RegCloseKey on one of the predefined root key entries. It reports a successful close but doesn't actually close the root key. This frees you from worrying about whether the HKEY you're trying to close is really yours or not.

When you close an HKEY, any data you've changed in that HKEY or its subkeys may be written to disk. On the other hand, it may not; the Registry support code may cache these changes until the next time it's convenient to flush them out to disk. Don't assume that your changes are immediately preserved as soon as you close a HKEY. Do assume that your changes are not preserved until you do so.

8.1.3 Creating Keys

You can create new keys anywhere you have permission. As I pointed out in earlier chapters, you probably won't need to do so very often unless you're writing applications that use the Registry to store their parameters. Just in case, though, here's how to do it.

RegCreateKeyEx is the most powerful function for creating a new key. When you ask it to create a key, it does so, then opens it. If the key already exists, it just opens it and returns a handle to it. In either case, after a successful call to RegCreateKeyEx you'll have an open key handle that can be used for all manner of things as described elsewhere in the chapter.

LONG RegCreateKeyEx(hKey, pszSubKey, Reserved, pszClass, dwOptions, 
     samDesired, lpSecurityAttributes, phkResult, lpdwDisposition);

HKEY
hKey

Handle to an open key under which the new subkey is created; applications can't create keys directly under HKLM or HKU.

LPCSTR
pszSubKey

Path to the new subkey you want to create; this path is interpreted relative to hKey. The pathname must not begin with a backslash. Any keys in the path that don't exist are created for you.

DWORD
Reserved

Reserved; must be NULL.

LPCSTR
pszClass

Specifies the class of the key. Microsoft says "No classes are currently defined; applications should pass a NULL string."

DWORD
dwOptions

May be REG_OPTION_NON_VOLATILE (creates the key as a normal, persistent key), REG_OPTION_VOLATILE (creates the key as a volatile key that is never stored to disk), or REG_OPTION_BACKUP_RESTORE (ignores samDesired and attempts to open the key for backup/restore access.) The default is REG_OPTION_NON_VOLATILE.

REGSAM
samDesired

Contains an access mask specifying what access you want to the new key; see Table 8.2 for a complete list.

LPSECURITY_ATTRIBUTES
lpSecurityAttributes

On input, points to a SECURITY_ATTRIBUTES structure that controls whether child processes and threads may access this key. Leave this NULL to turn off inheritance.

PHKEY
phkResult

Pointer to HKEY containing the newly opened key.

LPDWORD
lpdwDisposition

Points to a DWORD that indicates what happened; it is set to REG_CREATED_NEW_KEY if the requested key has to be created, or REG_OPENED_EXISTING_KEY if the key is merely opened.

When you open an existing key, RegCreateKeyEx ignores the lpClass, dwOptions, and lpSecurityAttributes parameters, since their values are determined by the existing key.

Once you successfully call RegCreateKeyEx, you're guaranteed to have an open HKEY you can use to add values or subkeys. Of course, a newly created key won't have any of either item, but an existing key that RegCreateKeyEx opened might indeed; be sure to check lpdwDisposition if you need to know whether the key was created or just opened.

You can use RegCreateKeyEx as a mutual-exclusion locking mechanism (or mutex) for two or more processes. When one process creates a new key using RegCreateKeyEx, the return value is REG_CREATED_NEW_KEY. When subsequent processes try to create the same key, they get back REG_OPENED_EXISTING_KEY, which they can use as a signal that the mutex is in use. Windows NT and 2000 offer more sophisticated mutex mechanisms, but this one has the advantage that it works on any variant of the Win32 API--even under emulators like Linux's Wine.

You can also use the less-flexible RegCreateKey, but neither Microsoft nor I recommend it. It lacks a way to specify what access or security attributes you want to apply to the key, meaning that it may fail unexpectedly when trying to open an existing key that has an ACL applied to it. In addition, it doesn't tell you whether it created a key or opened it.

LONG RegCreateKey(hKey, pszSubKey, phkResult);

HKEY
hKey

Handle to an open key under which the new subkey is created; applications may not create keys directly under HKLM or HKU.

LPCSTR
pszSubKey

Full path of key you want to create; any components that don't exist are created.

PHKEY
phkResult

Pointer to HKEY containing newly opened key.

8.1.4 Getting Information About Keys

Every key has a great deal of information associated with it, even if it's not immediately obvious. When you use one of the Registry editing tools, you see a neatly tree-structured view of what's beneath each root key, but the system maintains a lot more data beneath the surface so that it can efficiently access keys and values and give them back to requesting programs.

RegQueryInfoKey gives you access to a total of 11 different pieces of data for any key in the Registry. Typically you use it to find how many subkeys or values exist so you can efficiently enumerate through them (more on that in the next section). RegQueryInfoKey looks like the following.

LONG RegQueryInfoKey(hKey, pszClass, lpcbClass, lpReserved, lpcSubKeys,
	lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, 
	lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime);

HKEY
hKey

Handle to any open key or root key.

LPTSTR
lpClass

Points to a buffer that receives the key's class name. May be NULL if you don't want the class name back.

LPDWORD
lpcbClass

Points to a DWORD containing the length of the class name passed back in lpClass. May be NULL if lpClass is also NULL; if one is NULL, but the other isn't, you get ERROR_INVALID_PARAMETER back.

LPDWORD
lpReserved

Reserved; must always be NULL.

LPDWORD
lpcSubKeys

Points to a DWORD that receives the number of subkeys of hKey.

LPDWORD
lpcbMaxSubKeyLen

Points to a DWORD that holds the length (not including the terminating NULL) of the longest subkey name under hKey.

LPDWORD
lpcbMaxClassLen

Points to a DWORD that holds the length, not including the terminating NULL, of the longest class name of any key under hKey.

LPDWORD
lpcValues

Points to a DWORD that holds the number of values attached to hKey.

LPDWORD
lpcbMaxValueNameLen

Points to a DWORD that receives the length of the longest value name. This is useful when using RegEnumValue.

LPDWORD
lpcbMaxValueLen

Points to a DWORD that receives the length of the longest value contents. This is also useful when using RegEnumValue.

LPDWORD
lpcbSecurityDescriptor

Points to a DWORD that receives the size of the security descriptor associated with this key. Security descriptors can vary in size, so it's helpful to know how big a particular key's descriptor is before calling RegGetKeySecurity.

PFILETIME
lpftLastWriteTime

Points to a FILETIME structure (see Section 8.1.1.5) that is filled in with the date and time hKey or any of its values were modified.

Any of the parameters except hKey can be NULL; if you specify NULL for a parameter, that data isn't returned. Here's a small routine that gets the number of values attached to any open Registry key; notice that it passes NULL for everything except lpcValue :

DWORD GetKeyValueCount(HKEY inKey)
// Gets the count of values attached to a particular key. Returns
// the value count (which may be 0) or -1 if an error occurs.
{
    DWORD valCount = 0;
    DWORD result = ERROR_SUCCESS;

    result = RegQueryInfoKey (inKey, 
                              NULL, NULL,   // class & class size
                              NULL,         // reserved
                              NULL,         // # of subkeys
                              NULL,         // subkey length
                              NULL,         // class length
                              &valCount,    // # of values
                              NULL, NULL, NULL, NULL);
    if (ERROR_SUCCESS !=  result)
        valCount = -1;
    return valCount;
}

It's worth making special mention of lpcSubKeys, lpcValues, lpcbMaxValueNameLen, and lpcbMaxValueLen. It's often necessary to do some kind of processing over every key or value under a particular subkey. This enumeration is nothing more than an iterative loop that starts with the first key or value of interest, then proceeds on, continuing until it has processed every key or value. For example, you could enumerate the subkeys of HKU to find out the SIDs of every installed local account on a machine. Armed with that information, you can look up the account names to build a list of users who have profiles on the machine.

These four parameters make it easier to efficiently enumerate keys and values. Knowing how many items there are makes it possible to enumerate any subset of a key's values, and knowing the maximum name and content lengths means you can allocate a buffer that's just the right size, instead of too big or too small, to hold the data returned by the enumeration.

8.1.5 Enumerating Keys and Values

The enumeration API routines treat a key's subkeys or values as an ordered list of n values, numbered from to n-1. You pass an index value to the API routines to indicate which key or value you want; the corresponding key or value is returned. For values, there's an extra wrinkle: keys can have a default value, which always appears as item in the enumeration list. (You'll see how this works in Section 8.1.5.3 later in this chapter.) This is convenient, but don't be misled: the values or keys aren't really an ordered list, and if you enumerate the same subkey twice in a row, you can potentially get items back in a different order each time.

8.1.5.1 Enumeration strategies

When you enumerate keys or values, there are a few different strategies you can use to process all the enumerated keys. The easiest way is to call RegQueryInfo-Key to find out how many subkeys or values exist, then use a simple loop to process every key or value. A small snippet implementing this tactic might look like:

DWORD idx=0, keyCount = 0
LONG retVal = 0;

retVal = RegQueryInfoKey (inKey, 
                              NULL, NULL,   // class & class size
                              NULL,         // reserved
                              &keyCount,    // # of subkeys
                              NULL,         // subkey length
                              NULL,         // class length
                              NULL,         // # of values
                              NULL, NULL, NULL, NULL);

for (idx=0; idx < keyCount; idx++)
{
     // get the idx'th key's name and length
     retVal = RegEnumKeyEx(interestingKey, idx, name, nameLen, NULL, 
          NULL, NULL, NULL);

     // do something with it
}

This approach has the advantage of being simple to implement and understand. However, you may not want to process every key or value. Instead, if you want to process only keys or values that meet some criterion, you can use a conventional while loop like this:

DWORD idx = 0;
bool keepGoing = true;
LONG retVal = 0;

while (keepGoing)
    {
        retVal = RegEnumKeyEx(interestingKey, idx++, name, &nameLen, 
                            (unsigned long *)NULL, (char *)NULL, 
			       (unsigned long *)NULL, (LPFILETIME)NULL);
        if (ERROR_SUCCESS == retVal)
        {
            // If we're interested in this key, we'd process it further; 
            // we might also set keepGoing here if we only want one key
        }
        keepGoing = (keepGoing && retVal == ERROR_SUCCESS);
    }

With this approach, you don't have to know in advance how many keys or values exist, and it's a simple matter to stop enumerating as soon as you find what you're looking for.

8.1.5.2 Enumerating keys

You enumerate keys using the RegEnumKeyEx and RegEnumKey routines. They're very similar; the primary difference is that RegEnumKeyEx allows you to retrieve the modification time and class name for a subkey, while RegEnumKey doesn't. In either case, you simply supply the HKEY you want enumerated and an index value that indicates which subkey you want to see. The name (not the complete path) of the corresponding subkey is returned, so you can open any subkey you find by passing the name to RegOpenKey or RegOpenKeyEx.

LONG RegEnumKeyEx(hKey, dwIndex, pszName, lpcbName, lpReserved, pszClass, 
	 lpcbClass, lpftLastWriteTime);

HKEY
hKey

Handle to any open key or root key.

DWORD
dwIndex

Index, from to the number of subkeys-1, indicating which key you want to fetch.

LPSTR
pszName

Points to an area that will receive the name of the enumerated key.

LPDWORD
lpcbName

Points to a DWORD containing the size of pszName; on return, contains the length of pszName in bytes, including the NULL terminator.

LPDWORD
lpReserved

Reserved; as always, must be NULL .

LPSTR
pszClass

Points to a buffer that receives the subkey's class name; may be NULL if you don't care about this datum.

LPDWORD
lpcbClass

Points to a DWORD containing the size of pszClass ; on return, contains the length of pszClass in bytes, including the NULL terminator. May be NULL only if pszClass is also NULL .

PFILETIME
lpftLastWriteTime

Points to a structure that is filled in with the date and time of the last modification to the subkey; may be NULL if you're not interested.

RegEnumKey is identical in function, except for having fewer parameters.

LONG RegEnumKey(hKey, dwIndex, pszName, cbName);

HKEY
hKey

Handle to any open key or root key.

DWORD
dwIndex

Index, from to the number of subkeys, indicating which key you want to fetch.

LPSTR
pszName

Points to an area that receives the name of the enumerated key.

LPDWORD
cbName

Points to a DWORD containing the size of pszName ; on return, contains the length of pszName in bytes, including the NULLterminator .

8.1.5.3 Enumerating values

Once you've located a key of interest, you might want to enumerate its values. Most Registry keys have at least one value; quite a few have many values whose number and contents vary from machine to machine. (HKCR is a good example, because it differs depending on what classes and objects are registered on a machine.) You can accomplish this with RegEnumValue: [5]

[5] Surprisingly, there's no RegEnumValueEx. The original function hasn't changed since its introduction, so Microsoft left it alone in Win32.

LONG RegEnumValue(hKey, dwIndex, pszValueName, lpcbValueName, lpReserved,
         lpType, lpData, lpcbData);

HKEY
hKey

Handle to any open key or root key.

DWORD
dwIndex

Ordinal index of the value you want to fetch; you usually start with and move up until you either get ERROR_NO_MORE_ITEMS or hit the number of items returned by RegQueryInfoKey.

LPSTR
pszValueName

Points to a buffer that, on return, contains the value's name.

LPDWORD
lpcbValueName

On entry, points to the size of pszValueName; on return, points to length of string copied into pszValueName, not including the NULL terminator.

LPDWORD
lpReserved

Reserved; must be NULL.

LPDWORD
lpType

Points to a buffer that, on return, holds the type of the requested value (REG_DWORD, REG_SZ, etc.). May be NULL if you don't care what type the value is.

LPBYTE
lpData

Points to a buffer into which the contents of the specified value are copied.

LPDWORD
lpcbData

On entry, points to a DWORD containing the size of lpData; on return, contains the number of bytes written into lpData.

To see RegEnumValue in action, check out Example 8.7 in Section 8.3.3 later in this chapter; the example illustrates the basic things you should do when enumerating a set of values:

  • Call RegQueryInfoKey first to get the maximum subkey length, then use that to allocate any buffers you need to get the value name or contents.

  • Make sure you either check for ERROR_NO_MORE_ITEMS or honor the number of values returned by RegQueryInfoKey.

  • Open the parent key with KEY_READ or KEY_QUERY_VALUE access.

8.1.6 Getting Registry Data

Maybe you patiently enumerated a sequence or keys, or perhaps you already know just where the data you want is stored. Either way, at some point you'll want to actually retrieve a value stored under some Registry subkey. If you used RegEnumValue, you could have gotten the value's contents when you enumerated it, but if you just want to grab a single value whose path you know, there are better ways for doing so.

8.1.6.1 Getting a single value

The first, and most useful, method of getting a single value's contents out of the Registry is the RegQueryValueEx function. As its name implies, it's a Win32 routine; you supply an open key and a value name, and it returns the value's datatype, length, and contents.

LONG RegQueryValueEx(hKey, pszValueName, lpReserved, lpType, lpData,  lpcbData);

HKEY
hKey

Handle to any key or root key opened with KEY_READ or KEY_QUERY_VALUE access.

LPTSTR
pszValueName

Name of the value to query; if NULL or empty, queries default value.

LPDWORD
lpReserved

Unused; must be NULL .

LPDWORD
lpType

On return, holds the datatype of the value (REG_DWORD, REG_SZ, etc.). If you pass in NULL, no type data is returned.

LPBYTE
lpData

Points to the buffer that holds the value's contents on return. If you pass in NULL, no value data is returned but the lpcbData parameter holds the length of the contents.

LPDWORD
lpcbData

On input, points to the buffer that specifies the size of lpData buffer. On return, holds amount of data copied into lpData. You can pass in NULL if lpData is NULL also.

The most straightforward way to call RegQueryValueEx is just to get the value, like this (assuming you're fetching a REG_DWORD value named "SomeValue" from a previously opened key):

nResult = RegQueryValueEx(hOpenKey, "SomeValue", NULL, NULL, 
         (LPBYTE)&theValue, &valSize);

Since you always know how big a DWORD is, the size really isn't important. Things get a little more complex when querying for a string value named "SomeStringValue". At runtime, you don't know the string's length, which means you must either dynamically allocate a buffer or check to see whether there's more data available than your buffer can hold. RegQueryValueEx returns ERROR_MORE_DATA if the requested value has more data than can fit in the buffer length as specified by lpcbData:

DWORD bufSize = MAX_PATH;
char theBuf[MAX_PATH];

nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL, NULL, 
          (LPBYTE)theBuf, &bufSize);
if (ERROR_MORE_DATA == nResult)
{
     // too much data for our buffer; fail, use another buffer, or do
     // something else
}
else if (ERROR_SUCCESS == nResult)
{
     // continue normally
}

Alternatively, you can find out how big the value is, then allocate the buffer for it. This approach requires an extra Registry query but lets you economize on memory by not allocating any more than you actually need:

DWORD bufSize = 0;
char *theBuf = NULL;

nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL, NULL, 
                          NULL, &bufSize);
if (ERROR_SUCCESS == nResult)
{
   theBuf = (char *)malloc(bufSize+1);  // allow extra byte for NULL 
                                        // terminator
   if (theBuf)
   {
       nResult = RegQueryValueEx(hOpenKey, "SomeStringValue", NULL,
                                 NULL, (LPBYTE)theBuf, &bufSize);
       if (ERROR_SUCCESS == nResult)
         // do whatever with the value
       free(theBuf);
   }
}

Notice that this code snippet adds an extra byte to the buffer to allow for the NULL terminator, which may be stored as part of the string. Also notice that extra space isn't allocated for a Unicode string: if you define UNICODE, the initial call returns the string's Unicode length in bufSize, but if UNICODE isn't defined, the string is converted into ANSI, and bufSize contains the ANSI string length.

The MSDN documentation for RegQueryValueEx points out that it can return things you didn't ask for in some cases. In particular, if you use RegQueryValueEx to query a value under HKEY_PERFORMANCE_DATA, the data you get back in lpData may contain some extraneous data, so you have to walk through the value's contents yourself to see what's in it.

You can also use RegQueryValue to request a key's value, but it can get only the default value (remember, that's the only value Win3.x supports, and RegQueryValue is a 3.x compatibility function).

LONG RegQueryValue(hKey, pszSubKey, pszValue, lpcbValue );

HKEY
hKey

Points to any currently open key or one of the root keys.

LPCSTR
pszSubKey

Points to the subkey of hKey whose default value you want to get. If it's NULL, RegQueryValue fetches the value of hKey.

LPSTR
pszValue

Points to a buffer that holds the value contents; may be NULL if you only want the contents' length.

LPDWORD
lpcbValue

On entry, points to the length of lpValue ; on return, indicates the actual length of the value's contents.

8.1.6.2 Getting multiple values

You can retrieve multiple values from a key at once using RegQuery-MultipleValues, but its interface can be a little confusing.

LONG RegQueryMultipleValues(hKey, valList, numVals, pszValueBuf,  ldwTotalSize);

HKEY
hKey

Points to any currently open key or one of the root keys. The key must be opened with KEY_SET_VALUE or KEY_WRITE access.

PVALENT
valList

Array of VALENT structures (see the next paragraph); each item holds the name of a value to retrieve on entry and the value's data on exit.

DWORD
NumVals

Number of elements in the valList array.

LPTSTR
pszValueBuf

Points to the buffer which, at exit, holds the retrieved values.

LPDWORD
ldwTotalSize

On entry, points to the size (in bytes) of pszValueBuf; at exit, returns the number of bytes written to the buffer.

To use this function, fill out an array of VALENT structures: you put the value name you're looking for in ve_valuename, and RegQueryMultipleValues fills in the other fields for you:

typedef struct value_entA {
    LPSTR  ve_valuename;
    DWORD ve_valuelen;
    DWORD ve_valueptr;
    DWORD ve_type;
}VALENTA, FAR *PVALENTA;

On entry, pszValueBuf should point to a buffer big enough to hold all the value data you're requesting. On return, you can iterate through valList; each item's ve_valueptr member points to the location within pszValueBufwhere the value data's actually stored. You can also call RegQueryMultipleValues with an pszValueBuf of NULL; when you do, ldwTotalSize contains the buffer size required to hold all the requested values.

8.1.7 Adding and Modifying Values

Keys can signify things based on their presence or absence, but values are the best way to store persistent data in the Registry. The RegSetValueEx function does double duty; it can create new values or change the contents of existing ones.

LONG RegSetValueEx(hKey, pszValueName, Reserved, dwType, lpData, cbData);

HKEY
hKey

Points to any currently open key or one of the root keys. The key must be opened with KEY_SET_VALUE or KEY_WRITE access.

LPCSTR
pszValueName

Name of the value to set; if no value with the specified name exists, RegSetValueEx creates it. If pszValueName is empty or NULL, the supplied value is assigned to the key's default value.

DWORD
Reserved

Unused; must be 0.

DWORD
dwType

Type of the value you're adding or modifying; may be any of the types defined in Chapter 2.

CONST BYTE *
lpData

Data to load into the value.

DWORD
cbData

Length (in bytes) of the data pointed to by lpData. If the value contents are of type REG_SZ, REG_EXPAND_SZ, or REG_MULTI_SZ, cbData must reflect the length of the string plus the terminating NULL character.

If you call RegSetValueEx with the name of an existing value in pszValueName, its contents and type is replaced by whatever you pass in. If no such value exists, it's created with the contents and type you specify.

In addition to RegSetValueEx, there's also a second value-setting function you may use: RegSetValue. It was originally part of the Win 3.1 Registry API. You may remember from Chapter 1 that the Win 3.1 Registry allowed only a single value for each key. In keeping with that heritage, RegSetValue allows you to set only the default value for a key, and the value you set must be a REG_SZ. I present this function for completeness, but you should avoid it in favor of RegSetValueEx.

LONG RegSetValue(hKey,pszSubKey, dwType, pszData, cbData);

HKEY
hKey

Key to which the new value is added; can be any currently open key or root key.

LPCSTR
pszSubkey

Name of subkey that gets the value; if NULL, the value is added to hKey.

DWORD
dwType

Datatype of new value; must be REG_SZ.

LPCSTR
pszData

Pointer to string buffer containing new value's contents.

DWORD
cbData

Length of pszData, not including its terminating NULL character.

As with RegSetValueEx, if the key named in pszSubkey doesn't exist, it's created. In an additional twist, if the key named by pszSubkey doesn't exist, RegSetValue creates any keys necessary to construct a legal path, then adds the default value to it. Note that if all you want is to set the default value, you can do so using RegSetValueEx and passing NULL for pszValueName.

Example 8.2 illustrates how RegSetValueEx works; the example sets the DiskSpaceThreshold value to the percentage of disk space you specify. This routine is used in a tool I wrote that configures new servers with the desired default settings before delivering them to customers or remote sites.

Example 8.2. SetDiskWarningThreshold
// This routine sets the DiskSpaceThreshold to the specified percentage.
// You should check all the system's disk volumes to figure out a reasonable
// percentage for the machine, then call this routine to set it.
long SetDiskWarningThreshold(const int inThreshold) 
{
    char    pszName[MAX_PATH] = 
       "System\\CurrentControlSet\\Services\\LanmanServer\\Parameters";
    HKEY    hkResult = NULL;
    LONG    nResult = ERROR_SUCCESS;

    // preflight our arguments  
    if (inThreshold < 1 || inThreshold > 99)
        return ERROR_INVALID_PARAMETER;
        
    // open the key with write access so we can set the value    
    nResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, pszName, 0L, KEY_WRITE, &hkResult);
    if (ERROR_SUCCESS == nResult)
    {
        nResult = RegSetValueEx(hkResult, "DiskSpaceThreshold", 0L, 
                                REG_DWORD, (unsigned char *)&inThreshold,  
                                sizeof(int));
        if (ERROR_SUCCESS == nResult)
            nResult = RegCloseKey(hkResult);
    }
    return nResult;
}

8.1.8 Deleting Keys and Values

You may find it necessary to delete keys or values from within your home-brewed Registry utilities. Since many of the lesser-known features of Windows 2000 and NT discussed in Chapter 10, function based on the presence or absence of special trigger keys, turning these features on or off may require you to delete values, and there's no way to do so with a .REG file. You must be careful with your newfound destructive powers, though; accidentally deleting the wrong key or value can make your system stop working altogether.

Before you delete a key or value, you must have the parent key opened with adequate access. If you supply KEY_WRITE as the REGSAM value when you open the key, you can delete it. You can also request KEY_CREATE_SUB_KEY or KEY_SET_VALUE rights to gain delete access to keys and values, respectively.

There's one other thing worth mentioning here: when you delete a key or a value, it's not actually deleted. Instead, the Registry subsystem marks the deleted items as deleted, but doesn't delete them until the next time Registry data is flushed (either explicitly with RegFlushKey or automatically by the kernel's lazy flusher). If you try to read, write, or enumerate a key or value that's been marked as deleted, you get ERROR_KEY_DELETED as a return value. You can always call RegCloseKey on a deleted key without getting an error, though.

8.1.8.1 Deleting a key

You delete individual keys with the RegDeleteKey routine. If you specify a valid key and subkey, the key is immediately marked for deletion, even if other processes currently have the key open. This is different from the file metaphor used elsewhere in the Registry; if you try to delete an open file, the delete operation will fail, but not so with RegDeleteKey. At that point, attempts by other processes to access data attached to the open key will fail.

LONG RegDeleteKey(hKey, pszSubKey);

HKEY
hKey

Key pointing to parent of target value; may be a root key or a subkey.

LPCSTR
pszSubkey

Name of the subkey to be deleted; if NULL or empty, the key specified by hKey is deleted.

You can't delete a root key, and you can't delete first-level subkeys of root keys. For example, you can't remove HKLM\SOFTWARE or HKCU\SOFTWARE, but you can remove HKLM\SOFTWARE\Microsoft (though I wouldn't recommend it). In addition, you may not delete a key that has subkeys; if you try, you get an error. It's okay to delete keys that have values; the values are deleted along with the key.

Under Windows 95 and 98, RegDeleteKey deletes keys that have subkeys. If your code depends on the standard Windows 2000 behavior of failing when a targeted key has subkeys, it works fine under Win9x, but it deletes the subkeys without warning you! If you want to use routines with more explicit semantics, consider using SHDeleteKey or SHDeleteEmptyKey.

8.1.8.2 Deleting a value

Deleting values is wonderfully straightforward (as long as you have KEY_WRITE or KEY_SET_VALUE access on the target key)! RegDeleteValue removes the specified value from the key you provide.

LONG RegDeleteValue(hKey, pszValueName);

HKEY
hKey

Key pointing to parent of target value.

LPCSTR
pszValueName

Name of the value to be deleted.

If pszValueName is NULL or contains an empty string, RegDeleteValue deletes the default value (you know, the one that appears as <Default> or No Name in Registry editors). Otherwise, pszValueName must contain the correct name of an existing value.

8.1.9 Using Registry Security Information

Under Windows 2000, every object in the entire system has security information attached to it. Registry keys are just objects, so they too can have ACLs that control who can read, write, or delete the key and its values. Ordinarily, you don't need to control these ACLs; when you do, RegEdt32 is probably the best tool for doing so. If you find it necessary or desirable to get a key's security data programmatically, though, you certainly can.

What's in a Security Descriptor?

The short answer is "it depends." The long answer is, well, longer. A security descriptor, or SD, is really an opaque block of data that Windows 2000 can parse into a set of access controls. Every object in the system has an SD associated with it. A single SD contains one or many sets of the following items:

  • The security ID (SID) of the object's owner

  • The SID of the object owner's primary group

  • A discretionary ACL (the object owner can freely modify)

  • A system ACL (modified only by entities with system privileges)

  • Qualifiers (specify whether the other items are self-contained or point to other SDs and ACLs)

You can't directly modify (or even decipher) an SD's contents; instead, you have to use the security API routines, notably:

  • InitializeSecurityDescriptor

  • GetSecurityDescriptorOwner

  • SetSecurityDescriptorDacl

  • SetSecurityDescriptorOwner

  • SetSecurityDescriptorSacl

With these, you can peel back the contents of a single SD and use the security data therein to verify or change who owns an object and who may access it with which permissions. Of course, your ability to do this depends entirely on whether your code has adequate permission itself when it runs!

ACLs come in two types: system ACLs, or SACLs, are owned by (and can only be changed by) the system; while discretionary ACLs (DACLs for short) are controlled by the owner of the object. As you might expect from security information, not just anyone can read either type of ACL. To read the DACL, the requesting process must have READ_CONTROL access on the key. To get this access, the requester must either own the key itself, or the DACL must grant READ_CONTROL to the account under which the requester is running.

System ACLs are trickier. They can be read only by applications that have been granted the ACCESS_SYSTEM_SECURITY permission. In turn, the only way to get ACCESS_SYSTEM_SECURITY is for the calling process to ask for the SE_SECURITY_NAME privilege, open the key with a REGSAM value of ACCESS_SYSTEM_SECURITY, then turn off SE_SECURITY_NAME again.

To actually retrieve a key's security data (assuming you've fulfilled the access control requirements), you can use RegGetKeySecurity . Besides passing in the name of the key whose information you want, you must also fill in the SecurityInformation field to indicate which data you want. If you have permission, pSecurityDescriptor is filled with the ACL or ownership data on return, and lpcbSecurityDescriptor contains the ACL size. ACLs vary in size, since they may contain one or many entries.

LONG RegGetKeySecurity (hKey, SecurityInformation, pSecurityDescriptor,
              lpcbSecurityDescriptor);

HKEY
hKey

Open Registry key whose security information you want.

SECURITY_INFORMATION
SecurityInformation

SECURITY_INFORMATION structure indicating what parts of the security descriptor you're asking for; may be any combination of items from Table 8.3.

PSECURITY_DESCRIPTOR
pSecurityDescriptor

Pointer to record that receives the security descriptor specified by SecurityInformation.

LPDWORD
lpcbSecurityDescriptor

Points to a DWORD ; on entry, it must hold the size of pSecurity-Descriptor, and, on return, it contains the size, in bytes, of the returned security descriptor.

If the buffer pointed to by pSecurityDescriptor is too small, RegGetKey-Security returns ERROR_INSUFFICIENT_BUFFER and the lpcbSecurity-Descriptor parameter contains the number of bytes required for the requested security descriptor. This makes it possible to efficiently allocate a buffer of the right size by calling it twice, like this:

long retVal = 0, aclSize = 0;
PSECURITY_DESCRIPTOR pWhat = NULL;

retVal = RegGetKeySecurity(theKey, DACL_SECURITY_INFORMATION, pWhat, &aclSize);
if (ERROR_INSUFFICIENT_BUFFER  != retVal)
   throw(retVal);
pWhat = malloc(aclSize);
retVal = RegGetKeySecurity(theKey, DACL_SECURITY_INFORMATION, pWhat, &aclSize);
if (ERROR_SUCCESS != retVal)
   throw(retVal);
8.1.9.1 Setting an item's security information

If you're not thoroughly familiar with how Windows 2000's security system works, stay away from RegSetKeySecurity until you have a good set of Registry backups. Setting the wrong permissions on a key is much easier to do programmatically than through any of the GUI editing tools, so please be very careful.

Once you've gotten a security descriptor and modified it,[6] you can write it back to the key that owns it with RegSetKeySecurity.

[6] I'm not about to talk about how you actually create or modify ACLs; that's a book all by itself.

LONG RegSetKeySecurity (hKey, SecurityInformation, pSecurityDescriptor);

HKEY
hKey

Open Registry key whose security descriptor you want to set.

SECURITY_INFORMATION
SecurityInformation

SECURITY_INFORMATION structure indicating what parts of the security descriptor you're changing.

PSECURITY_DESCRIPTOR
pSecurityDescriptor

Pointer to security descriptor containing ACL data you want to apply to hKey.

To ensure that your new security data gets written, you should call RegCloseKey on the modified key after successfully calling RegSetKeySecurity. This is true even if you've set security on one of the root keys; it won't actually be closed, but its cached security data is updated.

8.1.10 Connecting to Remote Computers

In Chapter 4, and Chapter 5, you learned how to use RegEdit and RegEdt32 to edit Registry data on remote computers. Adding this same functionality to your own programs is trivial: all you need do is call RegConnectRegistry and use the HKEY it returns in any other calls you make to Registry API functions. When you're finished with the remote key, you call RegCloseKey on it as though it were a local key. The API function declaration looks like the following.

LONG RegConnectRegistry(pszMachineName, hKey, phkResult);

LPSTR
pszMachineName

Name of the remote machine you want to connect to; must not include the leading backslashes.

HKEY
hKey

Root key you want to connect to: may be either HKLM or HKU.

PHKEY
phkResult

Pointer to returned key in remote Registry.

HasPackage (shown in Example 8.3) showcases RegConnectRegistry in action. You supply it with a machine name and a subkey; it checks the Registry on the specified machine to see whether it has a subkey of HKLM\SOFTWARE by the name you specify. The call to RegConnectRegistry and the corresponding RegCloseKey on the key it returns are the only changes needed to enable remote Registry connections in this small program.

Example 8.3. HasPackage
void main(int argc, char **argv) 
{
    char    pszName[MAX_PATH];
    HKEY    hkRemoteKey = NULL, hkResult = NULL;
    DWORD   dwIdx = 0;
    LONG    nResult = ERROR_SUCCESS;
    memset(pszName, 0x0, MAX_PATH);

    // preflight our arguments  
    if (argc < 3)
        DoUsage(argv[0]);
        
    nResult = RegConnectRegistry(argv[1], HKEY_LOCAL_MACHINE, &hkRemoteKey);
    if (ERROR_SUCCESS == nResult) 
    {
        sprintf(pszName, "SOFTWARE\\%s", argv[2]);
        nResult = RegOpenKeyEx(hkRemoteKey, pszName, 0L, KEY_READ, &hkResult);
        if (ERROR_SUCCESS == nResult)
        {
            fprintf(stdout, "%s has a key for %s.\n", argv[1], argv[2]);
        }
        else
        {
            fprintf(stderr, 
                "Error %d while opening SOFTWARE\\%s on remote machine %s\n", 
                argv[2], argv[1]);
        }
        nResult = RegCloseKey(hkResult);
        nResult = RegCloseKey(hkRemoteKey);
    }
    else
    {
        fprintf(stderr, "Error %d while opening remote registry on %s\n", 
            nResult, argv[1]);
    }
    
    fflush(stdout);
}

8.1.11 Moving Keys to and from Hives

In Chapter 3, Chapter 4, and Chapter 5, you learned how to use the Registry editor functions that allow keys and values to be saved into hive files and later restored. You can do the same thing with your own code by using the routines discussed in this section.

8.1.11.1 Saving keys

The first step in moving keys in and out of hives around is to create a hive; you can do this with RegSaveKey .

LONG RegSaveKey(hKey, pszFile, lpSecurityAttributes);

HKEY
hKey

Key to be saved; must be open. Everything below the specified key is saved.

LPCTSTR
pszFile

Full path of file to save in.

LPSECURITY_ATTRIBUTES
lpSecurityAttributes

Pointer to SECURITY_ATTRIBUTES structure describing desired security on the new file; pass in NULL to use the process's default security descriptor.

If the file you specify in pszFile already exists, RegSaveKey will fail with the ERROR_ALREADY_EXISTS error code. This prevents you from accidentally overwriting another hive file you previously saved. There's another subtlety involved with pszFile : if you don't specify a full path, the file is created in the process's current directory if the key is from the local Registry, or %systemroot%\system32 for a key on a remote machine.

The created file has the archive attribute set and whatever permissions are specified by lpSecurityAttributes. Instead of creating a brand-new security descriptor, you may pass NULL to have whatever security context applies to the process applied to the file.

8.1.11.2 Loading keys

Once you've saved keys into a hive file, the next thing you're likely to want to do is load them. You can do so in two distinct ways: you can load a hive as a new key, or you can replace the contents of an existing key with the hive's contents. Either approach requires the process that loads the keys to have the SE_RESTORE_NAME privilege.

RegLoadKey supports the former: you tell it what file to load and what to name the new subkey, and it creates the specified subkey and loads the file into it. RegLoadKey will fail if the file doesn't exist or if the named subkey does exist.

LONG RegLoadKey(hKey, pszSubKey, pszFile);

HKEY
hKey

Open key under which the new subkey is created; may be HKLM or HKU on a local machine, or a handle obtained by opening HKLM or HKU with RegConnectRegistry.

LPCTSTR
pszSubKey

Name of the subkey to create beneath hKey ; the subkey must not currently exist.

LPCTSTR
pszFile

Full pathname to the hive file you want to load into the new key. This file must have been created with RegSaveKey or RegEdt32's Registryfigs/U2192.gifSave Key command.

Calling RegCloseKey on a key loaded with RegLoadKey doesn't unload it; instead, you must call RegUnloadKey as described later.

If you want to overwrite an existing key that's part of one of the standard hives, you can instead call RegRestoreKey. Like RegLoadKey, it takes a parent key and the name of a file to load. However, in this case the parent key's subkeys are replaced by the contents of the file. For example, if you open HKLM\SOFTWARE\Microsoft\Windows and pass that to RegRestoreKey, the key with that name persists, but all subkeys and values beneath it are deleted. After RegRestoreKey returns, the victim key contains whatever values and subkeys were in the loaded file.

LONG RegRestoreKey(hKey, pszFile, dwFlags);

HKEY
hKey

Key whose values and subkeys you want to replace.

LPCTSTR
pszFile

File (saved with RegSaveKey or RegEdt32) with the new contents you want loaded into hKey.

DWORD
dwFlags

If you pass in for this parameter, the entire hKey is replaced; if you pass in REG_WHOLE_HIVE_VOLATILE, hKey is replaced, but the changes are not written to the Registry.

8.1.11.3 Replacing a loaded key

Once you've loaded a hive file with RegLoadKey, you can replace the loaded key with another hive file. This is a good way to dynamically swap between several hives' worth of data. However, changes don't take effect until the machine is restarted.

LONG RegReplaceKey(hKey, pszSubKey, pszNewFile, pszOldFile);

HKEY
hKey

Open key that contains subkey you want to replace.

LPCTSTR
pszSubKey

Contains the name of the subkey whose values and subkeys are replaced by the newly loaded hive.

LPCTSTR
pszNewFile

Contains full path to the hive file you want loaded; the file must be generated by RegSaveKey or RegEdt32.

LPCTSTR
pszOldFile

Contains the name of a file to which Windows 2000 save a backup copy of the previously loaded hive file.

8.1.11.4 Unloading a key

RegLoadKey allows you to load a stored hive file as a new hive under HKLM or HKU. Once you've loaded a hive, it makes sense to have a way to unload it when you're done, and RegUnloadKey provides that functionality.

LONG RegUnLoadKey(hKey, pszSubKey);

HKEY
hKey

Handle to an open key.

LPCTSTR
pszSubKey

Full path to the subkey you want to unload.

You can unload only keys you load yourself, which prevents unloading (accidentally or on purpose) an important key such as HKLM\SOFTWARE. The process that calls RegUnloadKey must have the special SE_RESTORE_NAME privilege.

8.1.12 Getting Notification When Something Changes

If you want to write a program that does something when a particular Registry key or value changes, you can do so by sitting in an infinite loop and periodically checking the item of interest to see whether it changes. This is terribly inefficient, though, so it's good that there's another way to do it. The RegNotify-ChangeKeyValue routine allows you to register your interest in a particular key or value, then go do something else. Your code gets a notification you when the Registry key (or its attributes) changes; it doesn't, however, tell you if the key is deleted.

LONG RegNotifyChangeKeyValue (hKey, bWatchSubtree, dwNotifyFilter,hEvent
     fAsynchronous);

HKEY
hKey

Key you want to monitor for changes; may be a root key or any subkey.

BOOL
bWatchSubtree

When true, indicates that you want to watch all subkeys and values of hKey ; when false, indicates you want to watch hKey only.

DWORD
dwNotifyFilter

Flag specifying what events you're interested in; may be any combination of:

  • REG_NOTIFY_CHANGE_NAME for renaming, addition, or deletion of a subkey

  • REG_NOTIFY_CHANGE_ATTRIBUTES for changes to any key attributes

  • REG_NOTIFY_CHANGE_LAST_SET for changes to a value of a subkey

  • REG_NOTIFY_CHANGE_SECURITY for changes to security

HANDLE
hEvent

Event to post when a change is detected; ignored if fAsynchronous is false.

BOOL
fAsynchronous

When true, routine returns immediately and posts an event when a change takes place; when false, routine blocks until a change occurs .

8.1.13 Flushing Registry Changes

The Registry uses a "lazy flusher" to propagate changes from memory to disk. The overall goal is to minimize the number of disk I/O operations, since they tend to be relatively time-consuming. The lazy flusher achieves this goal by not immediately writing every change out to disk as it occurs. Instead, it aggregates changes and writes them when the system is mostly idle.

When you call RegCloseKey, whatever changes you've made are thus not immediately copied to disk. There can be an interlude of indeterminate length (Microsoft says "as long as several seconds" without elaborating) before your data actually hits the disk. For most applications, this is perfectly acceptable. However, if for some reason you want to make sure your changes get written to disk, you can use the RegFlushKey routine to immediately force a Registry update:

LONG RegFlushKey (hKey);

Calling this routine forces the lazy flusher to buckle down and flush the specified key's data to its hive file; it may also cause other keys to be written as well. Flushing the cached data also updates the .LOG files that act as a backup copy of the Registry. The Win32 SDK warns that this function is expensive, so you shouldn't call it often. RegFlushKey returns ERROR_SUCCESS when all goes well or a standard Win32 error code for failed flush attempts.

    Team LiB   Previous Section   Next Section