| /** |
| 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.windows; |
| 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 (unittest) 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); |
| } |
| body |
| { |
| /* 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()), |
| "Value cannot be deleted: \"" ~ valueName ~ "\""); |
| } |
| |
| private HKEY regDup(HKEY hkey) |
| in |
| { |
| assert(hkey !is null); |
| } |
| body |
| { |
| /* 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); |
| } |
| body |
| { |
| // 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| import core.bitop : bswap; |
| |
| REG_VALUE_TYPE type; |
| |
| // See bugzilla 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| body |
| { |
| 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); |
| } |
| |
| public: |
| /** |
| Returns the named sub-key of this key. |
| |
| Params: |
| name = The name of the subkey to create. May not be $(D null). |
| Returns: |
| The created key. |
| Throws: |
| $(D 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 $(D REGSAM) enumeration. |
| Returns: |
| The aquired key. |
| Throws: |
| This function never returns $(D null). If a key corresponding to |
| the requested name is not found, $(D 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 $(D 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 $(D name) is the empty string, then the default value is returned. |
| |
| Returns: |
| This function never returns $(D null). If a value corresponding |
| to the requested name is not found, $(D 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, |
| $(D 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 $(D Endian.BigEndian) or $(D Endian.LittleEndian). |
| Throws: |
| If a value corresponding to the requested name is not found, |
| $(D 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, |
| $(D 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, |
| $(D 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 $(D 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, |
| $(D 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, |
| $(D 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, |
| $(D 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 $(D null). |
| Throws: |
| If a value of the requested name is not found, |
| $(D 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); |
| } |
| body |
| { |
| 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 <b>not</b> |
| expanded; $(D value_EXPAND_SZ) should be called |
| |
| Returns: |
| The contents of the value. |
| Throws: |
| $(D 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 $(D 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: |
| $(D 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: |
| $(D 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: |
| $(D 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: |
| $(D 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: |
| $(D RegistryException) if no corresponding key is retrieved. |
| */ |
| string opIndex(size_t index) |
| { |
| return getKeyName(index); |
| } |
| |
| public: |
| /// |
| 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: |
| $(D 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: |
| $(D RegistryException) if no corresponding key is retrieved. |
| */ |
| Key opIndex(size_t index) |
| { |
| return getKey(index); |
| } |
| |
| public: |
| /// |
| 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: |
| $(D 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: |
| $(D RegistryException) if no corresponding value is retrieved. |
| */ |
| string opIndex(size_t index) |
| { |
| return getValueName(index); |
| } |
| |
| public: |
| /// |
| 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 $(D index). |
| |
| Params: |
| index = The 0-based index of the value to retrieve |
| Returns: |
| The value corresponding to the given index. |
| Throws: |
| $(D 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 $(D index). |
| |
| Params: |
| index = The 0-based index of the value to retrieve. |
| Returns: |
| The value corresponding to the given index. |
| Throws: |
| $(D RegistryException) if no corresponding value is retrieved. |
| */ |
| Value opIndex(size_t index) |
| { |
| return getValue(index); |
| } |
| |
| public: |
| /// |
| 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); |
| } |