Windows Installer手引書 Part.13 カスタムアクションを実行させるタイミング

[**前の記事へ**](https://qiita.com/tohshima/items/1be9c0673b91dfae60ff)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　[**次の記事へ**](https://qiita.com/tohshima/items/72d1e7602a48055c55f5)

## カスタムアクションを実行させるタイミング

Windows Installerが標準的に持っていない機能をインストーラーに実装する場合、カスタムアクションを使うことになるでしょう。そして、このカスタムアクションの実行に失敗し、インストールが中断されたとき、元に戻さなければなりません。そこで、Windows Installerには、インストールの途中で何か問題が起きてインストールが続行できなくなったら、それまで行った処理を元に戻してインストールを終了する機能があります。この機能をrollbackと呼びます。カスタムアクションはシーケンスのどこに挿入しても実行できますが、rollbackを利用する場合は実行可能な場所が決められています。それは、`InstallInitialize`アクションと`InstallFinalize`アクションの間です。[^1]

カスタムアクションの実行タイミングにかかわるオプションは以下の４つがあります。

- immediate（即時）
- deferred（遅延）
- rollback（ロールバック）
- commit（コミット）

`InstallInitialize`アクションと`InstallFinalize`アクションの間では、通常、全てのimmediate、全てのdeferred、全てのcommitの順に実行されます。カスタムアクションが下図のような構成（標準アクションを省略してあります）だった時、(1)、(2)、(3)の順に実行されます。deferredのカスタムアクションがエラーを返した時は、それ以前にrollback指定されたカスタムアクションが逆順に実行されます。
![BlogSquence.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83170/39017030-9039-61a6-8666-0aa2f7cd8aef.png)
この例で、何もエラーが起きずインストールに成功した場合は、以下の順でアクションが実行されます。
Action1 -> Action4 -> Action9 -> Action3 -> Action7 -> Action10 -> Action5 -> Action8

Action3がエラーだった場合は、以下の順でアクションが実行されます。
Action1 -> Action4 -> Action9 -> Action3 -> Action2

Action7がエラーだった場合は、以下の順でアクションが実行されます。
Action1 -> Action4 -> Action9 -> Action3 -> Action7 -> Action6 -> Action2

こういった特徴から、各実行タイミングにかかわるオプションは、次のように使い分けるのがよいことがわかります。

|オプション|用途|
|:--|:--|
|immediate<br>（即時）|失敗しても復旧の必要がない操作。主にインストール先からの情報取得など、インストール先に変更を加えない操作。|
|deferred<br>（遅延）|失敗したら復旧が必要な操作。例えば、ファイアウォール設定やユーザーの作成など、インストール先に何らかの変更を加える操作。|
|rollback<br>（ロールバック）|インストール先に加えた変更を元に戻す操作。deferredでインストールに成功したもののその先で失敗したために元に戻すとか、deferredで実行して中途半端に何かが残るので消すなど、インストール内容次第で多くのパターンを考慮することもある。immediateのカスタムアクションでエラーが発生しても、rollbackのカスタムアクションは呼ばれないので注意。|
|commit<br>（コミット）|全てが成功してから実行したい操作。rollbackに処理が移ると実行されない。|

## 実際の動きを確認する

実際にインストーラーを試作して動かしてみましょう。Visual Studioで作成されるWiX Toolsetのひな型をベースに、上図に示した10個のカスタムアクションを追加しました。

```xml:Product.wxs
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
	<Product Id="E431156F-12F8-4807-88E1-3D28559FE8CA" Name="Part13_01" Language="1033" Version="1.0.0" Manufacturer="tohshima" UpgradeCode="0c76a38f-0ec9-4efc-bfa2-f5b459af7d45">
		<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

		<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
		<MediaTemplate />

        <Binary Id="VbsAct" SourceFile="customAct.vbs" />
        <CustomAction Id="tAct01" BinaryKey="VbsAct" VBScriptCall="fAct01" Execute="immediate" Return="check"></CustomAction>
        <CustomAction Id="tAct02" BinaryKey="VbsAct" VBScriptCall="fAct02" Execute="rollback"  Return="check"></CustomAction>
        <CustomAction Id="tAct03" BinaryKey="VbsAct" VBScriptCall="fAct03" Execute="deferred"  Return="check"></CustomAction>
        <CustomAction Id="tAct04" BinaryKey="VbsAct" VBScriptCall="fAct04" Execute="immediate" Return="check"></CustomAction>
        <CustomAction Id="tAct05" BinaryKey="VbsAct" VBScriptCall="fAct05" Execute="commit"    Return="check"></CustomAction>
        <CustomAction Id="tAct06" BinaryKey="VbsAct" VBScriptCall="fAct06" Execute="rollback"  Return="check"></CustomAction>
        <CustomAction Id="tAct07" BinaryKey="VbsAct" VBScriptCall="fAct07" Execute="deferred"  Return="check"></CustomAction>
        <CustomAction Id="tAct08" BinaryKey="VbsAct" VBScriptCall="fAct08" Execute="commit"    Return="check"></CustomAction>
        <CustomAction Id="tAct09" BinaryKey="VbsAct" VBScriptCall="fAct09" Execute="immediate" Return="check"></CustomAction>
        <CustomAction Id="tAct10" BinaryKey="VbsAct" VBScriptCall="fAct10" Execute="deferred"  Return="check"></CustomAction>
        <InstallExecuteSequence>
            <Custom Action="tAct01" After="InstallFiles"></Custom>
            <Custom Action="tAct02" After="tAct01"></Custom>
            <Custom Action="tAct03" After="tAct02"></Custom>
            <Custom Action="tAct04" After="tAct03"></Custom>
            <Custom Action="tAct05" After="tAct04"></Custom>
            <Custom Action="tAct06" After="tAct05"></Custom>
            <Custom Action="tAct07" After="tAct06"></Custom>
            <Custom Action="tAct08" After="tAct07"></Custom>
            <Custom Action="tAct09" After="tAct08"></Custom>
            <Custom Action="tAct10" After="tAct09"></Custom>
        </InstallExecuteSequence>
        
		<Feature Id="ProductFeature" Title="Part13_01" Level="1">
			<ComponentGroupRef Id="ProductComponents" />
		</Feature>
	</Product>

	<Fragment>
		<Directory Id="TARGETDIR" Name="SourceDir">
			<Directory Id="ProgramFilesFolder">
				<Directory Id="INSTALLFOLDER" Name="Part13_01" />
			</Directory>
		</Directory>
	</Fragment>

	<Fragment>
		<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
            <Component Id="cExe" Guid="1A30F257-0709-4D57-AFAE-54EC1A44C196">
                <File Id="fExe" Source="C:\Windows\System32\calc.exe" KeyPath="yes"/>
            </Component>
		</ComponentGroup>
	</Fragment>
</Wix>
```

このソースのビルドにはもう一つ、VBScript[^3]のファイルが必要[^4]です。

```vb:customAct.vbs
Function fAct01()
  MsgBox("Action1 immediate")
  fAct01 = 1
End Function

Function fAct02()
  MsgBox("Action2 rollback")
  fAct02 = 1
End Function

Function fAct03()
  MsgBox("Action3 deferred")
  fAct03 = 1
End Function

Function fAct04()
  MsgBox("Action4 immediate")
  fAct04 = 1
End Function

Function fAct05()
  MsgBox("Action5 commit")
  fAct05 = 1
End Function

Function fAct06()
  MsgBox("Action6 rollback")
  fAct06 = 1
End Function

Function fAct07()
  MsgBox("Action7 deferred")
  fAct07 = 1
End Function

Function fAct08()
  MsgBox("Action8 commit")
  fAct08 = 1
End Function

Function fAct09()
  MsgBox("Action9 immediate")
  fAct09 = 1
End Function

Function fAct10()
  MsgBox("Action10 deferred")
  fAct10 = 1
End Function
```

このインストールを実行すると、各カスタムアクションを実行する毎にダイアログボックスを表示する[^2]ので、カスタムアクションの実行順を観測することができます。初期状態では全てのカスタムアクションの戻り値が正常終了（戻り値の一覧は[こちら](https://docs.microsoft.com/en-us/windows/win32/msi/return-values-of-jscript-and-vbscript-custom-actions)）しているので、インストールはエラーなく完了します。

ここで、`fAct07()`関数内の`fAct07 = 1`を、エラーである`fAct07 = 3`に変えてみると、ロールバックしていく様子が観測できます。エラーが起きる場所を色々変えて、実際に動かしてみると理解が深まると思います。

[^1]: これ以外の場所でカスタムアクションを実行した場合は、常に即時実行されます。
[^2]: 表示されるダイアログボックスは、親のウィンドウがデスクトップなので、インストーラーのダイアログの陰に隠れてしまいます。フリーズしていると勘違いして、焦らないように！
[^3]: 実験用にカスタムアクションをVBScriptやJScriptを書くのは時間の節約になって良いのですが、決して本番のインストーラーには使わないようにしてください。利用者の環境によっては実行できないケースがあります。
[^4]: Visual Studioの機能を使って新たにファイルを作成すると、「BOM付きUTF8形式」のファイルが作成されてしまいます。VBScriptやJScriptは「Shift-JIS形式」のファイルである必要があるので、注意しましょう。実行時に訳のわからないエラーが起きて、実験なのに悩むことになります。

[**前の記事へ**](https://qiita.com/tohshima/items/1be9c0673b91dfae60ff)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　[**次の記事へ**](https://qiita.com/tohshima/items/72d1e7602a48055c55f5)