1 /**
2  * Copyright: Copyright (c) 2011 Nick Sabalausky. All rights reserved.
3  * Authors: Nick Sabalausky
4  * Version: Initial created: Jun 1, 2011
5  * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
6  */
7 module dvm.sys.Registry;
8 
9 version (Windows):
10 
11 import std.utf : toUTF16z;
12 
13 import tango.sys.win32.Types;
14 import tango.sys.win32.UserGdi;
15 import tango.util.Convert;
16 
17 import dvm.dvm.Exceptions;
18 import dvm.util.Windows;
19 
20 /// Low-Level Registry Wrappers
21 ///
22 /// WARNING: REG_MULTI_SZ, REG_BINARY and REG_NONE are untested.
23 
24 /// Types and Constants ///////////////////////////
25 
26 public import tango.sys.win32.Types :
27     HKEY,
28     KEY_ALL_ACCESS,
29     KEY_CREATE_LINK,
30     KEY_CREATE_SUB_KEY,
31     KEY_ENUMERATE_SUB_KEYS,
32     KEY_EXECUTE,
33     KEY_NOTIFY,
34     KEY_QUERY_VALUE,
35     KEY_READ,
36     KEY_SET_VALUE,
37     KEY_WRITE,
38     REG_OPTION_NON_VOLATILE,
39     REG_OPTION_VOLATILE;
40 
41 enum : DWORD
42 {
43     REG_OPTION_CREATE_LINK = 2,
44     REG_OPTION_BACKUP_RESTORE = 4,
45 }
46 
47 enum RegRoot : DWORD
48 {
49     HKEY_CLASSES_ROOT = (0x80000000),
50     HKEY_CURRENT_USER = (0x80000001),
51     HKEY_LOCAL_MACHINE = (0x80000002),
52     HKEY_USERS = (0x80000003),
53     HKEY_PERFORMANCE_DATA = (0x80000004),
54     HKEY_CURRENT_CONFIG = (0x80000005),
55     HKEY_DYN_DATA = (0x80000006),
56 }
57 
58 HKEY HKEY_CLASSES_ROOT ()
59 {
60     return cast(HKEY) RegRoot.HKEY_CLASSES_ROOT;
61 }
62 
63 HKEY HKEY_CURRENT_USER ()
64 {
65     return cast(HKEY) RegRoot.HKEY_CURRENT_USER;
66 }
67 
68 HKEY HKEY_LOCAL_MACHINE ()
69 {
70     return cast(HKEY) RegRoot.HKEY_LOCAL_MACHINE;
71 }
72 
73 HKEY HKEY_USERS ()
74 {
75     return cast(HKEY) RegRoot.HKEY_USERS;
76 }
77 
78 HKEY HKEY_PERFORMANCE_DATA ()
79 {
80     return cast(HKEY) RegRoot.HKEY_PERFORMANCE_DATA;
81 }
82 
83 HKEY HKEY_CURRENT_CONFIG ()
84 {
85     return cast(HKEY) RegRoot.HKEY_CURRENT_CONFIG;
86 }
87 
88 HKEY HKEY_DYN_DATA ()
89 {
90     return cast(HKEY) RegRoot.HKEY_DYN_DATA;
91 }
92 
93 enum RegKeyAccess
94 {
95     Read, Write, All
96 }
97 
98 enum RegValueType : DWORD
99 {
100     Unknown = .DWORD.max,
101 
102     BINARY = REG_BINARY,
103     DWORD = REG_DWORD,
104     EXPAND_SZ = REG_EXPAND_SZ,
105     LINK = REG_LINK,
106     MULTI_SZ = REG_MULTI_SZ,
107     NONE = REG_NONE,
108     SZ = REG_SZ,
109 
110     // Beware, these are reported to not work on all versions
111     // of Windows from Win2K and up:
112     //QWORD = REG_QWORD,
113     //QWORD_LITTLE_ENDIAN = REG_QWORD_LITTLE_ENDIAN,
114     DWORD_LITTLE_ENDIAN = REG_DWORD_LITTLE_ENDIAN,
115     DWORD_BIG_ENDIAN = REG_DWORD_BIG_ENDIAN,
116 }
117 
118 struct RegQueryResult
119 {
120     RegValueType type;
121     string asString;
122     string[] asStringArray;
123     uint asUInt;
124     ubyte[] asBinary;
125 }
126 
127 template DataTypeOf (RegValueType type)
128 {
129     static if(type == RegValueType.DWORD)
130         alias uint DataTypeOf;
131 
132     else static if(type == RegValueType.DWORD_LITTLE_ENDIAN)
133         alias uint DataTypeOf;
134 
135     else static if(type == RegValueType.SZ)
136         alias string DataTypeOf;
137 
138     else static if(type == RegValueType.EXPAND_SZ)
139         alias string DataTypeOf;
140 
141     else static if(type == RegValueType.MULTI_SZ)
142         alias string[] DataTypeOf;
143 
144     else
145         alias ubyte[] DataTypeOf;
146 }
147 
148 /// Exception ///////////////////////////
149 
150 class RegistryException : WinAPIException
151 {
152     string registryMsg;
153     string path;
154     bool isKey; // Is path a key or a value?
155 
156     this (LONG code, string path, bool isKey, string registryMsg="")
157     {
158         this.registryMsg = registryMsg;
159         this.path = path;
160         this.isKey = isKey;
161 
162         string keyInfo;
163         if(isKey)
164             keyInfo = "Registry Key '" ~ path ~ "': ";
165         else
166             keyInfo = "Registry Value '" ~ path ~ "': ";
167 
168         string regMsgInfo = registryMsg;
169         string windowsMsg = "";
170 
171         if(code != ERROR_SUCCESS)
172             windowsMsg = WinAPIException.getMessage(code);
173 
174         if(regMsgInfo != "" && windowsMsg != "")
175             regMsgInfo ~= ": ";
176 
177         super(code, keyInfo ~ regMsgInfo ~ windowsMsg);
178     }
179 
180     this (string path, bool isKey, string registryMsg = "")
181     {
182         this(ERROR_SUCCESS, path, isKey, registryMsg);
183     }
184 }
185 
186 /// Conversion Functions ///////////////////////////
187 
188 ubyte[] toRegDWord (ref uint val)
189 {
190     return (cast(ubyte*) &val)[0..4];
191 }
192 
193 ubyte[] toRegSZ (string str)
194 {
195     auto wstr = to!(wstring)(str);
196 
197     if(wstr.length == 0 || wstr[$-1] != '\0')
198         wstr ~= '\0';
199 
200     return cast(ubyte[])wstr;
201 }
202 
203 ubyte[] toRegMultiSZ (string[] arr)
204 {
205     ushort[] result;
206 
207     foreach(str; arr)
208     {
209         if(str.length == 0)
210             throw new DvmException("Cannot store empty strings in a REG_MULTI_SZ", __FILE__, __LINE__);
211 
212         auto wstr = to!(wstring)(str);
213         result ~= cast(ushort[]) wstr;
214         result ~= 0;
215     }
216 
217     result ~= 0;
218 
219     return cast(ubyte[]) result;
220 }
221 
222 REGSAM toRegSam (RegKeyAccess access)
223 {
224     switch(access)
225     {
226         case RegKeyAccess.Read: return KEY_READ;
227         case RegKeyAccess.Write: return KEY_WRITE;
228         case RegKeyAccess.All: return KEY_READ | KEY_WRITE;
229 
230         default:
231             throw new Exception("Internal Error: Unhandled RegKeyAccess: '" ~ to!(string)(access) ~ "'", __FILE__, __LINE__);
232     }
233 }
234 
235 string toString (RegRoot root)
236 {
237     switch(root)
238     {
239         case RegRoot.HKEY_CLASSES_ROOT: return "HKEY_CLASSES_ROOT";
240         case RegRoot.HKEY_CURRENT_USER: return "HKEY_CURRENT_USER";
241         case RegRoot.HKEY_LOCAL_MACHINE: return "HKEY_LOCAL_MACHINE";
242         case RegRoot.HKEY_USERS: return "HKEY_USERS";
243         case RegRoot.HKEY_PERFORMANCE_DATA: return "HKEY_PERFORMANCE_DATA";
244         case RegRoot.HKEY_CURRENT_CONFIG: return "HKEY_CURRENT_CONFIG";
245         case RegRoot.HKEY_DYN_DATA: return "HKEY_DYN_DATA";
246 
247         default:
248             throw new Exception("Internal Error: Unhandled RegRoot '" ~ to!(string)(root) ~ "'", __FILE__, __LINE__);
249     }
250 }
251 
252 string toString (RegValueType type)
253 {
254     switch(type)
255     {
256         case RegValueType.BINARY: return "REG_BINARY";
257         case RegValueType.DWORD: return "REG_DWORD";
258         case RegValueType.EXPAND_SZ: return "REG_EXPAND_SZ";
259         case RegValueType.LINK: return "REG_LINK";
260         case RegValueType.MULTI_SZ: return "REG_MULTI_SZ";
261         case RegValueType.NONE: return "REG_NONE";
262         case RegValueType.SZ: return "REG_SZ";
263         case RegValueType.DWORD_BIG_ENDIAN: return "REG_DWORD_BIG_ENDIAN";
264         case RegValueType.Unknown: return "(Unknown KeyValueType)";
265 
266         default:
267             return "(KeyValueType #" ~ to!(string)(cast(DWORD) type) ~ ")";
268     }
269 }
270 
271 /// Private Error Handling Utilities ///////////////////////////
272 
273 private void ensureSuccess (LONG code, string path, bool isKey, string registryMsg="")
274 {
275     if (code != ERROR_SUCCESS)
276         error(code, path, isKey, registryMsg);
277 }
278 
279 private void ensureSuccessKey (LONG code, string path, string registryMsg="")
280 {
281     ensureSuccess(code, path, true, registryMsg);
282 }
283 
284 private void ensureSuccessValue (LONG code, string path, string registryMsg="")
285 {
286     ensureSuccess(code, path, false, registryMsg);
287 }
288 
289 private void error (LONG code, string path, bool isKey, string registryMsg="")
290 {
291     throw new RegistryException(code, `{Unknown Path}\` ~ path, isKey, registryMsg);
292 }
293 
294 private void errorKey (LONG code, string path, string registryMsg="")
295 {
296     error(code, path, true, registryMsg);
297 }
298 
299 private void errorValue (LONG code, string path, string registryMsg="")
300 {
301     error(code, path, false, registryMsg);
302 }
303 
304 /// Registry Functions ///////////////////////////
305 
306 HKEY regOpenKey (HKEY hKey, string subKey, RegKeyAccess access)
307 {
308     HKEY outKey;
309 
310     auto result = RegOpenKeyExW(hKey, subKey.toUTF16z(),0, toRegSam(access), &outKey);
311     ensureSuccessKey(result, subKey, "Couldn't open key");
312 
313     return outKey;
314 }
315 
316 HKEY regCreateKey (HKEY hKey, string subKey, DWORD dwOptions, RegKeyAccess access, out bool wasCreated)
317 {
318     HKEY outKey;
319     DWORD disposition;
320     auto result = RegCreateKeyExW(hKey, subKey.toUTF16z(), 0, null, dwOptions, toRegSam(access), null, &outKey, &disposition);
321     wasCreated = (disposition == REG_CREATED_NEW_KEY);
322     ensureSuccessKey(result, subKey, "Couldn't open or create key");
323 
324     return outKey;
325 }
326 
327 HKEY regCreateKey (HKEY hKey, string subKey, DWORD dwOptions, RegKeyAccess access)
328 {
329     bool wasCreated;
330     return regCreateKey(hKey, subKey, dwOptions, access, wasCreated);
331 }
332 
333 void regCloseKey (HKEY hKey)
334 {
335     auto result = RegCloseKey(hKey);
336     ensureSuccessKey(result, "{Unknown Key}", "Couldn't close key");
337 }
338 
339 bool regValueExists (HKEY hKey, string valueName)
340 {
341     auto result = RegQueryValueExW(hKey, valueName.toUTF16z(), null, null, null, null);
342 
343     if(result == ERROR_FILE_NOT_FOUND)
344         return false;
345 
346     if(result == ERROR_SUCCESS)
347         return true;
348 
349     errorValue(result, valueName, "Couldn't check if value exists");
350     return false;
351 }
352 
353 void regDeleteKey (HKEY hKey, string subKey)
354 {
355     auto result = RegDeleteKeyW(hKey, subKey.toUTF16z());
356     ensureSuccessKey(result, subKey, "Couldn't delete key");
357 }
358 
359 void regDeleteValue (HKEY hKey, string valueName)
360 {
361     auto result = RegDeleteValueW(hKey, valueName.toUTF16z());
362     ensureSuccessValue(result, valueName, "Couldn't delete value");
363 }
364 
365 /// Registry Functions: regSetValue ///////////////////////////
366 
367 /// Be very careful with this particuler version.
368 /// Make sure to follow all the rules in MS's documentation.
369 /// The other overloads of regSetValue are recommended over
370 /// this one, since they already handle all the proper rules.
371 void regSetValue (HKEY hKey, string valueName, RegValueType type, ubyte[] data)
372 {
373     if(type == RegValueType.Unknown)
374         errorValue(ERROR_SUCCESS, valueName, "Can't set a key value of type 'Unknown'");
375 
376     auto ptr = (data is null)? null : data.ptr;
377     auto len = (data is null)? 0 : data.length;
378 
379     auto result = RegSetValueExW(hKey, valueName.toUTF16z(), 0, type, ptr, len);
380     ensureSuccessValue(result, valueName, "Couldn't set "~toString(type)~" value");
381 }
382 
383 void regSetValue (HKEY hKey, string valueName, string data)
384 {
385     regSetValue(hKey, valueName, data, false);
386 }
387 
388 void regSetValueExpand (HKEY hKey, string valueName, string data)
389 {
390     regSetValue(hKey, valueName, data, true);
391 }
392 
393 void regSetValue (HKEY hKey, string valueName, string data, bool expand)
394 {
395     auto type = expand? RegValueType.EXPAND_SZ : RegValueType.SZ;
396     regSetValue(hKey, valueName, type, data.toRegSZ());
397 }
398 
399 void regSetValue (HKEY hKey, string valueName, string[] data)
400 {
401     regSetValue(hKey, valueName, RegValueType.MULTI_SZ, data.toRegMultiSZ());
402 }
403 
404 void regSetValue (HKEY hKey, string valueName, ubyte[] data)
405 {
406     regSetValue(hKey, valueName, RegValueType.BINARY, data);
407 }
408 
409 void regSetValue (HKEY hKey, string valueName, uint data)
410 {
411     regSetValue(hKey, valueName, RegValueType.DWORD, toRegDWord(data));
412 }
413 
414 void regSetValue (HKEY hKey, string valueName)
415 {
416     regSetValue(hKey, valueName, RegValueType.NONE, null);
417 }
418 
419 void regSetValue (HKEY hKey, string valueName, RegQueryResult data)
420 {
421     switch(data.type)
422     {
423         case RegValueType.DWORD:
424             regSetValue(hKey, valueName, data.type, toRegDWord(data.asUInt));
425         break;
426 
427         case RegValueType.SZ, RegValueType.EXPAND_SZ:
428             regSetValue(hKey, valueName, data.type, data.asString.toRegSZ());
429         break;
430 
431         case RegValueType.MULTI_SZ:
432             regSetValue(hKey, valueName, data.type, data.asStringArray.toRegMultiSZ());
433         break;
434 
435         default:
436             regSetValue(hKey, valueName, data.type, data.asBinary);
437         break;
438     }
439 }
440 
441 /// Registry Functions: regQueryValue ///////////////////////////
442 
443 RegQueryResult regQueryValue () (HKEY hKey, string valueName)
444 {
445     RegQueryResult ret;
446     DWORD dataSize;
447     auto valueNameZ = valueName.toUTF16z();
448 
449     auto result = RegQueryValueExW(hKey, valueNameZ, null, null, null, &dataSize);
450     ensureSuccessValue(result, valueName, "Couldn't check length of value's data");
451 
452     ubyte[] data;
453     data.length = dataSize;
454 
455     result = RegQueryValueExW(hKey, valueNameZ, null, &(cast(DWORD)(ret.type)), data.ptr, &dataSize);
456     ensureSuccessValue(result, valueName, "Couldn't get value");
457 
458     switch(ret.type)
459     {
460         case RegValueType.DWORD:
461             ret.asUInt = (cast(uint[])data)[0];
462         break;
463 
464         case RegValueType.SZ, RegValueType.EXPAND_SZ:
465             ret.asString = to!(string)( (cast(wstring) data)[0 .. $-1] );
466         break;
467 
468         case RegValueType.MULTI_SZ:
469             ret.asStringArray = null;
470             auto wstrArr = split(cast(wstring)data, "\0"w)[0 .. $-1];
471 
472             foreach(wstr; wstrArr)
473                 ret.asStringArray ~= to!(string)(wstr);
474         break;
475 
476         default:
477             ret.asBinary = data;
478         break;
479     }
480 
481     return ret;
482 }
483 
484 DataTypeOf!(type) regQueryValue (RegValueType type) (HKEY hKey, string valueName)
485 {
486     auto result = regQueryValue(hKey, valueName);
487 
488     if(result.type != type)
489         errorValue(ERROR_SUCCESS, valueName, "Expected key type '" ~ toString(type) ~ "', " ~"not '"~toString(result.type)~"'");
490 
491     alias DataTypeOf!(type) T;
492 
493     static if (is(T == uint))
494         return result.asUInt;
495 
496     else static if (is(T == string))
497         return result.asString;
498 
499     else static if (is(T == string[]))
500         return result.asStringArray;
501 
502     else
503         return result.asBinary;
504 }