Reverse the order for deleting the items in resetcontent to correctly
[wine] / dlls / msi / appsearch.c
1 /*
2  * Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
3  *
4  * Copyright 2005 Juan Lang
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 #include <stdarg.h>
21
22 #define COBJMACROS
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "msi.h"
27 #include "msiquery.h"
28 #include "winver.h"
29 #include "wine/unicode.h"
30 #include "wine/debug.h"
31 #include "msipriv.h"
32 #include "action.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(msi);
35
36 typedef struct tagMSISIGNATURE
37 {
38     LPWSTR   Name;     /* NOT owned by this structure */
39     LPWSTR   Property; /* NOT owned by this structure */
40     LPWSTR   File;
41     DWORD    MinVersionMS;
42     DWORD    MinVersionLS;
43     DWORD    MaxVersionMS;
44     DWORD    MaxVersionLS;
45     DWORD    MinSize;
46     DWORD    MaxSize;
47     FILETIME MinTime;
48     FILETIME MaxTime;
49     LPWSTR   Languages;
50 }MSISIGNATURE;
51
52 static void ACTION_VerStrToInteger(LPCWSTR verStr, PDWORD ms, PDWORD ls)
53 {
54     const WCHAR *ptr;
55     int x1 = 0, x2 = 0, x3 = 0, x4 = 0;
56
57     x1 = atoiW(verStr);
58     ptr = strchrW(verStr, '.');
59     if (ptr)
60     {
61         x2 = atoiW(ptr + 1);
62         ptr = strchrW(ptr + 1, '.');
63     }
64     if (ptr)
65     {
66         x3 = atoiW(ptr + 1);
67         ptr = strchrW(ptr + 1, '.');
68     }
69     if (ptr)
70         x4 = atoiW(ptr + 1);
71     /* FIXME: byte-order dependent? */
72     *ms = x1 << 16 | x2;
73     *ls = x3 << 16 | x4;
74 }
75
76 /* Fills in sig with the the values from the Signature table, where name is the
77  * signature to find.  Upon return, sig->File will be NULL if the record is not
78  * found, and not NULL if it is found.
79  * Warning: clears all fields in sig!
80  * Returns ERROR_SUCCESS upon success (where not finding the record counts as
81  * success), something else on error.
82  */
83 static UINT ACTION_AppSearchGetSignature(MSIPACKAGE *package, MSISIGNATURE *sig,
84  LPCWSTR name)
85 {
86     MSIQUERY *view;
87     UINT rc;
88     static const WCHAR ExecSeqQuery[] =  {
89    's','e','l','e','c','t',' ','*',' ',
90    'f','r','o','m',' ',
91    'S','i','g','n','a','t','u','r','e',' ',
92    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
93    '\'','%','s','\'',0};
94
95     TRACE("(package %p, sig %p)\n", package, sig);
96     memset(sig, 0, sizeof(*sig));
97     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, name);
98     if (rc == ERROR_SUCCESS)
99     {
100         MSIRECORD *row = 0;
101         DWORD time;
102         WCHAR *minVersion, *maxVersion;
103
104         rc = MSI_ViewExecute(view, 0);
105         if (rc != ERROR_SUCCESS)
106         {
107             TRACE("MSI_ViewExecute returned %d\n", rc);
108             goto end;
109         }
110         rc = MSI_ViewFetch(view,&row);
111         if (rc != ERROR_SUCCESS)
112         {
113             TRACE("MSI_ViewFetch returned %d\n", rc);
114             rc = ERROR_SUCCESS;
115             goto end;
116         }
117
118         /* get properties */
119         sig->File = load_dynamic_stringW(row,2);
120         minVersion = load_dynamic_stringW(row,3);
121         if (minVersion)
122         {
123             ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS,
124              &sig->MinVersionLS);
125             HeapFree(GetProcessHeap(), 0, minVersion);
126         }
127         maxVersion = load_dynamic_stringW(row,4);
128         if (maxVersion)
129         {
130             ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS,
131              &sig->MaxVersionLS);
132             HeapFree(GetProcessHeap(), 0, maxVersion);
133         }
134         sig->MinSize = MSI_RecordGetInteger(row,5);
135         if (sig->MinSize == MSI_NULL_INTEGER)
136             sig->MinSize = 0;
137         sig->MaxSize = MSI_RecordGetInteger(row,6);
138         if (sig->MaxSize == MSI_NULL_INTEGER)
139             sig->MaxSize = 0;
140         sig->Languages = load_dynamic_stringW(row,9);
141         time = MSI_RecordGetInteger(row,7);
142         if (time != MSI_NULL_INTEGER)
143             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
144         time = MSI_RecordGetInteger(row,8);
145         if (time != MSI_NULL_INTEGER)
146             DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
147         TRACE("Found file name %s for Signature_ %s;\n",
148          debugstr_w(sig->File), debugstr_w(name));
149         TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
150          LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
151          LOWORD(sig->MinVersionLS));
152         TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
153          LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
154          LOWORD(sig->MaxVersionLS));
155         TRACE("MinSize is %ld, MaxSize is %ld;\n", sig->MinSize, sig->MaxSize);
156         TRACE("Languages is %s\n", debugstr_w(sig->Languages));
157
158 end:
159         msiobj_release(&row->hdr);
160         MSI_ViewClose(view);
161         msiobj_release(&view->hdr);
162     }
163     else
164     {
165         TRACE("MSI_OpenQuery returned %d\n", rc);
166         rc = ERROR_SUCCESS;
167     }
168
169     TRACE("returning %d\n", rc);
170     return rc;
171 }
172
173 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, BOOL *appFound,
174  MSISIGNATURE *sig)
175 {
176     MSIQUERY *view;
177     UINT rc;
178     static const WCHAR ExecSeqQuery[] =  {
179    's','e','l','e','c','t',' ','*',' ',
180    'f','r','o','m',' ',
181    'C','o','m','p','L','o','c','a','t','o','r',' ',
182    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
183    '\'','%','s','\'',0};
184
185     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
186     *appFound = FALSE;
187     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
188     if (rc == ERROR_SUCCESS)
189     {
190         MSIRECORD *row = 0;
191         WCHAR guid[50];
192         DWORD sz;
193
194         rc = MSI_ViewExecute(view, 0);
195         if (rc != ERROR_SUCCESS)
196         {
197             TRACE("MSI_ViewExecute returned %d\n", rc);
198             goto end;
199         }
200         rc = MSI_ViewFetch(view,&row);
201         if (rc != ERROR_SUCCESS)
202         {
203             TRACE("MSI_ViewFetch returned %d\n", rc);
204             rc = ERROR_SUCCESS;
205             goto end;
206         }
207
208         /* get GUID */
209         guid[0] = 0;
210         sz=sizeof(guid)/sizeof(guid[0]);
211         rc = MSI_RecordGetStringW(row,2,guid,&sz);
212         if (rc != ERROR_SUCCESS)
213         {
214             ERR("Error is %x\n",rc);
215             goto end;
216         }
217         FIXME("AppSearch unimplemented for CompLocator table (GUID %s)\n",
218          debugstr_w(guid));
219
220 end:
221         msiobj_release(&row->hdr);
222         MSI_ViewClose(view);
223         msiobj_release(&view->hdr);
224     }
225     else
226     {
227         TRACE("MSI_OpenQuery returned %d\n", rc);
228         rc = ERROR_SUCCESS;
229     }
230
231     TRACE("returning %d\n", rc);
232     return rc;
233 }
234
235 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, BOOL *appFound,
236  MSISIGNATURE *sig)
237 {
238     MSIQUERY *view;
239     UINT rc;
240     static const WCHAR ExecSeqQuery[] =  {
241    's','e','l','e','c','t',' ','*',' ',
242    'f','r','o','m',' ',
243    'R','e','g','L','o','c','a','t','o','r',' ',
244    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
245    '\'','%','s','\'',0};
246
247     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
248     *appFound = FALSE;
249     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
250     if (rc == ERROR_SUCCESS)
251     {
252         MSIRECORD *row = 0;
253         LPWSTR keyPath;
254
255         rc = MSI_ViewExecute(view, 0);
256         if (rc != ERROR_SUCCESS)
257         {
258             TRACE("MSI_ViewExecute returned %d\n", rc);
259             goto end;
260         }
261         rc = MSI_ViewFetch(view,&row);
262         if (rc != ERROR_SUCCESS)
263         {
264             TRACE("MSI_ViewFetch returned %d\n", rc);
265             rc = ERROR_SUCCESS;
266             goto end;
267         }
268
269         /* get key path */
270         keyPath = load_dynamic_stringW(row,3);
271         FIXME("AppSearch unimplemented for RegLocator (key path %s)\n",
272          debugstr_w(keyPath));
273         HeapFree(GetProcessHeap(), 0, keyPath);
274
275 end:
276         msiobj_release(&row->hdr);
277         MSI_ViewClose(view);
278         msiobj_release(&view->hdr);
279     }
280     else
281     {
282         TRACE("MSI_OpenQuery returned %d\n", rc);
283         rc = ERROR_SUCCESS;
284     }
285
286     TRACE("returning %d\n", rc);
287     return rc;
288 }
289
290 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, BOOL *appFound,
291  MSISIGNATURE *sig)
292 {
293     MSIQUERY *view;
294     UINT rc;
295     static const WCHAR ExecSeqQuery[] =  {
296    's','e','l','e','c','t',' ','*',' ',
297    'f','r','o','m',' ',
298    'I','n','i','L','o','c','a','t','o','r',' ',
299    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
300    '\'','%','s','\'',0};
301
302     TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
303     *appFound = FALSE;
304     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
305     if (rc == ERROR_SUCCESS)
306     {
307         MSIRECORD *row = 0;
308         LPWSTR fileName;
309
310         rc = MSI_ViewExecute(view, 0);
311         if (rc != ERROR_SUCCESS)
312         {
313             TRACE("MSI_ViewExecute returned %d\n", rc);
314             goto end;
315         }
316         rc = MSI_ViewFetch(view,&row);
317         if (rc != ERROR_SUCCESS)
318         {
319             TRACE("MSI_ViewFetch returned %d\n", rc);
320             rc = ERROR_SUCCESS;
321             goto end;
322         }
323
324         /* get file name */
325         fileName = load_dynamic_stringW(row,2);
326         FIXME("AppSearch unimplemented for IniLocator (ini file name %s)\n",
327          debugstr_w(fileName));
328         HeapFree(GetProcessHeap(), 0, fileName);
329
330 end:
331         msiobj_release(&row->hdr);
332         MSI_ViewClose(view);
333         msiobj_release(&view->hdr);
334     }
335     else
336     {
337         TRACE("MSI_OpenQuery returned %d\n", rc);
338         rc = ERROR_SUCCESS;
339     }
340
341
342     TRACE("returning %d\n", rc);
343     return rc;
344 }
345
346 /* Expands the value in src into a path without property names and only
347  * containing long path names into dst.  Replaces at most len characters of dst,
348  * and always NULL-terminates dst if dst is not NULL and len >= 1.
349  * May modify src.
350  * Assumes src and dst are non-overlapping.
351  * FIXME: return code probably needed:
352  * - what does AppSearch return if the table values are invalid?
353  * - what if dst is too small?
354  */
355 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
356  size_t len)
357 {
358     WCHAR *ptr;
359     size_t copied = 0;
360
361     if (!src || !dst || !len)
362         return;
363
364     /* Ignore the short portion of the path, don't think we can use it anyway */
365     if ((ptr = strchrW(src, '|')))
366         ptr++;
367     else
368         ptr = src;
369     while (*ptr && copied < len - 1)
370     {
371         WCHAR *prop = strchrW(ptr, '[');
372
373         if (prop)
374         {
375             WCHAR *propEnd = strchrW(prop + 1, ']');
376
377             if (!propEnd)
378             {
379                 WARN("Unterminated property name in AnyPath: %s\n",
380                  debugstr_w(prop));
381                 break;
382             }
383             else
384             {
385                 DWORD propLen;
386
387                 *propEnd = 0;
388                 propLen = len - copied - 1;
389                 MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
390                 ptr = propEnd + 1;
391                 copied += propLen;
392             }
393         }
394         else
395         {
396             size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
397
398             memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
399             ptr += toCopy;
400             copied += toCopy;
401         }
402     }
403     *(dst + copied) = '\0';
404 }
405
406 /* Sets *matches to whether the file (whose path is filePath) matches the
407  * versions set in sig.
408  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
409  * something else if a install-halting error occurs.
410  */
411 static UINT ACTION_FileVersionMatches(MSISIGNATURE *sig, LPCWSTR filePath,
412  BOOL *matches)
413 {
414     UINT rc = ERROR_SUCCESS;
415
416     *matches = FALSE;
417     if (sig->Languages)
418     {
419         FIXME(": need to check version for languages %s\n",
420          debugstr_w(sig->Languages));
421     }
422     else
423     {
424         DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
425
426         if (size)
427         {
428             LPVOID buf = HeapAlloc(GetProcessHeap(), 0, size);
429
430             if (buf)
431             {
432                 static const WCHAR rootW[] = { '\\',0 };
433                 UINT versionLen;
434                 LPVOID subBlock = NULL;
435
436                 if (GetFileVersionInfoW(filePath, 0, size, buf))
437                     VerQueryValueW(buf, rootW, &subBlock, &versionLen);
438                 if (subBlock)
439                 {
440                     VS_FIXEDFILEINFO *info =
441                      (VS_FIXEDFILEINFO *)subBlock;
442
443                     TRACE("Comparing file version %d.%d.%d.%d:\n",
444                      HIWORD(info->dwFileVersionMS),
445                      LOWORD(info->dwFileVersionMS),
446                      HIWORD(info->dwFileVersionLS),
447                      LOWORD(info->dwFileVersionLS));
448                     if (info->dwFileVersionMS < sig->MinVersionMS
449                      || (info->dwFileVersionMS == sig->MinVersionMS &&
450                      info->dwFileVersionLS < sig->MinVersionLS))
451                     {
452                         TRACE("Less than minimum version %d.%d.%d.%d\n",
453                          HIWORD(sig->MinVersionMS),
454                          LOWORD(sig->MinVersionMS),
455                          HIWORD(sig->MinVersionLS),
456                          LOWORD(sig->MinVersionLS));
457                     }
458                     else if (info->dwFileVersionMS < sig->MinVersionMS
459                      || (info->dwFileVersionMS == sig->MinVersionMS &&
460                      info->dwFileVersionLS < sig->MinVersionLS))
461                     {
462                         TRACE("Greater than minimum version %d.%d.%d.%d\n",
463                          HIWORD(sig->MaxVersionMS),
464                          LOWORD(sig->MaxVersionMS),
465                          HIWORD(sig->MaxVersionLS),
466                          LOWORD(sig->MaxVersionLS));
467                     }
468                     else
469                         *matches = TRUE;
470                 }
471                 HeapFree(GetProcessHeap(), 0, buf);
472             }
473             else
474                 rc = ERROR_OUTOFMEMORY;
475         }
476     }
477     return rc;
478 }
479
480 /* Sets *matches to whether the file in findData matches that in sig.
481  * fullFilePath is assumed to be the full path of the file specified in
482  * findData, which may be necessary to compare the version.
483  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
484  * something else if a install-halting error occurs.
485  */
486 static UINT ACTION_FileMatchesSig(MSISIGNATURE *sig,
487  LPWIN32_FIND_DATAW findData, LPCWSTR fullFilePath, BOOL *matches)
488 {
489     UINT rc = ERROR_SUCCESS;
490
491     *matches = TRUE;
492     /* assumes the caller has already ensured the filenames match, so check
493      * the other fields..
494      */
495     if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
496     {
497         if (findData->ftCreationTime.dwHighDateTime <
498          sig->MinTime.dwHighDateTime ||
499          (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
500          && findData->ftCreationTime.dwLowDateTime <
501          sig->MinTime.dwLowDateTime))
502             *matches = FALSE;
503     }
504     if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
505     {
506         if (findData->ftCreationTime.dwHighDateTime >
507          sig->MaxTime.dwHighDateTime ||
508          (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
509          && findData->ftCreationTime.dwLowDateTime >
510          sig->MaxTime.dwLowDateTime))
511             *matches = FALSE;
512     }
513     if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
514         *matches = FALSE;
515     if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
516         *matches = FALSE;
517     if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
518      sig->MaxVersionMS || sig->MaxVersionLS))
519         rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
520     return rc;
521 }
522
523 /* Recursively searches the directory dir for files that match the signature
524  * sig, up to (depth + 1) levels deep.  That is, if depth is 0, it searches dir
525  * (and only dir).  If depth is 1, searches dir and its immediate
526  * subdirectories.
527  * Assumes sig->File is not NULL.
528  * Returns ERROR_SUCCESS on success (which may include non-critical errors),
529  * something else on failures which should halt the install.
530  */
531 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, BOOL *appFound,
532  MSISIGNATURE *sig, LPCWSTR dir, int depth)
533 {
534     static const WCHAR starDotStarW[] = { '*','.','*',0 };
535     UINT rc = ERROR_SUCCESS;
536     size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
537     WCHAR *buf;
538
539     TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
540      debugstr_w(sig->File), depth);
541
542     if (depth < 0)
543         return ERROR_INVALID_PARAMETER;
544
545     *appFound = FALSE;
546     /* We need the buffer in both paths below, so go ahead and allocate it
547      * here.  Add two because we might need to add a backslash if the dir name
548      * isn't backslash-terminated.
549      */
550     buf = HeapAlloc(GetProcessHeap(), 0,
551      (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
552     if (buf)
553     {
554         /* a depth of 0 implies we should search dir, so go ahead and search */
555         HANDLE hFind;
556         WIN32_FIND_DATAW findData;
557
558         memcpy(buf, dir, dirLen * sizeof(WCHAR));
559         if (buf[dirLen - 1] != '\\')
560             buf[dirLen++ - 1] = '\\';
561         memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
562         hFind = FindFirstFileW(buf, &findData);
563         if (hFind != INVALID_HANDLE_VALUE)
564         {
565             BOOL matches;
566
567             /* assuming Signature can't contain wildcards for the file name,
568              * so don't bother with FindNextFileW here.
569              */
570             if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
571              && matches)
572             {
573                 TRACE("found file, setting %s to %s\n",
574                  debugstr_w(sig->Property), debugstr_w(buf));
575                 rc = MSI_SetPropertyW(package, sig->Property, buf);
576                 *appFound = TRUE;
577             }
578             FindClose(hFind);
579         }
580         if (rc == ERROR_SUCCESS && !*appFound && depth > 0)
581         {
582             HANDLE hFind;
583             WIN32_FIND_DATAW findData;
584
585             memcpy(buf, dir, dirLen * sizeof(WCHAR));
586             if (buf[dirLen - 1] != '\\')
587                 buf[dirLen++ - 1] = '\\';
588             lstrcpyW(buf + dirLen, starDotStarW);
589             hFind = FindFirstFileW(buf, &findData);
590             if (hFind != INVALID_HANDLE_VALUE)
591             {
592                 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
593                     rc = ACTION_RecurseSearchDirectory(package, appFound,
594                      sig, findData.cFileName, depth - 1);
595                 while (rc == ERROR_SUCCESS && !*appFound &&
596                  FindNextFileW(hFind, &findData) != 0)
597                 {
598                     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
599                         rc = ACTION_RecurseSearchDirectory(package, appFound,
600                          sig, findData.cFileName, depth - 1);
601                 }
602                 FindClose(hFind);
603             }
604         }
605         HeapFree(GetProcessHeap(), 0, buf);
606     }
607     else
608         rc = ERROR_OUTOFMEMORY;
609
610     return rc;
611 }
612
613 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
614  LPCWSTR dir)
615 {
616     UINT rc = ERROR_SUCCESS;
617
618     if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
619     {
620         TRACE("directory exists, setting %s to %s\n",
621          debugstr_w(sig->Property), debugstr_w(dir));
622         rc = MSI_SetPropertyW(package, sig->Property, dir);
623     }
624     return rc;
625 }
626
627 static BOOL ACTION_IsFullPath(LPCWSTR path)
628 {
629     WCHAR first = toupperW(path[0]);
630     BOOL ret;
631
632     if (first >= 'A' && first <= 'A' && path[1] == ':')
633         ret = TRUE;
634     else if (path[0] == '\\' && path[1] == '\\')
635         ret = TRUE;
636     else
637         ret = FALSE;
638     return ret;
639 }
640
641 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
642  LPCWSTR expanded, int depth)
643 {
644     UINT rc;
645     BOOL found;
646
647     if (ACTION_IsFullPath(expanded))
648     {
649         if (sig->File)
650             rc = ACTION_RecurseSearchDirectory(package, &found, sig,
651              expanded, depth);
652         else
653         {
654             /* Recursively searching a directory makes no sense when the
655              * directory to search is the thing you're trying to find.
656              */
657             rc = ACTION_CheckDirectory(package, sig, expanded);
658         }
659     }
660     else
661     {
662         WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
663         DWORD drives = GetLogicalDrives();
664         int i;
665
666         rc = ERROR_SUCCESS;
667         found = FALSE;
668         for (i = 0; rc == ERROR_SUCCESS && !found && i < 26; i++)
669             if (drives & (1 << drives))
670             {
671                 pathWithDrive[0] = 'A' + i;
672                 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
673                 {
674                     strncpyW(pathWithDrive + 3, expanded,
675                      sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
676                     if (sig->File)
677                         rc = ACTION_RecurseSearchDirectory(package, &found, sig,
678                          pathWithDrive, depth);
679                     else
680                         rc = ACTION_CheckDirectory(package, sig, pathWithDrive);
681                 }
682             }
683     }
684     return rc;
685 }
686
687 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, MSISIGNATURE *sig)
688 {
689     MSIQUERY *view;
690     UINT rc;
691     static const WCHAR ExecSeqQuery[] =  {
692    's','e','l','e','c','t',' ','*',' ',
693    'f','r','o','m',' ',
694    'D','r','L','o','c','a','t','o','r',' ',
695    'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
696    '\'','%','s','\'',0};
697
698     TRACE("(package %p, sig %p)\n", package, sig);
699     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
700     if (rc == ERROR_SUCCESS)
701     {
702         MSIRECORD *row = 0;
703         WCHAR buffer[MAX_PATH], expanded[MAX_PATH];
704         DWORD sz;
705         int depth;
706
707         rc = MSI_ViewExecute(view, 0);
708         if (rc != ERROR_SUCCESS)
709         {
710             TRACE("MSI_ViewExecute returned %d\n", rc);
711             goto end;
712         }
713         rc = MSI_ViewFetch(view,&row);
714         if (rc != ERROR_SUCCESS)
715         {
716             TRACE("MSI_ViewFetch returned %d\n", rc);
717             rc = ERROR_SUCCESS;
718             goto end;
719         }
720
721         /* check whether parent is set */
722         buffer[0] = 0;
723         sz=sizeof(buffer)/sizeof(buffer[0]);
724         rc = MSI_RecordGetStringW(row,2,buffer,&sz);
725         if (rc != ERROR_SUCCESS)
726         {
727             ERR("Error is %x\n",rc);
728             goto end;
729         }
730         else if (buffer[0])
731         {
732             FIXME(": searching parent (%s) unimplemented\n",
733              debugstr_w(buffer));
734             goto end;
735         }
736         /* no parent, now look for path */
737         buffer[0] = 0;
738         sz=sizeof(buffer)/sizeof(buffer[0]);
739         rc = MSI_RecordGetStringW(row,3,buffer,&sz);
740         if (rc != ERROR_SUCCESS)
741         {
742             ERR("Error is %x\n",rc);
743             goto end;
744         }
745         if (MSI_RecordIsNull(row,4))
746             depth = 0;
747         else
748             depth = MSI_RecordGetInteger(row,4);
749         ACTION_ExpandAnyPath(package, buffer, expanded,
750          sizeof(expanded) / sizeof(expanded[0]));
751         rc = ACTION_SearchDirectory(package, sig, expanded, depth);
752
753 end:
754         msiobj_release(&row->hdr);
755         MSI_ViewClose(view);
756         msiobj_release(&view->hdr);
757     }
758     else
759     {
760         TRACE("MSI_OpenQuery returned %d\n", rc);
761         rc = ERROR_SUCCESS;
762     }
763
764
765     TRACE("returning %d\n", rc);
766     return rc;
767 }
768
769 /* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
770  * is the best reference for the AppSearch table and how it's used.
771  */
772 UINT ACTION_AppSearch(MSIPACKAGE *package)
773 {
774     MSIQUERY *view;
775     UINT rc;
776     static const WCHAR ExecSeqQuery[] =  {
777    's','e','l','e','c','t',' ','*',' ',
778    'f','r','o','m',' ',
779    'A','p','p','S','e','a','r','c','h',0};
780
781     rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery);
782     if (rc == ERROR_SUCCESS)
783     {
784         MSIRECORD *row = 0;
785         WCHAR propBuf[0x100], sigBuf[0x100];
786         DWORD sz;
787         MSISIGNATURE sig;
788         BOOL appFound = FALSE;
789
790         rc = MSI_ViewExecute(view, 0);
791         if (rc != ERROR_SUCCESS)
792             goto end;
793
794         while (!rc)
795         {
796             rc = MSI_ViewFetch(view,&row);
797             if (rc != ERROR_SUCCESS)
798             {
799                 rc = ERROR_SUCCESS;
800                 break;
801             }
802
803             /* get property and signature */
804             propBuf[0] = 0;
805             sz=sizeof(propBuf)/sizeof(propBuf[0]);
806             rc = MSI_RecordGetStringW(row,1,propBuf,&sz);
807             if (rc != ERROR_SUCCESS)
808             {
809                 ERR("Error is %x\n",rc);
810                 msiobj_release(&row->hdr);
811                 break;
812             }
813             sigBuf[0] = 0;
814             sz=sizeof(sigBuf)/sizeof(sigBuf[0]);
815             rc = MSI_RecordGetStringW(row,2,sigBuf,&sz);
816             if (rc != ERROR_SUCCESS)
817             {
818                 ERR("Error is %x\n",rc);
819                 msiobj_release(&row->hdr);
820                 break;
821             }
822             TRACE("Searching for Property %s, Signature_ %s\n",
823              debugstr_w(propBuf), debugstr_w(sigBuf));
824             /* This clears all the fields, so set Name and Property afterward */
825             rc = ACTION_AppSearchGetSignature(package, &sig, sigBuf);
826             sig.Name = sigBuf;
827             sig.Property = propBuf;
828             if (rc == ERROR_SUCCESS)
829             {
830                 rc = ACTION_AppSearchComponents(package, &appFound, &sig);
831                 if (rc == ERROR_SUCCESS && !appFound)
832                 {
833                     rc = ACTION_AppSearchReg(package, &appFound, &sig);
834                     if (rc == ERROR_SUCCESS && !appFound)
835                     {
836                         rc = ACTION_AppSearchIni(package, &appFound, &sig);
837                         if (rc == ERROR_SUCCESS && !appFound)
838                             rc = ACTION_AppSearchDr(package, &sig);
839                     }
840                 }
841             }
842             HeapFree(GetProcessHeap(), 0, sig.File);
843             HeapFree(GetProcessHeap(), 0, sig.Languages);
844             msiobj_release(&row->hdr);
845         }
846
847 end:
848         MSI_ViewClose(view);
849         msiobj_release(&view->hdr);
850     }
851     else
852         rc = ERROR_SUCCESS;
853
854     return rc;
855 }