EternalWindows
MSI データベース編 / サマリー情報

MSIファイルに用いられるテーブルやレコードといった概念は、 ある意味で目的の情報の所在を複雑にしているといえます。 しかしながら、ある一定の比較的よく参照する情報を個別に管理していれば、 開発者もユーザーも、その情報へのアクセスはテーブルを介するよりも容易になるでしょう。 サマリー情報とは、製品会社などの簡略な製品情報をまとめたもので、 これを利用することで、たとえばPropertyテーブルのManufacturerという列の フィールド値を取得するといったような手順を踏む必要がなくなります。 サマリー情報へのアクセスは、MsiGetSummaryInformationの呼び出しから始まります。

UINT MsiGetSummaryInformation(
  MSIHANDLE hDatabase,
  LPCTSTR szDatabasePath,
  UINT uiUpdateCount,
  MSIHANDLE *phSummaryInfo
);

hDatabaseは、データベースのハンドルを指定します。 szDatabasePathにMSIファイルのパスを指定する場合は、NULLを指定することができます。 szDatabasePathは、MSIファイルのパスを指定します。 サマリー情報へのアクセスだけを考える場合は、この引数にパスを指定してhDatabaseに NULLを指定するのですが、他のMSI関数の利用のために既にデータベースのハンドルを オープンしているのであれば、この引数にNULLを指定してhDatabaseにハンドルを指定します。 uiUpdateCountは、サマリー情報を読み取るだけの場合は0を指定してください。 phSummaryInfoは、MSIHANDLE型変数のアドレスを指定します。 このアドレスに、サマリー情報へアクセスするためのハンドルが返ります。

サマリー情報に含まれる個々のデータは、プロパティという単位で区切られています。 プロパティはIDによって識別され、プロパティ毎にデータの型も異なります。 これらの情報は、マイクロソフトのリファレンスに掲載されている プロパティセットとよばれる表から参照することができます。

プロパティ名 プロパティID タイプ
Codepage 1 VT_I2
Title 2 VT_LPSTR
Subject 3 VT_LPSTR
Author 4 VT_LPSTR
Keywords 5 VT_LPSTR
Comments 6 VT_LPSTR
Template 7 VT_LPSTR
Last Saved By 8 VT_LPSTR
Revision Number 9 VT_LPSTR
Last Printed 11 VT_FILETIME
Create Time/Date 12 VT_FILETIME
Last Saved Time/Date 13 VT_FILETIME
Page Count 14 VT_I4
Word Count 15 VT_I4
Character Count 16 VT_I4
Creating Application 18 VT_LPSTR
Security 19 VT_I4

表の見方としては、たとえばAuthorプロパティの値を取得するのであれば、 IDが4であり、データの型がVT_LPSTR(文字列型)であると分かります。 プロパティは、MsiSummaryInfoGetPropertyで取得することができます。

UINT MsiSummaryInfoGetProperty(
  MSIHANDLE hSummaryInfo,
  UINT uiProperty,
  UINT *puiDataType,
  INT *piValue,
  FILETIME *pftValue,
  LPTSTR szValueBuf,
  DWORD *pcchValueBuf
);

hSummaryInfoは、サマリー情報のハンドルを指定します。 uiPropertyは、プロパティのIDを指定します。 puiDataTypeは、VT_I4やVT_LPSTRといったタイプを受け取る変数のアドレスを指定します。 piValueは、数値を受け取る変数のアドレスを指定します。 VT_I2とVT_I4のタイプのプロパティを取得する場合は、この引数を参照します。 pftValueは、FILETIME構造体を受け取る変数のアドレスを指定します。 VT_FILETIMEのプロパティを取得する場合は、この引数を参照します。 szValueBufは、データを表す文字列を受け取るバッファのアドレスを指定します。 VT_LPSTRのプロパティを取得する場合は、この引数を参照します。

今回のプログラムは、MsiSummaryInfoGetPropertyで取得したプロパティの値を リストボックスで表示します。

#include <windows.h>
#include <msiquery.h>

