EternalWindows
MSI インストール編 / コンポーネントの列挙

これまで見てきたように、コンポーネントはフィーチャに関連付けられており、 フィーチャがインストールされることによってコンポーネントはインストールされます。 しかし、フィーチャというのはあくまでコンポーネントを機能化/グループ化する概念であり、 それ自体は1つの製品内でのみでしか有効ではありません。 フィーチャはフィーチャIDという文字列で1つの製品内でのみ意味を持ちますが、 製品はグローバルに識別できなければなりませんから、GUIDで表現されています。

それでは、コンポーネントはどうなのでしょうか。 製品の一部と考えられるとはいえ、他のアプリケーションからも参照できるべきですから、 やはりこれも、グローバルに識別できなければなりません。 製品同様、コンポーネントはコンポーネントコードと呼ばれるGUID形式で表現され、 レジストリにて一元管理されています。 MsiEnumComponentsを呼び出せば、コンポーネントを列挙することができます。

UINT MsiEnumComponents(
  DWORD iComponentIndex,
  LPTSTR lpComponentBuf
);

iComponentIndexは、ゼロベースのコンポーネントのインデックスを指定します。 lpComponentBufは、コンポーネントコードを受け取るバッファのアドレスを指定します。

コンポーネントコードが一意であるためか、レジストリでは製品ごとにコンポーネントコードが 管理されているのではなく、全ての製品のコンポーネントコードはまとめて管理されています。 このため、MsiEnumComponentsは特定の製品に関連するコンポーネントコードは列挙できませんが、 コンポーネントコードから製品コードの取得はMsiGetProductCodeで実現できるため、 関連する製品を特定できる手段がないわけではありません。

UINT MsiGetProductCode(
  LPCTSTR szComponent,
  LPTSTR lpProductBuf
);

szComponentは、コンポーネントコードを指定します。 lpProductBufは、コンポーネントに関連する製品の製品コードが返ります。

コンポーネントから情報を取得したいような場合、即ちそれはファイルやレジストリキーから 情報を取得したいことを意味していますから、コンポーネントコードからファイルパス、 もしくはレジストリキーのパスを取得する必要があります。 これには、MsiGetComponentPathを呼び出します。

INSTALLSTATE MsiGetComponentPath(
  LPCTSTR szProduct,
  LPCTSTR szComponent,
  LPTSTR lpPathBuf,
  DWORD *pcchBuf
);

szProductは、製品コードを指定します。 szComponentは、コンポーネントコードを指定します。 lpPathBufは、コンポーネントが表すパスを受け取るバッファを指定します。 pcchBufは、バッファのサイズを格納した変数のアドレスを指定します。

今回のプログラムは、左側のリストボックスにコンポーネントコードを列挙し、 選択したコンポーネントのパスを右側のリストボックスに表示します。

#include <windows.h>
#include <msi.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 hwndListBoxLeft = NULL;
	static HWND hwndListBoxRight = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		UINT  uResult;
		DWORD i;
		TCHAR szComponentCode[256];

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		for (i = 0; i < 100; i++) {
			uResult = MsiEnumComponents(i, szComponentCode);
			if (uResult != ERROR_SUCCESS)
				break;
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szComponentCode);
		}

		return 0;
	}

	case WM_COMMAND: {
		int   nIndex;
		DWORD dwSize;
		TCHAR szBuf[256];
		TCHAR szProductCode[256];
		TCHAR szComponentCode[256];

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;

		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		nIndex = (int)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
		SendMessage(hwndListBoxLeft, LB_GETTEXT, nIndex, (LPARAM)szComponentCode);
	
		MsiGetProductCode(szComponentCode, szProductCode);

		dwSize = sizeof(szBuf);
		MsiGetComponentPath(szProductCode, szComponentCode, szBuf, &dwSize);

		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
	
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 2, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

MsiEnumComponentsは、WM_CREATEにて呼ばれています。

for (i = 0; i < 100; i++) {
	uResult = MsiEnumComponents(i, szComponentCode);
	if (uResult != ERROR_SUCCESS)
		break;
	SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szComponentCode);
}

列挙関数の使い方に関しては、これまでの要領と変わるところはありません。 for文にてiの上限を100としているのは、コンポーネントの数があまりにも膨大であり、 全てを列挙していると非常に時間が掛かってしまうためです。 当然ながら100という値に特に意味はありませんが、ここでは100とします。

WM_COMMANDにて、コンポーネントのパスを取得する部分は、次のようになっています。

MsiGetProductCode(szComponentCode, szProductCode);

dwSize = sizeof(szBuf);
MsiGetComponentPath(szProductCode, szComponentCode, szBuf, &dwSize);

まず、コンポーネントコードから製品コードを取得し、 その後、それらをMsiGetComponentPathに指定します。 もう1つの手段として、MsiLocateComponentという関数を呼び出せば、 事前に製品コードを取得せずコンポーネントのパスを取得できるのですが、 そのパスが必ずしも正確であるという保障はないため、あまり推奨することはできません。

コンポーネントのクライアント

製品が動作するために必要なコンポーネントは、場合によってはランタイムDLLのように、 既にディスク上に存在することがあると思われます。 このようなとき、そのコンポーネントが共有コンポーネントである場合は、 実際に製品に含まれているコンポーネントをインストールするのではなく、 共有コンポーネントの参照カウントをインクリメントするという処理が行われます。 共有コンポーネントのクライアントとは、正にこのインクリメントを行った製品のことであり、 製品がアンインストールされれば参照カウントはデクリメントされ、0になると破棄されます。 クライアントを列挙するMsiEnumClientsの呼び出しの例を次に示します。

for (i = 0;; i++) {
	uResult = MsiEnumClients(szComponentCode, i, szProductCode);
	if (uResult != ERROR_SUCCESS)
		break;
	SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szProductCode);
}

この例では、szComponentCodeで識別されるコンポーネントのクライアントを リストボックスに順に追加しようとしています。 コンポーネントに複数の製品が関連付けられているとなると、 MsiGetProductCodeが返す製品コードが何を基準に選ばれているかが気になりますが、 恐らく最初にコンポーネントをインストールした製品が返ると思われます。



戻る