msi: Fix a copy and paste error.
[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 const WCHAR cszSourceDir[] = {'S','o','u','r','c','e','D','i','r',0};
41 const WCHAR cszSOURCEDIR[] = {'S','O','U','R','C','E','D','I','R',0};
42 const WCHAR cszRootDrive[] = {'R','O','O','T','D','R','I','V','E',0};
43 const WCHAR cszbs[]={'\\',0};
44
45 LPWSTR build_icon_path(MSIPACKAGE *package, LPCWSTR icon_name )
46 {
47     LPWSTR SystemFolder, dest, FilePath;
48
49     static const WCHAR szInstaller[] = 
50         {'M','i','c','r','o','s','o','f','t','\\',
51          'I','n','s','t','a','l','l','e','r','\\',0};
52     static const WCHAR szFolder[] =
53         {'A','p','p','D','a','t','a','F','o','l','d','e','r',0};
54
55     SystemFolder = msi_dup_property( package, szFolder );
56
57     dest = build_directory_name(3, SystemFolder, szInstaller, package->ProductCode);
58
59     create_full_pathW(dest);
60
61     FilePath = build_directory_name(2, dest, icon_name);
62
63     msi_free(SystemFolder);
64     msi_free(dest);
65     return FilePath;
66 }
67
68 LPWSTR msi_dup_record_field( MSIRECORD *rec, INT field )
69 {
70     DWORD sz = 0;
71     LPWSTR str;
72     UINT r;
73
74     if (MSI_RecordIsNull( rec, field ))
75         return NULL;
76
77     r = MSI_RecordGetStringW( rec, field, NULL, &sz );
78     if (r != ERROR_SUCCESS)
79         return NULL;
80
81     sz ++;
82     str = msi_alloc( sz * sizeof (WCHAR) );
83     if (!str)
84         return str;
85     str[0] = 0;
86     r = MSI_RecordGetStringW( rec, field, str, &sz );
87     if (r != ERROR_SUCCESS)
88     {
89         ERR("failed to get string!\n");
90         msi_free( str );
91         return NULL;
92     }
93     return str;
94 }
95
96 MSICOMPONENT* get_loaded_component( MSIPACKAGE* package, LPCWSTR Component )
97 {
98     MSICOMPONENT *comp;
99
100     LIST_FOR_EACH_ENTRY( comp, &package->components, MSICOMPONENT, entry )
101     {
102         if (lstrcmpW(Component,comp->Component)==0)
103             return comp;
104     }
105     return NULL;
106 }
107
108 MSIFEATURE* get_loaded_feature(MSIPACKAGE* package, LPCWSTR Feature )
109 {
110     MSIFEATURE *feature;
111
112     LIST_FOR_EACH_ENTRY( feature, &package->features, MSIFEATURE, entry )
113     {
114         if (lstrcmpW( Feature, feature->Feature )==0)
115             return feature;
116     }
117     return NULL;
118 }
119
120 MSIFILE* get_loaded_file( MSIPACKAGE* package, LPCWSTR key )
121 {
122     MSIFILE *file;
123
124     LIST_FOR_EACH_ENTRY( file, &package->files, MSIFILE, entry )
125     {
126         if (lstrcmpW( key, file->File )==0)
127             return file;
128     }
129     return NULL;
130 }
131
132 int track_tempfile( MSIPACKAGE *package, LPCWSTR path )
133 {
134     MSITEMPFILE *temp;
135
136     TRACE("%s\n", debugstr_w(path));
137
138     LIST_FOR_EACH_ENTRY( temp, &package->tempfiles, MSITEMPFILE, entry )
139         if (!lstrcmpW( path, temp->Path ))
140             return 0;
141
142     temp = msi_alloc_zero( sizeof (MSITEMPFILE) );
143     if (!temp)
144         return -1;
145
146     list_add_head( &package->tempfiles, &temp->entry );
147     temp->Path = strdupW( path );
148
149     return 0;
150 }
151
152 MSIFOLDER *get_loaded_folder( MSIPACKAGE *package, LPCWSTR dir )
153 {
154     MSIFOLDER *folder;
155
156     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
157     {
158         if (lstrcmpW( dir, folder->Directory )==0)
159             return folder;
160     }
161     return NULL;
162 }
163
164 void msi_reset_folders( MSIPACKAGE *package, BOOL source )
165 {
166     MSIFOLDER *folder;
167
168     LIST_FOR_EACH_ENTRY( folder, &package->folders, MSIFOLDER, entry )
169     {
170         if ( source )
171         {
172             msi_free( folder->ResolvedSource );
173             folder->ResolvedSource = NULL;
174         }
175         else
176         {
177             msi_free( folder->ResolvedTarget );
178             folder->ResolvedTarget = NULL;
179         }
180     }
181 }
182
183 static LPWSTR get_source_root( MSIPACKAGE *package )
184 {
185     LPWSTR path, p;
186
187     path = msi_dup_property( package, cszSourceDir );
188     if (path)
189         return path;
190
191     path = msi_dup_property( package, cszDatabase );
192     if (path)
193     {
194         p = strrchrW(path,'\\');
195         if (p)
196             *(p+1) = 0;
197     }
198     return path;
199 }
200
201 /*
202  * clean_spaces_from_path()
203  *
204  * removes spaces from the beginning and end of path segments
205  * removes multiple \\ characters
206  */
207 static void clean_spaces_from_path( LPWSTR p )
208 {
209     LPWSTR q = p;
210     int n, len = 0;
211
212     while (1)
213     {
214         /* copy until the end of the string or a space */
215         while (*p != ' ' && (*q = *p))
216         {
217             p++, len++;
218             /* reduce many backslashes to one */
219             if (*p != '\\' || *q != '\\')
220                 q++;
221         }
222
223         /* quit at the end of the string */
224         if (!*p)
225             break;
226
227         /* count the number of spaces */
228         n = 0;
229         while (p[n] == ' ')
230             n++;
231
232         /* if it's leading or trailing space, skip it */
233         if ( len == 0 || p[-1] == '\\' || p[n] == '\\' )
234             p += n;
235         else  /* copy n spaces */
236             while (n && (*q++ = *p++)) n--;
237     }
238 }
239
240 LPWSTR resolve_file_source(MSIPACKAGE *package, MSIFILE *file)
241 {
242     LPWSTR p, path;
243
244     TRACE("Working to resolve source of file %s\n", debugstr_w(file->File));
245
246     if (file->IsCompressed)
247         return NULL;
248
249     p = resolve_folder(package, file->Component->Directory,
250                        TRUE, FALSE, TRUE, NULL);
251     path = build_directory_name(2, p, file->ShortName);
252
253     if (file->LongName &&
254         GetFileAttributesW(path) == INVALID_FILE_ATTRIBUTES)
255     {
256         msi_free(path);
257         path = build_directory_name(2, p, file->LongName);
258     }
259
260     msi_free(p);
261
262     TRACE("file %s source resolves to %s\n", debugstr_w(file->File),
263           debugstr_w(path));
264
265     return path;
266 }
267
268 LPWSTR resolve_folder(MSIPACKAGE *package, LPCWSTR name, BOOL source, 
269                       BOOL set_prop, BOOL load_prop, MSIFOLDER **folder)
270 {
271     MSIFOLDER *f;
272     LPWSTR p, path = NULL, parent;
273
274     TRACE("Working to resolve %s\n",debugstr_w(name));
275
276     if (!name)
277         return NULL;
278
279     if (!lstrcmpW(name,cszSourceDir))
280         name = cszTargetDir;
281
282     f = get_loaded_folder( package, name );
283     if (!f)
284         return NULL;
285
286     /* special resolving for Target and Source root dir */
287     if (!strcmpW(name,cszTargetDir))
288     {
289         if (!f->ResolvedTarget && !f->Property)
290         {
291             LPWSTR check_path;
292             check_path = msi_dup_property( package, cszTargetDir );
293             if (!check_path)
294             {
295                 check_path = msi_dup_property( package, cszRootDrive );
296                 if (set_prop)
297                     MSI_SetPropertyW(package,cszTargetDir,check_path);
298             }
299
300             /* correct misbuilt target dir */
301             path = build_directory_name(2, check_path, NULL);
302             clean_spaces_from_path( path );
303             if (strcmpiW(path,check_path)!=0)
304                 MSI_SetPropertyW(package,cszTargetDir,path);
305             msi_free(check_path);
306
307             f->ResolvedTarget = path;
308         }
309
310         if (!f->ResolvedSource)
311             f->ResolvedSource = get_source_root( package );
312     }
313
314     if (folder)
315         *folder = f;
316
317     if (!source && f->ResolvedTarget)
318     {
319         path = strdupW( f->ResolvedTarget );
320         TRACE("   already resolved to %s\n",debugstr_w(path));
321         return path;
322     }
323
324     if (source && f->ResolvedSource)
325     {
326         path = strdupW( f->ResolvedSource );
327         TRACE("   (source)already resolved to %s\n",debugstr_w(path));
328         return path;
329     }
330
331     if (!source && f->Property)
332     {
333         path = build_directory_name( 2, f->Property, NULL );
334
335         TRACE("   internally set to %s\n",debugstr_w(path));
336         if (set_prop)
337             MSI_SetPropertyW( package, name, path );
338         return path;
339     }
340
341     if (!source && load_prop && (path = msi_dup_property( package, name )))
342     {
343         f->ResolvedTarget = strdupW( path );
344         TRACE("   property set to %s\n", debugstr_w(path));
345         return path;
346     }
347
348     if (!f->Parent)
349         return path;
350
351     parent = f->Parent;
352
353     TRACE(" ! Parent is %s\n", debugstr_w(parent));
354
355     p = resolve_folder(package, parent, source, set_prop, load_prop, NULL);
356     if (!source)
357     {
358         TRACE("   TargetDefault = %s\n", debugstr_w(f->TargetDefault));
359
360         path = build_directory_name( 3, p, f->TargetDefault, NULL );
361         clean_spaces_from_path( path );
362         f->ResolvedTarget = strdupW( path );
363         TRACE("target -> %s\n", debugstr_w(path));
364         if (set_prop)
365             MSI_SetPropertyW(package,name,path);
366     }
367     else
368     {
369         path = NULL;
370
371         if (package->WordCount & msidbSumInfoSourceTypeCompressed)
372             path = get_source_root( package );
373         else if (package->WordCount & msidbSumInfoSourceTypeSFN)
374             path = build_directory_name( 3, p, f->SourceShortPath, NULL );
375         else
376             path = build_directory_name( 3, p, f->SourceLongPath, NULL );
377
378         TRACE("source -> %s\n", debugstr_w(path));
379         f->ResolvedSource = strdupW( path );
380     }
381     msi_free(p);
382
383     return path;
384 }
385
386 /* wrapper to resist a need for a full rewrite right now */
387 DWORD deformat_string(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data )
388 {
389     if (ptr)
390     {
391         MSIRECORD *rec = MSI_CreateRecord(1);
392         DWORD size = 0;
393
394         MSI_RecordSetStringW(rec,0,ptr);
395         MSI_FormatRecordW(package,rec,NULL,&size);
396
397         size++;
398         *data = msi_alloc(size*sizeof(WCHAR));
399         if (size > 1)
400             MSI_FormatRecordW(package,rec,*data,&size);
401         else
402             *data[0] = 0;
403
404         msiobj_release( &rec->hdr );
405         return sizeof(WCHAR)*size;
406     }
407
408     *data = NULL;
409     return 0;
410 }
411
412 UINT schedule_action(MSIPACKAGE *package, UINT script, LPCWSTR action)
413 {
414     UINT count;
415     LPWSTR *newbuf = NULL;
416     if (script >= TOTAL_SCRIPTS)
417     {
418         FIXME("Unknown script requested %i\n",script);
419         return ERROR_FUNCTION_FAILED;
420     }
421     TRACE("Scheduling Action %s in script %i\n",debugstr_w(action), script);
422     
423     count = package->script->ActionCount[script];
424     package->script->ActionCount[script]++;
425     if (count != 0)
426         newbuf = msi_realloc( package->script->Actions[script],
427                         package->script->ActionCount[script]* sizeof(LPWSTR));
428     else
429         newbuf = msi_alloc( sizeof(LPWSTR));
430
431     newbuf[count] = strdupW(action);
432     package->script->Actions[script] = newbuf;
433
434    return ERROR_SUCCESS;
435 }
436
437 void msi_free_action_script(MSIPACKAGE *package, UINT script)
438 {
439     int i;
440     for (i = 0; i < package->script->ActionCount[script]; i++)
441         msi_free(package->script->Actions[script][i]);
442
443     msi_free(package->script->Actions[script]);
444     package->script->Actions[script] = NULL;
445     package->script->ActionCount[script] = 0;
446 }
447
448 static void remove_tracked_tempfiles(MSIPACKAGE* package)
449 {
450     struct list *item, *cursor;
451
452     LIST_FOR_EACH_SAFE( item, cursor, &package->tempfiles )
453     {
454         MSITEMPFILE *temp = LIST_ENTRY( item, MSITEMPFILE, entry );
455
456         list_remove( &temp->entry );
457         TRACE("deleting temp file %s\n", debugstr_w( temp->Path ));
458         if (!DeleteFileW( temp->Path ))
459             ERR("failed to delete %s\n", debugstr_w( temp->Path ));
460         msi_free( temp->Path );
461         msi_free( temp );
462     }
463 }
464
465 static void free_feature( MSIFEATURE *feature )
466 {
467     struct list *item, *cursor;
468
469     LIST_FOR_EACH_SAFE( item, cursor, &feature->Children )
470     {
471         FeatureList *fl = LIST_ENTRY( item, FeatureList, entry );
472         list_remove( &fl->entry );
473         msi_free( fl );
474     }
475
476     LIST_FOR_EACH_SAFE( item, cursor, &feature->Components )
477     {
478         ComponentList *cl = LIST_ENTRY( item, ComponentList, entry );
479         list_remove( &cl->entry );
480         msi_free( cl );
481     }
482     msi_free( feature->Feature );
483     msi_free( feature->Feature_Parent );
484     msi_free( feature->Directory );
485     msi_free( feature->Description );
486     msi_free( feature->Title );
487     msi_free( feature );
488 }
489
490 static void free_extension( MSIEXTENSION *ext )
491 {
492     struct list *item, *cursor;
493
494     LIST_FOR_EACH_SAFE( item, cursor, &ext->verbs )
495     {
496         MSIVERB *verb = LIST_ENTRY( item, MSIVERB, entry );
497
498         list_remove( &verb->entry );
499         msi_free( verb->Verb );
500         msi_free( verb->Command );
501         msi_free( verb->Argument );
502         msi_free( verb );
503     }
504
505     msi_free( ext->Extension );
506     msi_free( ext->ProgIDText );
507     msi_free( ext );
508 }
509
510 /* Called when the package is being closed */
511 void ACTION_free_package_structures( MSIPACKAGE* package)
512 {
513     INT i;
514     struct list *item, *cursor;
515
516     TRACE("Freeing package action data\n");
517
518     remove_tracked_tempfiles(package);
519
520     LIST_FOR_EACH_SAFE( item, cursor, &package->features )
521     {
522         MSIFEATURE *feature = LIST_ENTRY( item, MSIFEATURE, entry );
523         list_remove( &feature->entry );
524         free_feature( feature );
525     }
526
527     LIST_FOR_EACH_SAFE( item, cursor, &package->folders )
528     {
529         MSIFOLDER *folder = LIST_ENTRY( item, MSIFOLDER, entry );
530
531         list_remove( &folder->entry );
532         msi_free( folder->Parent );
533         msi_free( folder->Directory );
534         msi_free( folder->TargetDefault );
535         msi_free( folder->SourceLongPath );
536         msi_free( folder->SourceShortPath );
537         msi_free( folder->ResolvedTarget );
538         msi_free( folder->ResolvedSource );
539         msi_free( folder->Property );
540         msi_free( folder );
541     }
542
543     LIST_FOR_EACH_SAFE( item, cursor, &package->components )
544     {
545         MSICOMPONENT *comp = LIST_ENTRY( item, MSICOMPONENT, entry );
546
547         list_remove( &comp->entry );
548         msi_free( comp->Component );
549         msi_free( comp->ComponentId );
550         msi_free( comp->Directory );
551         msi_free( comp->Condition );
552         msi_free( comp->KeyPath );
553         msi_free( comp->FullKeypath );
554         msi_free( comp );
555     }
556
557     LIST_FOR_EACH_SAFE( item, cursor, &package->files )
558     {
559         MSIFILE *file = LIST_ENTRY( item, MSIFILE, entry );
560
561         list_remove( &file->entry );
562         msi_free( file->File );
563         msi_free( file->FileName );
564         msi_free( file->ShortName );
565         msi_free( file->LongName );
566         msi_free( file->Version );
567         msi_free( file->Language );
568         msi_free( file->TargetPath );
569         msi_free( file );
570     }
571
572     /* clean up extension, progid, class and verb structures */
573     LIST_FOR_EACH_SAFE( item, cursor, &package->classes )
574     {
575         MSICLASS *cls = LIST_ENTRY( item, MSICLASS, entry );
576
577         list_remove( &cls->entry );
578         msi_free( cls->clsid );
579         msi_free( cls->Context );
580         msi_free( cls->Description );
581         msi_free( cls->FileTypeMask );
582         msi_free( cls->IconPath );
583         msi_free( cls->DefInprocHandler );
584         msi_free( cls->DefInprocHandler32 );
585         msi_free( cls->Argument );
586         msi_free( cls->ProgIDText );
587         msi_free( cls );
588     }
589
590     LIST_FOR_EACH_SAFE( item, cursor, &package->extensions )
591     {
592         MSIEXTENSION *ext = LIST_ENTRY( item, MSIEXTENSION, entry );
593
594         list_remove( &ext->entry );
595         free_extension( ext );
596     }
597
598     LIST_FOR_EACH_SAFE( item, cursor, &package->progids )
599     {
600         MSIPROGID *progid = LIST_ENTRY( item, MSIPROGID, entry );
601
602         list_remove( &progid->entry );
603         msi_free( progid->ProgID );
604         msi_free( progid->Description );
605         msi_free( progid->IconPath );
606         msi_free( progid );
607     }
608
609     LIST_FOR_EACH_SAFE( item, cursor, &package->mimes )
610     {
611         MSIMIME *mt = LIST_ENTRY( item, MSIMIME, entry );
612
613         list_remove( &mt->entry );
614         msi_free( mt->clsid );
615         msi_free( mt->ContentType );
616         msi_free( mt );
617     }
618
619     LIST_FOR_EACH_SAFE( item, cursor, &package->appids )
620     {
621         MSIAPPID *appid = LIST_ENTRY( item, MSIAPPID, entry );
622
623         list_remove( &appid->entry );
624         msi_free( appid->AppID );
625         msi_free( appid->RemoteServerName );
626         msi_free( appid->LocalServer );
627         msi_free( appid->ServiceParameters );
628         msi_free( appid->DllSurrogate );
629         msi_free( appid );
630     }
631
632     LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_info )
633     {
634         MSISOURCELISTINFO *info = LIST_ENTRY( item, MSISOURCELISTINFO, entry );
635
636         list_remove( &info->entry );
637         msi_free( info->value );
638         msi_free( info );
639     }
640
641     LIST_FOR_EACH_SAFE( item, cursor, &package->sourcelist_media )
642     {
643         MSIMEDIADISK *info = LIST_ENTRY( item, MSIMEDIADISK, entry );
644
645         list_remove( &info->entry );
646         msi_free( info->volume_label );
647         msi_free( info->disk_prompt );
648         msi_free( info );
649     }
650
651     if (package->script)
652     {
653         for (i = 0; i < TOTAL_SCRIPTS; i++)
654             msi_free_action_script(package, i);
655
656         for (i = 0; i < package->script->UniqueActionsCount; i++)
657             msi_free(package->script->UniqueActions[i]);
658
659         msi_free(package->script->UniqueActions);
660         msi_free(package->script);
661     }
662
663     msi_free(package->BaseURL);
664     msi_free(package->PackagePath);
665     msi_free(package->ProductCode);
666     msi_free(package->ActionFormat);
667     msi_free(package->LastAction);
668
669     /* cleanup control event subscriptions */
670     ControlEvent_CleanupSubscriptions(package);
671 }
672
673 /*
674  *  build_directory_name()
675  *
676  *  This function is to save messing round with directory names
677  *  It handles adding backslashes between path segments, 
678  *   and can add \ at the end of the directory name if told to.
679  *
680  *  It takes a variable number of arguments.
681  *  It always allocates a new string for the result, so make sure
682  *   to free the return value when finished with it.
683  *
684  *  The first arg is the number of path segments that follow.
685  *  The arguments following count are a list of path segments.
686  *  A path segment may be NULL.
687  *
688  *  Path segments will be added with a \ separating them.
689  *  A \ will not be added after the last segment, however if the
690  *    last segment is NULL, then the last character will be a \
691  * 
692  */
693 LPWSTR build_directory_name(DWORD count, ...)
694 {
695     DWORD sz = 1, i;
696     LPWSTR dir;
697     va_list va;
698
699     va_start(va,count);
700     for(i=0; i<count; i++)
701     {
702         LPCWSTR str = va_arg(va,LPCWSTR);
703         if (str)
704             sz += strlenW(str) + 1;
705     }
706     va_end(va);
707
708     dir = msi_alloc(sz*sizeof(WCHAR));
709     dir[0]=0;
710
711     va_start(va,count);
712     for(i=0; i<count; i++)
713     {
714         LPCWSTR str = va_arg(va,LPCWSTR);
715         if (!str)
716             continue;
717         strcatW(dir, str);
718         if( ((i+1)!=count) && dir[strlenW(dir)-1]!='\\')
719             strcatW(dir, cszbs);
720     }
721     return dir;
722 }
723
724 /***********************************************************************
725  *            create_full_pathW
726  *
727  * Recursively create all directories in the path.
728  *
729  * shamelessly stolen from setupapi/queue.c
730  */
731 BOOL create_full_pathW(const WCHAR *path)
732 {
733     BOOL ret = TRUE;
734     int len;
735     WCHAR *new_path;
736
737     new_path = msi_alloc( (strlenW(path) + 1) * sizeof(WCHAR));
738
739     strcpyW(new_path, path);
740
741     while((len = strlenW(new_path)) && new_path[len - 1] == '\\')
742     new_path[len - 1] = 0;
743
744     while(!CreateDirectoryW(new_path, NULL))
745     {
746         WCHAR *slash;
747         DWORD last_error = GetLastError();
748         if(last_error == ERROR_ALREADY_EXISTS)
749             break;
750
751         if(last_error != ERROR_PATH_NOT_FOUND)
752         {
753             ret = FALSE;
754             break;
755         }
756
757         if(!(slash = strrchrW(new_path, '\\')))
758         {
759             ret = FALSE;
760             break;
761         }
762
763         len = slash - new_path;
764         new_path[len] = 0;
765         if(!create_full_pathW(new_path))
766         {
767             ret = FALSE;
768             break;
769         }
770         new_path[len] = '\\';
771     }
772
773     msi_free(new_path);
774     return ret;
775 }
776
777 void ui_progress(MSIPACKAGE *package, int a, int b, int c, int d )
778 {
779     MSIRECORD * row;
780
781     row = MSI_CreateRecord(4);
782     MSI_RecordSetInteger(row,1,a);
783     MSI_RecordSetInteger(row,2,b);
784     MSI_RecordSetInteger(row,3,c);
785     MSI_RecordSetInteger(row,4,d);
786     MSI_ProcessMessage(package, INSTALLMESSAGE_PROGRESS, row);
787     msiobj_release(&row->hdr);
788
789     msi_dialog_check_messages(NULL);
790 }
791
792 void ui_actiondata(MSIPACKAGE *package, LPCWSTR action, MSIRECORD * record)
793 {
794     static const WCHAR Query_t[] = 
795         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
796          '`','A','c','t','i','o', 'n','T','e','x','t','`',' ',
797          'W','H','E','R','E',' ', '`','A','c','t','i','o','n','`',' ','=', 
798          ' ','\'','%','s','\'',0};
799     WCHAR message[1024];
800     MSIRECORD * row = 0;
801     DWORD size;
802
803     if (!package->LastAction || strcmpW(package->LastAction,action))
804     {
805         row = MSI_QueryGetRecord(package->db, Query_t, action);
806         if (!row)
807             return;
808
809         if (MSI_RecordIsNull(row,3))
810         {
811             msiobj_release(&row->hdr);
812             return;
813         }
814
815         /* update the cached actionformat */
816         msi_free(package->ActionFormat);
817         package->ActionFormat = msi_dup_record_field(row,3);
818
819         msi_free(package->LastAction);
820         package->LastAction = strdupW(action);
821
822         msiobj_release(&row->hdr);
823     }
824
825     MSI_RecordSetStringW(record,0,package->ActionFormat);
826     size = 1024;
827     MSI_FormatRecordW(package,record,message,&size);
828
829     row = MSI_CreateRecord(1);
830     MSI_RecordSetStringW(row,1,message);
831  
832     MSI_ProcessMessage(package, INSTALLMESSAGE_ACTIONDATA, row);
833
834     msiobj_release(&row->hdr);
835 }
836
837 BOOL ACTION_VerifyComponentForAction( const MSICOMPONENT* comp, INSTALLSTATE check )
838 {
839     if (!comp)
840         return FALSE;
841
842     if (comp->ActionRequest == check)
843         return TRUE;
844     else
845         return FALSE;
846 }
847
848 BOOL ACTION_VerifyFeatureForAction( const MSIFEATURE* feature, INSTALLSTATE check )
849 {
850     if (!feature)
851         return FALSE;
852
853     if (feature->ActionRequest == check)
854         return TRUE;
855     else
856         return FALSE;
857 }
858
859 void reduce_to_longfilename(WCHAR* filename)
860 {
861     LPWSTR p = strchrW(filename,'|');
862     if (p)
863         memmove(filename, p+1, (strlenW(p+1)+1)*sizeof(WCHAR));
864 }
865
866 void reduce_to_shortfilename(WCHAR* filename)
867 {
868     LPWSTR p = strchrW(filename,'|');
869     if (p)
870         *p = 0;
871 }
872
873 LPWSTR create_component_advertise_string(MSIPACKAGE* package, 
874                 MSICOMPONENT* component, LPCWSTR feature)
875 {
876     static const WCHAR fmt[] = {'%','s','%','s','%','c','%','s',0};
877     WCHAR productid_85[21], component_85[21];
878     LPWSTR output = NULL;
879     DWORD sz = 0;
880     GUID clsid;
881
882     /* > is used if there is a component GUID and < if not.  */
883
884     productid_85[0] = 0;
885     component_85[0] = 0;
886
887     CLSIDFromString(package->ProductCode, &clsid);
888     encode_base85_guid(&clsid, productid_85);
889
890     if (component)
891     {
892         CLSIDFromString(component->ComponentId, &clsid);
893         encode_base85_guid(&clsid, component_85);
894     }
895
896     TRACE("prod=%s feat=%s comp=%s\n", debugstr_w(productid_85),
897           debugstr_w(feature), debugstr_w(component_85));
898  
899     sz = 20 + lstrlenW(feature) + 20 + 3;
900
901     output = msi_alloc_zero(sz*sizeof(WCHAR));
902
903     sprintfW(output, fmt, productid_85, feature,
904              component?'>':'<', component_85);
905     
906     return output;
907 }
908
909 /* update component state based on a feature change */
910 void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature)
911 {
912     INSTALLSTATE newstate;
913     MSIFEATURE *feature;
914     ComponentList *cl;
915
916     feature = get_loaded_feature(package,szFeature);
917     if (!feature)
918         return;
919
920     newstate = feature->ActionRequest;
921
922     if (newstate == INSTALLSTATE_ABSENT)
923         newstate = INSTALLSTATE_UNKNOWN;
924
925     LIST_FOR_EACH_ENTRY( cl, &feature->Components, ComponentList, entry )
926     {
927         MSICOMPONENT* component = cl->component;
928     
929         TRACE("MODIFYING(%i): Component %s (Installed %i, Action %i, Request %i)\n",
930             newstate, debugstr_w(component->Component), component->Installed, 
931             component->Action, component->ActionRequest);
932         
933         if (!component->Enabled)
934             continue;
935  
936         if (newstate == INSTALLSTATE_LOCAL)
937             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
938         else 
939         {
940             ComponentList *clist;
941             MSIFEATURE *f;
942
943             component->hasLocalFeature = FALSE;
944
945             msi_component_set_state(package, component, newstate);
946
947             /*if any other feature wants is local we need to set it local*/
948             LIST_FOR_EACH_ENTRY( f, &package->features, MSIFEATURE, entry )
949             {
950                 if ( f->ActionRequest != INSTALLSTATE_LOCAL &&
951                      f->ActionRequest != INSTALLSTATE_SOURCE )
952                 {
953                     continue;
954                 }
955
956                 LIST_FOR_EACH_ENTRY( clist, &f->Components, ComponentList, entry )
957                 {
958                     if ( clist->component == component &&
959                          (f->ActionRequest == INSTALLSTATE_LOCAL ||
960                           f->ActionRequest == INSTALLSTATE_SOURCE) )
961                     {
962                         TRACE("Saved by %s\n", debugstr_w(f->Feature));
963                         component->hasLocalFeature = TRUE;
964
965                         if (component->Attributes & msidbComponentAttributesOptional)
966                         {
967                             if (f->Attributes & msidbFeatureAttributesFavorSource)
968                                 msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
969                             else
970                                 msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
971                         }
972                         else if (component->Attributes & msidbComponentAttributesSourceOnly)
973                             msi_component_set_state(package, component, INSTALLSTATE_SOURCE);
974                         else
975                             msi_component_set_state(package, component, INSTALLSTATE_LOCAL);
976                     }
977                 }
978             }
979         }
980         TRACE("Result (%i): Component %s (Installed %i, Action %i, Request %i)\n",
981             newstate, debugstr_w(component->Component), component->Installed, 
982             component->Action, component->ActionRequest);
983     } 
984 }
985
986 UINT register_unique_action(MSIPACKAGE *package, LPCWSTR action)
987 {
988     UINT count;
989     LPWSTR *newbuf = NULL;
990
991     if (!package->script)
992         return FALSE;
993
994     TRACE("Registering Action %s as having fun\n",debugstr_w(action));
995     
996     count = package->script->UniqueActionsCount;
997     package->script->UniqueActionsCount++;
998     if (count != 0)
999         newbuf = msi_realloc( package->script->UniqueActions,
1000                         package->script->UniqueActionsCount* sizeof(LPWSTR));
1001     else
1002         newbuf = msi_alloc( sizeof(LPWSTR));
1003
1004     newbuf[count] = strdupW(action);
1005     package->script->UniqueActions = newbuf;
1006
1007     return ERROR_SUCCESS;
1008 }
1009
1010 BOOL check_unique_action(const MSIPACKAGE *package, LPCWSTR action)
1011 {
1012     INT i;
1013
1014     if (!package->script)
1015         return FALSE;
1016
1017     for (i = 0; i < package->script->UniqueActionsCount; i++)
1018         if (!strcmpW(package->script->UniqueActions[i],action))
1019             return TRUE;
1020
1021     return FALSE;
1022 }
1023
1024 WCHAR* generate_error_string(MSIPACKAGE *package, UINT error, DWORD count, ... )
1025 {
1026     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};
1027
1028     MSIRECORD *rec;
1029     MSIRECORD *row;
1030     DWORD size = 0;
1031     DWORD i;
1032     va_list va;
1033     LPCWSTR str;
1034     LPWSTR data;
1035
1036     row = MSI_QueryGetRecord(package->db, query, error);
1037     if (!row)
1038         return 0;
1039
1040     rec = MSI_CreateRecord(count+2);
1041
1042     str = MSI_RecordGetString(row,1);
1043     MSI_RecordSetStringW(rec,0,str);
1044     msiobj_release( &row->hdr );
1045     MSI_RecordSetInteger(rec,1,error);
1046
1047     va_start(va,count);
1048     for (i = 0; i < count; i++)
1049     {
1050         str = va_arg(va,LPCWSTR);
1051         MSI_RecordSetStringW(rec,(i+2),str);
1052     }
1053     va_end(va);
1054
1055     MSI_FormatRecordW(package,rec,NULL,&size);
1056
1057     size++;
1058     data = msi_alloc(size*sizeof(WCHAR));
1059     if (size > 1)
1060         MSI_FormatRecordW(package,rec,data,&size);
1061     else
1062         data[0] = 0;
1063     msiobj_release( &rec->hdr );
1064     return data;
1065 }
1066
1067 void msi_ui_error( DWORD msg_id, DWORD type )
1068 {
1069     WCHAR text[2048];
1070
1071     static const WCHAR title[] = {
1072         'W','i','n','d','o','w','s',' ','I','n','s','t','a','l','l','e','r',0
1073     };
1074
1075     if (!MsiLoadStringW( -1, msg_id, text, sizeof(text) / sizeof(text[0]),
1076                          MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) ))
1077         return;
1078
1079     MessageBoxW( NULL, text, title, type );
1080 }