2 * Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
4 * Copyright 2005 Juan Lang
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.
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.
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
32 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(msi
);
37 typedef struct tagMSISIGNATURE
39 LPCWSTR Name
; /* NOT owned by this structure */
52 void msi_parse_version_string(LPCWSTR verStr
, PDWORD ms
, PDWORD ls
)
55 int x1
= 0, x2
= 0, x3
= 0, x4
= 0;
57 x1
= wcstol(verStr
, NULL
, 10);
58 ptr
= wcschr(verStr
, '.');
61 x2
= wcstol(ptr
+ 1, NULL
, 10);
62 ptr
= wcschr(ptr
+ 1, '.');
66 x3
= wcstol(ptr
+ 1, NULL
, 10);
67 ptr
= wcschr(ptr
+ 1, '.');
70 x4
= wcstol(ptr
+ 1, NULL
, 10);
71 /* FIXME: byte-order dependent? */
73 if (ls
) *ls
= x3
<< 16 | x4
;
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.
83 static UINT
get_signature( MSIPACKAGE
*package
, MSISIGNATURE
*sig
, const WCHAR
*name
)
85 WCHAR
*minVersion
, *maxVersion
, *p
;
89 TRACE("package %p, sig %p\n", package
, sig
);
91 memset(sig
, 0, sizeof(*sig
));
93 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `Signature` WHERE `Signature` = '%s'", name
);
96 TRACE("failed to query signature for %s\n", debugstr_w(name
));
101 sig
->File
= msi_dup_record_field(row
,2);
102 if ((p
= wcschr(sig
->File
, '|')))
105 memmove(sig
->File
, p
, (lstrlenW(p
) + 1) * sizeof(WCHAR
));
108 minVersion
= msi_dup_record_field(row
,3);
111 msi_parse_version_string( minVersion
, &sig
->MinVersionMS
, &sig
->MinVersionLS
);
112 msi_free( minVersion
);
114 maxVersion
= msi_dup_record_field(row
,4);
117 msi_parse_version_string( maxVersion
, &sig
->MaxVersionMS
, &sig
->MaxVersionLS
);
118 msi_free( maxVersion
);
120 sig
->MinSize
= MSI_RecordGetInteger(row
,5);
121 if (sig
->MinSize
== MSI_NULL_INTEGER
)
123 sig
->MaxSize
= MSI_RecordGetInteger(row
,6);
124 if (sig
->MaxSize
== MSI_NULL_INTEGER
)
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
);
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
));
145 msiobj_release( &row
->hdr
);
147 return ERROR_SUCCESS
;
150 /* Frees any memory allocated in sig */
151 static void free_signature( MSISIGNATURE
*sig
)
154 msi_free(sig
->Languages
);
157 static WCHAR
*search_file( MSIPACKAGE
*package
, WCHAR
*path
, MSISIGNATURE
*sig
)
159 VS_FIXEDFILEINFO
*info
;
166 PathRemoveFileSpecW(path
);
167 PathAddBackslashW(path
);
169 attr
= msi_get_file_attributes( package
, path
);
170 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
))
171 return strdupW(path
);
176 attr
= msi_get_file_attributes( package
, path
);
177 if (attr
== INVALID_FILE_ATTRIBUTES
|| (attr
& FILE_ATTRIBUTE_DIRECTORY
))
180 size
= msi_get_file_version_info( package
, path
, 0, NULL
);
182 return strdupW(path
);
184 buffer
= msi_alloc(size
);
188 size
= msi_get_file_version_info( package
, path
, size
, buffer
);
192 if (!VerQueryValueW(buffer
, L
"\\", (LPVOID
)&info
, &size
) || !info
)
195 if (sig
->MinVersionLS
|| sig
->MinVersionMS
)
197 if (info
->dwFileVersionMS
< sig
->MinVersionMS
)
200 if (info
->dwFileVersionMS
== sig
->MinVersionMS
&&
201 info
->dwFileVersionLS
< sig
->MinVersionLS
)
205 if (sig
->MaxVersionLS
|| sig
->MaxVersionMS
)
207 if (info
->dwFileVersionMS
> sig
->MaxVersionMS
)
210 if (info
->dwFileVersionMS
== sig
->MaxVersionMS
&&
211 info
->dwFileVersionLS
> sig
->MaxVersionLS
)
222 static UINT
search_components( MSIPACKAGE
*package
, WCHAR
**appValue
, MSISIGNATURE
*sig
)
224 MSIRECORD
*row
, *rec
;
225 LPCWSTR signature
, guid
;
226 BOOL sigpresent
= TRUE
;
229 WCHAR path
[MAX_PATH
];
230 DWORD size
= MAX_PATH
;
234 TRACE("%s\n", debugstr_w(sig
->Name
));
238 row
= MSI_QueryGetRecord(package
->db
, L
"SELECT * FROM `CompLocator` WHERE `Signature_` = '%s'", sig
->Name
);
241 TRACE("failed to query CompLocator for %s\n", debugstr_w(sig
->Name
));
242 return ERROR_SUCCESS
;
245 signature
= MSI_RecordGetString(row
, 1);
246 guid
= MSI_RecordGetString(row
, 2);
247 type
= MSI_RecordGetInteger(row
, 3);
249 rec
= MSI_QueryGetRecord(package
->db
, L
"SELECT * FROM `Signature` WHERE `Signature` = '%s'", signature
);
254 MsiLocateComponentW(guid
, path
, &size
);
258 attr
= msi_get_file_attributes( package
, path
);
259 if (attr
== INVALID_FILE_ATTRIBUTES
)
262 isdir
= (attr
& FILE_ATTRIBUTE_DIRECTORY
);
264 if (type
!= msidbLocatorTypeDirectory
&& sigpresent
&& !isdir
)
266 *appValue
= search_file( package
, path
, sig
);
268 else if (!sigpresent
&& (type
!= msidbLocatorTypeDirectory
|| isdir
))
270 if (type
== msidbLocatorTypeFileName
)
272 ptr
= wcsrchr(path
, '\\');
276 PathAddBackslashW(path
);
278 *appValue
= strdupW(path
);
282 PathAddBackslashW(path
);
283 lstrcatW(path
, MSI_RecordGetString(rec
, 2));
285 attr
= msi_get_file_attributes( package
, path
);
286 if (attr
!= INVALID_FILE_ATTRIBUTES
&& !(attr
& FILE_ATTRIBUTE_DIRECTORY
))
287 *appValue
= strdupW(path
);
291 if (rec
) msiobj_release(&rec
->hdr
);
292 msiobj_release(&row
->hdr
);
293 return ERROR_SUCCESS
;
296 static void convert_reg_value( DWORD regType
, const BYTE
*value
, DWORD sz
, WCHAR
**appValue
)
304 if (*(LPCWSTR
)value
== '#')
306 /* escape leading pound with another */
307 *appValue
= msi_alloc(sz
+ sizeof(WCHAR
));
308 (*appValue
)[0] = '#';
309 lstrcpyW(*appValue
+ 1, (LPCWSTR
)value
);
313 *appValue
= msi_alloc(sz
);
314 lstrcpyW(*appValue
, (LPCWSTR
)value
);
318 /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
321 *appValue
= msi_alloc(10 * sizeof(WCHAR
));
322 swprintf(*appValue
, 10, L
"#%d", *(const DWORD
*)value
);
325 sz
= ExpandEnvironmentStringsW((LPCWSTR
)value
, NULL
, 0);
326 *appValue
= msi_alloc(sz
* sizeof(WCHAR
));
327 ExpandEnvironmentStringsW((LPCWSTR
)value
, *appValue
, sz
);
331 *appValue
= msi_alloc((sz
* 2 + 3) * sizeof(WCHAR
));
332 lstrcpyW(*appValue
, L
"#x");
333 ptr
= *appValue
+ lstrlenW(L
"#x");
334 for (i
= 0; i
< sz
; i
++, ptr
+= 2)
335 swprintf(ptr
, 3, L
"%02X", value
[i
]);
338 WARN("unimplemented for values of type %d\n", regType
);
343 static UINT
search_directory( MSIPACKAGE
*, MSISIGNATURE
*, const WCHAR
*, int, WCHAR
** );
345 static UINT
search_reg( MSIPACKAGE
*package
, WCHAR
**appValue
, MSISIGNATURE
*sig
)
347 const WCHAR
*keyPath
, *valueName
;
348 WCHAR
*deformatted
= NULL
, *ptr
= NULL
, *end
;
350 REGSAM access
= KEY_READ
;
351 HKEY rootKey
, key
= NULL
;
352 DWORD sz
= 0, regType
;
357 TRACE("%s\n", debugstr_w(sig
->Name
));
361 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `RegLocator` WHERE `Signature_` = '%s'", sig
->Name
);
364 TRACE("failed to query RegLocator for %s\n", debugstr_w(sig
->Name
));
365 return ERROR_SUCCESS
;
368 root
= MSI_RecordGetInteger(row
, 2);
369 keyPath
= MSI_RecordGetString(row
, 3);
370 valueName
= MSI_RecordGetString(row
, 4);
371 type
= MSI_RecordGetInteger(row
, 5);
373 deformat_string(package
, keyPath
, &deformatted
);
377 case msidbRegistryRootClassesRoot
:
378 rootKey
= HKEY_CLASSES_ROOT
;
380 case msidbRegistryRootCurrentUser
:
381 rootKey
= HKEY_CURRENT_USER
;
383 case msidbRegistryRootLocalMachine
:
384 rootKey
= HKEY_LOCAL_MACHINE
;
385 if (type
& msidbLocatorType64bit
) access
|= KEY_WOW64_64KEY
;
386 else access
|= KEY_WOW64_32KEY
;
388 case msidbRegistryRootUsers
:
389 rootKey
= HKEY_USERS
;
392 WARN("Unknown root key %d\n", root
);
396 rc
= RegOpenKeyExW( rootKey
, deformatted
, 0, access
, &key
);
399 TRACE("RegOpenKeyExW returned %d\n", rc
);
403 msi_free(deformatted
);
404 deformat_string(package
, valueName
, &deformatted
);
406 rc
= RegQueryValueExW(key
, deformatted
, NULL
, NULL
, NULL
, &sz
);
409 TRACE("RegQueryValueExW returned %d\n", rc
);
412 /* FIXME: sanity-check sz before allocating (is there an upper-limit
413 * on the value of a property?)
415 value
= msi_alloc( sz
);
416 rc
= RegQueryValueExW(key
, deformatted
, NULL
, ®Type
, value
, &sz
);
419 TRACE("RegQueryValueExW returned %d\n", rc
);
423 /* bail out if the registry key is empty */
427 /* expand if needed */
428 if (regType
== REG_EXPAND_SZ
)
430 sz
= ExpandEnvironmentStringsW((LPCWSTR
)value
, NULL
, 0);
433 LPWSTR buf
= msi_alloc(sz
* sizeof(WCHAR
));
434 ExpandEnvironmentStringsW((LPCWSTR
)value
, buf
, sz
);
440 if ((regType
== REG_SZ
|| regType
== REG_EXPAND_SZ
) &&
441 (ptr
= wcschr((LPWSTR
)value
, '"')) && (end
= wcschr(++ptr
, '"')))
448 case msidbLocatorTypeDirectory
:
449 search_directory( package
, sig
, ptr
, 0, appValue
);
451 case msidbLocatorTypeFileName
:
452 *appValue
= search_file( package
, ptr
, sig
);
454 case msidbLocatorTypeRawValue
:
455 convert_reg_value( regType
, value
, sz
, appValue
);
458 FIXME("unimplemented for type %d (key path %s, value %s)\n",
459 type
, debugstr_w(keyPath
), debugstr_w(valueName
));
464 msi_free( deformatted
);
466 msiobj_release(&row
->hdr
);
467 return ERROR_SUCCESS
;
470 static LPWSTR
get_ini_field(LPWSTR buf
, int field
)
479 while ((end
= wcschr(beg
, ',')) && i
< field
)
488 end
= wcschr(beg
, ',');
490 end
= beg
+ lstrlenW(beg
);
496 static UINT
search_ini( MSIPACKAGE
*package
, WCHAR
**appValue
, MSISIGNATURE
*sig
)
499 LPWSTR fileName
, section
, key
;
503 TRACE("%s\n", debugstr_w(sig
->Name
));
507 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `IniLocator` WHERE `Signature_` = '%s'", sig
->Name
);
510 TRACE("failed to query IniLocator for %s\n", debugstr_w(sig
->Name
));
511 return ERROR_SUCCESS
;
514 fileName
= msi_dup_record_field(row
, 2);
515 section
= msi_dup_record_field(row
, 3);
516 key
= msi_dup_record_field(row
, 4);
517 field
= MSI_RecordGetInteger(row
, 5);
518 type
= MSI_RecordGetInteger(row
, 6);
519 if (field
== MSI_NULL_INTEGER
)
521 if (type
== MSI_NULL_INTEGER
)
524 GetPrivateProfileStringW(section
, key
, NULL
, buf
, MAX_PATH
, fileName
);
529 case msidbLocatorTypeDirectory
:
530 search_directory( package
, sig
, buf
, 0, appValue
);
532 case msidbLocatorTypeFileName
:
533 *appValue
= search_file( package
, buf
, sig
);
535 case msidbLocatorTypeRawValue
:
536 *appValue
= get_ini_field(buf
, field
);
545 msiobj_release(&row
->hdr
);
547 return ERROR_SUCCESS
;
550 /* Expands the value in src into a path without property names and only
551 * containing long path names into dst. Replaces at most len characters of dst,
552 * and always NULL-terminates dst if dst is not NULL and len >= 1.
554 * Assumes src and dst are non-overlapping.
555 * FIXME: return code probably needed:
556 * - what does AppSearch return if the table values are invalid?
557 * - what if dst is too small?
559 static void expand_any_path( MSIPACKAGE
*package
, WCHAR
*src
, WCHAR
*dst
, size_t len
)
561 WCHAR
*ptr
, *deformatted
;
563 if (!src
|| !dst
|| !len
)
565 if (dst
) *dst
= '\0';
571 /* Ignore the short portion of the path */
572 if ((ptr
= wcschr(src
, '|')))
577 deformat_string(package
, ptr
, &deformatted
);
578 if (!deformatted
|| lstrlenW(deformatted
) > len
- 1)
580 msi_free(deformatted
);
584 lstrcpyW(dst
, deformatted
);
585 dst
[lstrlenW(deformatted
)] = '\0';
586 msi_free(deformatted
);
589 static LANGID
*parse_languages( const WCHAR
*languages
, DWORD
*num_ids
)
592 WCHAR
*str
= strdupW( languages
), *p
, *q
;
595 if (!str
) return NULL
;
596 for (p
= q
= str
; (q
= wcschr( q
, ',' )); q
++) count
++;
598 if (!(ret
= msi_alloc( count
* sizeof(LANGID
) )))
606 q
= wcschr( p
, ',' );
608 ret
[i
] = wcstol( p
, NULL
, 10 );
618 static BOOL
match_languages( const void *version
, const WCHAR
*languages
)
625 DWORD len
, num_ids
, i
, j
;
629 if (!languages
|| !languages
[0]) return TRUE
;
630 if (!VerQueryValueW( version
, L
"\\VarFileInfo\\Translation", (void **)&lang
, &len
)) return FALSE
;
631 if (!(ids
= parse_languages( languages
, &num_ids
))) return FALSE
;
633 for (i
= 0; i
< num_ids
; i
++)
636 for (j
= 0; j
< len
/ sizeof(struct lang
); j
++)
638 if (!ids
[i
] || ids
[i
] == lang
[j
].id
) found
= TRUE
;
640 if (!found
) goto done
;
648 /* Sets *matches to whether the file (whose path is filePath) matches the
649 * versions set in sig.
650 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
651 * something else if an install-halting error occurs.
653 static UINT
file_version_matches( MSIPACKAGE
*package
, const MSISIGNATURE
*sig
, const WCHAR
*filePath
,
658 VS_FIXEDFILEINFO
*info
= NULL
;
659 DWORD size
= msi_get_file_version_info( package
, filePath
, 0, NULL
);
663 if (!size
) return ERROR_SUCCESS
;
664 if (!(version
= msi_alloc( size
))) return ERROR_OUTOFMEMORY
;
666 if (msi_get_file_version_info( package
, filePath
, size
, version
))
667 VerQueryValueW( version
, L
"\\", (void **)&info
, &len
);
671 TRACE("comparing file version %d.%d.%d.%d:\n",
672 HIWORD(info
->dwFileVersionMS
),
673 LOWORD(info
->dwFileVersionMS
),
674 HIWORD(info
->dwFileVersionLS
),
675 LOWORD(info
->dwFileVersionLS
));
676 if (info
->dwFileVersionMS
< sig
->MinVersionMS
677 || (info
->dwFileVersionMS
== sig
->MinVersionMS
&&
678 info
->dwFileVersionLS
< sig
->MinVersionLS
))
680 TRACE("less than minimum version %d.%d.%d.%d\n",
681 HIWORD(sig
->MinVersionMS
),
682 LOWORD(sig
->MinVersionMS
),
683 HIWORD(sig
->MinVersionLS
),
684 LOWORD(sig
->MinVersionLS
));
686 else if ((sig
->MaxVersionMS
|| sig
->MaxVersionLS
) &&
687 (info
->dwFileVersionMS
> sig
->MaxVersionMS
||
688 (info
->dwFileVersionMS
== sig
->MaxVersionMS
&&
689 info
->dwFileVersionLS
> sig
->MaxVersionLS
)))
691 TRACE("greater than maximum version %d.%d.%d.%d\n",
692 HIWORD(sig
->MaxVersionMS
),
693 LOWORD(sig
->MaxVersionMS
),
694 HIWORD(sig
->MaxVersionLS
),
695 LOWORD(sig
->MaxVersionLS
));
697 else if (!match_languages( version
, sig
->Languages
))
699 TRACE("languages %s not supported\n", debugstr_w( sig
->Languages
));
701 else *matches
= TRUE
;
704 return ERROR_SUCCESS
;
707 /* Sets *matches to whether the file in findData matches that in sig.
708 * fullFilePath is assumed to be the full path of the file specified in
709 * findData, which may be necessary to compare the version.
710 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
711 * something else if an install-halting error occurs.
713 static UINT
file_matches_sig( MSIPACKAGE
*package
, const MSISIGNATURE
*sig
, const WIN32_FIND_DATAW
*findData
,
714 const WCHAR
*fullFilePath
, BOOL
*matches
)
716 UINT rc
= ERROR_SUCCESS
;
719 /* assumes the caller has already ensured the filenames match, so check
722 if (sig
->MinTime
.dwLowDateTime
|| sig
->MinTime
.dwHighDateTime
)
724 if (findData
->ftCreationTime
.dwHighDateTime
<
725 sig
->MinTime
.dwHighDateTime
||
726 (findData
->ftCreationTime
.dwHighDateTime
== sig
->MinTime
.dwHighDateTime
727 && findData
->ftCreationTime
.dwLowDateTime
<
728 sig
->MinTime
.dwLowDateTime
))
731 if (*matches
&& (sig
->MaxTime
.dwLowDateTime
|| sig
->MaxTime
.dwHighDateTime
))
733 if (findData
->ftCreationTime
.dwHighDateTime
>
734 sig
->MaxTime
.dwHighDateTime
||
735 (findData
->ftCreationTime
.dwHighDateTime
== sig
->MaxTime
.dwHighDateTime
736 && findData
->ftCreationTime
.dwLowDateTime
>
737 sig
->MaxTime
.dwLowDateTime
))
740 if (*matches
&& sig
->MinSize
&& findData
->nFileSizeLow
< sig
->MinSize
)
742 if (*matches
&& sig
->MaxSize
&& findData
->nFileSizeLow
> sig
->MaxSize
)
744 if (*matches
&& (sig
->MinVersionMS
|| sig
->MinVersionLS
||
745 sig
->MaxVersionMS
|| sig
->MaxVersionLS
))
746 rc
= file_version_matches( package
, sig
, fullFilePath
, matches
);
750 /* Recursively searches the directory dir for files that match the signature
751 * sig, up to (depth + 1) levels deep. That is, if depth is 0, it searches dir
752 * (and only dir). If depth is 1, searches dir and its immediate
754 * Assumes sig->File is not NULL.
755 * Returns ERROR_SUCCESS on success (which may include non-critical errors),
756 * something else on failures which should halt the install.
758 static UINT
recurse_search_directory( MSIPACKAGE
*package
, WCHAR
**appValue
, MSISIGNATURE
*sig
, const WCHAR
*dir
,
762 WIN32_FIND_DATAW findData
;
763 UINT rc
= ERROR_SUCCESS
;
764 size_t dirLen
= lstrlenW(dir
), fileLen
= lstrlenW(sig
->File
);
765 WCHAR subpath
[MAX_PATH
];
769 TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir
), debugstr_w(sig
->File
), depth
);
772 return ERROR_SUCCESS
;
775 /* We need the buffer in both paths below, so go ahead and allocate it
776 * here. Add two because we might need to add a backslash if the dir name
777 * isn't backslash-terminated.
779 len
= dirLen
+ max(fileLen
, lstrlenW(L
"*.*")) + 2;
780 buf
= msi_alloc(len
* sizeof(WCHAR
));
782 return ERROR_OUTOFMEMORY
;
785 PathAddBackslashW(buf
);
786 lstrcatW(buf
, sig
->File
);
788 hFind
= msi_find_first_file( package
, buf
, &findData
);
789 if (hFind
!= INVALID_HANDLE_VALUE
)
791 if (!(findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
795 rc
= file_matches_sig( package
, sig
, &findData
, buf
, &matches
);
796 if (rc
== ERROR_SUCCESS
&& matches
)
798 TRACE("found file, returning %s\n", debugstr_w(buf
));
805 if (rc
== ERROR_SUCCESS
&& !*appValue
)
808 PathAddBackslashW(buf
);
809 lstrcatW(buf
, L
"*.*");
811 hFind
= msi_find_first_file( package
, buf
, &findData
);
812 if (hFind
!= INVALID_HANDLE_VALUE
)
814 if (findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
&&
815 wcscmp( findData
.cFileName
, L
"." ) &&
816 wcscmp( findData
.cFileName
, L
".." ))
818 lstrcpyW(subpath
, dir
);
819 PathAppendW(subpath
, findData
.cFileName
);
820 rc
= recurse_search_directory( package
, appValue
, sig
, subpath
, depth
- 1 );
823 while (rc
== ERROR_SUCCESS
&& !*appValue
&& msi_find_next_file( package
, hFind
, &findData
))
825 if (!wcscmp( findData
.cFileName
, L
"." ) ||
826 !wcscmp( findData
.cFileName
, L
".." ))
829 lstrcpyW(subpath
, dir
);
830 PathAppendW(subpath
, findData
.cFileName
);
831 if (findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
832 rc
= recurse_search_directory( package
, appValue
, sig
, subpath
, depth
- 1 );
839 if (*appValue
!= buf
)
845 static UINT
check_directory( MSIPACKAGE
*package
, const WCHAR
*dir
, WCHAR
**appValue
)
847 DWORD attr
= msi_get_file_attributes( package
, dir
);
849 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
))
851 TRACE("directory exists, returning %s\n", debugstr_w(dir
));
852 *appValue
= strdupW(dir
);
855 return ERROR_SUCCESS
;
858 static BOOL
is_full_path( const WCHAR
*path
)
860 WCHAR first
= towupper(path
[0]);
863 if (first
>= 'A' && first
<= 'Z' && path
[1] == ':')
865 else if (path
[0] == '\\' && path
[1] == '\\')
872 static UINT
search_directory( MSIPACKAGE
*package
, MSISIGNATURE
*sig
, const WCHAR
*path
, int depth
, WCHAR
**appValue
)
878 TRACE("%p, %p, %s, %d, %p\n", package
, sig
, debugstr_w(path
), depth
, appValue
);
880 if (is_full_path( path
))
883 rc
= recurse_search_directory( package
, &val
, sig
, path
, depth
);
886 /* Recursively searching a directory makes no sense when the
887 * directory to search is the thing you're trying to find.
889 rc
= check_directory( package
, path
, &val
);
894 WCHAR pathWithDrive
[MAX_PATH
] = { 'C',':','\\',0 };
895 DWORD drives
= GetLogicalDrives();
899 for (i
= 0; rc
== ERROR_SUCCESS
&& !val
&& i
< 26; i
++)
901 if (!(drives
& (1 << i
)))
904 pathWithDrive
[0] = 'A' + i
;
905 if (GetDriveTypeW(pathWithDrive
) != DRIVE_FIXED
)
908 lstrcpynW(pathWithDrive
+ 3, path
, ARRAY_SIZE(pathWithDrive
) - 3);
911 rc
= recurse_search_directory( package
, &val
, sig
, pathWithDrive
, depth
);
913 rc
= check_directory( package
, pathWithDrive
, &val
);
917 attr
= msi_get_file_attributes( package
, val
);
918 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
) &&
919 val
&& val
[lstrlenW(val
) - 1] != '\\')
921 val
= msi_realloc(val
, (lstrlenW(val
) + 2) * sizeof(WCHAR
));
923 rc
= ERROR_OUTOFMEMORY
;
925 PathAddBackslashW(val
);
930 TRACE("returning %d\n", rc
);
934 static UINT
search_sig_name( MSIPACKAGE
*, const WCHAR
*, MSISIGNATURE
*, WCHAR
** );
936 static UINT
search_dr( MSIPACKAGE
*package
, WCHAR
**appValue
, MSISIGNATURE
*sig
)
938 LPWSTR parent
= NULL
;
940 WCHAR path
[MAX_PATH
];
941 WCHAR expanded
[MAX_PATH
];
947 TRACE("%s\n", debugstr_w(sig
->Name
));
951 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `DrLocator` WHERE `Signature_` = '%s'", sig
->Name
);
954 TRACE("failed to query DrLocator for %s\n", debugstr_w(sig
->Name
));
955 return ERROR_SUCCESS
;
958 /* check whether parent is set */
959 parentName
= MSI_RecordGetString(row
, 2);
962 MSISIGNATURE parentSig
;
964 search_sig_name( package
, parentName
, &parentSig
, &parent
);
965 free_signature( &parentSig
);
968 msiobj_release(&row
->hdr
);
969 return ERROR_SUCCESS
;
974 MSI_RecordGetStringW(row
, 3, path
, &sz
);
976 if (MSI_RecordIsNull(row
,4))
979 depth
= MSI_RecordGetInteger(row
,4);
982 expand_any_path( package
, path
, expanded
, MAX_PATH
);
984 lstrcpyW(expanded
, path
);
988 attr
= msi_get_file_attributes( package
, parent
);
989 if (attr
!= INVALID_FILE_ATTRIBUTES
&&
990 !(attr
& FILE_ATTRIBUTE_DIRECTORY
))
992 PathRemoveFileSpecW(parent
);
993 PathAddBackslashW(parent
);
996 lstrcpyW(path
, parent
);
997 lstrcatW(path
, expanded
);
999 else if (sz
) lstrcpyW(path
, expanded
);
1001 PathAddBackslashW(path
);
1003 rc
= search_directory( package
, sig
, path
, depth
, appValue
);
1006 msiobj_release(&row
->hdr
);
1007 TRACE("returning %d\n", rc
);
1011 static UINT
search_sig_name( MSIPACKAGE
*package
, const WCHAR
*sigName
, MSISIGNATURE
*sig
, WCHAR
**appValue
)
1016 rc
= get_signature( package
, sig
, sigName
);
1017 if (rc
== ERROR_SUCCESS
)
1019 rc
= search_components( package
, appValue
, sig
);
1020 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1022 rc
= search_reg( package
, appValue
, sig
);
1023 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1025 rc
= search_ini( package
, appValue
, sig
);
1026 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1027 rc
= search_dr( package
, appValue
, sig
);
1034 static UINT
ITERATE_AppSearch(MSIRECORD
*row
, LPVOID param
)
1036 MSIPACKAGE
*package
= param
;
1037 LPCWSTR propName
, sigName
;
1038 LPWSTR value
= NULL
;
1043 /* get property and signature */
1044 propName
= MSI_RecordGetString(row
, 1);
1045 sigName
= MSI_RecordGetString(row
, 2);
1047 TRACE("%s %s\n", debugstr_w(propName
), debugstr_w(sigName
));
1049 r
= search_sig_name( package
, sigName
, &sig
, &value
);
1052 r
= msi_set_property( package
->db
, propName
, value
, -1 );
1053 if (r
== ERROR_SUCCESS
&& !wcscmp( propName
, L
"SourceDir" ))
1054 msi_reset_source_folders( package
);
1058 free_signature( &sig
);
1060 uirow
= MSI_CreateRecord( 2 );
1061 MSI_RecordSetStringW( uirow
, 1, propName
);
1062 MSI_RecordSetStringW( uirow
, 2, sigName
);
1063 MSI_ProcessMessage(package
, INSTALLMESSAGE_ACTIONDATA
, uirow
);
1064 msiobj_release( &uirow
->hdr
);
1069 UINT
ACTION_AppSearch(MSIPACKAGE
*package
)
1074 if (msi_action_is_unique(package
, L
"AppSearch"))
1076 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1077 return ERROR_SUCCESS
;
1080 msi_register_unique_action(package
, L
"AppSearch");
1082 r
= MSI_OpenQuery( package
->db
, &view
, L
"SELECT * FROM `AppSearch`" );
1083 if (r
!= ERROR_SUCCESS
)
1084 return ERROR_SUCCESS
;
1086 r
= MSI_IterateRecords( view
, NULL
, ITERATE_AppSearch
, package
);
1087 msiobj_release( &view
->hdr
);
1091 static UINT
ITERATE_CCPSearch(MSIRECORD
*row
, LPVOID param
)
1093 MSIPACKAGE
*package
= param
;
1095 LPWSTR value
= NULL
;
1097 UINT r
= ERROR_SUCCESS
;
1099 signature
= MSI_RecordGetString(row
, 1);
1101 TRACE("%s\n", debugstr_w(signature
));
1103 search_sig_name( package
, signature
, &sig
, &value
);
1106 TRACE("Found signature %s\n", debugstr_w(signature
));
1107 msi_set_property( package
->db
, L
"CCP_Success", L
"1", -1 );
1109 r
= ERROR_NO_MORE_ITEMS
;
1112 free_signature(&sig
);
1116 UINT
ACTION_CCPSearch(MSIPACKAGE
*package
)
1121 if (msi_action_is_unique(package
, L
"CCPSearch"))
1123 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1124 return ERROR_SUCCESS
;
1127 msi_register_unique_action(package
, L
"CCPSearch");
1129 r
= MSI_OpenQuery(package
->db
, &view
, L
"SELECT * FROM `CCPSearch`");
1130 if (r
!= ERROR_SUCCESS
)
1131 return ERROR_SUCCESS
;
1133 r
= MSI_IterateRecords(view
, NULL
, ITERATE_CCPSearch
, package
);
1134 msiobj_release(&view
->hdr
);