EternalWindows
MSI カスタムアクション編 / コスティング

アプリケーションをインストールすることによって消費されるディスク量というのは、 多くのユーザーが意識することだと思われます。 MSIによるファイル等のコンポーネントのインストールは、 フィーチャのインストールによって生じるものであり、 そのフィーチャをインストールすることで、 どれだけのコストを消費するかを把握することは非常に重要といえるでしょう。 コスティングというのは、この消費するコストを計算する処理を指し、 コスティングに関するアクションが既に実行されていれば、 アプリケーションからもコストを参照することができるようになります。 次に示すMsiGetFeatureCostは、指定したフィーチャにおけるコストを取得します。

UINT MsiGetFeatureCost(
  MSIHANDLE hInstall,
  LPCTSTR szFeature,
  MSICOSTTREE iCostTree,
  INSTALLSTATE iState,
  INT *piCost
);

hInstallは、イントールハンドルを指定します。 szFeatureは、hInstallに関連する製品のフィーチャを指定します。 iCostTreeは、そのフィーチャが親や子を持つ場合、 それらのフィーチャのコストも含めるかどうかです。 含めない場合は、MSICOSTTREE_SELFONLYを指定します。 iStateは、INSTALLSTATE_LOCALを指定した場合は、 フィーチャのインストールによって生じるコスト、 INSTALLSTATE_ABSENTを指定した場合はアンインストールよって生じるコストの意味になります。 piCostは、消費するコストが512バイト単位で返ります。 iStateでINSTALLSTATE_ABSENTを指定した場合は、ディスク領域が空くことになるわけですから、 piCostはマイナスの値が格納されることになります。

コスティングに関するアクションに限ったことではありませんが、 ある一部のアクションの動作を確認したいだけのような場合、 MSIファイルによるインストールを行う必要はありません。 実は、MsiOpenPackageという関数を呼び出せば、 イントールのハンドルを擬似的に取得できることが可能であり、 このハンドルを利用すれば実際にインストールを行っているかのように振舞うことができます。 つまり、先のコスティングの例で言えば、 コスティングに関するアクションのみを実行してMsiGetFeatureCostを呼び出し、 その取得したコストのみを表示するアプリケーションを作成できるということです。

UINT MsiOpenPackage(
  LPCTSTR szPackagePath,
  MSIHANDLE *hProduct
);

szPackagePathは、MSIファイルを指定します。 これは、フルパスでなければなりません。 hProductは、インストールハンドルが返ります。

インストールハンドルを取得すれば、MsiDoActionでアクションを実行することができます。 MsiDoActionは、InstallFilesやWriteRegistryValuesなどの実際にシステムに変更を加えるアクションは実行できませんが、 そうでないアクションに関しては実行することができます。

UINT MsiDoAction(
  MSIHANDLE hInstall,
  LPCTSTR szAction
);

hInstallは、インストールハンドルを指定します。 szActionは、実行したいアクションを示す文字列を指定します。

