问题
I wanted to view the strings/text that are/is displayed on the UI when MSI is run in binary mode.
basically i have the localized wxl files and the localized msi. wanted to compare the text.
So my approach is to view the binary content of the strings to compare. Can anyone please suggest me what tools i can use?
Using orca I am able to see the strings. But I would like to see the binary/hex value for those.
Thanks a lot
Best Regards, Marc
回答1:
I don't know tools which export strings as binary. In the most cases it is really not needed.
If you do need to get binary information about the strings you can use IStorage::OpenStream
, IStream::Stat
with STATFLAG_NONAME
parameter and IStream::Read
to read information from the MSI directly. The information about the stings are saved in streams with the name "_StringData" and "_StringPool". The names of the streams are in a simple way encoded. If you have an interest I could post you the code which shows how to decode the names.
UPDATED: I prepared small demo from my old utility. The demo loads the strings from the "_StringData" and "_StringPool" and dump the information in the readable format. If you adjust the constants in the line
bSuccess = LoadStringPool (pStg, TRUE, 80, 10, 10);
(see below) you can dump more full information. In the same way you can easy modify the code to save the corresponding streams in a file as binary.
The C code you find below
#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;
}
An example of the output of vs_setup.msi
of Visual Studio 2010 Ultimate:
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
I spend many time at that time to find out how to decode String ID which has size 3 bytes and not only 2 bytes which is typical for small setups with not so long string table.
来源:https://stackoverflow.com/questions/9734978/view-msi-strings-in-binary