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