以二进制形式查看MSI字符串

时间:2012-03-16 09:48:13

标签: wix windows-installer wix3.5 wix3.6

我希望在MSI以二进制模式运行时查看UI上显示的字符串/文本。

基本上我有本地化的wxl文件和本地化的msi。想要比较文本。

所以我的方法是查看要比较的字符串的二进制内容。任何人都可以建议我使用哪些工具?

使用orca我能够看到字符串。但我希望看到那些二进制/十六进制值。

非常感谢

最诚挚的问候, 马克

1 个答案:

答案 0 :(得分:5)

我不知道将字符串导出为 binary 的工具。在大多数情况下,实际上并不需要它。

如果您确实需要获取有关字符串的二进制信息,可以使用IStorage::OpenStreamIStream::Stat STATFLAG_NONAME参数和IStream::Read直接从MSI读取信息。有关stings的信息保存在名为“_StringData”和“_StringPool”的流中。流的名称以简单的方式编码。如果您有兴趣,我可以向您发布显示如何解码名称的代码。

更新:我从旧实用程序中准备了一个小型演示。该演示从“_StringData”和“_StringPool”加载字符串,并以可读格式转储信息。如果你调整行中的常量

bSuccess = LoadStringPool (pStg, TRUE, 80, 10, 10);

(见下文)您可以转储更多完整信息。以同样的方式,您可以轻松修改代码,将相应的流保存为二进制文件。

您在下面找到的C代码

#define STRICT
#define _WIN32_WINNT 0x501
#define COBJMACROS

#include <stdio.h>
#include <windows.h>
#include <ShLwApi.h>    // for wnsprintf
#include <malloc.h>     // for _alloca
#include <lmerr.h>
#include <tchar.h>
// IPropertyUI in <ShObjIdl.h>
//#include <msi.h>

#define ARRAY_SIZE(arr)     (sizeof(arr)/sizeof(arr[0]))
#define CONST_STR_LEN(s)    (ARRAY_SIZE(s) - 1)

#pragma comment (lib, "ole32.lib")
#pragma comment (lib, "ShLwApi.lib")

typedef struct tagMSISTRINGTABLE {
    UINT    cStrings;
    LPWSTR  pszStringData;      // have '\0' bytes between strings
    LPWSTR *ppszStringPool;     // array of pointers to the corresponding string in pszStringData data block
    WORD    cbStringIdSize;     // size of StringId in all tables in bytes. Typically if cStrings<32K, cbStringIdSize=2, then 3 or more.
                                // cbStringIdSize value will be calculated based on first bytes of _StringPool stream.
} MSISTRINGTABLE, *PMSISTRINGTABLE;

MSISTRINGTABLE g_StringTable = {0, NULL, NULL, 2};

#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}

MIDL_DEFINE_GUID (CLSID, CLSID_MsiTransform, 0x000c1082, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);  //.mst
MIDL_DEFINE_GUID (CLSID, CLSID_MsiDatabase,  0x000c1084, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);  //.msi, .msm
MIDL_DEFINE_GUID (CLSID, CLSID_MsiPatch,     0x000c1086, 0x0000, 0x0000, 0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);  //.msp

