しばらくInstallShield+Windows Installerでインストーラ開発をやっていてたまってきたTipsをメモ用としてここに記載しておく。
前提
Windows Installer 2.0系と,InstallShield 7.0/Xを前提とする。
ログ
ログ出力
まず全般的に役に立つインストーラのログファイルの出力について。インストール時にエラーが起きたとき,どうしようもなくなくなるが,以下のコマンドでログが出せる。
msiexec /i xxx.msi /L*v [ログファイル名] setup.exe /v"/L*v [ログファイル名]"
なお,setup.exeの場合はあくまでもInstallShieldの場合で有効というだけで,一般的には特に統一されていない。
オプションについてはとりあえずL*vとしておけば全てのログが出力される。
必ずログ出力する方法
次に修復インストールなどでコマンドライン起動できない場合。以下のレジストリを作っておくと全てのインストーラで必ずログファイルが出力される。開発時に有効にしておくと便利。
キー: HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Installer 名前: Logging 型: 文字列 値: voicewarmup
「値」はv/o/i/c/e/w/a/r/m/u/pで11種類のログが出るようになっている。
出力先のパスは「C:\Documents and Settings\hoge\Local Settings\Temp」で,ファイル名はMSIxxxxx.LOGのような形で出力される。
ログ中のエラー表示
インストールログでアクションの戻り値が表示されるが,これはアクションそのものの戻り値ではなく,以下の値に変換されて返される。
値 | 定数 |
---|---|
-1 | msiMessageStatusError |
0 | msiMessageStatusNone |
1 | msiMessageStatusOk |
2 | msiMessageStatusCancel |
3 | msiMessageStatusAbort |
4 | msiMessageStatusRetry |
5 | msiMessageStatusIgnore |
6 | msiMessageStatusYes |
7 | msiMessageStatusNo |
ErrorIconというログが出力される
インストーラのログで以下のエラーが表示される場合がある。
DEBUG: Error 2835: The control ErrorIcon was not found on dialog SetupError 内部エラー 2835。 ErrorIcon, SetupError
これはSetupErrorダイアログのアイコンが「WarningIcon」になっているため。手動で変更すればOK。
バグ
「プログラムの追加と削除」のアイコンがフォルダアイコンになる
Windows2000でユーザごとのインストールを行うと「プログラムの追加と削除」でアイコンがフォルダアイコンになってしまう。
[サポート情報] ダイアログが閉じない
Windows2000で,「プログラムの追加と削除」内の[サポート情報] ダイアログボックスで [閉じる] ボタンを押してもウィンドウが閉じない。
アンインストールキャンセル時にロールバックカスタムアクションが呼ばれない
Windows2000またはWindowsXP SP1において,[アプリケーションの追加と削除]から[削除]を行った場合,DLLで実装したロールバックカスタムアクションが呼ばれない。Windows Installer3.0がインストールされている場合や,MSIではなくSetup.exeだった場合や,VBAによるカスタムアクションだった場合は発生しない。Windows Installer3.0で修正されているということはバグっぽいけど、とりあえず公式な情報は見たことはない。
その他
カスタムアクションでのエラー表示
カスタムアクションをDLLで作成したとき,エラーダイアログは以下のように表示する。
PMSIHANDLE hRecord = ::MsiCreateRecord(1); ::MsiRecordSetString(hRecord, 1, "error!");> ::MsiProcessMessage(hInstall, INSTALLMESSAGE(INSTALLMESSAGE_ERROR | MB_OK), hRecord);
これを使えば,ダイアログが表示され,Windowsのアプリケーションログに記録され,インストーラログに記録される。さらにサイレントインストール時にはダイアログだけ表示されないというおまけ付き(この場合MsiProcessMessageは0を返す)。
但し,例えば機能選択画面で「次へ」を押したタイミングでカスタムアクションを起動する場合,DoActionで起動することになるが,この場合はMsiProcessMessageを使えない。そのため,最前面にダイアログを表示させてごまかしたりしている。
Feature/Componentがインストールされているか
カスタムアクションの起動条件として特定のFeature/Componentがインストールされているか,もしくはインストールされる予定かどうかなどを知りたいことがある。この場合,以下のように指定する。
(例) &FeatureName=3…FeatureName機能が選ばれた !FeatureName=3…FeatureName機能が既に入っている $ComponentName=3…ComponentNameが選ばれた ?ComponentName=3…ComponentNameが既に入っている
ここでは3を指定しているが,他にもいくつか種類がある。
値 | 意味 |
---|---|
-1 | No action (feature unchanged) |
1 | Feature advertised |
2 | Feature not installed (uninstalled) |
3 | Feature installed locally |
4 | Feature installed to run from source |
ダウングレード防止
あるバージョンから過去のバージョンにダウングレードインストールしようとすると,変な挙動になる。以下推測。
- WindowsInstallerが,ファイルのバージョンを比較し,新しいバージョンが入っているのでコピーしないと判断する
- 前のバージョンのアンインストールが行われ,ファイルが削除されてしまう
- 今回のバージョンはコピーされない
- よって,ファイルがなくなってしまう
そこでダウングレードを防止したいわけだが,ちゃんとした方法は「Preventing an Old Package from Installing Over a Newer Version」に書かれている。要するにUpgradeテーブルで過去のバージョンがインストールされていることを判定し,あとは適当にエラーにすればいい。
コンポーネントの「上書きしない」オプション
コンポーネントに「上書きしない」オプションがあるが,このオプションの挙動を見ると,キーパスが明示的に設定されていない場合はオプションが無視されているように見える。この場合,通常の上書きルールが適用され,以下の場合に上書きされてしまう(AND条件)。
- 既存のファイルの更新日時が作成日時と同じまたは古い
- 既存のファイルとインストールするファイルのハッシュが違う
よって,上書きしたくないファイルは,1つずつコンポーネントに分割し,それぞれキーファイルに指定する必要がある。
本現象が起きる例としては,インストール後,ファイルを他のPCからコピーして,それ以降更新していない場合が挙げられる。インストール後にエディタなどでファイルを更新した場合では,本現象は発生しない。
ファイル使用中ダイアログ
以下のようにすれば「ファイルが使用中です」ダイアログが表示できる。
UINT __stdcall DetectFilesInUse(MSIHANDLE hInstall) { int iResult = IDRETRY; while(iResult == IDRETRY) { // アプリケーション検出 // 何もなければbreakする MSIHANDLE hRecord = ::MsiCreateRecord(5); ::MsiRecordSetString(hRecord,0,NULL); ::MsiRecordSetString(hRecord,1,_T("アプリケーション1")); ::MsiRecordSetString(hRecord,2,_T("このアプリケーションを閉じる必要がある")); ::MsiRecordSetString(hRecord,3,_T("アプリケーション2")); ::MsiRecordSetString(hRecord,4,_T("このアプリケーションも閉じる必要がある")); iResult = ::MsiProcessMessage(hInstall,INSTALLMESSAGE_FILESINUSE,hRecord); ::MsiCloseHandle(hRecord); } if(iResult == IDCANCEL) { return ERROR_INSTALL_FAILURE; } return ERROR_SUCCESS; }
但し、MSI3.1では以下のようにエラーとなる。
MSI (c) (C8:E0) [09:55:09:521]: GetWindowTitle: FilesInUse record has invalid ProcessID - Unexpected MSI (c) (C8:E0) [09:55:09:521]: No window with title could be found for FilesInUse
MSIがバグっているような気もするが、何か仕様が変わったのだろうか。