EternalWindows
MSI データベース編 / レジストリキー解析

アプリケーションのインストール時にレジストリを変更するかどうかは、 開発者にもユーザーにも重要な点であるといえます。 ユーザーからすれば、いくらアンインストールで書き込みの跡形がなくなるといっても、 できればレジストリに変更を加えてほしくありませんし、 どのキーに値を書き込むかは基本的にマニュアルに記載されていませんから、 いくつかの不安を背負ってインストールすることもしばしあるかもしれません。 しかし、MSIファイルによるインストールでは、インストール情報がMSIファイルに 含まれているわけですから、どのレジストリに書き込みが行われるかという情報も 当然、MSIファイルに含まれているはずです。 Registryテーブルには、インストールの際に書き込まれるレジストリキーの情報が 格納されているため、これを解析することで、実際にインストールを開始する前に レジストリキーの書き込み場所を推測することができます。 次に、Registryテーブルに存在するカラムを示します。

カラム 意味
Registry レコードを一意に識別するプライマリキー。
Root どのルートキーを親とするかを示す値。
0, HKEY_CLASSES_ROOT
1, HKEY_CURRENT_USER
2, HKEY_LOCAL_MACHINE
3, HKEY_USERS
Key ルートキーを除いたレジストリキー。
Name レジストリエントリに設定する名前。 NULLの場合は、規定のエントリとして扱う。 ValueがNULLである場合、次の文字を指定することができる。 +, インストールの際、キーが存在しない場合は作成する。 -, アンインストールの際、サブキーも含めてキーを削除する。 *, +と-の両方の意味を持つ。
Value レジストリエントリに設定する値。
Component_ Componentテーブルの第1カラムに相当する外部キー。

今回のプログラムは、RegistryテーブルのRootからValueまでのカラムを参照し、 取得したキーをウインドウ左側のツリービューへ、取得した値をウインドウ右側の リストビューに追加します。

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

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

struct DATA {
	TCHAR szName[256];
	TCHAR szValue[256];
	struct DATA *lpNext;
};
typedef struct DATA DATA;
typedef struct DATA *LPDATA;

void CreateRegistryTree(HWND hwndTreeView, MSIHANDLE hView);
HTREEITEM GetRootItem(HWND hwndTreeView, int nRootItem);
HTREEITEM GetChildItem(HWND hwndTreeView, HTREEITEM hitemParent, LPTSTR lpszKey);
void SetItemParam(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName, LPTSTR lpszValue);
HTREEITEM InsertTreeItem(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName);
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 hwndTreeView = NULL;
	static HWND hwndListView = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		UINT                 uResult;
		LVCOLUMN             column;
		MSIHANDLE            hDatabase;
		MSIHANDLE            hView;
		INITCOMMONCONTROLSEX ic;
		
		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_TREEVIEW_CLASSES | ICC_LISTVIEW_CLASSES;
		InitCommonControlsEx(&ic);

		hwndTreeView = CreateWindowEx(WS_EX_DLGMODALFRAME, WC_TREEVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListView = CreateWindowEx(WS_EX_DLGMODALFRAME, WC_LISTVIEW, TEXT(""), WS_CHILD | WS_VISIBLE | LVS_REPORT, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
	
		column.mask    = LVCF_WIDTH | LVCF_TEXT;
		column.cx      = 200;
		column.pszText = TEXT("名前");
		ListView_InsertColumn(hwndListView, 0, &column);

		column.pszText = TEXT("データ");
		ListView_InsertColumn(hwndListView, 1, &column);
		
		uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_READONLY, &hDatabase);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		uResult = MsiDatabaseOpenView(hDatabase, TEXT("SELECT * FROM Registry"), &hView);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("ビューのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			MsiCloseHandle(hDatabase);
			return -1;
		}
		
		CreateRegistryTree(hwndTreeView, hView);
		
		MsiCloseHandle(hView);
		MsiCloseHandle(hDatabase);

		return 0;
	}

	case WM_NOTIFY: {
		int          i;
		LPDATA       lp;
		LVITEM       item;
		LPNMTREEVIEW lpnmTree = (LPNMTREEVIEW)lParam;

		if (lpnmTree->hdr.code == TVN_SELCHANGED) {
			ListView_DeleteAllItems(hwndListView);
			for (i = 0, lp = (LPDATA)lpnmTree->itemNew.lParam; lp != NULL; i++, lp = lp->lpNext) {
				item.mask     = LVIF_TEXT;
				item.iItem    = i;
				item.iSubItem = 0;
				item.pszText  = lp->szName;
				ListView_InsertItem(hwndListView, &item);
				
				item.iSubItem = 1;
				item.pszText  = lp->szValue;
				ListView_SetItem(hwndListView, &item);
			}
			
			return 0;
		}

		break;
	}
	
	case WM_SIZE:
		MoveWindow(hwndTreeView, 0, 0, LOWORD(lParam) / 2, HIWORD(lParam), TRUE);
		MoveWindow(hwndListView, 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);
}

