EternalWindows
MSI データベース編 / レコードの編集

ユーザーにとってMSIファイルをオープンする一番の目的は、 やはり既存のレコードを変更したり、新しいレコードを追加することであると思われます。 これらは、MsiViewModifyに目的のレコードを指定することによって実現できます。

UINT MsiViewModify(
  MSIHANDLE hView,
  MSIMODIFY eModifyMode,
  MSIHANDLE hRecord
);

hViewは、ビューのハンドルを指定します。 eModifyModeは、修正モードを表すフラグを指定します。 MSIMODIFY_INSERTを指定した場合、hRecordは新しく追加するレコードとなり、 MSIMODIFY_UPDATEの場合は、更新したい既存レコードのハンドルとなります。 hRecordは、レコードのハンドルを指定します。

アプリケーションは一連の編集作業を終えた後、 その編集内容をMsiDatabaseCommitによってデータベースに割り当てます。 これにより、MSIファイルに編集内容が反映されます。

UINT MsiDatabaseCommit(
  MSIHANDLE hDatabase
);

hDatabaseは、データベースのハンドルを指定します。 このハンドルはオープン時に、読み取りを表すMSIDBOPEN_READONLYではなく、 MSIDBOPEN_TRANSACT等のモードを指定する必要があります。

では、まず既存レコードの特定のフィールドを変更する方法から見ていきたいと思います。 レコードのフィールドは、MsiRecordSetStringにて変更することができます。

UINT MsiRecordSetString(
  MSIHANDLE hRecord,
  unsigned int iField,
  LPCTSTR szValue
);

hRecordは、レコードのハンドルを指定します。 iFieldは、変更したいフィールドを示すインデックスを指定します。 szValueは、フィールドに設定する文字列を指定します。

これまで述べてきたように、既存レコードはMsiViewFetchによって取得することができますが、 今回のように特定のフィールドのみを変更したいというような場合は、 取得したレコードに不要なフィールドが格納されている必要はありません。 実は、レコードに格納されるフィールドは特定の列の値のみを表す単一の要素で構成可能であり、 そのように指定した場合は、MsiRecordGet(Set)Stringの第2引数に1を指定することで、 フィールドにアクセスすることができるのです。 つまり、変更したいフィールドが何列目にあるかを意識せずに済むのです。 特定の列の指定は、MsiDatabaseOpenViewの第2引数で指定することができます。

MsiDatabaseOpenView(hDatabase, TEXT("SELECT Title FROM Dialog"), &hView);

たとえば、この例ではDialogテーブルのTitleという列だけを取得するよう命令していますから、 MsiViewFetchで取得したレコードにはTitleという列のフィールド値のみが格納されます。 しかし、これだけでは変更する列は指定したものの、 一体どの行を対象とすればよいのかが分からないままですから、 多くの場合、SQL文にもう少し情報を付け足すことになります。

MsiDatabaseOpenView(hDatabase, TEXT("SELECT Title FROM Dialog WHERE Dialog = 'SelectFolderDialog'"), &hView);

WHERE句は、レコードの取得に条件をつける目的で使用され、 上記の例では、DialogテーブルのDialogという列のフィールド値がSelectFolderDialogである レコードのみを取得するという意味になります。 Dialog列がプライマリキーであるため、このような方法で特定の行が取得できるわけです。 上記コードを利用して次のようなコードを書けば、 SelectFolderDialogのTitleを新しく書き換えることができます。

MsiDatabaseOpenView(hDatabase, TEXT("SELECT Title FROM Dialog WHERE Dialog = 'SelectFolderDialog'"), &hView);

MsiViewExecute(hView, 0);
MsiViewFetch(hView, &hRecord);
	
MsiRecordSetString(hRecord, 1, TEXT("NewTitle"));
MsiViewModify(hView, MSIMODIFY_UPDATE, hRecord);

MsiDatabaseCommit(hDatabase);

注目すべきところは、特定のレコードのみを取得対象としているため、 MsiViewFetchの呼び出しが1回で済むことと、レコードに含めるフィールドを 1つに限定しているため、MsiRecordSetStringの第2引数を1を指定するところです。 既存のレコードを更新するため、MsiViewModifyの第2引数はMSIMODIFY_UPDATEとなります。

続いて、新しいレコードを追加する方法について説明します。 これにはまず、MsiCreateRecordで明示的にレコードを作成しなければなりません。

MSIHANDLE MsiCreateRecord(
  unsigned int cParams
);

cParamsは、レコードに含めたいフィールドの数を指定します。 ここに指定する数は、最終的に追加することになるテーブルの列数となるべきですから、 事前に数を把握して直接指定するか、MsiViewGetColumnInfoとMsiRecordGetFieldCountの 組み合わせで取得するかのどちらかになると思われます。

今回のプログラムは、MsiCreateRecordで作成したレコードのフィールドに値を書き込み、 それをMSIMODIFY_INSERTを指定したMsiViewModifyでテーブルに追加します。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	UINT      uResult;
	MSIHANDLE hDatabase;
	MSIHANDLE hView;
	MSIHANDLE hRecord;

	uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_TRANSACT, &hDatabase);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	uResult = MsiDatabaseOpenView(hDatabase, TEXT("SELECT * FROM Property"), &hView);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("ビューのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	hRecord = MsiCreateRecord(2);
	MsiRecordSetString(hRecord, 1, TEXT("TARGETDIR"));
	MsiRecordSetString(hRecord, 2, TEXT("c:\\"));
	
	uResult = MsiViewModify(hView, MSIMODIFY_INSERT, hRecord);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("ビューの修正に失敗しました。"), NULL, MB_ICONWARNING);
		MsiCloseHandle(hRecord);
		MsiCloseHandle(hView);
		MsiCloseHandle(hDatabase);
		return 0;
	}

	MsiDatabaseCommit(hDatabase);
	
	MsiCloseHandle(hRecord);
	MsiCloseHandle(hView);
	MsiCloseHandle(hDatabase);
	
	MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	return 0;
}

プログラムは、Propertyテーブルに1つのレコードを追加します。 1番目のフィールドは、データを参照するためのプライマリキーを指定し、 2番目のフィールドは実際のデータです。 TARGETDIRは、インストール先のデフォルトのディレクトリを表し、 ここではそのパスをCドライブの直下にしています。

hRecord = MsiCreateRecord(2);
MsiRecordSetString(hRecord, 1, TEXT("TARGETDIR"));
MsiRecordSetString(hRecord, 2, TEXT("c:\\"));

これにより、作成したレコードのフィールドは初期化したことになります。 後は、これをテーブルに追加するようにMsiViewModifyを呼び出し、 最後にMsiDatabaseCommitを呼び出せば完了です。


戻る