以二进制形式查看 MSI 字符串 [英] view MSI strings in binary
问题描述
当 MSI 以二进制模式运行时,我想查看显示在 UI 上的字符串/文本.
I wanted to view the strings/text that are/is displayed on the UI when MSI is run in binary mode.
基本上我有本地化的 wxl 文件和本地化的 msi.想比较文字.
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?
使用 orca 我能够看到字符串.但我想看看那些二进制/十六进制值.
Using orca I am able to see the strings. But I would like to see the binary/hex value for those.
非常感谢
最好的问候,马克
推荐答案
我不知道将字符串导出为二进制的工具.大多数情况下确实不需要.
I don't know tools which export strings as binary. In the most cases it is really not needed.
如果确实需要获取有关字符串的二进制信息,可以使用 IStorage::OpenStream
、IStream::Stat
和 STATFLAG_NONAME
参数和 IStream::Read
直接从 MSI 读取信息.有关字符串的信息保存在名称为_StringData"和_StringPool"的流中.流的名称以简单的方式编码.如果您有兴趣,我可以向您发布显示如何解码名称的代码.
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.
更新:我从我的旧实用程序中准备了小演示.该演示从_StringData"和_StringPool"加载字符串并以可读格式转储信息.如果调整行中的常量
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.
您在下面找到的 C 代码
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;
}
Visual Studio 2010 Ultimate vs_setup.msi
的输出示例:
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
当时我花了很多时间来了解如何解码具有 3 个字节的字符串 ID,而不仅仅是 2 个字节,这对于没有那么长字符串表的小型设置来说是典型的.
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.
这篇关于以二进制形式查看 MSI 字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!