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