msi: Improve a trace.
[wine] / dlls / msi / helpers.c
1 /*
2  * Implementation of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Aric Stewart 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 /*
22  * Here are helper functions formally in action.c that are used by a variety of
23  * actions and functions.
24  */
25
26 #include <stdarg.h>
27
28 #include "windef.h"
29 #include "wine/debug.h"
30 #include "msipriv.h"
31 #include "winuser.h"
32 #include "wine/unicode.h"
33 #include "msidefs.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(msi);
36
37 static const WCHAR cszTargetDir[] = {'T','A','R','G','E','T','D','I','R',0};
38 static const WCHAR cszDatabase[]={'D','A','T','A','B','A','S','E',0};
39
40 LPWSTR build_icon_path(MSIPACKAGE *package, LPCWSTR icon_name )
41 {
42     LPWSTR SystemFolder, dest, FilePath;
43
44     static const WCHAR szInstaller[] = 
45         {'M','i','c','r','o','s','o','f','t','\\',
46          'I','n','s','t','a','l','l','e','r','\\',0};
47     static const WCHAR szFolder[] =
48         {'A','p','p','D','a','t','a','F','o','l','d','e','r',0};
49
50     SystemFolder = msi_dup_property( package, szFolder );
51
52     dest = build_directory_name(3, SystemFolder, szInstaller, package->ProductCode);
53
54     create_full_pathW(dest);
55
56     FilePath = build_directory_name(2, dest, icon_name);
57
58     msi_free(SystemFolder);
59     msi_free(dest);
60     return FilePath;
61 }
62
63 LPWSTR msi_dup_record_field( MSIRECORD *rec, INT field )
64 {
65     DWORD sz = 0;
66     LPWSTR str;
67     UINT r;
68
69     if (MSI_RecordIsNull( rec, field ))
70         return NULL;
71
72     r = MSI_RecordGetStringW( rec, field, NULL, &sz );
73     if (r != ERROR_SUCCESS)
74         return NULL;
75
76     sz ++;
77     str = msi_alloc( sz * sizeof (WCHAR) );
78     if (!str)
79         return str;
80     str[0] = 0;
81     r = MSI_RecordGetStringW( rec, field, str, &sz );
82     if (r != ERROR_SUCCESS)
83     {
84         ERR("failed to get string!\n");
85         msi_free( str );
86         return NULL;
87     }
88     return str;
89 }
90
91 MSICOMPONENT* get_loaded_component( MSIPACKAGE* package, LPCWSTR Component )
92 {
93     MSICOMPONENT *comp;
94
95     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
96     {
97         if (lstrcmpW(Component,comp->Component)==0)
98             return comp;
99     }
100     return NULL;
101 }
102
103 MSIFEATURE* get_loaded_feature(MSIPACKAGE* package, LPCWSTR Feature )
104 {
105     MSIFEATURE *feature;
106
107     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
108     {
109         if (lstrcmpW( Feature, feature->Feature )==0)
110             return feature;
111     }
112     return NULL;
113 }
114
115 MSIFILE* get_loaded_file( MSIPACKAGE* package, LPCWSTR key )
116 {
117     MSIFILE *file;
118
119     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
120     {
121         if (lstrcmpW( key, file->File )==0)
122             return file;
123     }
124     return NULL;
125 }
126
127 int track_tempfile( MSIPACKAGE *package, LPCWSTR path )
128 {
129     MSITEMPFILE *temp;
130
131     TRACE("%s\n", debugstr_w(path));
132
133     LIST_FOR_EACH_ENTRY( temp, &package->tempfiles, MSITEMPFILE, entry )
134         if (!lstrcmpW( path, temp->Path ))
135             return 0;
136
137     temp = msi_alloc_zero( sizeof (MSITEMPFILE) );
138     if (!temp)
139         return -1;
140
141     list_add_head( &package->tempfiles, &temp->entry );
142     temp->Path = strdupW( path );
143
144     return 0;
145 }
146
147 MSIFOLDER *get_loaded_folder( MSIPACKAGE *package, LPCWSTR dir )
148 {
149     MSIFOLDER *folder;
150
151     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
152     {
153         if (lstrcmpW( dir, folder->Directory )==0)
154             return folder;
155     }
156     return NULL;
157 }
158
159 static LPWSTR get_source_root( MSIPACKAGE *package )
160 {
161     LPWSTR path, p;
162
163     path = msi_dup_property( package, cszSourceDir );
164     if (path)
165         return path;
166
167     path = msi_dup_property( package, cszDatabase );
168     if (path)
169     {
170         p = strrchrW(path,'\\');
171         if (p)
172             *(p+1) = 0;
173     }
174     return path;
175 }
176
177 /*
178  * clean_spaces_from_path()
179  *
180  * removes spaces from the beginning and end of path segments
181  * removes multiple \\ characters
182  */
183 static void clean_spaces_from_path( LPWSTR p )
184 {
185     LPWSTR q = p;
186     int n, len = 0;
187
188     while (1)
189     {
190         /* copy until the end of the string or a space */
191         while (*p != ' ' && (*q = *p))
192         {
193             p++, len++;
194             /* reduce many backslashes to one */
195             if (*p != '\\' || *q != '\\')
196                 q++;
197         }
198
199         /* quit at the end of the string */
200         if (!*p)
201             break;
202
203         /* count the number of spaces */
204         n = 0;
205         while (p[n] == ' ')
206             n++;
207
208         /* if it's leading or trailing space, skip it */
209         if ( len == 0 || p[-1] == '\\' || p[n] == '\\' )
210             p += n;
211         else  /* copy n spaces */
212             while (n && (*q++ = *p++)) n--;
213     }
214 }
215
216 LPWSTR resolve_file_source(MSIPACKAGE *package, MSIFILE *file)
217 {
218     LPWSTR p, path;
219
220     TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
221
222     if (file->IsCompressed)
223         return NULL;
224
225     p = resolve_folder(package, file->Component->Directory,
226                        TRUE, FALSE, TRUE, NULL);
227     path = build_directory_name(2, p, file->ShortName);
228
229     if (file->LongName &&
230         GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES)
231     {
232         msi_free(path);
233         path = build_directory_name(2, p, file->LongName);
234     }
235
236     msi_free(p);
237
238     TRACE("file %s source resolves to %s\n", debugstr_w(file->File),
239           debugstr_w(path));
240
241     return path;
242 }
243
244 LPWSTR resolve_folder(MSIPACKAGE *package, LPCWSTR name, BOOL source, 
245                       BOOL set_prop, BOOL load_prop, MSIFOLDER **folder)
246 {
247     MSIFOLDER *f;
248     LPWSTR p, path = NULL, parent;
249
250     TRACE("Working to resolve %s\n",debugstr_w(name));
251
252     if (!name)
253         return NULL;
254
255     if (!lstrcmpW(name,cszSourceDir))
256         name = cszTargetDir;
257
258     f = get_loaded_folder( package, name );
259     if (!f)
260         return NULL;
261
262     /* special resolving for Target and Source root dir */
263     if (!strcmpW(name,cszTargetDir))
264     {
265         if (!f->ResolvedTarget && !f->Property)
266         {
267             LPWSTR check_path;
268             check_path = msi_dup_property( package, cszTargetDir );
269             if (!check_path)
270             {
271                 check_path = msi_dup_property( package, cszRootDrive );
272                 if (set_prop)
273                     MSI_SetPropertyW(package,cszTargetDir,check_path);
274             }
275
276             /* correct misbuilt target dir */
277             path = build_directory_name(2, check_path, NULL);
278             clean_spaces_from_path( path );
279             if (strcmpiW(path,check_path)!=0)
280                 MSI_SetPropertyW(package,cszTargetDir,path);
281             msi_free(check_path);
282
283             f->ResolvedTarget = path;
284         }
285
286         if (!f->ResolvedSource)
287             f->ResolvedSource = get_source_root( package );
288     }
289
290     if (folder)
291         *folder = f;
292
293     if (!source && f->ResolvedTarget)
294     {
295         path = strdupW( f->ResolvedTarget );
296         TRACE("   already resolved to %s\n",debugstr_w(path));
297         return path;
298     }
299
300     if (source && f->ResolvedSource)
301     {
302         path = strdupW( f->ResolvedSource );
303         TRACE("   (source)already resolved to %s\n",debugstr_w(path));
304         return path;
305     }
306
307     if (!source && f->Property)
308     {
309         path = build_directory_name( 2, f->Property, NULL );
310
311         TRACE("   internally set to %s\n",debugstr_w(path));
312         if (set_prop)
313             MSI_SetPropertyW( package, name, path );
314         return path;
315     }
316
317     if (!source && load_prop && (path = msi_dup_property( package, name )))
318     {
319         f->ResolvedTarget = strdupW( path );
320         TRACE("   property set to %s\n", debugstr_w(path));
321         return path;
322     }
323
324     if (!f->Parent)
325         return path;
326
327     parent = f->Parent;
328
329     TRACE(" ! Parent is %s\n", debugstr_w(parent));
330
331     p = resolve_folder(package, parent, source, set_prop, load_prop, NULL);
332     if (!source)
333     {
334         TRACE("   TargetDefault = %s\n", debugstr_w(f->TargetDefault));
335
336         path = build_directory_name( 3, p, f->TargetDefault, NULL );
337         clean_spaces_from_path( path );
338         f->ResolvedTarget = strdupW( path );
339         TRACE("target -> %s\n", debugstr_w(path));
340         if (set_prop)
341             MSI_SetPropertyW(package,name,path);
342     }
343     else
344     {
345         path = NULL;
346
347         if (package->WordCount & msidbSumInfoSourceTypeCompressed)
348             path = get_source_root( package );
349         else if (package->WordCount & msidbSumInfoSourceTypeSFN)
350             path = build_directory_name( 3, p, f->SourceShortPath, NULL );
351         else
352             path = build_directory_name( 3, p, f->SourceLongPath, NULL );
353
354         TRACE("source -> %s\n", debugstr_w(path));
355         f->ResolvedSource = strdupW( path );
356     }
357     msi_free(p);
358
359     return path;
360 }
361
362 /* wrapper to resist a need for a full rewrite right now */
363 DWORD deformat_string(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data )
364 {
365     if (ptr)
366     {
367         MSIRECORD *rec = MSI_CreateRecord(1);
368         DWORD size = 0;
369
370         MSI_RecordSetStringW(rec,0,ptr);
371         MSI_FormatRecordW(package,rec,NULL,&size);
372
373         size++;
374         *data = msi_alloc(size*sizeof(WCHAR));
375         if (size > 1)
376             MSI_FormatRecordW(package,rec,*data,&size);
377         else
378             *data[0] = 0;
379
380         msiobj_release( &rec->hdr );
381         return sizeof(WCHAR)*size;
382     }
383
384     *data = NULL;
385     return 0;
386 }
387
388 UINT schedule_action(MSIPACKAGE *package, UINT script, LPCWSTR action)
389 {
390     UINT count;
391     LPWSTR *newbuf = NULL;
392     if (script >= TOTAL_SCRIPTS)
393     {
394         FIXME("Unknown script requested %i\n",script);
395         return ERROR_FUNCTION_FAILED;
396     }
397     TRACE("Scheduling Action %s in script %i\n",debugstr_w(action), script);
398     
399     count = package->script->ActionCount[script];
400     package->script->ActionCount[script]++;
401     if (count != 0)
402         newbuf = msi_realloc( package->script->Actions[script],
403                         package->script->ActionCount[script]* sizeof(LPWSTR));
404     else
405         newbuf = msi_alloc( sizeof(LPWSTR));
406
407     newbuf[count] = strdupW(action);
408     package->script->Actions[script] = newbuf;
409
410    return ERROR_SUCCESS;
411 }
412
413 void msi_free_action_script(MSIPACKAGE *package, UINT script)
414 {
415     UINT i;
416     for (i = 0; i < package->script->ActionCount[script]; i++)
417         msi_free(package->script->Actions[script][i]);
418
419     msi_free(package->script->Actions[script]);
420     package->script->Actions[script] = NULL;
421     package->script->ActionCount[script] = 0;
422 }
423
424 /*
425  *  build_directory_name()
426  *
427  *  This function is to save messing round with directory names
428  *  It handles adding backslashes between path segments, 
429  *   and can add \ at the end of the directory name if told to.
430  *
431  *  It takes a variable number of arguments.
432  *  It always allocates a new string for the result, so make sure
433  *   to free the return value when finished with it.
434  *
435  *  The first arg is the number of path segments that follow.
436  *  The arguments following count are a list of path segments.
437  *  A path segment may be NULL.
438  *
439  *  Path segments will be added with a \ separating them.
440  *  A \ will not be added after the last segment, however if the
441  *    last segment is NULL, then the last character will be a \
442  * 
443  */
444 LPWSTR build_directory_name(DWORD count, ...)
445 {
446     DWORD sz = 1, i;
447     LPWSTR dir;
448     va_list va;
449
450     va_start(va,count);
451     for(i=0; i<count; i++)
452     {
453         LPCWSTR str = va_arg(va,LPCWSTR);
454         if (str)
455             sz += strlenW(str) + 1;
456     }
457     va_end(va);
458
459     dir = msi_alloc(sz*sizeof(WCHAR));
460     dir[0]=0;
461
462     va_start(va,count);
463     for(i=0; i<count; i++)
464     {
465         LPCWSTR str = va_arg(va,LPCWSTR);
466         if (!str)
467             continue;
468         strcatW(dir, str);
469         if( ((i+1)!=count) && dir[strlenW(dir)-1]!='\\')
470             strcatW(dir, szBackSlash);
471     }
472     return dir;
473 }
474
475 /***********************************************************************
476  *            create_full_pathW
477  *
478  * Recursively create all directories in the path.
479  *
480  * shamelessly stolen from setupapi/queue.c
481  */
482 BOOL create_full_pathW(const WCHAR *path)
483 {
484     BOOL ret = TRUE;
485     int len;
486     WCHAR *new_path;
487
488     new_path = msi_alloc( (strlenW(path) + 1) * sizeof(WCHAR));
489
490     strcpyW(new_path, path);
491
492     while((len = strlenW(new_path)) && new_path[len - 1] == '\\')
493     new_path[len - 1] = 0;
494
495     while(!CreateDirectoryW(new_path, NULL))
496     {
497         WCHAR *slash;
498         DWORD last_error = GetLastError();
499         if(last_error == ERROR_ALREADY_EXISTS)
500             break;
501
502         if(last_error != ERROR_PATH_NOT_FOUND)
503         {
504             ret = FALSE;
505             break;
506         }
507
508         if(!(slash = strrchrW(new_path, '\\')))
509         {
510             ret = FALSE;
511             break;
512         }
513
514         len = slash - new_path;
515         new_path[len] = 0;
516         if(!create_full_pathW(new_path))
517         {
518             ret = FALSE;
519             break;
520         }
521         new_path[len] = '\\';
522     }
523
524     msi_free(new_path);
525     return ret;
526 }
527
528 void ui_progress(MSIPACKAGE *package, int a, int b, int c, int d )
529 {
530     MSIRECORD * row;
531
532     row = MSI_CreateRecord(4);
533     MSI_RecordSetInteger(row,1,a);
534     MSI_RecordSetInteger(row,2,b);
535     MSI_RecordSetInteger(row,3,c);
536     MSI_RecordSetInteger(row,4,d);
537     MSI_ProcessMessage(package, INSTALLMESSAGE_PROGRESS, row);
538     msiobj_release(&row->hdr);
539
540     msi_dialog_check_messages(NULL);
541 }
542
543 void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * record)
544 {
545     static const WCHAR Query_t[] = 
546         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
547          '`','A','c','t','i','o', 'n','T','e','x','t','`',' ',
548          'W','H','E','R','E',' ', '`','A','c','t','i','o','n','`',' ','=', 
549          ' ','\'','%','s','\'',0};
550     WCHAR message[1024];
551     MSIRECORD * row = 0;
552     DWORD size;
553
554     if (!package->LastAction || strcmpW(package->LastAction,action))
555     {
556         row = MSI_QueryGetRecord(package->db, Query_t, action);
557         if (!row)
558             return;
559
560         if (MSI_RecordIsNull(row,3))
561         {
562             msiobj_release(&row->hdr);
563             return;
564         }
565
566         /* update the cached actionformat */
567         msi_free(package->ActionFormat);
568         package->ActionFormat = msi_dup_record_field(row,3);
569
570         msi_free(package->LastAction);
571         package->LastAction = strdupW(action);
572
573         msiobj_release(&row->hdr);
574     }
575
576     MSI_RecordSetStringW(record,0,package->ActionFormat);
577     size = 1024;
578     MSI_FormatRecordW(package,record,message,&size);
579
580     row = MSI_CreateRecord(1);
581     MSI_RecordSetStringW(row,1,message);
582  
583     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row);
584
585     msiobj_release(&row->hdr);
586 }
587
588 void reduce_to_longfilename(WCHAR* filename)
589 {
590     LPWSTR p = strchrW(filename,'|');
591     if (p)
592         memmove(filename, p+1, (strlenW(p+1)+1)*sizeof(WCHAR));
593 }
594
595 LPWSTR create_component_advertise_string(MSIPACKAGE* package, 
596                 MSICOMPONENT* component, LPCWSTR feature)
597 {
598     static const WCHAR fmt[] = {'%','s','%','s','%','c','%','s',0};
599     WCHAR productid_85[21], component_85[21];
600     LPWSTR output = NULL;
601     DWORD sz = 0;
602     GUID clsid;
603
604     /* > is used if there is a component GUID and < if not.  */
605
606     productid_85[0] = 0;
607     component_85[0] = 0;
608
609     CLSIDFromString(package->ProductCode, &clsid);
610     encode_base85_guid(&clsid, productid_85);
611
612     if (component)
613     {
614         CLSIDFromString(component->ComponentId, &clsid);
615         encode_base85_guid(&clsid, component_85);
616     }
617
618     TRACE("prod=%s feat=%s comp=%s\n", debugstr_w(productid_85),
619           debugstr_w(feature), debugstr_w(component_85));
620  
621     sz = 20 + lstrlenW(feature) + 20 + 3;
622
623     output = msi_alloc_zero(sz*sizeof(WCHAR));
624
625     sprintfW(output, fmt, productid_85, feature,
626              component?'>':'<', component_85);
627     
628     return output;
629 }
630
631 /* update component state based on a feature change */
632 void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
633 {
634     INSTALLSTATE newstate;
635     MSIFEATURE *feature;
636     ComponentList *cl;
637
638     feature = get_loaded_feature(package,szFeature);
639     if (!feature)
640         return;
641
642     newstate = feature->ActionRequest;
643
644     if (newstate == INSTALLSTATE_ABSENT)
645         newstate = INSTALLSTATE_UNKNOWN;
646
647     LIST_FOR_EACH_ENTRY( cl, &feature->Components, ComponentList, entry )
648     {
649         MSICOMPONENT* component = cl->component;
650     
651         TRACE("MODIFYING(%i): Component %s (Installed %i, Action %i, Request %i)\n",
652             newstate, debugstr_w(component->Component), component->Installed, 
653             component->Action, component->ActionRequest);
654         
655         if (!component->Enabled)
656             continue;
657  
658         if (newstate == INSTALLSTATE_LOCAL)
659             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
660         else 
661         {
662             ComponentList *clist;
663             MSIFEATURE *f;
664
665             component->hasLocalFeature = FALSE;
666
667             msi_component_set_state(package, component, newstate);
668
669             /*if any other feature wants is local we need to set it local*/
670             LIST_FOR_EACH_ENTRY( f, &package->features, MSIFEATURE, entry )
671             {
672                 if ( f->ActionRequest != INSTALLSTATE_LOCAL &&
673                      f->ActionRequest != INSTALLSTATE_SOURCE )
674                 {
675                     continue;
676                 }
677
678                 LIST_FOR_EACH_ENTRY( clist, &f->Components, ComponentList, entry )
679                 {
680                     if ( clist->component == component &&
681                          (f->ActionRequest == INSTALLSTATE_LOCAL ||
682                           f->ActionRequest == INSTALLSTATE_SOURCE) )
683                     {
684                         TRACE("Saved by %s\n", debugstr_w(f->Feature));
685                         component->hasLocalFeature = TRUE;
686
687                         if (component->Attributes & msidbComponentAttributesOptional)
688                         {
689                             if (f->Attributes & msidbFeatureAttributesFavorSource)
690                                 msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
691                             else
692                                 msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
693                         }
694                         else if (component->Attributes & msidbComponentAttributesSourceOnly)
695                             msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
696                         else
697                             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
698                     }
699                 }
700             }
701         }
702         TRACE("Result (%i): Component %s (Installed %i, Action %i, Request %i)\n",
703             newstate, debugstr_w(component->Component), component->Installed, 
704             component->Action, component->ActionRequest);
705     } 
706 }
707
708 UINT register_unique_action(MSIPACKAGE *package, LPCWSTR action)
709 {
710     UINT count;
711     LPWSTR *newbuf = NULL;
712
713     if (!package->script)
714         return FALSE;
715
716     TRACE("Registering %s as unique action\n", debugstr_w(action));
717     
718     count = package->script->UniqueActionsCount;
719     package->script->UniqueActionsCount++;
720     if (count != 0)
721         newbuf = msi_realloc( package->script->UniqueActions,
722                         package->script->UniqueActionsCount* sizeof(LPWSTR));
723     else
724         newbuf = msi_alloc( sizeof(LPWSTR));
725
726     newbuf[count] = strdupW(action);
727     package->script->UniqueActions = newbuf;
728
729     return ERROR_SUCCESS;
730 }
731
732 BOOL check_unique_action(const MSIPACKAGE *package, LPCWSTR action)
733 {
734     UINT i;
735
736     if (!package->script)
737         return FALSE;
738
739     for (i = 0; i < package->script->UniqueActionsCount; i++)
740         if (!strcmpW(package->script->UniqueActions[i],action))
741             return TRUE;
742
743     return FALSE;
744 }
745
746 WCHAR* generate_error_string(MSIPACKAGE *package, UINT error, DWORD count, ... )
747 {
748     static const WCHAR query[] = {'S','E','L','E','C','T',' ','`','M','e','s','s','a','g','e','`',' ','F','R','O','M',' ','`','E','r','r','o','r','`',' ','W','H','E','R','E',' ','`','E','r','r','o','r','`',' ','=',' ','%','i',0};
749
750     MSIRECORD *rec;
751     MSIRECORD *row;
752     DWORD size = 0;
753     DWORD i;
754     va_list va;
755     LPCWSTR str;
756     LPWSTR data;
757
758     row = MSI_QueryGetRecord(package->db, query, error);
759     if (!row)
760         return 0;
761
762     rec = MSI_CreateRecord(count+2);
763
764     str = MSI_RecordGetString(row,1);
765     MSI_RecordSetStringW(rec,0,str);
766     msiobj_release( &row->hdr );
767     MSI_RecordSetInteger(rec,1,error);
768
769     va_start(va,count);
770     for (i = 0; i < count; i++)
771     {
772         str = va_arg(va,LPCWSTR);
773         MSI_RecordSetStringW(rec,(i+2),str);
774     }
775     va_end(va);
776
777     MSI_FormatRecordW(package,rec,NULL,&size);
778
779     size++;
780     data = msi_alloc(size*sizeof(WCHAR));
781     if (size > 1)
782         MSI_FormatRecordW(package,rec,data,&size);
783     else
784         data[0] = 0;
785     msiobj_release( &rec->hdr );
786     return data;
787 }