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