EternalWindows
MSI インストール編 / 製品のインストール

製品のインストールとは、msiexec.exeのみができる特別な作業の用に思えますが、 実際にはmsi.dllの関数の呼び出しで実現していることは周知の通りです。 MsiInstallProductという関数を呼び出せば、MSIファイルからダイアログの情報を取得し、 ユーザーが対話的に操作するためのダイアログを作成して表示してくれます。 さらに、インストールを実行をするボタンを押せば、実際にインストール作業が開始されます。

UINT MsiInstallProduct(
  LPCTSTR szPackagePath,
  LPCTSTR szCommandLine
);

szPackagePathは、MSIファイルのパスを指定します。 szCommandLineは、設定したいプロパティをコマンドライン形式で指定します。 関数の戻り値がERROR_SUCCESSのときはインストールが成功したことを意味し、 それ以外のときはエラーが発生したことを意味します。

プロパティとは、MSIファイルに含まれるPropertyテーブルのことです。 ここに書き込まれている情報が、デフォルトのインストール情報として利用されます。 インストール情報というのは、たとえば、ファイルをインストールすることになるパスや、 普通にインストールするのかアドバタイズとしてインストールするのか、 またユーザー単位でインストールするかなどの情報のことを意味し、 MsiInstallProductの第2引数にNULLを指定した場合がデフォルトとなります。 多くの場合、デフォルトのプロパティには最も望ましい値が指定されていると思われますが、 ユーザーが明示的に指定できた方がインストールの柔軟性も増すというものです。 よって、プロパティの詳細について理解しておく必要があります。

まず、プロパティは、プライベートプロパティとパブリックプロパティに大別されます。 プライベートプロパティの名前は、「UpgradeCode」のように必ず1つ以上の小文字を含み、 インストーラー内部で使用されたり変更されたりすることになります。 つまり、MsiInstallProductの引数で指定したり、インストール際に表示される ダイアログで変更したりするようなことはできません。 これに対して、パブリックプロパティの名前は、「TARGETDIR」のように、 小文字を含むことなく全て大文字で構成され、MsiInstallProductや インストールのダイアログで変更することができます。 なお、ここでいう変更とは、実際にMSIファイルのPropertyテーブルを 書き換えるということではなく、Propertyテーブルを読み込んで保持している値を 内部的に書き換えるということを意味します。

上記の話よりMsiInstallProductに指定できるプロパティがパブリックプロパティ であるということは分かりましたが、具体的にはどのようなプロパティがあるのでしょうか。 いくつかのプロパティを紹介したいと思います。 まず、TARGETDIRプロパティですが、これは製品のインストール先を決めるプロパティです。 プロパティの指定は、「プロパティ名=値」となっているため、TARGETDIRプロパティであれば、 「TARGETDIR=インストール先のパス」、というようになります。

続いて、ACTIONプロパティについて見てみましょう。 このプロパティは、製品をどのようにインストールするのかを表すもので、 次の3つの値のいずれかを指定することができます。

意味
INSTALL ローカルにインストールする。
ADVERTISE アドバタイズショートカットをインストールし、それが起動されたときに 実際に製品をインストールする。
ADMIN ネットワーク上から製品をインストールする。ここで言うADMINというのは、 ネットワーク上に存在する管理用のマシンのことを指していると思われる。

ALLUSERSプロパティについても見ておきます。 このプロパティは、製品をユーザー単位でインストールするのか、 システム単位でインストールするのかを決めることができ、 次の3つの値のいずれかを指定することができます。 インストールを実行するアカウントによって、意味が異なるので注意してください。

意味
NULL 一般ユーザーアカウントの場合、ユーザー単位のインストール。 管理者アカウントの場合、マシン単位のインストール。 ただし、リファレンスにはどちらもユーザー単位と記述されている。
1 一般ユーザーアカウントの場合、インストールに失敗する。 管理者アカウントの場合、マシン単位のインストール。
2 一般ユーザーアカウントの場合、ユーザー単位のインストール。 管理者アカウントの場合、マシン単位のインストール。

それでは、これまでの情報を基に、MsiInstallProductの第2引数に指定する コマンドライン文字列について考えていきましょう。 ここで言うコマンドライン文字列とは、実行ファイルなどに指定するそれと形式的に同じ意味を 持ちますが、単純に複数のプロパティを1つに表した文字列と解釈してもよいと思います。 たとえば、指定するプロパティが1つなのであれば次のようになります。

MsiInstallProduct(TEXT("sample.msi"), TEXT("TARGETDIR=C:\\"));

この例では、インストール先のパスとしてC:\を指定することを意味します。 プロパティが2つ以上のときは、単純に個々のプロパティをスペースで区切ることになります。

