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