void DisplayErrorMessage (DWORD dwErrorCode, LPCTSTR pszTemplate, ...)
{
    va_list pa;
    TCHAR szText[1024]; // 1024 is the maximum which wsprintf and wvsprintf support
    LPTSTR  pErrorString;
    LPCTSTR pszErrorDll = NULL;
    HMODULE hModule = NULL;
    //DWORD dwFacility = HRESULT_FACILITY(dwErrorCode);

    va_start (pa, pszTemplate);
    wvnsprintf (szText, ARRAY_SIZE(szText), pszTemplate, pa);
    va_end (pa);

    // HRESULT_FROM_WIN32 HRESULT_FROM_SETUPAPI
    // Choose default Error DLL
    if (HRESULT_FACILITY(dwErrorCode) == FACILITY_WINDOWS ||
        (HRESULT_FACILITY(dwErrorCode) == FACILITY_WIN32 && HRESULT_SEVERITY(dwErrorCode) == SEVERITY_ERROR))
        dwErrorCode = HRESULT_CODE(dwErrorCode);
    else if (HRESULT_FACILITY(dwErrorCode) == FACILITY_INTERNET && dwErrorCode > INET_E_ERROR_FIRST && dwErrorCode < INET_E_ERROR_LAST)
        pszErrorDll = TEXT("UrlMon.dll");
    else if (HRESULT_FACILITY(dwErrorCode) == FACILITY_INTERNET && dwErrorCode > 0xC00CE000L && dwErrorCode < 0xC00CE5FFL)
        pszErrorDll = TEXT("msxmlr.dll");   // TEXT("msxmlr4.dll");
    else if (HRESULT_FACILITY(dwErrorCode) == FACILITY_MSMQ)
        pszErrorDll = TEXT("MQUtil.dll");
    else if (dwErrorCode >= NERR_BASE && dwErrorCode <= MAX_NERR)
        pszErrorDll = TEXT("NetMsg.dll");
    else if (dwErrorCode >= 0xC0040002L && dwErrorCode <= 0xC004001FL)
        pszErrorDll = TEXT("IoLogMsg.dll");
    else if ((LONG)dwErrorCode < 0)
        pszErrorDll = TEXT("ntdll.dll");

    // Load the DLL if needed
    if (pszErrorDll) {
        hModule = LoadLibraryEx (pszErrorDll, NULL, LOAD_LIBRARY_AS_DATAFILE);
        if (!hModule) {
            _tprintf (TEXT("Can not load DLL \"%s\" to display description for error 0x%08lX.\r\n"), pszErrorDll, dwErrorCode);
            //StringFormatedOutput (ERROR_OUTPUT, TEXT("Can not load DLL \"%s\" to display description for error 0x%08lX.\r\n"),
            //                      pszErrorDll, dwErrorCode);
            return;
        }
    }

    // Query Error text.
    // See Q149409 as an example.
    if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM |    // Always search in system message table !!!
                        FORMAT_MESSAGE_ALLOCATE_BUFFER |
                        FORMAT_MESSAGE_IGNORE_INSERTS |
                        (hModule ? FORMAT_MESSAGE_FROM_HMODULE : 0),
                        hModule,                // source of message definition
                        dwErrorCode,            // message ID
//                        0,                      // language ID
//                        GetUserDefaultLangID(), // language ID
//                        GetSystemDefaultLangID(),
                        MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
                        (LPTSTR)&pErrorString,   // pointer for buffer to allocate
                        0,                      // min number of chars to allocate
                        NULL)) {

        if (dwErrorCode & 0xC0000000) {
            _tprintf (szText);
            _tprintf (TEXT("Unknown error. Error code 0x%08lX.\r\n"), dwErrorCode);
            //StringFormatedOutput (ERROR_OUTPUT, TEXT("%sUnknown error. Error code 0x%08lX.\r\n"), szText, dwErrorCode);
        }
        else {
            _tprintf (szText);
            _tprintf (TEXT("Unknown error. Error code %lu.\r\n"), dwErrorCode);
            //StringFormatedOutput (ERROR_OUTPUT, TEXT("%sUnknown error. Error code %lu.\r\n"), szText, dwErrorCode);
        }
    }
    else {
        _tprintf (szText);
        _tprintf (pErrorString);
        _tprintf (TEXT("\r\n"));
        //StringFormatedOutput (ERROR_OUTPUT, TEXT("%s%s\r\n"), szText, pErrorString);
        LocalFree (pErrorString);
    }

    if (hModule)
        FreeLibrary (hModule);
}

