loopback-20040623
[openafs.git] / src / WINNT / install / loopback / loopbackutils.cpp
1 /*
2    Copyright 2004 by the Massachusetts Institute of Technology              
3                                                                             
4    All rights reserved.                                                     
5                                                                             
6    Permission to use, copy, modify, and distribute this software and its    
7    documentation for any purpose and without fee is hereby granted,         
8    provided that the above copyright notice appear in all copies and that   
9    both that copyright notice and this permission notice appear in          
10    supporting documentation, and that the name of the Massachusetts         
11    Institute of Technology (M.I.T.) not be used in advertising or publicity 
12    pertaining to distribution of the software without specific, written     
13    prior permission.                                                        
14                                                                             
15    M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING  
16    ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 
17    M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR   
18    ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,      
19    WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,   
20    ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS      
21    SOFTWARE.                                                                
22 */
23
24 #define _WIN32_DCOM
25 #include <windows.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 struct Args {
30         bool bQuiet;
31         LPWSTR lpIPAddr;
32         LPWSTR lpSubnetMask;
33     LPWSTR lpConnectionName;
34         Args () : bQuiet (0), lpIPAddr (0), lpSubnetMask (0), lpConnectionName (0) { }
35         ~Args () {
36                 if (lpIPAddr) free (lpIPAddr);
37                 if (lpSubnetMask) free (lpSubnetMask);
38         if (lpConnectionName) free (lpConnectionName);
39         }
40 };
41
42 #include <shellapi.h>
43 #define INITGUID
44 #include <guiddef.h>
45 #include <devguid.h>
46 #include <objbase.h>
47 #include <setupapi.h>
48 #include <tchar.h>
49 #include <Msi.h>
50 #include <Msiquery.h>
51 #include "loopbackutils.h"
52
53 extern "C" DWORD UnInstallLoopBack(void)
54 {
55     BOOL ok;
56     DWORD ret = 0;
57     GUID netGuid;
58     HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE;
59     SP_DEVINFO_DATA DeviceInfoData;
60     TCHAR* deviceDesc;
61     DWORD index = 0;
62     BOOL found = FALSE;
63     DWORD size = 0;
64
65     // initialize the structure size
66     DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
67
68     // copy the net class GUID
69     memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET));
70
71     // return a device info set contains all installed devices of the Net class
72     hDeviceInfo = SetupDiGetClassDevs(&netGuid, NULL, NULL, DIGCF_PRESENT);
73
74     if (hDeviceInfo == INVALID_HANDLE_VALUE)
75         return GetLastError();
76
77     deviceDesc = (TCHAR *)malloc(MAX_PATH*sizeof(TCHAR));
78     // enumerate the driver info list
79     while (SetupDiEnumDeviceInfo(hDeviceInfo, index, &DeviceInfoData))
80     {
81         // try to get the DeviceDesc registry property
82         ok = SetupDiGetDeviceRegistryProperty(hDeviceInfo, &DeviceInfoData,
83                                               SPDRP_DEVICEDESC,
84                                               NULL, (PBYTE)deviceDesc,
85                                               MAX_PATH * sizeof(TCHAR), &size);
86         if (!ok)
87         {
88             ret = GetLastError();
89             if (ret != ERROR_INSUFFICIENT_BUFFER)
90                 break;
91             // if the buffer is too small, reallocate
92             free(deviceDesc);
93             deviceDesc = (TCHAR *)malloc(size);
94             ok = SetupDiGetDeviceRegistryProperty(hDeviceInfo,
95                                                   &DeviceInfoData,
96                                                   SPDRP_DEVICEDESC,
97                                                   NULL, (PBYTE)deviceDesc,
98                                                   size, NULL);
99             if (!ok)
100                 break;
101         }
102
103         // case insensitive comparison
104         _tcslwr(deviceDesc);
105         if( _tcsstr(deviceDesc, DRIVER))
106         {
107             found = TRUE;
108             break;
109         }
110
111         index++;
112     }
113
114     free(deviceDesc);
115     
116     if (found == FALSE)
117     {
118         ret = GetLastError();
119         ReportMessage(0,"Driver does not seem to be installed", DRIVER_DESC, NULL, ret);
120         goto cleanup;
121     }
122
123     ok = SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData);
124     if (!ok)
125     {
126         ret = GetLastError();
127         goto cleanup;
128     }
129     ok = SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData);
130     if (!ok)
131     {
132         ret = GetLastError();
133         goto cleanup;
134     }
135
136 cleanup:
137     // clean up the device info set
138     if (hDeviceInfo != INVALID_HANDLE_VALUE)
139         SetupDiDestroyDeviceInfoList(hDeviceInfo);
140
141     return ret;
142 }
143
144 BOOL IsLoopbackInstalled(void)
145 {
146     TCHAR * hwid = _T("*MSLOOP");
147     HDEVINFO DeviceInfoSet;
148     SP_DEVINFO_DATA DeviceInfoData;
149     DWORD i,err;
150     BOOL found;
151     
152     //
153     // Create a Device Information Set with all present devices.
154     //
155     DeviceInfoSet = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_ALLCLASSES | DIGCF_PRESENT ); // All devices present on system
156     if (DeviceInfoSet == INVALID_HANDLE_VALUE)
157     {
158         return FALSE; // nothing installed?
159     }
160     
161     //
162     //  Enumerate through all Devices.
163     //
164     found = FALSE;
165     DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
166     for (i=0; SetupDiEnumDeviceInfo(DeviceInfoSet,i,&DeviceInfoData); i++)
167     {
168         DWORD DataT;
169         TCHAR *p, *buffer = NULL;
170         DWORD buffersize = 0;
171         
172         //
173         // We won't know the size of the HardwareID buffer until we call
174         // this function. So call it with a null to begin with, and then 
175         // use the required buffer size to Alloc the nessicary space.
176         // Keep calling we have success or an unknown failure.
177         //
178         while (!SetupDiGetDeviceRegistryProperty(DeviceInfoSet,&DeviceInfoData,SPDRP_HARDWAREID,&DataT,(PBYTE)buffer,buffersize,&buffersize))
179         {
180             if (GetLastError() == ERROR_INVALID_DATA)
181             {
182                 // May be a Legacy Device with no hwid. Continue.
183                 break;
184             }
185             else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
186             {
187                 // We need to change the buffer size.
188                 if (buffer) 
189                     LocalFree(buffer);
190                 buffer = (TCHAR *)LocalAlloc(LPTR,buffersize);
191             }
192             else
193             {
194                 goto cleanup_DeviceInfo;
195             }            
196         }
197         
198         if (GetLastError() == ERROR_INVALID_DATA) 
199             continue;
200         
201         // Compare each entry in the buffer multi-sz list with our hwid.
202         for (p=buffer; *p && (p < &buffer[buffersize]); p += _tcslen(p)+1)
203         {
204             if (!_tcsicmp(hwid,p))
205             {
206                 found = TRUE;
207                 break;
208             }
209         }
210         
211         if (buffer) LocalFree(buffer);
212         if (found) break;
213     }
214     
215     //  Cleanup.
216 cleanup_DeviceInfo:
217     err = GetLastError();
218     SetupDiDestroyDeviceInfoList(DeviceInfoSet);
219     SetLastError(err);
220     
221     return found;
222 }
223
224
225 extern "C" DWORD InstallLoopBack(LPCTSTR pConnectionName, LPCTSTR ip, LPCTSTR mask)
226 {
227     BOOL ok;
228     DWORD ret = 0;
229     GUID netGuid;
230     HDEVINFO hDeviceInfo = INVALID_HANDLE_VALUE;
231     SP_DEVINFO_DATA DeviceInfoData;
232     SP_DRVINFO_DATA DriverInfoData;
233     SP_DEVINSTALL_PARAMS  DeviceInstallParams;
234     TCHAR className[MAX_PATH];
235     TCHAR temp[MAX_PATH];
236     DWORD index = 0;
237     BOOL found = FALSE;
238     BOOL registered = FALSE;
239     BOOL destroyList = FALSE;
240
241     HKEY hkey = NULL;
242     DWORD cbSize;
243     DWORD dwValueType;
244     TCHAR pCfgGuidString[40];
245
246     // initialize the structure size
247     DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
248     DriverInfoData.cbSize = sizeof(SP_DRVINFO_DATA);
249
250     // copy the net class GUID
251     memcpy(&netGuid, &GUID_DEVCLASS_NET, sizeof(GUID_DEVCLASS_NET));
252
253     // create an empty device info set associated with the net class GUID
254     hDeviceInfo = SetupDiCreateDeviceInfoList(&netGuid, NULL);
255     if (hDeviceInfo == INVALID_HANDLE_VALUE)
256         return GetLastError();
257
258     // get the class name from GUID
259     ok = SetupDiClassNameFromGuid(&netGuid, className, MAX_PATH, NULL);
260     if (!ok)
261     {
262         ret = GetLastError();
263         goto cleanup;
264     }
265
266     // create a device info element and add the new device instance
267     // key to registry
268     ok = SetupDiCreateDeviceInfo(hDeviceInfo, className, &netGuid, NULL, NULL,
269                                  DICD_GENERATE_ID, &DeviceInfoData);
270     if (!ok)
271     {
272         ret = GetLastError();
273         goto cleanup;
274     }
275
276     // select the newly created device info to be the currently
277     // selected member
278     ok = SetupDiSetSelectedDevice(hDeviceInfo, &DeviceInfoData);
279     if (!ok)
280     {
281         ret = GetLastError();
282         goto cleanup;
283     }
284
285     // build a list of class drivers
286     ok = SetupDiBuildDriverInfoList(hDeviceInfo, &DeviceInfoData,
287                                     SPDIT_CLASSDRIVER);
288     if (!ok)
289     {
290         ret = GetLastError();
291         goto cleanup;
292     }
293
294     destroyList = TRUE;
295
296     // enumerate the driver info list
297     while (SetupDiEnumDriverInfo(hDeviceInfo, &DeviceInfoData,
298                                  SPDIT_CLASSDRIVER, index, &DriverInfoData))
299     {
300         // if the manufacture is microsoft
301         if (_tcsicmp(DriverInfoData.MfgName, MANUFACTURE) == 0)
302         {
303             // case insensitive search for loopback
304             _tcscpy(temp, DriverInfoData.Description);
305             _tcslwr(temp);
306             if( _tcsstr(temp, DRIVER))
307             {
308                 found = TRUE;
309                 break;
310             }
311         }
312         index++;
313     }
314
315     if (!found)
316     {
317         ret = GetLastError();
318         ReportMessage(0,"Could not find the driver to install", DRIVER_DESC, NULL, 0);
319         goto cleanup;
320     }
321
322     // set the loopback driver to be the currently selected
323     ok = SetupDiSetSelectedDriver(hDeviceInfo, &DeviceInfoData,
324                                   &DriverInfoData);
325     if (!ok)
326     {
327         ret = GetLastError();
328         goto cleanup;
329     }
330
331     // register the phantom device to repare for install
332     ok = SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDeviceInfo,
333                                    &DeviceInfoData);
334     if (!ok)
335     {
336         ret = GetLastError();
337         goto cleanup;
338     }
339
340     // registered, but remove if errors occur in the following code
341     registered = TRUE;
342
343     // ask the installer if we can install the device
344     ok = SetupDiCallClassInstaller(DIF_ALLOW_INSTALL, hDeviceInfo,
345                                    &DeviceInfoData);
346     if (!ok)
347     {
348         ret = GetLastError();
349         if (ret != ERROR_DI_DO_DEFAULT)
350         {
351             goto cleanup;
352         }
353         else
354             ret = 0;
355     }
356
357     // install the files first
358     ok = SetupDiCallClassInstaller(DIF_INSTALLDEVICEFILES, hDeviceInfo,
359                                    &DeviceInfoData);
360     if (!ok)
361     {
362         ret = GetLastError();
363         goto cleanup;
364     }
365
366     // get the device install parameters and disable filecopy
367     DeviceInstallParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);
368     ok = SetupDiGetDeviceInstallParams(hDeviceInfo, &DeviceInfoData,
369                                        &DeviceInstallParams);
370     if (ok)
371     {
372         DeviceInstallParams.Flags |= DI_NOFILECOPY;
373         ok = SetupDiSetDeviceInstallParams(hDeviceInfo, &DeviceInfoData,
374                                            &DeviceInstallParams);
375         if (!ok)
376         {
377             ret = GetLastError();
378             goto cleanup;
379         }
380     }
381
382     //
383     // Register any device-specific co-installers for this device,
384     //
385
386     ok = SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS,
387                                    hDeviceInfo,
388                                    &DeviceInfoData);
389     if (!ok)
390     {
391         ret = GetLastError();
392         goto cleanup;
393     }
394
395     //
396     // install any  installer-specified interfaces.
397     // and then do the real install
398     //
399     ok = SetupDiCallClassInstaller(DIF_INSTALLINTERFACES,
400                                    hDeviceInfo,
401                                    &DeviceInfoData);
402     if (!ok)
403     {
404         ret = GetLastError();
405         goto cleanup;
406     }
407     PAUSE;
408     ok = SetupDiCallClassInstaller(DIF_INSTALLDEVICE,
409                                    hDeviceInfo,
410                                    &DeviceInfoData);
411     if (!ok)
412     {
413         ret = GetLastError();
414         PAUSE;
415         goto cleanup;
416     }
417
418     /* Skip to the end if we aren't setting the name */
419     if (!pConnectionName) goto cleanup;
420
421     // Figure out NetCfgInstanceId
422     hkey = SetupDiOpenDevRegKey(hDeviceInfo,
423                                 &DeviceInfoData,
424                                 DICS_FLAG_GLOBAL,
425                                 0,
426                                 DIREG_DRV,
427                                 KEY_READ);
428     if (hkey == INVALID_HANDLE_VALUE)
429     {
430         ret = GetLastError();
431         goto cleanup;
432     }
433
434     cbSize = sizeof(pCfgGuidString);
435     ret = RegQueryValueEx(hkey, _T("NetCfgInstanceId"), NULL,
436                           &dwValueType, (LPBYTE)pCfgGuidString, &cbSize);
437     RegCloseKey(hkey);
438
439     ret = RenameConnection(pCfgGuidString, pConnectionName);
440     if (ret)
441     {
442         ReportMessage(0,"Could not set the connection name", NULL, pConnectionName, 0);
443         goto cleanup;
444     }
445
446     if (!ip) goto cleanup;
447     ret = SetIpAddress(pCfgGuidString, ip, mask);
448     if (ret)
449     {
450         ReportMessage(0,"Could not set the ip address and network mask",NULL,NULL,0);
451         goto cleanup;
452     }
453     ret = LoopbackBindings(pCfgGuidString);
454     if (ret)
455     {
456         ReportMessage(0,"Could not properly set the bindings",NULL,NULL,0);
457         goto cleanup;
458     }
459     ret = !UpdateHostsFile( pConnectionName, ip, "hosts", FALSE );
460     if (ret)
461     {
462         ReportMessage(0,"Could not update hosts file",NULL,NULL,0);
463         goto cleanup;
464     }
465     ret = !UpdateHostsFile( pConnectionName, ip, "lmhosts", TRUE );
466     if (ret)
467     {
468         ReportMessage(0,"Could not update lmhosts file",NULL,NULL,0);
469         goto cleanup;
470     }
471
472
473 cleanup:
474     // an error has occured, but the device is registered, we must remove it
475     if (ret != 0 && registered)
476         SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfo, &DeviceInfoData);
477
478     found = SetupDiDeleteDeviceInfo(hDeviceInfo, &DeviceInfoData);
479
480     // destroy the driver info list
481     if (destroyList)
482         SetupDiDestroyDriverInfoList(hDeviceInfo, &DeviceInfoData,
483                                      SPDIT_CLASSDRIVER);
484     // clean up the device info set
485     if (hDeviceInfo != INVALID_HANDLE_VALUE)
486         SetupDiDestroyDeviceInfoList(hDeviceInfo);
487
488     return ret;
489 };
490
491 /* The following functions provide the RunDll32 interface 
492  *    RunDll32 loopback_install.dll doLoopBackEntry [Interface Name] [IP address] [Subnet Mask]
493  */
494
495 static void wcsMallocAndCpy (LPWSTR * dst, const LPWSTR src) {
496         *dst = (LPWSTR) malloc ((wcslen (src) + 1) * sizeof (WCHAR));
497         wcscpy (*dst, src);
498 }
499
500 static void display_usage()
501 {
502     MessageBoxW( NULL, 
503                  L"Installation utility for the MS Loopback Adapter\r\n\r\n"
504                  L"Usage:\r\n"
505                  L"RunDll32 loopback_install.dll doLoopBackEntry [q|quiet] [Connection Name] [IP address] [Submask]\r\n",
506                  L"loopback_install", MB_ICONINFORMATION | MB_OK );
507 }
508
509 static int process_args (LPWSTR lpCmdLine, int skip, Args & args) {
510         int i, iNumArgs;
511         LPWSTR * argvW;
512
513         argvW = CommandLineToArgvW (lpCmdLine, &iNumArgs);
514         // Skip over the command name
515         for (i = skip; i < iNumArgs; i++)
516         {
517                 if (wcsstr (argvW[i], L"help")
518                         || !_wcsicmp (argvW[i], L"?")
519                         || (wcslen(argvW[i]) == 2 && argvW[i][1] == L'?'))
520                 {
521                         display_usage();
522                         GlobalFree (argvW);
523                         return 0;
524                 }
525
526                 if (!_wcsicmp (argvW[i], L"q") || !_wcsicmp (argvW[i], L"quiet")) {
527                         args.bQuiet = true;
528                         continue;
529                 }
530
531                 if (!args.lpConnectionName) {
532                         wcsMallocAndCpy (&args.lpConnectionName, argvW[i]);
533                         continue;
534                 }
535
536                 if (!args.lpIPAddr) {
537                         wcsMallocAndCpy (&args.lpIPAddr, argvW[i]);
538                         continue;
539                 }
540
541                 if (!args.lpSubnetMask) {
542                         wcsMallocAndCpy (&args.lpSubnetMask, argvW[i]);
543                         continue;
544                 }
545
546                 display_usage();
547                 GlobalFree (argvW);
548                 return 0;
549         }
550
551         if (!args.lpConnectionName)
552                 wcsMallocAndCpy (&args.lpConnectionName, DEFAULT_NAME);
553         if (!args.lpIPAddr)
554                 wcsMallocAndCpy (&args.lpIPAddr, DEFAULT_IP);
555         if (!args.lpSubnetMask)
556                 wcsMallocAndCpy (&args.lpSubnetMask, DEFAULT_MASK);
557
558         GlobalFree (argvW);
559
560         return 1;
561 }
562
563 void CALLBACK doLoopBackEntryW (HWND hwnd, HINSTANCE hinst, LPWSTR lpCmdLine, int nCmdShow)
564 {
565         Args args;
566
567         if (!process_args(lpCmdLine, 0, args)) 
568         return;
569
570         InstallLoopBack(args.lpConnectionName, args.lpIPAddr, args.lpSubnetMask);
571 }
572
573 void CALLBACK uninstallLoopBackEntryW (HWND hwnd, HINSTANCE hinst, LPWSTR lpCmdLine, int nCmdSHow)
574 {
575     Args args;
576
577     // initialize COM
578         // This and CoInitializeSecurity fail when running under the MSI
579         // engine, but there seems to be no ill effect (the security is now
580         // set on the specific object via CoSetProxyBlanket in loopback_configure)
581     if(CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE | COINIT_APARTMENTTHREADED ))
582     {
583                 //Don't fail (MSI install will have already initialized COM)
584         //EasyErrorBox(0, L"Failed to initialize COM.");
585         //return 1;
586     }
587
588         // Initialize COM security (otherwise we'll get permission denied when we try to use WMI or NetCfg)
589     CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
590
591     UnInstallLoopBack();
592
593         CoUninitialize();
594 }
595
596 /* And an MSI installer interface */
597
598 UINT __stdcall installLoopbackMSI (MSIHANDLE hInstall)
599 {
600         LPWSTR szValueBuf;
601         DWORD cbValueBuf = 256;
602         Args args;
603         UINT rc;
604
605         SetMsiReporter("InstallLoopback", "Installing loopback adapter", hInstall);
606
607     /* check if there is already one installed.  If there is, we shouldn't try to 
608      * install another.
609      */
610         if(IsLoopbackInstalled())
611         return ERROR_SUCCESS;
612
613         szValueBuf = (LPWSTR) malloc (cbValueBuf * sizeof (WCHAR));
614         while (rc = MsiGetPropertyW(hInstall, L"CustomActionData", szValueBuf, &cbValueBuf)) {
615                 free (szValueBuf);
616                 if (rc == ERROR_MORE_DATA) {
617                         cbValueBuf++;
618                         szValueBuf = (LPWSTR) malloc (cbValueBuf * sizeof (WCHAR));
619                 } 
620         else 
621             return ERROR_INSTALL_FAILURE;
622         }
623
624         if (!process_args(szValueBuf, 1, args)) 
625         return ERROR_INSTALL_FAILURE;
626                 
627         rc = InstallLoopBack (args.lpConnectionName, args.lpIPAddr, args.lpSubnetMask);
628
629         if (rc != 2 && rc != 0) 
630         return ERROR_INSTALL_FAILURE;
631
632         if (rc == 2) {
633                 MsiDoActionW (hInstall, L"ScheduleReboot");
634         }
635
636         return ERROR_SUCCESS;
637 }
638
639 UINT __stdcall uninstallLoopbackMSI (MSIHANDLE hInstall)
640 {
641         LPWSTR szValueBuf;
642         DWORD cbValueBuf = 256;
643         Args args;
644         UINT rc;
645
646         SetMsiReporter("RemoveLoopback", "Removing loopback adapter",  hInstall);
647
648         szValueBuf = (LPWSTR) malloc (cbValueBuf * sizeof (WCHAR));
649         while (rc = MsiGetPropertyW(hInstall, L"CustomActionData", szValueBuf, &cbValueBuf)) {
650                 free (szValueBuf);
651                 if (rc == ERROR_MORE_DATA) {
652                         cbValueBuf++;
653                         szValueBuf = (LPWSTR) malloc (cbValueBuf * sizeof (WCHAR));
654                 } 
655         else 
656             return ERROR_INSTALL_FAILURE;
657         }
658
659         if (!process_args(szValueBuf, 1, args)) 
660         return ERROR_INSTALL_FAILURE;
661                 
662         rc = UnInstallLoopBack ();
663
664         if (rc == 1) 
665         return ERROR_INSTALL_FAILURE;
666
667         if (rc == 2) {
668                 MsiDoActionW (hInstall, L"ScheduleReboot");
669         }
670
671         return ERROR_SUCCESS;
672 }
673
674 DWORD hMsiHandle = 0;
675 DWORD dwReporterType = REPORT_PRINTF;
676
677 extern "C" void ReportMessage(int level, LPCSTR msg, LPCSTR str, LPCWSTR wstr, DWORD dw) {
678     if(dwReporterType == REPORT_PRINTF)
679                 printf("%s:[%s][%S][%d]\n", (msg?msg:""), (str?str:""), (wstr?wstr:L""), dw);
680         else if(dwReporterType == REPORT_MSI && hMsiHandle && level == 0) {
681                 MSIHANDLE hRec = MsiCreateRecord(5);
682         
683                 MsiRecordClearData(hRec);
684                 MsiRecordSetStringA(hRec,1,(msg)?msg:"");
685                 MsiRecordSetStringA(hRec,2,(str)?str:"");
686                 MsiRecordSetStringW(hRec,3,(wstr)?wstr:L"");
687                 MsiRecordSetInteger(hRec,4,dw);
688
689                 MsiProcessMessage(hMsiHandle,INSTALLMESSAGE_ACTIONDATA,hRec);
690
691                 MsiCloseHandle(hRec);
692         }
693 }
694
695 extern "C" void SetMsiReporter(LPCSTR strAction, LPCSTR strDesc,DWORD h) {
696         dwReporterType = REPORT_MSI;
697         hMsiHandle = h;
698
699         MSIHANDLE hRec = MsiCreateRecord(4);
700         
701         MsiRecordClearData(hRec);
702         MsiRecordSetStringA(hRec,1,strAction);
703         MsiRecordSetStringA(hRec,2,strDesc);
704         MsiRecordSetStringA(hRec,3,"[1]:([2])([3])([4])");
705
706         MsiProcessMessage(h,INSTALLMESSAGE_ACTIONSTART, hRec);
707
708         MsiCloseHandle(hRec);
709 }