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
);
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
, struct signature
*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
);
114 maxVersion
= msi_dup_record_field(row
,4);
117 msi_parse_version_string( maxVersion
, &sig
->MaxVersionMS
, &sig
->MaxVersionLS
);
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 %lu, MaxSize is %lu\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( struct signature
*sig
)
154 free(sig
->Languages
);
157 static WCHAR
*search_file( MSIPACKAGE
*package
, WCHAR
*path
, struct signature
*sig
)
159 VS_FIXEDFILEINFO
*info
;
167 PathRemoveFileSpecW(path
);
168 PathAddBackslashW(path
);
170 attr
= msi_get_file_attributes( package
, path
);
171 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
))
177 attr
= msi_get_file_attributes( package
, path
);
178 if (attr
== INVALID_FILE_ATTRIBUTES
|| (attr
& FILE_ATTRIBUTE_DIRECTORY
))
181 size
= msi_get_file_version_info( package
, path
, 0, NULL
);
185 buffer
= malloc(size
);
189 size
= msi_get_file_version_info( package
, path
, size
, buffer
);
193 if (!VerQueryValueW(buffer
, L
"\\", (LPVOID
)&info
, &size
) || !info
)
196 if (sig
->MinVersionLS
|| sig
->MinVersionMS
)
198 if (info
->dwFileVersionMS
< sig
->MinVersionMS
)
201 if (info
->dwFileVersionMS
== sig
->MinVersionMS
&&
202 info
->dwFileVersionLS
< sig
->MinVersionLS
)
206 if (sig
->MaxVersionLS
|| sig
->MaxVersionMS
)
208 if (info
->dwFileVersionMS
> sig
->MaxVersionMS
)
211 if (info
->dwFileVersionMS
== sig
->MaxVersionMS
&&
212 info
->dwFileVersionLS
> sig
->MaxVersionLS
)
223 static UINT
search_components( MSIPACKAGE
*package
, WCHAR
**appValue
, struct signature
*sig
)
225 MSIRECORD
*row
, *rec
;
226 LPCWSTR signature
, guid
;
227 BOOL sigpresent
= TRUE
;
230 WCHAR path
[MAX_PATH
];
231 DWORD size
= MAX_PATH
;
235 TRACE("%s\n", debugstr_w(sig
->Name
));
239 row
= MSI_QueryGetRecord(package
->db
, L
"SELECT * FROM `CompLocator` WHERE `Signature_` = '%s'", sig
->Name
);
242 TRACE("failed to query CompLocator for %s\n", debugstr_w(sig
->Name
));
243 return ERROR_SUCCESS
;
246 signature
= MSI_RecordGetString(row
, 1);
247 guid
= MSI_RecordGetString(row
, 2);
248 type
= MSI_RecordGetInteger(row
, 3);
250 rec
= MSI_QueryGetRecord(package
->db
, L
"SELECT * FROM `Signature` WHERE `Signature` = '%s'", signature
);
255 MsiLocateComponentW(guid
, path
, &size
);
259 attr
= msi_get_file_attributes( package
, path
);
260 if (attr
== INVALID_FILE_ATTRIBUTES
)
263 isdir
= (attr
& FILE_ATTRIBUTE_DIRECTORY
);
265 if (type
!= msidbLocatorTypeDirectory
&& sigpresent
&& !isdir
)
267 *appValue
= search_file( package
, path
, sig
);
269 else if (!sigpresent
&& (type
!= msidbLocatorTypeDirectory
|| isdir
))
271 if (type
== msidbLocatorTypeFileName
)
273 ptr
= wcsrchr(path
, '\\');
277 PathAddBackslashW(path
);
279 *appValue
= wcsdup(path
);
283 PathAddBackslashW(path
);
284 lstrcatW(path
, MSI_RecordGetString(rec
, 2));
286 attr
= msi_get_file_attributes( package
, path
);
287 if (attr
!= INVALID_FILE_ATTRIBUTES
&& !(attr
& FILE_ATTRIBUTE_DIRECTORY
))
288 *appValue
= wcsdup(path
);
292 if (rec
) msiobj_release(&rec
->hdr
);
293 msiobj_release(&row
->hdr
);
294 return ERROR_SUCCESS
;
297 static void convert_reg_value( DWORD regType
, const BYTE
*value
, DWORD sz
, WCHAR
**appValue
)
305 if (*(LPCWSTR
)value
== '#')
307 /* escape leading pound with another */
308 *appValue
= malloc(sz
+ sizeof(WCHAR
));
309 (*appValue
)[0] = '#';
310 lstrcpyW(*appValue
+ 1, (LPCWSTR
)value
);
314 *appValue
= malloc(sz
);
315 lstrcpyW(*appValue
, (LPCWSTR
)value
);
319 /* 7 chars for digits, 1 for NULL, 1 for #, and 1 for sign
322 *appValue
= malloc(10 * sizeof(WCHAR
));
323 swprintf(*appValue
, 10, L
"#%d", *(const DWORD
*)value
);
326 sz
= ExpandEnvironmentStringsW((LPCWSTR
)value
, NULL
, 0);
327 *appValue
= malloc(sz
* sizeof(WCHAR
));
328 ExpandEnvironmentStringsW((LPCWSTR
)value
, *appValue
, sz
);
332 *appValue
= malloc((sz
* 2 + 3) * sizeof(WCHAR
));
333 lstrcpyW(*appValue
, L
"#x");
334 ptr
= *appValue
+ lstrlenW(L
"#x");
335 for (i
= 0; i
< sz
; i
++, ptr
+= 2)
336 swprintf(ptr
, 3, L
"%02X", value
[i
]);
339 WARN( "unimplemented for values of type %lu\n", regType
);
344 static UINT
search_directory( MSIPACKAGE
*, struct signature
*, const WCHAR
*, int, WCHAR
** );
346 static UINT
search_reg( MSIPACKAGE
*package
, WCHAR
**appValue
, struct signature
*sig
)
348 const WCHAR
*keyPath
, *valueName
;
349 WCHAR
*deformatted
= NULL
, *ptr
= NULL
, *end
;
351 REGSAM access
= KEY_READ
;
352 HKEY rootKey
, key
= NULL
;
353 DWORD sz
= 0, regType
;
358 TRACE("%s\n", debugstr_w(sig
->Name
));
362 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `RegLocator` WHERE `Signature_` = '%s'", sig
->Name
);
365 TRACE("failed to query RegLocator for %s\n", debugstr_w(sig
->Name
));
366 return ERROR_SUCCESS
;
369 root
= MSI_RecordGetInteger(row
, 2);
370 keyPath
= MSI_RecordGetString(row
, 3);
371 valueName
= MSI_RecordGetString(row
, 4);
372 type
= MSI_RecordGetInteger(row
, 5);
374 deformat_string(package
, keyPath
, &deformatted
);
378 case msidbRegistryRootClassesRoot
:
379 rootKey
= HKEY_CLASSES_ROOT
;
381 case msidbRegistryRootCurrentUser
:
382 rootKey
= HKEY_CURRENT_USER
;
384 case msidbRegistryRootLocalMachine
:
385 rootKey
= HKEY_LOCAL_MACHINE
;
386 if (type
& msidbLocatorType64bit
) access
|= KEY_WOW64_64KEY
;
387 else access
|= KEY_WOW64_32KEY
;
389 case msidbRegistryRootUsers
:
390 rootKey
= HKEY_USERS
;
393 WARN("Unknown root key %d\n", root
);
397 rc
= RegOpenKeyExW( rootKey
, deformatted
, 0, access
, &key
);
400 TRACE("RegOpenKeyExW returned %d\n", rc
);
405 deformat_string(package
, valueName
, &deformatted
);
407 rc
= RegQueryValueExW(key
, deformatted
, NULL
, NULL
, NULL
, &sz
);
410 TRACE("RegQueryValueExW returned %d\n", rc
);
413 /* FIXME: sanity-check sz before allocating (is there an upper-limit
414 * on the value of a property?)
417 rc
= RegQueryValueExW(key
, deformatted
, NULL
, ®Type
, value
, &sz
);
420 TRACE("RegQueryValueExW returned %d\n", rc
);
424 /* bail out if the registry key is empty */
428 /* expand if needed */
429 if (regType
== REG_EXPAND_SZ
)
431 sz
= ExpandEnvironmentStringsW((LPCWSTR
)value
, NULL
, 0);
434 WCHAR
*buf
= malloc(sz
* sizeof(WCHAR
));
435 ExpandEnvironmentStringsW((LPCWSTR
)value
, buf
, sz
);
441 if ((regType
== REG_SZ
|| regType
== REG_EXPAND_SZ
) &&
442 (ptr
= wcschr((LPWSTR
)value
, '"')) && (end
= wcschr(++ptr
, '"')))
449 case msidbLocatorTypeDirectory
:
450 search_directory( package
, sig
, ptr
, 0, appValue
);
452 case msidbLocatorTypeFileName
:
453 *appValue
= search_file( package
, ptr
, sig
);
455 case msidbLocatorTypeRawValue
:
456 convert_reg_value( regType
, value
, sz
, appValue
);
459 FIXME("unimplemented for type %d (key path %s, value %s)\n",
460 type
, debugstr_w(keyPath
), debugstr_w(valueName
));
467 msiobj_release(&row
->hdr
);
468 return ERROR_SUCCESS
;
471 static LPWSTR
get_ini_field(LPWSTR buf
, int field
)
480 while ((end
= wcschr(beg
, ',')) && i
< field
)
489 end
= wcschr(beg
, ',');
491 end
= beg
+ lstrlenW(beg
);
497 static UINT
search_ini( MSIPACKAGE
*package
, WCHAR
**appValue
, struct signature
*sig
)
500 LPWSTR fileName
, section
, key
;
504 TRACE("%s\n", debugstr_w(sig
->Name
));
508 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `IniLocator` WHERE `Signature_` = '%s'", sig
->Name
);
511 TRACE("failed to query IniLocator for %s\n", debugstr_w(sig
->Name
));
512 return ERROR_SUCCESS
;
515 fileName
= msi_dup_record_field(row
, 2);
516 section
= msi_dup_record_field(row
, 3);
517 key
= msi_dup_record_field(row
, 4);
518 field
= MSI_RecordGetInteger(row
, 5);
519 type
= MSI_RecordGetInteger(row
, 6);
520 if (field
== MSI_NULL_INTEGER
)
522 if (type
== MSI_NULL_INTEGER
)
525 GetPrivateProfileStringW(section
, key
, NULL
, buf
, MAX_PATH
, fileName
);
530 case msidbLocatorTypeDirectory
:
531 search_directory( package
, sig
, buf
, 0, appValue
);
533 case msidbLocatorTypeFileName
:
534 *appValue
= search_file( package
, buf
, sig
);
536 case msidbLocatorTypeRawValue
:
537 *appValue
= get_ini_field(buf
, field
);
546 msiobj_release(&row
->hdr
);
548 return ERROR_SUCCESS
;
551 /* Expands the value in src into a path without property names and only
552 * containing long path names into dst. Replaces at most len characters of dst,
553 * and always NULL-terminates dst if dst is not NULL and len >= 1.
555 * Assumes src and dst are non-overlapping.
556 * FIXME: return code probably needed:
557 * - what does AppSearch return if the table values are invalid?
558 * - what if dst is too small?
560 static void expand_any_path( MSIPACKAGE
*package
, WCHAR
*src
, WCHAR
*dst
, size_t len
)
562 WCHAR
*ptr
, *deformatted
;
564 if (!src
|| !dst
|| !len
)
566 if (dst
) *dst
= '\0';
572 /* Ignore the short portion of the path */
573 if ((ptr
= wcschr(src
, '|')))
578 deformat_string(package
, ptr
, &deformatted
);
579 if (!deformatted
|| lstrlenW(deformatted
) > len
- 1)
585 lstrcpyW(dst
, deformatted
);
586 dst
[lstrlenW(deformatted
)] = '\0';
590 static LANGID
*parse_languages( const WCHAR
*languages
, DWORD
*num_ids
)
593 WCHAR
*str
= wcsdup( languages
), *p
, *q
;
596 if (!str
) return NULL
;
597 for (p
= q
= str
; (q
= wcschr( q
, ',' )); q
++) count
++;
599 if (!(ret
= malloc( count
* sizeof(LANGID
) )))
607 q
= wcschr( p
, ',' );
609 ret
[i
] = wcstol( p
, NULL
, 10 );
619 static BOOL
match_languages( const void *version
, const WCHAR
*languages
)
631 if (!languages
|| !languages
[0]) return TRUE
;
632 if (!VerQueryValueW( version
, L
"\\VarFileInfo\\Translation", (void **)&lang
, &len
)) return FALSE
;
633 if (!(ids
= parse_languages( languages
, &num_ids
))) return FALSE
;
635 for (i
= 0; i
< num_ids
; i
++)
638 for (j
= 0; j
< len
/ sizeof(struct lang
); j
++)
640 if (!ids
[i
] || ids
[i
] == lang
[j
].id
) found
= TRUE
;
642 if (!found
) goto done
;
650 /* Sets *matches to whether the file (whose path is filePath) matches the
651 * versions set in sig.
652 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
653 * something else if an install-halting error occurs.
655 static UINT
file_version_matches( MSIPACKAGE
*package
, const struct signature
*sig
, const WCHAR
*filePath
,
660 VS_FIXEDFILEINFO
*info
= NULL
;
661 DWORD size
= msi_get_file_version_info( package
, filePath
, 0, NULL
);
665 if (!size
) return ERROR_SUCCESS
;
666 if (!(version
= malloc( size
))) return ERROR_OUTOFMEMORY
;
668 if (msi_get_file_version_info( package
, filePath
, size
, version
))
669 VerQueryValueW( version
, L
"\\", (void **)&info
, &len
);
673 TRACE("comparing file version %d.%d.%d.%d:\n",
674 HIWORD(info
->dwFileVersionMS
),
675 LOWORD(info
->dwFileVersionMS
),
676 HIWORD(info
->dwFileVersionLS
),
677 LOWORD(info
->dwFileVersionLS
));
678 if (info
->dwFileVersionMS
< sig
->MinVersionMS
679 || (info
->dwFileVersionMS
== sig
->MinVersionMS
&&
680 info
->dwFileVersionLS
< sig
->MinVersionLS
))
682 TRACE("less than minimum version %d.%d.%d.%d\n",
683 HIWORD(sig
->MinVersionMS
),
684 LOWORD(sig
->MinVersionMS
),
685 HIWORD(sig
->MinVersionLS
),
686 LOWORD(sig
->MinVersionLS
));
688 else if ((sig
->MaxVersionMS
|| sig
->MaxVersionLS
) &&
689 (info
->dwFileVersionMS
> sig
->MaxVersionMS
||
690 (info
->dwFileVersionMS
== sig
->MaxVersionMS
&&
691 info
->dwFileVersionLS
> sig
->MaxVersionLS
)))
693 TRACE("greater than maximum version %d.%d.%d.%d\n",
694 HIWORD(sig
->MaxVersionMS
),
695 LOWORD(sig
->MaxVersionMS
),
696 HIWORD(sig
->MaxVersionLS
),
697 LOWORD(sig
->MaxVersionLS
));
699 else if (!match_languages( version
, sig
->Languages
))
701 TRACE("languages %s not supported\n", debugstr_w( sig
->Languages
));
703 else *matches
= TRUE
;
706 return ERROR_SUCCESS
;
709 /* Sets *matches to whether the file in findData matches that in sig.
710 * fullFilePath is assumed to be the full path of the file specified in
711 * findData, which may be necessary to compare the version.
712 * Return ERROR_SUCCESS in case of success (whether or not the file matches),
713 * something else if an install-halting error occurs.
715 static UINT
file_matches_sig( MSIPACKAGE
*package
, const struct signature
*sig
, const WIN32_FIND_DATAW
*findData
,
716 const WCHAR
*fullFilePath
, BOOL
*matches
)
718 UINT rc
= ERROR_SUCCESS
;
721 /* assumes the caller has already ensured the filenames match, so check
724 if (sig
->MinTime
.dwLowDateTime
|| sig
->MinTime
.dwHighDateTime
)
726 if (findData
->ftCreationTime
.dwHighDateTime
<
727 sig
->MinTime
.dwHighDateTime
||
728 (findData
->ftCreationTime
.dwHighDateTime
== sig
->MinTime
.dwHighDateTime
729 && findData
->ftCreationTime
.dwLowDateTime
<
730 sig
->MinTime
.dwLowDateTime
))
733 if (*matches
&& (sig
->MaxTime
.dwLowDateTime
|| sig
->MaxTime
.dwHighDateTime
))
735 if (findData
->ftCreationTime
.dwHighDateTime
>
736 sig
->MaxTime
.dwHighDateTime
||
737 (findData
->ftCreationTime
.dwHighDateTime
== sig
->MaxTime
.dwHighDateTime
738 && findData
->ftCreationTime
.dwLowDateTime
>
739 sig
->MaxTime
.dwLowDateTime
))
742 if (*matches
&& sig
->MinSize
&& findData
->nFileSizeLow
< sig
->MinSize
)
744 if (*matches
&& sig
->MaxSize
&& findData
->nFileSizeLow
> sig
->MaxSize
)
746 if (*matches
&& (sig
->MinVersionMS
|| sig
->MinVersionLS
||
747 sig
->MaxVersionMS
|| sig
->MaxVersionLS
))
748 rc
= file_version_matches( package
, sig
, fullFilePath
, matches
);
752 /* Recursively searches the directory dir for files that match the signature
753 * sig, up to (depth + 1) levels deep. That is, if depth is 0, it searches dir
754 * (and only dir). If depth is 1, searches dir and its immediate
756 * Assumes sig->File is not NULL.
757 * Returns ERROR_SUCCESS on success (which may include non-critical errors),
758 * something else on failures which should halt the install.
760 static UINT
recurse_search_directory( MSIPACKAGE
*package
, WCHAR
**appValue
, struct signature
*sig
, const WCHAR
*dir
,
764 WIN32_FIND_DATAW findData
;
765 UINT rc
= ERROR_SUCCESS
;
766 size_t dirLen
= lstrlenW(dir
), fileLen
= lstrlenW(sig
->File
);
767 WCHAR subpath
[MAX_PATH
];
771 TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir
), debugstr_w(sig
->File
), depth
);
774 return ERROR_SUCCESS
;
777 /* We need the buffer in both paths below, so go ahead and allocate it
778 * here. Add two because we might need to add a backslash if the dir name
779 * isn't backslash-terminated.
781 len
= dirLen
+ max(fileLen
, lstrlenW(L
"*.*")) + 2;
782 buf
= malloc(len
* sizeof(WCHAR
));
784 return ERROR_OUTOFMEMORY
;
787 PathAddBackslashW(buf
);
788 lstrcatW(buf
, sig
->File
);
790 hFind
= msi_find_first_file( package
, buf
, &findData
);
791 if (hFind
!= INVALID_HANDLE_VALUE
)
793 if (!(findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
))
797 rc
= file_matches_sig( package
, sig
, &findData
, buf
, &matches
);
798 if (rc
== ERROR_SUCCESS
&& matches
)
800 TRACE("found file, returning %s\n", debugstr_w(buf
));
807 if (rc
== ERROR_SUCCESS
&& !*appValue
)
810 PathAddBackslashW(buf
);
811 lstrcatW(buf
, L
"*.*");
813 hFind
= msi_find_first_file( package
, buf
, &findData
);
814 if (hFind
!= INVALID_HANDLE_VALUE
)
816 if (findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
&&
817 wcscmp( findData
.cFileName
, L
"." ) &&
818 wcscmp( findData
.cFileName
, L
".." ))
820 lstrcpyW(subpath
, dir
);
821 PathAppendW(subpath
, findData
.cFileName
);
822 rc
= recurse_search_directory( package
, appValue
, sig
, subpath
, depth
- 1 );
825 while (rc
== ERROR_SUCCESS
&& !*appValue
&& msi_find_next_file( package
, hFind
, &findData
))
827 if (!wcscmp( findData
.cFileName
, L
"." ) ||
828 !wcscmp( findData
.cFileName
, L
".." ))
831 lstrcpyW(subpath
, dir
);
832 PathAppendW(subpath
, findData
.cFileName
);
833 if (findData
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
834 rc
= recurse_search_directory( package
, appValue
, sig
, subpath
, depth
- 1 );
841 if (*appValue
!= buf
)
847 static UINT
check_directory( MSIPACKAGE
*package
, const WCHAR
*dir
, WCHAR
**appValue
)
849 DWORD attr
= msi_get_file_attributes( package
, dir
);
851 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
))
853 TRACE("directory exists, returning %s\n", debugstr_w(dir
));
854 *appValue
= wcsdup(dir
);
857 return ERROR_SUCCESS
;
860 static BOOL
is_full_path( const WCHAR
*path
)
862 WCHAR first
= towupper(path
[0]);
865 if (first
>= 'A' && first
<= 'Z' && path
[1] == ':')
867 else if (path
[0] == '\\' && path
[1] == '\\')
874 static UINT
search_directory( MSIPACKAGE
*package
, struct signature
*sig
, const WCHAR
*path
, int depth
, WCHAR
**appValue
)
878 WCHAR
*val
= NULL
, *new_val
;
880 TRACE("%p, %p, %s, %d, %p\n", package
, sig
, debugstr_w(path
), depth
, appValue
);
882 if (is_full_path( path
))
885 rc
= recurse_search_directory( package
, &val
, sig
, path
, depth
);
888 /* Recursively searching a directory makes no sense when the
889 * directory to search is the thing you're trying to find.
891 rc
= check_directory( package
, path
, &val
);
896 WCHAR pathWithDrive
[MAX_PATH
] = { 'C',':','\\',0 };
897 DWORD drives
= GetLogicalDrives();
901 for (i
= 0; rc
== ERROR_SUCCESS
&& !val
&& i
< 26; i
++)
903 if (!(drives
& (1 << i
)))
906 pathWithDrive
[0] = 'A' + i
;
907 if (GetDriveTypeW(pathWithDrive
) != DRIVE_FIXED
)
910 lstrcpynW(pathWithDrive
+ 3, path
, ARRAY_SIZE(pathWithDrive
) - 3);
913 rc
= recurse_search_directory( package
, &val
, sig
, pathWithDrive
, depth
);
915 rc
= check_directory( package
, pathWithDrive
, &val
);
919 attr
= msi_get_file_attributes( package
, val
);
920 if (attr
!= INVALID_FILE_ATTRIBUTES
&& (attr
& FILE_ATTRIBUTE_DIRECTORY
) &&
921 val
&& val
[lstrlenW(val
) - 1] != '\\')
923 new_val
= realloc(val
, (wcslen(val
) + 2) * sizeof(WCHAR
));
928 rc
= ERROR_OUTOFMEMORY
;
933 PathAddBackslashW(val
);
939 TRACE("returning %d\n", rc
);
943 static UINT
search_sig_name( MSIPACKAGE
*, const WCHAR
*, struct signature
*, WCHAR
** );
945 static UINT
search_dr( MSIPACKAGE
*package
, WCHAR
**appValue
, struct signature
*sig
)
947 LPWSTR parent
= NULL
;
949 WCHAR path
[MAX_PATH
];
950 WCHAR expanded
[MAX_PATH
];
956 TRACE("%s\n", debugstr_w(sig
->Name
));
960 row
= MSI_QueryGetRecord( package
->db
, L
"SELECT * FROM `DrLocator` WHERE `Signature_` = '%s'", sig
->Name
);
963 TRACE("failed to query DrLocator for %s\n", debugstr_w(sig
->Name
));
964 return ERROR_SUCCESS
;
967 /* check whether parent is set */
968 parentName
= MSI_RecordGetString(row
, 2);
971 struct signature parentSig
;
973 search_sig_name( package
, parentName
, &parentSig
, &parent
);
974 free_signature( &parentSig
);
977 msiobj_release(&row
->hdr
);
978 return ERROR_SUCCESS
;
983 MSI_RecordGetStringW(row
, 3, path
, &sz
);
985 if (MSI_RecordIsNull(row
,4))
988 depth
= MSI_RecordGetInteger(row
,4);
991 expand_any_path( package
, path
, expanded
, MAX_PATH
);
993 lstrcpyW(expanded
, path
);
997 attr
= msi_get_file_attributes( package
, parent
);
998 if (attr
!= INVALID_FILE_ATTRIBUTES
&&
999 !(attr
& FILE_ATTRIBUTE_DIRECTORY
))
1001 PathRemoveFileSpecW(parent
);
1002 PathAddBackslashW(parent
);
1005 lstrcpyW(path
, parent
);
1006 lstrcatW(path
, expanded
);
1008 else if (sz
) lstrcpyW(path
, expanded
);
1010 PathAddBackslashW(path
);
1012 rc
= search_directory( package
, sig
, path
, depth
, appValue
);
1015 msiobj_release(&row
->hdr
);
1016 TRACE("returning %d\n", rc
);
1020 static UINT
search_sig_name( MSIPACKAGE
*package
, const WCHAR
*sigName
, struct signature
*sig
, WCHAR
**appValue
)
1025 rc
= get_signature( package
, sig
, sigName
);
1026 if (rc
== ERROR_SUCCESS
)
1028 rc
= search_components( package
, appValue
, sig
);
1029 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1031 rc
= search_reg( package
, appValue
, sig
);
1032 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1034 rc
= search_ini( package
, appValue
, sig
);
1035 if (rc
== ERROR_SUCCESS
&& !*appValue
)
1036 rc
= search_dr( package
, appValue
, sig
);
1043 static UINT
ITERATE_AppSearch(MSIRECORD
*row
, LPVOID param
)
1045 MSIPACKAGE
*package
= param
;
1046 LPCWSTR propName
, sigName
;
1047 LPWSTR value
= NULL
;
1048 struct signature sig
;
1052 /* get property and signature */
1053 propName
= MSI_RecordGetString(row
, 1);
1054 sigName
= MSI_RecordGetString(row
, 2);
1056 TRACE("%s %s\n", debugstr_w(propName
), debugstr_w(sigName
));
1058 r
= search_sig_name( package
, sigName
, &sig
, &value
);
1061 r
= msi_set_property( package
->db
, propName
, value
, -1 );
1062 if (r
== ERROR_SUCCESS
&& !wcscmp( propName
, L
"SourceDir" ))
1063 msi_reset_source_folders( package
);
1067 free_signature( &sig
);
1069 uirow
= MSI_CreateRecord( 2 );
1070 MSI_RecordSetStringW( uirow
, 1, propName
);
1071 MSI_RecordSetStringW( uirow
, 2, sigName
);
1072 MSI_ProcessMessage(package
, INSTALLMESSAGE_ACTIONDATA
, uirow
);
1073 msiobj_release( &uirow
->hdr
);
1078 UINT
ACTION_AppSearch(MSIPACKAGE
*package
)
1083 if (msi_action_is_unique(package
, L
"AppSearch"))
1085 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1086 return ERROR_SUCCESS
;
1089 msi_register_unique_action(package
, L
"AppSearch");
1091 r
= MSI_OpenQuery( package
->db
, &view
, L
"SELECT * FROM `AppSearch`" );
1092 if (r
!= ERROR_SUCCESS
)
1093 return ERROR_SUCCESS
;
1095 r
= MSI_IterateRecords( view
, NULL
, ITERATE_AppSearch
, package
);
1096 msiobj_release( &view
->hdr
);
1100 static UINT
ITERATE_CCPSearch(MSIRECORD
*row
, LPVOID param
)
1102 MSIPACKAGE
*package
= param
;
1104 LPWSTR value
= NULL
;
1105 struct signature sig
;
1106 UINT r
= ERROR_SUCCESS
;
1108 signature
= MSI_RecordGetString(row
, 1);
1110 TRACE("%s\n", debugstr_w(signature
));
1112 search_sig_name( package
, signature
, &sig
, &value
);
1115 TRACE("Found signature %s\n", debugstr_w(signature
));
1116 msi_set_property( package
->db
, L
"CCP_Success", L
"1", -1 );
1118 r
= ERROR_NO_MORE_ITEMS
;
1121 free_signature(&sig
);
1125 UINT
ACTION_CCPSearch(MSIPACKAGE
*package
)
1130 if (msi_action_is_unique(package
, L
"CCPSearch"))
1132 TRACE("Skipping AppSearch action: already done in UI sequence\n");
1133 return ERROR_SUCCESS
;
1136 msi_register_unique_action(package
, L
"CCPSearch");
1138 r
= MSI_OpenQuery(package
->db
, &view
, L
"SELECT * FROM `CCPSearch`");
1139 if (r
!= ERROR_SUCCESS
)
1140 return ERROR_SUCCESS
;
1142 r
= MSI_IterateRecords(view
, NULL
, ITERATE_CCPSearch
, package
);
1143 msiobj_release(&view
->hdr
);