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