wine/dlls/msi/appsearch.c
2005-02-03 10:41:41 +00:00

810 lines
25 KiB
C

/*
* Implementation of the AppSearch action of the Microsoft Installer (msi.dll)
*
* Copyright 2005 Juan Lang
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdarg.h>
#define COBJMACROS
#include "windef.h"
#include "winbase.h"
#include "msi.h"
#include "msiquery.h"
#include "winver.h"
#include "wine/unicode.h"
#include "wine/debug.h"
#include "msipriv.h"
#include "action.h"
WINE_DEFAULT_DEBUG_CHANNEL(msi);
typedef struct tagMSISIGNATURE
{
LPWSTR Name; /* NOT owned by this structure */
LPWSTR Property; /* NOT owned by this structure */
LPWSTR File;
DWORD MinVersionMS;
DWORD MinVersionLS;
DWORD MaxVersionMS;
DWORD MaxVersionLS;
DWORD MinSize;
DWORD MaxSize;
FILETIME MinTime;
FILETIME MaxTime;
LPWSTR Languages;
}MSISIGNATURE;
static void ACTION_VerStrToInteger(LPCWSTR verStr, PDWORD ms, PDWORD ls)
{
const WCHAR *ptr;
int x1 = 0, x2 = 0, x3 = 0, x4 = 0;
x1 = atoiW(verStr);
ptr = strchrW(verStr, '.');
if (ptr)
{
x2 = atoiW(ptr + 1);
ptr = strchrW(ptr + 1, '.');
}
if (ptr)
{
x3 = atoiW(ptr + 1);
ptr = strchrW(ptr + 1, '.');
}
if (ptr)
x4 = atoiW(ptr + 1);
/* FIXME: byte-order dependent? */
*ms = x1 << 16 | x2;
*ls = x3 << 16 | x4;
}
/* Fills in sig with the the values from the Signature table, where name is the
* signature to find. Upon return, sig->File will be NULL if the record is not
* found, and not NULL if it is found.
* Warning: clears all fields in sig!
* Returns ERROR_SUCCESS upon success (where not finding the record counts as
* success), something else on error.
*/
static UINT ACTION_AppSearchGetSignature(MSIPACKAGE *package, MSISIGNATURE *sig,
LPCWSTR name)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'S','i','g','n','a','t','u','r','e',' ',
'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e',' ','=',' ',
'\'','%','s','\'',0};
TRACE("(package %p, sig %p)\n", package, sig);
memset(sig, 0, sizeof(*sig));
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, name);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
DWORD time;
WCHAR *minVersion, *maxVersion;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewExecute returned %d\n", rc);
goto end;
}
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewFetch returned %d\n", rc);
rc = ERROR_SUCCESS;
goto end;
}
/* get properties */
sig->File = load_dynamic_stringW(row,2);
minVersion = load_dynamic_stringW(row,3);
if (minVersion)
{
ACTION_VerStrToInteger(minVersion, &sig->MinVersionMS,
&sig->MinVersionLS);
HeapFree(GetProcessHeap(), 0, minVersion);
}
maxVersion = load_dynamic_stringW(row,4);
if (maxVersion)
{
ACTION_VerStrToInteger(maxVersion, &sig->MaxVersionMS,
&sig->MaxVersionLS);
HeapFree(GetProcessHeap(), 0, maxVersion);
}
sig->MinSize = MSI_RecordGetInteger(row,5);
if (sig->MinSize == MSI_NULL_INTEGER)
sig->MinSize = 0;
sig->MaxSize = MSI_RecordGetInteger(row,6);
if (sig->MaxSize == MSI_NULL_INTEGER)
sig->MaxSize = 0;
sig->Languages = load_dynamic_stringW(row,9);
time = MSI_RecordGetInteger(row,7);
if (time != MSI_NULL_INTEGER)
DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MinTime);
time = MSI_RecordGetInteger(row,8);
if (time != MSI_NULL_INTEGER)
DosDateTimeToFileTime(HIWORD(time), LOWORD(time), &sig->MaxTime);
TRACE("Found file name %s for Signature_ %s;\n",
debugstr_w(sig->File), debugstr_w(name));
TRACE("MinVersion is %d.%d.%d.%d\n", HIWORD(sig->MinVersionMS),
LOWORD(sig->MinVersionMS), HIWORD(sig->MinVersionLS),
LOWORD(sig->MinVersionLS));
TRACE("MaxVersion is %d.%d.%d.%d\n", HIWORD(sig->MaxVersionMS),
LOWORD(sig->MaxVersionMS), HIWORD(sig->MaxVersionLS),
LOWORD(sig->MaxVersionLS));
TRACE("MinSize is %ld, MaxSize is %ld;\n", sig->MinSize, sig->MaxSize);
TRACE("Languages is %s\n", debugstr_w(sig->Languages));
end:
msiobj_release(&row->hdr);
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
{
TRACE("MSI_OpenQuery returned %d\n", rc);
rc = ERROR_SUCCESS;
}
TRACE("returning %d\n", rc);
return rc;
}
static UINT ACTION_AppSearchComponents(MSIPACKAGE *package, BOOL *appFound,
MSISIGNATURE *sig)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'C','o','m','p','L','o','c','a','t','o','r',' ',
'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
'\'','%','s','\'',0};
TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
*appFound = FALSE;
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
WCHAR guid[50];
DWORD sz;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewExecute returned %d\n", rc);
goto end;
}
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewFetch returned %d\n", rc);
rc = ERROR_SUCCESS;
goto end;
}
/* get GUID */
guid[0] = 0;
sz=sizeof(guid)/sizeof(guid[0]);
rc = MSI_RecordGetStringW(row,2,guid,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
goto end;
}
FIXME("AppSearch unimplemented for CompLocator table (GUID %s)\n",
debugstr_w(guid));
end:
msiobj_release(&row->hdr);
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
{
TRACE("MSI_OpenQuery returned %d\n", rc);
rc = ERROR_SUCCESS;
}
TRACE("returning %d\n", rc);
return rc;
}
static UINT ACTION_AppSearchReg(MSIPACKAGE *package, BOOL *appFound,
MSISIGNATURE *sig)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'R','e','g','L','o','c','a','t','o','r',' ',
'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
'\'','%','s','\'',0};
TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
*appFound = FALSE;
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
LPWSTR keyPath;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewExecute returned %d\n", rc);
goto end;
}
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewFetch returned %d\n", rc);
rc = ERROR_SUCCESS;
goto end;
}
/* get key path */
keyPath = load_dynamic_stringW(row,3);
FIXME("AppSearch unimplemented for RegLocator (key path %s)\n",
debugstr_w(keyPath));
HeapFree(GetProcessHeap(), 0, keyPath);
end:
msiobj_release(&row->hdr);
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
{
TRACE("MSI_OpenQuery returned %d\n", rc);
rc = ERROR_SUCCESS;
}
TRACE("returning %d\n", rc);
return rc;
}
static UINT ACTION_AppSearchIni(MSIPACKAGE *package, BOOL *appFound,
MSISIGNATURE *sig)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'I','n','i','L','o','c','a','t','o','r',' ',
'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
'\'','%','s','\'',0};
TRACE("(package %p, appFound %p, sig %p)\n", package, appFound, sig);
*appFound = FALSE;
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
LPWSTR fileName;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewExecute returned %d\n", rc);
goto end;
}
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewFetch returned %d\n", rc);
rc = ERROR_SUCCESS;
goto end;
}
/* get file name */
fileName = load_dynamic_stringW(row,2);
FIXME("AppSearch unimplemented for IniLocator (ini file name %s)\n",
debugstr_w(fileName));
HeapFree(GetProcessHeap(), 0, fileName);
end:
msiobj_release(&row->hdr);
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
{
TRACE("MSI_OpenQuery returned %d\n", rc);
rc = ERROR_SUCCESS;
}
TRACE("returning %d\n", rc);
return rc;
}
/* Expands the value in src into a path without property names and only
* containing long path names into dst. Replaces at most len characters of dst,
* and always NULL-terminates dst if dst is not NULL and len >= 1.
* May modify src.
* Assumes src and dst are non-overlapping.
* FIXME: return code probably needed:
* - what does AppSearch return if the table values are invalid?
* - what if dst is too small?
*/
static void ACTION_ExpandAnyPath(MSIPACKAGE *package, WCHAR *src, WCHAR *dst,
size_t len)
{
WCHAR *ptr;
size_t copied = 0;
if (!src || !dst || !len)
return;
/* Ignore the short portion of the path, don't think we can use it anyway */
if ((ptr = strchrW(src, '|')))
ptr++;
else
ptr = src;
while (*ptr && copied < len - 1)
{
WCHAR *prop = strchrW(ptr, '[');
if (prop)
{
WCHAR *propEnd = strchrW(prop + 1, ']');
if (!propEnd)
{
WARN("Unterminated property name in AnyPath: %s\n",
debugstr_w(prop));
break;
}
else
{
DWORD propLen;
*propEnd = 0;
propLen = len - copied - 1;
MSI_GetPropertyW(package, prop + 1, dst + copied, &propLen);
ptr = propEnd + 1;
copied += propLen;
}
}
else
{
size_t toCopy = min(strlenW(ptr) + 1, len - copied - 1);
memcpy(dst + copied, ptr, toCopy * sizeof(WCHAR));
ptr += toCopy;
copied += toCopy;
}
}
*(dst + copied) = '\0';
}
/* Sets *matches to whether the file (whose path is filePath) matches the
* versions set in sig.
* Return ERROR_SUCCESS in case of success (whether or not the file matches),
* something else if a install-halting error occurs.
*/
static UINT ACTION_FileVersionMatches(MSISIGNATURE *sig, LPCWSTR filePath,
BOOL *matches)
{
UINT rc = ERROR_SUCCESS;
*matches = FALSE;
if (sig->Languages)
{
FIXME(": need to check version for languages %s\n",
debugstr_w(sig->Languages));
}
else
{
DWORD zero, size = GetFileVersionInfoSizeW(filePath, &zero);
if (size)
{
LPVOID buf = HeapAlloc(GetProcessHeap(), 0, size);
if (buf)
{
static const WCHAR rootW[] = { '\\',0 };
UINT versionLen;
LPVOID subBlock = NULL;
if (GetFileVersionInfoW(filePath, 0, size, buf))
VerQueryValueW(buf, rootW, &subBlock, &versionLen);
if (subBlock)
{
VS_FIXEDFILEINFO *info =
(VS_FIXEDFILEINFO *)subBlock;
TRACE("Comparing file version %d.%d.%d.%d:\n",
HIWORD(info->dwFileVersionMS),
LOWORD(info->dwFileVersionMS),
HIWORD(info->dwFileVersionLS),
LOWORD(info->dwFileVersionLS));
if (info->dwFileVersionMS < sig->MinVersionMS
|| (info->dwFileVersionMS == sig->MinVersionMS &&
info->dwFileVersionLS < sig->MinVersionLS))
{
TRACE("Less than minimum version %d.%d.%d.%d\n",
HIWORD(sig->MinVersionMS),
LOWORD(sig->MinVersionMS),
HIWORD(sig->MinVersionLS),
LOWORD(sig->MinVersionLS));
}
else if (info->dwFileVersionMS < sig->MinVersionMS
|| (info->dwFileVersionMS == sig->MinVersionMS &&
info->dwFileVersionLS < sig->MinVersionLS))
{
TRACE("Greater than minimum version %d.%d.%d.%d\n",
HIWORD(sig->MaxVersionMS),
LOWORD(sig->MaxVersionMS),
HIWORD(sig->MaxVersionLS),
LOWORD(sig->MaxVersionLS));
}
else
*matches = TRUE;
}
HeapFree(GetProcessHeap(), 0, buf);
}
else
rc = ERROR_OUTOFMEMORY;
}
}
return rc;
}
/* Sets *matches to whether the file in findData matches that in sig.
* fullFilePath is assumed to be the full path of the file specified in
* findData, which may be necessary to compare the version.
* Return ERROR_SUCCESS in case of success (whether or not the file matches),
* something else if a install-halting error occurs.
*/
static UINT ACTION_FileMatchesSig(MSISIGNATURE *sig,
LPWIN32_FIND_DATAW findData, LPCWSTR fullFilePath, BOOL *matches)
{
UINT rc = ERROR_SUCCESS;
*matches = TRUE;
/* assumes the caller has already ensured the filenames match, so check
* the other fields..
*/
if (sig->MinTime.dwLowDateTime || sig->MinTime.dwHighDateTime)
{
if (findData->ftCreationTime.dwHighDateTime <
sig->MinTime.dwHighDateTime ||
(findData->ftCreationTime.dwHighDateTime == sig->MinTime.dwHighDateTime
&& findData->ftCreationTime.dwLowDateTime <
sig->MinTime.dwLowDateTime))
*matches = FALSE;
}
if (*matches && (sig->MaxTime.dwLowDateTime || sig->MaxTime.dwHighDateTime))
{
if (findData->ftCreationTime.dwHighDateTime >
sig->MaxTime.dwHighDateTime ||
(findData->ftCreationTime.dwHighDateTime == sig->MaxTime.dwHighDateTime
&& findData->ftCreationTime.dwLowDateTime >
sig->MaxTime.dwLowDateTime))
*matches = FALSE;
}
if (*matches && sig->MinSize && findData->nFileSizeLow < sig->MinSize)
*matches = FALSE;
if (*matches && sig->MaxSize && findData->nFileSizeLow > sig->MaxSize)
*matches = FALSE;
if (*matches && (sig->MinVersionMS || sig->MinVersionLS ||
sig->MaxVersionMS || sig->MaxVersionLS))
rc = ACTION_FileVersionMatches(sig, fullFilePath, matches);
return rc;
}
/* Recursively searches the directory dir for files that match the signature
* sig, up to (depth + 1) levels deep. That is, if depth is 0, it searches dir
* (and only dir). If depth is 1, searches dir and its immediate
* subdirectories.
* Assumes sig->File is not NULL.
* Returns ERROR_SUCCESS on success (which may include non-critical errors),
* something else on failures which should halt the install.
* FIXME: according to MSDN, FindFirstFile doesn't work on root directories.
* Is that also true in Wine?
* FIXME: if dir is not a full path, have to check all fixed drives.
*/
static UINT ACTION_RecurseSearchDirectory(MSIPACKAGE *package, BOOL *appFound,
MSISIGNATURE *sig, LPCWSTR dir, int depth)
{
static const WCHAR starDotStarW[] = { '*','.','*',0 };
UINT rc = ERROR_SUCCESS;
size_t dirLen = lstrlenW(dir), fileLen = lstrlenW(sig->File);
WCHAR *buf;
TRACE("Searching directory %s for file %s, depth %d\n", debugstr_w(dir),
debugstr_w(sig->File), depth);
if (depth < 0)
return ERROR_INVALID_PARAMETER;
*appFound = FALSE;
/* We need the buffer in both paths below, so go ahead and allocate it
* here. Add two because we might need to add a backslash if the dir name
* isn't backslash-terminated.
*/
buf = HeapAlloc(GetProcessHeap(), 0,
(dirLen + max(fileLen, lstrlenW(starDotStarW)) + 2) * sizeof(WCHAR));
if (buf)
{
/* a depth of 0 implies we should search dir, so go ahead and search */
HANDLE hFind;
WIN32_FIND_DATAW findData;
memcpy(buf, dir, dirLen * sizeof(WCHAR));
if (buf[dirLen - 1] != '\\')
buf[dirLen++ - 1] = '\\';
memcpy(buf + dirLen, sig->File, (fileLen + 1) * sizeof(WCHAR));
hFind = FindFirstFileW(buf, &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
BOOL matches;
/* assuming Signature can't contain wildcards for the file name,
* so don't bother with FindNextFileW here.
*/
if (!(rc = ACTION_FileMatchesSig(sig, &findData, buf, &matches))
&& matches)
{
TRACE("found file, setting %s to %s\n",
debugstr_w(sig->Property), debugstr_w(buf));
rc = MSI_SetPropertyW(package, sig->Property, buf);
*appFound = TRUE;
}
FindClose(hFind);
}
if (rc == ERROR_SUCCESS && !*appFound && depth > 0)
{
HANDLE hFind;
WIN32_FIND_DATAW findData;
memcpy(buf, dir, dirLen * sizeof(WCHAR));
if (buf[dirLen - 1] != '\\')
buf[dirLen++ - 1] = '\\';
lstrcpyW(buf + dirLen, starDotStarW);
hFind = FindFirstFileW(buf, &findData);
if (hFind != INVALID_HANDLE_VALUE)
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
rc = ACTION_RecurseSearchDirectory(package, appFound,
sig, findData.cFileName, depth - 1);
while (rc == ERROR_SUCCESS && !*appFound &&
FindNextFileW(hFind, &findData) != 0)
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
rc = ACTION_RecurseSearchDirectory(package, appFound,
sig, findData.cFileName, depth - 1);
}
FindClose(hFind);
}
}
HeapFree(GetProcessHeap(), 0, buf);
}
else
rc = ERROR_OUTOFMEMORY;
return rc;
}
/* FIXME: if dir is not a full path, have to check all fixed drives. */
static UINT ACTION_CheckDirectory(MSIPACKAGE *package, MSISIGNATURE *sig,
LPCWSTR dir)
{
UINT rc = ERROR_SUCCESS;
if (GetFileAttributesW(dir) & FILE_ATTRIBUTE_DIRECTORY)
{
TRACE("directory exists, setting %s to %s\n",
debugstr_w(sig->Property), debugstr_w(dir));
rc = MSI_SetPropertyW(package, sig->Property, dir);
}
return rc;
}
static UINT ACTION_AppSearchDr(MSIPACKAGE *package, MSISIGNATURE *sig)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'D','r','L','o','c','a','t','o','r',' ',
'w','h','e','r','e',' ','S','i','g','n','a','t','u','r','e','_',' ','=',' ',
'\'','%','s','\'',0};
TRACE("(package %p, sig %p)\n", package, sig);
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery, sig->Name);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
WCHAR buffer[MAX_PATH], expanded[MAX_PATH];
DWORD sz;
int depth;
BOOL found;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewExecute returned %d\n", rc);
goto end;
}
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
TRACE("MSI_ViewFetch returned %d\n", rc);
rc = ERROR_SUCCESS;
goto end;
}
/* check whether parent is set */
buffer[0] = 0;
sz=sizeof(buffer)/sizeof(buffer[0]);
rc = MSI_RecordGetStringW(row,2,buffer,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
goto end;
}
else if (buffer[0])
{
FIXME(": searching parent (%s) unimplemented\n",
debugstr_w(buffer));
goto end;
}
/* no parent, now look for path */
buffer[0] = 0;
sz=sizeof(buffer)/sizeof(buffer[0]);
rc = MSI_RecordGetStringW(row,3,buffer,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
goto end;
}
if (MSI_RecordIsNull(row,4))
depth = 0;
else
depth = MSI_RecordGetInteger(row,4);
ACTION_ExpandAnyPath(package, buffer, expanded,
sizeof(expanded) / sizeof(expanded[0]));
if (sig->File)
rc = ACTION_RecurseSearchDirectory(package, &found, sig,
expanded, depth);
else
{
/* Recursively searching a directory makes no sense when the
* directory to search is the thing you're trying to find.
*/
rc = ACTION_CheckDirectory(package, sig, expanded);
}
end:
msiobj_release(&row->hdr);
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
{
TRACE("MSI_OpenQuery returned %d\n", rc);
rc = ERROR_SUCCESS;
}
TRACE("returning %d\n", rc);
return rc;
}
/* http://msdn.microsoft.com/library/en-us/msi/setup/appsearch_table.asp
* is the best reference for the AppSearch table and how it's used.
*/
UINT ACTION_AppSearch(MSIPACKAGE *package)
{
MSIQUERY *view;
UINT rc;
static const WCHAR ExecSeqQuery[] = {
's','e','l','e','c','t',' ','*',' ',
'f','r','o','m',' ',
'A','p','p','S','e','a','r','c','h',0};
rc = MSI_OpenQuery(package->db, &view, ExecSeqQuery);
if (rc == ERROR_SUCCESS)
{
MSIRECORD *row = 0;
WCHAR propBuf[0x100], sigBuf[0x100];
DWORD sz;
MSISIGNATURE sig;
BOOL appFound = FALSE;
rc = MSI_ViewExecute(view, 0);
if (rc != ERROR_SUCCESS)
goto end;
while (!rc)
{
rc = MSI_ViewFetch(view,&row);
if (rc != ERROR_SUCCESS)
{
rc = ERROR_SUCCESS;
break;
}
/* get property and signature */
propBuf[0] = 0;
sz=sizeof(propBuf)/sizeof(propBuf[0]);
rc = MSI_RecordGetStringW(row,1,propBuf,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
msiobj_release(&row->hdr);
break;
}
sigBuf[0] = 0;
sz=sizeof(sigBuf)/sizeof(sigBuf[0]);
rc = MSI_RecordGetStringW(row,2,sigBuf,&sz);
if (rc != ERROR_SUCCESS)
{
ERR("Error is %x\n",rc);
msiobj_release(&row->hdr);
break;
}
TRACE("Searching for Property %s, Signature_ %s\n",
debugstr_w(propBuf), debugstr_w(sigBuf));
/* This clears all the fields, so set Name and Property afterward */
rc = ACTION_AppSearchGetSignature(package, &sig, sigBuf);
sig.Name = sigBuf;
sig.Property = propBuf;
if (rc == ERROR_SUCCESS)
{
rc = ACTION_AppSearchComponents(package, &appFound, &sig);
if (rc == ERROR_SUCCESS && !appFound)
{
rc = ACTION_AppSearchReg(package, &appFound, &sig);
if (rc == ERROR_SUCCESS && !appFound)
{
rc = ACTION_AppSearchIni(package, &appFound, &sig);
if (rc == ERROR_SUCCESS && !appFound)
rc = ACTION_AppSearchDr(package, &sig);
}
}
}
HeapFree(GetProcessHeap(), 0, sig.File);
HeapFree(GetProcessHeap(), 0, sig.Languages);
msiobj_release(&row->hdr);
}
end:
MSI_ViewClose(view);
msiobj_release(&view->hdr);
}
else
rc = ERROR_SUCCESS;
return rc;
}