アプリケーションの回復性: Windows インストーラーの非表示機能のロックを解除する

 

Michael Sanford
701 ソフトウェア

2005 年 3 月

概要: Windows インストーラーには、開発コミュニティによってほとんど気付かれていないいくつかの機能があります。 これらの機能を使用すると、実行時にアプリケーション自体を修復したり、ユーザーとアプリケーションとの対話に基づいてオプションのコンポーネントをインストールしたりできます。 (10ページ印刷)

MSI 統合サンプル Code.msiをダウンロードします。

内容

はじめに
シェル統合による回復性
Windows インストーラー API の概要
主要なアプリケーション API
課題 #1: 回復性の Self-Invoked
課題 #2: オンデマンドでインストールする
まとめ

はじめに

開発者は、理想的な環境、理想的なシステム、およびインストールが成功した後にアプリケーションを使用している理想的なユーザーによって実行されているアプリケーションを考える傾向があります。 実際には、アプリケーションが正常にインストールされた後、そのユーザーの有効期間は開始されたばかりです。 アプリケーションの安定性と機能が残っている場合にアプリケーションが直面する可能性がある課題は多数ありますが、ほとんどのアプリケーションは、アプリケーションが動作不能になる可能性がある動作環境の変更に対処する準備ができていません。

Windows インストーラーは、アプリケーションの安定性を維持するために大幅な進歩を遂げた回復性機能を提供しますが、この機能は、ユーザーがシェルと対話しながら実行する特定のアクションに基づいて、Windows インストーラーがアプリケーションの構成に関する問題を検出し、修復するための手順を実行できる "エントリ ポイント" を提供します。

Windows インストーラーの "エントリ ポイント" の簡単な一覧を次に示します。

  • ショートカット。 Windows インストーラーには特別な種類のショートカットが導入されています。このショートカットには、ユーザーに対して透過的ですが、Windows インストーラーがシェル統合を通じて使用する追加のメタデータが含まれており、アプリケーションを起動する前に、指定したアプリケーションのインストールの状態を確認します。
  • ファイルの関連付け。 Windows インストーラーは、ドキュメントまたはファイルに関連付けられているアプリケーションの呼び出しをインターセプトするメカニズムを提供します。これにより、ユーザーがシェルを使用してドキュメントまたはファイルを開いたときに、Windows インストーラーは、関連付けられたアプリケーションを起動する前にアプリケーションを検証できます。
  • COM 広告。 Windows インストーラーは、COM サブシステムにフックされるメカニズムを提供します。これにより、Windows インストーラーによってインストールされた (この機能を使用するように構成された) COM コンポーネントのインスタンスを作成するアプリケーションは、Windows インストーラーがそのコンポーネントのインストールの状態を確認した後に、そのコンポーネントのインスタンスを受け取ります。

特定の状況では、Windows インストーラーの組み込みの回復性機能では、アプリケーションの構成に関するすべての問題を検出できない場合や、アプリケーションが必要なエントリ ポイントをアクティブ化しないように機能する場合があります。 幸いなことに、Windows インストーラー チームのスマートな人たちは、この課題を理解し、豊富な Windows インストーラー API を通じて追加の回復性機能を利用できるようにしました。

シェル統合による回復性

Windows インストーラー API で提供される高度な回復性機能に進む前に、Windows インストーラーを使用してアプリをデプロイするときに通常無料で得られる一般的な回復性シナリオを見てみましょう。