// This function do almost the same as Base64 encoding used for example in MIME (see 6.8 in http://www.ietf.org/rfc/rfc2045.txt).
// Base64 convert codes from 0 till 63 (0x3F) to the corresponding character from the array 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// This function convert it to the corresponding character from the another array '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._'
static BYTE MsiBase64Encode (BYTE x)
{
    // 0-0x3F converted to '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._'
    // all other values higher as 0x3F converted also to '_'
    if (x < 10)
        return x + '0';             // 0-9 (0x0-0x9) -> '0123456789'
    else if (x < (10+26))
        return x - 10 + 'A';        // 10-35 (0xA-0x23) -> 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    else if (x < (10+26+26))
        return x - 10 - 26 + 'a';   // 36-61 (0x24-0x3D) -> 'abcdefghijklmnopqrstuvwxyz'
    else if (x == (10+26+26))       // 62 (0x3E) -> '.'
        return '.';
    else
        return '_';                 // 63-0xffffffff (0x3F-0xFFFFFFFF) -> '_'
}

#pragma warning (disable: 4706)
static UINT DecodeStreamName (LPWSTR pszInStreamName, LPWSTR pszOutStreamName)
{
    WCHAR ch;
    DWORD count = 0;

    while ((ch = *pszInStreamName++)) {
        if ((ch >= 0x3800) && (ch < 0x4840)) {
            // a part of Unicode charecterd used with CJK Unified Ideographs Extension A. (added with Unicode 3.0) used by
            // Windows Installer for encoding one or two ANSI characters. This subset of Unicode characters are not currently
            // used nether in "MS PMincho" or "MS PGothic" font nor in "Arial Unicode MS"
            if (ch >= 0x4800)   // 0x4800 - 0x483F
                // only one charecter can be decoded
                ch = (WCHAR) MsiBase64Encode ((BYTE)(ch - 0x4800));
            else {              // 0x3800 - 0x383F
                // the value contains two characters
                ch -= 0x3800;
                *pszOutStreamName++ = (WCHAR) MsiBase64Encode ((BYTE)(ch & 0x3f));
                count++;
                ch = (WCHAR) MsiBase64Encode ((BYTE)((ch >> 6) & 0x3f));
            }
        }
        // all characters lower as 0x3800 or higher or equel to 0x4840 will be saved without any decoding

        *pszOutStreamName++ = ch;
        count++;
    }
    *pszOutStreamName = L'\0';

    return count;
}
#pragma warning (default: 4706)

#define INVALID_DECODING_RESULT ((BYTE)(-1))
// This function do almost the same as Base64 decoding used for example in MIME (see 6.8 in http://www.ietf.org/rfc/rfc2045.txt).
// Base64 convert character from the array 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' to the corresponding codes from 0 till 63 (0x3F)
// This function convert character from the another array '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' to it to 0 till 63 (0x3F)
static BYTE MsiBase64Decode (BYTE ch)
// returns values 0 till 0x3F or 0xFF in the case of an error
{
    // only '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' are allowed and converted to 0-0x3F
    if ((ch>=L'0') && (ch<=L'9'))   // '0123456789' -> 0-9  (0x0-0x9)
        return ch-L'0';
    else if ((ch>=L'A') && (ch<=L'Z'))   // 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -> 10-35 (26 chars) - (0xA-0x23)
        return ch-'A'+10;
    else if ((ch>=L'a') && (ch<=L'z'))   // 'abcdefghijklmnopqrstuvwxyz' -> 36-61 (26 chars) - (0x24-0x3D)
        return ch-L'a'+10+26;
    else if (ch==L'.')
        return 10+26+26;        // '.' -> 62 (0x3E)
    else if (ch==L'_')
        return 10+26+26+1;      // '_' -> 63 (0x3F) - 6 bits
    else
        return INVALID_DECODING_RESULT; // other -> -1 (0xFF)
}

#define MAX_STREAM_NAME 0x1f

