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