EternalWindows
独自インストール / レジストリ登録

前節では、ファイルのコピーを行うためのスレッドを作成し、 コピーが終了した場合はWM_ENDOPERATIONというメッセージをポストしました。 今回はこのメッセージを処理し、レジストリの書き込みを行います。

case WM_ENDOPERATION: {
	BOOL  bCancel = (BOOL)wParam;
	WCHAR szInstallDir[MAX_PATH];
	WCHAR szMainFilePath[MAX_PATH];
	WCHAR szUninstFilePath[MAX_PATH];
	
	if (bCancel) {
		MessageBox(NULL, TEXT("インストールをキャンセルしました。"), TEXT("OK"), MB_OK);
		PostMessage(hwnd, WM_CLOSE, 0, 0);
		return 0;
	}

	wsprintfW(szInstallDir, L"%s\\%s", g_szTargetFolder, g_szProductName);
	wsprintfW(szMainFilePath, L"%s\\%s", szInstallDir, g_szMainExeName);
	wsprintfW(szUninstFilePath, L"%s\\%s", szInstallDir, g_szUninstExeName);

	if (!Register(szInstallDir, szUninstFilePath)) {
		MessageBox(NULL, TEXT("レジストリ登録に失敗しました。"), NULL, MB_ICONWARNING);
		PostMessage(hwnd, WM_CLOSE, 0, 0);
		return 0;
	}

	if (IsDlgButtonChecked(hwnd, ID_CHECK_DESKTOP))
		CreateDesktopShortcut(szMainFilePath);

	if (IsDlgButtonChecked(hwnd, ID_CHECK_STARTMENU))
		CreateStartMenuShortcut(szMainFilePath, szUninstFilePath);

	MessageBox(hwnd, TEXT("インストールが終了しました。"), TEXT("OK"), MB_OK);

	PostMessage(hwnd, WM_CLOSE, 0, 0);

	return 0;
}

wParamが0でない場合はファイルのコピーがキャンセルをされたことを意味するため、 インストールを続行しないようにします。 g_szTargetFolderにはインストール先フォルダのパスが格納されており、これとg_szProductNameを連結すれば、 コピーによって作成されたフォルダのパスが完成します。 この中にはsample.exeとuninst.exeが存在するはずですが、それらのパスはszMainFilePathとszUninstFilePathで識別されます。 Registerはレジストリへの書き込みを行う自作関数であり、CreateDesktopShortcutやCreateStartMenuShortcutはショートカットを作成する自作関数です。 この2つの関数はチェックボックスにチェックが付いている場合に呼び出すべきですから、 IsDlgButtonCheckedでそれを確認しています。

今回のインストーラーでは、レジストリへの書き込みを2つの目的で行います。 1つは、アプリケーションフォルダがコピーされたパスを保存するためであり、 アンインストール時はこのパスを参照して削除を行います。 そしてもう1つは、コントロールパネルから「プログラムのアンインストール」を実行できるようにするためです。 このためには、次のレジストリキー以下に独自のサブキーを作成しなければなりません。

HKEY_LOCAL_MACHINE\Microsoft\Windows\CurrentVersion\Uninstall

作成したサブキーのエントリには、DisplayNameとUninstallStringを必ず含めるようにします。 DisplayNameは「プログラムのアンインストール」にて表示され、 そこでアンインストールを選択することにより、UninstallStringに格納されたファイルパスが実行されます。 HKEY_LOCAL_MACHINE\SOFTWAREは通常のユーザーでも書き込めますが、 上記キーは管理者でなければ書き込めない点に注意してください。

レジストリキーの作成やレジストリエントリの作成に失敗した場合、 インストールを続行できないということで、それまで作成したキーやエントリを削除するべきといえます。 こうした削除は明示的に行うことも可能ですが、 Windows Vistaから登場したKTMを使用するほうが効率的でしょう。 KTMは一連の操作をトランザクションとして定義し、もし一連の操作をなかったことにしたい場合は、 ロールバックと呼ばれる操作を行うだけで済みます。 一方、一連の操作を実際に反映させたい場合は、コミットと呼ばれる操作を行います。