void CreateRegistryTree(HWND hwndTreeView, MSIHANDLE hView)
{
	int       i;
	int       nRootItem;
	UINT      uResult;
	DWORD     dwSize;
	TCHAR     szKey[256];
	TCHAR     szName[256];
	TCHAR     szValue[256];
	LPTSTR    lp;
	HTREEITEM hitem;
	MSIHANDLE hRecord;
	
	MsiViewExecute(hView, 0);

	for (;;) {
		uResult = MsiViewFetch(hView, &hRecord);
		if (uResult != ERROR_SUCCESS)
			break;

		nRootItem = MsiRecordGetInteger(hRecord, 2);
		dwSize = sizeof(szKey);
		MsiRecordGetString(hRecord, 3, szKey, &dwSize);
		dwSize = sizeof(szName);
		MsiRecordGetString(hRecord, 4, szName, &dwSize);
		dwSize = sizeof(szValue);
		MsiRecordGetString(hRecord, 5, szValue, &dwSize);
		MsiCloseHandle(hRecord);

		hitem = GetRootItem(hwndTreeView, nRootItem);

		for (i = 0, lp = szKey;;) {
			if (lp[i] == '\\') {
				lp[i] = '\0';
				hitem = GetChildItem(hwndTreeView, hitem, lp);
				lp = lp + (i + 1);
				i = 0;
			}
			else if (lp[i] == '\0') {
				hitem = GetChildItem(hwndTreeView, hitem, lp);
				SetItemParam(hwndTreeView, hitem, szName, szValue);
				break;
			}
			else
				i++;
		}

	}
}

HTREEITEM GetRootItem(HWND hwndTreeView, int nRootItem)
{
	static HTREEITEM hitem[] = {NULL, NULL, NULL, NULL};
	LPTSTR lpszRootName[] = {
		TEXT("HKEY_CLASSES_ROOT"), TEXT("HKEY_CURRENT_USER"),
		TEXT("HKEY_LOCAL_MACHINE"), TEXT("HKEY_USERS")
	};
	
	if (hitem[nRootItem] == NULL)
		hitem[nRootItem] = InsertTreeItem(hwndTreeView, TVI_ROOT, lpszRootName[nRootItem]);

	return hitem[nRootItem];
}

HTREEITEM GetChildItem(HWND hwndTreeView, HTREEITEM hitemParent, LPTSTR lpszKey)
{
	BOOL      bFirst = TRUE;
	BOOL      bExistItem = FALSE;
	TCHAR     szBuf[256];
	TVITEM    item;
	HTREEITEM hitem = NULL;

	for (;;) {
		if (bFirst) {
			hitem = TreeView_GetChild(hwndTreeView, hitemParent);
			if (hitem == NULL)
				break;
			bFirst = FALSE;
		}
		else {
			hitem = TreeView_GetNextSibling(hwndTreeView, hitem);
			if (hitem == NULL)
				break;
		}

		item.mask       = TVIF_HANDLE | TVIF_TEXT;
		item.hItem      = hitem;
		item.pszText    = szBuf;
		item.cchTextMax = sizeof(szBuf);
		TreeView_GetItem(hwndTreeView, &item);

		if (lstrcmp(lpszKey, item.pszText) == 0) {
			bExistItem = TRUE;
			break;
		}
	}

	if (!bExistItem)
		hitem = InsertTreeItem(hwndTreeView, hitemParent, lpszKey);

	return hitem;
}

