| /* | 
 |  * msiexec.exe implementation | 
 |  * | 
 |  * Copyright 2004 Vincent Béron | 
 |  * Copyright 2005 Mike McCormack | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2.1 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the Free Software | 
 |  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 |  */ | 
 |  | 
 | #define WIN32_LEAN_AND_MEAN | 
 |  | 
 | #include <windows.h> | 
 | #include <msi.h> | 
 | #include <winsvc.h> | 
 | #include <objbase.h> | 
 | #include <stdio.h> | 
 |  | 
 | #include "wine/debug.h" | 
 | #include "wine/unicode.h" | 
 |  | 
 | WINE_DEFAULT_DEBUG_CHANNEL(msiexec); | 
 |  | 
 | typedef HRESULT (WINAPI *DLLREGISTERSERVER)(void); | 
 | typedef HRESULT (WINAPI *DLLUNREGISTERSERVER)(void); | 
 |  | 
 | DWORD DoService(void); | 
 |  | 
 | struct string_list | 
 | { | 
 | 	struct string_list *next; | 
 | 	WCHAR str[1]; | 
 | }; | 
 |  | 
 | static const WCHAR ActionAdmin[] = { | 
 |    'A','C','T','I','O','N','=','A','D','M','I','N',0 }; | 
 | static const WCHAR RemoveAll[] = { | 
 |    'R','E','M','O','V','E','=','A','L','L',0 }; | 
 |  | 
 | static const WCHAR InstallRunOnce[] = { | 
 |    'S','o','f','t','w','a','r','e','\\', | 
 |    'M','i','c','r','o','s','o','f','t','\\', | 
 |    'W','i','n','d','o','w','s','\\', | 
 |    'C','u','r','r','e','n','t','V','e','r','s','i','o','n','\\', | 
 |    'I','n','s','t','a','l','l','e','r','\\', | 
 |    'R','u','n','O','n','c','e','E','n','t','r','i','e','s',0}; | 
 |  | 
 | static void ShowUsage(int ExitCode) | 
 | { | 
 |     WCHAR msiexec_version[40]; | 
 |     WCHAR filename[MAX_PATH]; | 
 |     LPWSTR msi_res; | 
 |     LPWSTR msiexec_help; | 
 |     HMODULE hmsi = GetModuleHandleA("msi.dll"); | 
 |     DWORD len; | 
 |     DWORD res; | 
 |  | 
 |     /* MsiGetFileVersion need the full path */ | 
 |     *filename = 0; | 
 |     res = GetModuleFileNameW(hmsi, filename, sizeof(filename) / sizeof(filename[0])); | 
 |     if (!res) | 
 |         WINE_ERR("GetModuleFileName failed: %d\n", GetLastError()); | 
 |  | 
 |     len = sizeof(msiexec_version) / sizeof(msiexec_version[0]); | 
 |     *msiexec_version = 0; | 
 |     res = MsiGetFileVersionW(filename, msiexec_version, &len, NULL, NULL); | 
 |     if (res) | 
 |         WINE_ERR("MsiGetFileVersion failed with %d\n", res); | 
 |  | 
 |     /* Return the length of the resource. | 
 |        No typo: The LPWSTR parameter must be a LPWSTR * for this mode */ | 
 |     len = LoadStringW(hmsi, 10, (LPWSTR) &msi_res, 0); | 
 |  | 
 |     msi_res = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR)); | 
 |     msiexec_help = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR) + sizeof(msiexec_version)); | 
 |     if (msi_res && msiexec_help) { | 
 |         *msi_res = 0; | 
 |         LoadStringW(hmsi, 10, msi_res, len + 1); | 
 |  | 
 |         sprintfW(msiexec_help, msi_res, msiexec_version); | 
 |         MsiMessageBoxW(0, msiexec_help, NULL, 0, GetUserDefaultLangID(), 0); | 
 |     } | 
 |     HeapFree(GetProcessHeap(), 0, msi_res); | 
 |     HeapFree(GetProcessHeap(), 0, msiexec_help); | 
 |     ExitProcess(ExitCode); | 
 | } | 
 |  | 
 | static BOOL IsProductCode(LPWSTR str) | 
 | { | 
 | 	GUID ProductCode; | 
 |  | 
 | 	if(lstrlenW(str) != 38) | 
 | 		return FALSE; | 
 | 	return ( (CLSIDFromString(str, &ProductCode) == NOERROR) ); | 
 |  | 
 | } | 
 |  | 
 | static VOID StringListAppend(struct string_list **list, LPCWSTR str) | 
 | { | 
 | 	struct string_list *entry; | 
 | 	DWORD size; | 
 |  | 
 | 	size = sizeof *entry + lstrlenW(str) * sizeof (WCHAR); | 
 | 	entry = HeapAlloc(GetProcessHeap(), 0, size); | 
 | 	if(!entry) | 
 | 	{ | 
 | 		WINE_ERR("Out of memory!\n"); | 
 | 		ExitProcess(1); | 
 | 	} | 
 | 	lstrcpyW(entry->str, str); | 
 | 	entry->next = NULL; | 
 |  | 
 | 	/* | 
 | 	 * Ignoring o(n^2) time complexity to add n strings for simplicity, | 
 | 	 *  add the string to the end of the list to preserve the order. | 
 | 	 */ | 
 | 	while( *list ) | 
 | 		list = &(*list)->next; | 
 | 	*list = entry; | 
 | } | 
 |  | 
 | static LPWSTR build_properties(struct string_list *property_list) | 
 | { | 
 | 	struct string_list *list; | 
 | 	LPWSTR ret, p, value; | 
 | 	DWORD len; | 
 | 	BOOL needs_quote; | 
 |  | 
 | 	if(!property_list) | 
 | 		return NULL; | 
 |  | 
 | 	/* count the space we need */ | 
 | 	len = 1; | 
 | 	for(list = property_list; list; list = list->next) | 
 | 		len += lstrlenW(list->str) + 3; | 
 |  | 
 | 	ret = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR) ); | 
 |  | 
 | 	/* add a space before each string, and quote the value */ | 
 | 	p = ret; | 
 | 	for(list = property_list; list; list = list->next) | 
 | 	{ | 
 | 		value = strchrW(list->str,'='); | 
 | 		if(!value) | 
 | 			continue; | 
 | 		len = value - list->str; | 
 | 		*p++ = ' '; | 
 | 		memcpy(p, list->str, len * sizeof(WCHAR)); | 
 | 		p += len; | 
 | 		*p++ = '='; | 
 |  | 
 | 		/* check if the value contains spaces and maybe quote it */ | 
 | 		value++; | 
 | 		needs_quote = strchrW(value,' ') ? 1 : 0; | 
 | 		if(needs_quote) | 
 | 			*p++ = '"'; | 
 | 		len = lstrlenW(value); | 
 | 		memcpy(p, value, len * sizeof(WCHAR)); | 
 | 		p += len; | 
 | 		if(needs_quote) | 
 | 			*p++ = '"'; | 
 | 	} | 
 | 	*p = 0; | 
 |  | 
 | 	WINE_TRACE("properties -> %s\n", wine_dbgstr_w(ret) ); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static LPWSTR build_transforms(struct string_list *transform_list) | 
 | { | 
 | 	struct string_list *list; | 
 | 	LPWSTR ret, p; | 
 | 	DWORD len; | 
 |  | 
 | 	/* count the space we need */ | 
 | 	len = 1; | 
 | 	for(list = transform_list; list; list = list->next) | 
 | 		len += lstrlenW(list->str) + 1; | 
 |  | 
 | 	ret = HeapAlloc( GetProcessHeap(), 0, len*sizeof(WCHAR) ); | 
 |  | 
 | 	/* add all the transforms with a semicolon between each one */ | 
 | 	p = ret; | 
 | 	for(list = transform_list; list; list = list->next) | 
 | 	{ | 
 | 		len = lstrlenW(list->str); | 
 | 		lstrcpynW(p, list->str, len ); | 
 | 		p += len; | 
 | 		if(list->next) | 
 | 			*p++ = ';'; | 
 | 	} | 
 | 	*p = 0; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static DWORD msi_atou(LPCWSTR str) | 
 | { | 
 | 	DWORD ret = 0; | 
 | 	while(*str >= '0' && *str <= '9') | 
 | 	{ | 
 | 		ret *= 10; | 
 | 		ret += (*str - '0'); | 
 | 		str++; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static LPWSTR msi_strdup(LPCWSTR str) | 
 | { | 
 | 	DWORD len = lstrlenW(str)+1; | 
 | 	LPWSTR ret = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*len); | 
 | 	lstrcpyW(ret, str); | 
 | 	return ret; | 
 | } | 
 |  | 
 | /* str1 is the same as str2, ignoring case */ | 
 | static BOOL msi_strequal(LPCWSTR str1, LPCSTR str2) | 
 | { | 
 | 	DWORD len, ret; | 
 | 	LPWSTR strW; | 
 |  | 
 | 	len = MultiByteToWideChar( CP_ACP, 0, str2, -1, NULL, 0); | 
 | 	if( !len ) | 
 | 		return FALSE; | 
 | 	if( lstrlenW(str1) != (len-1) ) | 
 | 		return FALSE; | 
 | 	strW = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*len); | 
 | 	MultiByteToWideChar( CP_ACP, 0, str2, -1, strW, len); | 
 | 	ret = CompareStringW(GetThreadLocale(), NORM_IGNORECASE, str1, len, strW, len); | 
 | 	HeapFree(GetProcessHeap(), 0, strW); | 
 | 	return (ret == CSTR_EQUAL); | 
 | } | 
 |  | 
 | /* prefix is hyphen or dash, and str1 is the same as str2, ignoring case */ | 
 | static BOOL msi_option_equal(LPCWSTR str1, LPCSTR str2) | 
 | { | 
 |     if (str1[0] != '/' && str1[0] != '-') | 
 |         return FALSE; | 
 |  | 
 |     /* skip over the hyphen or slash */ | 
 |     return msi_strequal(str1 + 1, str2); | 
 | } | 
 |  | 
 | /* str2 is at the beginning of str1, ignoring case */ | 
 | static BOOL msi_strprefix(LPCWSTR str1, LPCSTR str2) | 
 | { | 
 | 	DWORD len, ret; | 
 | 	LPWSTR strW; | 
 |  | 
 | 	len = MultiByteToWideChar( CP_ACP, 0, str2, -1, NULL, 0); | 
 | 	if( !len ) | 
 | 		return FALSE; | 
 | 	if( lstrlenW(str1) < (len-1) ) | 
 | 		return FALSE; | 
 | 	strW = HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR)*len); | 
 | 	MultiByteToWideChar( CP_ACP, 0, str2, -1, strW, len); | 
 | 	ret = CompareStringW(GetThreadLocale(), NORM_IGNORECASE, str1, len-1, strW, len-1); | 
 | 	HeapFree(GetProcessHeap(), 0, strW); | 
 | 	return (ret == CSTR_EQUAL); | 
 | } | 
 |  | 
 | /* prefix is hyphen or dash, and str2 is at the beginning of str1, ignoring case */ | 
 | static BOOL msi_option_prefix(LPCWSTR str1, LPCSTR str2) | 
 | { | 
 |     if (str1[0] != '/' && str1[0] != '-') | 
 |         return FALSE; | 
 |  | 
 |     /* skip over the hyphen or slash */ | 
 |     return msi_strprefix(str1 + 1, str2); | 
 | } | 
 |  | 
 | static VOID *LoadProc(LPCWSTR DllName, LPCSTR ProcName, HMODULE* DllHandle) | 
 | { | 
 | 	VOID* (*proc)(void); | 
 |  | 
 | 	*DllHandle = LoadLibraryExW(DllName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); | 
 | 	if(!*DllHandle) | 
 | 	{ | 
 | 		fprintf(stderr, "Unable to load dll %s\n", wine_dbgstr_w(DllName)); | 
 | 		ExitProcess(1); | 
 | 	} | 
 | 	proc = (VOID *) GetProcAddress(*DllHandle, ProcName); | 
 | 	if(!proc) | 
 | 	{ | 
 | 		fprintf(stderr, "Dll %s does not implement function %s\n", | 
 | 			wine_dbgstr_w(DllName), ProcName); | 
 | 		FreeLibrary(*DllHandle); | 
 | 		ExitProcess(1); | 
 | 	} | 
 |  | 
 | 	return proc; | 
 | } | 
 |  | 
 | static DWORD DoDllRegisterServer(LPCWSTR DllName) | 
 | { | 
 | 	HRESULT hr; | 
 | 	DLLREGISTERSERVER pfDllRegisterServer = NULL; | 
 | 	HMODULE DllHandle = NULL; | 
 |  | 
 | 	pfDllRegisterServer = LoadProc(DllName, "DllRegisterServer", &DllHandle); | 
 |  | 
 | 	hr = pfDllRegisterServer(); | 
 | 	if(FAILED(hr)) | 
 | 	{ | 
 | 		fprintf(stderr, "Failed to register dll %s\n", wine_dbgstr_w(DllName)); | 
 | 		return 1; | 
 | 	} | 
 | 	printf("Successfully registered dll %s\n", wine_dbgstr_w(DllName)); | 
 | 	if(DllHandle) | 
 | 		FreeLibrary(DllHandle); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static DWORD DoDllUnregisterServer(LPCWSTR DllName) | 
 | { | 
 | 	HRESULT hr; | 
 | 	DLLUNREGISTERSERVER pfDllUnregisterServer = NULL; | 
 | 	HMODULE DllHandle = NULL; | 
 |  | 
 | 	pfDllUnregisterServer = LoadProc(DllName, "DllUnregisterServer", &DllHandle); | 
 |  | 
 | 	hr = pfDllUnregisterServer(); | 
 | 	if(FAILED(hr)) | 
 | 	{ | 
 | 		fprintf(stderr, "Failed to unregister dll %s\n", wine_dbgstr_w(DllName)); | 
 | 		return 1; | 
 | 	} | 
 | 	printf("Successfully unregistered dll %s\n", wine_dbgstr_w(DllName)); | 
 | 	if(DllHandle) | 
 | 		FreeLibrary(DllHandle); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static DWORD DoRegServer(void) | 
 | { | 
 |     SC_HANDLE scm, service; | 
 |     CHAR path[MAX_PATH+12]; | 
 |     DWORD ret = 0; | 
 |  | 
 |     scm = OpenSCManagerA(NULL, SERVICES_ACTIVE_DATABASEA, SC_MANAGER_CREATE_SERVICE); | 
 |     if (!scm) | 
 |     { | 
 |         fprintf(stderr, "Failed to open the service control manager.\n"); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     GetSystemDirectoryA(path, MAX_PATH); | 
 |     lstrcatA(path, "\\msiexec.exe /V"); | 
 |  | 
 |     service = CreateServiceA(scm, "MSIServer", "MSIServer", GENERIC_ALL, | 
 |                              SERVICE_WIN32_SHARE_PROCESS, SERVICE_DEMAND_START, | 
 |                              SERVICE_ERROR_NORMAL, path, NULL, NULL, | 
 |                              NULL, NULL, NULL); | 
 |  | 
 |     if (service) CloseServiceHandle(service); | 
 |     else if (GetLastError() != ERROR_SERVICE_EXISTS) | 
 |     { | 
 |         fprintf(stderr, "Failed to create MSI service\n"); | 
 |         ret = 1; | 
 |     } | 
 |     CloseServiceHandle(scm); | 
 |     return ret; | 
 | } | 
 |  | 
 | static INT DoEmbedding( LPWSTR key ) | 
 | { | 
 | 	printf("Remote custom actions are not supported yet\n"); | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  * state machine to break up the command line properly | 
 |  */ | 
 |  | 
 | enum chomp_state | 
 | { | 
 | 	cs_whitespace, | 
 | 	cs_token, | 
 | 	cs_quote | 
 | }; | 
 |  | 
 | static int chomp( WCHAR *str ) | 
 | { | 
 | 	enum chomp_state state = cs_token; | 
 | 	WCHAR *p, *out; | 
 | 	int count = 1, ignore; | 
 |  | 
 | 	for( p = str, out = str; *p; p++ ) | 
 | 	{ | 
 | 		ignore = 1; | 
 | 		switch( state ) | 
 | 		{ | 
 | 		case cs_whitespace: | 
 | 			switch( *p ) | 
 | 			{ | 
 | 			case ' ': | 
 | 				break; | 
 | 			case '"': | 
 | 				state = cs_quote; | 
 | 				count++; | 
 | 				break; | 
 | 			default: | 
 | 				count++; | 
 | 				ignore = 0; | 
 | 				state = cs_token; | 
 | 			} | 
 | 			break; | 
 |  | 
 | 		case cs_token: | 
 | 			switch( *p ) | 
 | 			{ | 
 | 			case '"': | 
 | 				state = cs_quote; | 
 | 				break; | 
 | 			case ' ': | 
 | 				state = cs_whitespace; | 
 | 				*out++ = 0; | 
 | 				break; | 
 | 			default: | 
 | 				ignore = 0; | 
 | 			} | 
 | 			break; | 
 |  | 
 | 		case cs_quote: | 
 | 			switch( *p ) | 
 | 			{ | 
 | 			case '"': | 
 | 				state = cs_token; | 
 | 				break; | 
 | 			default: | 
 | 				ignore = 0; | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 		if( !ignore ) | 
 | 			*out++ = *p; | 
 | 	} | 
 |  | 
 | 	*out = 0; | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static void process_args( WCHAR *cmdline, int *pargc, WCHAR ***pargv ) | 
 | { | 
 | 	WCHAR **argv, *p = msi_strdup(cmdline); | 
 | 	int i, n; | 
 |  | 
 | 	n = chomp( p ); | 
 | 	argv = HeapAlloc(GetProcessHeap(), 0, sizeof (WCHAR*)*(n+1)); | 
 | 	for( i=0; i<n; i++ ) | 
 | 	{ | 
 | 		argv[i] = p; | 
 | 		p += lstrlenW(p) + 1; | 
 | 	} | 
 | 	argv[i] = NULL; | 
 |  | 
 | 	*pargc = n; | 
 | 	*pargv = argv; | 
 | } | 
 |  | 
 | static BOOL process_args_from_reg( const WCHAR *ident, int *pargc, WCHAR ***pargv ) | 
 | { | 
 | 	LONG r; | 
 | 	HKEY hkey; | 
 | 	DWORD sz = 0, type = 0; | 
 | 	WCHAR *buf; | 
 | 	BOOL ret = FALSE; | 
 |  | 
 | 	r = RegOpenKeyW(HKEY_LOCAL_MACHINE, InstallRunOnce, &hkey); | 
 | 	if(r != ERROR_SUCCESS) | 
 | 		return FALSE; | 
 | 	r = RegQueryValueExW(hkey, ident, 0, &type, 0, &sz); | 
 | 	if(r == ERROR_SUCCESS && type == REG_SZ) | 
 | 	{ | 
 | 		int len = lstrlenW( *pargv[0] ); | 
 | 		if (!(buf = HeapAlloc( GetProcessHeap(), 0, sz + (len + 1) * sizeof(WCHAR) ))) | 
 | 		{ | 
 | 			RegCloseKey( hkey ); | 
 | 			return FALSE; | 
 | 		} | 
 | 		memcpy( buf, *pargv[0], len * sizeof(WCHAR) ); | 
 | 		buf[len++] = ' '; | 
 | 		r = RegQueryValueExW(hkey, ident, 0, &type, (LPBYTE)(buf + len), &sz); | 
 | 		if( r == ERROR_SUCCESS ) | 
 | 		{ | 
 | 			process_args(buf, pargc, pargv); | 
 | 			ret = TRUE; | 
 | 		} | 
 | 		HeapFree(GetProcessHeap(), 0, buf); | 
 | 	} | 
 | 	RegCloseKey(hkey); | 
 | 	return ret; | 
 | } | 
 |  | 
 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) | 
 | { | 
 | 	int i; | 
 | 	BOOL FunctionInstall = FALSE; | 
 | 	BOOL FunctionInstallAdmin = FALSE; | 
 | 	BOOL FunctionRepair = FALSE; | 
 | 	BOOL FunctionAdvertise = FALSE; | 
 | 	BOOL FunctionPatch = FALSE; | 
 | 	BOOL FunctionDllRegisterServer = FALSE; | 
 | 	BOOL FunctionDllUnregisterServer = FALSE; | 
 | 	BOOL FunctionRegServer = FALSE; | 
 | 	BOOL FunctionUnregServer = FALSE; | 
 | 	BOOL FunctionServer = FALSE; | 
 | 	BOOL FunctionUnknown = FALSE; | 
 |  | 
 | 	LPWSTR PackageName = NULL; | 
 | 	LPWSTR Properties = NULL; | 
 | 	struct string_list *property_list = NULL; | 
 |  | 
 | 	DWORD RepairMode = 0; | 
 |  | 
 | 	DWORD_PTR AdvertiseMode = 0; | 
 | 	struct string_list *transform_list = NULL; | 
 | 	LANGID Language = 0; | 
 |  | 
 | 	DWORD LogMode = 0; | 
 | 	LPWSTR LogFileName = NULL; | 
 | 	DWORD LogAttributes = 0; | 
 |  | 
 | 	LPWSTR PatchFileName = NULL; | 
 | 	INSTALLTYPE InstallType = INSTALLTYPE_DEFAULT; | 
 |  | 
 | 	INSTALLUILEVEL InstallUILevel = INSTALLUILEVEL_FULL; | 
 |  | 
 | 	LPWSTR DllName = NULL; | 
 | 	DWORD ReturnCode; | 
 | 	int argc; | 
 | 	LPWSTR *argvW = NULL; | 
 |  | 
 | 	/* parse the command line */ | 
 | 	process_args( GetCommandLineW(), &argc, &argvW ); | 
 |  | 
 | 	/* | 
 | 	 * If the args begin with /@ IDENT then we need to load the real | 
 | 	 * command line out of the RunOnceEntries key in the registry. | 
 | 	 *  We do that before starting to process the real commandline, | 
 | 	 * then overwrite the commandline again. | 
 | 	 */ | 
 | 	if(argc>1 && msi_option_equal(argvW[1], "@")) | 
 | 	{ | 
 | 		if(!process_args_from_reg( argvW[2], &argc, &argvW )) | 
 | 			return 1; | 
 | 	} | 
 |  | 
 | 	if (argc == 3 && msi_option_equal(argvW[1], "Embedding")) | 
 | 		return DoEmbedding( argvW[2] ); | 
 |  | 
 | 	for(i = 1; i < argc; i++) | 
 | 	{ | 
 | 		WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 |  | 
 | 		if (msi_option_equal(argvW[i], "regserver")) | 
 | 		{ | 
 | 			FunctionRegServer = TRUE; | 
 | 		} | 
 | 		else if (msi_option_equal(argvW[i], "unregserver") || msi_option_equal(argvW[i], "unregister") | 
 | 			||  msi_option_equal(argvW[i], "unreg")) | 
 | 		{ | 
 | 			FunctionUnregServer = TRUE; | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "i") || msi_option_prefix(argvW[i], "package")) | 
 | 		{ | 
 | 			LPWSTR argvWi = argvW[i]; | 
 | 			int argLen = (msi_option_prefix(argvW[i], "i") ? 2 : 8); | 
 | 			FunctionInstall = TRUE; | 
 | 			if(lstrlenW(argvW[i]) > argLen) | 
 | 				argvWi += argLen; | 
 | 			else | 
 | 			{ | 
 | 				i++; | 
 | 				if(i >= argc) | 
 | 					ShowUsage(1); | 
 | 				WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 				argvWi = argvW[i]; | 
 | 			} | 
 | 			PackageName = argvWi; | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "a")) | 
 | 		{ | 
 | 			FunctionInstall = TRUE; | 
 | 			FunctionInstallAdmin = TRUE; | 
 | 			InstallType = INSTALLTYPE_NETWORK_IMAGE; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PackageName = argvW[i]; | 
 | 			StringListAppend(&property_list, ActionAdmin); | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "f")) | 
 | 		{ | 
 | 			int j; | 
 | 			int len = lstrlenW(argvW[i]); | 
 | 			FunctionRepair = TRUE; | 
 | 			for(j = 2; j < len; j++) | 
 | 			{ | 
 | 				switch(argvW[i][j]) | 
 | 				{ | 
 | 					case 'P': | 
 | 					case 'p': | 
 | 						RepairMode |= REINSTALLMODE_FILEMISSING; | 
 | 						break; | 
 | 					case 'O': | 
 | 					case 'o': | 
 | 						RepairMode |= REINSTALLMODE_FILEOLDERVERSION; | 
 | 						break; | 
 | 					case 'E': | 
 | 					case 'e': | 
 | 						RepairMode |= REINSTALLMODE_FILEEQUALVERSION; | 
 | 						break; | 
 | 					case 'D': | 
 | 					case 'd': | 
 | 						RepairMode |= REINSTALLMODE_FILEEXACT; | 
 | 						break; | 
 | 					case 'C': | 
 | 					case 'c': | 
 | 						RepairMode |= REINSTALLMODE_FILEVERIFY; | 
 | 						break; | 
 | 					case 'A': | 
 | 					case 'a': | 
 | 						RepairMode |= REINSTALLMODE_FILEREPLACE; | 
 | 						break; | 
 | 					case 'U': | 
 | 					case 'u': | 
 | 						RepairMode |= REINSTALLMODE_USERDATA; | 
 | 						break; | 
 | 					case 'M': | 
 | 					case 'm': | 
 | 						RepairMode |= REINSTALLMODE_MACHINEDATA; | 
 | 						break; | 
 | 					case 'S': | 
 | 					case 's': | 
 | 						RepairMode |= REINSTALLMODE_SHORTCUT; | 
 | 						break; | 
 | 					case 'V': | 
 | 					case 'v': | 
 | 						RepairMode |= REINSTALLMODE_PACKAGE; | 
 | 						break; | 
 | 					default: | 
 | 						fprintf(stderr, "Unknown option \"%c\" in Repair mode\n", argvW[i][j]); | 
 | 						break; | 
 | 				} | 
 | 			} | 
 | 			if(len == 2) | 
 | 			{ | 
 | 				RepairMode = REINSTALLMODE_FILEMISSING | | 
 | 					REINSTALLMODE_FILEEQUALVERSION | | 
 | 					REINSTALLMODE_FILEVERIFY | | 
 | 					REINSTALLMODE_MACHINEDATA | | 
 | 					REINSTALLMODE_SHORTCUT; | 
 | 			} | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PackageName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "x") || msi_option_equal(argvW[i], "uninstall")) | 
 | 		{ | 
 | 			FunctionInstall = TRUE; | 
 | 			if(msi_option_prefix(argvW[i], "x")) PackageName = argvW[i]+2; | 
 | 			if(!PackageName || !PackageName[0]) | 
 | 			{ | 
 | 				i++; | 
 | 				if (i >= argc) | 
 | 					ShowUsage(1); | 
 | 				PackageName = argvW[i]; | 
 | 			} | 
 | 			WINE_TRACE("PackageName = %s\n", wine_dbgstr_w(PackageName)); | 
 | 			StringListAppend(&property_list, RemoveAll); | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "j")) | 
 | 		{ | 
 | 			int j; | 
 | 			int len = lstrlenW(argvW[i]); | 
 | 			FunctionAdvertise = TRUE; | 
 | 			for(j = 2; j < len; j++) | 
 | 			{ | 
 | 				switch(argvW[i][j]) | 
 | 				{ | 
 | 					case 'U': | 
 | 					case 'u': | 
 | 						AdvertiseMode = ADVERTISEFLAGS_USERASSIGN; | 
 | 						break; | 
 | 					case 'M': | 
 | 					case 'm': | 
 | 						AdvertiseMode = ADVERTISEFLAGS_MACHINEASSIGN; | 
 | 						break; | 
 | 					default: | 
 | 						fprintf(stderr, "Unknown option \"%c\" in Advertise mode\n", argvW[i][j]); | 
 | 						break; | 
 | 				} | 
 | 			} | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PackageName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_strequal(argvW[i], "u")) | 
 | 		{ | 
 | 			FunctionAdvertise = TRUE; | 
 | 			AdvertiseMode = ADVERTISEFLAGS_USERASSIGN; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PackageName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_strequal(argvW[i], "m")) | 
 | 		{ | 
 | 			FunctionAdvertise = TRUE; | 
 | 			AdvertiseMode = ADVERTISEFLAGS_MACHINEASSIGN; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PackageName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "t")) | 
 | 		{ | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			StringListAppend(&transform_list, argvW[i]); | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "g")) | 
 | 		{ | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			Language = msi_atou(argvW[i]); | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "l")) | 
 | 		{ | 
 | 			int j; | 
 | 			int len = lstrlenW(argvW[i]); | 
 | 			for(j = 2; j < len; j++) | 
 | 			{ | 
 | 				switch(argvW[i][j]) | 
 | 				{ | 
 | 					case 'I': | 
 | 					case 'i': | 
 | 						LogMode |= INSTALLLOGMODE_INFO; | 
 | 						break; | 
 | 					case 'W': | 
 | 					case 'w': | 
 | 						LogMode |= INSTALLLOGMODE_WARNING; | 
 | 						break; | 
 | 					case 'E': | 
 | 					case 'e': | 
 | 						LogMode |= INSTALLLOGMODE_ERROR; | 
 | 						break; | 
 | 					case 'A': | 
 | 					case 'a': | 
 | 						LogMode |= INSTALLLOGMODE_ACTIONSTART; | 
 | 						break; | 
 | 					case 'R': | 
 | 					case 'r': | 
 | 						LogMode |= INSTALLLOGMODE_ACTIONDATA; | 
 | 						break; | 
 | 					case 'U': | 
 | 					case 'u': | 
 | 						LogMode |= INSTALLLOGMODE_USER; | 
 | 						break; | 
 | 					case 'C': | 
 | 					case 'c': | 
 | 						LogMode |= INSTALLLOGMODE_COMMONDATA; | 
 | 						break; | 
 | 					case 'M': | 
 | 					case 'm': | 
 | 						LogMode |= INSTALLLOGMODE_FATALEXIT; | 
 | 						break; | 
 | 					case 'O': | 
 | 					case 'o': | 
 | 						LogMode |= INSTALLLOGMODE_OUTOFDISKSPACE; | 
 | 						break; | 
 | 					case 'P': | 
 | 					case 'p': | 
 | 						LogMode |= INSTALLLOGMODE_PROPERTYDUMP; | 
 | 						break; | 
 | 					case 'V': | 
 | 					case 'v': | 
 | 						LogMode |= INSTALLLOGMODE_VERBOSE; | 
 | 						break; | 
 | 					case '*': | 
 | 						LogMode = INSTALLLOGMODE_FATALEXIT | | 
 | 							INSTALLLOGMODE_ERROR | | 
 | 							INSTALLLOGMODE_WARNING | | 
 | 							INSTALLLOGMODE_USER | | 
 | 							INSTALLLOGMODE_INFO | | 
 | 							INSTALLLOGMODE_RESOLVESOURCE | | 
 | 							INSTALLLOGMODE_OUTOFDISKSPACE | | 
 | 							INSTALLLOGMODE_ACTIONSTART | | 
 | 							INSTALLLOGMODE_ACTIONDATA | | 
 | 							INSTALLLOGMODE_COMMONDATA | | 
 | 							INSTALLLOGMODE_PROPERTYDUMP | | 
 | 							INSTALLLOGMODE_PROGRESS | | 
 | 							INSTALLLOGMODE_INITIALIZE | | 
 | 							INSTALLLOGMODE_TERMINATE | | 
 | 							INSTALLLOGMODE_SHOWDIALOG; | 
 | 						break; | 
 | 					case '+': | 
 | 						LogAttributes |= INSTALLLOGATTRIBUTES_APPEND; | 
 | 						break; | 
 | 					case '!': | 
 | 						LogAttributes |= INSTALLLOGATTRIBUTES_FLUSHEACHLINE; | 
 | 						break; | 
 | 					default: | 
 | 						break; | 
 | 				} | 
 | 			} | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			LogFileName = argvW[i]; | 
 | 			if(MsiEnableLogW(LogMode, LogFileName, LogAttributes) != ERROR_SUCCESS) | 
 | 			{ | 
 | 				fprintf(stderr, "Logging in %s (0x%08x, %u) failed\n", | 
 | 					 wine_dbgstr_w(LogFileName), LogMode, LogAttributes); | 
 | 				ExitProcess(1); | 
 | 			} | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "p")) | 
 | 		{ | 
 | 			FunctionPatch = TRUE; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			PatchFileName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_option_prefix(argvW[i], "q")) | 
 | 		{ | 
 | 			if(lstrlenW(argvW[i]) == 2 || msi_strequal(argvW[i]+2, "n") || | 
 | 			   msi_strequal(argvW[i] + 2, "uiet")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_NONE; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "b")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_BASIC; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "r")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_REDUCED; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "f")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_FULL|INSTALLUILEVEL_ENDDIALOG; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "n+")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_NONE|INSTALLUILEVEL_ENDDIALOG; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "b+")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_BASIC|INSTALLUILEVEL_ENDDIALOG; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "b-")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_BASIC|INSTALLUILEVEL_PROGRESSONLY; | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "b+!")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_BASIC|INSTALLUILEVEL_ENDDIALOG; | 
 | 				WINE_FIXME("Unknown modifier: !\n"); | 
 | 			} | 
 | 			else if(msi_strequal(argvW[i]+2, "b!")) | 
 | 			{ | 
 | 				InstallUILevel = INSTALLUILEVEL_BASIC; | 
 | 				WINE_FIXME("Unknown modifier: !\n"); | 
 | 			} | 
 | 			else | 
 | 			{ | 
 | 				fprintf(stderr, "Unknown option \"%s\" for UI level\n", | 
 | 					 wine_dbgstr_w(argvW[i]+2)); | 
 | 			} | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "y")) | 
 | 		{ | 
 | 			FunctionDllRegisterServer = TRUE; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			DllName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "z")) | 
 | 		{ | 
 | 			FunctionDllUnregisterServer = TRUE; | 
 | 			i++; | 
 | 			if(i >= argc) | 
 | 				ShowUsage(1); | 
 | 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i])); | 
 | 			DllName = argvW[i]; | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "help") || msi_option_equal(argvW[i], "?")) | 
 | 		{ | 
 | 			ShowUsage(0); | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "m")) | 
 | 		{ | 
 | 			FunctionUnknown = TRUE; | 
 | 			WINE_FIXME("Unknown parameter /m\n"); | 
 | 		} | 
 | 		else if(msi_option_equal(argvW[i], "D")) | 
 | 		{ | 
 | 			FunctionUnknown = TRUE; | 
 | 			WINE_FIXME("Unknown parameter /D\n"); | 
 | 		} | 
 | 		else if (msi_option_equal(argvW[i], "V")) | 
 | 		{ | 
 | 		    FunctionServer = TRUE; | 
 | 		} | 
 | 		else | 
 | 			StringListAppend(&property_list, argvW[i]); | 
 | 	} | 
 |  | 
 | 	/* start the GUI */ | 
 | 	MsiSetInternalUI(InstallUILevel, NULL); | 
 |  | 
 | 	Properties = build_properties( property_list ); | 
 |  | 
 | 	if(FunctionInstallAdmin && FunctionPatch) | 
 | 		FunctionInstall = FALSE; | 
 |  | 
 | 	ReturnCode = 1; | 
 | 	if(FunctionInstall) | 
 | 	{ | 
 | 		if(IsProductCode(PackageName)) | 
 | 			ReturnCode = MsiConfigureProductExW(PackageName, 0, INSTALLSTATE_DEFAULT, Properties); | 
 | 		else | 
 | 			ReturnCode = MsiInstallProductW(PackageName, Properties); | 
 | 	} | 
 | 	else if(FunctionRepair) | 
 | 	{ | 
 | 		if(IsProductCode(PackageName)) | 
 | 			WINE_FIXME("Product code treatment not implemented yet\n"); | 
 | 		else | 
 | 			ReturnCode = MsiReinstallProductW(PackageName, RepairMode); | 
 | 	} | 
 | 	else if(FunctionAdvertise) | 
 | 	{ | 
 | 		LPWSTR Transforms = build_transforms( property_list ); | 
 | 		ReturnCode = MsiAdvertiseProductW(PackageName, (LPWSTR) AdvertiseMode, Transforms, Language); | 
 | 	} | 
 | 	else if(FunctionPatch) | 
 | 	{ | 
 | 		ReturnCode = MsiApplyPatchW(PatchFileName, PackageName, InstallType, Properties); | 
 | 	} | 
 | 	else if(FunctionDllRegisterServer) | 
 | 	{ | 
 | 		ReturnCode = DoDllRegisterServer(DllName); | 
 | 	} | 
 | 	else if(FunctionDllUnregisterServer) | 
 | 	{ | 
 | 		ReturnCode = DoDllUnregisterServer(DllName); | 
 | 	} | 
 | 	else if (FunctionRegServer) | 
 | 	{ | 
 | 		ReturnCode = DoRegServer(); | 
 | 	} | 
 | 	else if (FunctionUnregServer) | 
 | 	{ | 
 | 		WINE_FIXME( "/unregserver not implemented yet, ignoring\n" ); | 
 | 	} | 
 | 	else if (FunctionServer) | 
 | 	{ | 
 | 	    ReturnCode = DoService(); | 
 | 	} | 
 | 	else if (FunctionUnknown) | 
 | 	{ | 
 | 		WINE_FIXME( "Unknown function, ignoring\n" ); | 
 | 	} | 
 | 	else | 
 | 		ShowUsage(1); | 
 |  | 
 | 	return ReturnCode; | 
 | } |