Implemented a large number of the 32-bit setupapi functions.
[wine] / dlls / setupapi / install.c
1 /*
2  * Setupapi install routines
3  *
4  * Copyright 2002 Alexandre Julliard for CodeWeavers
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "windef.h"
22 #include "winbase.h"
23 #include "ntddk.h"
24 #include "winerror.h"
25 #include "setupapi.h"
26 #include "wine/unicode.h"
27 #include "setupapi_private.h"
28 #include "wine/debug.h"
29
30 WINE_DEFAULT_DEBUG_CHANNEL(setupapi);
31
32 /* info passed to callback functions dealing with files */
33 struct files_callback_info
34 {
35     HSPFILEQ queue;
36     PCWSTR   src_root;
37     UINT     copy_flags;
38     HINF     layout;
39 };
40
41 /* info passed to callback functions dealing with the registry */
42 struct registry_callback_info
43 {
44     HKEY default_root;
45     BOOL delete;
46 };
47
48 typedef BOOL (*iterate_fields_func)( HINF hinf, PCWSTR field, void *arg );
49
50 /* Unicode constants */
51 static const WCHAR CopyFiles[]  = {'C','o','p','y','F','i','l','e','s',0};
52 static const WCHAR DelFiles[]   = {'D','e','l','F','i','l','e','s',0};
53 static const WCHAR RenFiles[]   = {'R','e','n','F','i','l','e','s',0};
54 static const WCHAR Ini2Reg[]    = {'I','n','i','2','R','e','g',0};
55 static const WCHAR LogConf[]    = {'L','o','g','C','o','n','f',0};
56 static const WCHAR AddReg[]     = {'A','d','d','R','e','g',0};
57 static const WCHAR DelReg[]     = {'D','e','l','R','e','g',0};
58 static const WCHAR UpdateInis[] = {'U','p','d','a','t','e','I','n','i','s',0};
59 static const WCHAR UpdateIniFields[] = {'U','p','d','a','t','e','I','n','i','F','i','e','l','d','s',0};
60
61
62 /***********************************************************************
63  *            get_field_string
64  *
65  * Retrieve the contents of a field, dynamically growing the buffer if necessary.
66  */
67 static WCHAR *get_field_string( INFCONTEXT *context, DWORD index, WCHAR *buffer,
68                                 WCHAR *static_buffer, DWORD *size )
69 {
70     DWORD required;
71
72     if (SetupGetStringFieldW( context, index, buffer, *size, &required )) return buffer;
73     if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
74     {
75         /* now grow the buffer */
76         if (buffer != static_buffer) HeapFree( GetProcessHeap(), 0, buffer );
77         if (!(buffer = HeapAlloc( GetProcessHeap(), 0, required*sizeof(WCHAR) ))) return NULL;
78         *size = required;
79         if (SetupGetStringFieldW( context, index, buffer, *size, &required )) return buffer;
80     }
81     if (buffer != static_buffer) HeapFree( GetProcessHeap(), 0, buffer );
82     return NULL;
83 }
84
85
86 /***********************************************************************
87  *            copy_files_callback
88  *
89  * Called once for each CopyFiles entry in a given section.
90  */
91 static BOOL copy_files_callback( HINF hinf, PCWSTR field, void *arg )
92 {
93     struct files_callback_info *info = arg;
94
95     if (field[0] == '@')  /* special case: copy single file */
96         SetupQueueDefaultCopyW( info->queue, info->layout, info->src_root, NULL, field, info->copy_flags );
97     else
98         SetupQueueCopySectionW( info->queue, info->src_root, info->layout, hinf, field, info->copy_flags );
99     return TRUE;
100 }
101
102
103 /***********************************************************************
104  *            delete_files_callback
105  *
106  * Called once for each DelFiles entry in a given section.
107  */
108 static BOOL delete_files_callback( HINF hinf, PCWSTR field, void *arg )
109 {
110     struct files_callback_info *info = arg;
111     SetupQueueDeleteSectionW( info->queue, hinf, 0, field );
112     return TRUE;
113 }
114
115
116 /***********************************************************************
117  *            rename_files_callback
118  *
119  * Called once for each RenFiles entry in a given section.
120  */
121 static BOOL rename_files_callback( HINF hinf, PCWSTR field, void *arg )
122 {
123     struct files_callback_info *info = arg;
124     SetupQueueRenameSectionW( info->queue, hinf, 0, field );
125     return TRUE;
126 }
127
128
129 /***********************************************************************
130  *            get_root_key
131  *
132  * Retrieve the registry root key from its name.
133  */
134 static HKEY get_root_key( const WCHAR *name, HKEY def_root )
135 {
136     static const WCHAR HKCR[] = {'H','K','C','R',0};
137     static const WCHAR HKCU[] = {'H','K','C','U',0};
138     static const WCHAR HKLM[] = {'H','K','L','M',0};
139     static const WCHAR HKU[]  = {'H','K','U',0};
140     static const WCHAR HKR[]  = {'H','K','R',0};
141
142     if (!strcmpiW( name, HKCR )) return HKEY_CLASSES_ROOT;
143     if (!strcmpiW( name, HKCU )) return HKEY_CURRENT_USER;
144     if (!strcmpiW( name, HKLM )) return HKEY_LOCAL_MACHINE;
145     if (!strcmpiW( name, HKU )) return HKEY_USERS;
146     if (!strcmpiW( name, HKR )) return def_root;
147     return 0;
148 }
149
150
151 /***********************************************************************
152  *            append_multi_sz_value
153  *
154  * Append a multisz string to a multisz registry value.
155  */
156 static void append_multi_sz_value( HKEY hkey, const WCHAR *value, const WCHAR *strings,
157                                    DWORD str_size )
158 {
159     DWORD size, type, total;
160     WCHAR *buffer, *p;
161
162     if (RegQueryValueExW( hkey, value, NULL, &type, NULL, &size )) return;
163     if (type != REG_MULTI_SZ) return;
164
165     if (!(buffer = HeapAlloc( GetProcessHeap(), 0, (size + str_size) * sizeof(WCHAR) ))) return;
166     if (RegQueryValueExW( hkey, value, NULL, NULL, (BYTE *)buffer, &size )) goto done;
167
168     /* compare each string against all the existing ones */
169     total = size;
170     while (*strings)
171     {
172         int len = strlenW(strings) + 1;
173
174         for (p = buffer; *p; p += strlenW(p) + 1)
175             if (!strcmpiW( p, strings )) break;
176
177         if (!*p)  /* not found, need to append it */
178         {
179             memcpy( p, strings, len * sizeof(WCHAR) );
180             p[len] = 0;
181             total += len;
182         }
183         strings += len;
184     }
185     if (total != size)
186     {
187         TRACE( "setting value %s to %s\n", debugstr_w(value), debugstr_w(buffer) );
188         RegSetValueExW( hkey, value, 0, REG_MULTI_SZ, (BYTE *)buffer, total );
189     }
190  done:
191     HeapFree( GetProcessHeap(), 0, buffer );
192 }
193
194
195 /***********************************************************************
196  *            delete_multi_sz_value
197  *
198  * Remove a string from a multisz registry value.
199  */
200 static void delete_multi_sz_value( HKEY hkey, const WCHAR *value, const WCHAR *string )
201 {
202     DWORD size, type;
203     WCHAR *buffer, *src, *dst;
204
205     if (RegQueryValueExW( hkey, value, NULL, &type, NULL, &size )) return;
206     if (type != REG_MULTI_SZ) return;
207     /* allocate double the size, one for value before and one for after */
208     if (!(buffer = HeapAlloc( GetProcessHeap(), 0, size * 2 * sizeof(WCHAR) ))) return;
209     if (RegQueryValueExW( hkey, value, NULL, NULL, (BYTE *)buffer, &size )) goto done;
210     src = buffer;
211     dst = buffer + size;
212     while (*src)
213     {
214         int len = strlenW(src) + 1;
215         if (strcmpiW( src, string ))
216         {
217             memcpy( dst, src, len * sizeof(WCHAR) );
218             dst += len;
219         }
220         src += len;
221     }
222     *dst++ = 0;
223     if (dst != buffer + 2*size)  /* did we remove something? */
224     {
225         TRACE( "setting value %s to %s\n", debugstr_w(value), debugstr_w(buffer + size) );
226         RegSetValueExW( hkey, value, 0, REG_MULTI_SZ,
227                         (BYTE *)(buffer + size), dst - (buffer + size) );
228     }
229  done:
230     HeapFree( GetProcessHeap(), 0, buffer );
231 }
232
233
234 /***********************************************************************
235  *            do_reg_operation
236  *
237  * Perform an add/delete registry operation depending on the flags.
238  */
239 static BOOL do_reg_operation( HKEY hkey, const WCHAR *value, INFCONTEXT *context, INT flags )
240 {
241     DWORD type, size;
242
243     if (flags & (FLG_ADDREG_DELREG_BIT | FLG_ADDREG_DELVAL))  /* deletion */
244     {
245         if (*value && !(flags & FLG_DELREG_KEYONLY_COMMON))
246         {
247             if ((flags & FLG_DELREG_MULTI_SZ_DELSTRING) == FLG_DELREG_MULTI_SZ_DELSTRING)
248             {
249                 WCHAR *str;
250
251                 if (!SetupGetStringFieldW( context, 5, NULL, 0, &size ) || !size) return TRUE;
252                 if (!(str = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) ))) return FALSE;
253                 SetupGetStringFieldW( context, 5, str, size, NULL );
254                 delete_multi_sz_value( hkey, value, str );
255                 HeapFree( GetProcessHeap(), 0, str );
256             }
257             else RegDeleteValueW( hkey, value );
258         }
259         else RegDeleteKeyW( hkey, NULL );
260         return TRUE;
261     }
262
263     if (flags & (FLG_ADDREG_KEYONLY|FLG_ADDREG_KEYONLY_COMMON)) return TRUE;
264
265     if (flags & (FLG_ADDREG_NOCLOBBER|FLG_ADDREG_OVERWRITEONLY))
266     {
267         BOOL exists = !RegQueryValueExW( hkey, value, NULL, NULL, NULL, NULL );
268         if (exists && (flags & FLG_ADDREG_NOCLOBBER)) return TRUE;
269         if (!exists & (flags & FLG_ADDREG_OVERWRITEONLY)) return TRUE;
270     }
271
272     switch(flags & FLG_ADDREG_TYPE_MASK)
273     {
274     case FLG_ADDREG_TYPE_SZ:        type = REG_SZ; break;
275     case FLG_ADDREG_TYPE_MULTI_SZ:  type = REG_MULTI_SZ; break;
276     case FLG_ADDREG_TYPE_EXPAND_SZ: type = REG_EXPAND_SZ; break;
277     case FLG_ADDREG_TYPE_BINARY:    type = REG_BINARY; break;
278     case FLG_ADDREG_TYPE_DWORD:     type = REG_DWORD; break;
279     case FLG_ADDREG_TYPE_NONE:      type = REG_NONE; break;
280     default:                        type = flags >> 16; break;
281     }
282
283     if (!(flags & FLG_ADDREG_BINVALUETYPE) ||
284         (type == REG_DWORD && SetupGetFieldCount(context) == 5))
285     {
286         static const WCHAR empty;
287         WCHAR *str = NULL;
288
289         if (type == REG_MULTI_SZ)
290         {
291             if (!SetupGetMultiSzFieldW( context, 5, NULL, 0, &size )) size = 0;
292             if (size)
293             {
294                 if (!(str = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) ))) return FALSE;
295                 SetupGetMultiSzFieldW( context, 5, str, size, NULL );
296             }
297             if (flags & FLG_ADDREG_APPEND)
298             {
299                 if (!str) return TRUE;
300                 append_multi_sz_value( hkey, value, str, size );
301                 HeapFree( GetProcessHeap(), 0, str );
302                 return TRUE;
303             }
304             /* else fall through to normal string handling */
305         }
306         else
307         {
308             if (!SetupGetStringFieldW( context, 5, NULL, 0, &size )) size = 0;
309             if (size)
310             {
311                 if (!(str = HeapAlloc( GetProcessHeap(), 0, size * sizeof(WCHAR) ))) return FALSE;
312                 SetupGetStringFieldW( context, 5, str, size, NULL );
313             }
314         }
315
316         if (type == REG_DWORD)
317         {
318             DWORD dw = str ? wcstol( str, NULL, 16 ) : 0;
319             TRACE( "setting dword %s to %lx\n", debugstr_w(value), dw );
320             RegSetValueExW( hkey, value, 0, type, (BYTE *)&dw, sizeof(dw) );
321         }
322         else
323         {
324             TRACE( "setting value %s to %s\n", debugstr_w(value), debugstr_w(str) );
325             if (str) RegSetValueExW( hkey, value, 0, type, (BYTE *)str, size * sizeof(WCHAR) );
326             else RegSetValueExW( hkey, value, 0, type, (BYTE *)&empty, sizeof(WCHAR) );
327         }
328         HeapFree( GetProcessHeap(), 0, str );
329         return TRUE;
330     }
331     else  /* get the binary data */
332     {
333         BYTE *data = NULL;
334
335         if (!SetupGetBinaryField( context, 5, NULL, 0, &size )) size = 0;
336         if (size)
337         {
338             if (!(data = HeapAlloc( GetProcessHeap(), 0, size ))) return FALSE;
339             TRACE( "setting binary data %s len %ld\n", debugstr_w(value), size );
340             SetupGetBinaryField( context, 5, data, size, NULL );
341         }
342         RegSetValueExW( hkey, value, 0, type, data, size );
343         HeapFree( GetProcessHeap(), 0, data );
344         return TRUE;
345     }
346 }
347
348
349 /***********************************************************************
350  *            registry_callback
351  *
352  * Called once for each AddReg and DelReg entry in a given section.
353  */
354 static BOOL registry_callback( HINF hinf, PCWSTR field, void *arg )
355 {
356     struct registry_callback_info *info = arg;
357     INFCONTEXT context;
358     HKEY root_key, hkey;
359
360     BOOL ok = SetupFindFirstLineW( hinf, field, NULL, &context );
361
362     for (; ok; ok = SetupFindNextLine( &context, &context ))
363     {
364         WCHAR buffer[MAX_INF_STRING_LENGTH];
365         INT flags;
366
367         /* get root */
368         if (!SetupGetStringFieldW( &context, 1, buffer, sizeof(buffer)/sizeof(WCHAR), NULL ))
369             continue;
370         if (!(root_key = get_root_key( buffer, info->default_root )))
371             continue;
372
373         /* get key */
374         if (!SetupGetStringFieldW( &context, 2, buffer, sizeof(buffer)/sizeof(WCHAR), NULL ))
375             *buffer = 0;
376
377         /* get flags */
378         if (!SetupGetIntField( &context, 4, &flags )) flags = 0;
379
380         if (!info->delete)
381         {
382             if (flags & FLG_ADDREG_DELREG_BIT) continue;  /* ignore this entry */
383         }
384         else
385         {
386             if (!flags) flags = FLG_ADDREG_DELREG_BIT;
387             else if (!(flags & FLG_ADDREG_DELREG_BIT)) continue;  /* ignore this entry */
388         }
389
390         if (info->delete || (flags & FLG_ADDREG_OVERWRITEONLY))
391         {
392             if (RegOpenKeyW( root_key, buffer, &hkey )) continue;  /* ignore if it doesn't exist */
393         }
394         else if (RegCreateKeyW( root_key, buffer, &hkey ))
395         {
396             ERR( "could not create key %08x %s\n", root_key, debugstr_w(buffer) );
397             continue;
398         }
399         TRACE( "key %08x %s\n", root_key, debugstr_w(buffer) );
400
401         /* get value name */
402         if (!SetupGetStringFieldW( &context, 3, buffer, sizeof(buffer)/sizeof(WCHAR), NULL ))
403             *buffer = 0;
404
405         /* and now do it */
406         if (!do_reg_operation( hkey, buffer, &context, flags ))
407         {
408             RegCloseKey( hkey );
409             return FALSE;
410         }
411         RegCloseKey( hkey );
412     }
413     return TRUE;
414 }
415
416
417 static BOOL update_ini_callback( HINF hinf, PCWSTR field, void *arg )
418 {
419     FIXME( "should update ini %s\n", debugstr_w(field) );
420     return TRUE;
421 }
422
423 static BOOL update_ini_fields_callback( HINF hinf, PCWSTR field, void *arg )
424 {
425     FIXME( "should update ini fields %s\n", debugstr_w(field) );
426     return TRUE;
427 }
428
429 static BOOL ini2reg_callback( HINF hinf, PCWSTR field, void *arg )
430 {
431     FIXME( "should do ini2reg %s\n", debugstr_w(field) );
432     return TRUE;
433 }
434
435 static BOOL logconf_callback( HINF hinf, PCWSTR field, void *arg )
436 {
437     FIXME( "should do logconf %s\n", debugstr_w(field) );
438     return TRUE;
439 }
440
441
442 /***********************************************************************
443  *            iterate_section_fields
444  *
445  * Iterate over all fields of a certain key of a certain section
446  */
447 static BOOL iterate_section_fields( HINF hinf, PCWSTR section, PCWSTR key,
448                                     iterate_fields_func callback, void *arg )
449 {
450     WCHAR static_buffer[200];
451     WCHAR *buffer = static_buffer;
452     DWORD size = sizeof(static_buffer)/sizeof(WCHAR);
453     INFCONTEXT context;
454     BOOL ret = FALSE;
455
456     BOOL ok = SetupFindFirstLineW( hinf, section, key, &context );
457     while (ok)
458     {
459         UINT i, count = SetupGetFieldCount( &context );
460         for (i = 1; i <= count; i++)
461         {
462             if (!(buffer = get_field_string( &context, i, buffer, static_buffer, &size )))
463                 goto done;
464             if (!callback( hinf, buffer, arg ))
465             {
466                 ERR("callback failed for %s %s\n", debugstr_w(section), debugstr_w(buffer) );
467                 goto done;
468             }
469         }
470         ok = SetupFindNextMatchLineW( &context, key, &context );
471     }
472     ret = TRUE;
473  done:
474     if (buffer && buffer != static_buffer) HeapFree( GetProcessHeap(), 0, buffer );
475     return ret;
476 }
477
478
479 /***********************************************************************
480  *            SetupInstallFilesFromInfSectionA   (SETUPAPI.@)
481  */
482 BOOL WINAPI SetupInstallFilesFromInfSectionA( HINF hinf, HINF hlayout, HSPFILEQ queue,
483                                               PCSTR section, PCSTR src_root, UINT flags )
484 {
485     UNICODE_STRING sectionW;
486     BOOL ret = FALSE;
487
488     if (!RtlCreateUnicodeStringFromAsciiz( &sectionW, section ))
489     {
490         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
491         return FALSE;
492     }
493     if (!src_root)
494         ret = SetupInstallFilesFromInfSectionW( hinf, hlayout, queue, sectionW.Buffer,
495                                                 NULL, flags );
496     else
497     {
498         UNICODE_STRING srcW;
499         if (RtlCreateUnicodeStringFromAsciiz( &srcW, src_root ))
500         {
501             ret = SetupInstallFilesFromInfSectionW( hinf, hlayout, queue, sectionW.Buffer,
502                                                     srcW.Buffer, flags );
503             RtlFreeUnicodeString( &srcW );
504         }
505         else SetLastError( ERROR_NOT_ENOUGH_MEMORY );
506     }
507     RtlFreeUnicodeString( &sectionW );
508     return ret;
509 }
510
511
512 /***********************************************************************
513  *            SetupInstallFilesFromInfSectionW   (SETUPAPI.@)
514  */
515 BOOL WINAPI SetupInstallFilesFromInfSectionW( HINF hinf, HINF hlayout, HSPFILEQ queue,
516                                               PCWSTR section, PCWSTR src_root, UINT flags )
517 {
518     struct files_callback_info info;
519
520     info.queue      = queue;
521     info.src_root   = src_root;
522     info.copy_flags = flags;
523     info.layout     = hlayout;
524     return iterate_section_fields( hinf, section, CopyFiles, copy_files_callback, &info );
525 }
526
527
528 /***********************************************************************
529  *            SetupInstallFromInfSectionA   (SETUPAPI.@)
530  */
531 BOOL WINAPI SetupInstallFromInfSectionA( HWND owner, HINF hinf, PCSTR section, UINT flags,
532                                          HKEY key_root, PCSTR src_root, UINT copy_flags,
533                                          PSP_FILE_CALLBACK_A callback, PVOID context,
534                                          HDEVINFO devinfo, PSP_DEVINFO_DATA devinfo_data )
535 {
536     UNICODE_STRING sectionW, src_rootW;
537     struct callback_WtoA_context ctx;
538     BOOL ret = FALSE;
539
540     src_rootW.Buffer = NULL;
541     if (src_root && !RtlCreateUnicodeStringFromAsciiz( &src_rootW, src_root ))
542     {
543         SetLastError( ERROR_NOT_ENOUGH_MEMORY );
544         return FALSE;
545     }
546
547     if (RtlCreateUnicodeStringFromAsciiz( &sectionW, section ))
548     {
549         ctx.orig_context = context;
550         ctx.orig_handler = callback;
551         ret = SetupInstallFromInfSectionW( owner, hinf, sectionW.Buffer, flags, key_root,
552                                            src_rootW.Buffer, copy_flags, QUEUE_callback_WtoA,
553                                            &ctx, devinfo, devinfo_data );
554         RtlFreeUnicodeString( &sectionW );
555     }
556     else SetLastError( ERROR_NOT_ENOUGH_MEMORY );
557
558     RtlFreeUnicodeString( &src_rootW );
559     return ret;
560 }
561
562
563 /***********************************************************************
564  *            SetupInstallFromInfSectionW   (SETUPAPI.@)
565  */
566 BOOL WINAPI SetupInstallFromInfSectionW( HWND owner, HINF hinf, PCWSTR section, UINT flags,
567                                          HKEY key_root, PCWSTR src_root, UINT copy_flags,
568                                          PSP_FILE_CALLBACK_W callback, PVOID context,
569                                          HDEVINFO devinfo, PSP_DEVINFO_DATA devinfo_data )
570 {
571     if (flags & SPINST_FILES)
572     {
573         struct files_callback_info info;
574         HSPFILEQ queue;
575         BOOL ret;
576
577         if (!(queue = SetupOpenFileQueue())) return FALSE;
578         info.queue      = queue;
579         info.src_root   = src_root;
580         info.copy_flags = copy_flags;
581         info.layout     = hinf;
582         ret = (iterate_section_fields( hinf, section, CopyFiles, copy_files_callback, &info ) &&
583                iterate_section_fields( hinf, section, DelFiles, delete_files_callback, &info ) &&
584                iterate_section_fields( hinf, section, RenFiles, rename_files_callback, &info ) &&
585                SetupCommitFileQueueW( owner, queue, callback, context ));
586         SetupCloseFileQueue( queue );
587         if (!ret) return FALSE;
588     }
589     if (flags & SPINST_INIFILES)
590     {
591         if (!iterate_section_fields( hinf, section, UpdateInis, update_ini_callback, NULL ) ||
592             !iterate_section_fields( hinf, section, UpdateIniFields,
593                                      update_ini_fields_callback, NULL ))
594             return FALSE;
595     }
596     if (flags & SPINST_INI2REG)
597     {
598         if (!iterate_section_fields( hinf, section, Ini2Reg, ini2reg_callback, NULL ))
599             return FALSE;
600     }
601
602     if (flags & SPINST_LOGCONFIG)
603     {
604         if (!iterate_section_fields( hinf, section, LogConf, logconf_callback, NULL ))
605             return FALSE;
606     }
607
608     if (flags & SPINST_REGISTRY)
609     {
610         struct registry_callback_info info;
611
612         info.default_root = key_root;
613         info.delete = FALSE;
614         if (!iterate_section_fields( hinf, section, AddReg, registry_callback, &info ))
615             return FALSE;
616         info.delete = TRUE;
617         if (!iterate_section_fields( hinf, section, DelReg, registry_callback, &info ))
618             return FALSE;
619     }
620     if (flags & (SPINST_BITREG|SPINST_REGSVR|SPINST_UNREGSVR|SPINST_PROFILEITEMS|SPINST_COPYINF))
621         FIXME( "unsupported flags %x\n", flags );
622     return TRUE;
623 }