EternalWindows
MSI カスタムアクション編 / カスタムアクション(EXE)

セットアッププロジェクトでカスタムアクションを追加するには、 カスタム動作エディタを利用することになります。 厳密には、カスタムアクションはその他のエディタでも暗黙的に追加されることがあるのですが、 少なくとも開発者が用意したコードを実行させたい場合は、カスタム動作エディタを利用します。 メニューの「表示」から「カスタム動作」を選択すると、次のような画面が表示されます。

カスタム動作エディタでは、開発者のコードをどのタイミングで実行するかをまず決めなければなりません。 このタイミングというのが、「インストール」や「確定」という言葉で表されているのですが、 これらはシーケンス上でのカスタムアクションの実行位置を抽象化しているので、 次に概要を示します。

種類 説明
インストール ファイルのインストールを終えた後に実行される。 失敗する可能性のある処理、たとえば、 独自に何らかのデータを作成したり書き込みを行う場合は、 ここで処理する。
確定 インストールのカスタム動作が成功した場合に呼ばれる。 ここでは、失敗することのない処理を行うようにするが、 それはインストールのカスタム動作に含めてもよい。
ロールバック インストールのカスタム動作が失敗した場合に実行される。 もし、インストールのカスタム動作で何らかのデータの作成などを行っていた場合は、 ここでそれらを破棄する。 つまり、元の何もなかった状態に戻す。
アンインストール アンインストールのときに呼ばれる。

カスタム動作エディタでは、ファイルのインストール後にカスタムアクションを実行するように設定されるため、 それ以前の段階でカスタムアクションを実行することはできません。 ただし、作成されたMSIファイルを開いてシーケンステーブルにアクセスし、 Sequenceカラムの値を変更さえすれば、任意のタイミングで実行できるようになりますから、 それほど問題になることはありません。 単純にファイルのインストール後にカスタムアクションを実行したい場合は、 「インストール」を対象にするだけで構いません。

それでは、追加作業に入りたいと思います。 セットアッププロジェクトの設定は前節の続きと仮定し、 readme.txtとsample.exeはファイルシステムエディタで 既にアプリケーションフォルダに追加しているものとします。 「インストール」のフォルダアイコン上で右クリックして「カスタム動作の追加」を選択し、 アプリケーションフォルダからsample.exeを追加します。 「インストール」の下が次のようになっていれば成功です。

これにより、CustomActionテーブルにsample.exeを実行するためのアクションが追加され、 シーケンステーブル上でファイルのインストールを終えた後に、 そのアクションの名前が追加されることになります。

カスタム動作エディタで実行できるファイル形式は.exeや.dll、 またはスクリプトファイルを表す.vbsなどがありますが、 それではテキストファイルなどを表示することはできないものでしょうか。 製品のインストールを終えるときにアプリケーションを起動するよりも、 readmeのようなヘルプを表示するほうが一般的だといえるはずです。 これは、プロパティウインドウのArgumentsを利用することで可能となります。

MSIはEXEファイルを起動するときに、Argumentsで指定された値をコマンドライン文字列とするため、 上記の例であればWinMainのlpszCmdLineが/Installであれば、 MSIによってEXEファイルが起動されたことが分かります。 そうすれば、このときにreadme.txtを表示するコードを実行し、 そうでない場合はユーザーが普通にEXEファイルを起動したということですから、 そのアプリケーション本来の動作を実行すればよいことになります。 デフォルトで設定されている/Installという値は、 「インストール」フォルダにEXEファイルを追加したからであり、 値は自由に変更して構いません。 なお、チェックボックスをユーザーインターフェースエディタで追加し、 そのチェックが入っている場合だけEXEファイルを起動したいような場合は、 Conditionにそのチェックボックスのプロパティを設定しておきます。

readme.txtの表示は、ShellExecuteを呼び出すのが最も簡単です。 readme.txtをsample.exeと同じディレクトリに配置したことから、 ShellExecuteのディレクトリを要求する引数にNULLを指定できるように思えますが、 これは上手くいきません。 MSIは起動したプロセスのカレントディレクトをsystem32フォルダにしているため、 明示的にインストール先ディレクトリのパスを算出しなければならないのです。 Argumentsに/Installを指定したとして、次に例を示します。

#include <windows.h>
#include <shlwapi.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	if (lstrcmpA(lpszCmdLine, "/Install") == 0) {
		TCHAR szDirectory[256];

		GetModuleFileName(NULL, szDirectory, sizeof(szDirectory));
		PathRemoveFileSpec(szDirectory);
		ShellExecute(NULL, TEXT("open"), TEXT("readme.txt"), NULL, szDirectory, SW_SHOW);
		return 0;
	}

	// アプリケーション本来のコード

	return 0;
}

GetModuleFileNameを呼び出せば、EXEファイルのフルパスを取得することができます。 PathRemoveFileSpecはフルパスからファイル名の部分を取り除くため、 szDirectoryはインストール先ディレクトリのパスとなります。 後は、これをディレクトリとしてShellExecuteに指定します。

インストール先ディレクトリと聞くと、TARGETDIRプロパティのことを思い出すかもしれませんが、 実はこれを利用する方法もあります。 プロパティ名を括弧でくくると、そのプロパティが示す値に置き換わりますから、 [TARGETDIR]をArgumentsに含めればよいのです。 Argumentsに/Install [TARGETDIR]を指定したとして、次に例を示します。

#include <windows.h>
#include <shlwapi.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	char szOption[] = "/Install";

	if (StrCmpNA(lpszCmdLine, szOption, lstrlenA(szOption)) == 0) {
		LPSTR lpszDirectory = lpszCmdLine + lstrlenA(szOption) + 1;

		ShellExecuteA(NULL, "open", "readme.txt", NULL, lpszDirectory, SW_SHOW);
		return 0;
	}

	// アプリケーション本来のコード

	return 0;
}

まず、lpszCmdLineの先頭からlstrlenA(szOption)までの文字が/Installであるかを確かめるべく、 文字数を考慮した文字列比較関数であるStrCmpNを呼び出します。 [TARGETDIR]の文字列が格納されている位置は、 lpszCmdLineの先頭からlstrlenA(szOption)までをスキップし、 さらに空白の分をスキップするため+1することになります。 lpszCmdLineがLPSTR型であるため、かなりANSIよりのコードになってしまったのは気になるところです。


戻る