/*++

    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 };
#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, CHARCOUNT(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) {

            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, CHARCOUNT(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: ?
--*/
{
    //
    // 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;
}