static void EncodeStreamName (BOOL bTable, LPCWSTR pszInStreamName, LPWSTR pszOutStreamName, UINT cchOutStreamName)
{
    LPWSTR pszCurrentOut = pszOutStreamName;

    if (bTable) {
         *pszCurrentOut++ = 0x4840;
         cchOutStreamName--;
    }

    while (cchOutStreamName--) {
        WCHAR ch = *pszInStreamName++;

        if (ch && (ch < 0x80) && (MsiBase64Decode((BYTE)ch) <= 0x3F)) {
            WCHAR chNext = *pszInStreamName;

            // MsiBase64Decode() convert any "standard" character '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._' to 0-0x3F.
            // One can pack two charecters together in 0-0xFFF. To do so, one needs convert the first one with respect of MsiBase64Decode(),
            // convert the next character also with respect MsiBase64Decode() and shift it 6 bits on the left. Two characters together
            // produce a value from 0 till 0xFFF. We add 0x3800 to the result. We receive a value between 0x3800 and 0x47FF
            if (chNext && (chNext < 0x80) && (MsiBase64Decode((BYTE)chNext) <= 0x3F)) {
                ch = (WCHAR)(MsiBase64Decode((BYTE)ch) + 0x3800 + (MsiBase64Decode((BYTE)chNext)<<6));
                pszInStreamName++;
            }
            else
                ch = MsiBase64Decode((BYTE)ch) + 0x4800;
        }
        *pszCurrentOut++ = ch;

        if (!ch)
            break;
    }
}

static HRESULT LoadStreamInMemory (IStorage *pStg, LPCWSTR pszStreamName, PBYTE *ppData, PUINT pSize)
{
    HRESULT hr = E_UNEXPECTED;
    IStream *pStm = NULL;

    // set defaults
    *ppData = NULL;
    *pSize = 0;

    __try {
        STATSTG stat;
        ULONG cbRead;

        hr = IStorage_OpenStream (pStg, pszStreamName, NULL, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStm);   // STGM_SHARE_EXCLUSIVE
        if (FAILED(hr)) {
            DisplayErrorMessage (hr, TEXT("Failed IStorage::OpenStream(). "));
            __leave;
        }

        hr = IStream_Stat (pStm, &stat, STATFLAG_NONAME);
        if (FAILED(hr)) {
            DisplayErrorMessage (hr, TEXT("Failed IStream::Stat(). "));
            __leave;
        }

        if (stat.cbSize.HighPart) {
            hr = HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER);
            __leave;
        }

        *pSize = stat.cbSize.LowPart;
        if (*pSize) {
            *ppData = (PBYTE) LocalAlloc (LMEM_FIXED, *pSize);
            if (!*ppData) {
                hr = HRESULT_FROM_WIN32 (ERROR_NOT_ENOUGH_MEMORY);
                __leave;
            }

            //r = IStream_Read (stm, pData, sz, &cbRead);
            hr = pStm->lpVtbl->Read (pStm, *ppData, *pSize, &cbRead);
            //hr = IStream_Read (pStm, *ppData, *pSize, &cbRead);
            if (FAILED(hr) || (cbRead != *pSize)) {
                *ppData = (PBYTE) LocalFree (*ppData);
                if (SUCCEEDED(hr))
                    hr = HRESULT_FROM_WIN32 (ERROR_INSUFFICIENT_BUFFER);
                __leave;
            }
            else 
                hr = S_OK;
        }
        else
            hr = S_OK;
    }
    __finally {
        if (pStm)
            IStream_Release (pStm);
    }

    return hr;
}

