1 // Written by Christopher E. Miller
2 // See the included license.txt for copyright and license details.
3 
4 // Not actually part of forms, but is handy.
5 
6 module dfl.registry;
7 import core.sys.windows.windows;
8 
9 import dfl.base;
10 import dfl.exception;
11 import dfl.internal.dlib;
12 import dfl.internal.utf;
13 
14 class DflRegistryException : DflException { // package
15    this(Dstring msg, int errorCode = 0) {
16       this.errorCode = errorCode;
17       debug {
18          if (errorCode) {
19             msg = msg ~ " (error " ~ intToString(errorCode) ~ ")"; // Dup.
20          }
21       }
22       super(msg);
23    }
24 
25    int errorCode;
26 }
27 
28 class Registry {
29    private this() {
30    }
31 
32 static:
33 
34    @property RegistryKey classesRoot() {
35       if (!_classesRoot) {
36          _classesRoot = new RegistryKey(HKEY_CLASSES_ROOT, false);
37       }
38       return _classesRoot;
39    }
40 
41    @property RegistryKey currentConfig() {
42       if (!_currentConfig) {
43          _currentConfig = new RegistryKey(HKEY_CURRENT_CONFIG, false);
44       }
45       return _currentConfig;
46    }
47 
48    @property RegistryKey currentUser() {
49       if (!_currentUser) {
50          _currentUser = new RegistryKey(HKEY_CURRENT_USER, false);
51       }
52       return _currentUser;
53    }
54 
55    @property RegistryKey dynData() {
56       if (!_dynData) {
57          _dynData = new RegistryKey(HKEY_DYN_DATA, false);
58       }
59       return _dynData;
60    }
61 
62    @property RegistryKey localMachine() {
63       if (!_localMachine) {
64          _localMachine = new RegistryKey(HKEY_LOCAL_MACHINE, false);
65       }
66       return _localMachine;
67    }
68 
69    @property RegistryKey performanceData() {
70       if (!_performanceData) {
71          _performanceData = new RegistryKey(HKEY_PERFORMANCE_DATA, false);
72       }
73       return _performanceData;
74    }
75 
76    @property RegistryKey users() {
77       if (!_users) {
78          _users = new RegistryKey(HKEY_USERS, false);
79       }
80       return _users;
81    }
82 
83 private:
84    RegistryKey _classesRoot;
85    RegistryKey _currentConfig;
86    RegistryKey _currentUser;
87    RegistryKey _dynData;
88    RegistryKey _localMachine;
89    RegistryKey _performanceData;
90    RegistryKey _users;
91 
92    /+
93    static this() {
94       _classesRoot = new RegistryKey(HKEY_CLASSES_ROOT, false);
95       _currentConfig = new RegistryKey(HKEY_CURRENT_CONFIG, false);
96       _currentUser = new RegistryKey(HKEY_CURRENT_USER, false);
97       _dynData = new RegistryKey(HKEY_DYN_DATA, false);
98       _localMachine = new RegistryKey(HKEY_LOCAL_MACHINE, false);
99       _performanceData = new RegistryKey(HKEY_PERFORMANCE_DATA, false);
100       _users = new RegistryKey(HKEY_USERS, false);
101    }
102    +/
103 }
104 
105 private enum uint MAX_REG_BUFFER = 256;
106 
107 abstract class RegistryValue {
108    @property DWORD valueType();
109    override Dstring toString();
110    /+ package +/
111    protected LONG save(HKEY hkey, Dstring name); // package
112    package final @property RegistryValue _reg() {
113       return this;
114    }
115 }
116 
117 class RegistryValueSz : RegistryValue {
118 
119    Dstring value;
120 
121    this(Dstring str) {
122       this.value = str;
123    }
124 
125    this() {
126    }
127 
128    override @property DWORD valueType() {
129       return REG_SZ;
130    }
131 
132    override Dstring toString() {
133       return value;
134    }
135 
136    /+ package +/
137    protected override LONG save(HKEY hkey, Dstring name) { // package
138       auto valuez = unsafeStringz(value);
139       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_SZ,
140          cast(BYTE*) valuez, value.length + 1);
141    }
142 }
143 
144 /+
145 // Extra.
146 
147 class RegistryValueSzW: RegistryValue {
148 
149    wDstring value;
150 
151 
152 
153    this(wDstring str) {
154       this.value = str;
155    }
156 
157 
158    this() {
159    }
160 
161 
162    override @property DWORD valueType() {
163       return REG_SZ;
164    }
165 
166 
167    override Dstring toString() {
168       return utf16stringtoUtf8string(value);
169    }
170 
171 
172    /+ package +/ protected override LONG save(HKEY hkey, Dstring name) { // package
173       if(dfl.internal.utf.useUnicode) {
174 
175       } else {
176 
177       }
178    }
179 }
180 +/
181 
182 class RegistryValueMultiSz : RegistryValue {
183 
184    Dstring[] value;
185 
186    this(Dstring[] strs) {
187       this.value = strs;
188    }
189 
190    this() {
191    }
192 
193    override @property DWORD valueType() {
194       return REG_MULTI_SZ;
195    }
196 
197    override Dstring toString() {
198       Dstring result;
199       foreach (Dstring str; value) {
200          result ~= str ~ "\r\n";
201       }
202       if (result.length) {
203          result = result[0 .. result.length - 2]; // Exclude last \r\n.
204       }
205       return result;
206    }
207 
208    /+ package +/
209    protected override LONG save(HKEY hkey, Dstring name) { // package
210       char[] multi;
211       uint i;
212 
213       i = value.length + 1; // Each NUL and the extra terminating NUL.
214       foreach (Dstring s; value) {
215          i += s.length;
216       }
217 
218       multi = new char[i];
219       foreach (Dstring s; value) {
220          if (!s.length) {
221             throw new DflRegistryException(
222                "Empty strings are not allowed in multi_sz registry values");
223          }
224 
225          multi[i .. i + s.length] = s[];
226          i += s.length;
227          multi[i++] = 0;
228       }
229       multi[i++] = 0;
230       assert(i == multi.length);
231 
232       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_MULTI_SZ,
233          cast(BYTE*) multi, multi.length);
234    }
235 }
236 
237 class RegistryValueExpandSz : RegistryValue {
238 
239    Dstring value;
240 
241    this(Dstring str) {
242       this.value = str;
243    }
244 
245    this() {
246    }
247 
248    override @property DWORD valueType() {
249       return REG_EXPAND_SZ;
250    }
251 
252    override Dstring toString() {
253       return value;
254    }
255 
256    /+ package +/
257    protected override LONG save(HKEY hkey, Dstring name) { // package
258       auto valuez = unsafeStringz(value);
259       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_EXPAND_SZ,
260          cast(BYTE*) valuez, value.length + 1);
261    }
262 }
263 
264 private Dstring dwordToString(DWORD dw)
265 out (result) {
266    assert(result.length == 10);
267    assert(result[0 .. 2] == "0x");
268    foreach (char ch; result[2 .. result.length]) {
269       assert(charIsHexDigit(ch));
270    }
271 }
272 body {
273    char[] result;
274    Dstring stmp;
275    uint ntmp;
276 
277    stmp = uintToHexString(dw);
278    assert(stmp.length <= 8);
279    ntmp = 8 - stmp.length + 2; // Plus 0x.
280    result = new char[ntmp + stmp.length];
281    result[0 .. 2] = "0x";
282    result[2 .. ntmp] = '0';
283    result[ntmp .. result.length] = stmp[];
284 
285    //return result;
286    return cast(Dstring) result; // Needed in D2.
287 }
288 
289 unittest {
290    assert(dwordToString(0x8934) == "0x00008934");
291    assert(dwordToString(0xF00BA2) == "0x00F00BA2");
292    assert(dwordToString(0xBADBEEF0) == "0xBADBEEF0");
293    assert(dwordToString(0xCAFEBEEF) == "0xCAFEBEEF");
294    assert(dwordToString(0x09090BB) == "0x009090BB");
295    assert(dwordToString(0) == "0x00000000");
296 }
297 
298 class RegistryValueDword : RegistryValue {
299 
300    DWORD value;
301 
302    this(DWORD dw) {
303       this.value = dw;
304    }
305 
306    this() {
307    }
308 
309    override @property DWORD valueType() {
310       return REG_DWORD;
311    }
312 
313    override Dstring toString() {
314       return dwordToString(value);
315    }
316 
317    /+ package +/
318    protected override LONG save(HKEY hkey, Dstring name) { // package
319       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_DWORD,
320          cast(BYTE*)&value, DWORD.sizeof);
321    }
322 }
323 
324 alias RegistryValueDwordLittleEndian = RegistryValueDword;
325 
326 class RegistryValueDwordBigEndian : RegistryValue {
327 
328    DWORD value;
329 
330    this(DWORD dw) {
331       this.value = dw;
332    }
333 
334    this() {
335    }
336 
337    override @property DWORD valueType() {
338       return REG_DWORD_BIG_ENDIAN;
339    }
340 
341    override Dstring toString() {
342       return dwordToString(value);
343    }
344 
345    /+ package +/
346    protected override LONG save(HKEY hkey, Dstring name) { // package
347       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_DWORD_BIG_ENDIAN,
348          cast(BYTE*)&value, DWORD.sizeof);
349    }
350 }
351 
352 class RegistryValueBinary : RegistryValue {
353 
354    void[] value;
355 
356    this(void[] val) {
357       this.value = val;
358    }
359 
360    this() {
361    }
362 
363    override @property DWORD valueType() {
364       return REG_BINARY;
365    }
366 
367    override Dstring toString() {
368       return "Binary";
369    }
370 
371    /+ package +/
372    protected override LONG save(HKEY hkey, Dstring name) { // package
373       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_BINARY,
374          cast(BYTE*) value, value.length);
375    }
376 }
377 
378 class RegistryValueLink : RegistryValue {
379 
380    void[] value;
381 
382    this(void[] val) {
383       this.value = val;
384    }
385 
386    this() {
387    }
388 
389    override @property DWORD valueType() {
390       return REG_LINK;
391    }
392 
393    override Dstring toString() {
394       return "Symbolic Link";
395    }
396 
397    /+ package +/
398    protected override LONG save(HKEY hkey, Dstring name) { // package
399       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_LINK,
400          cast(BYTE*) value, value.length);
401    }
402 }
403 
404 class RegistryValueResourceList : RegistryValue {
405 
406    void[] value;
407 
408    this(void[] val) {
409       this.value = val;
410    }
411 
412    this() {
413    }
414 
415    override @property DWORD valueType() {
416       return REG_RESOURCE_LIST;
417    }
418 
419    override Dstring toString() {
420       return "Resource List";
421    }
422 
423    /+ package +/
424    protected override LONG save(HKEY hkey, Dstring name) { // package
425       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_RESOURCE_LIST,
426          cast(BYTE*) value, value.length);
427    }
428 }
429 
430 class RegistryValueNone : RegistryValue {
431 
432    void[] value;
433 
434    this(void[] val) {
435       this.value = val;
436    }
437 
438    this() {
439    }
440 
441    override @property DWORD valueType() {
442       return REG_NONE;
443    }
444 
445    override Dstring toString() {
446       return "None";
447    }
448 
449    /+ package +/
450    protected override LONG save(HKEY hkey, Dstring name) { // package
451       return RegSetValueExA(hkey, unsafeStringz(name), 0, REG_NONE,
452          cast(BYTE*) value, value.length);
453    }
454 }
455 
456 enum RegistryHive : size_t {
457    /+
458    // DMD 0.98:
459    // C:\dmd\bin\..\src\phobos\std\c\windows\windows.d(493): cast(HKEY)(2147483648) is not an expression
460    // ...
461    CLASSES_ROOT = cast(size_t)HKEY_CLASSES_ROOT,
462    CURRENT_CONFIG = cast(size_t)HKEY_CURRENT_CONFIG,
463    CURRENT_USER = cast(size_t)HKEY_CURRENT_USER,
464    DYN_DATA = cast(size_t)HKEY_DYN_DATA,
465    LOCAL_MACHINE = cast(size_t)HKEY_LOCAL_MACHINE,
466    PERFORMANCE_DATA = cast(size_t)HKEY_PERFORMANCE_DATA,
467    USERS = cast(size_t)HKEY_USERS,
468    +/
469 
470    CLASSES_ROOT = 0x80000000,
471    CURRENT_CONFIG = 0x80000005,
472    CURRENT_USER = 0x80000001,
473    DYN_DATA = 0x80000006,
474    LOCAL_MACHINE = 0x80000002,
475    PERFORMANCE_DATA = 0x80000004,
476    USERS = 0x80000003,
477 }
478 
479 class RegistryKey {
480 private:
481    HKEY hkey;
482    bool owned = true;
483 
484 public:
485 final:
486    /+
487    // An absolute key path.
488    // This doesn't work.
489    final @property Dstring name() {
490       Dstring buf;
491       DWORD buflen;
492 
493       buf = new char[MAX_REG_BUFFER];
494       buflen = buf.length;
495       if(ERROR_SUCCESS != RegQueryInfoKeyA(hkey, buf, &buflen, null, null,
496                                            null, null, null, null, null, null, null)) {
497          infoErr();
498       }
499 
500       return buf[0 .. buflen];
501    }
502    +/
503 
504    final @property int subKeyCount() {
505       DWORD count;
506 
507       LONG rr = RegQueryInfoKeyA(hkey, null, null, null, &count, null, null,
508          null, null, null, null, null);
509       if (ERROR_SUCCESS != rr) {
510          infoErr(rr);
511       }
512 
513       return count;
514    }
515 
516    final @property int valueCount() {
517       DWORD count;
518 
519       LONG rr = RegQueryInfoKeyA(hkey, null, null, null, null, null, null,
520          &count, null, null, null, null);
521       if (ERROR_SUCCESS != rr) {
522          infoErr(rr);
523       }
524 
525       return count;
526    }
527 
528    final void close() {
529       //if(!owned)
530       RegCloseKey(hkey);
531    }
532 
533    final RegistryKey createSubKey(Dstring name) {
534       HKEY newHkey;
535       DWORD cdisp;
536 
537       LONG rr = RegCreateKeyExA(hkey, unsafeStringz(name), 0, null, 0,
538          KEY_ALL_ACCESS, null, &newHkey, &cdisp);
539       if (ERROR_SUCCESS != rr) {
540          throw new DflRegistryException("Unable to create registry key", rr);
541       }
542 
543       return new RegistryKey(newHkey);
544    }
545 
546    final void deleteSubKey(Dstring name, bool throwIfMissing) {
547       HKEY openHkey;
548 
549       if (!name.length || !name[0]) {
550          throw new DflRegistryException("Unable to delete subkey");
551       }
552 
553       auto namez = unsafeStringz(name);
554 
555       LONG opencode = RegOpenKeyExA(hkey, namez, 0, KEY_ALL_ACCESS, &openHkey);
556       if (ERROR_SUCCESS == opencode) {
557          DWORD count;
558 
559          LONG querycode = RegQueryInfoKeyA(openHkey, null, null, null, &count,
560             null, null, null, null, null, null, null);
561          if (ERROR_SUCCESS == querycode) {
562             RegCloseKey(openHkey);
563 
564             LONG delcode;
565             if (!count) {
566                delcode = RegDeleteKeyA(hkey, namez);
567                if (ERROR_SUCCESS == delcode) {
568                   return; // OK.
569                }
570 
571                throw new DflRegistryException("Unable to delete subkey", delcode);
572             }
573 
574             throw new DflRegistryException("Cannot delete registry key with subkeys");
575          }
576 
577          RegCloseKey(openHkey);
578 
579          throw new DflRegistryException("Unable to delete registry key", querycode);
580       } else {
581          if (!throwIfMissing) {
582             switch (opencode) {
583             case ERROR_FILE_NOT_FOUND:
584                return;
585 
586             default:
587             }
588          }
589 
590          throw new DflRegistryException("Unable to delete registry key", opencode);
591       }
592    }
593 
594    final void deleteSubKey(Dstring name) {
595       deleteSubKey(name, true);
596    }
597 
598    final void deleteSubKeyTree(Dstring name) {
599       _deleteSubKeyTree(hkey, name);
600    }
601 
602    // Note: name is not written to! it's just not "invariant".
603    private static void _deleteSubKeyTree(HKEY shkey, Dstring name) {
604       HKEY openHkey;
605 
606       auto namez = unsafeStringz(name);
607 
608       if (ERROR_SUCCESS == RegOpenKeyExA(shkey, namez, 0, KEY_ALL_ACCESS, &openHkey)) {
609          void ouch(LONG why = 0) {
610             throw new DflRegistryException("Unable to delete entire subkey tree", why);
611          }
612 
613          DWORD count;
614 
615          LONG querycode = RegQueryInfoKeyA(openHkey, null, null, null, &count,
616             null, null, null, null, null, null, null);
617          if (ERROR_SUCCESS == querycode) {
618             if (!count) {
619             del_me:
620                RegCloseKey(openHkey);
621                LONG delcode = RegDeleteKeyA(shkey, namez);
622                if (ERROR_SUCCESS == delcode) {
623                   return; // OK.
624                }
625 
626                ouch(delcode);
627             } else {
628                try {
629                   // deleteSubKeyTree on all subkeys.
630 
631                   char[MAX_REG_BUFFER] skn;
632                   DWORD len;
633 
634                next_subkey:
635                   len = skn.length;
636                   LONG enumcode = RegEnumKeyExA(openHkey, 0, skn.ptr, &len,
637                      null, null, null, null);
638                   switch (enumcode) {
639                   case ERROR_SUCCESS:
640                      //_deleteSubKeyTree(openHkey, skn[0 .. len]);
641                      _deleteSubKeyTree(openHkey, cast(Dstring) skn[0 .. len]); // Needed in D2. WARNING: NOT REALLY INVARIANT.
642                      goto next_subkey;
643 
644                   case ERROR_NO_MORE_ITEMS:
645                      // Done!
646                      break;
647 
648                   default:
649                      ouch(enumcode);
650                   }
651 
652                   // Now go back to delete the origional key.
653                   goto del_me;
654                }
655                finally {
656                   RegCloseKey(openHkey);
657                }
658             }
659          } else {
660             ouch(querycode);
661          }
662       }
663    }
664 
665    final void deleteValue(Dstring name, bool throwIfMissing) {
666       LONG rr = RegDeleteValueA(hkey, unsafeStringz(name));
667       switch (rr) {
668       case ERROR_SUCCESS:
669          break;
670 
671       case ERROR_FILE_NOT_FOUND:
672          if (!throwIfMissing) {
673             break;
674          }
675          goto default;
676       default:
677          throw new DflRegistryException("Unable to delete registry value", rr);
678       }
679    }
680 
681    final void deleteValue(Dstring name) {
682       deleteValue(name, true);
683    }
684 
685    override Dequ opEquals(Object o) {
686       RegistryKey rk;
687 
688       rk = cast(RegistryKey) o;
689       if (!rk) {
690          return false;
691       }
692       return opEquals(rk);
693    }
694 
695    Dequ opEquals(RegistryKey rk) {
696       return hkey == rk.hkey;
697    }
698 
699    final void flush() {
700       RegFlushKey(hkey);
701    }
702 
703    final Dstring[] getSubKeyNames() {
704       char[MAX_REG_BUFFER] buf;
705       DWORD len;
706       DWORD idx;
707       Dstring[] result;
708 
709       key_names: for (idx = 0;; idx++) {
710          len = buf.length;
711          LONG rr = RegEnumKeyExA(hkey, idx, buf.ptr, &len, null, null, null, null);
712          switch (rr) {
713          case ERROR_SUCCESS:
714             //result ~= buf[0 .. len].dup;
715             //result ~= buf[0 .. len].idup; // Needed in D2. Doesn't work in D1.
716             result ~= cast(Dstring) buf[0 .. len].dup; // Needed in D2.
717             break;
718 
719          case ERROR_NO_MORE_ITEMS:
720             // Done!
721             break key_names;
722 
723          default:
724             throw new DflRegistryException("Unable to obtain subkey names", rr);
725          }
726       }
727 
728       return result;
729    }
730 
731    final RegistryValue getValue(Dstring name, RegistryValue defaultValue) {
732       DWORD type;
733       DWORD len;
734       ubyte[] data;
735 
736       len = 0;
737       LONG querycode = RegQueryValueExA(hkey, unsafeStringz(name), null, &type, null,
738          &len);
739       switch (querycode) {
740       case ERROR_SUCCESS:
741          // Good.
742          break;
743 
744       case ERROR_FILE_NOT_FOUND:
745          // Value doesn't exist.
746          return defaultValue;
747 
748       default:
749       errquerycode:
750          throw new DflRegistryException("Unable to get registry value", querycode);
751       }
752 
753       data = new ubyte[len];
754       // Note: reusing querycode here and above.
755       querycode = RegQueryValueExA(hkey, unsafeStringz(name), null, &type, data.ptr,
756          &len);
757       if (ERROR_SUCCESS != querycode) {
758          goto errquerycode;
759       }
760 
761       switch (type) {
762       case REG_SZ:
763          with (new RegistryValueSz) {
764             assert(!data[data.length - 1]);
765             value = cast(Dstring) data[0 .. data.length - 1];
766             defaultValue = _reg;
767          }
768          break;
769 
770       case REG_DWORD: // REG_DWORD_LITTLE_ENDIAN
771          with (new RegistryValueDword) {
772             assert(data.length == DWORD.sizeof);
773             value = *(cast(DWORD*) cast(void*) data);
774             defaultValue = _reg;
775          }
776          break;
777 
778       case REG_EXPAND_SZ:
779          with (new RegistryValueExpandSz) {
780             assert(!data[data.length - 1]);
781             value = cast(Dstring) data[0 .. data.length - 1];
782             defaultValue = _reg;
783          }
784          break;
785 
786       case REG_MULTI_SZ:
787          with (new RegistryValueMultiSz) {
788             Dstring s;
789 
790       next_sz:
791             s = stringFromStringz(cast(char*) data);
792             if (s.length) {
793                value ~= s;
794                data = data[s.length + 1 .. data.length];
795                goto next_sz;
796             }
797 
798             defaultValue = _reg;
799          }
800          break;
801 
802       case REG_BINARY:
803          with (new RegistryValueBinary) {
804             value = data;
805             defaultValue = _reg;
806          }
807          break;
808 
809       case REG_DWORD_BIG_ENDIAN:
810          with (new RegistryValueDwordBigEndian) {
811             assert(data.length == DWORD.sizeof);
812             value = *(cast(DWORD*) cast(void*) data);
813             defaultValue = _reg;
814          }
815          break;
816 
817       case REG_LINK:
818          with (new RegistryValueLink) {
819             value = data;
820             defaultValue = _reg;
821          }
822          break;
823 
824       case REG_RESOURCE_LIST:
825          with (new RegistryValueResourceList) {
826             value = data;
827             defaultValue = _reg;
828          }
829          break;
830 
831       case REG_NONE:
832          with (new RegistryValueNone) {
833             value = data;
834             defaultValue = _reg;
835          }
836          break;
837 
838       default:
839          throw new DflRegistryException("Unknown type for registry value");
840       }
841 
842       return defaultValue;
843    }
844 
845    final RegistryValue getValue(Dstring name) {
846       return getValue(name, null);
847    }
848 
849    final Dstring[] getValueNames() {
850       char[MAX_REG_BUFFER] buf;
851       DWORD len;
852       DWORD idx;
853       Dstring[] result;
854 
855       value_names: for (idx = 0;; idx++) {
856          len = buf.length;
857          LONG rr = RegEnumValueA(hkey, idx, buf.ptr, &len, null, null, null, null);
858          switch (rr) {
859          case ERROR_SUCCESS:
860             //result ~= buf[0 .. len].dup;
861             //result ~= buf[0 .. len].idup; // Needed in D2. Doesn't work in D1.
862             result ~= cast(Dstring) buf[0 .. len].dup; // Needed in D2.
863             break;
864 
865          case ERROR_NO_MORE_ITEMS:
866             // Done!
867             break value_names;
868 
869          default:
870             throw new DflRegistryException("Unable to obtain value names", rr);
871          }
872       }
873 
874       return result;
875    }
876 
877    static RegistryKey openRemoteBaseKey(RegistryHive hhive, Dstring machineName) {
878       HKEY openHkey;
879 
880       LONG rr = RegConnectRegistryA(unsafeStringz(machineName), cast(HKEY) hhive, &openHkey);
881       if (ERROR_SUCCESS != rr) {
882          throw new DflRegistryException("Unable to open remote base key", rr);
883       }
884 
885       return new RegistryKey(openHkey);
886    }
887 
888    // Returns null on error.
889    final RegistryKey openSubKey(Dstring name, bool writeAccess) {
890       HKEY openHkey;
891 
892       if (ERROR_SUCCESS != RegOpenKeyExA(hkey, unsafeStringz(name), 0,
893             writeAccess ? KEY_READ | KEY_WRITE : KEY_READ, &openHkey)) {
894          return null;
895       }
896 
897       return new RegistryKey(openHkey);
898    }
899 
900    final RegistryKey openSubKey(Dstring name) {
901       return openSubKey(name, false);
902    }
903 
904    final void setValue(Dstring name, RegistryValue value) {
905       LONG rr = value.save(hkey, name);
906       if (ERROR_SUCCESS != rr) {
907          throw new DflRegistryException("Unable to set registry value", rr);
908       }
909    }
910 
911    // Shortcut.
912    final void setValue(Dstring name, Dstring value) {
913       scope rv = new RegistryValueSz(value);
914       setValue(name, rv);
915    }
916 
917    // Shortcut.
918    final void setValue(Dstring name, Dstring[] value) {
919       scope rv = new RegistryValueMultiSz(value);
920       setValue(name, rv);
921    }
922 
923    // Shortcut.
924    final void setValue(Dstring name, DWORD value) {
925       scope rv = new RegistryValueDword(value);
926       setValue(name, rv);
927    }
928 
929    // Used internally.
930    final @property HKEY handle() {
931       return hkey;
932    }
933 
934    // Used internally.
935    this(HKEY hkey, bool owned = true) {
936       this.hkey = hkey;
937       this.owned = owned;
938    }
939 
940    ~this() {
941       if (owned) {
942          RegCloseKey(hkey);
943       }
944    }
945 
946    private void infoErr(LONG why) {
947       throw new DflRegistryException("Unable to obtain registry information", why);
948    }
949 }