前のレッスンで、私たちは、完全にカスタマイズ可能な本格的インストーラ・パッケージの作成方法を学びました。既にそこには、よくある見た目と使い勝手、ビットマップ、アイコン、使用許諾契約など、他の人たちのインストーラ・パッケージで見たことがある全てがあります。インストールの課題の圧倒的多数は、前のレッスンまでで積み重ねた知識によって解決することが出来ます。それでも、時として、まだもう少し、標準的なパッケージには無い何かが必要だ、という事があります。
3.1 列に並んで
Windows Installer は、インストールの間に、数多くのステップ、いわゆるアクションを実行します。基本的なアクションも、特定のインストーラによって要求される追加のアクションも、自動的にスケジュールされます(どんなアクションが追加されるかは、レジストリ・サーチやユーザー・インタフェイスなど、使用する機能によって左右されます)。言い換えると、アクションの順序は、ツールセットによって、インストーラ・データベースが作成される時に、前もって決められます。普通の .msi ファイルの場合は以下のようになります。
- AppSearch
- LaunchConditions
- ValidateProductID
- CostInitialize
- FileCost
- CostFinalize
- InstallValidate
- InstallInitialize
- ProcessComponents
- UnpublishFeatures
- RemoveShortcuts
- RemoveFiles
- InstallFiles
- CreateShortcuts
- RegisterUser
- RegisterProduct
- PublishFeatures
- PublishProduct
- InstallFinalize
- RemoveExistingProducts
インストーラの実際のアクションの順序は、Windows Installer SDK に入っている Orca という MSI エディタを使って確認することが出来ます。
これらのイベントの順序を変更することは、適切なタグを使うことで可能になります。実際、そういうタグが四つもあります。
- AdminUISequence
- InstallUISequence
- AdminExecuteSequence
- InstallExecuteSequence
Admin- で始まるものは、(msiexec /a で起動される)管理インストールです。管理インストールは、アプリケーションのソース・イメージをネットワーク上に作成して、後でワークグループのユーザーが元のメディアの代りにこのソース・イメージからインストール出来るようにします。この機能は無料で付いてきます。今までこの機能について思い悩んだ覚えはありませんが、私たちが作ったサンプルは全てこの方法でインストールすることが出来ます(試してみて下さい!)。
と言うわけで、差し当って残されているのは二つのタグだけです。InstallExecuteSequence は、アクションを決定するために、常にインストーラによって参照されます。一方、InstallUISequence は、インストーラが完全 UI モードか、簡易 UI モードで走るときだけ考慮に入れられます(この機能も実験できます。msiexec /qn, /qb および /qr を試してみて下さい)。どんな UI のモードの場合でも、レジストリ・サーチを起動条件の前に予定しておく必要がありますので、そのことを示す行を二つのタグの両方に挿入します。コンパイルして、レジストリ・キーの名前を変更しながら、走らせてみて下さい。期待通りに動くはずです。
† 訳註:/q[n|b|r|f] は msiexec の表示オプションで、ユーザー・インタフェイスのレベルを設定します。レベルの低い方から、/qn = UI 無し, /qb = 基本 UI, /qr = 簡易 UI, /qf = 完全 UI となります。省略時は完全 UI モードです。
上の Orca のスクリーンショットで、アクションの順序を示す番号を見ることが出来ます。この番号を使うことも出来ますが、番号で頭を悩ますよりも、WiX にアクションの相対的な順序を指示する方がもっと簡単です。単に、そのアクションが、どのアクションの Before または After に来るかを指定すれば良いのです。アクションを実行のチェーンから削除したい場合は、Suppress = yes 属性を使って下さい。
<InstallExecuteSequence> <LaunchConditions After='AppSearch' /> <RemoveExistingProducts After='InstallFinalize' /> </InstallExecuteSequence>
3.2 追加のアクション
スタンダード・アクションには、この他にも、既定ではスケジュールされない利用可能なアクションが数多くあります。例えば、ScheduleReboot は、インストールの後でシステムを再起動するようにユーザーに指示します。
<InstallExecuteSequence> <ScheduleReboot After='InstallFinalize' /> </InstallExecuteSequence>
再起動の必要性が何らかの条件(例えば、インストーラが走っているオペレーティング・システム)に依存する場合は、条件文を使います。
<InstallExecuteSequence> <ScheduleReboot After='InstallFinalize'>Version9X</ScheduleReboot> </InstallExecuteSequence>
予定に入れたり予定を変更したり出来るのは、いわゆるスタンダード・アクションだけではありません。二~三のカスタム・アクションも同様に出来ます(ここで言うカスタムとは、通常のイベントの進行には出現しないけれども、望むときには、いつでもどこでも使うことが出来る、という意味です)。非常によくある要望として、インストールしたアプリケーションを起動したい、というものがあります。
カスタム・アクションはソース・ファイルの二箇所で言及しなければなりません。第一に、Product タグの子供として(例えば、Feature の終了タグと UI の開始タグの間で)、カスタム・アクションを定義します。この CustomAction タグで何をするかを指定します。インストールした実行ファイルを起動する場合は、そのファイルを定義している File タグの Id 識別子を使って実行ファイルを参照します。コマンド・ラインも指定しなければなりませんが、必要でなければ空文字列にしておくことも出来ます。
<CustomAction Id='LaunchFile' FileKey='FoobarEXE' ExeCommand='' Return='asyncNoWait' />
第二に、通常と同じ方法で、アクションを予定に入れなければなりません。アクションとスケジュール項目の間のリンクは、Id — Action の整合する属性のペアで指定します。カスタム・アクションの実行に条件がある場合は、Custom タグの中で条件を定義することが出来ます。ここでは、インストールを実行する場合にだけ実行ファイルを起動し、製品を削除するときは起動しないように、条件を設定する必要があります。
<InstallExecuteSequence> ... <Custom Action='LaunchFile' After='InstallFinalize'>NOT Installed</Custom> </InstallExecuteSequence>
† 訳註:上記ソース断片の NOT Installed は、間違いではありません。Installed は、製品がインストールされているかどうかを示す定義済みプロパティですが、その値が取得されるのは、インストーラの初期化時であり、完了時ではありません。従って、インストールを実行するときの Installed は、false になっています。
場合によっては、インストーラ・パッケージに入れて持ち回るけれども、ユーザーのマシンにはインストールしたくないヘルパー・ユーティリティ(例えば、readme ファイルのビュワーや、特別な設定ユーティリティ)を起動したいことがあります。その場合は、File ではなく、Binary タグの識別子を参照するようにします。スケジューリングの方法は同じです。
<CustomAction Id='LaunchFile' BinaryKey='FoobarEXE' ExeCommand='' Return='asyncNoWait' />
また、ユーザーのマシン上にある他のどんな実行ファイルでも、プロパティで名前を指定すれば、起動することが出来ます。
<Property Id='NOTEPAD'>Notepad.exe</Property> <CustomAction Id='LaunchFile' Property='NOTEPAD' ExeCommand='[SourceDir]Readme.txt' Return='asyncNoWait' />
カスタム・アクションは、Return 属性を使って、アクションの完了をどのように扱うかを指定することも出来ます。指定できる値は以下の通りです — check は、カスタム・アクションの完了を待って、その戻り値をチェックします。ignore は、アクションの完了を待ちますが、戻り値は無視します。asyncWait は、非同期的にアクションを走らせますが、インストーラはスケジュールされた一連のイベントの最後で、アクションから戻り値が返ってくるのを待ちます。そして、asyncNoWait は、単にアクションを起動して、その後は放置します。この場合、起動されたアクションは、インストーラが終了した後も走り続けることが出来ます。インストール完了後にアプリケーションを起動したり、readme ファイルを表示したりする場合は、この最後の値を使います。
通常の機構では表示できないエラーに遭遇した場合に、エラー・メッセージを表示してインストールを終了することが出来ます。Error 属性には、実際のメッセージのテキストを入れることも、Error タグの Id 識別子を入れることも出来ます。
<CustomAction Id='AbortError' Error='この謎は解けません。諦めます。' />
プロパティの値を別のプロパティの値に割り当てる直接的な方法はありません。しかし、カスタム・アクションを使うと、この間隙を乗り越えることが出来ます。Value 属性は書式指定文字列でも構いませんので、ちょっとした文字列操作もすることが出来ます(パスの参照には、常に末尾のバックスラッシュが自動的に追加されている事に注意して下さい。バックスラッシュをもう一つ余計に追加する必要はありません)。
<CustomAction Id='PropertyAssign' Property='PathProperty' Value='[INSTALLDIR][FilenameProperty].[ExtensionProperty]' />
ディレクトリも、同様のパスを示す書式指定文字列として設定することが出来ます。
<CustomAction Id='PropertyAssign' Directory='INSTALLDIR' Value='[TARGETDIR]\Program Files\Acme\Foobar 1.0\bin' />
3.3 本に書かれていないこと
Windows Installer が解決方法を提供してくれない非常に特殊なアクション(例えば、ユーザーが入力した登録キーの妥当性と整合性をチェックすること)に対しては、もう一つ別のタイプのカスタム・アクションを使うことが出来ます。すなわち、私たちが書く DLL です。ここでは、例として、ユーザー・キーの最初の数字が '1' であれば承認する、という極めて安直な手法を使います。
以下のソースは Visual C++ ではそのままコンパイル出来ます。別のコンパイラでコンパイルする場合でも、必要な修正は、(有るとしても)ほんの少しでしょう。ヘッダ・ファイル、msi.h と msiquery.h は、MSI SDK から取得できます。更に msi.lib もリンクする必要があります。
#include <windows.h> #include <msi.h> #include <msiquery.h> #pragma comment(linker, "/EXPORT:CheckPID=_CheckPID@4") extern "C" UINT __stdcall CheckPID (MSIHANDLE hInstall) { char Pid[MAX_PATH]; DWORD PidLen = MAX_PATH; MsiGetProperty (hInstall, "PIDKEY", Pid, &PidLen); MsiSetProperty (hInstall, "PIDACCEPTED", Pid[0] == '1' ? "1" : "0"); return ERROR_SUCCESS; }
この DLL を使うために、下記の数行を適切な場所に追加します(今や、三回目のレッスンの終り近くですから、これぐらいは自分で出来るでしょうが、ずるをしたい場合は、SampleCA をダウンロードして下さい)。
† 訳註:SampleCA の日本語版は Sample-3-3-CA.zip です。
<Condition Message='このインストーラは完全 UI モードでのみ実行出来ます。'> <![CDATA[UILevel = 5]]> </Condition> <CustomAction Id='CheckingPID' BinaryKey='CheckPID' DllEntry='CheckPID' /> <CustomAction Id='RefusePID' Error='無効なキーです。インストールを中止します。' /> <InstallExecuteSequence> <Custom Action='CheckingPID' After='CostFinalize' /> <Custom Action='RefusePID' After='CheckingPID'> PIDACCEPTED = "0" AND NOT Installed </Custom> </InstallExecuteSequence> <Binary Id='CheckPID' SourceFile='CheckPID.dll' />
簡単に説明します。最初に、私たちはこのインストーラが簡易 UI モードや UI 無しのモードで走ることを許可しません。なぜなら、それらのモードでは、ユーザーが登録キーを入力することが出来ないからです。醜い CDATA ラッパーを使っている理由は、XML がいくつかの文字、とりわけ "<" と ">" に特別な意味を与えているからです。これらの文字が、より小さい や より大きい を意味する別の文脈で出現する場合には、常に、式全体を CDATA に入れてエスケープしなければなりません。この実例の場合は、等価であることをチェックしているだけなので、エスケープしなくても済みます。しかし、このような条件式をすべて CDATA で包むようにするのは、良い習慣です。そうしておけば、万一、後で条件を修正する必要があっても、そういう XML の衝突を招かずに済みます。
次に、CheckingPID という名前のカスタム・アクションを CostFinalize の後に走らせます。すなわち、どの機能が必要で、どこにインストールしたいかを決定した後、インストーラに実際のインストールを開始するように指示する時です。このカスタム・アクションは、インストーラに同梱されている CheckPID.dll の CheckPID という関数を呼び出します。DLL は、関連するコントロールによって入力されて PIDKEY プロパティに保存されているユーザー・キーの正当性を判断して、PIDACCEPTED プロパティを 1 または 0 に設定します。カスタム・アクションとの間で引数を渡したり戻り値を受け取ったりするためには、プロパティを使うしか方法がないということを覚えておいて下さい。また、そのプロパティの名前はすべて大文字でなければなりません。そうでないと、Windows Installer はそのプロパティを public なものとは見なしません。
そして、RefusePID という名前の第二のカスタム・アクションを第一のアクションの後に走らせるように予定します。これは条件付きのカスタム・アクションで、戻り値の PIDACCEPTED プロパティがゼロであった場合にだけ走らせます。その場合、このカスタム・アクションはエラー・メッセージを表示して、インストールを中止します。ただし、私たちが PIDACCEPTED の値に関心を持つのは、インストールの時だけです。製品をアンインストールする場合には、PIDACCEPTED の値を問題にしません。
これらのアクションがどのように呼び出され、お互いにどのように関係しているのかを理解するためには、詳細ロギングを有効にしてインストーラを走らせてみるのが良いでしょう。ログは本当に詳細なものになりますから、実際に起っている事を記録している箇所を探すためには、テキスト・エディタを使って、プロパティやカスタム・アクションの名前(“PID”でも大丈夫です)を検索するのが良いでしょう。
msiexec /i SampleCA.msi /l*v SampleCA.log
呼び出す必要がある DLL が単にパッケージに含まれているのではなくて、インストールされている場合は、次のように記述することが出来ます。
<CustomAction Id='CheckingPID' FileKey='HelperDLL' DllEntry='CheckPID' />
3.4 コントロールのカスタム・アクション
いや、確かに、前の章で私たちがしたことは、エレガントではありませんでした。私たちは、後の段階になってからインストールを中止するのではなく、ユーザーがキーを入力するその場でチェックし、警告を表示して、キーを入力し直すチャンスをユーザーに提供するべきです。どうすればそれを達成できるか、見ていきましょう。
カスタム・アクションは、二種類のユーザー・インタフェイス・コントロール、プッシュ・ボタンとチェック・ボックスにリンクすることが出来ます。このリンクを実行するためには、既に知っている Publish タグを使います。Value 属性がカスタム・アクションの名前を保持します。
<Control Id="..." Type="PushButton" ...> <Publish Event="DoAction" Value="CheckingPID">1</Publish> </Control>
このようにすると、ユーザーが「ユーザー情報」ページの「次へ」ボタンを押した時に、DLL を呼ぶカスタム・アクションが引き起こされます。カスタム・アクションはこの UI イベントにリンクされますので、もう InstallExecuteSequence タグの中にスケジュールする必要は有りません。ただし、カスタム・アクションの定義はソースの中に残ります。
<CustomAction Id='CheckingPID' BinaryKey='CheckPID' DllEntry='CheckPID' />
ユーザーに警告をするためにメッセージ・ボックスが要ります。これも、また、前に作成した「ユーザー情報」ページと同じようなダイアログです。前と同じように、断片(fragment)として独立したソース・ファイルに入れて、DialogRef タグを使って参照することも出来ます。しかし、ここでは、もう一つの解法があることを示すために、直接にメインのソース・ファイルの UI セクションの直下で定義することにします。
<Dialog Id="InvalidPidDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes"> <Control Id="Icon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Information icon" FixedSize="yes" IconSize="32" Text="Exclam.ico" /> <Control Id="Return" Type="PushButton" X="100" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Return"> <Publish Event="EndDialog" Value="Return">1</Publish> </Control> <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30" TabSkip="no"> <Text> 入力されたユーザー・キーは無効です。インストール CD のケースのラベルに 印刷されているキーを入力してください。 </Text> </Control> </Dialog>
「ユーザー情報」ページも更新しなければなりません。と言うのは、このダイアログからカスタム・アクションと新しいメッセージ・ボックスを呼び出さなければならないからです。
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="[ButtonText_Next]"> <Publish Event="DoAction" Value="CheckingPID">1</Publish> <Publish Event="SpawnDialog" Value="InvalidPidDlg">PIDACCEPTED = "0"</Publish> <Publish Event="NewDialog" Value="SetupTypeDlg">PIDACCEPTED = "1"</Publish> </Control>
さあ、これで、ユーザーが「次へ」ボタンを押すと、DLL の中の関数が呼ばれます(条件が真に評価されるため、毎回呼ばれます)。DLL の中の関数は PIDKEY プロパティをチェックして、キーが承認されたかどうかを示すために PIDACCEPTED をセットします。承認された場合は、SetupTypeDlg へと進みます。承認されなかった場合は、エラー・メッセージを表示します。
あと一つだけ、小さな項目が残っています。メッセージ・ボックスの中でアイコンに言及していますので、これもインストーラの中に入れなければなりません。
<Binary Id="Exclam.ico" SourceFile="Exclam.ico" />
全体のソースは、SampleAskKey として、ダウンロードすることが出来ます。
† 訳註:SampleAskKey の日本語版は Sample-3-4-AskKey.zip です。
ところで、ログ・ファイルにユーザー・キーが出現するのは、必ずしも、良いことでも安全なことでもありません。これを回避するためには、以下のように記述します。
<Property Id="PIDKEY" Hidden='yes' />
3.5 カスタム・アクションをマネージする方法?
よくある質問の一つは、カスタム・アクションはマネージ・コード、つまり、C#、VB.NET またはそれに類するもので書くことが出来るか、というものです。何と言っても、それらの実行時環境は、はるかに豊かな機能セットを提供してくれますからね。それに、これらの言語で仕事をしているプログラマには、他のプログラミング言語をあまり知らない人もいるようです。
以前、WiX 2 の時代には、マネージ・コードでカスタム・アクションを書くためには裏技(hack)が必要で、それは良くない危険な行為だと考えられていました。しかし、WiX 3 になって、Deployment Tools Foundation (DTF) という .NET クラス・ライブラリと関連するリソースのセットが導入されたことによって、事情が変りました。依存性による制約は明白です(インストール対象マシンに .NET が入っていることを確認しなければなりません。おそらくは、ブートストラップ・インストーラを初めに使う必要があるでしょう。また、ユーザーがアプリケーションをアンインストールする前に .NET Framework を削除すると、アンインストールの際にも問題が生じる可能性があります)が、その制約を受け入れることが出来るのであれば、前のサンプルのカスタム・アクションを C# に移植したものを以下に示します。
namespace WiXTutorial.Samples { using System; using System.Collections.Generic; using System.IO; using Microsoft.Deployment.WindowsInstaller; public class SampleCheckPID { [CustomAction] public static ActionResult CheckPID(Session session) { string Pid = session["PIDKEY"]; session["PIDACCEPTED"] = Pid.StartsWith("1") ? "1" : "0"; return ActionResult.Success; } } }
SampleAskKeyNET のソース・コードには、ほんの一箇所だけ、修正が必要なところがあります。DLL の名前は違うものになります。と言うのは、Windows Installer とマネージされた世界の間隙を埋めるために、純粋なマネージ DLL を特殊なパッケージに包む必要があるためです。
† 訳註:SampleAskKeyNET の日本語版は Sample-3-5-AskKeyNet.zip です。
<Binary Id="CheckPID" SourceFile="CheckPIDPackage.dll" />
加えて、CustomAction.config という小さなファイルも用意して下さい。このファイルは、マネージ・カスタム・アクションが依存するランタイムについて記述するものです。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v2.0.50727"/> </startup> </configuration>
.NET 言語で仕事をするときの相対的な容易さには代償があります。それは、ビルドのプロセスが複雑になるということです。IDE を使えばいくらか簡単になるでしょう。WiX ソース・パッケージの DTF 部門の下にサンプル・プロジェクト・ファイルがあります。ここでは、コマンド・ラインによる方法を示すことしか出来ません。Microsoft.Deployment.WindowsInstaller.dll, MakeSfxCA.exe および sfxca.dll を探して下さい。インストールした WiX ツールセットの中にある筈です。下記のコマンドで、path として記述している箇所は、省略しない絶対パスを指定しなければいけません。そうしないと、MakeSfxCA はエラー・メッセージを出し、作成される DLL は役に立たない物になります。
csc.exe /target:library /reference:path\Microsoft.Deployment.WindowsInstaller.dll /out:CheckPID.dll CheckPID.cs MakeSfxCA.exe path\CheckPIDPackage.dll path\sfxca.dll path\CheckPID.dll path\CustomAction.config path\Microsoft.Deployment.WindowsInstaller.dll candle.exe SampleAskKeyNET.wxs UserRegistrationDlg.wxs light.exe -ext WixUIExtension -out SampleAskKeyNET.msi SampleAskKeyNET.wixobj UserRegistrationDlg.wixobj
ツールセットの中には DTF 自体のドキュメントがあります。従って、このチュートリアルでは、DTF についてこれ以上言及しません。そちらのドキュメントとサンプル・コードを使って下さい。
3.6 後の段階で
カスタム・アクションは、シーケンス・テーブルに行を挿入することによって、プロパティをセットしたり、機能やコンポーネントの状態を変更したり、インストール先ディレクトリを設定したり、あるいはシステムの操作をスケジュールしたりします。たいていの場合、それらのカスタム・アクションは即時に実行しても支障の無いものです。しかし、システムを直接に変更したり、他のシステム・サービスを呼んだりする必要があるカスタム・アクションは、インストール・スクリプトが実行される時まで延期されなければなりません。Windows Installer は、こういう遅延実行の(deferred)カスタム・アクションをインストール・スクリプトに書き込んで、後で実行します。
遅延実行のカスタム・アクションは、下記のように定義します。
<CustomAction Id="MyAction" Return="check" Execute="deferred" BinaryKey="CustomActionsLibrary" DllEntry="MyAction" HideTarget="yes"/>
Execute 属性が、カスタム・アクションが遅延実行されるものであることを指し示しています。呼び出さなければならない DLL 関数を DllEntry 属性で参照しなければなりません(コンパイル環境が要求する場合は _MyAction@4 のような C++ スタイルの関数名に対する装飾を忘れないで下さい)。そして、最後に、セキュリティー上の判断が命ずる場合は、HideTarget を指定して、このカスタム・アクションに渡される引数のロギングを無効にすることが出来ます。
インストール・スクリプトは通常のインストール・セッションの外で実行されますので、遅延実行のアクションが実行される時には元のセッションはもう存在しません。元のセッションのハンドルも、元のインストールのシーケンスで設定されたプロパティ・データも、遅延実行アクションからは使うことが出来ません。遅延実行のアクションが取得できる非常に限られた量の情報は、以下の三つのプロパティから成り立っています。
- CustomActionData
- カスタム・アクションがシーケンス・テーブルで処理される時の値。このプロパティは遅延実行のカスタム・アクションだけが使用出来るもので、即時実行のカスタム・アクションからはアクセス出来ません。
- ProductCode
- 製品の一意の GUID コード。
- UserSID
- ユーザーのセキュリティー識別子(SID)。インストーラによってセットされます。
遅延実行のアクションに他のプロパティ・データを渡す必要がある場合は、その値を前もって設定する第二のカスタム・アクションを使うことが出来ます。一番簡単な解法はプロパティ設定のカスタム・アクションです。設定されるプロパティの名前が遅延実行のカスタム・アクションの Id 属性と同じになるように設定して下さい。
<Property Id="SOME_PUBLIC_PROPERTY">こんにちは、遅延実行 CA です。</Property> <CustomAction Id="MyAction.SetProperty" Return="check" Property="MyAction" Value="[SOME_PUBLIC_PROPERTY]" />
プロパティの設定を遅延実行のアクションの前にスケジュールすることも重要です。
<InstallExecuteSequence> <Custom Action="MyAction.SetProperty" After="ValidateProductID" /> <Custom Action="MyAction" After="MyAction.SetProperty" /> </InstallExecuteSequence>
渡そうとしたデータは、CustomActionData プロパティの中に出現します。複数の情報を渡す必要がある場合は、それらをこの単一のプロパティに組み入れる方法を工夫しなければなりません。例えば、セミコロンで区切られた Name=Value のペアのリストを使う等です。
#include <windows.h>
#include <msi.h>
#include <msiquery.h>
#include <tchar.h>
#pragma comment(linker, "/EXPORT:MyAction=_MyAction@4")
extern "C" UINT __stdcall MyAction (MSIHANDLE hInstall) {
TCHAR szActionData[MAX_PATH] = {0};
DWORD dActionDataLen = MAX_PATH;
MsiGetProperty (hInstall, _T("CustomActionData"), szActionData, &dActionDataLen);
MessageBox (NULL, szActionData, _T("遅延実行のカスタムアクション"),
MB_OK | MB_ICONINFORMATION);
return ERROR_SUCCESS;
}
Vadym Stetsyak