UINT DumpString (LPCWSTR pszString, LPCTSTR pszFormat, UINT nMaxLen)
{
    UINT cchPrinted = 0;
    LPWSTR pszText = (LPWSTR) _alloca (max(5,min((UINT)lstrlenW(pszString),nMaxLen)+1)*sizeof(WCHAR));

    if ((UINT)lstrlenW(pszString) <= nMaxLen)
        cchPrinted = _tprintf (pszFormat, pszString);
    else if (nMaxLen > 3) {
        lstrcpynW (pszText, pszString, nMaxLen-2);
        pszText[nMaxLen] = L'\0';
        pszText[nMaxLen-1] = L'.';
        pszText[nMaxLen-2] = L'.';
        pszText[nMaxLen-3] = L'.';
        cchPrinted = _tprintf (pszFormat, pszText);
    }
    else if (nMaxLen == 3) {
        pszText[0] = pszString[0];
        pszText[1] = pszString[1];
        pszText[2] = L'.';
        pszText[3] = L'\0';
        cchPrinted = _tprintf (pszFormat, pszText);
    }
    else if (nMaxLen == 2) {
        pszText[0] = pszString[0];
        pszText[1] = L'.';
        pszText[2] = L'\0';
        cchPrinted = _tprintf (pszFormat, pszText);
    }
    else if (nMaxLen == 1) {
        pszText[0] = pszString[0];
        pszText[1] = L'\0';
        cchPrinted = _tprintf (pszFormat, pszText);
    }

    return cchPrinted;
}

static HRESULT LoadTableFromStream (IStorage *pStg, LPCWSTR pszTableName, PBYTE *ppData, PUINT pSize)
{
    WCHAR szEncodedStreamName[32];
    HRESULT hr;

    EncodeStreamName (TRUE, pszTableName, szEncodedStreamName, ARRAY_SIZE(szEncodedStreamName));

    hr = LoadStreamInMemory (pStg, szEncodedStreamName, ppData, pSize);
    if (FAILED(hr))
        DisplayErrorMessage (hr, TEXT("Failed LoadStreamInMemory() for the table %ls. "), pszTableName);

    return hr;
}