今回のプログラムは、前述の通りコスティングに関するアクションのみを実行し、 特定のフィーチャにおけるコストを取得します。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	MSIHANDLE    hInstall;
	UINT         uResult;
	int          nCost;
	TCHAR        szBuf[256];
	INSTALLSTATE installState;
	TCHAR        szFeatureName[] = TEXT("DefaultFeature");
	
	MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);

	uResult = MsiOpenPackage(TEXT("c:\\sample.msi"), &hInstall);
	if (uResult != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("インストールに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	MsiDoAction(hInstall, TEXT("CostInitialize"));
	MsiDoAction(hInstall, TEXT("FileCost"));
	MsiDoAction(hInstall, TEXT("CostFinalize"));
	
	MsiGetFeatureState(hInstall, szFeatureName, &installState, NULL);

	if (installState == INSTALLSTATE_ABSENT) {
		MsiDoAction(hInstall, TEXT("InstallValidate"));
		MsiGetFeatureCost(hInstall, szFeatureName, MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &nCost);
	}
	else if (installState == INSTALLSTATE_LOCAL) {
		MsiSetFeatureState(hInstall, szFeatureName, INSTALLSTATE_ABSENT);
		MsiDoAction(hInstall, TEXT("InstallValidate"));
		MsiGetFeatureCost(hInstall, szFeatureName, MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &nCost);
	}
	else
		nCost = 0;

	wsprintf(szBuf, TEXT("cost %d byte"), nCost * 512);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	MsiCloseHandle(hInstall);

	return 0;
}

MsiOpenPackageで指定されるMSIファイルは、 szFeatureNameで初期化されているフィーチャを持っている必要があります。 Visual Studioのセットアッププロジェクトで作成されたMSIファイルならば、 DefaultFeatureという1つのフィーチャだけを持っているでしょう。 MsiSetInternalUIを呼び出しているのは、 MsiOpenPackageでプログレスバーを持ったダイアログを表示させないためですが、 表示させておくとコストの計算過程を確認することもできます。 次に、MsiDoActionの呼び出しを確認します。

MsiDoAction(hInstall, TEXT("CostInitialize"));
MsiDoAction(hInstall, TEXT("FileCost"));
MsiDoAction(hInstall, TEXT("CostFinalize"));

コスティングに関するアクションは、CostInitialize、FileCost、CostFinalizeです。 これらは、この順番で実行する必要があります。 MSIファイルのシーケンス上では、CostInitializeの前に様々なアクションが記述されていますが、 CostInitializeはそれらのアクションの実行に依存しないため、 最初からCostInitializeを実行しても問題はありません。

コスティングを終えればMsiGetFeatureCostを呼ぶことができるようになりますが、 その前に、MsiOpenPackageで取得したハンドルが現在、 製品がインストールされているかどうかに依存することを理解しておかなければなりません。 たとえば、フィーチャがアンインストールされるときのコストを取得したいような場合、 それは既にフィーチャがインストールされていることが条件となるわけです。 Windows Installer 2.0から登場したMsiOpenPackageExでは、 インストール状態を考慮しない制限されたハンドルを返すことができますが、 このハンドルを利用してもコストを取得することはできませんでした。 よって、フィーチャが既にインストールされているかを調べ、 それに応じてMsiGetFeatureCostの呼び出し方を変えることになります。

MsiGetFeatureState(hInstall, szFeatureName, &installState, NULL);

if (installState == INSTALLSTATE_ABSENT) {
	MsiDoAction(hInstall, TEXT("InstallValidate"));
	MsiGetFeatureCost(hInstall, szFeatureName, MSICOSTTREE_SELFONLY, INSTALLSTATE_LOCAL, &nCost);
}
else if (installState == INSTALLSTATE_LOCAL) {
	MsiSetFeatureState(hInstall, szFeatureName, INSTALLSTATE_ABSENT);
	MsiDoAction(hInstall, TEXT("InstallValidate"));
	MsiGetFeatureCost(hInstall, szFeatureName, MSICOSTTREE_SELFONLY, INSTALLSTATE_ABSENT, &nCost);
}
else
	nCost = 0;

MsiGetFeatureStateは、フィーチャの現在の状態を第3引数に返します。 INSTALLSTATE_ABSENTで、フィーチャがインストールされていないことを意味しますから、 MsiGetFeatureCostの第4引数にINSTALLSTATE_LOCALを指定して、 フィーチャがインストールされたときのコストを取得できます。 フィーチャの状態がINSTALLSTATE_LOCALであった場合は、既にフィーチャがインストールされているため、 MsiGetFeatureCostの第4引数にINSTALLSTATE_ABSENTを指定して、 フィーチャがアンインストールされたときのコストを取得できます。 このとき、MsiSetFeatureStateで事前にフィーチャをアンインストール状態にしておきます。 MsiGetFeatureCostの呼び出しの前にInstallValidateアクションを実行するのは、 コストの値がディスクの空き領域を超過していないかを保障するためです。


戻る