#pragma comment (lib, "msi.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		UINT       i;
		UINT       uResult;
		UINT       uDataType;
		int        nValue;
		TCHAR      szValue[256];
		TCHAR      szBuf[256];
		DWORD      dwSize;
		FILETIME   fileTime;
		SYSTEMTIME systemTime;
		MSIHANDLE  hSummaryInfo;

		LPTSTR lpszPropertyName[] = {
			TEXT("Codepage"), TEXT("Title"), TEXT("Subject "), TEXT("Author"),
			TEXT("Keywords"), TEXT("Comments"), TEXT("Template"), TEXT("Last Saved By"),
			TEXT("Revision Number"), TEXT("Last Printed"), TEXT("Create Time/Date"), TEXT("Last Saved Time/Date"),
			TEXT("Page Count"), TEXT("Word Count"), TEXT("Character Count"), TEXT("Creating Application"),
			TEXT("Security")
		};
		int nPropertyId[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 19};
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		uResult = MsiGetSummaryInformation(NULL, TEXT("sample.msi"), 0, &hSummaryInfo);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("サマリー情報のハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		for (i = 0; i < 17; i++) {
			dwSize = sizeof(szValue);
			MsiSummaryInfoGetProperty(hSummaryInfo, nPropertyId[i], &uDataType, &nValue, &fileTime, szValue, &dwSize);
			if (uDataType == VT_I2 || uDataType == VT_I4)
				wsprintf(szBuf, TEXT("%s : %d"), lpszPropertyName[i], nValue);
			else if (uDataType == VT_FILETIME) {
				FileTimeToSystemTime(&fileTime, &systemTime);
				wsprintf(szBuf, TEXT("%s : %d年%d月%d日"), lpszPropertyName[i], systemTime.wYear, systemTime.wMonth, systemTime.wDay);
			}
			else if (uDataType == VT_LPSTR)
				wsprintf(szBuf, TEXT("%s : %s"), lpszPropertyName[i], szValue);
			else if (uDataType == VT_EMPTY)
				wsprintf(szBuf, TEXT("%s : "), lpszPropertyName[i]);
			else
				;
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}

		MsiCloseHandle(hSummaryInfo);

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), FALSE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

個々のプロパティをループ文で統一的に扱うために、 WM_CREATEではnPropertyIdという全てのプロパティのIDを配列で宣言しています。 一見するとこの配列の要素数は19に思えますが、実際にはIDが10と17であるプロパティは 存在しないため、要素数は17となります。 そのため、ループ文の条件式では17を指定します。 プロパティの数をMsiSummaryInfoGetPropertyCountという関数で取得していないのは、 この関数が返す数が何故か15となっているからです。 次に、ループ文の内部を示します。

dwSize = sizeof(szValue);
MsiSummaryInfoGetProperty(hSummaryInfo, nPropertyId[i], &uDataType, &nValue, &fileTime, szValue, &dwSize);
if (uDataType == VT_I2 || uDataType == VT_I4)
	wsprintf(szBuf, TEXT("%s : %d"), lpszPropertyName[i], nValue);
else if (uDataType == VT_FILETIME) {
	FileTimeToSystemTime(&fileTime, &systemTime);
	wsprintf(szBuf, TEXT("%s : %d年%d月%d日"), lpszPropertyName[i], systemTime.wYear, systemTime.wMonth, systemTime.wDay);
}
else if (uDataType == VT_LPSTR)
	wsprintf(szBuf, TEXT("%s : %s"), lpszPropertyName[i], szValue);
else if (uDataType == VT_EMPTY)
	wsprintf(szBuf, TEXT("%s : "), lpszPropertyName[i]);
else
	;

MsiSummaryInfoGetPropertyに指定するプロパティIDが常に変化することから、 受け取るデータのタイプも一定ではないことになります。 このため、どのタイプにも対応できるように全ての引数にアドレスを指定しておき、 uDataTypeで取得したタイプの値を確認してから、個々のタイプの処理を行うようにしています。 wsprintfで作成する文字列のフォーマットは、:を挟んで左側にプロパティの名前を、 右側にプロパティの値が設定されます。

サマリー情報の位置づけ

エクスプローラ上などからMSIファイルのプロパティを開いて概要タブを確認してみると、 その内容がMsiGetSummaryInformationで取得できるサマリー情報と一致することが分かります。 一般に、プロパティの概要タブの値を変更するには、構造化ストレージという機能を 用いることになりますが、MSIファイルの場合はサマリー情報へアクセスする関数を 呼び出すことで、概要タブの値を変更することができます。 だたし、インストールの際に表示されるタイトルや作成者名は、 サマリー情報ではなくPropertyテーブルのものが参照されるため、 この点については意識しておかなければなりません。

MsiGetSummaryInformation(NULL, TEXT("sample.msi"), 1, &hSummaryInfo);
MsiSummaryInfoSetProperty(hSummaryInfo, 2, VT_LPSTR, 0, NULL, TEXT("NewTitle"));
MsiSummaryInfoPersist(hSummaryInfo);

MsiSummaryInfoSetPropertyは、第2引数に指定されたIDで示されるプロパティを 第3引数で示された型の引数で上書きします。 VT_LPSTR(文字列型)の引数は第6引数であるため、この場合、NewTitleという文字列が IDが2であるTitleプロパティに書き込まれることになります。 ただし、実際に書き込みがファイルに反映されるのは、 MsiGetSummaryInformationの第3引数で書き込み回数を指定し、 一連の書き込みが終了したときにMsiSummaryInfoPersistを呼び出しときのみです。 この最後のMsiSummaryInfoPersistの呼び出しを忘れてしまうと、 サマリー情報の全ての内容が失われてしまうので注意してください。



戻る