このシナリオでは、SimplePad と呼ぶ単純なテキスト編集アプリケーションをデプロイします。 インストールを作成するには、Microsoft のオープン ソース WiX Toolkit (詳細については を参照 https://sourceforge.net/projects/wix/.) を使用しますが、任意のツールで同じ操作を実行できます。

<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="SimplePad" Guid="BDDFA5DC-BD69-4232-998E-5167814C21B9" 
  KeyPath="no">
  <File Id="SimplePadConfig" Name="SP.cfg"
    src="$(var.SrcFilesPath)SimplePad.exe.config"
    LongName="SimplePad.exe.config" Vital="yes" KeyPath="no" DiskId="1" />
  <File Id="SimplePad" Name="Simple~1.exe"
    src="$(var.SrcFilesPath)SimplePad.EXE" LongName="SimplePad.exe" Vital="yes"
    KeyPath="yes" DiskId="1" >
  <Shortcut Id="SC1" Advertise="yes"  Directory="ProgramMenuFolder"
    Name="SimpPad" LongName="Run SimplePad"  />
  </File>
</Component>
<Directory Id="ProgramMenuFolder" Name="ProgMenu"></Directory>
</Directory>

上記の XML フラグメントでわかるように、1 つのファイル (SimplePad.exe) とユーザーの [スタート] メニューにある 1 つのショートカットを使用して、非常に簡単なインストールを作成しました。 この例では、作成するショートカットが、アプリケーションの状態を検出して必要に応じて修復するために Windows インストーラーが使用するエントリ ポイントであることに注意してください。

この時点で、インストーラーをビルドし、アプリケーションをインストールし、新しく作成した [スタート] メニュー ショートカットを使用して実行できます。 予想どおり、アプリケーションは意図したとおりに機能します。 Windows インストーラーの組み込みの回復性機能をテストするには、SimplePad.exe ファイルを削除し、[スタート] メニュー ショートカットからアプリケーションを実行してみてください。 ここでも、予想どおり、Windows インストーラーは SimplePad.exe が見つからないことを検出し、修復が開始されます。 修復操作中、Windows インストーラーは、インストール パッケージの内部キャッシュされたコピーから必要な構成情報を読み取り、最後に不足しているファイルを置き換え、インストール元の元の場所に存在しない場合は、ソース インストール メディアの入力をユーザーに求めます。 修復操作が完了すると、アプリケーションは通常どおりに起動します。

図 1. 修復操作が進行中です

アプリケーションの回復性は、ここでも言及する価値のある他のいくつかのメカニズムを通じて、Windows インストーラーによって提供されます。 アプリケーションの高可用性を維持するための 2 番目に一般的な方法は、Windows インストーラー ファイルの関連付けです。 このメカニズムは、Windows インストーラーのショートカットとほとんど同じように動作しますが、アプリケーションの実行可能ファイルに直接リンクする代わりに、登録されたファイルの種類によって関連付けが行われます。 図 2 に示すように、Windows インストーラー ファイルの関連付けは、標準のファイル関連付けと同じメカニズムを使用して定義されていますが、1 つの例外があります。 図 2 では、一般的な "shell\Open\command" レジストリ キーの下に追加の値が一覧表示されていることに注意してください。 余分な値 ("command" とも呼ばれます) は、Windows シェル内からファイルをダブルクリックするたびに Windows インストーラーが表示される場所です。 "Darwin Descriptor" とも呼ばれるこの不可解な文字列は、実際には特定の製品、コンポーネント、および機能のエンコードされた表現です。 この余分な値が存在する場合、Windows インストーラーはデータをデコードし、それを使用してその製品とコンポーネントに対してチェックを実行します。 コンポーネントが見つからないか破損していることが判明した場合、Windows インストーラーは、不足しているファイルまたはデータを復元するための修復を起動し、最後に参照先のアプリケーションを通常どおりに起動し、適切なコマンド ライン オプションを渡します。

図 2. ファイル関連付けの "Darwin Descriptor" の表示

現在説明する最後の回復性メカニズムは、一般に COM Advertising と呼ばれます。 COM Advertising の仕組みを見る前に、その背後にあるユース ケースを理解することが重要です。 たとえば、リアルタイムの郵便料金を提供する COM ベースの共有ライブラリを提供するコンポーネント ベンダーであるとします。 このコンポーネントは多くの異なる製品で使用できるため、エンド ユーザーのシステム上の 1 つの共有場所にインストールされます。 コンポーネントが常に同じ場所にインストールされるようにし、コンポーネントの高可用性を確保するために、COM Advertising を利用するように適切に構成されたマージ モジュールで顧客に配布します。 もちろん、ソリューションはユーザー インターフェイスのない単一の .dll ファイルとして付属しているため、回復性の他のメカニズムでは不十分です。 この場合、COM Advertising を利用して、コンポーネントがユーザーのシステムに適切にインストールされ、登録されたままになるようにすることができます。 アプリケーションが通常の COM メカニズムを使用してこのコンポーネントのインスタンスを作成すると、Windows インストーラーは、ファイルの関連付けと同じようにプロセスに "フック" します。 図 3 では、今回は"Darwin Descriptor" がコンポーネントの COM 登録の InprocServer32 レジストリ値に格納されていることに注意してください。 ここでも、この情報は Windows インストーラーによってデコードされ、コンポーネントが正しくインストールおよび構成されていることを確認するために使用され、必要に応じて修復を実行してから、最後にコンポーネントのインスタンスを呼び出し元のアプリケーションに返します。

この固有の機能は、 コンポーネントを使用するアプリケーションとは完全に独立して動作することを指摘する価値があります。 つまり、コンポーネントを使用するアプリケーションが Windows インストーラーを使用してインストールされていない場合でも、呼び出し元のアプリケーションが単なる VBScript であっても、コンポーネントによって使用される COM Advertising は引き続き正常に機能します。

図 3: COM サーバーの "Darwin 記述子" の表示

ここまでで説明し、デモを行ったことはすべて、1 行のコードを記述しなくても Windows インストーラーの機能を利用してきましたが、次は、より豊富で堅牢な実装に進みます。

Windows インストーラー API の概要

Windows インストーラーの既定の動作は、前のシナリオでは適切に機能しましたが、多くの場合、実際の世界では、もう少し高度なアプリケーションがあります。 より困難なシナリオに対処するために、サンプル シナリオを拡張しましょう。

多くの場合、アプリケーションは複数の実行可能ファイルで構成されます。 たとえば、ブートストラップ実行可能ファイルを使用して、Updater アプリケーション ブロックに示されているように、アプリケーションの更新プログラムをチェックしてインストールするアプリケーションがあります。 この場合、最初の実行可能ファイルは、ユーザーが [スタート] メニューのショートカットをクリックしたときに呼び出される実行可能ファイルです。 これにより、アプリケーションのメインユーザー インターフェイスを含む 2 番目の実行可能ファイルが起動されます。 インストールの構成方法によっては、メインアプリケーション実行可能ファイルに関する問題が Windows インストーラー エンジンによって検出されない可能性が高くなります。 1 つのオプションとして、ランタイム環境を検査する起動時に実行されるコードの束を記述することもできますが、実行可能ファイル自体が見つからないか破損していて、さらに、問題を簡単に修復できない場合は、これは単に機能しません。 はるかに効果的なソリューションは、展開パッケージで既に定義されているアプリケーションの構成に関する Windows インストーラーの知識を活用することです。

Windows インストーラー API は、ユーザーがシェルと対話するときに使用するアプリケーションの整合性を検証するための同じメカニズムを公開します。 アプリケーション内からこれらの API 呼び出しを使用することで、前に説明したシェルの "エントリ ポイント" に依存することなく、引き続き同じ利点を実現できます。

Windows インストーラーのシェル統合の回復性機能でカバーされていないシナリオの一覧を次に示します。

  • OS で始まるアプリ (実行または一度実行するレジストリ キー)
  • システム サービス
  • スケジュールされたタスク
  • 他のアプリによって実行されるアプリ
  • コマンド ライン アプリ

上記のリストに追加できるシナリオはもっとたくさんあると思いますが、私はあなたがアイデアを得ると思います。 次の例では、前に説明したシェル統合機能に依存することなく、Windows インストーラーの回復性の利点を得る方法を示します。 完了すると、これらの概念を受け取り、実行可能コードの実行に関連するすべてのシナリオに簡単に適用できるようになります。

主要なアプリケーション API

シナリオの例をいくつか見る前に、アプリケーション内から使用できる主要な Windows インストーラー API をいくつか見てみましょう。 これらの各 API の使用方法の詳細については、プラットフォーム SDK の Windows インストーラー Finction リファレンスを参照 してください。

Windows インストーラーの主な機能 説明
MsiProvideComponent コンポーネントのインストール場所を取得し、必要に応じてインストールまたは修復して、コンポーネントが使用可能であることを確認します。
MsiQueryFeatureState 特定の機能のインストール状態を返します。 たとえば、この関数は、機能がインストールされているか、インストールされていないか、アドバタイズされているかを示します。
MsiQueryProductState 製品のインストール状態を返します。 たとえば、この関数は、製品がインストールされているか、アドバタイズされているか、別のユーザーにインストールされているか、まったくインストールされていないかを示します。
MsiConfigureProduct
MsiConfigureProductEx
これら 2 つの関数を使用すると、プログラムでアプリケーションをインストールまたはアンインストールできます。 MsiConfigureProductEx を使用すると、コマンド ラインで通常行うのと同様のオプションを指定できるため、より詳細な制御が可能になります。
MsiConfigureFeature この機能を使用すると、アプリケーションの特定の機能をインストール、アンインストール、またはアドバタイズできます。
MsiGetUserInfo この関数は、製品のインストール シーケンス中に収集されたユーザー名、organization、および製品シリアル番号を返します。
MsiGetComponentPath
MsiLocateComponent
これら 2 つの機能は、ターゲット システム上のコンポーネント ファイルまたはレジストリ キーの物理的な場所を決定する際に役立ちます。 MsiGetComponentPath は特定の製品によってインストールされたコンポーネントのインスタンスのパスを返し、 MsiLocateComponent は ANY 製品によってインストールされたコンポーネントの最初のインスタンスを返します。

課題 #1: 回復性の Self-Invoked

以前は、アプリケーションの実行可能ファイルをシステムから実際に削除し、ショートカットを使用して、不足しているファイルを再インストールして問題を検出して修復する非常に基本的なシナリオについて説明しました。 このシナリオは、Windows インストーラーが利用するシェル統合を示すために適切に機能しますが、これらの概念をより深く理解するために、もう少し高度なシナリオを見てみましょう。

このシナリオでは、アプリケーションは、1 つの .exe ファイルと、アプリケーションに重要な構成情報を提供するいくつかのテキスト ファイルで構成されます。

架空のソフトウェア会社のテクニカル サポート スタッフは、インストールによって作成された [スタート] メニューショートカットを使用する代わりに、Windows エクスプローラーの実行可能ファイルをダブルクリックしてアプリケーションを直接実行しているため、アプリケーション構成の問題が Windows インストーラーによって解決されていないことを明らかにする多くのサポートリクエストを受け取っています。

チームの常駐デプロイの専門家と相談した後、エンジニアのチームは、アプリケーションが適切に構成されていることを確認するために、起動時に独自の回復性チェックを実行することで、アプリケーションに大きな利益をもたらすと判断します。 これを実現するために、チームは MsiProvideComponent API の呼び出しを追加するだけで、アプリケーションのインストール パッケージで定義されている重要なコンポーネントが適切にインストールおよび構成されるようにします。

<DllImport("msi.dll")> _
Private Shared Function MsiProvideComponent(ByVal szProduct As String, ByVal _
 szFeature As String, ByVal szComponent As String, ByVal dwInstallMode As _
 MSI_REINSTALLMODE, ByVal lpPathBuf As System.Text.StringBuilder, ByRef _
 pcchPathBuf As IntPtr) As Integer
End Function

Public Shared Function ProvideComponent(ByVal productCode As String, ByVal _
 featureName As String, ByVal compID As String) As String
  Dim iRet As Integer
  Dim cbBuffer As Integer = 255
  Dim buffer1 As New System.text.StringBuilder(cbBuffer)
  Dim pSize As New IntPtr(cbBuffer)
  iRet = MsiProvideComponent(productCode, featureName, compID, _
   MSI_INSTALLMODE.INSTALLMODE_DEFAULT, buffer1, pSize)
  Return buffer1.ToString
End Function

このコードをより適切にカプセル化するために、 WIHelper という新しいクラスがプロジェクトに追加され、Windows インストーラー API メソッドの宣言とラッパー メソッドが格納されます。 このコードを呼び出すことは、メイン フォームの Load イベント ハンドラーに数行を追加するだけで簡単でした。

Private CONST PRODUCTID As String = "PRODUCT_GUID_HERE"
Private CONST MAIN_FEATUREID As String = "DefaultFeatureKey"
Private CONST COMPID_1 As String = "COMP1_GUID_HERE"
Private CONST COMPID_2 As String = "COMP2_GUID_HERE"
Private Sub MainForm_Load() Handles MyBase.Load
  If WIHelper.IsProductInstalled(PRODUCTID) Then
    WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_1)
    WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_2)
  End If