BOOL LoadStringPool (IStorage *pStg, BOOL bDumpStringPool, UINT cMaxStrOutLen, UINT cMaxFirstRowsOut, UINT cMaxLastRowsOut)
{
    UINT nOffsetSrc = 0, nOffsetDest = 0, nStringPoolLength, nStringDataLength;
    UINT iStringId, iSrc, uBufferSize;
    PSTR pszStringData = NULL;
    struct _StringPool {
        WORD wLength;
        WORD wRefcnt;
    } *pStringPool = NULL;
    HRESULT hr;
    DWORD dwCodePage;
    BOOL bAllPrinted = TRUE;
    UINT cStringIdsPrinted = 0;

    hr = LoadTableFromStream (pStg, OLESTR("_StringPool"), (PBYTE *)&pStringPool, &nStringPoolLength);
    if (FAILED(hr))
        return FALSE;

    dwCodePage = pStringPool[0].wLength;
    if (pStringPool[0].wRefcnt == 0)
        g_StringTable.cbStringIdSize = 2;
    else if (pStringPool[0].wRefcnt == 0x8000)
        g_StringTable.cbStringIdSize = 3;

    if (bDumpStringPool)
        _tprintf (TEXT("\r\nString ID size: %d\r\n"), g_StringTable.cbStringIdSize);

    // convert bytes to indexes
    nStringPoolLength /= sizeof (struct _StringPool);

    hr = LoadTableFromStream (pStg, OLESTR("_StringData"), (PBYTE *)&pszStringData, &nStringDataLength);
    if (FAILED(hr))
        return FALSE;

    // Allocate buffer large enough to hold all strings from _StringData steam together with '\0' at the end of each string.
    // We allocate all memory in one block and not per string, to speed up allocation and to reduce overhead in heap menagement.
    uBufferSize = nStringDataLength + nStringPoolLength;
    g_StringTable.pszStringData = (PWSTR) LocalAlloc (LPTR, uBufferSize*sizeof(WCHAR));

    // allocate and initialize to NULL all pointers
    g_StringTable.ppszStringPool = (PWSTR *) LocalAlloc (LPTR, nStringPoolLength * sizeof (PWSTR *));

    if (bDumpStringPool) {
        _tprintf (TEXT("\r\nCode page of the string pool: %d\r\n"), dwCodePage);
        _tprintf (TEXT("+++String Pool Entries+++\r\n"));
    }

    for (iSrc=1, iStringId=1; iSrc<nStringPoolLength; iSrc++) {
        DWORD dwLen = pStringPool[iSrc].wLength;
        if (pStringPool[iSrc].wLength == 0) {
            // A string is lagrer as 64K. In the case one create one dummy entry with pStringPool[iStringId].wLength
            // and high word of string length saved in the next entry will be saved in pStringPool[iStringId].wRefcnt
            if (pStringPool[iSrc].wRefcnt == 0) // empty entry
                iStringId++;
            continue;
        }
        if (iSrc != 1 && pStringPool[iSrc-1].wLength == 0 && pStringPool[iSrc-1].wRefcnt != 0)
            // current string have length over 64K
            dwLen += pStringPool[iSrc-1].wRefcnt << 16; //* 0x10000;

        if (dwLen < uBufferSize) {
            MultiByteToWideChar (dwCodePage, MB_PRECOMPOSED | MB_ERR_INVALID_CHARS,
                                 pszStringData+nOffsetSrc, (int)dwLen,
                                 g_StringTable.pszStringData+nOffsetDest, uBufferSize);
            g_StringTable.pszStringData[nOffsetDest+dwLen] = L'\0';
            uBufferSize -= dwLen+1;
            g_StringTable.ppszStringPool[iStringId] = g_StringTable.pszStringData+nOffsetDest;
            if (bDumpStringPool) {
                //_tprintf (TEXT("\tId:%5d  Refcnt:%5d  String: %ls\r\n"), iStringId, pStringPool[iStringId].wRefcnt, g_StringTable.pszStringData+nOffsetDest);
                if (cStringIdsPrinted<cMaxFirstRowsOut || iStringId+cMaxLastRowsOut>=nStringPoolLength) {
                    _tprintf (TEXT("\tId:%5d  Refcnt:%5d  String: "), iStringId, pStringPool[iStringId].wRefcnt);
                    DumpString (g_StringTable.pszStringData+nOffsetDest, TEXT("%ls\r\n"), cMaxStrOutLen);
                    cStringIdsPrinted++;
                }
                else {
                    if (bAllPrinted)
                        _tprintf (TEXT("...\r\n"));

                    bAllPrinted = FALSE;
                }
            }
            iStringId++;
            nOffsetDest += dwLen+1;
        }
        nOffsetSrc += dwLen;
        if (nOffsetSrc >= nStringDataLength)
            break;
    }

    if (iStringId < nStringPoolLength)
        g_StringTable.cStrings = iStringId;
    else
        g_StringTable.cStrings = iStringId-1;

    return TRUE;
}

