/** This library provides Win32 Registry facilities. Copyright: Copyright 2003-2004 by Matthew Wilson and Synesis Software Written by Matthew Wilson License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). Author: Matthew Wilson, Kenji Hara History: Created 15th March 2003, Updated 25th April 2004, Source: $(PHOBOSSRC std/windows/registry.d) */ /* ///////////////////////////////////////////////////////////////////////////// * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, in both source and binary form, subject to the following * restrictions: * * - The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * - Altered source versions must be plainly marked as such, and must not * be misrepresented as being the original software. * - This notice may not be removed or altered from any source * distribution. * * ////////////////////////////////////////////////////////////////////////// */ module std.windows.registry; version (Windows): import core.sys.windows.winbase, core.sys.windows.windef, core.sys.windows.winreg; import std.array; import std.conv; import std.exception; import std.internal.cstring; import std.internal.windows.advapi32; import std.system : Endian, endian; import std.windows.syserror; //debug = winreg; debug(winreg) import std.stdio; private { import core.sys.windows.winbase : lstrlenW; void enforceSucc(LONG res, lazy string message, string fn = __FILE__, size_t ln = __LINE__) { if (res != ERROR_SUCCESS) throw new RegistryException(message, res, fn, ln); } } /* ************* Exceptions *************** */ // Do not use. Left for compatibility. class Win32Exception : WindowsException { @safe this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) { super(0, message, fn, ln); } @safe this(string message, int errnum, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) { super(errnum, message, fn, ln); } @property int error() { return super.code; } } version (StdUnittest) import std.string : startsWith, endsWith; @safe unittest { // Test that we can throw and catch one by its own type string message = "Test W1"; auto e = collectException!Win32Exception( enforce(false, new Win32Exception(message))); assert(e.msg.startsWith(message)); } @system unittest { // ditto string message = "Test W2"; int code = 5; auto e = collectException!Win32Exception( enforce(false, new Win32Exception(message, code))); assert(e.error == code); assert(e.msg.startsWith(message)); } /** Exception class thrown by the std.windows.registry classes. */ class RegistryException : Win32Exception { public: /** Creates an instance of the exception. Params: message = The message associated with the exception. */ @safe this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) { super(message, fn, ln, next); } /** Creates an instance of the exception, with the given. Params: message = The message associated with the exception. error = The Win32 error number associated with the exception. */ @safe this(string message, int error, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) { super(message, error, fn, ln, next); } } @system unittest { // (i) Test that we can throw and catch one by its own type string message = "Test 1"; int code = 3; auto e = collectException!RegistryException( enforce(false, new RegistryException(message, code))); assert(e.error == code); assert(e.msg.startsWith(message)); } @safe unittest { // ditto string message = "Test 2"; auto e = collectException!RegistryException( enforce(false, new RegistryException(message))); assert(e.msg.startsWith(message)); } /* ************* public enumerations *************** */ /** Enumeration of the recognised registry access modes. */ enum REGSAM { KEY_QUERY_VALUE = 0x0001, /// Permission to query subkey data KEY_SET_VALUE = 0x0002, /// Permission to set subkey data KEY_CREATE_SUB_KEY = 0x0004, /// Permission to create subkeys KEY_ENUMERATE_SUB_KEYS = 0x0008, /// Permission to enumerate subkeys KEY_NOTIFY = 0x0010, /// Permission for change notification KEY_CREATE_LINK = 0x0020, /// Permission to create a symbolic link KEY_WOW64_32KEY = 0x0200, /// Enables a 64- or 32-bit application to open a 32-bit key KEY_WOW64_64KEY = 0x0100, /// Enables a 64- or 32-bit application to open a 64-bit key KEY_WOW64_RES = 0x0300, /// KEY_READ = (STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & ~(SYNCHRONIZE), /// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, /// KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY access rights KEY_WRITE = (STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) & ~(SYNCHRONIZE), /// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, /// and KEY_CREATE_SUB_KEY access rights KEY_EXECUTE = KEY_READ & ~(SYNCHRONIZE), /// Permission for read access KEY_ALL_ACCESS = (STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & ~(SYNCHRONIZE), /// Combines the KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, /// KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, and /// KEY_SET_VALUE access rights, plus all the standard /// access rights except SYNCHRONIZE } /** Enumeration of the recognised registry value types. */ enum REG_VALUE_TYPE : DWORD { REG_UNKNOWN = -1, /// REG_NONE = 0, /// The null value type. (In practise this is treated as a zero-length binary array by the Win32 registry) REG_SZ = 1, /// A zero-terminated string REG_EXPAND_SZ = 2, /// A zero-terminated string containing expandable environment variable references REG_BINARY = 3, /// A binary blob REG_DWORD = 4, /// A 32-bit unsigned integer REG_DWORD_LITTLE_ENDIAN = 4, /// A 32-bit unsigned integer, stored in little-endian byte order REG_DWORD_BIG_ENDIAN = 5, /// A 32-bit unsigned integer, stored in big-endian byte order REG_LINK = 6, /// A registry link REG_MULTI_SZ = 7, /// A set of zero-terminated strings REG_RESOURCE_LIST = 8, /// A hardware resource list REG_FULL_RESOURCE_DESCRIPTOR = 9, /// A hardware resource descriptor REG_RESOURCE_REQUIREMENTS_LIST = 10, /// A hardware resource requirements list REG_QWORD = 11, /// A 64-bit unsigned integer REG_QWORD_LITTLE_ENDIAN = 11, /// A 64-bit unsigned integer, stored in little-endian byte order } /* ************* private *************** */ import core.sys.windows.winnt : DELETE , READ_CONTROL , WRITE_DAC , WRITE_OWNER , SYNCHRONIZE , STANDARD_RIGHTS_REQUIRED, STANDARD_RIGHTS_READ , STANDARD_RIGHTS_WRITE , STANDARD_RIGHTS_EXECUTE , STANDARD_RIGHTS_ALL , SPECIFIC_RIGHTS_ALL ; import core.sys.windows.winreg : REG_CREATED_NEW_KEY , REG_OPENED_EXISTING_KEY ; // Returns samDesired but without WoW64 flags if not in WoW64 mode // for compatibility with Windows 2000 private REGSAM compatibleRegsam(in REGSAM samDesired) { return isWow64 ? samDesired : cast(REGSAM)(samDesired & ~REGSAM.KEY_WOW64_RES); } ///Returns true, if we are in WoW64 mode and have WoW64 flags private bool haveWoW64Job(in REGSAM samDesired) { return isWow64 && (samDesired & REGSAM.KEY_WOW64_RES); } private REG_VALUE_TYPE _RVT_from_Endian(Endian endian) { final switch (endian) { case Endian.bigEndian: return REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN; case Endian.littleEndian: return REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN; } } private LONG regCloseKey(in HKEY hkey) in { assert(hkey !is null); } do { /* No need to attempt to close any of the standard hive keys. * Although it's documented that calling RegCloseKey() on any of * these hive keys is ignored, we'd rather not trust the Win32 * API. */ if (cast(uint) hkey & 0x80000000) { switch (cast(uint) hkey) { case HKEY_CLASSES_ROOT: case HKEY_CURRENT_USER: case HKEY_LOCAL_MACHINE: case HKEY_USERS: case HKEY_PERFORMANCE_DATA: case HKEY_PERFORMANCE_TEXT: case HKEY_PERFORMANCE_NLSTEXT: case HKEY_CURRENT_CONFIG: case HKEY_DYN_DATA: return ERROR_SUCCESS; default: /* Do nothing */ break; } } return RegCloseKey(hkey); } private void regFlushKey(in HKEY hkey) in { assert(hkey !is null); } do { immutable res = RegFlushKey(hkey); enforceSucc(res, "Key cannot be flushed"); } private HKEY regCreateKey(in HKEY hkey, in string subKey, in DWORD dwOptions, in REGSAM samDesired, in LPSECURITY_ATTRIBUTES lpsa, out DWORD disposition) in { assert(hkey !is null); assert(subKey !is null); } do { HKEY hkeyResult; enforceSucc(RegCreateKeyExW( hkey, subKey.tempCStringW(), 0, null, dwOptions, compatibleRegsam(samDesired), cast(LPSECURITY_ATTRIBUTES) lpsa, &hkeyResult, &disposition), "Failed to create requested key: \"" ~ subKey ~ "\""); return hkeyResult; } private void regDeleteKey(in HKEY hkey, in string subKey, in REGSAM samDesired) in { assert(hkey !is null); assert(subKey !is null); } do { LONG res; if (haveWoW64Job(samDesired)) { loadAdvapi32(); res = pRegDeleteKeyExW(hkey, subKey.tempCStringW(), samDesired, 0); } else { res = RegDeleteKeyW(hkey, subKey.tempCStringW()); } enforceSucc(res, "Key cannot be deleted: \"" ~ subKey ~ "\""); } private void regDeleteValue(in HKEY hkey, in string valueName) in { assert(hkey !is null); assert(valueName !is null); } do { enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()), "Value cannot be deleted: \"" ~ valueName ~ "\""); } private HKEY regDup(HKEY hkey) in { assert(hkey !is null); } do { /* Can't duplicate standard keys, but don't need to, so can just return */ if (cast(uint) hkey & 0x80000000) { switch (cast(uint) hkey) { case HKEY_CLASSES_ROOT: case HKEY_CURRENT_USER: case HKEY_LOCAL_MACHINE: case HKEY_USERS: case HKEY_PERFORMANCE_DATA: case HKEY_PERFORMANCE_TEXT: case HKEY_PERFORMANCE_NLSTEXT: case HKEY_CURRENT_CONFIG: case HKEY_DYN_DATA: return hkey; default: /* Do nothing */ break; } } HKEY hkeyDup; immutable res = RegOpenKeyW(hkey, null, &hkeyDup); debug(winreg) { if (res != ERROR_SUCCESS) { writefln("regDup() failed: 0x%08x 0x%08x %d", hkey, hkeyDup, res); } assert(res == ERROR_SUCCESS); } return (res == ERROR_SUCCESS) ? hkeyDup : null; } private LONG regEnumKeyName(in HKEY hkey, in DWORD index, ref wchar[] name, out DWORD cchName) in { assert(hkey !is null); assert(name !is null); assert(name.length > 0); } out(res) { assert(res != ERROR_MORE_DATA); } do { // The Registry API lies about the lengths of a very few sub-key lengths // so we have to test to see if it whinges about more data, and provide // more if it does. for (;;) { cchName = to!DWORD(name.length); immutable res = RegEnumKeyExW(hkey, index, name.ptr, &cchName, null, null, null, null); if (res != ERROR_MORE_DATA) return res; // Now need to increase the size of the buffer and try again name.length *= 2; } assert(0); } private LONG regEnumValueName(in HKEY hkey, in DWORD dwIndex, ref wchar[] name, out DWORD cchName) in { assert(hkey !is null); } do { for (;;) { cchName = to!DWORD(name.length); immutable res = RegEnumValueW(hkey, dwIndex, name.ptr, &cchName, null, null, null, null); if (res != ERROR_MORE_DATA) return res; name.length *= 2; } assert(0); } private LONG regGetNumSubKeys(in HKEY hkey, out DWORD cSubKeys, out DWORD cchSubKeyMaxLen) in { assert(hkey !is null); } do { return RegQueryInfoKeyW(hkey, null, null, null, &cSubKeys, &cchSubKeyMaxLen, null, null, null, null, null, null); } private LONG regGetNumValues(in HKEY hkey, out DWORD cValues, out DWORD cchValueMaxLen) in { assert(hkey !is null); } do { return RegQueryInfoKeyW(hkey, null, null, null, null, null, null, &cValues, &cchValueMaxLen, null, null, null); } private REG_VALUE_TYPE regGetValueType(in HKEY hkey, in string name) in { assert(hkey !is null); } do { REG_VALUE_TYPE type; enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, null, null), "Value cannot be opened: \"" ~ name ~ "\""); return type; } private HKEY regOpenKey(in HKEY hkey, in string subKey, in REGSAM samDesired) in { assert(hkey !is null); assert(subKey !is null); } do { HKEY hkeyResult; enforceSucc(RegOpenKeyExW(hkey, subKey.tempCStringW(), 0, compatibleRegsam(samDesired), &hkeyResult), "Failed to open requested key: \"" ~ subKey ~ "\""); return hkeyResult; } private void regQueryValue(in HKEY hkey, string name, out string value, REG_VALUE_TYPE reqType) in { assert(hkey !is null); } do { import core.bitop : bswap; REG_VALUE_TYPE type; // See https://issues.dlang.org/show_bug.cgi?id=961 on this union U { uint dw; ulong qw; } U u; void* data = &u.qw; DWORD cbData = u.qw.sizeof; auto keynameTmp = name.tempCStringW(); LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); if (res == ERROR_MORE_DATA) { data = (new ubyte[cbData]).ptr; res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); } enforceSucc(res, "Cannot read the requested value"); enforce(type == reqType, new RegistryException("Value type has been changed since the value was acquired")); switch (type) { case REG_VALUE_TYPE.REG_SZ: case REG_VALUE_TYPE.REG_EXPAND_SZ: auto wstr = (cast(immutable(wchar)*)data)[0 .. cbData / wchar.sizeof]; assert(wstr.length > 0 && wstr[$-1] == '\0'); if (wstr.length && wstr[$-1] == '\0') wstr.length = wstr.length - 1; assert(wstr.length == 0 || wstr[$-1] != '\0'); value = wstr.to!string; break; case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: version (LittleEndian) value = to!string(u.dw); else value = to!string(bswap(u.dw)); break; case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: version (LittleEndian) value = to!string(bswap(u.dw)); else value = to!string(u.dw); break; case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: value = to!string(u.qw); break; case REG_VALUE_TYPE.REG_BINARY: case REG_VALUE_TYPE.REG_MULTI_SZ: default: throw new RegistryException("Cannot read the given value as a string"); } } private void regQueryValue(in HKEY hkey, in string name, out string[] value, REG_VALUE_TYPE reqType) in { assert(hkey !is null); } do { REG_VALUE_TYPE type; auto keynameTmp = name.tempCStringW(); wchar[] data = new wchar[256]; DWORD cbData = to!DWORD(data.length * wchar.sizeof); LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); if (res == ERROR_MORE_DATA) { data.length = cbData / wchar.sizeof; res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); } else if (res == ERROR_SUCCESS) { data.length = cbData / wchar.sizeof; } enforceSucc(res, "Cannot read the requested value"); enforce(type == REG_VALUE_TYPE.REG_MULTI_SZ, new RegistryException("Cannot read the given value as a string")); enforce(type == reqType, new RegistryException("Value type has been changed since the value was acquired")); // Remove last two (or one) null terminator assert(data.length > 0 && data[$-1] == '\0'); data.length = data.length - 1; if (data.length > 0 && data[$-1] == '\0') data.length = data.length - 1; auto list = std.array.split(data[], "\0"); value.length = list.length; foreach (i, ref v; value) { v = list[i].to!string; } } private void regQueryValue(in HKEY hkey, in string name, out uint value, REG_VALUE_TYPE reqType) in { assert(hkey !is null); } do { import core.bitop : bswap; REG_VALUE_TYPE type; DWORD cbData = value.sizeof; enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), "Cannot read the requested value"); enforce(type == reqType, new RegistryException("Value type has been changed since the value was acquired")); switch (type) { case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: version (LittleEndian) static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); else value = bswap(value); break; case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: version (LittleEndian) value = bswap(value); else static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN); break; default: throw new RegistryException("Cannot read the given value as a 32-bit integer"); } } private void regQueryValue(in HKEY hkey, in string name, out ulong value, REG_VALUE_TYPE reqType) in { assert(hkey !is null); } do { REG_VALUE_TYPE type; DWORD cbData = value.sizeof; enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), "Cannot read the requested value"); enforce(type == reqType, new RegistryException("Value type has been changed since the value was acquired")); switch (type) { case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: break; default: throw new RegistryException("Cannot read the given value as a 64-bit integer"); } } private void regQueryValue(in HKEY hkey, in string name, out byte[] value, REG_VALUE_TYPE reqType) in { assert(hkey !is null); } do { REG_VALUE_TYPE type; byte[] data = new byte[100]; DWORD cbData = to!DWORD(data.length); LONG res; auto keynameTmp = name.tempCStringW(); res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); if (res == ERROR_MORE_DATA) { data.length = cbData; res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); } enforceSucc(res, "Cannot read the requested value"); enforce(type == reqType, new RegistryException("Value type has been changed since the value was acquired")); switch (type) { case REG_VALUE_TYPE.REG_BINARY: data.length = cbData; value = data; break; default: throw new RegistryException("Cannot read the given value as a string"); } } private void regSetValue(in HKEY hkey, in string subKey, in REG_VALUE_TYPE type, in LPCVOID lpData, in DWORD cbData) in { assert(hkey !is null); } do { enforceSucc(RegSetValueExW(hkey, subKey.tempCStringW(), 0, type, cast(BYTE*) lpData, cbData), "Value cannot be set: \"" ~ subKey ~ "\""); } private void regProcessNthKey(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) { DWORD cSubKeys; DWORD cchSubKeyMaxLen; immutable res = regGetNumSubKeys(key.m_hkey, cSubKeys, cchSubKeyMaxLen); assert(res == ERROR_SUCCESS); wchar[] sName = new wchar[cchSubKeyMaxLen + 1]; // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). dg((DWORD index, out string name) { DWORD cchName; immutable res = regEnumKeyName(key.m_hkey, index, sName, cchName); if (res == ERROR_SUCCESS) { name = sName[0 .. cchName].to!string; } return res; }); } private void regProcessNthValue(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) { DWORD cValues; DWORD cchValueMaxLen; immutable res = regGetNumValues(key.m_hkey, cValues, cchValueMaxLen); assert(res == ERROR_SUCCESS); wchar[] sName = new wchar[cchValueMaxLen + 1]; // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). dg((DWORD index, out string name) { DWORD cchName; immutable res = regEnumValueName(key.m_hkey, index, sName, cchName); if (res == ERROR_SUCCESS) { name = sName[0 .. cchName].to!string; } return res; }); } /* ************* public classes *************** */ /** This class represents a registry key. */ class Key { @safe pure nothrow invariant() { assert(m_hkey !is null); } private: @safe pure nothrow this(HKEY hkey, string name, bool created) in { assert(hkey !is null); } do { m_hkey = hkey; m_name = name; } ~this() { regCloseKey(m_hkey); // Even though this is horried waste-of-cycles programming // we're doing it here so that the m_hkey = null; } public: /// The name of the key @property string name() @safe pure nothrow const { return m_name; } /** The number of sub keys. */ @property size_t keyCount() const { uint cSubKeys; uint cchSubKeyMaxLen; enforceSucc(regGetNumSubKeys(m_hkey, cSubKeys, cchSubKeyMaxLen), "Number of sub-keys cannot be determined"); return cSubKeys; } /** An enumerable sequence of all the sub-keys of this key. */ @property KeySequence keys() @safe pure { return new KeySequence(this); } /** An enumerable sequence of the names of all the sub-keys of this key. */ @property KeyNameSequence keyNames() @safe pure { return new KeyNameSequence(this); } /** The number of values. */ @property size_t valueCount() const { uint cValues; uint cchValueMaxLen; enforceSucc(regGetNumValues(m_hkey, cValues, cchValueMaxLen), "Number of values cannot be determined"); return cValues; } /** An enumerable sequence of all the values of this key. */ @property ValueSequence values() @safe pure { return new ValueSequence(this); } /** An enumerable sequence of the names of all the values of this key. */ @property ValueNameSequence valueNames() @safe pure { return new ValueNameSequence(this); } /** Returns the named sub-key of this key. Params: name = The name of the subkey to create. May not be `null`. Returns: The created key. Throws: `RegistryException` is thrown if the key cannot be created. */ Key createKey(string name, REGSAM access = REGSAM.KEY_ALL_ACCESS) { enforce(!name.empty, new RegistryException("Key name is invalid")); DWORD disposition; HKEY hkey = regCreateKey(m_hkey, name, 0, access, null, disposition); assert(hkey !is null); // Potential resource leak here!! // // If the allocation of the memory for Key fails, the HKEY could be // lost. Hence, we catch such a failure by the finally, and release // the HKEY there. If the creation of try { Key key = new Key(hkey, name, disposition == REG_CREATED_NEW_KEY); hkey = null; return key; } finally { if (hkey !is null) { regCloseKey(hkey); } } } /** Returns the named sub-key of this key. Params: name = The name of the subkey to aquire. If name is the empty string, then the called key is duplicated. access = The desired access; one of the `REGSAM` enumeration. Returns: The aquired key. Throws: This function never returns `null`. If a key corresponding to the requested name is not found, `RegistryException` is thrown. */ Key getKey(string name, REGSAM access = REGSAM.KEY_READ) { if (name.empty) return new Key(regDup(m_hkey), m_name, false); HKEY hkey = regOpenKey(m_hkey, name, access); assert(hkey !is null); // Potential resource leak here!! // // If the allocation of the memory for Key fails, the HKEY could be // lost. Hence, we catch such a failure by the finally, and release // the HKEY there. If the creation of try { Key key = new Key(hkey, name, false); hkey = null; return key; } finally { if (hkey != null) { regCloseKey(hkey); } } } /** Deletes the named key. Params: name = The name of the key to delete. May not be `null`. */ void deleteKey(string name, REGSAM access = cast(REGSAM) 0) { enforce(!name.empty, new RegistryException("Key name is invalid")); regDeleteKey(m_hkey, name, access); } /** Returns the named value. If `name` is the empty string, then the default value is returned. Returns: This function never returns `null`. If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ Value getValue(string name) { return new Value(this, name, regGetValueType(m_hkey, name)); } /** Sets the named value with the given 32-bit unsigned integer value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The 32-bit unsigned value to set. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, uint value) { setValue(name, value, endian); } /** Sets the named value with the given 32-bit unsigned integer value, according to the desired byte-ordering. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The 32-bit unsigned value to set. endian = Can be `Endian.BigEndian` or `Endian.LittleEndian`. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, uint value, Endian endian) { REG_VALUE_TYPE type = _RVT_from_Endian(endian); assert(type == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN || type == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); regSetValue(m_hkey, name, type, &value, value.sizeof); } /** Sets the named value with the given 64-bit unsigned integer value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The 64-bit unsigned value to set. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, ulong value) { regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_QWORD, &value, value.sizeof); } /** Sets the named value with the given string value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The string value to set. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, string value) { setValue(name, value, false); } /** Sets the named value with the given string value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The string value to set. asEXPAND_SZ = If `true`, the value will be stored as an expandable environment string, otherwise as a normal string. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, string value, bool asEXPAND_SZ) { auto pszTmp = value.tempCStringW(); const(void)* data = pszTmp; DWORD len = to!DWORD(lstrlenW(pszTmp) * wchar.sizeof); regSetValue(m_hkey, name, asEXPAND_SZ ? REG_VALUE_TYPE.REG_EXPAND_SZ : REG_VALUE_TYPE.REG_SZ, data, len); } /** Sets the named value with the given multiple-strings value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The multiple-strings value to set. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, string[] value) { wstring[] data = new wstring[value.length+1]; foreach (i, ref s; data[0..$-1]) { s = value[i].to!wstring; } data[$-1] = "\0"; auto ws = std.array.join(data, "\0"w); regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_MULTI_SZ, ws.ptr, to!uint(ws.length * wchar.sizeof)); } /** Sets the named value with the given binary value. Params: name = The name of the value to set. If it is the empty string, sets the default value. value = The binary value to set. Throws: If a value corresponding to the requested name is not found, `RegistryException` is thrown. */ void setValue(string name, byte[] value) { regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_BINARY, value.ptr, to!DWORD(value.length)); } /** Deletes the named value. Params: name = The name of the value to delete. May not be `null`. Throws: If a value of the requested name is not found, `RegistryException` is thrown. */ void deleteValue(string name) { regDeleteValue(m_hkey, name); } /** Flushes any changes to the key to disk. */ void flush() { regFlushKey(m_hkey); } private: HKEY m_hkey; string m_name; } /** This class represents a value of a registry key. */ class Value { @safe pure nothrow invariant() { assert(m_key !is null); } private: @safe pure nothrow this(Key key, string name, REG_VALUE_TYPE type) in { assert(null !is key); } do { m_key = key; m_type = type; m_name = name; } public: /** The name of the value. If the value represents a default value of a key, which has no name, the returned string will be of zero length. */ @property string name() @safe pure nothrow const { return m_name; } /** The type of value. */ @property REG_VALUE_TYPE type() @safe pure nothrow const { return m_type; } /** Obtains the current value of the value as a string. If the value's type is REG_EXPAND_SZ the returned value is not expanded; `value_EXPAND_SZ` should be called Returns: The contents of the value. Throws: `RegistryException` if the type of the value is not REG_SZ, REG_EXPAND_SZ, REG_DWORD, or REG_QWORD. */ @property string value_SZ() const { string value; regQueryValue(m_key.m_hkey, m_name, value, m_type); return value; } /** Obtains the current value as a string, within which any environment variables have undergone expansion. This function works with the same value-types as `value_SZ`. Returns: The contents of the value. */ @property string value_EXPAND_SZ() const { string value = value_SZ; // ExpandEnvironemntStrings(): // http://msdn2.microsoft.com/en-us/library/ms724265.aspx const srcTmp = value.tempCStringW(); DWORD cchRequired = ExpandEnvironmentStringsW(srcTmp, null, 0); wchar[] newValue = new wchar[cchRequired]; immutable DWORD count = enforce!Win32Exception( ExpandEnvironmentStringsW(srcTmp, newValue.ptr, to!DWORD(newValue.length)), "Failed to expand environment variables"); return newValue[0 .. count-1].to!string; // remove trailing 0 } /** Obtains the current value as an array of strings. Returns: The contents of the value. Throws: `RegistryException` if the type of the value is not REG_MULTI_SZ. */ @property string[] value_MULTI_SZ() const { string[] value; regQueryValue(m_key.m_hkey, m_name, value, m_type); return value; } /** Obtains the current value as a 32-bit unsigned integer, ordered correctly according to the current architecture. Returns: The contents of the value. Throws: `RegistryException` is thrown for all types other than REG_DWORD, REG_DWORD_LITTLE_ENDIAN and REG_DWORD_BIG_ENDIAN. */ @property uint value_DWORD() const { uint value; regQueryValue(m_key.m_hkey, m_name, value, m_type); return value; } /** Obtains the value as a 64-bit unsigned integer, ordered correctly according to the current architecture. Returns: The contents of the value. Throws: `RegistryException` if the type of the value is not REG_QWORD. */ @property ulong value_QWORD() const { ulong value; regQueryValue(m_key.m_hkey, m_name, value, m_type); return value; } /** Obtains the value as a binary blob. Returns: The contents of the value. Throws: `RegistryException` if the type of the value is not REG_BINARY. */ @property byte[] value_BINARY() const { byte[] value; regQueryValue(m_key.m_hkey, m_name, value, m_type); return value; } private: Key m_key; REG_VALUE_TYPE m_type; string m_name; } /** Represents the local system registry. */ final class Registry { private: @disable this(); public: /// Returns the root key for the HKEY_CLASSES_ROOT hive static @property Key classesRoot() { return new Key(HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT", false); } /// Returns the root key for the HKEY_CURRENT_USER hive static @property Key currentUser() { return new Key(HKEY_CURRENT_USER, "HKEY_CURRENT_USER", false); } /// Returns the root key for the HKEY_LOCAL_MACHINE hive static @property Key localMachine() { return new Key(HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", false); } /// Returns the root key for the HKEY_USERS hive static @property Key users() { return new Key(HKEY_USERS, "HKEY_USERS", false); } /// Returns the root key for the HKEY_PERFORMANCE_DATA hive static @property Key performanceData() { return new Key(HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA", false); } /// Returns the root key for the HKEY_CURRENT_CONFIG hive static @property Key currentConfig() { return new Key(HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG", false); } /// Returns the root key for the HKEY_DYN_DATA hive static @property Key dynData() { return new Key(HKEY_DYN_DATA, "HKEY_DYN_DATA", false); } } /** An enumerable sequence representing the names of the sub-keys of a registry Key. Example: ---- Key key = ... foreach (string subkeyName; key.keyNames) { // using subkeyName } ---- */ class KeyNameSequence { @safe pure nothrow invariant() { assert(m_key !is null); } private: @safe pure nothrow this(Key key) { m_key = key; } public: /** The number of keys. */ @property size_t count() const { return m_key.keyCount; } /** The name of the key at the given index. Params: index = The 0-based index of the key to retrieve. Returns: The name of the key corresponding to the given index. Throws: RegistryException if no corresponding key is retrieved. */ string getKeyName(size_t index) { string name; regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) { enforceSucc(getName(to!DWORD(index), name), "Invalid key"); }); return name; } /** The name of the key at the given index. Params: index = The 0-based index of the key to retrieve. Returns: The name of the key corresponding to the given index. Throws: `RegistryException` if no corresponding key is retrieved. */ string opIndex(size_t index) { return getKeyName(index); } /// int opApply(scope int delegate(ref string name) dg) { int result; regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) { for (DWORD index = 0; !result; ++index) { string name; immutable res = getName(index, name); if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete break; enforceSucc(res, "Key name enumeration incomplete"); result = dg(name); } }); return result; } private: Key m_key; } /** An enumerable sequence representing the sub-keys of a registry Key. Example: ---- Key key = ... foreach (Key subkey; key.keys) { // using subkey } ---- */ class KeySequence { @safe pure nothrow invariant() { assert(m_key !is null); } private: @safe pure nothrow this(Key key) { m_key = key; } public: /** The number of keys. */ @property size_t count() const { return m_key.keyCount; } /** The key at the given index. Params: index = The 0-based index of the key to retrieve. Returns: The key corresponding to the given index. Throws: `RegistryException` if no corresponding key is retrieved. */ Key getKey(size_t index) { string name; regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) { enforceSucc(getName(to!DWORD(index), name), "Invalid key"); }); return m_key.getKey(name); } /** The key at the given index. Params: index = The 0-based index of the key to retrieve. Returns: The key corresponding to the given index. Throws: `RegistryException` if no corresponding key is retrieved. */ Key opIndex(size_t index) { return getKey(index); } /// int opApply(scope int delegate(ref Key key) dg) { int result = 0; regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) { for (DWORD index = 0; !result; ++index) { string name; immutable res = getName(index, name); if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete break; enforceSucc(res, "Key enumeration incomplete"); try { Key key = m_key.getKey(name); result = dg(key); } catch (RegistryException e) { // Skip inaccessible keys; they are // accessible via the KeyNameSequence if (e.error == ERROR_ACCESS_DENIED) continue; throw e; } } }); return result; } private: Key m_key; } /** An enumerable sequence representing the names of the values of a registry Key. Example: ---- Key key = ... foreach (string valueName; key.valueNames) { // using valueName } ---- */ class ValueNameSequence { @safe pure nothrow invariant() { assert(m_key !is null); } private: @safe pure nothrow this(Key key) { m_key = key; } public: /** The number of values. */ @property size_t count() const { return m_key.valueCount; } /** The name of the value at the given index. Params: index = The 0-based index of the value to retrieve. Returns: The name of the value corresponding to the given index. Throws: `RegistryException` if no corresponding value is retrieved. */ string getValueName(size_t index) { string name; regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) { enforceSucc(getName(to!DWORD(index), name), "Invalid value"); }); return name; } /** The name of the value at the given index. Params: index = The 0-based index of the value to retrieve. Returns: The name of the value corresponding to the given index. Throws: `RegistryException` if no corresponding value is retrieved. */ string opIndex(size_t index) { return getValueName(index); } /// int opApply(scope int delegate(ref string name) dg) { int result = 0; regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) { for (DWORD index = 0; !result; ++index) { string name; immutable res = getName(index, name); if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete break; enforceSucc(res, "Value name enumeration incomplete"); result = dg(name); } }); return result; } private: Key m_key; } /** An enumerable sequence representing the values of a registry Key. Example: ---- Key key = ... foreach (Value value; key.values) { // using value } ---- */ class ValueSequence { @safe pure nothrow invariant() { assert(m_key !is null); } private: @safe pure nothrow this(Key key) { m_key = key; } public: /// The number of values @property size_t count() const { return m_key.valueCount; } /** The value at the given `index`. Params: index = The 0-based index of the value to retrieve Returns: The value corresponding to the given index. Throws: `RegistryException` if no corresponding value is retrieved */ Value getValue(size_t index) { string name; regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) { enforceSucc(getName(to!DWORD(index), name), "Invalid value"); }); return m_key.getValue(name); } /** The value at the given `index`. Params: index = The 0-based index of the value to retrieve. Returns: The value corresponding to the given index. Throws: `RegistryException` if no corresponding value is retrieved. */ Value opIndex(size_t index) { return getValue(index); } /// int opApply(scope int delegate(ref Value value) dg) { int result = 0; regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) { for (DWORD index = 0; !result; ++index) { string name; immutable res = getName(index, name); if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete break; enforceSucc(res, "Value enumeration incomplete"); Value value = m_key.getValue(name); result = dg(value); } }); return result; } private: Key m_key; } @system unittest { debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); debug(winreg) writefln("std.windows.registry.unittest read"); /+ // Mask for test speed up Key HKCR = Registry.classesRoot; Key CLSID = HKCR.getKey("CLSID"); foreach (Key key; CLSID.keys) { foreach (Value val; key.values) { } } +/ Key HKCU = Registry.currentUser; assert(HKCU); // Enumerate all subkeys of key Software Key softwareKey = HKCU.getKey("Software"); assert(softwareKey); foreach (Key key; softwareKey.keys) { //writefln("Key %s", key.name); foreach (Value val; key.values) { } } } @system unittest { debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); debug(winreg) writefln("std.windows.registry.unittest write"); // Warning: This unit test writes to the registry. // The test can fail if you don't have sufficient rights Key HKCU = Registry.currentUser; assert(HKCU); // Create a new key string unittestKeyName = "Temporary key for a D UnitTest which can be deleted afterwards"; Key unittestKey = HKCU.createKey(unittestKeyName); assert(unittestKey); Key cityKey = unittestKey.createKey( "CityCollection using foreign names with umlauts and accents: " ~"\u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e0\u00e1\u00e2\u00df" ); cityKey.setValue("K\u00f6ln", "Germany"); // Cologne cityKey.setValue("\u041c\u0438\u043d\u0441\u043a", "Belarus"); // Minsk cityKey.setValue("\u5317\u4eac", "China"); // Bejing bool foundCologne, foundMinsk, foundBeijing; foreach (Value v; cityKey.values) { auto vname = v.name; auto vvalue_SZ = v.value_SZ; if (v.name == "K\u00f6ln") { foundCologne = true; assert(v.value_SZ == "Germany"); } if (v.name == "\u041c\u0438\u043d\u0441\u043a") { foundMinsk = true; assert(v.value_SZ == "Belarus"); } if (v.name == "\u5317\u4eac") { foundBeijing = true; assert(v.value_SZ == "China"); } } assert(foundCologne); assert(foundMinsk); assert(foundBeijing); Key stateKey = unittestKey.createKey("StateCollection"); stateKey.setValue("Germany", ["D\u00fcsseldorf", "K\u00f6ln", "Hamburg"]); Value v = stateKey.getValue("Germany"); string[] actual = v.value_MULTI_SZ; assert(actual.length == 3); assert(actual[0] == "D\u00fcsseldorf"); assert(actual[1] == "K\u00f6ln"); assert(actual[2] == "Hamburg"); Key numberKey = unittestKey.createKey("Number"); numberKey.setValue("One", 1); Value one = numberKey.getValue("One"); assert(one.value_SZ == "1"); assert(one.value_DWORD == 1); unittestKey.deleteKey(numberKey.name); unittestKey.deleteKey(stateKey.name); unittestKey.deleteKey(cityKey.name); HKCU.deleteKey(unittestKeyName); auto e = collectException!RegistryException(HKCU.getKey("cDhmxsX9K23a8Uf869uB")); assert(e.msg.endsWith(" (error 2)")); } @system unittest { Key HKCU = Registry.currentUser; assert(HKCU); Key key = HKCU.getKey("Control Panel"); assert(key); assert(key.keyCount >= 2); // Make sure `key` isn't garbage-collected while iterating over it. // Trigger a collection in the first iteration and check whether we // make it successfully to the second iteration. int i = 0; foreach (name; key.keyNames) { if (i++ > 0) break; import core.memory; GC.collect(); } assert(i == 2); }