【问题标题】:view MSI strings in binary以二进制形式查看 MSI 字符串
【发布时间】:2012-03-16 09:48:13
【问题描述】:

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

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

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

使用 orca 我可以看到字符串。但我想看看那些的二进制/十六进制值。

非常感谢

最好的问候, 马克

【问题讨论】:

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


    【解决方案1】:

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

    如果您确实需要获取有关字符串的二进制信息,您可以使用IStorage::OpenStreamIStream::StatSTATFLAG_NONAME 参数和IStream::Read 直接从MSI 读取信息。有关 sting 的信息保存在名为“_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 个字节,这对于字符串表不太长的小型设置来说是典型的。

    【讨论】:

    • 哇。这真的很棒。非常感谢您提供的信息。我对代码非常感兴趣。请张贴它:)
    • @Mark:我更新了我的答案并包含了我承诺的代码。我使用大约 8 年前编写的旧代码制作端口。我测试了我提出的代码,但不对输入参数进行任何分析。代码应该可以工作。
    猜你喜欢
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    • 1970-01-01
    • 2019-07-19
    • 2021-01-11
    • 2015-04-23
    • 2018-08-30
    • 1970-01-01
    相关资源
    最近更新 更多