a39ead72a1f245ec086322dbad3406a4f8c96229
[mono.git] / mcs / class / corlib / Microsoft.Win32 / Win32RegistryApi.cs
1 //
2 // Microsoft.Win32/Win32RegistryApi.cs: wrapper for win32 registry API
3 //
4 // Authos:
5 //      Erik LeBel (eriklebel@yahoo.ca)
6 //      Jackson Harper (jackson@ximian.com)
7 //      Miguel de Icaza (miguel@gnome.org)
8 //
9 // Copyright (C) Erik LeBel 2004
10 // (C) 2004, 2005 Novell, Inc (http://www.novell.com)
11 // 
12
13 //
14 // Copyright (C) 2004, 2005 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.Runtime.InteropServices;
39 using System.Security;
40 using System.Text;
41
42 namespace Microsoft.Win32
43 {
44         /// <summary>
45         ///     Function stubs, constants and helper functions for
46         ///     the Win32 registry manipulation utilities.
47         /// </summary>
48         internal class Win32RegistryApi : IRegistryApi
49         {
50                 // bit masks for registry key open access permissions
51                 const int OpenRegKeyRead = 0x00020019; 
52                 const int OpenRegKeyWrite = 0x00020006; 
53
54                 // FIXME must be a way to determin this dynamically?
55                 const int Int32ByteSize = 4;
56
57                 // FIXME this is hard coded on Mono, can it be determined dynamically? 
58                 readonly int NativeBytesPerCharacter = Marshal.SystemDefaultCharSize;
59
60                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegCreateKey")]
61                 static extern int RegCreateKey (IntPtr keyBase, string keyName, out IntPtr keyHandle);
62                
63                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegCloseKey")]
64                 static extern int RegCloseKey (IntPtr keyHandle);
65
66                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegFlushKey")]
67                 private static extern int RegFlushKey (IntPtr keyHandle);
68
69                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegOpenKeyEx")]
70                 private static extern int RegOpenKeyEx (IntPtr keyBase,
71                                 string keyName, IntPtr reserved, int access,
72                                 out IntPtr keyHandle);
73
74                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegDeleteKey")]
75                 private static extern int RegDeleteKey (IntPtr keyHandle, string valueName);
76
77                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegDeleteValue")]
78                 private static extern int RegDeleteValue (IntPtr keyHandle, string valueName);
79
80                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegEnumKey")]
81                 private static extern int RegEnumKey (IntPtr keyBase, int index, [Out] byte[] nameBuffer, int bufferLength);
82
83                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegEnumValue")]
84                 private static extern int RegEnumValue (IntPtr keyBase, 
85                                 int index, StringBuilder nameBuffer, 
86                                 ref int nameLength, IntPtr reserved, 
87                                 ref RegistryValueKind type, IntPtr data, IntPtr dataLength);
88
89                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
90                 private static extern int RegSetValueEx (IntPtr keyBase, 
91                                 string valueName, IntPtr reserved, RegistryValueKind type,
92                                 StringBuilder data, int rawDataLength);
93
94                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
95                 private static extern int RegSetValueEx (IntPtr keyBase, 
96                                 string valueName, IntPtr reserved, RegistryValueKind type,
97                                 string data, int rawDataLength);
98
99                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
100                 private static extern int RegSetValueEx (IntPtr keyBase, 
101                                 string valueName, IntPtr reserved, RegistryValueKind type,
102                                 byte[] rawData, int rawDataLength);
103
104                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegSetValueEx")]
105                 private static extern int RegSetValueEx (IntPtr keyBase, 
106                                 string valueName, IntPtr reserved, RegistryValueKind type,
107                                 ref int data, int rawDataLength);
108
109                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
110                 private static extern int RegQueryValueEx (IntPtr keyBase,
111                                 string valueName, IntPtr reserved, ref RegistryValueKind type,
112                                 IntPtr zero, ref int dataSize);
113
114                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
115                 private static extern int RegQueryValueEx (IntPtr keyBase,
116                                 string valueName, IntPtr reserved, ref RegistryValueKind type,
117                                 [Out] byte[] data, ref int dataSize);
118
119                 [DllImport ("advapi32.dll", CharSet=CharSet.Unicode, EntryPoint="RegQueryValueEx")]
120                 private static extern int RegQueryValueEx (IntPtr keyBase,
121                                 string valueName, IntPtr reserved, ref RegistryValueKind type,
122                                 ref int data, ref int dataSize);
123
124                 // Returns our handle from the RegistryKey
125                 static IntPtr GetHandle (RegistryKey key)
126                 {
127                         return key.IsRoot ? new IntPtr ((int) key.Data)
128                                 : (IntPtr) key.Data;
129                 }
130
131                 static bool IsHandleValid (RegistryKey key)
132                 {
133                         return key.Data != null;
134                 }
135
136                 /// <summary>
137                 /// Acctually read a registry value. Requires knowledge of the
138                 /// value's type and size.
139                 /// </summary>
140                 public object GetValue (RegistryKey rkey, string name, object defaultValue, RegistryValueOptions options)
141                 {
142                         RegistryValueKind type = 0;
143                         int size = 0;
144                         object obj = null;
145                         IntPtr handle = GetHandle (rkey);
146                         int result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, IntPtr.Zero, ref size);
147
148                         if (result == Win32ResultCode.FileNotFound || result == Win32ResultCode.MarkedForDeletion) {
149                                 return defaultValue;
150                         }
151                         
152                         if (result != Win32ResultCode.MoreData && result != Win32ResultCode.Success ) {
153                                 GenerateException (result);
154                         }
155                         
156                         if (type == RegistryValueKind.String) {
157                                 byte[] data;
158                                 result = GetBinaryValue (rkey, name, type, out data, size);
159                                 obj = RegistryKey.DecodeString (data);
160                         } else if (type == RegistryValueKind.ExpandString) {
161                                 byte [] data;
162                                 result = GetBinaryValue (rkey, name, type, out data, size);
163                                 obj = RegistryKey.DecodeString (data);
164                                 if ((options & RegistryValueOptions.DoNotExpandEnvironmentNames) == 0)
165                                         obj = Environment.ExpandEnvironmentVariables ((string) obj);
166                         } else if (type == RegistryValueKind.DWord) {
167                                 int data = 0;
168                                 result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, ref data, ref size);
169                                 obj = data;
170                         } else if (type == RegistryValueKind.Binary) {
171                                 byte[] data;
172                                 result = GetBinaryValue (rkey, name, type, out data, size);
173                                 obj = data;
174                         } else if (type == RegistryValueKind.MultiString) {
175                                 obj = null;
176                                 byte[] data;
177                                 result = GetBinaryValue (rkey, name, type, out data, size);
178                                 
179                                 if (result == Win32ResultCode.Success)
180                                         obj = RegistryKey.DecodeString (data).Split ('\0');
181                         } else {
182                                 // should never get here
183                                 throw new SystemException ();
184                         }
185
186                         // check result codes again:
187                         if (result != Win32ResultCode.Success)
188                         {
189                                 GenerateException (result);
190                         }
191                         
192
193                         return obj;
194                 }
195
196 #if NET_2_0
197                 //
198                 // This version has to do extra checking, make sure that the requested
199                 // valueKind matches the type of the value being stored
200                 //
201                 public void SetValue (RegistryKey rkey, string name, object value, RegistryValueKind valueKind)
202                 {
203                         Type type = value.GetType ();
204                         int result;
205                         IntPtr handle = GetHandle (rkey);
206
207                         if (valueKind == RegistryValueKind.DWord && type == typeof (int)) {
208                                 int rawValue = (int)value;
209                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.DWord, ref rawValue, Int32ByteSize); 
210                         } else if (valueKind == RegistryValueKind.Binary && type == typeof (byte[])) {
211                                 byte[] rawValue = (byte[]) value;
212                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.Binary, rawValue, rawValue.Length);
213                         } else if (valueKind == RegistryValueKind.MultiString && type == typeof (string[])) {
214                                 string[] vals = (string[]) value;
215                                 StringBuilder fullStringValue = new StringBuilder ();
216                                 foreach (string v in vals)
217                                 {
218                                         fullStringValue.Append (v);
219                                         fullStringValue.Append ('\0');
220                                 }
221                                 fullStringValue.Append ('\0');
222
223                                 byte[] rawValue = Encoding.Unicode.GetBytes (fullStringValue.ToString ());
224                         
225                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.MultiString, rawValue, rawValue.Length); 
226                         } else if ((valueKind == RegistryValueKind.String || valueKind == RegistryValueKind.ExpandString) &&
227                                    type == typeof (string)){
228                                 string rawValue = String.Format ("{0}{1}", value, '\0');
229                                 result = RegSetValueEx (handle, name, IntPtr.Zero, valueKind, rawValue,
230                                                         rawValue.Length * NativeBytesPerCharacter);
231                                 
232                         } else if (type.IsArray) {
233                                 throw new ArgumentException ("Only string and byte arrays can written as registry values");
234                         } else {
235                                 throw new ArgumentException ("Type does not match the valueKind");
236                         }
237
238                         // handle the result codes
239                         if (result != Win32ResultCode.Success)
240                         {
241                                 GenerateException (result);
242                         }
243                 }
244 #endif
245         
246                 public void SetValue (RegistryKey rkey, string name, object value)
247                 {
248                         Type type = value.GetType ();
249                         int result;
250                         IntPtr handle = GetHandle (rkey);
251
252                         if (type == typeof (int)) {
253                                 int rawValue = (int)value;
254                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.DWord, ref rawValue, Int32ByteSize); 
255                         } else if (type == typeof (byte[])) {
256                                 byte[] rawValue = (byte[]) value;
257                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.Binary, rawValue, rawValue.Length);
258                         } else if (type == typeof (string[])) {
259                                 string[] vals = (string[]) value;
260                                 StringBuilder fullStringValue = new StringBuilder ();
261                                 foreach (string v in vals)
262                                 {
263                                         fullStringValue.Append (v);
264                                         fullStringValue.Append ('\0');
265                                 }
266                                 fullStringValue.Append ('\0');
267
268                                 byte[] rawValue = Encoding.Unicode.GetBytes (fullStringValue.ToString ());
269                         
270                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.MultiString, rawValue, rawValue.Length); 
271                         } else if (type.IsArray) {
272                                 throw new ArgumentException ("Only string and byte arrays can written as registry values");
273                         } else {
274                                 string rawValue = String.Format ("{0}{1}", value, '\0');
275                                 result = RegSetValueEx (handle, name, IntPtr.Zero, RegistryValueKind.String, rawValue,
276                                                         rawValue.Length * NativeBytesPerCharacter);
277                         }
278
279                         if (result == Win32ResultCode.MarkedForDeletion)
280                                 throw RegistryKey.CreateMarkedForDeletionException ();
281
282                         // handle the result codes
283                         if (result != Win32ResultCode.Success)
284                         {
285                                 GenerateException (result);
286                         }
287                 }
288
289                 /// <summary>
290                 ///     Get a binary value.
291                 /// </summary>
292                 private int GetBinaryValue (RegistryKey rkey, string name, RegistryValueKind type, out byte[] data, int size)
293                 {
294                         byte[] internalData = new byte [size];
295                         IntPtr handle = GetHandle (rkey);
296                         int result = RegQueryValueEx (handle, name, IntPtr.Zero, ref type, internalData, ref size);
297                         data = internalData;
298                         return result;
299                 }
300
301                 
302                 // Arbitrary max size for key/values names that can be fetched.
303                 // .NET framework SDK docs say that the max name length that can 
304                 // be used is 255 characters, we'll allow for a bit more.
305                 const int BufferMaxLength = 1024;
306                 
307                 public int SubKeyCount (RegistryKey rkey)
308                 {
309                         int index, result;
310                         byte[] stringBuffer = new byte [BufferMaxLength];
311                         IntPtr handle = GetHandle (rkey);
312                         
313                         for (index = 0; true; index ++) {
314                                 result = RegEnumKey (handle, index, stringBuffer, BufferMaxLength);
315
316                                 if (result == Win32ResultCode.MarkedForDeletion)
317                                         throw RegistryKey.CreateMarkedForDeletionException ();
318
319                                 if (result == Win32ResultCode.Success)
320                                         continue;
321                                 
322                                 if (result == Win32ResultCode.NoMoreEntries)
323                                         break;
324                                 
325                                 // something is wrong!!
326                                 GenerateException (result);
327                         }
328                         return index;
329                 }
330
331                 public int ValueCount (RegistryKey rkey)
332                 {
333                         int index, result, bufferCapacity;
334                         RegistryValueKind type;
335                         StringBuilder buffer = new StringBuilder (BufferMaxLength);
336                         
337                         IntPtr handle = GetHandle (rkey);
338                         for (index = 0; true; index ++) {
339                                 type = 0;
340                                 bufferCapacity = buffer.Capacity;
341                                 result = RegEnumValue (handle, index, 
342                                                        buffer, ref bufferCapacity,
343                                                        IntPtr.Zero, ref type, 
344                                                        IntPtr.Zero, IntPtr.Zero);
345
346                                 if (result == Win32ResultCode.MarkedForDeletion)
347                                         throw RegistryKey.CreateMarkedForDeletionException ();
348
349                                 if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData)
350                                         continue;
351                                 
352                                 if (result == Win32ResultCode.NoMoreEntries)
353                                         break;
354                                 
355                                 // something is wrong
356                                 GenerateException (result);
357                         }
358                         return index;
359                 }
360                 
361                 public RegistryKey OpenSubKey (RegistryKey rkey, string keyName, bool writable)
362                 {
363                         int access = OpenRegKeyRead;
364                         if (writable) access |= OpenRegKeyWrite;
365                         IntPtr handle = GetHandle (rkey);
366                         
367                         IntPtr subKeyHandle;
368                         int result = RegOpenKeyEx (handle, keyName, IntPtr.Zero, access, out subKeyHandle);
369
370                         if (result == Win32ResultCode.FileNotFound || result == Win32ResultCode.MarkedForDeletion)
371                                 return null;
372                         
373                         if (result != Win32ResultCode.Success)
374                                 GenerateException (result);
375                         
376                         return new RegistryKey (subKeyHandle, CombineName (rkey, keyName), writable);
377                 }
378
379                 public void Flush (RegistryKey rkey)
380                 {
381                         if (!IsHandleValid (rkey))
382                                 return;
383                         IntPtr handle = GetHandle (rkey);
384                         RegFlushKey (handle);
385                 }
386
387                 public void Close (RegistryKey rkey)
388                 {
389                         if (!IsHandleValid (rkey))
390                                 return;
391                         IntPtr handle = GetHandle (rkey);
392                         RegCloseKey (handle);
393                 }
394
395                 public RegistryKey CreateSubKey (RegistryKey rkey, string keyName)
396                 {
397                         IntPtr handle = GetHandle (rkey);
398                         IntPtr subKeyHandle;
399                         int result = RegCreateKey (handle , keyName, out subKeyHandle);
400
401                         if (result == Win32ResultCode.MarkedForDeletion)
402                                 throw RegistryKey.CreateMarkedForDeletionException ();
403
404                         if (result != Win32ResultCode.Success) {
405                                 GenerateException (result);
406                         }
407                         
408                         return new RegistryKey (subKeyHandle, CombineName (rkey, keyName),
409                                 true);
410                 }
411
412                 public void DeleteKey (RegistryKey rkey, string keyName, bool shouldThrowWhenKeyMissing)
413                 {
414                         IntPtr handle = GetHandle (rkey);
415                         int result = RegDeleteKey (handle, keyName);
416
417                         if (result == Win32ResultCode.FileNotFound) {
418                                 if (shouldThrowWhenKeyMissing)
419                                         throw new ArgumentException ("key " + keyName);
420                                 return;
421                         }
422                         
423                         if (result != Win32ResultCode.Success)
424                                 GenerateException (result);
425                 }
426
427                 public void DeleteValue (RegistryKey rkey, string value, bool shouldThrowWhenKeyMissing)
428                 {
429                         IntPtr handle = GetHandle (rkey);
430                         int result = RegDeleteValue (handle, value);
431
432                         if (result == Win32ResultCode.MarkedForDeletion)
433                                 return;
434
435                         if (result == Win32ResultCode.FileNotFound){
436                                 if (shouldThrowWhenKeyMissing)
437                                         throw new ArgumentException ("value " + value);
438                                 return;
439                         }
440                         
441                         if (result != Win32ResultCode.Success)
442                                 GenerateException (result);
443                 }
444
445                 public string [] GetSubKeyNames (RegistryKey rkey)
446                 {
447                         IntPtr handle = GetHandle (rkey);
448                         byte[] buffer = new byte [BufferMaxLength];
449                         int bufferCapacity = BufferMaxLength;
450                         ArrayList keys = new ArrayList ();
451                                 
452                         for (int index = 0; true; index ++) {
453                                 int result = RegEnumKey (handle, index, buffer, bufferCapacity);
454
455                                 if (result == Win32ResultCode.Success) {
456                                         keys.Add (RegistryKey.DecodeString (buffer));
457                                         continue;
458                                 }
459
460                                 if (result == Win32ResultCode.NoMoreEntries)
461                                         break;
462
463                                 // should not be here!
464                                 GenerateException (result);
465                         }
466                         return (string []) keys.ToArray (typeof(String));
467                 }
468
469
470                 public string [] GetValueNames (RegistryKey rkey)
471                 {
472                         IntPtr handle = GetHandle (rkey);
473                         ArrayList values = new ArrayList ();
474                         
475                         for (int index = 0; true; index ++)
476                         {
477                                 StringBuilder buffer = new StringBuilder (BufferMaxLength);
478                                 int bufferCapacity = buffer.Capacity;
479                                 RegistryValueKind type = 0;
480                                 
481                                 int result = RegEnumValue (handle, index, buffer, ref bufferCapacity,
482                                                         IntPtr.Zero, ref type, IntPtr.Zero, IntPtr.Zero);
483
484                                 if (result == Win32ResultCode.Success || result == Win32ResultCode.MoreData) {
485                                         values.Add (buffer.ToString ());
486                                         continue;
487                                 }
488                                 
489                                 if (result == Win32ResultCode.NoMoreEntries)
490                                         break;
491
492                                 if (result == Win32ResultCode.MarkedForDeletion)
493                                         throw RegistryKey.CreateMarkedForDeletionException ();
494
495                                 GenerateException (result);
496                         }
497
498                         return (string []) values.ToArray (typeof(String));
499                 }
500
501                 /// <summary>
502                 /// convert a win32 error code into an appropriate exception.
503                 /// </summary>
504                 private void GenerateException (int errorCode)
505                 {
506                         switch (errorCode) {
507                                 case Win32ResultCode.FileNotFound:
508                                 case Win32ResultCode.InvalidParameter:
509                                         throw new ArgumentException ();
510                                 
511                                 case Win32ResultCode.AccessDenied:
512                                         throw new SecurityException ();
513
514                                 default:
515                                         // unidentified system exception
516                                         throw new SystemException ();
517                         }
518                 }
519
520                 public string ToString (RegistryKey rkey)
521                 {
522                         IntPtr handle = GetHandle (rkey);
523                         
524                         return String.Format ("{0} [0x{1:X}]", rkey.Name, handle.ToInt32 ());
525                 }
526
527                 /// <summary>
528                 ///     utility: Combine the sub key name to the current name to produce a 
529                 ///     fully qualified sub key name.
530                 /// </summary>
531                 internal static string CombineName (RegistryKey rkey, string localName)
532                 {
533                         return String.Concat (rkey.Name, "\\", localName);
534                 }
535         }
536 }
537