void SetItemParam(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName, LPTSTR lpszValue)
{
	LPDATA lpHeader;
	LPDATA lp;
	TVITEM item;
	
	item.mask  = TVIF_HANDLE | TVIF_PARAM;
	item.hItem = hitem;
	TreeView_GetItem(hwndTreeView, &item);
	
	lpHeader = (LPDATA)item.lParam;

	if (lpHeader == NULL) {
		lpHeader = (LPDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DATA));
		lp = lpHeader;
	}
	else {
		for (lp = lpHeader; lp->lpNext != NULL; lp = lp->lpNext)
			;
		lp->lpNext = (LPDATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DATA));
		lp = lp->lpNext;
	}

	if (lpszName[0] == '\0')
		lstrcpy(lp->szName, TEXT("(既定)"));
	else
		lstrcpy(lp->szName, lpszName);
	lstrcpy(lp->szValue, lpszValue);

	item.mask   = TVIF_HANDLE | TVIF_PARAM;
	item.hItem  = hitem;
	item.lParam = (LPARAM)lpHeader;
	TreeView_SetItem(hwndTreeView, &item);
}

HTREEITEM InsertTreeItem(HWND hwndTreeView, HTREEITEM hitem, LPTSTR lpszName)
{
	TVINSERTSTRUCT tvis;

	tvis.hParent         = hitem;
	tvis.item.mask       = TVIF_TEXT;
	tvis.item.pszText    = lpszName;
	tvis.item.cchTextMax = 256;
	
	return TreeView_InsertItem(hwndTreeView, &tvis);
}

WM_CREATEにて呼ばれるCreateRegistryTreeという自作関数は、 Registryテーブルのレコードを取得し、フィールド値を基にツリービューに アイテムを追加する関数を呼び出していきます。 まず、取得したRootが既にツリービューにアイテムとして追加されているかを GetRootItemで調べ、追加されていないのであればInsertTreeItemで新しく追加し、 既に存在する場合は保存しているハンドルを返すようにします。

続いて、取得したKeyを\を区切りとして階層を成せるように、文字を順番に走査します。 \\が現れたら、それを\0文字とすることで一時的に一個の文字列と見なすようにし、 GetChildItemでサブキーとしてアイテムを追加することになります。 この関数では、最初のループでhitemParentの下位のアイテムに潜り、 それ以降はその階層にて同名のアイテムが存在するかを探すことになります。 存在しない場合は、hitemParentを親として新しくアイテムを追加します。 関数から制御が返ったら一階層処理をしたということで、 カウントした文字数に1を足すことで先の\を飛ばし、次の\を探す処理に入ります。 最終的に\0文字に達した場合は、目的のアイテムが追加されたということで、 そのアイテムにNameとValueをSetItemParamで関連付けます。 方法としては、アイテムのハンドルからパラメータ(TVIF_PARAM)を取得し、 何も関連付けられていない場合は関連付け用の自作構造体を確保、 関連付けられている場合は、既存のリスト構造の最後に追加するようにします。 自作構造体にNameとValueを指定したら、それを含むリストの先頭アドレスを アイテムのパラメータとして関連付けます。

ツリービューでアイテムが選択されたかどうかは、 WM_NOTIFYでTVN_SELCHANGEDを捕らえることで特定できます。 リストビューの内容はアイテムによって異なるため、 まず、ListView_DeleteAllItemsで全てのアイテムを削除し、 その後、SetItemParamで設定したリストの個々の構造体のメンバを リストビューに追加していくことになります。 先頭のリストアイテムはiSubItemを0としてListView_InsertItemを呼び出し、 2番目のアイテムはiSubItemを1としてListView_SetItemを呼び出します。

Registryテーブルの限界

Registryテーブルにはインストールの際に書き込まれる情報が格納されていますが、 たとえば、このテーブルの内容が空の場合や存在しなかった場合では、 レジストリが一切変更されないのかというとそうではありません。 MSIでは、インストールしたアプリケーションを次に示すレジストリキーに 書き込むことになっているからです。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

正確にはMSIは自身の専用のレジストリにも書き込みを行うのですが、 いずれも、アンインストールの際にキーが削除されることは保障されています。 実際の問題として、レジストリの書き込みというのは、 インストールの際ではなくアプリケーションが始めて実行されたときでも可能で、 たとえば、Orcaの場合では次に示すキーに書き込みを行っています。

HKEY_CURRENT_USER\Software\Microsoft\Orca

このキーはあくまでOrcaの実行時にて作成されたものですから、 Orcaをアンインストールしても削除されることはありません。 もし、Registryテーブルにこのキーを*を名前として追加していれば、 キーは削除されることになっていたでしょう。 Registryテーブルが推測できるレジストリ書き込みは、 インストール時のみだということを忘れないでください。



戻る