Don't quote numbers in a query, as it doesn't parse correctly.
[wine] / dlls / msi / custom.c
1 /*
2  * Custom Action processing for 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  * Pages I need
23  *
24 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/summary_list_of_all_custom_action_types.asp
25  */
26
27 #include <stdarg.h>
28 #include <stdio.h>
29
30 #define COBJMACROS
31
32 #include "windef.h"
33 #include "winbase.h"
34 #include "winerror.h"
35 #include "winreg.h"
36 #include "wine/debug.h"
37 #include "fdi.h"
38 #include "msi.h"
39 #include "msidefs.h"
40 #include "msiquery.h"
41 #include "msvcrt/fcntl.h"
42 #include "objbase.h"
43 #include "objidl.h"
44 #include "msipriv.h"
45 #include "winnls.h"
46 #include "winuser.h"
47 #include "shlobj.h"
48 #include "wine/unicode.h"
49 #include "winver.h"
50 #include "action.h"
51
52 WINE_DEFAULT_DEBUG_CHANNEL(msi);
53
54 #define CUSTOM_ACTION_TYPE_MASK 0x3F
55 static const WCHAR c_collen[] = {'C',':','\\',0};
56 static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
57
58 typedef struct tagMSIRUNNINGACTION
59 {
60     struct list entry;
61     HANDLE handle;
62     BOOL   process;
63     LPWSTR name;
64 } MSIRUNNINGACTION;
65
66 static UINT HANDLE_CustomType1(MSIPACKAGE *package, LPCWSTR source,
67                                LPCWSTR target, const INT type, LPCWSTR action);
68 static UINT HANDLE_CustomType2(MSIPACKAGE *package, LPCWSTR source,
69                                LPCWSTR target, const INT type, LPCWSTR action);
70 static UINT HANDLE_CustomType18(MSIPACKAGE *package, LPCWSTR source,
71                                 LPCWSTR target, const INT type, LPCWSTR action);
72 static UINT HANDLE_CustomType19(MSIPACKAGE *package, LPCWSTR source,
73                                 LPCWSTR target, const INT type, LPCWSTR action);
74 static UINT HANDLE_CustomType50(MSIPACKAGE *package, LPCWSTR source,
75                                 LPCWSTR target, const INT type, LPCWSTR action);
76 static UINT HANDLE_CustomType34(MSIPACKAGE *package, LPCWSTR source,
77                                 LPCWSTR target, const INT type, LPCWSTR action);
78
79
80 static BOOL check_execution_scheduling_options(MSIPACKAGE *package, LPCWSTR action, UINT options)
81 {
82     if (!package->script)
83         return TRUE;
84
85     if ((options & msidbCustomActionTypeClientRepeat) == 
86             msidbCustomActionTypeClientRepeat)
87     {
88         if (!(package->script->InWhatSequence & SEQUENCE_UI &&
89             package->script->InWhatSequence & SEQUENCE_EXEC))
90         {
91             TRACE("Skipping action due to dbCustomActionTypeClientRepeat option.\n");
92             return FALSE;
93         }
94     }
95     else if (options & msidbCustomActionTypeFirstSequence)
96     {
97         if (package->script->InWhatSequence & SEQUENCE_UI &&
98             package->script->InWhatSequence & SEQUENCE_EXEC )
99         {
100             TRACE("Skipping action due to msidbCustomActionTypeFirstSequence option.\n");
101             return FALSE;
102         }
103     }
104     else if (options & msidbCustomActionTypeOncePerProcess)
105     {
106         if (check_unique_action(package,action))
107         {
108             TRACE("Skipping action due to msidbCustomActionTypeOncePerProcess option.\n");
109             return FALSE;
110         }
111         else
112             register_unique_action(package,action);
113     }
114
115     return TRUE;
116 }
117
118 UINT ACTION_CustomAction(MSIPACKAGE *package,LPCWSTR action, BOOL execute)
119 {
120     UINT rc = ERROR_SUCCESS;
121     MSIRECORD * row = 0;
122     static const WCHAR ExecSeqQuery[] =
123     {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
124      '`','C','u','s','t','o' ,'m','A','c','t','i','o','n','`',
125      ' ','W','H','E','R','E',' ','`','A','c','t','i' ,'o','n','`',' ',
126      '=',' ','\'','%','s','\'',0};
127     UINT type;
128     LPWSTR source;
129     LPWSTR target;
130     WCHAR *deformated=NULL;
131
132     row = MSI_QueryGetRecord( package->db, ExecSeqQuery, action );
133     if (!row)
134         return ERROR_CALL_NOT_IMPLEMENTED;
135
136     type = MSI_RecordGetInteger(row,2);
137
138     source = load_dynamic_stringW(row,3);
139     target = load_dynamic_stringW(row,4);
140
141     TRACE("Handling custom action %s (%x %s %s)\n",debugstr_w(action),type,
142           debugstr_w(source), debugstr_w(target));
143
144     /* handle some of the deferred actions */
145     if (type & msidbCustomActionTypeTSAware)
146         FIXME("msidbCustomActionTypeTSAware not handled\n");
147
148     if (type & msidbCustomActionTypeInScript)
149     {
150         if (type & msidbCustomActionTypeNoImpersonate)
151             FIXME("msidbCustomActionTypeNoImpersonate not handled\n");
152
153         if (type & msidbCustomActionTypeRollback)
154         {
155             FIXME("Rollback only action... rollbacks not supported yet\n");
156             schedule_action(package, ROLLBACK_SCRIPT, action);
157             rc = ERROR_SUCCESS;
158             goto end;
159         }
160         if (!execute)
161         {
162             if (type & msidbCustomActionTypeCommit)
163             {
164                 TRACE("Deferring Commit Action!\n");
165                 schedule_action(package, COMMIT_SCRIPT, action);
166             }
167             else
168             {
169                 TRACE("Deferring Action!\n");
170                 schedule_action(package, INSTALL_SCRIPT, action);
171             }
172
173             rc = ERROR_SUCCESS;
174             goto end;
175         }
176         else
177         {
178             /*Set ActionData*/
179
180             static const WCHAR szActionData[] = {
181             'C','u','s','t','o','m','A','c','t','i','o','n','D','a','t','a',0};
182             static const WCHAR szBlank[] = {0};
183             LPWSTR actiondata = msi_dup_property( package, action );
184             if (actiondata)
185                 MSI_SetPropertyW(package,szActionData,actiondata);
186             else
187                 MSI_SetPropertyW(package,szActionData,szBlank);
188             msi_free(actiondata);
189         }
190     }
191     else if (!check_execution_scheduling_options(package,action,type))
192     {
193         rc = ERROR_SUCCESS;
194         goto end;
195     }
196
197     switch (type & CUSTOM_ACTION_TYPE_MASK)
198     {
199         case 1: /* DLL file stored in a Binary table stream */
200             rc = HANDLE_CustomType1(package,source,target,type,action);
201             break;
202         case 2: /* EXE file stored in a Binary table strem */
203             rc = HANDLE_CustomType2(package,source,target,type,action);
204             break;
205         case 18: /*EXE file installed with package */
206             rc = HANDLE_CustomType18(package,source,target,type,action);
207             break;
208         case 19: /* Error that halts install */
209             rc = HANDLE_CustomType19(package,source,target,type,action);
210             break;
211         case 50: /*EXE file specified by a property value */
212             rc = HANDLE_CustomType50(package,source,target,type,action);
213             break;
214         case 34: /*EXE to be run in specified directory */
215             rc = HANDLE_CustomType34(package,source,target,type,action);
216             break;
217         case 35: /* Directory set with formatted text. */
218             deformat_string(package,target,&deformated);
219             MSI_SetTargetPathW(package, source, deformated);
220             msi_free(deformated);
221             break;
222         case 51: /* Property set with formatted text. */
223             deformat_string(package,target,&deformated);
224             rc = MSI_SetPropertyW(package,source,deformated);
225             msi_free(deformated);
226             break;
227         default:
228             FIXME("UNHANDLED ACTION TYPE %i (%s %s)\n",
229              type & CUSTOM_ACTION_TYPE_MASK, debugstr_w(source),
230              debugstr_w(target));
231     }
232
233 end:
234     msi_free(source);
235     msi_free(target);
236     msiobj_release(&row->hdr);
237     return rc;
238 }
239
240
241 static UINT store_binary_to_temp(MSIPACKAGE *package, LPCWSTR source, 
242                                 LPWSTR tmp_file)
243 {
244     DWORD sz=MAX_PATH;
245     static const WCHAR f1[] = {'m','s','i',0};
246     WCHAR fmt[MAX_PATH];
247
248     if (MSI_GetPropertyW(package, cszTempFolder, fmt, &sz) != ERROR_SUCCESS)
249         GetTempPathW(MAX_PATH,fmt);
250
251     if (GetTempFileNameW(fmt,f1,0,tmp_file) == 0)
252     {
253         TRACE("Unable to create file\n");
254         return ERROR_FUNCTION_FAILED;
255     }
256     else
257     {
258         /* write out the file */
259         UINT rc;
260         MSIRECORD * row = 0;
261         static const WCHAR fmt[] =
262         {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
263          '`','B','i' ,'n','a','r','y','`',' ','W','H','E','R','E',
264          ' ','`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0};
265         HANDLE the_file;
266         CHAR buffer[1024];
267
268         the_file = CreateFileW(tmp_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
269                            FILE_ATTRIBUTE_NORMAL, NULL);
270     
271         if (the_file == INVALID_HANDLE_VALUE)
272             return ERROR_FUNCTION_FAILED;
273
274         row = MSI_QueryGetRecord(package->db, fmt, source);
275         if (!row)
276             return ERROR_FUNCTION_FAILED;
277
278         do 
279         {
280             DWORD write;
281             sz = 1024;
282             rc = MSI_RecordReadStream(row,2,buffer,&sz);
283             if (rc != ERROR_SUCCESS)
284             {
285                 ERR("Failed to get stream\n");
286                 CloseHandle(the_file);  
287                 DeleteFileW(tmp_file);
288                 break;
289             }
290             WriteFile(the_file,buffer,sz,&write,NULL);
291         } while (sz == 1024);
292
293         CloseHandle(the_file);
294
295         msiobj_release(&row->hdr);
296     }
297
298     return ERROR_SUCCESS;
299 }
300
301 static void file_running_action(MSIPACKAGE* package, HANDLE Handle, 
302                                 BOOL process, LPCWSTR name)
303 {
304     MSIRUNNINGACTION *action;
305
306     action = msi_alloc( sizeof(MSIRUNNINGACTION) );
307
308     action->handle = Handle;
309     action->process = process;
310     action->name = strdupW(name);
311
312     list_add_tail( &package->RunningActions, &action->entry );
313 }
314
315 static UINT process_action_return_value(UINT type, HANDLE ThreadHandle)
316 {
317     DWORD rc=0;
318     
319     if (type == 2)
320     {
321         GetExitCodeProcess(ThreadHandle,&rc);
322     
323         if (rc == 0)
324             return ERROR_SUCCESS;
325         else
326             return ERROR_FUNCTION_FAILED;
327     }
328
329     GetExitCodeThread(ThreadHandle,&rc);
330
331     switch (rc)
332     {
333         case ERROR_FUNCTION_NOT_CALLED:
334         case ERROR_SUCCESS:
335         case ERROR_INSTALL_USEREXIT:
336         case ERROR_INSTALL_FAILURE:
337             return rc;
338         case ERROR_NO_MORE_ITEMS:
339             return ERROR_SUCCESS;
340         default:
341             ERR("Invalid Return Code %lx\n",rc);
342             return ERROR_INSTALL_FAILURE;
343     }
344 }
345
346 static UINT process_handle(MSIPACKAGE* package, UINT type, 
347                            HANDLE ThreadHandle, HANDLE ProcessHandle,
348                            LPCWSTR Name, BOOL *finished)
349 {
350     UINT rc = ERROR_SUCCESS;
351
352     if (!(type & msidbCustomActionTypeAsync))
353     {
354         /* synchronous */
355         TRACE("Synchronous Execution of action %s\n",debugstr_w(Name));
356         if (ProcessHandle)
357             msi_dialog_check_messages(ProcessHandle);
358         else
359             msi_dialog_check_messages(ThreadHandle);
360
361         if (!(type & msidbCustomActionTypeContinue))
362         {
363             if (ProcessHandle)
364                 rc = process_action_return_value(2,ProcessHandle);
365             else
366                 rc = process_action_return_value(1,ThreadHandle);
367         }
368
369         CloseHandle(ThreadHandle);
370         if (ProcessHandle)
371             CloseHandle(ProcessHandle);
372         if (finished)
373             *finished = TRUE;
374     }
375     else 
376     {
377         TRACE("Asynchronous Execution of action %s\n",debugstr_w(Name));
378         /* asynchronous */
379         if (type & msidbCustomActionTypeContinue)
380         {
381             if (ProcessHandle)
382             {
383                 file_running_action(package, ProcessHandle, TRUE, Name);
384                 CloseHandle(ThreadHandle);
385             }
386             else
387             file_running_action(package, ThreadHandle, FALSE, Name);
388         }
389         else
390         {
391             CloseHandle(ThreadHandle);
392             if (ProcessHandle)
393                 CloseHandle(ProcessHandle);
394         }
395         if (finished)
396             *finished = FALSE;
397     }
398
399     return rc;
400 }
401
402
403 typedef UINT __stdcall CustomEntry(MSIHANDLE);
404
405 typedef struct 
406 {
407         MSIPACKAGE *package;
408         WCHAR *target;
409         WCHAR *source;
410 } thread_struct;
411
412 static DWORD WINAPI ACTION_CallDllFunction(thread_struct *stuff)
413 {
414     HANDLE hModule;
415     LPSTR proc;
416     CustomEntry *fn;
417     DWORD rc = ERROR_SUCCESS;
418
419     TRACE("calling function (%s, %s) \n", debugstr_w(stuff->source),
420           debugstr_w(stuff->target));
421
422     hModule = LoadLibraryW(stuff->source);
423     if (hModule)
424     {
425         proc = strdupWtoA( stuff->target );
426         fn = (CustomEntry*)GetProcAddress(hModule,proc);
427         if (fn)
428         {
429             MSIHANDLE hPackage;
430             MSIPACKAGE *package = stuff->package;
431
432             TRACE("Calling function %s\n", proc);
433             hPackage = alloc_msihandle( &package->hdr );
434             if (hPackage)
435             {
436                 rc = fn( hPackage );
437                 MsiCloseHandle( hPackage );
438             }
439             else
440                 ERR("Handle for object %p not found\n", package );
441         }
442         else
443             ERR("Cannot load functon\n");
444
445         msi_free(proc);
446         FreeLibrary(hModule);
447     }
448     else
449         ERR("Unable to load library\n");
450     msiobj_release( &stuff->package->hdr );
451     msi_free(stuff->source);
452     msi_free(stuff->target);
453     msi_free(stuff);
454     return rc;
455 }
456
457 static DWORD WINAPI DllThread(LPVOID info)
458 {
459     thread_struct *stuff;
460     DWORD rc = 0;
461   
462     TRACE("MSI Thread (0x%lx) started for custom action\n",
463                         GetCurrentThreadId());
464     
465     stuff = (thread_struct*)info;
466     rc = ACTION_CallDllFunction(stuff);
467
468     TRACE("MSI Thread (0x%lx) finished (rc %li)\n",GetCurrentThreadId(), rc);
469     /* clse all handles for this thread */
470     MsiCloseAllHandles();
471     return rc;
472 }
473
474 static UINT HANDLE_CustomType1(MSIPACKAGE *package, LPCWSTR source, 
475                                LPCWSTR target, const INT type, LPCWSTR action)
476 {
477     WCHAR tmp_file[MAX_PATH];
478     thread_struct *info;
479     DWORD ThreadId;
480     HANDLE ThreadHandle;
481     UINT rc = ERROR_SUCCESS;
482     BOOL finished = FALSE;
483
484     store_binary_to_temp(package, source, tmp_file);
485
486     TRACE("Calling function %s from %s\n",debugstr_w(target),
487           debugstr_w(tmp_file));
488
489     if (!strchrW(tmp_file,'.'))
490     {
491         static const WCHAR dot[]={'.',0};
492         strcatW(tmp_file,dot);
493     } 
494
495     info = msi_alloc( sizeof(*info) );
496     msiobj_addref( &package->hdr );
497     info->package = package;
498     info->target = strdupW(target);
499     info->source = strdupW(tmp_file);
500
501     ThreadHandle = CreateThread(NULL,0,DllThread,(LPVOID)info,0,&ThreadId);
502
503     rc = process_handle(package, type, ThreadHandle, NULL, action, &finished );
504
505     if (!finished)
506         track_tempfile(package, tmp_file, tmp_file);
507     else
508         DeleteFileW(tmp_file);
509  
510     return rc;
511 }
512
513 static UINT HANDLE_CustomType2(MSIPACKAGE *package, LPCWSTR source, 
514                                LPCWSTR target, const INT type, LPCWSTR action)
515 {
516     WCHAR tmp_file[MAX_PATH];
517     STARTUPINFOW si;
518     PROCESS_INFORMATION info;
519     BOOL rc;
520     INT len;
521     WCHAR *deformated;
522     WCHAR *cmd;
523     static const WCHAR spc[] = {' ',0};
524     UINT prc = ERROR_SUCCESS;
525     BOOL finished = FALSE;
526
527     memset(&si,0,sizeof(STARTUPINFOW));
528
529     store_binary_to_temp(package, source, tmp_file);
530
531     deformat_string(package,target,&deformated);
532
533     len = strlenW(tmp_file)+2;
534
535     if (deformated)
536         len += strlenW(deformated);
537    
538     cmd = msi_alloc(sizeof(WCHAR)*len);
539
540     strcpyW(cmd,tmp_file);
541     if (deformated)
542     {
543         strcatW(cmd,spc);
544         strcatW(cmd,deformated);
545
546         msi_free(deformated);
547     }
548
549     TRACE("executing exe %s \n",debugstr_w(cmd));
550
551     rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
552                   c_collen, &si, &info);
553
554     msi_free(cmd);
555
556     if ( !rc )
557     {
558         ERR("Unable to execute command\n");
559         return ERROR_SUCCESS;
560     }
561
562     prc = process_handle(package, type, info.hThread, info.hProcess, action, 
563                           &finished);
564
565     if (!finished)
566         track_tempfile(package, tmp_file, tmp_file);
567     else
568         DeleteFileW(tmp_file);
569     
570     return prc;
571 }
572
573 static UINT HANDLE_CustomType18(MSIPACKAGE *package, LPCWSTR source,
574                                 LPCWSTR target, const INT type, LPCWSTR action)
575 {
576     STARTUPINFOW si;
577     PROCESS_INFORMATION info;
578     BOOL rc;
579     WCHAR *deformated;
580     WCHAR *cmd;
581     INT len;
582     static const WCHAR spc[] = {' ',0};
583     MSIFILE *file;
584     UINT prc;
585
586     memset(&si,0,sizeof(STARTUPINFOW));
587
588     file = get_loaded_file(package,source);
589     if( !file )
590         return ERROR_FUNCTION_FAILED;
591
592     len = lstrlenW( file->TargetPath );
593
594     deformat_string(package,target,&deformated);
595     if (deformated)
596         len += strlenW(deformated);
597     len += 2;
598
599     cmd = msi_alloc(len * sizeof(WCHAR));
600
601     lstrcpyW( cmd, file->TargetPath);
602     if (deformated)
603     {
604         strcatW(cmd, spc);
605         strcatW(cmd, deformated);
606
607         msi_free(deformated);
608     }
609
610     TRACE("executing exe %s \n",debugstr_w(cmd));
611
612     rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
613                   c_collen, &si, &info);
614
615     msi_free(cmd);
616     
617     if ( !rc )
618     {
619         ERR("Unable to execute command\n");
620         return ERROR_SUCCESS;
621     }
622
623     prc = process_handle(package, type, info.hThread, info.hProcess, action, 
624                          NULL);
625
626     return prc;
627 }
628
629 static UINT HANDLE_CustomType19(MSIPACKAGE *package, LPCWSTR source,
630                                 LPCWSTR target, const INT type, LPCWSTR action)
631 {
632     static const WCHAR query[] = {
633       'S','E','L','E','C','T',' ','`','M','e','s','s','a','g','e','`',' ',
634       'F','R','O','M',' ','`','E','r','r','o','r','`',' ',
635       'W','H','E','R','E',' ','`','E','r','r','o','r','`',' ','=',' ',
636       '%','s',0
637     };
638     MSIRECORD *row = 0;
639     LPWSTR deformated = NULL;
640
641     deformat_string( package, target, &deformated );
642
643     /* first try treat the error as a number */
644     row = MSI_QueryGetRecord( package->db, query, deformated );
645     if( row )
646     {
647         LPCWSTR error = MSI_RecordGetString( row, 1 );
648         MessageBoxW( NULL, error, NULL, MB_OK );
649         msiobj_release( &row->hdr );
650     }
651     else
652         MessageBoxW( NULL, deformated, NULL, MB_OK );
653
654     msi_free( deformated );
655
656     return ERROR_FUNCTION_FAILED;
657 }
658
659 static UINT HANDLE_CustomType50(MSIPACKAGE *package, LPCWSTR source,
660                                 LPCWSTR target, const INT type, LPCWSTR action)
661 {
662     STARTUPINFOW si;
663     PROCESS_INFORMATION info;
664     WCHAR *prop;
665     BOOL rc;
666     WCHAR *deformated;
667     WCHAR *cmd;
668     INT len;
669     static const WCHAR spc[] = {' ',0};
670
671     memset(&si,0,sizeof(STARTUPINFOW));
672     memset(&info,0,sizeof(PROCESS_INFORMATION));
673
674     prop = msi_dup_property( package, source );
675     if (!prop)
676         return ERROR_SUCCESS;
677
678     deformat_string(package,target,&deformated);
679     len = strlenW(prop) + 2;
680     if (deformated)
681          len += strlenW(deformated);
682
683     cmd = msi_alloc(sizeof(WCHAR)*len);
684
685     strcpyW(cmd,prop);
686     if (deformated)
687     {
688         strcatW(cmd,spc);
689         strcatW(cmd,deformated);
690
691         msi_free(deformated);
692     }
693     msi_free(prop);
694
695     TRACE("executing exe %s \n",debugstr_w(cmd));
696
697     rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
698                   c_collen, &si, &info);
699
700     msi_free(cmd);
701     
702     if ( !rc )
703     {
704         ERR("Unable to execute command\n");
705         return ERROR_SUCCESS;
706     }
707
708     return process_handle(package, type, info.hThread, info.hProcess, action, NULL);
709 }
710
711 static UINT HANDLE_CustomType34(MSIPACKAGE *package, LPCWSTR source,
712                                 LPCWSTR target, const INT type, LPCWSTR action)
713 {
714     LPWSTR filename, deformated;
715     STARTUPINFOW si;
716     PROCESS_INFORMATION info;
717     BOOL rc;
718     UINT prc;
719
720     memset(&si,0,sizeof(STARTUPINFOW));
721
722     filename = resolve_folder(package, source, FALSE, FALSE, NULL);
723
724     if (!filename)
725         return ERROR_FUNCTION_FAILED;
726
727     SetCurrentDirectoryW(filename);
728     msi_free(filename);
729
730     deformat_string(package,target,&deformated);
731
732     if (!deformated)
733         return ERROR_FUNCTION_FAILED;
734
735     TRACE("executing exe %s \n",debugstr_w(deformated));
736
737     rc = CreateProcessW(NULL, deformated, NULL, NULL, FALSE, 0, NULL,
738                   c_collen, &si, &info);
739     msi_free(deformated);
740
741     if ( !rc )
742     {
743         ERR("Unable to execute command\n");
744         return ERROR_SUCCESS;
745     }
746
747     prc = process_handle(package, type, info.hThread, info.hProcess, action,
748                          NULL);
749
750     return prc;
751 }
752
753
754 void ACTION_FinishCustomActions(MSIPACKAGE* package)
755 {
756     struct list *item, *cursor;
757     DWORD rc;
758
759     LIST_FOR_EACH_SAFE( item, cursor, &package->RunningActions )
760     {
761         MSIRUNNINGACTION *action = LIST_ENTRY( item, MSIRUNNINGACTION, entry );
762
763         TRACE("Checking on action %s\n", debugstr_w(action->name));
764
765         list_remove( &action->entry );
766
767         if (action->process)
768             GetExitCodeProcess( action->handle, &rc );
769         else
770             GetExitCodeThread( action->handle, &rc );
771
772         if (rc == STILL_ACTIVE)
773         {
774             TRACE("Waiting on action %s\n", debugstr_w( action->name) );
775             msi_dialog_check_messages( action->handle );
776         }
777
778         CloseHandle( action->handle );
779         msi_free( action->name );
780         msi_free( action );
781     }
782 }