即時実行カスタムアクション
即時実行に指定したカスタムアクションは、インストール先環境に変更を加えない処理のために利用します。カスタムアクションを即時実行する場合は、後で説明する遅延実行する場合に比べて、比較的に自由に情報の入出力が行えるようになっています。カスタムアクションをC言語で記述したとき、C言語の関数に情報を入力する場合も、処理結果を出力する場合も、プロパティを使用します。
ここでは例として、特定のファイルへのフルパスを与えると、ファイルのディレクトリ部分と、ファイル名部分に分割するアクションを作成します。ここで入力のプロパティは_inputPath
、出力のプロパティは、_DirName
と_BaseName
になります。
#pragma comment(lib, "msi.lib")
#include <windows.h>
#include <msiquery.h>
#include <string>
extern "C" __declspec(dllexport) UINT expandPath(MSIHANDLE hInstall){
TCHAR inStr[MAX_PATH];
DWORD inStrLen = MAX_PATH;
// プロパティ経由でフルパスを受け取る
MsiGetProperty(hInstall, "_inputPath", inStr, &inStrLen);
// ファイル名直前の\の位置を取得し
std::string manipulateStr = inStr;
std::string::size_type discripterPos = manipulateStr.find_last_of("\\");
// プロパティ経由で結果を返す
MsiSetProperty(hInstall, "_DirName", manipulateStr.substr(0, discripterPos).c_str());
MsiSetProperty(hInstall, "_BaseName", manipulateStr.substr(discripterPos + 1).c_str());
return ERROR_SUCCESS;
}
ビルドするには、下記のコマンドをx86 Native Tools Command PromptやVisual Studioのメイクファイルから実行します。この結果、CustomAction.dll
を得ることができます。
cl /LD CustomAction.cpp
ここから、C言語のソースの詳細について説明していきます。カスタムアクションの関数プロトタイプは本例の様に記述しておけば、インストーラーから呼ぶことができます。この書き方なら、関数をDLL外部から参照するための.def
ファイルが不要になるため、ビルドが簡単になります。
extern "C" __declspec(dllexport) UINT 関数名(MSIHANDLE hInstall)
また、カスタムアクションのソースにあるように、#pragma
を使ってリンクするライブラリを指定すれば、ビルドコマンドを変更する必要もなくなり、メンテナンス対象はC言語のソースだけになり、便利です。
関数の入出力に使われるプロパティのアクセスは、読み込みはMsiGetProperty()
関数を、書き出しはMsiSetProperty()
関数を使います。ここで使用するプロパティの名称には、特に制限がなく、プロパティの一般的な命名規則にのっとっていれば、どのような名前でも問題ありません。
カスタムアクションの関数は、Custom Action Return Valuesで説明されている以下の値を返すことができます。
戻り値 | 説明 |
---|---|
ERROR_FUNCTION_NOT_CALLED | アクションは実行されなかった。 |
ERROR_SUCCESS | アクションは成功した。 |
ERROR_INSTALL_USEREXIT | ユーザーが中断した。 |
ERROR_INSTALL_FAILURE | 修復できないエラーが発生した。 |
ERROR_NO_MORE_ITEMS | エラーではないが、何らかの処理がスキップされた。 |
気を付けなければならないのは、カスタムアクションがエラーを返すと、ロールバックが実行され元に戻される、ということです。特にアンインストール時にエラーが発生すると、製品がアンインストールできなくなってしまいます。アンインストール時のカスタムアクションの実行時エラーには十分気を付ける必要1があります。
以下に、このカスタムアクションの使用例を示します。
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="28123AA8-D6A7-424D-8495-7F5AE87C05AB" Name="Part16_01" Language="1033" Version="1.0.0" Manufacturer="tohshima" UpgradeCode="a5a0b62e-b912-4d21-ab16-1f23c79a5ab3">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Binary Id="CA" SourceFile="CA\CustomAction.dll"/>
<Property Id="_inputPath" Value="C:\Windows\System32\calc.exe" />
<CustomAction Id="aExpandPath" BinaryKey="CA" DllEntry="expandPath" Execute="immediate" Return="check"/>
<CustomAction Id="aPrExpanded" Script="vbscript" Execute="immediate" Return="ignore">
<![CDATA[
MsgBox("_DirName : "+Session.Property("_DirName")+Chr(13)+"_BaseName : "+Session.Property("_BaseName"))
]]>
</CustomAction>
<InstallExecuteSequence>
<Custom Action="aExpandPath" After="InstallInitialize">not Installed</Custom>
<Custom Action="aPrExpanded" After="aExpandPath">not Installed</Custom>
</InstallExecuteSequence>
<Feature Id="ProductFeature" Title="Part16_01" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Part16_01" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="cExe" Guid="{46BEB206-817D-43DD-B86D-49A1C2406D4F}">
<File Id="fExe" Source="SimpleMsgBox.exe" KeyPath="yes"/>
</Component>
</ComponentGroup>
</Fragment>
</Wix>
Binary
エレメントからInstallExecuteSequence
エレメントまでに着目してください。以下のような処理を実行しています。
-
Property
エレメントで_inputPath
プロパティに電卓アプリへのフルパスを格納 - さきに作成した
expandPath
カスタムアクションを実行(aExpandPath
アクション) - これにより、
_DirName
プロパティに電卓アプリが存在するパスが、_BaseName
プロパティに電卓アプリのファイル名が格納される -
_DirName
プロパティと_BaseName
プロパティの内容をダイアログボックスに表示(aPrExpanded
アクション)
遅延実行カスタムアクション
インストール先環境に変更を加えるカスタムアクションは、遅延実行する必要があります。遅延実行は、別のプロセスが起動され、そのうえで実行されるため、情報の受け渡しに大きな制限があります。カスタムアクションの入力として利用できるプロパティは、1つのカスタムアクションあたり1つのプロパティだけ。プロパティに値を出力することはできません。
ここでは例として、次回OS起動時に1回だけ実行されるRunOnce
レジストリにプログラムを登録するアクションを作成します。
#pragma comment(lib, "msi.lib")
#pragma comment(lib, "Advapi32.lib")
#include <windows.h>
#include <msiquery.h>
#define KEY_RUNONCE "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce"
#define STR_MAX 512
extern "C" __declspec(dllexport) UINT setRunOnce(MSIHANDLE hInstall){
TCHAR inStr[MAX_PATH];
DWORD inStrLen = MAX_PATH;
HKEY hKey;
// プロパティ経由でフルパスを受け取る
MsiGetProperty(hInstall, "CustomActionData", inStr, &inStrLen);
// レジストリに書き込む
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, KEY_RUNONCE, 0, KEY_ALL_ACCESS, &hKey) != ERROR_SUCCESS)
return ERROR_INSTALL_FAILURE;
if (RegSetValueEx(hKey, "tohshima Products", 0, REG_SZ, reinterpret_cast<const BYTE*>(inStr), strnlen(inStr, inStrLen)) != ERROR_SUCCESS)
return ERROR_INSTALL_FAILURE;
if (RegCloseKey(hKey) != ERROR_SUCCESS)
return ERROR_INSTALL_FAILURE;
return ERROR_SUCCESS;
}
MsiGetProperty()
関数に注目してください。遅延実行時には、カスタムアクションに渡されるプロパティは、常にCustomActionData
というプロパティ名に変換され渡されます。ここで、「変換」と書いたのは、プロパティに値を格納するときは、別のプロパティ名だからです。
以下に、このカスタムアクションの使用例を示します。このインストーラーでインストールしているSimpleMsgBox.exe
は、Part.15の下準備で作成したプログラムです。このプログラムを、RunOnceレジストリに登録します。インストール後にOSを再起動すると、「This is test!」というメッセージが表示されます。
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="1097FE92-F1AD-4910-BC6E-E3A8CF9E667D" Name="Part16_02" Language="1033" Version="1.0.0" Manufacturer="tohshima" UpgradeCode="79571ef1-b65b-4c7f-9227-030216080610">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Binary Id="CA" SourceFile="CA\CustomAction.dll"/>
<CustomAction Id="aSetRunOnceArg" Property="aSetRunOnceExe" Value=""[#fExe]" "This is test!"" Execute="immediate"/>
<CustomAction Id="aSetRunOnceExe" BinaryKey="CA" DllEntry="setRunOnce" Execute="deferred" Impersonate="no"/>
<InstallExecuteSequence>
<Custom Action="aSetRunOnceArg" After="InstallInitialize">NOT Installed</Custom>
<Custom Action="aSetRunOnceExe" After="aSetRunOnceArg">NOT Installed</Custom>
</InstallExecuteSequence>
<Feature Id="ProductFeature" Title="Part16_02" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Part16_02" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="cExe" Guid="{2C4BA3D4-6F7A-4523-A232-5858713CBE8D}">
<File Id="fExe" Source="SimpleMsgBox.exe" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>
BinaryエレメントからInstallExecuteSequenceエレメントまでに着目してください。カスタムアクションに渡すデータをプロパティに格納しているのは、aSetRunOnceArg
というIdのカスタムアクションですが、ここではaSetRunOnceExe
という名称のプロパティに値を格納しています。この名前は、C言語で書かれたカスタムアクションのIdと同じです。遅延実行するカスタムアクションでは、そのIdと同名のプロパティに値を設定すれば、値を渡すことができるのです。
以上見てきたように、即時実行されるカスタムアクションと遅延実行されるカスタムアクションは、引数の受け渡し方法が異なります。自作したカスタムアクションは、どちらで使用することを想定しているのか、しっかり管理しておく必要があります。
-
アンインストール時はカスタムアクションはエラーを返さない、などの配慮が必要になってきます。テスト環境で発生しないエラーも、ほかの環境では起きるかもしれません。カスタムアクションが利用する機能について、十分なエラー処理を検討してください。 ↩