MsiInstallProduct(TEXT("\\\\MachineName\\SharedDocs\\sample.msi", TEXT("TARGETDIR=C:\\ ACTION=Admin"));

スペースがプロパティを区切る単位になりますから、「プロパティ名=値」の形式に スペースが入ってはならないことに注意してください。 上の例は、インストール先のパスとしてC:\を指定し、ネットワークインストールを行うために、 ACTIONプロパティにAdminを指定しています。 MSIファイルは、ネットワークマシン上の共有フォルダにあるものと仮定しています。

さて、最後にINSTALLのUIレベルについて考えておきたいと思います。 UIレベルは、いわばインストールの際にどれだけの対話的な機能を表示するかで、 設定によってはダイアログを一切表示しないようなインストールも可能です。 このようなインストールはサイレントインストールと呼ばれ、 プロパティの値はPropertyテーブルに記述されている通りになります。 UIレベルは、MsiSetInternalUIで指定することができます。

INSTALLUILEVEL MsiSetInternalUI(
  INSTALLUILEVEL dwUILevel,
  HWND *phWnd
);

dwUILevelは、UIレベルを表す定数を指定します。 phWndは、ダイアログの親ウインドウとするウインドウハンドルを指定します。

dwUILevelに指定可能な定数の一部を次に示します。

定数 意味
INSTALLUILEVEL_FULL MSIファイルに記述した通りの正規のダイアログが表示される。
INSTALLUILEVEL_REDUCED 正規のダイアログが表示されるが、自動でインストールされる。
INSTALLUILEVEL_DEFAULT インストール状況を示すプログレスバーとキャンセルボタンを持ったダイアログが表示され、 自動でインストールされる。 これは、デフォルトのUIレベルである。
INSTALLUILEVEL_NONE サイレントインストールとも呼ばれ、ダイアログを一切表示せずインストールを行う。

今回のプログラムは、カレントディレクトリに用意したMSIファイルをインストールします。 UIレベルとしてINSTALLUILEVEL_FULLを指定し、完全に対話的なインストールを目指します。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	UINT uResult;
	
	MsiSetInternalUI(INSTALLUILEVEL_FULL, NULL);

	uResult = MsiInstallProduct(TEXT("smaple.msi"), NULL);
	if (uResult == ERROR_SUCCESS)
		MessageBox(NULL, TEXT("インストールを完了しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("インストールに失敗しました。"), NULL, MB_ICONWARNING);

	return 0;
}

MsiInstallProductでは、プロパティとしてNULLを指定しているため、 基本的にはPropertyテーブルの値がそのままプロパティとして利用されることになります。 NULLを指定している理由は、ユーザーの意思を反映するコードを記述していないからであり、 たとえば、msiexec.exeのようにEXEファイルに指定されたコマンドライン文字列を解析する コードを書いたのであれば、プロパティを明示的に指定するのもよいと思われます。

プロパティを明示的に指定しなかった場合は、MSIファイルのPropertyテーブルの値が 参照されると述べましたが、たとえば、ACTIONプロパティがPropertyテーブルに 存在しなかった場合は、ACTIONプロパティの値はどうなるのでしょうか。 実は個々のプロパティはデフォルトの値というものを持っており、 MSIファイルに明示的に記述されていない場合は、その値が参照されることになります。 たとえば、ACTIONプロパティであればINSTALLであり、 TARGETDIRプロパティであればc:\Program Filesとなります。 また、ALLUSERSプロパティ場合は、ユーザー単位のインストールとなります。

製品の修復

製品をインストールした後、ユーザーの何らかの操作によって、 製品の実行に不具合が生じる可能性は十分に考えられることだと思われます。 ファイルが削除された場合、そのファイルの内容は読み取ることができませんし、 該当レジストリのエントリを削除された場合は、値を読み取ることができません。 このように製品の一部を修復する必要になった場合は、REINSTALLMODEプロパティを利用します。

MsiInstallProduct(TEXT("sample.msi"), TEXT("REINSTALLMODE=p"));

REINSTALLMODEプロパティは、修復時に行う動作を指定することができます。 pという文字には、ファイルの修復という意味が含まれています。 ただし、この修復はあくまでファイルの作成という意味での修復であり、 たとえば、変更されたファイルの中身を修復したいというような場合は上手くいきません。

製品コードを指定して修復作業を行いたい場合は、MsiReinstallProductを呼び出します。

MsiReinstallProduct(TEXT("product code"), REINSTALLMODE_FILEMISSING);

MsiReinstallProductの第1引数は、修正したい製品の製品コードを指定します。 第2引数は、どのような修正を施すかを示す定数を複数指定可能で、 REINSTALLMODE_FILEMISSINGは、ファイルの修復を行います。

修復機能はMSIの大きな特徴といえるところですが、ファイル以外の修復となる レジストリエントリの修復やMSIファイルの復元などは行うことができません。 REINSTALLMODE_USERDATAやREINSTALLMODE_PACKAGEというそれらしい 定数はあるのですが、これらを指定しても望むべき結果は得られないように思えます。 なお、MsiReinstallProductの呼び出しにもUIレベルの概念は存在し、 サイレントインストールでない限りダイアログは表示されます。



戻る