EternalWindows
MSI インストール編 / アップグレードコード

製品をアップグレードするとは、既にインストール済みの製品を新しい製品に置き換えることです。 原理的に考えればこれは、その既にインストールしてある製品をアンインストールし、 その後に新しい製品をインストールするということですから、 次のようなコードで簡単に実装できるように思えます。

uResult = MsiQueryProductState(szOldProductCode);
if (uResult == INSTALLSTATE_DEFAULT)
	MsiConfigureProduct(szOldProductCode, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT);

MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);
MsiInstallProduct(szNewPackage, NULL);

新しい製品を作成したメーカーは当然、旧バージョンの製品の製品コードを把握しているため、 それを利用してMsiQueryProductStateで製品のインストール状態を確認できます。 このとき、旧バージョンの製品がインストールされていれば、 MsiConfigureProductにINSTALLSTATE_ABSENTを指定してアンインストールを行います。 そして、最終的にはMsiInstallProductで新しい製品をインストールしようとしています。 確かにこの実装は問題のないものですが、 実際にはMSIが提供するアップグレードのメカニズムを利用したほうがはるかに効率的です。 このメカニズムでは、新しい製品のMSIファイルにしかるべき値を設定してさえいれば、 自動で旧バージョンの製品をアンインストールしてくれるため、 単純にMSIファイルを公開するだけでアップグレードを行えます。

アップグレードを行うためには新バージョンのMSIファイルのプロパティの一部が、 旧バージョンのMSIファイルのプロパティのそれと異なっていなければなりません。 具体的には、PackageCode、ProductVersion、ProductCodeのプロパティが異なっている必要があるのですが、 UpgradeCodeプロパティに関しては同一にしておかなければなりません。 このプロパティは、アプリケーションの複数のバージョンを表す共有識別子と働き、 製品コードと同じようにGUID形式で一意の値となります。 アップグレードコードが同一である製品が複数個インストールされている場合、 それらは互いに製品コードやバージョンが違うものの、 アップグレードコードで識別することのできる1つの製品グループということになります。

既存製品からアップグレードコードを確認したい場合、 その製品のMSIファイルのUpgradeCodeプロパティにアクセスしなければなりません。 以前、フィーチャの情報を取得するときに説明したように、 これはMSIのデータベース系の関数を呼び出すということではなく、 MsiOpenProductで取得したハンドルを使うということなります。 Propertyテーブルにアクセスするには、MsiGetProductPropertyを呼び出します。

UINT MsiGetProductProperty(
  MSIHANDLE hProduct,
  LPCTSTR szProperty,
  LPTSTR lpValueBuf,
  DWORD *pcchValueBuf
);

hProductは、MsiOpenProductで取得したハンドルを指定します。 szPropertyは、Propertyテーブルから取得したいプロパティの名前を指定します。 lpValueBufは、プロパティのデータを受け取るバッファのアドレスを指定します。 pcchValueBufは、lpValueBufのサイズを格納した変数のアドレスを指定します。

今回のプログラムは、既存製品の製品コードからアップグレードコードを取得します。 szProductCodeを既存製品の製品コードで初期化しておいてください。

#include <windows.h>
#include <msi.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR     szProductCode[] = TEXT("");
	TCHAR     szUpgradeCode[256];
	DWORD     dwSize;
	MSIHANDLE hProduct;

	MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL);
	if (MsiOpenProduct(szProductCode, &hProduct) != ERROR_SUCCESS)
		return 0;

	dwSize = sizeof(szUpgradeCode);
	MsiGetProductProperty(hProduct, TEXT("UpgradeCode"), szUpgradeCode, &dwSize);

	MessageBox(NULL, szUpgradeCode, TEXT("OK"), MB_OK);

	MsiCloseHandle(hProduct);

	return 0;
}

MsiOpenProductの呼び出しではMSIのダイアログが表示されるため、 MsiSetInternalUIにINSTALLUILEVEL_NONEを指定することでこれを無効にします。 MsiGetProductPropertyではアップグレードコードを取得したいので、 第2引数にUpgradeCodeプロパティを指定します。

関連する製品の列挙

新しいバージョンの製品をインストールするとなったとき、 旧バージョンの製品は依然として残しておきたい場合があります。 この様な場合、アップグレードは行わないということになりますが、 敢えてアップグレードコードを同一にしておくのもよいかもしれません。 このようにした場合、その2つの製品はアップグレードコードで識別できる製品グループということになりますから、 MsiEnumRelatedProductsを利用してグループ内の製品を列挙することができます。 Visual Studioの2002と2003は、この例に当てはまります。

for (i = 0;; i++) {
	uResult = MsiEnumRelatedProducts(szUpgradeCode, 0, i, szRelatedProductCode);
	if (uResult != ERROR_SUCCESS)
		break;
	MessageBox(NULL, szRelatedProductCode, TEXT("OK"), MB_OK);
}

MsiEnumRelatedProductsの第1引数にアップグレードコードを指定すると、 そのアップグレードコードを持つ製品の製品コードが第4引数に返ることになります。 第2引数は常に0を指定し、第3引数はゼロベースのインデックスを指定します。 MsiEnumRelatedProductsを利用する場合は、_WIN32_WINNTを0x0500と定義します。



戻る