riched20: Fix test crash on WinXP-SP2.
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 #include <stdarg.h>
21
22 #define COBJMACROS
23
24 #include "windef.h"
25 #include "winbase.h"
26 #include "winreg.h"
27 #include "msi.h"
28 #include "msiquery.h"
29 #include "msidefs.h"
30 #include "winver.h"
31 #include "wine/unicode.h"
32 #include "wine/debug.h"
33 #include "msipriv.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(msi);
36
37 typedef struct tagMSISIGNATURE
38 {
39     LPCWSTR  Name;     /* 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 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, LPCWSTR name)
84 {
85     static const WCHAR query[] = {
86         's','e','l','e','c','t',' ','*',' ',
87         'f','r','o','m',' ',
88         'S','i','g','n','a','t','u','r','e',' ',
89         'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
90         '\'','%','s','\'',0};
91     LPWSTR minVersion, maxVersion;
92     MSIRECORD *row;
93     DWORD time;
94
95     TRACE("package %p, sig %p\n", package, sig);
96
97     memset(sig, 0, sizeof(*sig));
98     sig->Name = name;
99     row = MSI_QueryGetRecord( package->db, query, name );
100     if (!row)
101     {
102         TRACE("failed to query signature for %s\n", debugstr_w(name));
103         return ERROR_SUCCESS;
104     }
105
106     /* get properties */
107     sig->File = msi_dup_record_field(row,2);
108     minVersion = msi_dup_record_field(row,3);
109     if (minVersion)
110     {
111         ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS, &sig->MinVersionLS);
112         msi_free( minVersion );
113     }
114     maxVersion = msi_dup_record_field(row,4);
115     if (maxVersion)
116     {
117         ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS, &sig->MaxVersionLS);
118         msi_free( maxVersion );
119     }
120     sig->MinSize = MSI_RecordGetInteger(row,5);
121     if (sig->MinSize == MSI_NULL_INTEGER)
122         sig->MinSize = 0;
123     sig->MaxSize = MSI_RecordGetInteger(row,6);
124     if (sig->MaxSize == MSI_NULL_INTEGER)
125         sig->MaxSize = 0;
126     sig->Languages = msi_dup_record_field(row,9);
127     time = MSI_RecordGetInteger(row,7);
128     if (time != MSI_NULL_INTEGER)
129         DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
130     time = MSI_RecordGetInteger(row,8);
131     if (time != MSI_NULL_INTEGER)
132         DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
133
134     TRACE("Found file name %s for Signature_ %s;\n",
135           debugstr_w(sig->File), debugstr_w(name));
136     TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
137           LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
138           LOWORD(sig->MinVersionLS));
139     TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
140           LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
141           LOWORD(sig->MaxVersionLS));
142     TRACE("MinSize is %d, MaxSize is %d;\n", sig->MinSize, sig->MaxSize);
143     TRACE("Languages is %s\n", debugstr_w(sig->Languages));
144
145     msiobj_release( &row->hdr );
146
147     return ERROR_SUCCESS;
148 }
149
150 /* Frees any memory allocated in sig */
151 static void ACTION_FreeSignature(MSISIGNATURE *sig)
152 {
153     msi_free(sig->File);
154     msi_free(sig->Languages);
155 }
156
157 static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
158 {
159     static const WCHAR query[] =  {
160         's','e','l','e','c','t',' ','*',' ',
161         'f','r','o','m',' ',
162         'C','o','m','p','L','o','c','a','t','o','r',' ',
163         'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
164         '\'','%','s','\'',0};
165     MSIRECORD *row;
166     LPWSTR guid;
167
168     TRACE("%s\n", debugstr_w(sig->Name));
169
170     *appValue = NULL;
171
172     row = MSI_QueryGetRecord( package->db, query, sig->Name );
173     if (!row)
174     {
175         TRACE("failed to query CompLocator for %s\n", debugstr_w(sig->Name));
176         return ERROR_SUCCESS;
177     }
178
179     guid = msi_dup_record_field( row, 2 );
180     FIXME("AppSearch CompLocator (%s) unimplemented\n", debugstr_w(guid));
181     msi_free( guid );
182     msiobj_release( &row->hdr );
183
184     return ERROR_SUCCESS;
185 }
186
187 static void ACTION_ConvertRegValue(DWORD regType, const BYTE *value, DWORD sz,
188  LPWSTR *appValue)
189 {
190     static const WCHAR dwordFmt[] = { '#','%','d','\0' };
191     static const WCHAR expandSzFmt[] = { '#','%','%','%','s','\0' };
192     static const WCHAR binFmt[] = { '#','x','%','x','\0' };
193     DWORD i;
194
195     switch (regType)
196     {
197         case REG_SZ:
198             if (*(LPCWSTR)value == '#')
199             {
200                 /* escape leading pound with another */
201                 *appValue = msi_alloc(sz + sizeof(WCHAR));
202                 (*appValue)[0] = '#';
203                 strcpyW(*appValue + 1, (LPCWSTR)value);
204             }
205             else
206             {
207                 *appValue = msi_alloc(sz);
208                 strcpyW(*appValue, (LPCWSTR)value);
209             }
210             break;
211         case REG_DWORD:
212             /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
213              * char if needed
214              */
215             *appValue = msi_alloc(10 * sizeof(WCHAR));
216             sprintfW(*appValue, dwordFmt, *(const DWORD *)value);
217             break;
218         case REG_EXPAND_SZ:
219             /* space for extra #% characters in front */
220             *appValue = msi_alloc(sz + 2 * sizeof(WCHAR));
221             sprintfW(*appValue, expandSzFmt, (LPCWSTR)value);
222             break;
223         case REG_BINARY:
224             /* 3 == length of "#x<nibble>" */
225             *appValue = msi_alloc((sz * 3 + 1) * sizeof(WCHAR));
226             for (i = 0; i < sz; i++)
227                 sprintfW(*appValue + i * 3, binFmt, value[i]);
228             break;
229         default:
230             WARN("unimplemented for values of type %d\n", regType);
231             *appValue = NULL;
232     }
233 }
234
235 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
236  LPCWSTR path, int depth, LPWSTR *appValue);
237
238 static UINT ACTION_AppSearchReg(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
239 {
240     static const WCHAR query[] =  {
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',' ',
245         'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
246     LPWSTR keyPath = NULL, valueName = NULL;
247     LPWSTR deformatted = NULL;
248     int root, type;
249     HKEY rootKey, key = NULL;
250     DWORD sz = 0, regType;
251     LPBYTE value = NULL;
252     MSIRECORD *row;
253     UINT rc;
254
255     TRACE("%s\n", debugstr_w(sig->Name));
256
257     *appValue = NULL;
258
259     row = MSI_QueryGetRecord( package->db, query, sig->Name );
260     if (!row)
261     {
262         TRACE("failed to query RegLocator for %s\n", debugstr_w(sig->Name));
263         return ERROR_SUCCESS;
264     }
265
266     root = MSI_RecordGetInteger(row,2);
267     keyPath = msi_dup_record_field(row,3);
268     valueName = msi_dup_record_field(row,4);
269     type = MSI_RecordGetInteger(row,5);
270
271     deformat_string(package, keyPath, &deformatted);
272
273     switch (root)
274     {
275     case msidbRegistryRootClassesRoot:
276         rootKey = HKEY_CLASSES_ROOT;
277         break;
278     case msidbRegistryRootCurrentUser:
279         rootKey = HKEY_CURRENT_USER;
280         break;
281     case msidbRegistryRootLocalMachine:
282         rootKey = HKEY_LOCAL_MACHINE;
283         break;
284     case msidbRegistryRootUsers:
285         rootKey = HKEY_USERS;
286         break;
287     default:
288         WARN("Unknown root key %d\n", root);
289         goto end;
290     }
291
292     rc = RegOpenKeyW(rootKey, deformatted, &key);
293     if (rc)
294     {
295         TRACE("RegOpenKeyW returned %d\n", rc);
296         goto end;
297     }
298
299     rc = RegQueryValueExW(key, valueName, NULL, NULL, NULL, &sz);
300     if (rc)
301     {
302         TRACE("RegQueryValueExW returned %d\n", rc);
303         goto end;
304     }
305     /* FIXME: sanity-check sz before allocating (is there an upper-limit
306      * on the value of a property?)
307      */
308     value = msi_alloc( sz );
309     rc = RegQueryValueExW(key, valueName, NULL, &regType, value, &sz);
310     if (rc)
311     {
312         TRACE("RegQueryValueExW returned %d\n", rc);
313         goto end;
314     }
315
316     /* bail out if the registry key is empty */
317     if (sz == 0)
318         goto end;
319
320     switch (type & 0x0f)
321     {
322     case msidbLocatorTypeDirectory:
323         rc = ACTION_SearchDirectory(package, sig, (LPWSTR)value, 0, appValue);
324         break;
325     case msidbLocatorTypeFileName:
326         *appValue = strdupW((LPWSTR)value);
327         break;
328     case msidbLocatorTypeRawValue:
329         ACTION_ConvertRegValue(regType, value, sz, appValue);
330         break;
331     default:
332         FIXME("AppSearch unimplemented for type %d (key path %s, value %s)\n",
333               type, debugstr_w(keyPath), debugstr_w(valueName));
334     }
335 end:
336     msi_free( value );
337     RegCloseKey( key );
338
339     msi_free( keyPath );
340     msi_free( valueName );
341     msi_free( deformatted );
342
343     msiobj_release(&row->hdr);
344
345     return ERROR_SUCCESS;
346 }
347
348 static UINT ACTION_AppSearchIni(MSIPACKAGE *package, LPWSTR *appValue,
349  MSISIGNATURE *sig)
350 {
351     static const WCHAR query[] =  {
352         's','e','l','e','c','t',' ','*',' ',
353         'f','r','o','m',' ',
354         'I','n','i','L','o','c','a','t','o','r',' ',
355         'w','h','e','r','e',' ',
356         'S','i','g','n','a','t','u','r','e','_',' ','=',' ','\'','%','s','\'',0};
357     MSIRECORD *row;
358     LPWSTR fileName, section, key;
359     int field, type;
360     WCHAR buf[MAX_PATH];
361
362     TRACE("%s\n", debugstr_w(sig->Name));
363
364     *appValue = NULL;
365
366     row = MSI_QueryGetRecord( package->db, query, sig->Name );
367     if (!row)
368     {
369         TRACE("failed to query IniLocator for %s\n", debugstr_w(sig->Name));
370         return ERROR_SUCCESS;
371     }
372
373     fileName = msi_dup_record_field(row, 2);
374     section = msi_dup_record_field(row, 3);
375     key = msi_dup_record_field(row, 4);
376     field = MSI_RecordGetInteger(row, 5);
377     type = MSI_RecordGetInteger(row, 6);
378     if (field == MSI_NULL_INTEGER)
379         field = 0;
380     if (type == MSI_NULL_INTEGER)
381         type = 0;
382
383     GetPrivateProfileStringW(section, key, NULL, buf, MAX_PATH, fileName);
384     if (buf[0])
385     {
386         switch (type & 0x0f)
387         {
388         case msidbLocatorTypeDirectory:
389             FIXME("unimplemented for Directory (%s)\n", debugstr_w(buf));
390             break;
391         case msidbLocatorTypeFileName:
392             FIXME("unimplemented for File (%s)\n", debugstr_w(buf));
393             break;
394         case msidbLocatorTypeRawValue:
395             *appValue = strdupW(buf);
396             break;
397         }
398     }
399
400     msi_free(fileName);
401     msi_free(section);
402     msi_free(key);
403
404     msiobj_release(&row->hdr);
405
406     return ERROR_SUCCESS;
407 }
408
409 /* Expands the value in src into a path without property names and only
410  * containing long path names into dst.  Replaces at most len characters of dst,
411  * and always NULL-terminates dst if dst is not NULL and len >= 1.
412  * May modify src.
413  * Assumes src and dst are non-overlapping.
414  * FIXME: return code probably needed:
415  * - what does AppSearch return if the table values are invalid?
416  * - what if dst is too small?
417  */
418 static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
419  size_t len)
420 {
421     WCHAR *ptr;
422     size_t copied = 0;
423
424     if (!src || !dst || !len)
425     {
426         if (dst) *dst = '\0';
427         return;
428     }
429
430     /* Ignore the short portion of the path, don't think we can use it anyway */
431     if ((ptr = strchrW(src, '|')))
432         ptr++;
433     else
434         ptr = src;
435     while (*ptr && copied < len - 1)
436     {
437         WCHAR *prop = strchrW(ptr, '[');
438
439         if (prop)
440         {
441             WCHAR *propEnd = strchrW(prop + 1, ']');
442
443             if (!propEnd)
444             {
445                 WARN("Unterminated property name in AnyPath: %s\n",
446                  debugstr_w(prop));
447                 break;
448             }
449             else
450             {
451                 DWORD propLen;
452
453                 *propEnd = 0;
454                 propLen = len - copied - 1;
455                 MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
456                 ptr = propEnd + 1;
457                 copied += propLen;
458             }
459         }
460         else
461         {
462             size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
463
464             memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
465             ptr += toCopy;
466             copied += toCopy;
467         }
468     }
469     *(dst + copied) = '\0';
470 }
471
472 /* Sets *matches to whether the file (whose path is filePath) matches the
473  * versions set in sig.
474  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
475  * something else if an install-halting error occurs.
476  */
477 static UINT ACTION_FileVersionMatches(const MSISIGNATURE *sig, LPCWSTR filePath,
478  BOOL *matches)
479 {
480     UINT rc = ERROR_SUCCESS;
481
482     *matches = FALSE;
483     if (sig->Languages)
484     {
485         FIXME(": need to check version for languages %s\n",
486          debugstr_w(sig->Languages));
487     }
488     else
489     {
490         DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
491
492         if (size)
493         {
494             LPVOID buf = msi_alloc( size);
495
496             if (buf)
497             {
498                 static const WCHAR rootW[] = { '\\',0 };
499                 UINT versionLen;
500                 LPVOID subBlock = NULL;
501
502                 if (GetFileVersionInfoW(filePath, 0, size, buf))
503                     VerQueryValueW(buf, rootW, &subBlock, &versionLen);
504                 if (subBlock)
505                 {
506                     VS_FIXEDFILEINFO *info =
507                      (VS_FIXEDFILEINFO *)subBlock;
508
509                     TRACE("Comparing file version %d.%d.%d.%d:\n",
510                      HIWORD(info->dwFileVersionMS),
511                      LOWORD(info->dwFileVersionMS),
512                      HIWORD(info->dwFileVersionLS),
513                      LOWORD(info->dwFileVersionLS));
514                     if (info->dwFileVersionMS < sig->MinVersionMS
515                      || (info->dwFileVersionMS == sig->MinVersionMS &&
516                      info->dwFileVersionLS < sig->MinVersionLS))
517                     {
518                         TRACE("Less than minimum version %d.%d.%d.%d\n",
519                          HIWORD(sig->MinVersionMS),
520                          LOWORD(sig->MinVersionMS),
521                          HIWORD(sig->MinVersionLS),
522                          LOWORD(sig->MinVersionLS));
523                     }
524                     else if (info->dwFileVersionMS < sig->MinVersionMS
525                      || (info->dwFileVersionMS == sig->MinVersionMS &&
526                      info->dwFileVersionLS < sig->MinVersionLS))
527                     {
528                         TRACE("Greater than minimum version %d.%d.%d.%d\n",
529                          HIWORD(sig->MaxVersionMS),
530                          LOWORD(sig->MaxVersionMS),
531                          HIWORD(sig->MaxVersionLS),
532                          LOWORD(sig->MaxVersionLS));
533                     }
534                     else
535                         *matches = TRUE;
536                 }
537                 msi_free( buf);
538             }
539             else
540                 rc = ERROR_OUTOFMEMORY;
541         }
542     }
543     return rc;
544 }
545
546 /* Sets *matches to whether the file in findData matches that in sig.
547  * fullFilePath is assumed to be the full path of the file specified in
548  * findData, which may be necessary to compare the version.
549  * Return ERROR_SUCCESS in case of success (whether or not the file matches),
550  * something else if an install-halting error occurs.
551  */
552 static UINT ACTION_FileMatchesSig(const MSISIGNATURE *sig,
553  const WIN32_FIND_DATAW *findData, LPCWSTR fullFilePath, BOOL *matches)
554 {
555     UINT rc = ERROR_SUCCESS;
556
557     *matches = TRUE;
558     /* assumes the caller has already ensured the filenames match, so check
559      * the other fields..
560      */
561     if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
562     {
563         if (findData->ftCreationTime.dwHighDateTime <
564          sig->MinTime.dwHighDateTime ||
565          (findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
566          && findData->ftCreationTime.dwLowDateTime <
567          sig->MinTime.dwLowDateTime))
568             *matches = FALSE;
569     }
570     if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
571     {
572         if (findData->ftCreationTime.dwHighDateTime >
573          sig->MaxTime.dwHighDateTime ||
574          (findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
575          && findData->ftCreationTime.dwLowDateTime >
576          sig->MaxTime.dwLowDateTime))
577             *matches = FALSE;
578     }
579     if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
580         *matches = FALSE;
581     if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
582         *matches = FALSE;
583     if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
584      sig->MaxVersionMS || sig->MaxVersionLS))
585         rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
586     return rc;
587 }
588
589 /* Recursively searches the directory dir for files that match the signature
590  * sig, up to (depth + 1) levels deep.  That is, if depth is 0, it searches dir
591  * (and only dir).  If depth is 1, searches dir and its immediate
592  * subdirectories.
593  * Assumes sig->File is not NULL.
594  * Returns ERROR_SUCCESS on success (which may include non-critical errors),
595  * something else on failures which should halt the install.
596  */
597 static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, LPWSTR *appValue,
598  MSISIGNATURE *sig, LPCWSTR dir, int depth)
599 {
600     static const WCHAR starDotStarW[] = { '*','.','*',0 };
601     UINT rc = ERROR_SUCCESS;
602     size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
603     WCHAR *buf;
604
605     TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
606      debugstr_w(sig->File), depth);
607
608     if (depth < 0)
609         return ERROR_INVALID_PARAMETER;
610
611     *appValue = NULL;
612     /* We need the buffer in both paths below, so go ahead and allocate it
613      * here.  Add two because we might need to add a backslash if the dir name
614      * isn't backslash-terminated.
615      */
616     buf = msi_alloc( (dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
617     if (buf)
618     {
619         /* a depth of 0 implies we should search dir, so go ahead and search */
620         HANDLE hFind;
621         WIN32_FIND_DATAW findData;
622
623         memcpy(buf, dir, dirLen * sizeof(WCHAR));
624         if (buf[dirLen - 1] != '\\')
625             buf[dirLen++ - 1] = '\\';
626         memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
627         hFind = FindFirstFileW(buf, &findData);
628         if (hFind != INVALID_HANDLE_VALUE)
629         {
630             BOOL matches;
631
632             /* assuming Signature can't contain wildcards for the file name,
633              * so don't bother with FindNextFileW here.
634              */
635             if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
636              && matches)
637             {
638                 TRACE("found file, returning %s\n", debugstr_w(buf));
639                 *appValue = buf;
640             }
641             FindClose(hFind);
642         }
643         if (rc == ERROR_SUCCESS && !*appValue && depth > 0)
644         {
645             HANDLE hFind;
646             WIN32_FIND_DATAW findData;
647
648             memcpy(buf, dir, dirLen * sizeof(WCHAR));
649             if (buf[dirLen - 1] != '\\')
650                 buf[dirLen++ - 1] = '\\';
651             lstrcpyW(buf + dirLen, starDotStarW);
652             hFind = FindFirstFileW(buf, &findData);
653             if (hFind != INVALID_HANDLE_VALUE)
654             {
655                 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
656                     rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
657                      findData.cFileName, depth - 1);
658                 while (rc == ERROR_SUCCESS && !*appValue &&
659                  FindNextFileW(hFind, &findData) != 0)
660                 {
661                     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
662                         rc = ACTION_RecurseSearchDirectory(package, appValue,
663                          sig, findData.cFileName, depth - 1);
664                 }
665                 FindClose(hFind);
666             }
667         }
668         if (!*appValue)
669             msi_free(buf);
670     }
671     else
672         rc = ERROR_OUTOFMEMORY;
673
674     return rc;
675 }
676
677 static UINT ACTION_CheckDirectory(MSIPACKAGE *package, LPCWSTR dir,
678  LPWSTR *appValue)
679 {
680     UINT rc = ERROR_SUCCESS;
681
682     if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
683     {
684         TRACE("directory exists, returning %s\n", debugstr_w(dir));
685         *appValue = strdupW(dir);
686     }
687     return rc;
688 }
689
690 static BOOL ACTION_IsFullPath(LPCWSTR path)
691 {
692     WCHAR first = toupperW(path[0]);
693     BOOL ret;
694
695     if (first >= 'A' && first <= 'Z' && path[1] == ':')
696         ret = TRUE;
697     else if (path[0] == '\\' && path[1] == '\\')
698         ret = TRUE;
699     else
700         ret = FALSE;
701     return ret;
702 }
703
704 static UINT ACTION_SearchDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
705  LPCWSTR path, int depth, LPWSTR *appValue)
706 {
707     UINT rc;
708
709     TRACE("%p, %p, %s, %d, %p\n", package, sig, debugstr_w(path), depth,
710      appValue);
711     if (ACTION_IsFullPath(path))
712     {
713         if (sig->File)
714             rc = ACTION_RecurseSearchDirectory(package, appValue, sig,
715              path, depth);
716         else
717         {
718             /* Recursively searching a directory makes no sense when the
719              * directory to search is the thing you're trying to find.
720              */
721             rc = ACTION_CheckDirectory(package, path, appValue);
722         }
723     }
724     else
725     {
726         WCHAR pathWithDrive[MAX_PATH] = { 'C',':','\\',0 };
727         DWORD drives = GetLogicalDrives();
728         int i;
729
730         rc = ERROR_SUCCESS;
731         *appValue = NULL;
732         for (i = 0; rc == ERROR_SUCCESS && !*appValue && i < 26; i++)
733             if (drives & (1 << drives))
734             {
735                 pathWithDrive[0] = 'A' + i;
736                 if (GetDriveTypeW(pathWithDrive) == DRIVE_FIXED)
737                 {
738                     lstrcpynW(pathWithDrive + 3, path,
739                               sizeof(pathWithDrive) / sizeof(pathWithDrive[0]) - 3);
740                     if (sig->File)
741                         rc = ACTION_RecurseSearchDirectory(package, appValue,
742                          sig, pathWithDrive, depth);
743                     else
744                         rc = ACTION_CheckDirectory(package, pathWithDrive,
745                          appValue);
746                 }
747             }
748     }
749     TRACE("returning %d\n", rc);
750     return rc;
751 }
752
753 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
754  MSISIGNATURE *sig, LPWSTR *appValue);
755
756 static UINT ACTION_AppSearchDr(MSIPACKAGE *package, LPWSTR *appValue, MSISIGNATURE *sig)
757 {
758     static const WCHAR query[] =  {
759         's','e','l','e','c','t',' ','*',' ',
760         'f','r','o','m',' ',
761         'D','r','L','o','c','a','t','o','r',' ',
762         'w','h','e','r','e',' ',
763         'S','i','g','n','a','t','u','r','e','_',' ','=',' ', '\'','%','s','\'',0};
764     LPWSTR parentName = NULL, path = NULL, parent = NULL;
765     WCHAR expanded[MAX_PATH];
766     MSIRECORD *row;
767     int depth;
768     UINT rc;
769
770     TRACE("%s\n", debugstr_w(sig->Name));
771
772     *appValue = NULL;
773
774     row = MSI_QueryGetRecord( package->db, query, sig->Name );
775     if (!row)
776     {
777         TRACE("failed to query DrLocator for %s\n", debugstr_w(sig->Name));
778         return ERROR_SUCCESS;
779     }
780
781     /* check whether parent is set */
782     parentName = msi_dup_record_field(row,2);
783     if (parentName)
784     {
785         MSISIGNATURE parentSig;
786
787         rc = ACTION_AppSearchSigName(package, parentName, &parentSig, &parent);
788         ACTION_FreeSignature(&parentSig);
789         msi_free(parentName);
790     }
791     /* now look for path */
792     path = msi_dup_record_field(row,3);
793     if (MSI_RecordIsNull(row,4))
794         depth = 0;
795     else
796         depth = MSI_RecordGetInteger(row,4);
797     ACTION_ExpandAnyPath(package, path, expanded, MAX_PATH);
798     msi_free(path);
799     if (parent)
800     {
801         path = msi_alloc((strlenW(parent) + strlenW(expanded) + 1) * sizeof(WCHAR));
802         if (!path)
803         {
804             rc = ERROR_OUTOFMEMORY;
805             goto end;
806         }
807         strcpyW(path, parent);
808         strcatW(path, expanded);
809     }
810     else
811         path = expanded;
812
813     rc = ACTION_SearchDirectory(package, sig, path, depth, appValue);
814
815 end:
816     if (path != expanded)
817         msi_free(path);
818     msi_free(parent);
819     msiobj_release(&row->hdr);
820
821     TRACE("returning %d\n", rc);
822     return rc;
823 }
824
825 static UINT ACTION_AppSearchSigName(MSIPACKAGE *package, LPCWSTR sigName,
826  MSISIGNATURE *sig, LPWSTR *appValue)
827 {
828     UINT rc;
829
830     *appValue = NULL;
831     rc = ACTION_AppSearchGetSignature(package, sig, sigName);
832     if (rc == ERROR_SUCCESS)
833     {
834         rc = ACTION_AppSearchComponents(package, appValue, sig);
835         if (rc == ERROR_SUCCESS && !*appValue)
836         {
837             rc = ACTION_AppSearchReg(package, appValue, sig);
838             if (rc == ERROR_SUCCESS && !*appValue)
839             {
840                 rc = ACTION_AppSearchIni(package, appValue, sig);
841                 if (rc == ERROR_SUCCESS && !*appValue)
842                     rc = ACTION_AppSearchDr(package, appValue, sig);
843             }
844         }
845     }
846     return rc;
847 }
848
849 static UINT iterate_appsearch(MSIRECORD *row, LPVOID param)
850 {
851     MSIPACKAGE *package = param;
852     LPWSTR propName, sigName, value = NULL;
853     MSISIGNATURE sig;
854     UINT r;
855
856     /* get property and signature */
857     propName = msi_dup_record_field(row,1);
858     sigName = msi_dup_record_field(row,2);
859
860     TRACE("%s %s\n", debugstr_w(propName), debugstr_w(sigName));
861
862     r = ACTION_AppSearchSigName(package, sigName, &sig, &value);
863     if (value)
864     {
865         MSI_SetPropertyW(package, propName, value);
866         msi_free(value);
867     }
868     ACTION_FreeSignature(&sig);
869     msi_free(propName);
870     msi_free(sigName);
871
872     return r;
873 }
874
875 /* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
876  * is the best reference for the AppSearch table and how it's used.
877  */
878 UINT ACTION_AppSearch(MSIPACKAGE *package)
879 {
880     static const WCHAR query[] =  {
881         's','e','l','e','c','t',' ','*',' ',
882         'f','r','o','m',' ',
883         'A','p','p','S','e','a','r','c','h',0};
884     MSIQUERY *view = NULL;
885     UINT r;
886
887     r = MSI_OpenQuery( package->db, &view, query );
888     if (r != ERROR_SUCCESS)
889         return ERROR_SUCCESS;
890
891     r = MSI_IterateRecords( view, NULL, iterate_appsearch, package );
892     msiobj_release( &view->hdr );
893
894     return r;
895 }