End Sub
 

上記のサンプル コードでは、まず、アプリケーションがインストール パッケージを介して実際にインストールされているかどうかを確認します。 これは重要な概念です。開発環境でデバッグしている場合でも、アプリケーションが正常に機能することを確認する必要があるためです。 これを実現するために、 IsProductInstalled というヘルパー クラスで メソッドを呼び出します。 このメソッドは、 MsiQueryProductState を呼び出して、製品がシステムにインストールされているかどうかを判断するだけです。 IsProductInstalled の呼び出しで、製品がインストールされていることが明らかになった場合は、ヘルパー クラスで ProvideComponent メソッドを一連の呼び出しを行います。 このメソッドは、 MsiProvideComponent API を囲む単純なラッパーです。これは、指定されたコンポーネントへの完全なパスを返し、コンポーネントが適切にインストールされ、使用できる状態になっていることを確認します。 特定の製品のニーズに応じて、 ProvideComponent メソッドを何度でも呼び出して、アプリケーションがユーザーに対して完全に使用可能であることを確認できます。

課題 #2: オンデマンドでインストールする

当社の架空の企業の営業幹部は、SimplePad で提供される一連の標準テンプレートを見たいとお客様から多くのフィードバックを受けています。 この機能に対して強い要望を表明しているお客様もいれば、ほとんどのユーザーが不要な余分なデータをインストールすることに懸念を表明しているユーザーもいます。

