diff --git a/dlls/msi/Makefile.in b/dlls/msi/Makefile.in index efd51b66cd0..2f7ff0b5cef 100644 --- a/dlls/msi/Makefile.in +++ b/dlls/msi/Makefile.in @@ -8,6 +8,7 @@ EXTRALIBS = -luuid $(LIBUNICODE) C_SRCS = \ action.c \ + appsearch.c \ create.c \ custom.c \ dialog.c \ diff --git a/dlls/msi/action.c b/dlls/msi/action.c index 1428421c95a..107be402d21 100644 --- a/dlls/msi/action.c +++ b/dlls/msi/action.c @@ -157,6 +157,8 @@ const static WCHAR szForceReboot[] = {'F','o','r','c','e','R','e','b','o','o','t',0}; const static WCHAR szResolveSource[] = {'R','e','s','o','l','v','e','S','o','u','r','c','e',0}; +const static WCHAR szAppSearch[] = +{'A','p','p','S','e','a','r','c','h',0}; struct _actions { LPCWSTR action; @@ -164,6 +166,7 @@ struct _actions { }; struct _actions StandardActions[] = { + { szAppSearch, ACTION_AppSearch }, { szCostFinalize, ACTION_CostFinalize }, { szCostInitialize, ACTION_CostInitialize }, { szCreateFolders, ACTION_CreateFolders }, diff --git a/dlls/msi/action.h b/dlls/msi/action.h index 0150c9b97b8..1ebaa9fa99f 100644 --- a/dlls/msi/action.h +++ b/dlls/msi/action.h @@ -101,7 +101,7 @@ UINT ACTION_PerformAction(MSIPACKAGE *package, const WCHAR *action); void ACTION_FinishCustomActions( MSIPACKAGE* package); UINT ACTION_CustomAction(MSIPACKAGE *package,const WCHAR *action, BOOL execute); void ACTION_UpdateComponentStates(MSIPACKAGE *package, LPCWSTR szFeature); - +UINT ACTION_AppSearch(MSIPACKAGE *package); DWORD deformat_string(MSIPACKAGE *package, LPCWSTR ptr, WCHAR** data ); WCHAR *load_dynamic_stringW(MSIRECORD *row, INT index); diff --git a/dlls/msi/appsearch.c b/dlls/msi/appsearch.c new file mode 100644 index 00000000000..0638db7c10f --- /dev/null +++ b/dlls/msi/appsearch.c @@ -0,0 +1,810 @@ +/* + * 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 + +#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(minVersion, &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'; +} + +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)) + { + DWORD zero, size; + + if (sig->Languages) + { + FIXME(": need to check version for languages %s\n", + debugstr_w(sig->Languages)); + *matches = FALSE; + } + else + { + size = GetFileVersionInfoSizeW(fullFilePath, &zero); + if (size) + { + LPVOID buf = HeapAlloc(GetProcessHeap(), 0, size); + + if (buf) + { + static const WCHAR rootW[] = { '\\',0 }; + UINT versionLen; + LPVOID subBlock = NULL; + + if (!GetFileVersionInfoW(fullFilePath, 0, size, buf)) + { + *matches = FALSE; + rc = GetLastError(); + } + if (*matches && !VerQueryValueW(buf, rootW, &subBlock, + &versionLen)) + { + *matches = FALSE; + rc = GetLastError(); + } + 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)); + *matches = FALSE; + } + 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)); + *matches = FALSE; + } + } + HeapFree(GetProcessHeap(), 0, buf); + } + else + { + *matches = FALSE; + rc = ERROR_OUTOFMEMORY; + } + } + else + { + *matches = FALSE; + rc = GetLastError(); + } + } + } + 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. + * 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; + + /* 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); + } + else + rc = GetLastError(); + 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); + } + if (GetLastError() != ERROR_NO_MORE_FILES) + rc = GetLastError(); + 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, BOOL *appFound, + 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); + *appFound = TRUE; + } + return rc; +} + +static UINT ACTION_AppSearchDr(MSIPACKAGE *package, BOOL *appFound, + 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, 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 buffer[MAX_PATH], expanded[MAX_PATH]; + DWORD sz; + int depth; + + 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; + } + depth = MSI_RecordGetInteger(row,4); + ACTION_ExpandAnyPath(package, buffer, expanded, + sizeof(expanded) / sizeof(expanded[0])); + if (sig->File) + rc = ACTION_RecurseSearchDirectory(package, appFound, 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, appFound, 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, &appFound, &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; +}