Corrections to Microsoft documentation about the Registry Policy (registry.pol) File Format
This report corrects and clarifies Microsoft’s documentation of its Registry Policy File format as documented on these web pages:
- Registry Policy File Format | Microsoft Learn
https://learn.microsoft.com/en-us/previous-versions/windows/desktop/policy/registry-policy-file-format - [MS-GPREG]: New or Changed GPO List Processing | Microsoft Learn
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpreg/57226664-ce00-4487-994e-a6b3820f3e49
Introduction and Clarifications
A registry policy file is typically named Registry.pol, and can be thought of as a series of registry commands to be executed by the Group Policy engine on a Windows endpoint. Microsoft’s documentation says that a registry policy file is a “text file,” but it is a combination of binary content interspersed with Unicode (UTF-16LE) text.
The first eight bytes of the file are header content (hex 50 52 65 67 01 00 00 00). This header is followed by a series of zero or more registry commands in this form:
[key;value;type;size;data]That is, five fields (key, value, type, size, and data) delimited by semicolons and within a pair of square brackets.
The square brackets and semicolons are rendered in that sequence as UTF-16LE characters:
[ 5b 00
; 3b 00
] 53 00The key field is a null-terminated UTF-16LE string representing the full name of a registry key, not including the root. The root key (HKLM or HKCU) is not specified in the Registry.pol file but is implied by the name of the file’s parent directory (“Machine” or “User”). Note that there must be a 16-bit null character after the key name and before the semicolon.
The value field is a null-terminated UTF-16LE string representing the name of a registry value to configure. As the documentation says, value can be one of a set of special names to perform actions other than setting a registry value. As with key, the value string must be followed by a 16-bit null character after the value name and before the semicolon.
The type field is a 32-bit (4 byte) little-endian value representing the registry value type corresponding to the winnt.h definitions. For example, REG_SZ is 1 and REG_DWORD is 4. The little-endian representation of REG_SZ is 01 00 00 00. The four-byte type value is followed by the UTF-16LE (two-byte) semicolon character.
The size field is a 32-bit (4 byte) little-endian value representing the length in bytes of the data item. If data represents a null-terminated string, the size value must include the byte count of the null terminator.
When the value field contains the name of a registry value to configure (and not one of the defined special names), the data field contains the binary representation of the bytes to write to the registry value. For example, if the type is REG_SZ, data should be a null-terminated UTF-16LE string, and if the type is REG_DWORD, data should be a 32-bit (4 byte) little-endian value.
The MS-GPREG documentation accurately describes what the type and data fields should contain when the value field is **DeleteValues, **Del.valuename, or **DelVals. Note that while the DataValue descriptions in the MS-GPREG documentation show quotes around example content for those cases, the content of the data field should not include those quote characters.
Corrections and clarifications are below for **DeleteKeys, **SecureKey, **soft., and the undocumented **Comment: instruction.
**DeleteKeys
Microsoft’s documentation for **DeleteKeys incorrectly indicates that data should contain the names of subkeys to delete under the registry key specified by the key field. In fact, the Group Policy engine ignores the name specified by the key field — it can even be an empty string. Instead, the data field must contain a null-terminated, semicolon-delimited list of full key names (not including the root key).
When the **DeleteKeys policy is applied, the named keys are deleted if they exist, along with any subkeys they may have.
For example, to delete subkeys SubKey1 and SubKey2 under (root)\Software\Policies\MyProduct, the command parts should be:
key: (anything)
value: **DeleteKeys
type: REG_SZ (little-endian hex: 01 00 00 00)
size: 144 (little-endian hex: 90 00 00 00)
data: Software\Policies\MyProduct\SubKey1;Software\Policies\MyProduct\SubKey2
Notes:
- key can be any UTF-16LE null-terminated string, including an empty string.
- With
**DeleteKeys, type is actually ignored and can be any four-byte value. - The 144 size is the byte count of the null-terminated, UTF-16LE data string.
**Comment:
Microsoft does not currently document the **Comment: special name publicly, but Windows does use it, for example in ntuser.pol registry policy files that the Group Policy engine creates under ProgramData on domain-joined machines in which the comments indicate from which domain GPOs the subsequent registry settings derived.
As the name implies, the Group Policy engine does not process entries with a value field beginning with “**Comment:”.
The comment text is represented as a null-terminated UTF-16LE string immediately following the colon in the value field. For example:
**Comment:GPO Name: MSOffice_LockdownIn observed instances, the type field is REG_SZ (1), the size field is 0, and the data is empty (zero-length).
**SecureKey
As far as I can tell, the **SecureKey instruction is entirely inert. Setting it to DWORD 0 or 1 (as the documentation advises) does nothing. Nor does setting it to DWORD 0xFFFF, nor setting it to a REG_SZ with an SDDL string. If this instruction ever had an effect, it must have been on platforms that preceded Windows 7 SP1 (the oldest system I tested on).
**soft.
The **soft. instruction works mostly as documented, except that it will not create the named key if that key does not exist. A more accurate description is, “Create the value <valuename> if it does not exist and the specified key does exist.”