int _tmain (int argc, LPTSTR argv[])
{
    HRESULT hr = S_OK;
    IStorage *pStg = NULL;
    LPTSTR pszFileName;
    LPWSTR pszwFileName;
    BOOL bSuccess = FALSE;

    if (argc < 2) {
        _tprintf (TEXT("Usage:    GetMsiStringTable <filename>\r\n"));
        return 1;
    }

    pszFileName = argv[1];

#ifdef _UNICODE
    pszwFileName = pszFileName;
#else
    {
        DWORD cchLen = lstrlenA (pszFileName) + 1;
        pszwFileName = _alloca (cchLen*sizeof(WCHAR));
        MultiByteToWideChar (CP_ACP, MB_ERR_INVALID_CHARS | MB_PRECOMPOSED, pszFileName, -1, pszwFileName, cchLen);
    }
#endif

    __try {
        CLSID clsidStg;

        STGOPTIONS stgOption = {0};
        stgOption.usVersion = STGOPTIONS_VERSION;

        // Open the root storage.
        hr = StgOpenStorageEx (pszwFileName,
                               STGM_DIRECT_SWMR | STGM_READ | STGM_SHARE_DENY_NONE,
                               //STGM_DIRECT_SWMR | STGM_READ | STGM_SHARE_DENY_WRITE,
                               //STGM_READ|STGM_SHARE_EXCLUSIVE,
                               STGFMT_DOCFILE,  //STGFMT_ANY,
                               0,
                               &stgOption,      // NULL,
                               NULL,
                               &IID_IStorage,   // instaed of IID_IStorage it is possible to use IID_IPropertySetStorage
                               (PVOID *)&pStg);
        if (FAILED(hr)) {
            DisplayErrorMessage (hr, TEXT("Error: couldn't open storage \"%ls\". "), pszFileName);
            __leave;
        }

        hr = ReadClassStg (pStg, &clsidStg);
        if (SUCCEEDED(hr)) {
            // MsiInfo.exe
            //
            // Transform: Class Id for the MSI storage is {000C1082-0000-0000-C000-000000000046} CLSID_MsiTransform
            // MSI:       Class Id for the MSI storage is {000C1084-0000-0000-C000-000000000046} CLSID_MsiDatabase
            // Patch:     Class Id for the MSI storage is {000C1086-0000-0000-C000-000000000046} CLSID_MsiPatch
            OLECHAR szClsidStg[39];
            StringFromGUID2 (&clsidStg, szClsidStg, ARRAY_SIZE(szClsidStg));
            _tprintf (TEXT("Class Id for the storage is %ls:\r\n"), szClsidStg);

            if (IsEqualCLSID (&clsidStg, &CLSID_MsiDatabase))
                _tprintf (TEXT("\tStorage has MSI database/Merge module class id.\r\n"));
            else if (IsEqualCLSID (&clsidStg, &CLSID_MsiPatch))
                _tprintf (TEXT("\tStorage has MSI patch class id.\r\n"));
            else if (IsEqualCLSID (&clsidStg, &CLSID_MsiTransform))
                _tprintf (TEXT("\tStorage has MSI transform class id.\r\n"));
            else {
                _tprintf (TEXT("\tStorage is not a Windows Installer file.\r\n"));
                __leave;
            }
        }

        bSuccess = LoadStringPool (pStg, TRUE, 80, 10, 10);
        if (!bSuccess)
            __leave;

    }
    __finally {
        if (pStg)
            IStorage_Release (pStg);
    }

    return 0;
}

Visual Studio 2010 Ultimate的vs_setup.msi输出示例:

Class Id for the storage is {000C1084-0000-0000-C000-000000000046}:
        Storage has MSI database/Merge module class id.

String ID size: 3

Code page of the string pool: 1252
+++String Pool Entries+++
        Id:    1  Refcnt:  542  String: Name
        Id:    2  Refcnt:    7  String: Table
        Id:    4  Refcnt:    7  String: Type
        Id:    5  Refcnt:    5  String: _sqlAssembly
        Id:    6  Refcnt:    8  String: File_
        Id:    7  Refcnt:   18  String: MS.VS.vspGridControl.dll.27F9E354_F6F7_44D7_9637_42C9575D0C37
        Id:    8  Refcnt:    7  String: _sqlFollowComponents
        Id:    9  Refcnt:    2  String: FollowComponent
        Id:   10  Refcnt:   30  String: Component_
        Id:   11  Refcnt:    2  String: ParentComponent_
...
        Id:94617  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3205
        Id:94618  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3206
        Id:94619  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3207
        Id:94620  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3208
        Id:94621  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3209
        Id:94622  Refcnt:    3  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3210
        Id:94623  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3211
        Id:94624  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3212
        Id:94625  Refcnt:    1  String: Microsoft.VisualStudio.Diagnostics.ServiceModelSink.Behavior, Microsoft.Visua...
        Id:94626  Refcnt:    1  String: VS_Debugging_ServiceModelSink.MachineConfigV4.3213

我花了很多时间来了解如何解码大小为3字节的字符串ID,而不仅仅是2字节,这对于没有太长字符串表的小型设置来说是典型的。