* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
-/*
- * Pages I need
- *
-http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/summary_list_of_all_custom_action_types.asp
- */
-
-#include <stdarg.h>
-#include <stdio.h>
+#include "config.h"
+#include "wine/port.h"
#define COBJMACROS
+#include <stdarg.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
-#include "winreg.h"
-#include "wine/debug.h"
-#include "fdi.h"
-#include "msi.h"
#include "msidefs.h"
-#include "msiquery.h"
-#include "msvcrt/fcntl.h"
+#include "winuser.h"
#include "objbase.h"
-#include "objidl.h"
+#include "oleauto.h"
+
#include "msipriv.h"
-#include "winnls.h"
-#include "winuser.h"
-#include "shlobj.h"
+#include "msiserver.h"
+#include "wine/debug.h"
#include "wine/unicode.h"
-#include "winver.h"
+#include "wine/exception.h"
WINE_DEFAULT_DEBUG_CHANNEL(msi);
static const WCHAR c_collen[] = {'C',':','\\',0};
static const WCHAR cszTempFolder[]= {'T','e','m','p','F','o','l','d','e','r',0};
+
+static const WCHAR szActionData[] = {
+ 'C','u','s','t','o','m','A','c','t','i','o','n','D','a','t','a',0
+};
+static const WCHAR ProdCode[] = {
+ 'P','r','o','d','u','c','t','C','o','d','e',0
+};
+static const WCHAR UserSID[] = {'U','s','e','r','S','I','D',0};
+
typedef struct tagMSIRUNNINGACTION
{
struct list entry;
LPCWSTR target, const INT type, LPCWSTR action);
static UINT HANDLE_CustomType19(MSIPACKAGE *package, LPCWSTR source,
LPCWSTR target, const INT type, LPCWSTR action);
+static UINT HANDLE_CustomType23(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action);
static UINT HANDLE_CustomType50(MSIPACKAGE *package, LPCWSTR source,
LPCWSTR target, const INT type, LPCWSTR action);
static UINT HANDLE_CustomType34(MSIPACKAGE *package, LPCWSTR source,
LPCWSTR target, const INT type, LPCWSTR action);
+static UINT HANDLE_CustomType37_38(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action);
+static UINT HANDLE_CustomType5_6(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action);
+static UINT HANDLE_CustomType21_22(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action);
+static UINT HANDLE_CustomType53_54(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action);
+typedef UINT (WINAPI *MsiCustomActionEntryPoint)( MSIHANDLE );
+
+static CRITICAL_SECTION msi_custom_action_cs;
+static CRITICAL_SECTION_DEBUG msi_custom_action_cs_debug =
+{
+ 0, 0, &msi_custom_action_cs,
+ { &msi_custom_action_cs_debug.ProcessLocksList,
+ &msi_custom_action_cs_debug.ProcessLocksList },
+ 0, 0, { (DWORD_PTR)(__FILE__ ": msi_custom_action_cs") }
+};
+static CRITICAL_SECTION msi_custom_action_cs = { &msi_custom_action_cs_debug, -1, 0, 0, 0, 0 };
+
+static struct list msi_pending_custom_actions = LIST_INIT( msi_pending_custom_actions );
static BOOL check_execution_scheduling_options(MSIPACKAGE *package, LPCWSTR action, UINT options)
{
return TRUE;
}
-/* stores the CustomActionData before the action:
- * [CustomActionData]Action
+/* stores the following properties before the action:
+ *
+ * [CustomActionData<=>UserSID<=>ProductCode]Action
*/
-static LPWSTR msi_get_deferred_action(LPCWSTR action, LPWSTR actiondata)
+static LPWSTR msi_get_deferred_action(LPCWSTR action, LPCWSTR actiondata,
+ LPCWSTR usersid, LPCWSTR prodcode)
{
LPWSTR deferred;
DWORD len;
- static const WCHAR begin[] = {'[',0};
- static const WCHAR end[] = {']',0};
+ static const WCHAR format[] = {
+ '[','%','s','<','=','>','%','s','<','=','>','%','s',']','%','s',0
+ };
if (!actiondata)
return strdupW(action);
- len = lstrlenW(action) + lstrlenW(actiondata) + 3;
+ len = lstrlenW(action) + lstrlenW(actiondata) +
+ lstrlenW(usersid) + lstrlenW(prodcode) +
+ lstrlenW(format) - 7;
deferred = msi_alloc(len * sizeof(WCHAR));
- lstrcpyW(deferred, begin);
- lstrcatW(deferred, actiondata);
- lstrcatW(deferred, end);
- lstrcatW(deferred, action);
-
+ sprintfW(deferred, format, actiondata, usersid, prodcode, action);
return deferred;
}
-UINT ACTION_CustomAction(MSIPACKAGE *package,LPCWSTR action, BOOL execute)
+static void set_deferred_action_props(MSIPACKAGE *package, LPWSTR deferred_data)
+{
+ LPWSTR end, beg = deferred_data + 1;
+
+ static const WCHAR sep[] = {'<','=','>',0};
+
+ end = strstrW(beg, sep);
+ *end = '\0';
+ MSI_SetPropertyW(package, szActionData, beg);
+ beg = end + 3;
+
+ end = strstrW(beg, sep);
+ *end = '\0';
+ MSI_SetPropertyW(package, UserSID, beg);
+ beg = end + 3;
+
+ end = strchrW(beg, ']');
+ *end = '\0';
+ MSI_SetPropertyW(package, ProdCode, beg);
+}
+
+UINT ACTION_CustomAction(MSIPACKAGE *package, LPCWSTR action, UINT script, BOOL execute)
{
UINT rc = ERROR_SUCCESS;
MSIRECORD * row = 0;
LPWSTR action_copy = strdupW(action);
WCHAR *deformated=NULL;
- /* deferred action: [CustomActionData]Action */
- if ((ptr = strchrW(action_copy, ']')))
+ /* deferred action: [properties]Action */
+ if ((ptr = strrchrW(action_copy, ']')))
{
- deferred_data = action_copy + 1;
- *ptr = '\0';
+ deferred_data = action_copy;
action = ptr + 1;
}
if (type & msidbCustomActionTypeInScript)
{
if (type & msidbCustomActionTypeNoImpersonate)
- FIXME("msidbCustomActionTypeNoImpersonate not handled\n");
+ WARN("msidbCustomActionTypeNoImpersonate not handled\n");
if (type & msidbCustomActionTypeRollback)
{
if (!execute)
{
LPWSTR actiondata = msi_dup_property(package, action);
- LPWSTR deferred = msi_get_deferred_action(action, actiondata);
+ LPWSTR usersid = msi_dup_property(package, UserSID);
+ LPWSTR prodcode = msi_dup_property(package, ProdCode);
+ LPWSTR deferred = msi_get_deferred_action(action, actiondata, usersid, prodcode);
if (type & msidbCustomActionTypeCommit)
{
}
rc = ERROR_SUCCESS;
+ msi_free(actiondata);
+ msi_free(usersid);
+ msi_free(prodcode);
msi_free(deferred);
goto end;
}
else
{
- /*Set ActionData*/
-
- static const WCHAR szActionData[] = {
- 'C','u','s','t','o','m','A','c','t','i','o','n','D','a','t','a',0};
static const WCHAR szBlank[] = {0};
+
LPWSTR actiondata = msi_dup_property( package, action );
+
+ switch (script)
+ {
+ case INSTALL_SCRIPT:
+ package->scheduled_action_running = TRUE;
+ break;
+ case COMMIT_SCRIPT:
+ package->commit_action_running = TRUE;
+ break;
+ case ROLLBACK_SCRIPT:
+ package->rollback_action_running = TRUE;
+ break;
+ default:
+ break;
+ }
+
if (deferred_data)
- MSI_SetPropertyW(package,szActionData,deferred_data);
+ set_deferred_action_props(package, deferred_data);
else if (actiondata)
MSI_SetPropertyW(package,szActionData,actiondata);
else
MSI_SetPropertyW(package,szActionData,szBlank);
+
msi_free(actiondata);
}
}
case 1: /* DLL file stored in a Binary table stream */
rc = HANDLE_CustomType1(package,source,target,type,action);
break;
- case 2: /* EXE file stored in a Binary table strem */
+ case 2: /* EXE file stored in a Binary table stream */
rc = HANDLE_CustomType2(package,source,target,type,action);
break;
case 18: /*EXE file installed with package */
case 17:
rc = HANDLE_CustomType17(package,source,target,type,action);
break;
+ case 23: /* installs another package in the source tree */
+ deformat_string(package,target,&deformated);
+ rc = HANDLE_CustomType23(package,source,deformated,type,action);
+ msi_free(deformated);
+ break;
case 50: /*EXE file specified by a property value */
rc = HANDLE_CustomType50(package,source,target,type,action);
break;
msi_free(deformated);
break;
case 51: /* Property set with formatted text. */
+ if (!source)
+ break;
+
deformat_string(package,target,&deformated);
rc = MSI_SetPropertyW(package,source,deformated);
msi_free(deformated);
break;
+ case 37: /* JScript/VBScript text stored in target column. */
+ case 38:
+ rc = HANDLE_CustomType37_38(package,source,target,type,action);
+ break;
+ case 5:
+ case 6: /* JScript/VBScript file stored in a Binary table stream. */
+ rc = HANDLE_CustomType5_6(package,source,target,type,action);
+ break;
+ case 21: /* JScript/VBScript file installed with the product. */
+ case 22:
+ rc = HANDLE_CustomType21_22(package,source,target,type,action);
+ break;
+ case 53: /* JScript/VBScript text specified by a property value. */
+ case 54:
+ rc = HANDLE_CustomType53_54(package,source,target,type,action);
+ break;
default:
FIXME("UNHANDLED ACTION TYPE %i (%s %s)\n",
type & CUSTOM_ACTION_TYPE_MASK, debugstr_w(source),
}
end:
+ package->scheduled_action_running = FALSE;
+ package->commit_action_running = FALSE;
+ package->rollback_action_running = FALSE;
msi_free(action_copy);
msiobj_release(&row->hdr);
return rc;
static UINT store_binary_to_temp(MSIPACKAGE *package, LPCWSTR source,
LPWSTR tmp_file)
{
- DWORD sz=MAX_PATH;
+ static const WCHAR query[] = {
+ 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
+ '`','B','i' ,'n','a','r','y','`',' ','W','H','E','R','E',' ',
+ '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0};
+ MSIRECORD *row = 0;
+ HANDLE file;
+ CHAR buffer[1024];
static const WCHAR f1[] = {'m','s','i',0};
WCHAR fmt[MAX_PATH];
+ DWORD sz = MAX_PATH;
+ UINT r;
if (MSI_GetPropertyW(package, cszTempFolder, fmt, &sz) != ERROR_SUCCESS)
- GetTempPathW(MAX_PATH,fmt);
+ GetTempPathW(MAX_PATH, fmt);
- if (GetTempFileNameW(fmt,f1,0,tmp_file) == 0)
+ if (GetTempFileNameW(fmt, f1, 0, tmp_file) == 0)
{
TRACE("Unable to create file\n");
return ERROR_FUNCTION_FAILED;
}
- else
- {
- /* write out the file */
- UINT rc;
- MSIRECORD * row = 0;
- static const WCHAR fmt[] =
- {'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
- '`','B','i' ,'n','a','r','y','`',' ','W','H','E','R','E',
- ' ','`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0};
- HANDLE the_file;
- CHAR buffer[1024];
-
- the_file = CreateFileW(tmp_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
- FILE_ATTRIBUTE_NORMAL, NULL);
+ track_tempfile(package, tmp_file);
- if (the_file == INVALID_HANDLE_VALUE)
- return ERROR_FUNCTION_FAILED;
-
- row = MSI_QueryGetRecord(package->db, fmt, source);
- if (!row)
- return ERROR_FUNCTION_FAILED;
+ row = MSI_QueryGetRecord(package->db, query, source);
+ if (!row)
+ return ERROR_FUNCTION_FAILED;
+ /* write out the file */
+ file = CreateFileW(tmp_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (file == INVALID_HANDLE_VALUE)
+ r = ERROR_FUNCTION_FAILED;
+ else
+ {
do
{
DWORD write;
- sz = 1024;
- rc = MSI_RecordReadStream(row,2,buffer,&sz);
- if (rc != ERROR_SUCCESS)
+ sz = sizeof buffer;
+ r = MSI_RecordReadStream(row, 2, buffer, &sz);
+ if (r != ERROR_SUCCESS)
{
ERR("Failed to get stream\n");
- CloseHandle(the_file);
- DeleteFileW(tmp_file);
break;
}
- WriteFile(the_file,buffer,sz,&write,NULL);
- } while (sz == 1024);
-
- CloseHandle(the_file);
-
- msiobj_release(&row->hdr);
+ WriteFile(file, buffer, sz, &write, NULL);
+ } while (sz == sizeof buffer);
+ CloseHandle(file);
}
- return ERROR_SUCCESS;
+ msiobj_release(&row->hdr);
+
+ return r;
}
static void file_running_action(MSIPACKAGE* package, HANDLE Handle,
list_add_tail( &package->RunningActions, &action->entry );
}
-static UINT process_action_return_value(UINT type, HANDLE ThreadHandle)
+static UINT custom_get_process_return( HANDLE process )
{
- DWORD rc=0;
+ DWORD rc = 0;
- if (type == 2)
- {
- GetExitCodeProcess(ThreadHandle,&rc);
+ GetExitCodeProcess( process, &rc );
+ if (rc != 0)
+ return ERROR_FUNCTION_FAILED;
+ return ERROR_SUCCESS;
+}
- if (rc == 0)
- return ERROR_SUCCESS;
- else
- return ERROR_FUNCTION_FAILED;
- }
+static UINT custom_get_thread_return( MSIPACKAGE *package, HANDLE thread )
+{
+ DWORD rc = 0;
- GetExitCodeThread(ThreadHandle,&rc);
+ GetExitCodeThread( thread, &rc );
switch (rc)
{
return rc;
case ERROR_NO_MORE_ITEMS:
return ERROR_SUCCESS;
+ case ERROR_INSTALL_SUSPEND:
+ ACTION_ForceReboot( package );
+ return ERROR_SUCCESS;
default:
ERR("Invalid Return Code %d\n",rc);
return ERROR_INSTALL_FAILURE;
}
}
-static UINT process_handle(MSIPACKAGE* package, UINT type,
- HANDLE ThreadHandle, HANDLE ProcessHandle,
- LPCWSTR Name, BOOL *finished)
+static UINT wait_process_handle(MSIPACKAGE* package, UINT type,
+ HANDLE ProcessHandle, LPCWSTR name)
{
UINT rc = ERROR_SUCCESS;
if (!(type & msidbCustomActionTypeAsync))
{
- /* synchronous */
- TRACE("Synchronous Execution of action %s\n",debugstr_w(Name));
- if (ProcessHandle)
- msi_dialog_check_messages(ProcessHandle);
- else
- msi_dialog_check_messages(ThreadHandle);
+ TRACE("waiting for %s\n", debugstr_w(name));
+
+ msi_dialog_check_messages(ProcessHandle);
if (!(type & msidbCustomActionTypeContinue))
- {
- if (ProcessHandle)
- rc = process_action_return_value(2,ProcessHandle);
- else
- rc = process_action_return_value(1,ThreadHandle);
- }
+ rc = custom_get_process_return(ProcessHandle);
- CloseHandle(ThreadHandle);
- if (ProcessHandle)
- CloseHandle(ProcessHandle);
- if (finished)
- *finished = TRUE;
+ CloseHandle(ProcessHandle);
}
else
{
- TRACE("Asynchronous Execution of action %s\n",debugstr_w(Name));
- /* asynchronous */
- if (type & msidbCustomActionTypeContinue)
- {
- if (ProcessHandle)
- {
- file_running_action(package, ProcessHandle, TRUE, Name);
- CloseHandle(ThreadHandle);
- }
- else
- file_running_action(package, ThreadHandle, FALSE, Name);
- }
+ TRACE("%s running in background\n", debugstr_w(name));
+
+ if (!(type & msidbCustomActionTypeContinue))
+ file_running_action(package, ProcessHandle, TRUE, name);
else
+ CloseHandle(ProcessHandle);
+ }
+
+ return rc;
+}
+
+typedef struct _msi_custom_action_info {
+ struct list entry;
+ LONG refs;
+ MSIPACKAGE *package;
+ LPWSTR source;
+ LPWSTR target;
+ HANDLE handle;
+ LPWSTR action;
+ INT type;
+ GUID guid;
+} msi_custom_action_info;
+
+static void release_custom_action_data( msi_custom_action_info *info )
+{
+ EnterCriticalSection( &msi_custom_action_cs );
+
+ if (!--info->refs)
+ {
+ list_remove( &info->entry );
+ if (info->handle)
+ CloseHandle( info->handle );
+ msi_free( info->action );
+ msi_free( info->source );
+ msi_free( info->target );
+ msiobj_release( &info->package->hdr );
+ msi_free( info );
+ }
+
+ LeaveCriticalSection( &msi_custom_action_cs );
+}
+
+/* must be called inside msi_custom_action_cs if info is in the pending custom actions list */
+static void addref_custom_action_data( msi_custom_action_info *info )
+{
+ info->refs++;
+ }
+
+static UINT wait_thread_handle( msi_custom_action_info *info )
+{
+ UINT rc = ERROR_SUCCESS;
+
+ if (!(info->type & msidbCustomActionTypeAsync))
+ {
+ TRACE("waiting for %s\n", debugstr_w( info->action ));
+
+ msi_dialog_check_messages( info->handle );
+
+ if (!(info->type & msidbCustomActionTypeContinue))
+ rc = custom_get_thread_return( info->package, info->handle );
+
+ release_custom_action_data( info );
+ }
+ else
+ {
+ TRACE("%s running in background\n", debugstr_w( info->action ));
+ }
+
+ return rc;
+}
+
+static msi_custom_action_info *find_action_by_guid( const GUID *guid )
+{
+ msi_custom_action_info *info;
+ BOOL found = FALSE;
+
+ EnterCriticalSection( &msi_custom_action_cs );
+
+ LIST_FOR_EACH_ENTRY( info, &msi_pending_custom_actions, msi_custom_action_info, entry )
+ {
+ if (IsEqualGUID( &info->guid, guid ))
{
- CloseHandle(ThreadHandle);
- if (ProcessHandle)
- CloseHandle(ProcessHandle);
+ addref_custom_action_data( info );
+ found = TRUE;
+ break;
}
- if (finished)
- *finished = FALSE;
}
- return rc;
+ LeaveCriticalSection( &msi_custom_action_cs );
+
+ if (!found)
+ return NULL;
+
+ return info;
}
+static void handle_msi_break( LPCWSTR target )
+{
+ LPWSTR msg;
+ WCHAR val[MAX_PATH];
-typedef UINT __stdcall CustomEntry(MSIHANDLE);
+ static const WCHAR MsiBreak[] = { 'M','s','i','B','r','e','a','k',0 };
+ static const WCHAR WindowsInstaller[] = {
+ 'W','i','n','d','o','w','s',' ','I','n','s','t','a','l','l','e','r',0
+ };
+
+ static const WCHAR format[] = {
+ 'T','o',' ','d','e','b','u','g',' ','y','o','u','r',' ',
+ 'c','u','s','t','o','m',' ','a','c','t','i','o','n',',',' ',
+ 'a','t','t','a','c','h',' ','y','o','u','r',' ','d','e','b','u','g','g','e','r',' ',
+ 't','o',' ','p','r','o','c','e','s','s',' ','%','i',' ','(','0','x','%','X',')',' ',
+ 'a','n','d',' ','p','r','e','s','s',' ','O','K',0
+ };
+
+ if( !GetEnvironmentVariableW( MsiBreak, val, MAX_PATH ))
+ return;
+
+ if( lstrcmpiW( val, target ))
+ return;
+
+ msg = msi_alloc( (lstrlenW(format) + 10) * sizeof(WCHAR) );
+ if (!msg)
+ return;
-typedef struct
+ wsprintfW( msg, format, GetCurrentProcessId(), GetCurrentProcessId());
+ MessageBoxW( NULL, msg, WindowsInstaller, MB_OK);
+ msi_free(msg);
+ DebugBreak();
+}
+
+static UINT get_action_info( const GUID *guid, INT *type, MSIHANDLE *handle,
+ BSTR *dll, BSTR *funcname,
+ IWineMsiRemotePackage **package )
{
- MSIPACKAGE *package;
- WCHAR *target;
- WCHAR *source;
-} thread_struct;
+ IClassFactory *cf = NULL;
+ IWineMsiRemoteCustomAction *rca = NULL;
+ HRESULT r;
+
+ r = DllGetClassObject( &CLSID_IWineMsiRemoteCustomAction,
+ &IID_IClassFactory, (LPVOID *)&cf );
+ if (FAILED(r))
+ {
+ ERR("failed to get IClassFactory interface\n");
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ r = IClassFactory_CreateInstance( cf, NULL, &IID_IWineMsiRemoteCustomAction, (LPVOID *)&rca );
+ if (FAILED(r))
+ {
+ ERR("failed to get IWineMsiRemoteCustomAction interface\n");
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ r = IWineMsiRemoteCustomAction_GetActionInfo( rca, guid, type, handle, dll, funcname, package );
+ IWineMsiRemoteCustomAction_Release( rca );
+ if (FAILED(r))
+ {
+ ERR("GetActionInfo failed\n");
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ return ERROR_SUCCESS;
+}
-static DWORD WINAPI ACTION_CallDllFunction(thread_struct *stuff)
+static DWORD ACTION_CallDllFunction( const GUID *guid )
{
+ MsiCustomActionEntryPoint fn;
+ MSIHANDLE hPackage, handle;
HANDLE hModule;
LPSTR proc;
- CustomEntry *fn;
- DWORD rc = ERROR_SUCCESS;
+ UINT r = ERROR_FUNCTION_FAILED;
+ BSTR dll = NULL, function = NULL;
+ INT type;
+ IWineMsiRemotePackage *remote_package = NULL;
- TRACE("calling function (%s, %s)\n", debugstr_w(stuff->source),
- debugstr_w(stuff->target));
+ TRACE("%s\n", debugstr_guid( guid ));
- hModule = LoadLibraryW(stuff->source);
- if (hModule)
+ r = get_action_info( guid, &type, &handle, &dll, &function, &remote_package );
+ if (r != ERROR_SUCCESS)
+ return r;
+
+ hModule = LoadLibraryW( dll );
+ if (!hModule)
+ {
+ ERR("failed to load dll %s (%u)\n", debugstr_w( dll ), GetLastError() );
+ return r;
+ }
+
+ proc = strdupWtoA( function );
+ fn = (MsiCustomActionEntryPoint) GetProcAddress( hModule, proc );
+ msi_free( proc );
+ if (fn)
{
- proc = strdupWtoA( stuff->target );
- fn = (CustomEntry*)GetProcAddress(hModule,proc);
- if (fn)
+ hPackage = alloc_msi_remote_handle( (IUnknown *)remote_package );
+ if (hPackage)
{
- MSIHANDLE hPackage;
- MSIPACKAGE *package = stuff->package;
+ IWineMsiRemotePackage_SetMsiHandle( remote_package, handle );
+ TRACE("calling %s\n", debugstr_w( function ) );
+ handle_msi_break( function );
- TRACE("Calling function %s\n", proc);
- hPackage = alloc_msihandle( &package->hdr );
- if (hPackage)
+ __TRY
{
- rc = fn( hPackage );
- MsiCloseHandle( hPackage );
+ r = fn( hPackage );
}
- else
- ERR("Handle for object %p not found\n", package );
+ __EXCEPT_PAGE_FAULT
+ {
+ ERR("Custom action (%s:%s) caused a page fault: %08x\n",
+ debugstr_w(dll), debugstr_w(function), GetExceptionCode());
+ r = ERROR_SUCCESS;
+ }
+ __ENDTRY;
+
+ MsiCloseHandle( hPackage );
}
else
- ERR("failed to resolve functon %s\n", debugstr_a(proc));
-
- msi_free(proc);
- FreeLibrary(hModule);
+ ERR("failed to create handle for %p\n", remote_package );
}
else
- ERR("failed to load dll %s\n", debugstr_w(stuff->source));
- msiobj_release( &stuff->package->hdr );
- msi_free(stuff->source);
- msi_free(stuff->target);
- msi_free(stuff);
- return rc;
+ ERR("GetProcAddress(%s) failed\n", debugstr_w( function ) );
+
+ FreeLibrary(hModule);
+
+ IWineMsiRemotePackage_Release( remote_package );
+ SysFreeString( dll );
+ SysFreeString( function );
+ MsiCloseHandle( handle );
+
+ return r;
}
-static DWORD WINAPI DllThread(LPVOID info)
+static DWORD WINAPI DllThread( LPVOID arg )
{
- thread_struct *stuff;
+ LPGUID guid = arg;
DWORD rc = 0;
- TRACE("MSI Thread (%x) started for custom action\n", GetCurrentThreadId());
+ TRACE("custom action (%x) started\n", GetCurrentThreadId() );
+
+ rc = ACTION_CallDllFunction( guid );
- stuff = (thread_struct*)info;
- rc = ACTION_CallDllFunction(stuff);
+ TRACE("custom action (%x) returned %i\n", GetCurrentThreadId(), rc );
- TRACE("MSI Thread (%x) finished (rc %i)\n",GetCurrentThreadId(), rc);
- /* clse all handles for this thread */
MsiCloseAllHandles();
return rc;
}
-static HANDLE do_msidbCustomActionTypeDll(MSIPACKAGE *package, LPCWSTR dll, LPCWSTR target)
+static DWORD ACTION_CAInstallPackage(const GUID *guid)
+{
+ msi_custom_action_info *info;
+ UINT r = ERROR_FUNCTION_FAILED;
+ INSTALLUILEVEL old_level;
+
+ info = find_action_by_guid(guid);
+ if (!info)
+ {
+ ERR("failed to find action %s\n", debugstr_guid(guid));
+ return r;
+ }
+
+ old_level = MsiSetInternalUI(INSTALLUILEVEL_BASIC, NULL);
+ r = MsiInstallProductW(info->source, info->target);
+ MsiSetInternalUI(old_level, NULL);
+
+ release_custom_action_data(info);
+
+ return r;
+}
+
+static DWORD WINAPI ConcurrentInstallThread(LPVOID arg)
+{
+ LPGUID guid = arg;
+ DWORD rc;
+
+ TRACE("concurrent installation (%x) started\n", GetCurrentThreadId());
+
+ rc = ACTION_CAInstallPackage(guid);
+
+ TRACE("concurrent installation (%x) returned %i\n", GetCurrentThreadId(), rc);
+
+ MsiCloseAllHandles();
+ return rc;
+}
+
+static msi_custom_action_info *do_msidbCustomActionTypeDll(
+ MSIPACKAGE *package, INT type, LPCWSTR source, LPCWSTR target, LPCWSTR action )
+{
+ msi_custom_action_info *info;
+
+ info = msi_alloc( sizeof *info );
+ if (!info)
+ return NULL;
+
+ msiobj_addref( &package->hdr );
+ info->refs = 2; /* 1 for our caller and 1 for thread we created */
+ info->package = package;
+ info->type = type;
+ info->target = strdupW( target );
+ info->source = strdupW( source );
+ info->action = strdupW( action );
+ CoCreateGuid( &info->guid );
+
+ EnterCriticalSection( &msi_custom_action_cs );
+ list_add_tail( &msi_pending_custom_actions, &info->entry );
+ LeaveCriticalSection( &msi_custom_action_cs );
+
+ info->handle = CreateThread( NULL, 0, DllThread, &info->guid, 0, NULL );
+ if (!info->handle)
+ {
+ /* release both references */
+ release_custom_action_data( info );
+ release_custom_action_data( info );
+ return NULL;
+ }
+
+ return info;
+}
+
+static msi_custom_action_info *do_msidbCAConcurrentInstall(
+ MSIPACKAGE *package, INT type, LPCWSTR source, LPCWSTR target, LPCWSTR action)
{
- thread_struct *info;
+ msi_custom_action_info *info;
+
+ info = msi_alloc( sizeof *info );
+ if (!info)
+ return NULL;
- info = msi_alloc( sizeof(*info) );
msiobj_addref( &package->hdr );
+ info->refs = 2; /* 1 for our caller and 1 for thread we created */
info->package = package;
- info->target = strdupW(target);
- info->source = strdupW(dll);
+ info->type = type;
+ info->target = strdupW( target );
+ info->source = strdupW( source );
+ info->action = strdupW( action );
+ CoCreateGuid( &info->guid );
+
+ EnterCriticalSection( &msi_custom_action_cs );
+ list_add_tail( &msi_pending_custom_actions, &info->entry );
+ LeaveCriticalSection( &msi_custom_action_cs );
+
+ info->handle = CreateThread( NULL, 0, ConcurrentInstallThread, &info->guid, 0, NULL );
+ if (!info->handle)
+ {
+ /* release both references */
+ release_custom_action_data( info );
+ release_custom_action_data( info );
+ return NULL;
+ }
- return CreateThread(NULL, 0, DllThread, info, 0, NULL);
+ return info;
+}
+
+static UINT HANDLE_CustomType23(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action)
+{
+ msi_custom_action_info *info;
+ WCHAR package_path[MAX_PATH];
+ DWORD size;
+ UINT r;
+
+ static const WCHAR backslash[] = {'\\',0};
+
+ size = MAX_PATH;
+ MSI_GetPropertyW(package, cszSourceDir, package_path, &size);
+ lstrcatW(package_path, backslash);
+ lstrcatW(package_path, source);
+
+ TRACE("Installing package %s concurrently\n", debugstr_w(package_path));
+
+ info = do_msidbCAConcurrentInstall(package, type, package_path, target, action);
+
+ r = wait_thread_handle(info);
+ release_custom_action_data( info );
+ return r;
}
static UINT HANDLE_CustomType1(MSIPACKAGE *package, LPCWSTR source,
LPCWSTR target, const INT type, LPCWSTR action)
{
+ msi_custom_action_info *info;
WCHAR tmp_file[MAX_PATH];
- UINT rc = ERROR_SUCCESS;
- BOOL finished = FALSE;
- HANDLE ThreadHandle;
+ UINT r;
- store_binary_to_temp(package, source, tmp_file);
+ r = store_binary_to_temp(package, source, tmp_file);
+ if (r != ERROR_SUCCESS)
+ return r;
TRACE("Calling function %s from %s\n",debugstr_w(target),
debugstr_w(tmp_file));
strcatW(tmp_file,dot);
}
- ThreadHandle = do_msidbCustomActionTypeDll( package, tmp_file, target );
-
- rc = process_handle(package, type, ThreadHandle, NULL, action, &finished );
+ info = do_msidbCustomActionTypeDll( package, type, tmp_file, target, action );
- if (!finished)
- track_tempfile(package, tmp_file, tmp_file);
- else
- DeleteFileW(tmp_file);
-
- return rc;
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+ return r;
}
static UINT HANDLE_CustomType2(MSIPACKAGE *package, LPCWSTR source,
PROCESS_INFORMATION info;
BOOL rc;
INT len;
- WCHAR *deformated;
+ WCHAR *deformated = NULL;
WCHAR *cmd;
static const WCHAR spc[] = {' ',0};
- UINT prc = ERROR_SUCCESS;
- BOOL finished = FALSE;
+ UINT r;
memset(&si,0,sizeof(STARTUPINFOW));
- store_binary_to_temp(package, source, tmp_file);
+ r = store_binary_to_temp(package, source, tmp_file);
+ if (r != ERROR_SUCCESS)
+ return r;
deformat_string(package,target,&deformated);
rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
c_collen, &si, &info);
-
+ msi_free(cmd);
if ( !rc )
{
ERR("Unable to execute command %s\n", debugstr_w(cmd));
- msi_free(cmd);
return ERROR_SUCCESS;
}
- msi_free(cmd);
-
- prc = process_handle(package, type, info.hThread, info.hProcess, action,
- &finished);
+ CloseHandle( info.hThread );
- if (!finished)
- track_tempfile(package, tmp_file, tmp_file);
- else
- DeleteFileW(tmp_file);
+ r = wait_process_handle(package, type, info.hProcess, action);
- return prc;
+ return r;
}
static UINT HANDLE_CustomType17(MSIPACKAGE *package, LPCWSTR source,
LPCWSTR target, const INT type, LPCWSTR action)
{
- HANDLE hThread;
+ msi_custom_action_info *info;
MSIFILE *file;
+ UINT r;
TRACE("%s %s\n", debugstr_w(source), debugstr_w(target));
return ERROR_FUNCTION_FAILED;
}
- hThread = do_msidbCustomActionTypeDll( package, file->TargetPath, target );
+ info = do_msidbCustomActionTypeDll( package, type, file->TargetPath, target, action );
- return process_handle(package, type, hThread, NULL, action, NULL );
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+ return r;
}
static UINT HANDLE_CustomType18(MSIPACKAGE *package, LPCWSTR source,
INT len;
static const WCHAR spc[] = {' ',0};
MSIFILE *file;
- UINT prc;
memset(&si,0,sizeof(STARTUPINFOW));
rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
c_collen, &si, &info);
-
if ( !rc )
{
ERR("Unable to execute command %s\n", debugstr_w(cmd));
return ERROR_SUCCESS;
}
msi_free(cmd);
+ CloseHandle( info.hThread );
- prc = process_handle(package, type, info.hThread, info.hProcess, action,
- NULL);
-
- return prc;
+ return wait_process_handle(package, type, info.hProcess, action);
}
static UINT HANDLE_CustomType19(MSIPACKAGE *package, LPCWSTR source,
if( row )
{
LPCWSTR error = MSI_RecordGetString( row, 1 );
- MessageBoxW( NULL, error, NULL, MB_OK );
+ if ((gUILevel & INSTALLUILEVEL_MASK) != INSTALLUILEVEL_NONE)
+ MessageBoxW( NULL, error, NULL, MB_OK );
msiobj_release( &row->hdr );
}
- else
+ else if ((gUILevel & INSTALLUILEVEL_MASK) != INSTALLUILEVEL_NONE)
MessageBoxW( NULL, deformated, NULL, MB_OK );
msi_free( deformated );
- return ERROR_FUNCTION_FAILED;
+ return ERROR_INSTALL_FAILURE;
}
static UINT HANDLE_CustomType50(MSIPACKAGE *package, LPCWSTR source,
rc = CreateProcessW(NULL, cmd, NULL, NULL, FALSE, 0, NULL,
c_collen, &si, &info);
-
if ( !rc )
{
ERR("Unable to execute command %s\n", debugstr_w(cmd));
}
msi_free(cmd);
- return process_handle(package, type, info.hThread, info.hProcess, action, NULL);
+ CloseHandle( info.hThread );
+
+ return wait_process_handle(package, type, info.hProcess, action);
}
static UINT HANDLE_CustomType34(MSIPACKAGE *package, LPCWSTR source,
STARTUPINFOW si;
PROCESS_INFORMATION info;
BOOL rc;
- UINT prc;
memset(&si,0,sizeof(STARTUPINFOW));
- filename = resolve_folder(package, source, FALSE, FALSE, NULL);
+ filename = resolve_folder(package, source, FALSE, FALSE, TRUE, NULL);
if (!filename)
return ERROR_FUNCTION_FAILED;
return ERROR_SUCCESS;
}
msi_free(deformated);
+ CloseHandle( info.hThread );
+
+ return wait_process_handle(package, type, info.hProcess, action);
+}
- prc = process_handle(package, type, info.hThread, info.hProcess, action,
- NULL);
+static DWORD ACTION_CallScript( const GUID *guid )
+{
+ msi_custom_action_info *info;
+ MSIHANDLE hPackage;
+ UINT r = ERROR_FUNCTION_FAILED;
- return prc;
+ info = find_action_by_guid( guid );
+ if (!info)
+ {
+ ERR("failed to find action %s\n", debugstr_guid( guid) );
+ return r;
+ }
+
+ TRACE("function %s, script %s\n", debugstr_w( info->target ), debugstr_w( info->source ) );
+
+ hPackage = alloc_msihandle( &info->package->hdr );
+ if (hPackage)
+ {
+ r = call_script( hPackage, info->type, info->source, info->target, info->action );
+ MsiCloseHandle( hPackage );
+ }
+ else
+ ERR("failed to create handle for %p\n", info->package );
+
+ release_custom_action_data( info );
+
+ return S_OK;
}
+static DWORD WINAPI ScriptThread( LPVOID arg )
+{
+ LPGUID guid = arg;
+ DWORD rc = 0;
+
+ TRACE("custom action (%x) started\n", GetCurrentThreadId() );
+
+ rc = ACTION_CallScript( guid );
+
+ TRACE("custom action (%x) returned %i\n", GetCurrentThreadId(), rc );
+
+ MsiCloseAllHandles();
+ return rc;
+}
-void ACTION_FinishCustomActions(MSIPACKAGE* package)
+static msi_custom_action_info *do_msidbCustomActionTypeScript(
+ MSIPACKAGE *package, INT type, LPCWSTR script, LPCWSTR function, LPCWSTR action )
{
- struct list *item, *cursor;
- DWORD rc;
+ msi_custom_action_info *info;
+
+ info = msi_alloc( sizeof *info );
+ if (!info)
+ return NULL;
- LIST_FOR_EACH_SAFE( item, cursor, &package->RunningActions )
+ msiobj_addref( &package->hdr );
+ info->refs = 2; /* 1 for our caller and 1 for thread we created */
+ info->package = package;
+ info->type = type;
+ info->target = strdupW( function );
+ info->source = strdupW( script );
+ info->action = strdupW( action );
+ CoCreateGuid( &info->guid );
+
+ EnterCriticalSection( &msi_custom_action_cs );
+ list_add_tail( &msi_pending_custom_actions, &info->entry );
+ LeaveCriticalSection( &msi_custom_action_cs );
+
+ info->handle = CreateThread( NULL, 0, ScriptThread, &info->guid, 0, NULL );
+ if (!info->handle)
{
- MSIRUNNINGACTION *action = LIST_ENTRY( item, MSIRUNNINGACTION, entry );
+ /* release both references */
+ release_custom_action_data( info );
+ release_custom_action_data( info );
+ return NULL;
+ }
- TRACE("Checking on action %s\n", debugstr_w(action->name));
+ return info;
+}
- list_remove( &action->entry );
+static UINT HANDLE_CustomType37_38(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action)
+{
+ UINT r;
+ msi_custom_action_info *info;
- if (action->process)
- GetExitCodeProcess( action->handle, &rc );
- else
- GetExitCodeThread( action->handle, &rc );
+ TRACE("%s %s\n", debugstr_w(source), debugstr_w(target));
- if (rc == STILL_ACTIVE)
- {
- TRACE("Waiting on action %s\n", debugstr_w( action->name) );
- msi_dialog_check_messages( action->handle );
- }
+ info = do_msidbCustomActionTypeScript( package, type, target, NULL, action );
+
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+ return r;
+}
+
+static UINT HANDLE_CustomType5_6(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action)
+{
+ static const WCHAR query[] = {
+ 'S','E','L','E','C','T',' ','*',' ','F','R','O','M',' ',
+ '`','B','i' ,'n','a','r','y','`',' ','W','H','E','R','E',' ',
+ '`','N','a','m','e','`',' ','=',' ','\'','%','s','\'',0};
+ MSIRECORD *row = 0;
+ msi_custom_action_info *info;
+ CHAR *buffer = NULL;
+ WCHAR *bufferw = NULL;
+ DWORD sz = 0;
+ UINT r;
+
+ TRACE("%s %s\n", debugstr_w(source), debugstr_w(target));
+
+ row = MSI_QueryGetRecord(package->db, query, source);
+ if (!row)
+ return ERROR_FUNCTION_FAILED;
+
+ r = MSI_RecordReadStream(row, 2, NULL, &sz);
+ if (r != ERROR_SUCCESS)
+ return r;
+
+ buffer = msi_alloc(sizeof(CHAR)*(sz+1));
+ if (!buffer)
+ return ERROR_FUNCTION_FAILED;
+
+ r = MSI_RecordReadStream(row, 2, buffer, &sz);
+ if (r != ERROR_SUCCESS)
+ goto done;
+
+ buffer[sz] = 0;
+ bufferw = strdupAtoW(buffer);
+ if (!bufferw)
+ {
+ r = ERROR_FUNCTION_FAILED;
+ goto done;
+ }
+
+ info = do_msidbCustomActionTypeScript( package, type, bufferw, target, action );
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+
+done:
+ msi_free(bufferw);
+ msi_free(buffer);
+ return r;
+}
+
+static UINT HANDLE_CustomType21_22(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action)
+{
+ msi_custom_action_info *info;
+ MSIFILE *file;
+ HANDLE hFile;
+ DWORD sz, szHighWord = 0, read;
+ CHAR *buffer=NULL;
+ WCHAR *bufferw=NULL;
+ BOOL bRet;
+ UINT r;
+
+ TRACE("%s %s\n", debugstr_w(source), debugstr_w(target));
+
+ file = get_loaded_file(package,source);
+ if (!file)
+ {
+ ERR("invalid file key %s\n", debugstr_w(source));
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ hFile = CreateFileW(file->TargetPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return ERROR_FUNCTION_FAILED;
+
+ sz = GetFileSize(hFile, &szHighWord);
+ if (sz == INVALID_FILE_SIZE || szHighWord != 0)
+ {
+ CloseHandle(hFile);
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ buffer = msi_alloc(sizeof(CHAR)*(sz+1));
+ if (!buffer)
+ {
+ CloseHandle(hFile);
+ return ERROR_FUNCTION_FAILED;
+ }
+
+ bRet = ReadFile(hFile, buffer, sz, &read, NULL);
+ CloseHandle(hFile);
+ if (!bRet)
+ {
+ r = ERROR_FUNCTION_FAILED;
+ goto done;
+ }
+
+ buffer[read] = 0;
+ bufferw = strdupAtoW(buffer);
+ if (!bufferw)
+ {
+ r = ERROR_FUNCTION_FAILED;
+ goto done;
+ }
+
+ info = do_msidbCustomActionTypeScript( package, type, bufferw, target, action );
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+
+done:
+ msi_free(bufferw);
+ msi_free(buffer);
+ return r;
+}
+
+static UINT HANDLE_CustomType53_54(MSIPACKAGE *package, LPCWSTR source,
+ LPCWSTR target, const INT type, LPCWSTR action)
+{
+ msi_custom_action_info *info;
+ WCHAR *prop;
+ UINT r;
+
+ TRACE("%s %s\n", debugstr_w(source), debugstr_w(target));
+
+ prop = msi_dup_property(package,source);
+ if (!prop)
+ return ERROR_SUCCESS;
+
+ info = do_msidbCustomActionTypeScript( package, type, prop, NULL, action );
+ msi_free(prop);
+ r = wait_thread_handle( info );
+ release_custom_action_data( info );
+ return r;
+}
+
+void ACTION_FinishCustomActions(const MSIPACKAGE* package)
+{
+ struct list *item;
+ HANDLE *wait_handles;
+ unsigned int handle_count, i;
+ msi_custom_action_info *info, *cursor;
+
+ while ((item = list_head( &package->RunningActions )))
+ {
+ MSIRUNNINGACTION *action = LIST_ENTRY( item, MSIRUNNINGACTION, entry );
+
+ list_remove( &action->entry );
+
+ TRACE("waiting for %s\n", debugstr_w( action->name ) );
+ msi_dialog_check_messages( action->handle );
CloseHandle( action->handle );
msi_free( action->name );
msi_free( action );
}
+
+ EnterCriticalSection( &msi_custom_action_cs );
+
+ handle_count = list_count( &msi_pending_custom_actions );
+ wait_handles = HeapAlloc( GetProcessHeap(), 0, handle_count * sizeof(HANDLE) );
+
+ handle_count = 0;
+ LIST_FOR_EACH_ENTRY_SAFE( info, cursor, &msi_pending_custom_actions, msi_custom_action_info, entry )
+ {
+ if (info->package == package )
+ {
+ if (DuplicateHandle(GetCurrentProcess(), info->handle, GetCurrentProcess(), &wait_handles[handle_count], SYNCHRONIZE, FALSE, 0))
+ handle_count++;
+ }
+ }
+
+ LeaveCriticalSection( &msi_custom_action_cs );
+
+ for (i = 0; i < handle_count; i++)
+ {
+ msi_dialog_check_messages( wait_handles[i] );
+ CloseHandle( wait_handles[i] );
+ }
+
+ HeapFree( GetProcessHeap(), 0, wait_handles );
+}
+
+typedef struct _msi_custom_remote_impl {
+ const IWineMsiRemoteCustomActionVtbl *lpVtbl;
+ LONG refs;
+} msi_custom_remote_impl;
+
+static inline msi_custom_remote_impl* mcr_from_IWineMsiRemoteCustomAction( IWineMsiRemoteCustomAction* iface )
+{
+ return (msi_custom_remote_impl*) iface;
+}
+
+static HRESULT WINAPI mcr_QueryInterface( IWineMsiRemoteCustomAction *iface,
+ REFIID riid,LPVOID *ppobj)
+{
+ if( IsEqualCLSID( riid, &IID_IUnknown ) ||
+ IsEqualCLSID( riid, &IID_IWineMsiRemoteCustomAction ) )
+ {
+ IUnknown_AddRef( iface );
+ *ppobj = iface;
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+static ULONG WINAPI mcr_AddRef( IWineMsiRemoteCustomAction *iface )
+{
+ msi_custom_remote_impl* This = mcr_from_IWineMsiRemoteCustomAction( iface );
+
+ return InterlockedIncrement( &This->refs );
+}
+
+static ULONG WINAPI mcr_Release( IWineMsiRemoteCustomAction *iface )
+{
+ msi_custom_remote_impl* This = mcr_from_IWineMsiRemoteCustomAction( iface );
+ ULONG r;
+
+ r = InterlockedDecrement( &This->refs );
+ if (r == 0)
+ msi_free( This );
+ return r;
+}
+
+static HRESULT WINAPI mcr_GetActionInfo( IWineMsiRemoteCustomAction *iface, LPCGUID custom_action_guid,
+ INT *type, MSIHANDLE *handle, BSTR *dll, BSTR *func, IWineMsiRemotePackage **remote_package )
+{
+ msi_custom_action_info *info;
+
+ info = find_action_by_guid( custom_action_guid );
+ if (!info)
+ return E_FAIL;
+
+ *type = info->type;
+ *handle = alloc_msihandle( &info->package->hdr );
+ *dll = SysAllocString( info->source );
+ *func = SysAllocString( info->target );
+
+ release_custom_action_data( info );
+ return create_msi_remote_package( NULL, (LPVOID *)remote_package );
+}
+
+static const IWineMsiRemoteCustomActionVtbl msi_custom_remote_vtbl =
+{
+ mcr_QueryInterface,
+ mcr_AddRef,
+ mcr_Release,
+ mcr_GetActionInfo,
+};
+
+HRESULT create_msi_custom_remote( IUnknown *pOuter, LPVOID *ppObj )
+{
+ msi_custom_remote_impl* This;
+
+ This = msi_alloc( sizeof *This );
+ if (!This)
+ return E_OUTOFMEMORY;
+
+ This->lpVtbl = &msi_custom_remote_vtbl;
+ This->refs = 1;
+
+ *ppObj = This;
+
+ return S_OK;
}