Geoff Chappell - Software Analyst
CURRENT WORK ITEM - PREVIEW ONLY
The ControlPanel class is one of many that SHELL32 exposes through its exported DllGetClass function. Instances can therefore be created using such well-documented COM functions as CoCreateInstance, by anyone who knows the CLSID:
CLSID_ControlPanel | {21EC2020-3AEA-1069-A2DD-08002B30309D} |
The class implements the following interfaces (in the SHELL32 version 6.00 from Windows Vista):
Note that several of these interfaces are acquired by aggregating a RegFolder object. The IControlPanelEnumerator, IRegItemCustomAttributes and IRegItemCustomEnumerator interfaces are special in that the ControlPanel class provides the only known implementations.
As a registry folder, the ControlPanel object has a namespace that is built from a standard pattern defined through registry keys, after allowing for items that the Control Panel finds for itself.
The native Control Panel items are supported through specially written DLLs that are traditionally (but not necessarily) named with CPL as the file extension. All such CPL modules export a function, necessarily named CPlApplet, which SHELL32 calls in various circumstances as if to send Control Panel messages. These messages provide that a CPL module may support multiple Control Panel items, typically with different icons. A well-known example is that the Keyboard and Mouse items are both supported from MAIN.CPL.
Candidate CPL modules are discovered from several sources:
Each candidate module may however be declined. There are several possible reasons:
This list of accepted CPL modules is built when the enumerator is created for the ControlPanel object’s EnumObjects method. (This method is not called when enumerating the Control Panel in Category View, which subject is left for another time.) Building the list does not require that any CPL module be loaded as an executable. That does not happen until the list is retrieved in successive calls to the enumerator’s Next method, and even then is typically avoided by relying on cached information.
CPL modules are specified first from the [MMCPL] section of the CONTROL.INI file. This must once upon a time have been the only way that a CPL module could be usable without being installed in the Windows system directory. In Microsoft’s best traditions of attention to backward compatibility, this old technique continues to be supported. Note however that the CONTROL.INI file has long been subject to an early form of virtualisation, such that the GetPrivateProfileString function actually reads a simulated [MMCPL] section from the registry. This redirection is set up through well-documented mechanisms whose standard configuration is here taken as granted:
Key: | HKEY_CURRENT_USER\Control Panel\MMCPL |
Value: | anything other than: the case-insensitive NumApps; the case-sensitive H, W, X or Y |
Type: | REG_SZ |
For each value other than the few exceptions, the data for the value is the pathname of a CPL module. The name of the value has no known significance.
Every *.CPL file in the Windows System directory is automatically a candidate CPL module. In ordinary practice, this is still the main way that CPL modules are discovered, but Microsoft discourages it in the hope that the System directory might be left alone for files that actually do come with the system.
The modern way to specify CPL modules is to list them in the registry. Each of the following keys is enumerated in turn:
In each key, the data for each value is interpreted as a pathname to a CPL module. The name of the value has no known significance. The type of data is immaterial, though clearly string data is intended. Note in particular that the interpretation provides for expansion of environment variables even if the data type is not REG_EXPAND_SZ.
When running in a 32-bit process under 64-bit Windows, the System directory is subject to WOW64 File System Redirection. Indeed, 64-bit Windows has two System directories. The directory that actually is named SYSTEM32 is the System directory for 64-bit Windows executables. The other, named SysWOW64, is the System directory for 32-bit Windows executables. A 32-bit process running on 64-bit Windows is told that the System directory is named SYSTEM32 and its access to any file that it thinks is in the SYSTEM32 directory is actually redirected to the file that has the same name in the SysWOW64 directory.
This redirection affects the Control Panel also, not as seen familiarly through the (64-bit) Windows Explorer but when browsed from a 32-bit process (including when the 32-bit EXPLORER is started to View 32-bit Control Panel Items). As each CPL module is learnt from the System directory or from either of the CPLs registry keys, it is rejected as a candidate if a file of the same name exists in the true System directory. Note that specification through the [MMCPL] section is not subject to this WOW64 issue.
A candidate CPL module is rejected if its filename appears as a value in either of the following registry keys:
Data for the value is immaterial. The first of the keys is the registry mapping of the [don't load] section of the CONTROL.INI file.
A candidate CPL module is rejected if a file with the same filename is known not to export a function named CPlApplet. This information is remembered from earlier attempts at initialisation (for the same process).
Note throughout that CPL modules are identified by their filename. There is no allowance for different modules in different directories to have the same filename. A candidate CPL module is rejected if a file with the same filename is already accepted.
Except in any sort of Safe Mode or if the license value shell32-EnableProxyFeature is not enabled, the discovery of CPL modules is completed by loading cached details. This does not cause any new CPL modules to be found as candidates. It just identifies some Control Panel items as being known already, so that the corresponding CPL modules do not have to be loaded when enumerated. That caching is skipped in Safe Mode is unsurprising. The significance of the license value is not known. In the SHELL32 from Windows Vista, the code that interprets this cache is disguised using a technique with which Microsoft protects some license-validation code. Specifically, it is sprinkled with INT 3 breakpoints which execute code that is ordinarily encrypted.
The cached details include such things as are needed to display the item when the Control Panel is browsed. Some, such as the friendly name and the description (which is nowadays seen only as an infotip), are language-specific, and the cache is therefore only valid if the right language is in use. This is guarded through the following registry value:
Key: | HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Controls
Folder HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Controls Folder (Wow64) |
Value: | Presentation LCID |
Type: | REG_DWORD |
The first key is used ordinarily. The second applies to a 32-bit process on 64-bit Windows. Data of any type is accepted, up to 4 bytes. When SHELL32 sets the value, it sets it as REG_DWORD. The dword of data is a language ID. If it does match the default UI language, then the cache is ignored.
The cache itself is the data from another value in the same key:
Key: | HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Controls
Folder HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Controls Folder (Wow64) |
Value: | Presentation Cache |
Type: | REG_BINARY |
Again, any type of data is accepted, but when SHELL32 sets the value, it sets it as REG_BINARY. The data is a sequence of variably-sized cache entries, each for a different Control Panel item:
Offset | Size or Type | Description |
---|---|---|
0x00 | dword | size of structure, in bytes |
0x04 | dword | bit flags |
0x08 | FILETIME | creation time of CPL module as file |
0x10 | dword | high dword of CPL module’s file size in bytes |
0x14 | dword | low dword of CPL module’s file size in bytes |
0x18 | dword | index of item’s icon as resource in CPL module |
0x1C | dword | offset in Unicode characters from start of pathname to start of item’s friendly name |
0x20 | dword | offset in Unicode characters from start of pathname to start of item’s description |
0x24 | varies | pathname of CPL module, as null-terminated Unicode string (allowed MAX_PATH characters) |
varies | varies | item’s friendly name, as null-terminated Unicode string (allowed MAX_PATH characters) |
varies | varies | item’s description, as null-terminated Unicode string (allowed 0x0200 characters) |
Two bit flags are supported at offset 0x08. Persistence of the 0x01 bit has no known consequence. The bit is clear in a cache entry when first recorded but is set in a cache entry that has been read from the value and written back. The 0x02 bit indicates that the entry has the format that dates from SHELL32 version 6.00. However, the bit wasn’t added as a marker of the new format until the version 6.00 from Windows Server 2003 and Windows XP SP2. The cache itself dates from version 4.00.
The only change in the format is in how much space is allowed for the strings (which are ANSI in the Windows builds and Unicode in the NT builds). The original format provides MAX_PATH characters for the pathname, but only 32 and 64 characters for the friendly name and description. These last two conform to the documented allowances in the documented NEWCPLINFO structure but are much smaller than the MAX_PATH and 0x0200 characters that all SHELL32 versions allow when loading these strings from resources IDs (as when learnt from an old CPLINFO structure). When such strings are copied to cache entries, SHELL32 risks a buffer overflow, if only in theory. In practice, the one buffer is used for the total of all cache entries and any excess in one entry is therefore compensated by slack from other entries, and there is yet more slack because few CPL modules need anywhere near as many as MAX_PATH characters for their pathnames.
The cache is ignored if it does not have the new format. The test is not only that the first cache entry has the 0x02 bit set in its flags but also that the total size of data for the value must be at least 0x0834 bytes. (This is, of course, the maximum size for any one entry, but taking it as the minimum for the total size means that a cache of only a few small entries is ignored as old.) The cache is also ignored if it is not plausibly a sequence of the expected structures, i.e., if the size at the beginning of any one cache entry is too small or too large, or if the total of sizes is too large for the amount of data.
A valid cache comes into its own when Control Panel items are enumerated. Indeed, items are enumerated in their cached order. For each cached item, if an accepted CPL module has the same filename as the cached item and has matching file details, then the item is knowable in terms of its icon, friendly name and description without having to load the module.
Entries are added to the cache (in memory) when uncached items are discovered during enumeration. The whole of a dirty cache may be flushed to the registry when an enumeration ends with no more items to report and certainly written when the enumerator is destroyed.
Ordinarily, the first item in the Control Panel namespace is a Printers object as a required item. Other items in the namespace are loaded from registry keys in the following order:
in which session is a decimal representation of the current session ID.
When the current process is a 32-bit process on 64-bit Windows, there is no required item and the registry keys for the namespace have ControlPanel changed to ControlPanelWOW64.
As with all registry folders, an item can be specified in a NameSpace key in either of two ways. Both require a CLSID in the standard string representation with curly braces. It can be specified either as the name of a subkey or as the default value of a subkey (in which case, the name of the subkey has no known signficance).
The display names of Control Panel items are parsed first according to the syntax of registry folders. An item that is defined by a subkey {clsid} in any of the namespace keys given above has the display name in the usual form with the double colon prefix and can in practice be opened by the command
explorer ::{21ec2020-3aea-1069-a2dd-08002b30309d}\::{clsid}
The Control Panel itself supports two other syntaxes, which are presumably intended for items that are not picked up from the registry folder’s namespace mechanism. A Control Panel item may be represented by a canonical name or by an applet ID.
To have a canonical name, the item must have at least the following registry value:
Key: | HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace\canonical |
Value: | Module |
Though the type of data is not checked, string data is clearly intended. The string data, with environment variables expanded, is a pathname to the item as a CPL module.
No particular syntax is expected of the canonical name (though it would be redundant to use a CLSID with curly braces). With the Control Panel’s namespace, an item with a canonical name is represented by the canonical name. For instance, since Windows Vista is installed with the Internet Options item defined under the canonical name Internet_Options, it is in practice possible to open the Internet Options dialog box by running
explorer ::{21ec2020-3aea-1069-a2dd-08002b30309d}\Internet_Options
Definition through a canonical name provides for some other specifications through other values in the same key:
Note that one module can provide multiple Control Panel items, with a different canonical name for each.
For the purpose just of parsing a display name into a PIDL, use of the canonical name is very efficient, since everything that is needed for the item ID list at the Control Panel’s level is already known from the values in the one registry key. It is strange, then, that Microsoft seems not to document thse registry values.
Another set of names for Control Panel items is defined elsewhere in the registry:
Key: | HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Control Panel\Extended Properties\System.Software.AppId |
Value: | module,@i-iconindex
module |
Type: | REG_SZ |
Data: | {appid} |
The item is represented by the applet ID, with its curly braces. For instance, Windows Vista is installed with an applet ID for the Internet Options item, which may in practice be opened by running
explorer ::{21ec2020-3aea-1069-a2dd-08002b30309d}\{a3dd4f92-658a-410f-84fd-6fbbbef2fffe}
Note the (necessary) absence of the double colon in the last path element.
Again, one module can provide multiple Control Panel items, with a different applet ID for each. To do so, each item must have its own registry value. The module must therefore specify a different icon for each item and must know the syntax (which Microsoft seems not to document).
For the purpose just of parsing a display name into a PIDL, use of an applet ID is inefficient. To determine its friendly name and description (and typically also the icon index), the module must be found in a list of loaded modules, or it must be loaded and interrograted (via its CPlApplet function).
The ControlPanel class is implemented in SHELL32 version 4.00 and higher.