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