BOOL Register(LPWSTR lpszInstallDir, LPWSTR lpszUninstFilePath)
{
	HKEY   hKey;
	DWORD  dwDisposition;
	WCHAR  szKey[256];
	LONG   lResult;
	HANDLE hTransaction;

	hTransaction = CreateTransaction(NULL, NULL, 0, 0, 0, 0, L"MyTransaction");
	if (hTransaction == NULL)
		return FALSE;

	wsprintfW(szKey, L"SOFTWARE\\%s", g_szProductName);
	lResult = RegCreateKeyTransactedW(HKEY_LOCAL_MACHINE, szKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, &dwDisposition, hTransaction, NULL);
	if (lResult != ERROR_SUCCESS) {
		CloseHandle(hTransaction);
		return FALSE;
	}
	
	lResult = RegSetValueExW(hKey, L"InstallDir", 0, REG_SZ, (LPBYTE)lpszInstallDir, lstrlenW(lpszInstallDir) * sizeof(WCHAR));
	RegCloseKey(hKey);
	if (lResult != ERROR_SUCCESS) {
		RollbackTransaction(hTransaction);
		CloseHandle(hTransaction);
		return FALSE;
	}

	wsprintfW(szKey, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s", g_szProductName);
	lResult = RegCreateKeyTransactedW(HKEY_LOCAL_MACHINE, szKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, &dwDisposition, hTransaction, NULL);
	if (lResult != ERROR_SUCCESS) {
		RollbackTransaction(hTransaction);
		CloseHandle(hTransaction);
		return FALSE;
	}
	
	lResult = RegSetValueExW(hKey, L"DisplayName", 0, REG_SZ, (LPBYTE)g_szDisplayName, lstrlenW(g_szDisplayName) * sizeof(WCHAR));
	if (lResult != ERROR_SUCCESS) {
		RegCloseKey(hKey);
		RollbackTransaction(hTransaction);
		CloseHandle(hTransaction);
		return FALSE;
	}

	lResult = RegSetValueExW(hKey, L"UninstallString", 0, REG_SZ, (LPBYTE)lpszUninstFilePath, lstrlenW(lpszUninstFilePath) * sizeof(WCHAR));
	RegCloseKey(hKey);
	if (lResult != ERROR_SUCCESS) {
		RollbackTransaction(hTransaction);
		CloseHandle(hTransaction);
		return FALSE;
	}

	CommitTransaction(hTransaction);
	CloseHandle(hTransaction);

	return TRUE;
}

CreateTransactionを呼び出せば、トランザクションに使用するハンドルを取得できます。 この関数の引数は全てオプションであるため、NULLや0を指定して問題ありません。 レジストリキーの作成には従来のRegCreateKeyExではなく、 トランザクションのハンドルを受け取るRegCreateKeyTransactedを呼び出します。 トランザクションを使用する場合はコミットを行うまで操作が反映されませんから、 関数が成功した時点ではレジストキーが作成されないことに注意してください。 RegSetValueExでは、先のキーのエントリとしてアプリケーションフォルダのパスを格納しています。 これに失敗した場合は、前に行った操作をキャンセルするべきですからRollbackTransactionを呼び出します。 2回目のRegCreateKeyTransactedは、「プログラムのアンインストール」に対応するために呼び出しています。 このキーにはDisplayNameとUninstallStringが必要ですから、それぞれRegSetValueExを呼び出します。 全ての処理が問題なく成功した場合はCommitTransactionを呼び出します。 これにより、これまで行ってきた操作が実際にレジストリへ反映されます。

前節ではフォルダのコピーを行っていましたが、これをトランザクションに関連して行うにはどうすればよいのでしょうか。 Windows VistaにはCopyFileTransactedという関数が用意されているため、 これをフォルダ内のアイテム1つずつに対して実行する方法が考えられます。 そして、インストールが問題なく成功した場合はコミットを実行し、 失敗した場合はロールバックを実行すればよいでしょう。 コピー中にプログレスダイアログを表示したい場合は、 IOperationsProgressDialog(従来のIProgressDialogでも可)が役に立ちます。

Uninstallキーのサブキー

アプリケーションを「プログラムのアンインストール」から削除するためには、 既に述べたUninstallキーに独自のサブキーを作成することになります。 この事を考えると、Uninstallキー以下のサブキーを列挙することで、 アンインストール可能なアプリケーションの一覧を列挙できるように思えます。 次にこの例を示します。

#include <windows.h>

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: {
		DWORD i;
		HKEY  hKey;
		LONG  lResult;
		TCHAR szName[256];
		DWORD dwName;
		
		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);
	
		lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"), 0, KEY_ENUMERATE_SUB_KEYS, &hKey);
		if (lResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("レジストリキーのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		for (i = 0;; i++) {
			dwName = sizeof(szName);
			lResult = RegEnumKeyEx(hKey, i, szName, &dwName, NULL, NULL, NULL, NULL);
			if (lResult != ERROR_SUCCESS)
				break;

			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szName);
		}

		RegCloseKey(hKey);

		return 0;
	}

	case WM_COMMAND: {
		int   i;
		int   nIndex;
		HKEY  hKey;
		LONG  lResult;
		DWORD dwType;
		DWORD dwSize;
		TCHAR szKey[256];
		TCHAR szData[256];
		TCHAR szBuf[256];
		LPTSTR lpszEntryName[] = {
			TEXT("DisplayName"), TEXT("DisplayIcon"), TEXT("UninstallString")
		};
		int nEntryCount = sizeof(lpszEntryName) / sizeof(lpszEntryName[0]);

		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)szBuf);

		wsprintf(szKey, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%s"), szBuf);
		lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_QUERY_VALUE, &hKey);
		if (lResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("レジストリキーのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}

		for (i = 0; i < nEntryCount; i++) {
			dwSize = sizeof(szData);
			szData[0] = '\0';
			RegQueryValueEx(hKey, lpszEntryName[i], NULL, &dwType, (LPBYTE)szData, &dwSize);
			wsprintf(szBuf, TEXT("%s : %s"), lpszEntryName[i], szData);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		}
		
		RegCloseKey(hKey);

		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);
}

