EternalWindows
MSI データベース編 / トランスフォームファイル

MSIデータベースを編集する手段として、直接レコードを操作する関数を呼び出すのは プログラミング的に敷居が高く、変更した内容を管理するのも容易でないのが実状です。 インストーラーの設計者が自らOrca等のツールを使って編集するにしても、 MSIファイルの数が増えればそれだけ管理の複雑さは増すことになり、 手作業であるため効率的であるともいえません。 トランスフォームファイルというのは、MSIファイルに対して反映すべき 変換内容を記したファイルのことで、このファイルを1つ作成すれば、 それをMSIファイルに割り当てるだけで変換が反映される利点があります。

トランスフォームファイルを利用することは、インストーラーの設計者に プログラミングの知識を必要とさせないという点でも優れています。 設計者はまず、既存のMSIファイルのコピーを作成し、 そのコピーしたファイルをOrca等のツールを使って編集します。 ここでいう編集というのは、特定のテーブルのフィールド値を目的の値に 書き換えることで、たとえばトランスフォームファイルの適応により、 インストール先のパスをc:\としたいのであれば、PropetryテーブルのTARGETDIRに その値を書き込むことになります。 後は、編集を終えたMSIファイルと元となるMSIファイルをPlatformSDKに 付属するMsitran.exeに指定すれば、2つのファイルの 差分情報からトランスフォームファイルが作成されることになります。 Msitran.exeは、MSIファイルにトランスフォームファイルを割り当てる機能もあります。

トランスフォームファイルの割り当てで自動的に変換が適応できるとはいえ、 その割り当て自体をMSIファイル毎に行うのも大変な作業といえます。 たとえば、自社において複数の製品用のMSIファイルが存在し、 それらの特定のデータを全て同一にしたいという要望があるのであれば、 全てのMSIファイルのパスを取得し、MSIファイル1つずつに対して トランスフォームファイルを割り当てるプログラムがあれば非常に便利でしょう。 また、ローカライズの問題を考慮して全ての文字列を外国語にすることもあるかもしれません。 こういった意味でも、トランスフォームファイルの関数を知ることには意味があるといえるでしょう。 次に示すMsiDatabaseGenerateTransformは、トランスフォームファイルを作成します。

UINT MsiDatabaseGenerateTransform(
  MSIHANDLE hDatabase,
  MSIHANDLE hDatabaseReference,
  LPCTSTR szTransformFile,
  int iReserved1,
  int iReserved2
);

hDatabaseは、hDatabaseReferenceを基に作成されたデータベースのハンドルを指定します。 このデータベースは、変換内容が反映された状態を記述しているべきです。 hDatabaseReferenceは、hDatabaseが参照すべきデータベースのハンドルを指定します。 変換内容は、hDatabaseとhDatabaseReferenceの差分から生じることを思い出してください。 szTransformFileは、作成したいトランスフォームファイルの名前を指定します。 iReserved1は、予約されているため0を指定します。 iReserved2は、予約されているため0を指定します。

トランスフォームファイルの名前、及び拡張子は自由に決定して問題ありません。 ただ、一般的にトランスフォームファイルの拡張は.mstとなっているため、 できる限りそれに倣っておいたほうがいいでしょう。 トランスフォームファイルを割り当てる関数は、MsiDatabaseApplyTransformです。

UINT MsiDatabaseApplyTransform(
  MSIHANDLE hDatabase,
  LPCTSTR szTransformFile,
  int iErrorConditions
);

hDatabaseは、割り当てたいデータベースのハンドルを指定します。 szTransformFileは、トランスフォームファイルの名前を指定します。 iErrorConditionsは、割り当てによって生じる結果が特定の条件に一致する場合は エラーにするという狙いで、その特定の条件を示すフラグ群を指定することになります。 覚えておきたい点は、エラーの条件を考慮しない場合は0を指定できることと、 MSITRANSFORM_ERROR_VIEWTRANSFORMフラグを指定すると変換が適応されないことです。

