/*++ Copyright (c) 1989-2000 Microsoft Corporation Module Name: apphelp.c Abstract: This module implements high-level functions to access msi installer information Author: vadimb created sometime in 2000 Revision History: --*/ #include "sdbp.h" /*++ // Local function prototype // // --*/ PDB SdbpGetNextMsiDatabase( IN HSDB hSDB, IN LPCTSTR lpszLocalDB, IN OUT PSDBMSIFINDINFO pFindInfo ); TAGID SdbpFindPlatformMatch( IN HSDB hSDB, IN PDB pdb, IN TAGID tiMatch, // current match using the GUID index IN PSDBMSIFINDINFO pFindInfo ) { TAGID tiRuntimePlatform; DWORD dwRuntimePlatform; LPCTSTR pszGuid = NULL; #ifndef WIN32A_MODE UNICODE_STRING ustrGUID = { 0 }; NTSTATUS Status; #else TCHAR szGUID[64]; // guid is about 38 chars + 0 #endif // WIN32A_MODE TAGID tiOSSKU; DWORD dwOSSKU; if (tiMatch != TAGID_NULL) { #ifndef WIN32A_MODE GUID_TO_UNICODE_STRING(&pFindInfo->guidID, &ustrGUID); pszGuid = ustrGUID.Buffer; #else // WIN32A_MODE GUID_TO_STRING(&pFindInfo->guidID, szGUID); pszGuid = szGUID; #endif // WIN32A_MODE } while (tiMatch != TAGID_NULL) { tiRuntimePlatform = SdbFindFirstTag(pdb, tiMatch, TAG_RUNTIME_PLATFORM); if (tiRuntimePlatform != TAGID_NULL) { dwRuntimePlatform = SdbReadDWORDTag(pdb, tiRuntimePlatform, RUNTIME_PLATFORM_ANY); // // Check for the platform match // if (!SdbpCheckRuntimePlatform(hSDB, pszGuid, dwRuntimePlatform)) { goto CheckNextMatch; } } // check for SKU match tiOSSKU = SdbFindFirstTag(pdb, tiMatch, TAG_OS_SKU); if (tiOSSKU != TAGID_NULL) { dwOSSKU = SdbReadDWORDTag(pdb, tiOSSKU, OS_SKU_ALL); if (dwOSSKU != OS_SKU_ALL) { PSDBCONTEXT pDBContext = (PSDBCONTEXT)hSDB; // // Check for the OS SKU match // if (!(dwOSSKU & pDBContext->dwOSSKU)) { DBGPRINT((sdlInfo, "SdbpCheckExe", "MSI OS SKU Mismatch %s Database(0x%lx) vs 0x%lx\n", (pszGuid ? pszGuid : TEXT("Unknown")), dwOSSKU, pDBContext->dwOSSKU)); goto CheckNextMatch; } } } break; // if we are here -- both sku and platform match CheckNextMatch: tiMatch = SdbFindNextGUIDIndexedTag(pdb, &pFindInfo->sdbFindInfo); } #ifndef WIN32A_MODE FREE_GUID_STRING(&ustrGUID); #endif // WIN32A_MODE return tiMatch; } TAGREF SDBAPI SdbpFindFirstMsiMatch( IN HSDB hSDB, IN LPCTSTR lpszLocalDB, OUT PSDBMSIFINDINFO pFindInfo ) /*++ Return: TAGREF of a matching MSI transform in whatever database we have found to be valid the state of the search is updated (pFindInfo->sdbLookupState) or TAGREF_NULL if there were no matches in any of the remaining databases Desc: When this function is called first, the state is set to LOOKUP_NONE - the local db is up for lookup first, followed by arbitrary number of other lookup states. --*/ { TAGREF trMatch = TAGREF_NULL; TAGID tiMatch = TAGID_NULL; PDB pdb; do { // // If we have a database to look into first, use it, otherwise grab the // next database from the list of things we use. // pdb = SdbpGetNextMsiDatabase(hSDB, lpszLocalDB, pFindInfo); // // There is no database for us to look at - get out // if (pdb == NULL) { // // All options are out -- get out now // break; } tiMatch = SdbFindFirstGUIDIndexedTag(pdb, TAG_MSI_PACKAGE, TAG_MSI_PACKAGE_ID, &pFindInfo->guidID, &pFindInfo->sdbFindInfo); // // Skip entries that do not match our runtime platform // tiMatch = SdbpFindPlatformMatch(hSDB, pdb, tiMatch, pFindInfo); } while (tiMatch == TAGID_NULL); if (tiMatch != TAGID_NULL) { // // We have a match if we are here, state information is stored in pFindInfo with // sdbLookupState containing the NEXT search state for us to feast on. // if (!SdbTagIDToTagRef(hSDB, pdb, tiMatch, &trMatch)) { DBGPRINT((sdlError, "SdbpFindFirstMsiMatch", "Failed to convert tagid 0x%x to tagref\n", tiMatch)); return TAGREF_NULL; } } return trMatch; } TAGREF SDBAPI SdbpFindNextMsiMatch( IN HSDB hSDB, IN PDB pdb, OUT PSDBMSIFINDINFO pFindInfo ) { TAGREF trMatch = TAGREF_NULL; TAGID tiMatch = TAGID_NULL; tiMatch = SdbFindNextGUIDIndexedTag(pdb, &pFindInfo->sdbFindInfo); if (tiMatch == TAGID_NULL) { return TAGREF_NULL; } tiMatch = SdbpFindPlatformMatch(hSDB, pdb, tiMatch, pFindInfo); if (tiMatch == TAGID_NULL) { return TAGREF_NULL; } if (!SdbTagIDToTagRef(hSDB, pdb, tiMatch, &trMatch)) { DBGPRINT((sdlError, "SdbpFindFirstMsiMatch", "Failed to convert tagid 0x%x to tagref\n", tiMatch)); return TAGREF_NULL; } return trMatch; } TAGREF SDBAPI SdbFindFirstMsiPackage_Str( IN HSDB hSDB, IN LPCTSTR lpszGuid, IN LPCTSTR lpszLocalDB, OUT PSDBMSIFINDINFO pFindInfo ) /*++ Return: BUGBUG: ? Desc: BUGBUG: ? --*/ { GUID guidID; if (!SdbGUIDFromString(lpszGuid, &guidID)) { DBGPRINT((sdlError, "SdbFindFirstMsiPackage_Str", "Failed to convert guid from string %s\n", lpszGuid)); return TAGREF_NULL; } return SdbFindFirstMsiPackage(hSDB, &guidID, lpszLocalDB, pFindInfo); } // // The workings of MSI database search: // // 1. Function SdbpGetNextMsiDatabase returns the database corresponding to the // state stored in sdbLookupState // 2. Only non-null values are returned and the state is advanced to the next // valid value. For instance, if we were not able to open a local (supplied) database // we try main database - if that was a no-show, we try test db, if that is not available, // we try custom dbs // 3. When function returns NULL - it means that no more dbs are available for lookup PDB SdbpGetNextMsiDatabase( IN HSDB hSDB, IN LPCTSTR lpszLocalDB, IN OUT PSDBMSIFINDINFO pFindInfo ) /*++ Func: SdbpGetNextMsiDatabase Returns the next database to be looked at or NULL if no more databases are availalble Uses pFindInfo->sdbLookupState and updates it upon exit --*/ { PSDBCONTEXT pContext = (PSDBCONTEXT)hSDB; PDB pdbRet; LPTSTR pszGuid; SDBMSILOOKUPSTATE LookupState = LOOKUP_DONE; #ifndef WIN32A_MODE UNICODE_STRING ustrGUID = { 0 }; NTSTATUS Status; #else TCHAR szGUID[64]; // guid is about 38 chars + 0 #endif do { pdbRet = NULL; switch (pFindInfo->sdbLookupState) { case LOOKUP_DONE: // no next state break; case LOOKUP_NONE: // initial state, start with local db LookupState = LOOKUP_LOCAL; break; case LOOKUP_LOCAL: SdbCloseLocalDatabase(hSDB); if (lpszLocalDB != NULL) { if (!SdbOpenLocalDatabase(hSDB, lpszLocalDB)) { DBGPRINT((sdlWarning, "SdbpGetNextMsiDatabase", "Cannot open database \"%s\"\n", lpszLocalDB)); } else { pdbRet = pContext->pdbLocal; } } LookupState = LOOKUP_CUSTOM; break; case LOOKUP_CUSTOM: #ifndef WIN32A_MODE Status = GUID_TO_UNICODE_STRING(&pFindInfo->guidID, &ustrGUID); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "SdbGetNextMsiDatabase", "Failed to convert guid to string, status 0x%lx\n", Status)); break; } pszGuid = ustrGUID.Buffer; #else GUID_TO_STRING(&pFindInfo->guidID, szGUID); pszGuid = szGUID; #endif SdbCloseLocalDatabase(hSDB); if (SdbOpenNthLocalDatabase(hSDB, pszGuid, &pFindInfo->dwCustomIndex, FALSE)) { pdbRet = pContext->pdbLocal; // // The state does not change when we have a match // assert(pdbRet != NULL); } else { LookupState = LOOKUP_TEST; } break; case LOOKUP_TEST: pdbRet = pContext->pdbTest; // // Next one is custom // LookupState = LOOKUP_MAIN; break; case LOOKUP_MAIN: pdbRet = pContext->pdbMain; LookupState = LOOKUP_DONE; break; default: DBGPRINT((sdlError, "SdbGetNextMsiDatabase", "Unknown MSI Lookup State 0x%lx\n", pFindInfo->sdbLookupState)); LookupState = LOOKUP_DONE; break; } pFindInfo->sdbLookupState = LookupState; } while (pdbRet == NULL && pFindInfo->sdbLookupState != LOOKUP_DONE); #ifndef WIN32A_MODE FREE_GUID_STRING(&ustrGUID); #endif return pdbRet; } TAGREF SDBAPI SdbFindFirstMsiPackage( IN HSDB hSDB, // HSDB context IN GUID* pGuidID, // GUID that we're looking for IN LPCTSTR lpszLocalDB, // optional path to local db, dos path style OUT PSDBMSIFINDINFO pFindInfo // pointer to our search context ) /*++ Return: BUGBUG: ? Desc: BUGBUG: ? --*/ { PSDBCONTEXT pContext = (PSDBCONTEXT)hSDB; LPTSTR pszGuid; // // Initialize MSI search structure // RtlZeroMemory(pFindInfo, sizeof(*pFindInfo)); pFindInfo->guidID = *pGuidID; // store the guid ptr in the context pFindInfo->sdbLookupState = LOOKUP_NONE; pFindInfo->trMatch = SdbpFindFirstMsiMatch(hSDB, lpszLocalDB, pFindInfo); return pFindInfo->trMatch; } TAGREF SDBAPI SdbFindNextMsiPackage( IN HSDB hSDB, IN OUT PSDBMSIFINDINFO pFindInfo ) /*++ Return: BUGBUG: ? Desc: BUGBUG: ? --*/ { PDB pdb = NULL; TAGID tiMatch; TAGREF trMatch = TAGREF_NULL; assert(hSDB != NULL && pFindInfo != NULL); if (pFindInfo->trMatch == TAGREF_NULL) { DBGPRINT((sdlError, "SdbFindNextMsiPackage", "No more matches\n")); return trMatch; } // // Take the last match and look in the same database // if (!SdbTagRefToTagID(hSDB, pFindInfo->trMatch, &pdb, &tiMatch)) { DBGPRINT((sdlError, "SdbFindNextMsiPackage", "Failed to convert tagref 0x%x to tagid\n", pFindInfo->trMatch)); return trMatch; } // // Call to find the next match in this (current) database // trMatch = SdbpFindNextMsiMatch(hSDB, pdb, pFindInfo); if (trMatch != TAGREF_NULL) { pFindInfo->trMatch = trMatch; return trMatch; } // // So in this (current) database we have no further matches, look for the first match // in the next db // trMatch = SdbpFindFirstMsiMatch(hSDB, NULL, pFindInfo); // // We have found a match -- or not, store supplemental information and return. // pFindInfo->trMatch = trMatch; return trMatch; } DWORD SDBAPI SdbEnumMsiTransforms( IN HSDB hSDB, IN TAGREF trMatch, OUT TAGREF* ptrBuffer, IN OUT DWORD* pdwBufferSize ) /*++ Return: BUGBUG: ? Desc: Enumerate fixes for a given MSI package. --*/ { TAGID tiMatch = TAGID_NULL; TAGID tiTransform; DWORD nTransforms = 0; DWORD dwError = ERROR_SUCCESS; PDB pdb; // // Get a list of transforms available for this entry // if (!SdbTagRefToTagID(hSDB, trMatch, &pdb, &tiMatch)) { DBGPRINT((sdlError, "SdbEnumerateMsiTransforms", "Failed to convert tagref 0x%x to tagid\n", trMatch)); return ERROR_INTERNAL_DB_CORRUPTION; } if (ptrBuffer == NULL) { // // We should have the pdwBufferSize not NULL in this case. // if (pdwBufferSize == NULL) { DBGPRINT((sdlError, "SdbEnumerateMsiTransforms", "when ptrBuffer is not specified, pdwBufferSize should not be NULL\n")); return ERROR_INVALID_PARAMETER; } } // // Now start enumerating transforms. Count them first. // if (pdwBufferSize != NULL) { tiTransform = SdbFindFirstTag(pdb, tiMatch, TAG_MSI_TRANSFORM_REF); while (tiTransform != TAGID_NULL) { nTransforms++; tiTransform = SdbFindNextTag(pdb, tiMatch, tiTransform); } // // Both buffer size and buffer specified, see if we fit in. // if (ptrBuffer == NULL || *pdwBufferSize < nTransforms * sizeof(TAGREF)) { *pdwBufferSize = nTransforms * sizeof(TAGREF); DBGPRINT((sdlInfo, "SdbEnumerateMsiTransforms", "Buffer specified is too small\n")); return ERROR_INSUFFICIENT_BUFFER; } } // // Now we have counted them all and either the buffer size is not supplied // or it has enough room, do it again now // The only case when we are here is ptrBuffer != NULL // assert(ptrBuffer != NULL); __try { tiTransform = SdbFindFirstTag(pdb, tiMatch, TAG_MSI_TRANSFORM_REF); while (tiTransform != TAGID_NULL) { if (!SdbTagIDToTagRef(hSDB, pdb, tiTransform, ptrBuffer)) { DBGPRINT((sdlError, "SdbEnumerateMsiTransforms", "Failed to convert tagid 0x%x to tagref\n", tiTransform)); return ERROR_INTERNAL_DB_CORRUPTION; } // // Advance the pointer // ++ptrBuffer; // // Lookup next transform // tiTransform = SdbFindNextTag(pdb, tiMatch, tiTransform); } if (pdwBufferSize != NULL) { *pdwBufferSize = nTransforms * sizeof(TAGREF); } } __except(EXCEPTION_EXECUTE_HANDLER) { dwError = ERROR_INVALID_DATA; } return dwError; } BOOL SDBAPI SdbReadMsiTransformInfo( IN HSDB hSDB, IN TAGREF trTransformRef, OUT PSDBMSITRANSFORMINFO pTransformInfo ) /*++ Return: BUGBUG: ? Desc: BUGBUG: ? --*/ { TAGID tiTransformRef = TAGID_NULL; TAGID tiName = TAGID_NULL; TAGID tiFile = TAGID_NULL; PDB pdb = NULL; TAGREF trTransform = TAGREF_NULL; TAGREF trFileTag = TAGREF_NULL; TAGREF trFile = TAGREF_NULL; TAGREF trFileName = TAGREF_NULL; DWORD dwLength; LPTSTR pszFileName = NULL; RtlZeroMemory(pTransformInfo, sizeof(*pTransformInfo)); if (!SdbTagRefToTagID(hSDB, trTransformRef, &pdb, &tiTransformRef)) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Failed to convert tagref 0x%lx to tagid\n", trTransformRef)); return FALSE; } if (SdbGetTagFromTagID(pdb, tiTransformRef) != TAG_MSI_TRANSFORM_REF) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Bad Transform reference 0x%lx\n", trTransformRef)); return FALSE; } // // First find the name. // tiName = SdbFindFirstTag(pdb, tiTransformRef, TAG_NAME); if (tiName) { pTransformInfo->lpszTransformName = SdbGetStringTagPtr(pdb, tiName); } // // Then locate the transform itself. // trTransform = SdbGetItemFromItemRef(hSDB, trTransformRef, TAG_NAME, TAG_MSI_TRANSFORM_TAGID, TAG_MSI_TRANSFORM); if (trTransform == TAGREF_NULL) { // // We can't do it, return TRUE however. // Reason: Caller will have the name of the transform // and should know what to do. // return TRUE; } pTransformInfo->trTransform = trTransform; // // Now that we have the transform entry get the description and the bits. // trFileTag = SdbFindFirstTagRef(hSDB, trTransform, TAG_MSI_TRANSFORM_TAGID); if (trFileTag != TAGREF_NULL) { // // Read the reference to an actual file within this db // tiFile = SdbReadDWORDTagRef(hSDB, trFileTag, (DWORD)TAGID_NULL); // // If we attained the tiFile - note that it is an id within // the current database, so make a trFile out of it. // if (tiFile) { if (!SdbTagIDToTagRef(hSDB, pdb, tiFile, &trFile)) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Failed to convert File tag to tagref 0x%lx\n", tiFile)); trFile = TAGREF_NULL; } } } if (trFile == TAGREF_NULL) { // // We wil have to look by (file) name. // trFileName = SdbFindFirstTagRef(hSDB, trTransform, TAG_MSI_TRANSFORM_FILE); if (trFileName == TAGREF_NULL) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Failed to get MSI Transform for tag 0x%x\n", trTransform)); return FALSE; } dwLength = SdbpGetStringRefLength(hSDB, trFileName); STACK_ALLOC(pszFileName, (dwLength + 1) * sizeof(TCHAR)); if (pszFileName == NULL) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Failed to allocate buffer for %ld characters tag 0x%lx\n", dwLength, trFileName)); return FALSE; } // // Now read the filename. // if (!SdbReadStringTagRef(hSDB, trFileName, pszFileName, dwLength + 1)) { DBGPRINT((sdlError, "SdbReadMsiTransformInfo", "Failed to read filename string tag, length %d characters, tag 0x%x\n", dwLength, trFileName)); STACK_FREE(pszFileName); return FALSE; } // // Locate the transform in the library (of the current file first, if // not found -- in the main db then). // trFile = SdbpGetLibraryFile(pdb, pszFileName); if (trFile == TAGREF_NULL) { trFile = SdbpGetMainLibraryFile(hSDB, pszFileName); } STACK_FREE(pszFileName); } pTransformInfo->trFile = trFile; return TRUE; } BOOL SDBAPI SdbCreateMsiTransformFile( IN HSDB hSDB, IN LPCTSTR lpszFileName, OUT PSDBMSITRANSFORMINFO pTransformInfo ) /*++ Return: BUGBUG: ? Desc: BUGBUG: ? --*/ { TAGREF trBits; DWORD dwSize; PBYTE pBuffer = NULL; BOOL bSuccess = FALSE; if (pTransformInfo->trFile == TAGREF_NULL) { DBGPRINT((sdlError, "SdbCreateMsiTransformFile", "File for transform \"%s\" was not found\n", pTransformInfo->lpszTransformName)); goto out; } trBits = SdbFindFirstTagRef(hSDB, pTransformInfo->trFile, TAG_FILE_BITS); if (trBits == TAGREF_NULL) { DBGPRINT((sdlError, "SdbCreateMsiTransformFile", "File bits not found tag 0x%x\n", trBits)); goto out; } dwSize = SdbpGetTagRefDataSize(hSDB, trBits); pBuffer = (PBYTE)SdbAlloc(dwSize); if (pBuffer == NULL) { DBGPRINT((sdlError, "SdbCreateMsiTransformFile", "Failed to allocate %d bytes.\n", dwSize)); goto out; } // // Now read the DLL's bits. // if (!SdbpReadBinaryTagRef(hSDB, trBits, pBuffer, dwSize)) { DBGPRINT((sdlError, "SdbCreateMsiTransformFile", "Can't read transform bits.\n")); goto out; } if (!SdbpWriteBitsToFile(lpszFileName, pBuffer, dwSize)) { DBGPRINT((sdlError, "SdbCreateMsiTransformFile", "Can't write transform bits to disk.\n")); goto out; } bSuccess = TRUE; out: if (pBuffer != NULL) { SdbFree(pBuffer); } return bSuccess; } BOOL SDBAPI SdbGetMsiPackageInformation( IN HSDB hSDB, IN TAGREF trMatch, OUT PMSIPACKAGEINFO pPackageInfo ) { PDB pdb = NULL; TAGID tiMatch; TAGID tiPackageID; TAGID tiExeID; TAGID tiApphelp; TAGID tiCustomAction; BOOL bSuccess; RtlZeroMemory(pPackageInfo, sizeof(*pPackageInfo)); if (!SdbTagRefToTagID(hSDB, trMatch, &pdb, &tiMatch)) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to convert tagref 0x%lx to tagid\n", trMatch)); return FALSE; } // // Fill in important id's // if (!SdbGetDatabaseID(pdb, &pPackageInfo->guidDatabaseID)) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to get database id, tagref 0x%lx\n", trMatch)); return FALSE; } // // Retrieve match id (unique one) // tiPackageID = SdbFindFirstTag(pdb, tiMatch, TAG_MSI_PACKAGE_ID); if (tiPackageID == TAGID_NULL) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to get msi package id, tagref = 0x%lx\n", trMatch)); return FALSE; } bSuccess = SdbReadBinaryTag(pdb, tiPackageID, (PBYTE)&pPackageInfo->guidMsiPackageID, sizeof(pPackageInfo->guidMsiPackageID)); if (!bSuccess) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to read MSI Package ID referenced by 0x%x\n", trMatch)); return FALSE; } tiExeID = SdbFindFirstTag(pdb, tiMatch, TAG_EXE_ID); if (tiExeID == TAGID_NULL) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to read TAG_EXE_ID for tagref 0x%x\n", trMatch)); return FALSE; } bSuccess = SdbReadBinaryTag(pdb, tiExeID, (PBYTE)&pPackageInfo->guidID, sizeof(pPackageInfo->guidID)); if (!bSuccess) { DBGPRINT((sdlError, "SdbGetMsiPackageInformation", "Failed to read EXE ID referenced by tagref 0x%x\n", trMatch)); return FALSE; } // // Set the flags to indicate whether apphelp or shims are available for // this package // note that shims/layers might be set for the subordinate actions and not // for the package itself. If custom_action tag exists however -- we need to check // with the full api later. // tiApphelp = SdbFindFirstTag(pdb, tiMatch, TAG_APPHELP); if (tiApphelp != TAGID_NULL) { pPackageInfo->dwPackageFlags |= MSI_PACKAGE_HAS_APPHELP; } // // Check to see whether we have any shims/layers // tiCustomAction = SdbFindFirstTag(pdb, tiMatch, TAG_MSI_CUSTOM_ACTION); if (tiCustomAction != TAGID_NULL) { pPackageInfo->dwPackageFlags |= MSI_PACKAGE_HAS_SHIMS; } return TRUE; } TAGREF SDBAPI SdbFindMsiPackageByID( IN HSDB hSDB, IN GUID* pguidID ) { TAGID tiMatch; PSDBCONTEXT pContext = (PSDBCONTEXT)hSDB; FIND_INFO FindInfo; TAGREF trMatch = TAGREF_NULL; // // We search only the LOCAL database in this case // tiMatch = SdbFindFirstGUIDIndexedTag(pContext->pdbLocal, TAG_MSI_PACKAGE, TAG_EXE_ID, pguidID, &FindInfo); if (tiMatch == TAGID_NULL) { return trMatch; } if (!SdbTagIDToTagRef(hSDB, pContext->pdbLocal, tiMatch, &trMatch)) { DBGPRINT((sdlError, "SdbFindMsiPackageByID", "Failed to convert tagid 0x%lx to tagref\n", tiMatch)); } return trMatch; } TAGREF SDBAPI SdbFindCustomActionForPackage( IN HSDB hSDB, IN TAGREF trPackage, IN LPCTSTR lpszCustomAction ) { PDB pdb = NULL; TAGID tiMatch = TAGID_NULL; TAGREF trReturn = TAGREF_NULL; TAGID tiCustomAction; if (!SdbTagRefToTagID(hSDB, trPackage, &pdb, &tiMatch)) { DBGPRINT((sdlError, "SdbFindCustomActionForPackage", "Failed to convert tagref 0x%lx to tagid\n", trPackage)); return TAGREF_NULL; } // // Now, for this tiMatch look for a custom action // tiCustomAction = SdbFindFirstNamedTag(pdb, tiMatch, TAG_MSI_CUSTOM_ACTION, TAG_NAME, lpszCustomAction); if (tiCustomAction != TAGID_NULL) { if (!SdbTagIDToTagRef(hSDB, pdb, tiCustomAction, &trReturn)) { DBGPRINT((sdlError, "SdbFindCustomActionForPackage", "Failed to convert tagid 0x%lx to tagref\n", tiCustomAction)); trReturn = TAGREF_NULL; } } return trReturn; }