xref: /reactos/base/system/msiexec/msiexec.c (revision f4be6dc3)
1 /*
2  * msiexec.exe implementation
3  *
4  * Copyright 2004 Vincent Béron
5  * Copyright 2005 Mike McCormack
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #define WIN32_LEAN_AND_MEAN
23 
24 #include <windows.h>
25 #include <commctrl.h>
26 #include <msi.h>
27 #include <winsvc.h>
28 #include <objbase.h>
29 
30 #include "wine/debug.h"
31 #include "msiexec_internal.h"
32 
33 #include "initguid.h"
34 DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
35 
36 WINE_DEFAULT_DEBUG_CHANNEL(msiexec);
37 
38 typedef HRESULT (WINAPI *DLLREGISTERSERVER)(void);
39 typedef HRESULT (WINAPI *DLLUNREGISTERSERVER)(void);
40 
41 DWORD DoService(void);
42 static BOOL silent;
43 
44 struct string_list
45 {
46 	struct string_list *next;
47 	WCHAR str[1];
48 };
49 
report_error(const char * msg,...)50 void report_error(const char* msg, ...)
51 {
52     char buffer[2048];
53     va_list va_args;
54 
55     va_start(va_args, msg);
56     vsnprintf(buffer, sizeof(buffer), msg, va_args);
57     va_end(va_args);
58 
59     if (silent)
60         MESSAGE("%s", buffer);
61     else
62         MsiMessageBoxA(NULL, buffer, "MsiExec", 0, GetUserDefaultLangID(), 0);
63 }
64 
ShowUsage(int ExitCode)65 static void ShowUsage(int ExitCode)
66 {
67     WCHAR msiexec_version[40];
68     WCHAR filename[MAX_PATH];
69     LPWSTR msi_res;
70     LPWSTR msiexec_help;
71     HMODULE hmsi = GetModuleHandleA("msi.dll");
72     DWORD len;
73     DWORD res;
74 
75     /* MsiGetFileVersion need the full path */
76     *filename = 0;
77     res = GetModuleFileNameW(hmsi, filename, ARRAY_SIZE(filename));
78     if (!res)
79         WINE_ERR("GetModuleFileName failed: %ld\n", GetLastError());
80 
81     len = ARRAY_SIZE(msiexec_version);
82     *msiexec_version = 0;
83     res = MsiGetFileVersionW(filename, msiexec_version, &len, NULL, NULL);
84     if (res)
85         WINE_ERR("MsiGetFileVersion failed with %ld\n", res);
86 
87     /* Return the length of the resource.
88        No typo: The LPWSTR parameter must be a LPWSTR * for this mode */
89     len = LoadStringW(hmsi, 10, (LPWSTR) &msi_res, 0);
90 
91     msi_res = malloc((len + 1) * sizeof(WCHAR));
92     msiexec_help = malloc((len + 1) * sizeof(WCHAR) + sizeof(msiexec_version));
93     if (msi_res && msiexec_help) {
94         *msi_res = 0;
95         LoadStringW(hmsi, 10, msi_res, len + 1);
96 
97         swprintf(msiexec_help, len + 1 + ARRAY_SIZE(msiexec_version), msi_res, msiexec_version);
98         MsiMessageBoxW(0, msiexec_help, NULL, 0, GetUserDefaultLangID(), 0);
99     }
100     free(msi_res);
101     free(msiexec_help);
102     ExitProcess(ExitCode);
103 }
104 
IsProductCode(LPWSTR str)105 static BOOL IsProductCode(LPWSTR str)
106 {
107 	GUID ProductCode;
108 
109 	if(lstrlenW(str) != 38)
110 		return FALSE;
111 	return ( (CLSIDFromString(str, &ProductCode) == NOERROR) );
112 
113 }
114 
StringListAppend(struct string_list ** list,LPCWSTR str)115 static VOID StringListAppend(struct string_list **list, LPCWSTR str)
116 {
117 	struct string_list *entry;
118 
119 	entry = malloc(FIELD_OFFSET(struct string_list, str[wcslen(str) + 1]));
120 	if(!entry)
121 	{
122 		WINE_ERR("Out of memory!\n");
123 		ExitProcess(1);
124 	}
125 	lstrcpyW(entry->str, str);
126 	entry->next = NULL;
127 
128 	/*
129 	 * Ignoring o(n^2) time complexity to add n strings for simplicity,
130 	 *  add the string to the end of the list to preserve the order.
131 	 */
132 	while( *list )
133 		list = &(*list)->next;
134 	*list = entry;
135 }
136 
build_properties(struct string_list * property_list)137 static LPWSTR build_properties(struct string_list *property_list)
138 {
139 	struct string_list *list;
140 	LPWSTR ret, p, value;
141 	DWORD len;
142 	BOOL needs_quote;
143 
144 	if(!property_list)
145 		return NULL;
146 
147 	/* count the space we need */
148 	len = 1;
149 	for(list = property_list; list; list = list->next)
150 		len += lstrlenW(list->str) + 3;
151 
152 	ret = malloc(len * sizeof(WCHAR));
153 
154 	/* add a space before each string, and quote the value */
155 	p = ret;
156 	for(list = property_list; list; list = list->next)
157 	{
158 		value = wcschr(list->str,'=');
159 		if(!value)
160 			continue;
161 		len = value - list->str;
162 		*p++ = ' ';
163 		memcpy(p, list->str, len * sizeof(WCHAR));
164 		p += len;
165 		*p++ = '=';
166 
167 		/* check if the value contains spaces and maybe quote it */
168 		value++;
169 		needs_quote = wcschr(value,' ') ? 1 : 0;
170 		if(needs_quote)
171 			*p++ = '"';
172 		len = lstrlenW(value);
173 		memcpy(p, value, len * sizeof(WCHAR));
174 		p += len;
175 		if(needs_quote)
176 			*p++ = '"';
177 	}
178 	*p = 0;
179 
180 	WINE_TRACE("properties -> %s\n", wine_dbgstr_w(ret) );
181 
182 	return ret;
183 }
184 
build_transforms(struct string_list * transform_list)185 static LPWSTR build_transforms(struct string_list *transform_list)
186 {
187 	struct string_list *list;
188 	LPWSTR ret, p;
189 	DWORD len;
190 
191 	/* count the space we need */
192 	len = 1;
193 	for(list = transform_list; list; list = list->next)
194 		len += lstrlenW(list->str) + 1;
195 
196 	ret = malloc(len * sizeof(WCHAR));
197 
198 	/* add all the transforms with a semicolon between each one */
199 	p = ret;
200 	for(list = transform_list; list; list = list->next)
201 	{
202 		len = lstrlenW(list->str);
203 		lstrcpynW(p, list->str, len );
204 		p += len;
205 		if(list->next)
206 			*p++ = ';';
207 	}
208 	*p = 0;
209 
210 	return ret;
211 }
212 
msi_atou(LPCWSTR str)213 static DWORD msi_atou(LPCWSTR str)
214 {
215 	DWORD ret = 0;
216 	while(*str >= '0' && *str <= '9')
217 	{
218 		ret *= 10;
219 		ret += (*str - '0');
220 		str++;
221 	}
222 	return ret;
223 }
224 
225 /* str1 is the same as str2, ignoring case */
msi_strequal(LPCWSTR str1,LPCSTR str2)226 static BOOL msi_strequal(LPCWSTR str1, LPCSTR str2)
227 {
228 	DWORD len, ret;
229 	LPWSTR strW;
230 
231 	len = MultiByteToWideChar( CP_ACP, 0, str2, -1, NULL, 0);
232 	if( !len )
233 		return FALSE;
234 	if( lstrlenW(str1) != (len-1) )
235 		return FALSE;
236 	strW = malloc(sizeof(WCHAR) * len);
237 	MultiByteToWideChar( CP_ACP, 0, str2, -1, strW, len);
238 	ret = CompareStringW(GetThreadLocale(), NORM_IGNORECASE, str1, len, strW, len);
239 	free(strW);
240 	return (ret == CSTR_EQUAL);
241 }
242 
243 /* prefix is hyphen or dash, and str1 is the same as str2, ignoring case */
msi_option_equal(LPCWSTR str1,LPCSTR str2)244 static BOOL msi_option_equal(LPCWSTR str1, LPCSTR str2)
245 {
246     if (str1[0] != '/' && str1[0] != '-')
247         return FALSE;
248 
249     /* skip over the hyphen or slash */
250     return msi_strequal(str1 + 1, str2);
251 }
252 
253 /* str2 is at the beginning of str1, ignoring case */
msi_strprefix(LPCWSTR str1,LPCSTR str2)254 static BOOL msi_strprefix(LPCWSTR str1, LPCSTR str2)
255 {
256 	DWORD len, ret;
257 	LPWSTR strW;
258 
259 	len = MultiByteToWideChar( CP_ACP, 0, str2, -1, NULL, 0);
260 	if( !len )
261 		return FALSE;
262 	if( lstrlenW(str1) < (len-1) )
263 		return FALSE;
264 	strW = malloc(sizeof(WCHAR) * len);
265 	MultiByteToWideChar( CP_ACP, 0, str2, -1, strW, len);
266 	ret = CompareStringW(GetThreadLocale(), NORM_IGNORECASE, str1, len-1, strW, len-1);
267 	free(strW);
268 	return (ret == CSTR_EQUAL);
269 }
270 
271 /* prefix is hyphen or dash, and str2 is at the beginning of str1, ignoring case */
msi_option_prefix(LPCWSTR str1,LPCSTR str2)272 static BOOL msi_option_prefix(LPCWSTR str1, LPCSTR str2)
273 {
274     if (str1[0] != '/' && str1[0] != '-')
275         return FALSE;
276 
277     /* skip over the hyphen or slash */
278     return msi_strprefix(str1 + 1, str2);
279 }
280 
LoadProc(LPCWSTR DllName,LPCSTR ProcName,HMODULE * DllHandle)281 static VOID *LoadProc(LPCWSTR DllName, LPCSTR ProcName, HMODULE* DllHandle)
282 {
283 	VOID* (*proc)(void);
284 
285 	*DllHandle = LoadLibraryExW(DllName, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
286 	if(!*DllHandle)
287 	{
288 		report_error("Unable to load dll %s\n", wine_dbgstr_w(DllName));
289 		ExitProcess(1);
290 	}
291 	proc = (VOID *) GetProcAddress(*DllHandle, ProcName);
292 	if(!proc)
293 	{
294 		report_error("Dll %s does not implement function %s\n",
295 			     wine_dbgstr_w(DllName), ProcName);
296 		FreeLibrary(*DllHandle);
297 		ExitProcess(1);
298 	}
299 
300 	return proc;
301 }
302 
DoDllRegisterServer(LPCWSTR DllName)303 static DWORD DoDllRegisterServer(LPCWSTR DllName)
304 {
305 	HRESULT hr;
306 	DLLREGISTERSERVER pfDllRegisterServer = NULL;
307 	HMODULE DllHandle = NULL;
308 
309 	pfDllRegisterServer = LoadProc(DllName, "DllRegisterServer", &DllHandle);
310 
311 	hr = pfDllRegisterServer();
312 	if(FAILED(hr))
313 	{
314 		report_error("Failed to register dll %s\n", wine_dbgstr_w(DllName));
315 		return 1;
316 	}
317 	MESSAGE("Successfully registered dll %s\n", wine_dbgstr_w(DllName));
318 	if(DllHandle)
319 		FreeLibrary(DllHandle);
320 	return 0;
321 }
322 
DoDllUnregisterServer(LPCWSTR DllName)323 static DWORD DoDllUnregisterServer(LPCWSTR DllName)
324 {
325 	HRESULT hr;
326 	DLLUNREGISTERSERVER pfDllUnregisterServer = NULL;
327 	HMODULE DllHandle = NULL;
328 
329 	pfDllUnregisterServer = LoadProc(DllName, "DllUnregisterServer", &DllHandle);
330 
331 	hr = pfDllUnregisterServer();
332 	if(FAILED(hr))
333 	{
334 		report_error("Failed to unregister dll %s\n", wine_dbgstr_w(DllName));
335 		return 1;
336 	}
337 	MESSAGE("Successfully unregistered dll %s\n", wine_dbgstr_w(DllName));
338 	if(DllHandle)
339 		FreeLibrary(DllHandle);
340 	return 0;
341 }
342 
DoRegServer(void)343 static DWORD DoRegServer(void)
344 {
345     SC_HANDLE scm, service;
346     WCHAR path[MAX_PATH+12];
347     DWORD len, ret = 0;
348 
349     if (!(scm = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASEW, SC_MANAGER_CREATE_SERVICE)))
350     {
351         report_error("Failed to open the service control manager.\n");
352         return 1;
353     }
354     len = GetSystemDirectoryW(path, MAX_PATH);
355     lstrcpyW(path + len, L"\\msiexec /V");
356     if ((service = CreateServiceW(scm, L"MSIServer", L"MSIServer", GENERIC_ALL,
357                                   SERVICE_WIN32_SHARE_PROCESS, SERVICE_DEMAND_START,
358                                   SERVICE_ERROR_NORMAL, path, NULL, NULL, NULL, NULL, NULL)))
359     {
360         CloseServiceHandle(service);
361     }
362     else if (GetLastError() != ERROR_SERVICE_EXISTS)
363     {
364         report_error("Failed to create MSI service\n");
365         ret = 1;
366     }
367     CloseServiceHandle(scm);
368     return ret;
369 }
370 
DoUnregServer(void)371 static DWORD DoUnregServer(void)
372 {
373     SC_HANDLE scm, service;
374     DWORD ret = 0;
375 
376     if (!(scm = OpenSCManagerW(NULL, SERVICES_ACTIVE_DATABASEW, SC_MANAGER_CONNECT)))
377     {
378         report_error("Failed to open service control manager\n");
379         return 1;
380     }
381     if ((service = OpenServiceW(scm, L"MSIServer", DELETE)))
382     {
383         if (!DeleteService(service))
384         {
385             report_error("Failed to delete MSI service\n");
386             ret = 1;
387         }
388         CloseServiceHandle(service);
389     }
390     else if (GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST)
391     {
392         report_error("Failed to open MSI service\n");
393         ret = 1;
394     }
395     CloseServiceHandle(scm);
396     return ret;
397 }
398 
399 extern UINT CDECL __wine_msi_call_dll_function(DWORD client_pid, const GUID *guid);
400 
401 static DWORD client_pid;
402 
custom_action_thread(void * arg)403 static DWORD CALLBACK custom_action_thread(void *arg)
404 {
405     GUID guid = *(GUID *)arg;
406     free(arg);
407     return __wine_msi_call_dll_function(client_pid, &guid);
408 }
409 
custom_action_server(const WCHAR * arg)410 static int custom_action_server(const WCHAR *arg)
411 {
412     GUID guid, *thread_guid;
413     DWORD64 thread64;
414     WCHAR buffer[24];
415     HANDLE thread;
416     HANDLE pipe;
417     DWORD size;
418 
419     TRACE("%s\n", debugstr_w(arg));
420 
421     if (!(client_pid = wcstol(arg, NULL, 10)))
422     {
423         ERR("Invalid parameter %s\n", debugstr_w(arg));
424         return 1;
425     }
426 
427     swprintf(buffer, ARRAY_SIZE(buffer), L"\\\\.\\pipe\\msica_%x_%d", client_pid, (int)(sizeof(void *) * 8));
428     pipe = CreateFileW(buffer, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
429     if (pipe == INVALID_HANDLE_VALUE)
430     {
431         ERR("Failed to create custom action server pipe: %lu\n", GetLastError());
432         return GetLastError();
433     }
434 
435     /* We need this to unmarshal streams, and some apps expect it to be present. */
436     CoInitializeEx(NULL, COINIT_MULTITHREADED);
437 
438     while (ReadFile(pipe, &guid, sizeof(guid), &size, NULL) && size == sizeof(guid))
439     {
440         if (IsEqualGUID(&guid, &GUID_NULL))
441         {
442             /* package closed; time to shut down */
443             CoUninitialize();
444             return 0;
445         }
446 
447         thread_guid = malloc(sizeof(GUID));
448         memcpy(thread_guid, &guid, sizeof(GUID));
449         thread = CreateThread(NULL, 0, custom_action_thread, thread_guid, 0, NULL);
450 
451         /* give the thread handle to the client to wait on, since we might have
452          * to run a nested action and can't block during this one */
453         thread64 = (DWORD_PTR)thread;
454         if (!WriteFile(pipe, &thread64, sizeof(thread64), &size, NULL) || size != sizeof(thread64))
455         {
456             ERR("Failed to write to custom action server pipe: %lu\n", GetLastError());
457             CoUninitialize();
458             return GetLastError();
459         }
460     }
461     ERR("Failed to read from custom action server pipe: %lu\n", GetLastError());
462     CoUninitialize();
463     return GetLastError();
464 }
465 
466 /*
467  * state machine to break up the command line properly
468  */
469 
470 enum chomp_state
471 {
472     CS_WHITESPACE,
473     CS_TOKEN,
474     CS_QUOTE
475 };
476 
chomp(const WCHAR * in,WCHAR * out)477 static int chomp( const WCHAR *in, WCHAR *out )
478 {
479     enum chomp_state state = CS_TOKEN;
480     const WCHAR *p;
481     int count = 1;
482     BOOL ignore;
483 
484     for (p = in; *p; p++)
485     {
486         ignore = TRUE;
487         switch (state)
488         {
489         case CS_WHITESPACE:
490             switch (*p)
491             {
492             case ' ':
493                 break;
494             case '"':
495                 state = CS_QUOTE;
496                 count++;
497                 break;
498             default:
499                 count++;
500                 ignore = FALSE;
501                 state = CS_TOKEN;
502             }
503             break;
504 
505         case CS_TOKEN:
506             switch (*p)
507             {
508             case '"':
509                 state = CS_QUOTE;
510                 break;
511             case ' ':
512                 state = CS_WHITESPACE;
513                 if (out) *out++ = 0;
514                 break;
515             default:
516                 if (p > in && p[-1] == '"')
517                 {
518                     if (out) *out++ = 0;
519                     count++;
520                 }
521                 ignore = FALSE;
522             }
523             break;
524 
525         case CS_QUOTE:
526             switch (*p)
527             {
528             case '"':
529                 state = CS_TOKEN;
530                 break;
531             default:
532                 ignore = FALSE;
533             }
534             break;
535         }
536         if (!ignore && out) *out++ = *p;
537     }
538     if (out) *out = 0;
539     return count;
540 }
541 
process_args(WCHAR * cmdline,int * pargc,WCHAR *** pargv)542 static void process_args( WCHAR *cmdline, int *pargc, WCHAR ***pargv )
543 {
544     WCHAR **argv, *p;
545     int i, count;
546 
547     *pargc = 0;
548     *pargv = NULL;
549 
550     count = chomp( cmdline, NULL );
551     if (!(p = malloc( (wcslen(cmdline) + count + 1) * sizeof(WCHAR) )))
552         return;
553 
554     count = chomp( cmdline, p );
555     if (!(argv = malloc( (count + 1) * sizeof(WCHAR *) )))
556     {
557         free( p );
558         return;
559     }
560     for (i = 0; i < count; i++)
561     {
562         argv[i] = p;
563         p += lstrlenW( p ) + 1;
564     }
565     argv[i] = NULL;
566 
567     *pargc = count;
568     *pargv = argv;
569 }
570 
process_args_from_reg(const WCHAR * ident,int * pargc,WCHAR *** pargv)571 static BOOL process_args_from_reg( const WCHAR *ident, int *pargc, WCHAR ***pargv )
572 {
573 	LONG r;
574 	HKEY hkey;
575 	DWORD sz = 0, type = 0;
576 	WCHAR *buf;
577 	BOOL ret = FALSE;
578 
579 	r = RegOpenKeyW(HKEY_LOCAL_MACHINE,
580 			L"Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\RunOnceEntries", &hkey);
581 	if(r != ERROR_SUCCESS)
582 		return FALSE;
583 	r = RegQueryValueExW(hkey, ident, 0, &type, 0, &sz);
584 	if(r == ERROR_SUCCESS && type == REG_SZ)
585 	{
586 		int len = lstrlenW( *pargv[0] );
587 		if (!(buf = malloc( (len + 1) * sizeof(WCHAR) )))
588 		{
589 			RegCloseKey( hkey );
590 			return FALSE;
591 		}
592 		memcpy( buf, *pargv[0], len * sizeof(WCHAR) );
593 		buf[len++] = ' ';
594 		r = RegQueryValueExW(hkey, ident, 0, &type, (LPBYTE)(buf + len), &sz);
595 		if( r == ERROR_SUCCESS )
596 		{
597 			process_args(buf, pargc, pargv);
598 			ret = TRUE;
599 		}
600 		free(buf);
601 	}
602 	RegCloseKey(hkey);
603 	return ret;
604 }
605 
get_path_with_extension(const WCHAR * package_name)606 static WCHAR *get_path_with_extension(const WCHAR *package_name)
607 {
608     static const WCHAR ext[] = L".msi";
609     unsigned int p;
610     WCHAR *path;
611 
612     if (!(path = malloc(wcslen(package_name) * sizeof(WCHAR) + sizeof(ext))))
613     {
614         WINE_ERR("No memory.\n");
615         return NULL;
616     }
617 
618     lstrcpyW(path, package_name);
619     p = lstrlenW(path);
620     while (p && path[p] != '.' && path[p] != L'\\' && path[p] != '/')
621         --p;
622     if (path[p] == '.')
623     {
624         free(path);
625         return NULL;
626     }
627     lstrcatW(path, ext);
628     return path;
629 }
630 
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)631 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
632 {
633 	int i;
634 	BOOL FunctionInstall = FALSE;
635 	BOOL FunctionInstallAdmin = FALSE;
636 	BOOL FunctionRepair = FALSE;
637 	BOOL FunctionAdvertise = FALSE;
638 	BOOL FunctionPatch = FALSE;
639 	BOOL FunctionDllRegisterServer = FALSE;
640 	BOOL FunctionDllUnregisterServer = FALSE;
641 	BOOL FunctionRegServer = FALSE;
642 	BOOL FunctionUnregServer = FALSE;
643 	BOOL FunctionServer = FALSE;
644 	BOOL FunctionUnknown = FALSE;
645 
646 	LPWSTR PackageName = NULL;
647 	LPWSTR Properties = NULL;
648 	struct string_list *property_list = NULL;
649 
650 	DWORD RepairMode = 0;
651 
652 	DWORD_PTR AdvertiseMode = 0;
653 	struct string_list *transform_list = NULL;
654 	LANGID Language = 0;
655 
656 	DWORD LogMode = 0;
657 	LPWSTR LogFileName = NULL;
658 	DWORD LogAttributes = 0;
659 
660 	LPWSTR PatchFileName = NULL;
661 	INSTALLTYPE InstallType = INSTALLTYPE_DEFAULT;
662 
663 	INSTALLUILEVEL InstallUILevel = INSTALLUILEVEL_FULL;
664 
665 	LPWSTR DllName = NULL;
666 	DWORD ReturnCode;
667 	int argc;
668 	LPWSTR *argvW = NULL;
669 	WCHAR *path;
670 
671         InitCommonControls();
672 
673 	/* parse the command line */
674 	process_args( GetCommandLineW(), &argc, &argvW );
675 
676 	/*
677 	 * If the args begin with /@ IDENT then we need to load the real
678 	 * command line out of the RunOnceEntries key in the registry.
679 	 *  We do that before starting to process the real commandline,
680 	 * then overwrite the commandline again.
681 	 */
682 	if(argc>1 && msi_option_equal(argvW[1], "@"))
683 	{
684 		if(!process_args_from_reg( argvW[2], &argc, &argvW ))
685 			return 1;
686 	}
687 
688 	if (argc == 3 && msi_option_equal(argvW[1], "Embedding"))
689         return custom_action_server(argvW[2]);
690 
691 	for(i = 1; i < argc; i++)
692 	{
693 		WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
694 
695 		if (msi_option_equal(argvW[i], "regserver"))
696 		{
697 			FunctionRegServer = TRUE;
698 		}
699 		else if (msi_option_equal(argvW[i], "unregserver") || msi_option_equal(argvW[i], "unregister")
700 			||  msi_option_equal(argvW[i], "unreg"))
701 		{
702 			FunctionUnregServer = TRUE;
703 		}
704 		else if(msi_option_prefix(argvW[i], "i") || msi_option_prefix(argvW[i], "package"))
705 		{
706 			LPWSTR argvWi = argvW[i];
707 			int argLen = (msi_option_prefix(argvW[i], "i") ? 2 : 8);
708 			FunctionInstall = TRUE;
709 			if(lstrlenW(argvW[i]) > argLen)
710 				argvWi += argLen;
711 			else
712 			{
713 				i++;
714 				if(i >= argc)
715 					ShowUsage(1);
716 				WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
717 				argvWi = argvW[i];
718 			}
719 			PackageName = argvWi;
720 		}
721 		else if(msi_option_equal(argvW[i], "a"))
722 		{
723 			FunctionInstall = TRUE;
724 			FunctionInstallAdmin = TRUE;
725 			InstallType = INSTALLTYPE_NETWORK_IMAGE;
726 			i++;
727 			if(i >= argc)
728 				ShowUsage(1);
729 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
730 			PackageName = argvW[i];
731 			StringListAppend(&property_list, L"ACTION=ADMIN");
732 			WINE_FIXME("Administrative installs are not currently supported\n");
733 		}
734 		else if(msi_option_prefix(argvW[i], "f"))
735 		{
736 			int j;
737 			int len = lstrlenW(argvW[i]);
738 			FunctionRepair = TRUE;
739 			for(j = 2; j < len; j++)
740 			{
741 				switch(argvW[i][j])
742 				{
743 					case 'P':
744 					case 'p':
745 						RepairMode |= REINSTALLMODE_FILEMISSING;
746 						break;
747 					case 'O':
748 					case 'o':
749 						RepairMode |= REINSTALLMODE_FILEOLDERVERSION;
750 						break;
751 					case 'E':
752 					case 'e':
753 						RepairMode |= REINSTALLMODE_FILEEQUALVERSION;
754 						break;
755 					case 'D':
756 					case 'd':
757 						RepairMode |= REINSTALLMODE_FILEEXACT;
758 						break;
759 					case 'C':
760 					case 'c':
761 						RepairMode |= REINSTALLMODE_FILEVERIFY;
762 						break;
763 					case 'A':
764 					case 'a':
765 						RepairMode |= REINSTALLMODE_FILEREPLACE;
766 						break;
767 					case 'U':
768 					case 'u':
769 						RepairMode |= REINSTALLMODE_USERDATA;
770 						break;
771 					case 'M':
772 					case 'm':
773 						RepairMode |= REINSTALLMODE_MACHINEDATA;
774 						break;
775 					case 'S':
776 					case 's':
777 						RepairMode |= REINSTALLMODE_SHORTCUT;
778 						break;
779 					case 'V':
780 					case 'v':
781 						RepairMode |= REINSTALLMODE_PACKAGE;
782 						break;
783 					default:
784 						report_error("Unknown option \"%c\" in Repair mode\n", argvW[i][j]);
785 						break;
786 				}
787 			}
788 			if(len == 2)
789 			{
790 				RepairMode = REINSTALLMODE_FILEMISSING |
791 					REINSTALLMODE_FILEEQUALVERSION |
792 					REINSTALLMODE_FILEVERIFY |
793 					REINSTALLMODE_MACHINEDATA |
794 					REINSTALLMODE_SHORTCUT;
795 			}
796 			i++;
797 			if(i >= argc)
798 				ShowUsage(1);
799 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
800 			PackageName = argvW[i];
801 		}
802 		else if(msi_option_prefix(argvW[i], "x") || msi_option_equal(argvW[i], "uninstall"))
803 		{
804 			FunctionInstall = TRUE;
805 			if(msi_option_prefix(argvW[i], "x")) PackageName = argvW[i]+2;
806 			if(!PackageName || !PackageName[0])
807 			{
808 				i++;
809 				if (i >= argc)
810 					ShowUsage(1);
811 				PackageName = argvW[i];
812 			}
813 			WINE_TRACE("PackageName = %s\n", wine_dbgstr_w(PackageName));
814 			StringListAppend(&property_list, L"REMOVE=ALL");
815 		}
816 		else if(msi_option_prefix(argvW[i], "j"))
817 		{
818 			int j;
819 			int len = lstrlenW(argvW[i]);
820 			FunctionAdvertise = TRUE;
821 			for(j = 2; j < len; j++)
822 			{
823 				switch(argvW[i][j])
824 				{
825 					case 'U':
826 					case 'u':
827 						AdvertiseMode = ADVERTISEFLAGS_USERASSIGN;
828 						break;
829 					case 'M':
830 					case 'm':
831 						AdvertiseMode = ADVERTISEFLAGS_MACHINEASSIGN;
832 						break;
833 					default:
834 						report_error("Unknown option \"%c\" in Advertise mode\n", argvW[i][j]);
835 						break;
836 				}
837 			}
838 			i++;
839 			if(i >= argc)
840 				ShowUsage(1);
841 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
842 			PackageName = argvW[i];
843 		}
844 		else if(msi_strequal(argvW[i], "u"))
845 		{
846 			FunctionAdvertise = TRUE;
847 			AdvertiseMode = ADVERTISEFLAGS_USERASSIGN;
848 			i++;
849 			if(i >= argc)
850 				ShowUsage(1);
851 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
852 			PackageName = argvW[i];
853 		}
854 		else if(msi_strequal(argvW[i], "m"))
855 		{
856 			FunctionAdvertise = TRUE;
857 			AdvertiseMode = ADVERTISEFLAGS_MACHINEASSIGN;
858 			i++;
859 			if(i >= argc)
860 				ShowUsage(1);
861 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
862 			PackageName = argvW[i];
863 		}
864 		else if(msi_option_equal(argvW[i], "t"))
865 		{
866 			i++;
867 			if(i >= argc)
868 				ShowUsage(1);
869 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
870 			StringListAppend(&transform_list, argvW[i]);
871 		}
872 		else if(msi_option_equal(argvW[i], "g"))
873 		{
874 			i++;
875 			if(i >= argc)
876 				ShowUsage(1);
877 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
878 			Language = msi_atou(argvW[i]);
879 		}
880 		else if(msi_option_prefix(argvW[i], "l"))
881 		{
882 			int j;
883 			int len = lstrlenW(argvW[i]);
884 			for(j = 2; j < len; j++)
885 			{
886 				switch(argvW[i][j])
887 				{
888 					case 'I':
889 					case 'i':
890 						LogMode |= INSTALLLOGMODE_INFO;
891 						break;
892 					case 'W':
893 					case 'w':
894 						LogMode |= INSTALLLOGMODE_WARNING;
895 						break;
896 					case 'E':
897 					case 'e':
898 						LogMode |= INSTALLLOGMODE_ERROR;
899 						break;
900 					case 'A':
901 					case 'a':
902 						LogMode |= INSTALLLOGMODE_ACTIONSTART;
903 						break;
904 					case 'R':
905 					case 'r':
906 						LogMode |= INSTALLLOGMODE_ACTIONDATA;
907 						break;
908 					case 'U':
909 					case 'u':
910 						LogMode |= INSTALLLOGMODE_USER;
911 						break;
912 					case 'C':
913 					case 'c':
914 						LogMode |= INSTALLLOGMODE_COMMONDATA;
915 						break;
916 					case 'M':
917 					case 'm':
918 						LogMode |= INSTALLLOGMODE_FATALEXIT;
919 						break;
920 					case 'O':
921 					case 'o':
922 						LogMode |= INSTALLLOGMODE_OUTOFDISKSPACE;
923 						break;
924 					case 'P':
925 					case 'p':
926 						LogMode |= INSTALLLOGMODE_PROPERTYDUMP;
927 						break;
928 					case 'V':
929 					case 'v':
930 						LogMode |= INSTALLLOGMODE_VERBOSE;
931 						break;
932 					case '*':
933 						LogMode = INSTALLLOGMODE_FATALEXIT |
934 							INSTALLLOGMODE_ERROR |
935 							INSTALLLOGMODE_WARNING |
936 							INSTALLLOGMODE_USER |
937 							INSTALLLOGMODE_INFO |
938 							INSTALLLOGMODE_RESOLVESOURCE |
939 							INSTALLLOGMODE_OUTOFDISKSPACE |
940 							INSTALLLOGMODE_ACTIONSTART |
941 							INSTALLLOGMODE_ACTIONDATA |
942 							INSTALLLOGMODE_COMMONDATA |
943 							INSTALLLOGMODE_PROPERTYDUMP |
944 							INSTALLLOGMODE_PROGRESS |
945 							INSTALLLOGMODE_INITIALIZE |
946 							INSTALLLOGMODE_TERMINATE |
947 							INSTALLLOGMODE_SHOWDIALOG;
948 						break;
949 					case '+':
950 						LogAttributes |= INSTALLLOGATTRIBUTES_APPEND;
951 						break;
952 					case '!':
953 						LogAttributes |= INSTALLLOGATTRIBUTES_FLUSHEACHLINE;
954 						break;
955 					default:
956 						break;
957 				}
958 			}
959 			i++;
960 			if(i >= argc)
961 				ShowUsage(1);
962 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
963 			LogFileName = argvW[i];
964 			if(MsiEnableLogW(LogMode, LogFileName, LogAttributes) != ERROR_SUCCESS)
965 			{
966 				report_error("Logging in %s (0x%08lx, %lu) failed\n",
967 					     wine_dbgstr_w(LogFileName), LogMode, LogAttributes);
968 				ExitProcess(1);
969 			}
970 		}
971 		else if(msi_option_equal(argvW[i], "p") || msi_option_equal(argvW[i], "update"))
972 		{
973 			FunctionPatch = TRUE;
974 			i++;
975 			if(i >= argc)
976 				ShowUsage(1);
977 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
978 			PatchFileName = argvW[i];
979 		}
980 		else if(msi_option_prefix(argvW[i], "q"))
981 		{
982 			if(lstrlenW(argvW[i]) == 2 || msi_strequal(argvW[i]+2, "n") ||
983 			   msi_strequal(argvW[i] + 2, "uiet"))
984 			{
985 				silent = TRUE;
986 				InstallUILevel = INSTALLUILEVEL_NONE;
987 			}
988 			else if(msi_strequal(argvW[i]+2, "r"))
989 			{
990 				InstallUILevel = INSTALLUILEVEL_REDUCED;
991 			}
992 			else if(msi_strequal(argvW[i]+2, "f"))
993 			{
994 				InstallUILevel = INSTALLUILEVEL_FULL|INSTALLUILEVEL_ENDDIALOG;
995 			}
996 			else if(msi_strequal(argvW[i]+2, "n+"))
997 			{
998 				InstallUILevel = INSTALLUILEVEL_NONE|INSTALLUILEVEL_ENDDIALOG;
999 			}
1000 			else if(msi_strprefix(argvW[i]+2, "b"))
1001 			{
1002                 const WCHAR *ptr = argvW[i] + 3;
1003 
1004                 InstallUILevel = INSTALLUILEVEL_BASIC;
1005 
1006                 while (*ptr)
1007                 {
1008                     if (msi_strprefix(ptr, "+"))
1009                         InstallUILevel |= INSTALLUILEVEL_ENDDIALOG;
1010                     if (msi_strprefix(ptr, "-"))
1011                         InstallUILevel |= INSTALLUILEVEL_PROGRESSONLY;
1012                     if (msi_strprefix(ptr, "!"))
1013                     {
1014                         WINE_FIXME("Unhandled modifier: !\n");
1015                         InstallUILevel |= INSTALLUILEVEL_HIDECANCEL;
1016                     }
1017                     ptr++;
1018                 }
1019 			}
1020 			else
1021 			{
1022 				report_error("Unknown option \"%s\" for UI level\n",
1023 					     wine_dbgstr_w(argvW[i]+2));
1024 			}
1025 		}
1026                 else if(msi_option_equal(argvW[i], "passive"))
1027                 {
1028                     InstallUILevel = INSTALLUILEVEL_BASIC|INSTALLUILEVEL_PROGRESSONLY|INSTALLUILEVEL_HIDECANCEL;
1029                     StringListAppend(&property_list, L"REBOOTPROMPT=\"S\"");
1030                 }
1031 		else if(msi_option_equal(argvW[i], "y"))
1032 		{
1033 			FunctionDllRegisterServer = TRUE;
1034 			i++;
1035 			if(i >= argc)
1036 				ShowUsage(1);
1037 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
1038 			DllName = argvW[i];
1039 		}
1040 		else if(msi_option_equal(argvW[i], "z"))
1041 		{
1042 			FunctionDllUnregisterServer = TRUE;
1043 			i++;
1044 			if(i >= argc)
1045 				ShowUsage(1);
1046 			WINE_TRACE("argvW[%d] = %s\n", i, wine_dbgstr_w(argvW[i]));
1047 			DllName = argvW[i];
1048 		}
1049 		else if(msi_option_equal(argvW[i], "help") || msi_option_equal(argvW[i], "?"))
1050 		{
1051 			ShowUsage(0);
1052 		}
1053 		else if(msi_option_equal(argvW[i], "m"))
1054 		{
1055 			FunctionUnknown = TRUE;
1056 			WINE_FIXME("Unknown parameter /m\n");
1057 		}
1058 		else if(msi_option_equal(argvW[i], "D"))
1059 		{
1060 			FunctionUnknown = TRUE;
1061 			WINE_FIXME("Unknown parameter /D\n");
1062 		}
1063 		else if (msi_option_equal(argvW[i], "V"))
1064 		{
1065 		    FunctionServer = TRUE;
1066 		}
1067 		else
1068 			StringListAppend(&property_list, argvW[i]);
1069 	}
1070 
1071 	/* start the GUI */
1072 	MsiSetInternalUI(InstallUILevel, NULL);
1073 
1074 	Properties = build_properties( property_list );
1075 
1076 	if(FunctionInstallAdmin && FunctionPatch)
1077 		FunctionInstall = FALSE;
1078 
1079 	ReturnCode = 1;
1080 	if(FunctionInstall)
1081 	{
1082 		if(IsProductCode(PackageName))
1083 			ReturnCode = MsiConfigureProductExW(PackageName, 0, INSTALLSTATE_DEFAULT, Properties);
1084 		else
1085 		{
1086 			if ((ReturnCode = MsiInstallProductW(PackageName, Properties)) == ERROR_FILE_NOT_FOUND
1087 					&& (path = get_path_with_extension(PackageName)))
1088 			{
1089 				ReturnCode = MsiInstallProductW(path, Properties);
1090 				free(path);
1091 			}
1092 		}
1093 	}
1094 	else if(FunctionRepair)
1095 	{
1096 		if(IsProductCode(PackageName))
1097 			WINE_FIXME("Product code treatment not implemented yet\n");
1098 		else
1099 		{
1100 			if ((ReturnCode = MsiReinstallProductW(PackageName, RepairMode)) == ERROR_FILE_NOT_FOUND
1101 					&& (path = get_path_with_extension(PackageName)))
1102 			{
1103 				ReturnCode = MsiReinstallProductW(path, RepairMode);
1104 				free(path);
1105 			}
1106 		}
1107 	}
1108 	else if(FunctionAdvertise)
1109 	{
1110 		LPWSTR Transforms = build_transforms( property_list );
1111 		ReturnCode = MsiAdvertiseProductW(PackageName, (LPWSTR) AdvertiseMode, Transforms, Language);
1112 	}
1113 	else if(FunctionPatch)
1114 	{
1115 		ReturnCode = MsiApplyPatchW(PatchFileName, PackageName, InstallType, Properties);
1116 	}
1117 	else if(FunctionDllRegisterServer)
1118 	{
1119 		ReturnCode = DoDllRegisterServer(DllName);
1120 	}
1121 	else if(FunctionDllUnregisterServer)
1122 	{
1123 		ReturnCode = DoDllUnregisterServer(DllName);
1124 	}
1125 	else if (FunctionRegServer)
1126 	{
1127 		ReturnCode = DoRegServer();
1128 	}
1129 	else if (FunctionUnregServer)
1130 	{
1131 		ReturnCode = DoUnregServer();
1132 	}
1133 	else if (FunctionServer)
1134 	{
1135 	    ReturnCode = DoService();
1136 	}
1137 	else if (FunctionUnknown)
1138 	{
1139 		WINE_FIXME( "Unknown function, ignoring\n" );
1140 	}
1141 	else
1142 		ShowUsage(1);
1143 
1144 	return ReturnCode;
1145 }
1146