今回のプログラムは、トランスフォームを作成するコードと トランスフォームをデータベースに割り当てるコードが存在しています。 プログラムの性質上、カレントディレクトリに3つのMSIファイルがあることを前提としています。 まず、1つはsampleRef.msiで、これは既存のMSIファイルであり、 差分情報を参照するために利用されます。 残りの2つは、sampleRef.msiのコピーによって作成されたものと考え、 それぞれをsample.msiとsampleApply.msiとしています。 sample.msiに変換内容が反映された状態が記述されており、 sampleApply.msiはトランスフォーム割り当て用のファイルとします。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL      bCreateTransform = TRUE;
	UINT      uResult;
	MSIHANDLE hDatabase;
	MSIHANDLE hDatabaseReference;
	
	if (bCreateTransform) {
		uResult = MsiOpenDatabase(TEXT("sample.msi"), MSIDBOPEN_READONLY, &hDatabase);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), TEXT("OK"), MB_ICONWARNING);
			return 0;
		}
	
		uResult = MsiOpenDatabase(TEXT("sampleRef.msi"), MSIDBOPEN_READONLY, &hDatabaseReference);
		if (uResult != ERROR_SUCCESS) {
			MsiCloseHandle(hDatabase);
			MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
		
		uResult = MsiDatabaseGenerateTransform(hDatabase, hDatabaseReference, TEXT("sample.mst"), 0, 0);
		if (uResult == ERROR_SUCCESS)
			MessageBox(NULL, TEXT("トランスフォームファイルを作成しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("トランスフォームファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

		MsiCloseHandle(hDatabaseReference);
		MsiCloseHandle(hDatabase);
	}
	else {
		uResult = MsiOpenDatabase(TEXT("sampleApply.msi"), MSIDBOPEN_TRANSACT, &hDatabase);
		if (uResult != ERROR_SUCCESS) {
			MessageBox(NULL, TEXT("データベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
	
		uResult = MsiDatabaseApplyTransform(hDatabase, TEXT("sample.mst"), 0);
	
		MessageBox(NULL, TEXT("トランスフォームファイルを割り当てました。"), TEXT("OK"), MB_OK);

		MsiDatabaseCommit(hDatabase);
		MsiCloseHandle(hDatabase);
	}

	return 0;
}

bCreateTransformの値がTRUEの場合、トランスフォームが作成されるコードが実行され、 FALSEの場合はトランスフォームを割り当てるコードが実行されることになります。 まず、作成のコードから見ていきます。

uResult = MsiDatabaseGenerateTransform(hDatabase, hDatabaseReference, TEXT("sample.mst"), 0, 0);

hDatabaseとhDatabaseReferenceは、共にMsiOpenDatabaseで取得することになります。 どちらのデータベースも変更することはないので、MSIDBOPEN_READONLYでオープンしています。 関数成功後、sample.msiが生成されることになります。 続いて、割り当てのコードを見てみます。

uResult = MsiDatabaseApplyTransform(hDatabase, TEXT("sample.mst"), 0);	

この呼び出しにおいて、hDatabaseには変換内容が適応されることになりますから、 最終的にファイルに反映させるためにMsiDatabaseCommitを呼び出すことに注意してください。 また、データベースの変更のためにhDatabaseはMSIDBOPEN_TRANSACTでオープンされます。 なお、MsiDatabaseApplyTransformは、実際に変換が割り当てられていないのに ERROR_SUCCESSを返すことがあるので、関数終了後にMSIファイルの所定の箇所が 適切に変更されているかは確認しておくべきといえます。

トランスフォームファイルとパッチ

MSIの世界でいわゆる更新という言葉を用いた場合、 それはトランスフォームファイルの事ではなく、 パッチファイルの事を指しています。 パッチというのは、既にインストールされたアプリケーションに対して、 ファイルの追加やレジストリの変更などを行うことで、ユーザーから見ても、 更新という作業はパッチの割り当てだと解釈することが多いと思われます。 一方、トランスフォームファイルの割り当てはインストールを行う前の段階、 即ちインストール自体の内容をMSIファイルに対して書き込むことですから、 トランスフォームファイル単体をユーザーに配布することには意味がありません。 更新プログラムの作成例としては、まず自作製品がインストールされているかを調べ、 インストールをしているのならばパッチの割り当てを行い、 していないのであれば、MSIファイルに対してトランスフォームファイルを 割り当てる方法が考えられます。



戻る