まず、WM_CREATEでリストボックスを作成し、 Uninstallキーで列挙して得られたサブキーの名前を順番に追加していきます。 WM_COMMANDでリストボックスの選択を検出したら、 選択されたサブキーの名前とUninstallキーのパスを組み合わせてサブキーのパスを完成させ、 そこで取得したエントリを右側のリストボックスに追加します。 サブキーの名前にはGUIDで識別されるものが多数存在するかと思われますが、 これはMSIによってインストールされた製品であることを意味しています。

Uninstallキーのサブキーは、次のようなエントリを持つことができます。

エントリ 意味
DisplayName リストの一覧に表示される項目の名前を指定する。 MSIによってインストールされた製品の場合は、 正規のレジストリのProductNameというエントリの値が参照される。
DisplayIcon 項目の隣に表示されるアイコンのパスを指定する。 EXEファイルがアイコンを持つ場合は、EXEファイルのパスを指定してもよい。 このエントリが存在しない場合、インストーラーパッケージのアイコンが表示されるはずだが、 実際にはUninstallStringで指定したパスからアプリケーションフォルダを特定し、 そこに含まれるEXEファイルからアイコンを取り出しているようにも思える。 なお、MSIによってインストールされた製品の場合は、 正規のレジストリのProductIconというエントリの値が参照される。 この場合、ファイルが置かれるパスは、製品が全ユーザー対象の場合は、 C:\WINDOWS\Installer\[製品のGUID]\となり、特定のユーザーに対しての場合は、 %APPDATA%\Microsoft\Installer\[製品のGUID]\となる。
UninstallString アプリケーションをアンインストールするために実行するファイルパスを指定する。
ModifyPath このエントリが存在して、なおかつNoModifyが1でない場合、 「変更」と「削除」の二種類のボタンが表示されることになる。 「変更」を選んだ場合、ModifyPathのファイルが実行され、 「削除」を選んだ場合、UninstallStringのファイルが実行される。
ModifyPath このエントリが存在して、なおかつNoModifyが1でない場合、 「変更」と「削除」の二種類のボタンが表示されることになる。 「変更」を選んだ場合、ModifyPathのファイルが実行され、 「削除」を選んだ場合、UninstallStringのファイルが実行される。
NoModify このエントリが存在してその値が1である場合、 「変更と削除」というボタンの名前が「削除」になる。 UninstallStringで指定したファイルが変更の機能を持たないような場合、 このエントリを作成して1を設定するべきと思われる。
NoRemove このエントリが存在してその値が1である場合、 アプリケーションを削除することはできない。
HelpLink サポート情報を参照することができるようになる。
Publisher サポート情報ダイアログの「発行元」に表示される。
DisplayVersion サポート情報ダイアログの「Version」に表示される。
Contact サポート情報ダイアログの「問い合わせ先」に表示される。
URLInfoAbout サポート情報ダイアログの「サポート情報」に表示される。
Readme サポート情報ダイアログの「説明ファイル」に表示される。
URLUpdateInfo サポート情報ダイアログの「説明更新」に表示される。
Comments サポート情報ダイアログの「メモ」に表示される。
NoRepair このエントリが存在してその値が1である場合、サポート情報ダイアログで「修復」を 選択することができなくなる。
SystemComponent このエントリが存在してその値が1である場合、「プログラムの追加と削除」に 表示されない。

既に述べたように、必須であるエントリはDisplayNameとUninstallStringですが、 「プログラムのアンインストール」からより多くの情報を参照できるように、 他のエントリを書き込むこともできます。



戻る