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