この新しい要件に対処する方法についてエンジニアが話し合った後、私たちのイントレピッドインストールエンジニアは、Windowsインストーラーが少量の追加コーディングでこれを簡単に処理できることを指摘します。

いくつかの簡単な計画の後、チームは、アプリケーションの [ファイル] メニューに新しい [テンプレート] メニュー項目を実装することを決定します。 テンプレート機能がユーザーのシステムにローカルにインストールされている場合、使用可能な各テンプレートとテンプレート機能をアンインストールするオプションが表示されます。 テンプレート機能がインストールされていない場合、テンプレートのポップアップ メニューには 1 つのエントリがあり、ユーザーは追加のテンプレートをインストールできます。

Private Sub mnuFile_Popup(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles mnuFile.Popup
  Dim newItem As MenuItem
  With mnuTemplates.MenuItems
    .Clear()
    If WIHelper.IsFeatureInstalled(PRODUCTID, TEMPLATES_FEATUREID) Then
      Dim dirInfo As New DirectoryInfo(Application.ExecutablePath)
      For Each dirFile As FileInfo In dirInfo.Parent.GetFiles("*.tpl")
        Dim mi As New MenuItem(Path.GetFileNameWithoutExtension(dirFile.Name))
        AddHandler mi.Click, AddressOf OpenTemplate
        .Add(mi)
      Next
      .Add("-")
      newItem = New MenuItem("Uninstall Templates")
      AddHandler newItem.Click, AddressOf UninstallTemplates
      .Add(newItem)
    Else
      newItem = New MenuItem("Install Templates")
      AddHandler newItem.Click, AddressOf InstallTemplates
      .Add(newItem)
    End If
  End With
End Sub

ご覧のように、最初にテンプレート機能がインストールされているかどうかを確認チェック。 その場合は、"tpl" 拡張子を持つアプリケーション フォルダー内のファイルを列挙し、各テンプレートの名前をメニューに追加します。 そうでない場合は、ユーザーがテンプレートをインストールするためのオプションを追加するだけです。 その前に、まずテンプレート機能がインストールされているかどうかを判断する方法を見てみましょう。

<DllImport("msi.dll")> _
Private Shared Function MsiQueryFeatureState(ByVal szProduct As String, 
ByVal szFeature As String) As MSI_INSTALLSTATE
End Function    
Public Shared Function IsFeatureInstalled(ByVal pid As String, ByVal fid As String) As Boolean
  Return MsiQueryFeatureState(pid, fid) = MSI_INSTALLSTATE.INSTALLSTATE_LOCAL
End Function

この単純な関数では、単に Windows インストーラー MsiQueryFeatureState 関数を呼び出し、アプリケーションの ProductCode と、調査対象の機能の名前を渡します。 Windows インストーラーが INSTALLSTATE_LOCALを返す場合は true を返します。これは、機能がローカルにインストールされていることを意味するためです。

テンプレート機能のインストールとアンインストールも簡単に行うことができます。

<DllImport("msi.dll")> _
Private Shared Function MsiConfigureFeature(ByVal szProduct As String, ByVal szFeature As String, ByVal eInstallState As MSI_INSTALLSTATE) As Integer
End Function

Public Shared Function InstallFeature(ByVal pid As String, ByVal fid As String)
  As Boolean
  Return MsiConfigureFeature(pid, fid, MSI_INSTALLSTATE.INSTALLSTATE_LOCAL) = ERROR_SUCCESS
End Function

Public Shared Function UninstallFeature(ByVal pid As String, ByVal fid As String) As Boolean
  Return MsiConfigureFeature(pid, fid, 
MSI_INSTALLSTATE.INSTALLSTATE_ABSENT) = ERROR_SUCCESS
End Function

ユーザーが [テンプレートのインストール] メニュー項目をクリックすると、ProductCode を使用して MsiConfigureFeature を呼び出し、構成する機能の名前、および機能をローカルにインストールすることを示す列挙値が呼び出されます。 テンプレート機能のインストール中に、ユーザーに Windows インストーラーの進行状況ダイアログが短時間表示されます。 ダイアログが消えると、テンプレートがインストールされ、使用できる状態になります。 ユーザーが [ファイル] メニューに戻ると、上記のようにテンプレート サブメニューにテンプレートの名前が設定されます。

まとめ

Windows インストーラーによって公開される "無料" 機能と API を利用すると、サポート コストの削減、アプリケーションの安定性の向上、ユーザー エクスペリエンスの向上に向けて長い道のりを行くいくつかの優れた機能が提供されます。 ここで示す例は、本質的にはやや簡単ですが、独自のソリューションを実装するための優れた出発点を形成することを願っています。 利用可能な API の一部を見てきましたが、すべてについては説明していません。 Windows インストーラー API のすべての機能を調べるには少し時間を取ります。Windows インストーラーのこれらの比較的未設定の機能をどれだけ簡単に利用できるかに驚かれることでしょう。

 

筆者について

Michael Sanford は、701 Software (http://www.701software.com) の社長兼最高ソフトウェア アーキテクトです。 701を設立する前は、Zero G Softwareによって買収された ActiveInstall Corporation の代表取締役社長でした。 ActiveInstall は、Windows インストーラー作成ソリューションの知名人を達成しました。 Michael は、Microsoft Certified Solution Developer (MCSD)、Microsoft Certified Systems Engineer (MCSE)、Windows インストーラー MVP です。 Michael のブログは、 で http://msmvps.com/michael読むことができます。