次の方法で共有


アプリケーションの復元: Windows インストーラの隠れた機能を明らかにする

Michael Sanford
701Software

March 2005

要約: Windows インストーラには開発者コミュニティにこれまでほとんど注目されていなかった機能がいくつかあります。これらの機能により、アプリケーションによる実行時の自己修復や、ユーザーの操作に基づいてオプション コンポーネントのインストールが可能になります。サンプル プログラム ファイル内では実際のコメント行は英語で書かれていますが、この記事内では説明目的で日本語で書かれています。

MSI Integration Sample Code.msi のダウンロード

目次

はじめに
シェルの統合による復元
Windows インストーラ API を導入する
主なアプリケーション API
問題 #1: 自己呼び出しによる復元
問題 #2: オンデマンドでのインストール
まとめ

はじめに

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

Windows インストーラは、アプリケーションの安定性維持に著しい進歩をもたらした復元機能を提供していますが、この機能は、Windows インストーラに "エントリ ポイント" を与えるシェルと対話しながらユーザーが行う特定の操作に基づいており、このエントリ ポイントによって Windows インストーラはアプリケーションの構成に関する問題を検出し、それを修復できます。

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

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

特定の環境において、Windows インストーラに組み込まれた復元機能がアプリケーションの構成に関するすべての問題を検出できない、または必要なエントリ ポイントがアクティブになっていないためにアプリケーションが機能しない場合があります。幸い、Windows インストーラの開発チームではこの問題を理解し、豊富な Windows インストーラ API によって追加の復元機能を用意しました。

シェルの統合による復元

Windows インストーラ API の提供する高度な復元機能について知る前に、Windows インストーラでアプリケーションを配布する際に追加コストを伴わずに解決できる一般的な復元のシナリオについて見てみましょう。

このシナリオでは、SimplePad と呼ばれる簡単なテキスト編集アプリケーションを配布します。インストールの作成には、Microsoft の Open Source WiX Toolkit (詳細については、http://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 インストーラのファイル関連付けによるものです。このメカニズムの動作はショートカットとほぼ同様ですが、アプリケーションの実行可能ファイルに直接リンクする代わりに、登録したファイルの種類によって関連付けが行われます。図 2 のように、Windows インストーラのファイル関連付けは、標準的なファイル関連付けと同じメカニズムを使用して定義されますが、1 つ例外があります。図 2 で、一般的な "shell\Open\command" レジストリ キーの下に追加の値が表示されていることに注目してください。この"command" という名前が付けられた追加の値は、ユーザーが Windows のシェル内からファイルをダブルクリックした際に常に Windows インストーラによって参照されます。この不可解に見える文字列は、"Darwin Descriptor" と呼ばれることもあり、特定の製品、コンポーネント、および機能をエンコード表示したものです。この値が存在すると、Windows インストーラはデータをデコードし、それを使用して製品やコンポーネントに対するチェックを行います。コンポーネントに不足や破損が見つかった場合は、不足しているファイルやデータの復元を行う修復機能が Windows インストーラにより起動され、適切なコマンド ライン オプションをアプリケーションに渡してそのアプリケーションが通常どおり起動されます。

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

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

この独特な機能が、コンポーネントを使用するアプリケーションから完全に独立したものであることは特筆に値します。つまり、そのコンポーネントを使用するアプリケーションが Windows インストーラを使用してインストールされなかった場合、また呼び出し元のアプリケーションが単に VBScript の場合でも、コンポーネントに使用された COM のアドバタイズ機能は引き続き正しく動作します。

図 3. COM サーバーにおける "Darwin Descriptor" の表示

これまでは、コードを一切書く必要のない Windows インストーラの機能を利用した復元について説明してきましたが、次により高度で堅牢な復元の実装について説明しましょう。

Windows インストーラ API を導入する

これまでのシナリオでは Windows インストーラの既定の動作がうまく機能しましたが、現実には、もう少し高度な方法で Windows インストーラを使用する場合があります。もっと困難なシナリオに対処できるよう、シナリオの例を発展させてみましょう。

アプリケーションが 2 つ以上の実行可能ファイルから構成されている場合がよくあります。この一例として、Updater Application Block で見られるように、更新のチェックとインストールを行うブートストラップ実行可能ファイルを使用するアプリケーションが考えられます。この場合、最初の実行可能ファイルはユーザーが [スタート] メニューのショートカットをクリックしたときに呼び出されるファイルです。これによって次に、アプリケーションのメインのユーザー インターフェイスを含む 2 つ目の実行可能ファイルが起動されます。インストールの構成によっては、メインのアプリケーション実行可能ファイルに関する問題が Windows インストーラのエンジンによって検出されずにそのまま残る可能性が十分あります。一連のコードを作成して起動時に実行し、ランタイム環境を検証するというオプションも考えられますが、実行可能ファイル自体が不足または破損している状況では役に立ちませんし、さらには問題を容易に修復することができなくなります。そこで、より効果的なソリューションとして、配置パッケージに既に定義されているアプリケーションの設定に関して Windows インストーラが取得した情報を利用するという方法があります。

Windows インストーラ API では、アプリケーションの完全性を確認する方法として、ユーザーがシェルを操作する際に使用されるものと同じメカニズムを使用します。アプリケーション内から API を呼び出すことで、既に説明したシェルの "エントリ ポイント" に頼ることなく同じ結果を確実に得ることができます。

以下に、Windows インストーラのシェル統合復元機能では対処できないシナリオの一覧を示します。

  • OS から開始するアプリケーション (実行レジストリ キーまたは一度だけ実行するレジストリ キー)
  • システム サービス
  • 定期的なタスク
  • 他のアプリケーションによって実行されるアプリケーション
  • コマンド ライン アプリケーション

上記以外にも多くのシナリオがありますが、その傾向は理解していただけると思います。次の例では、これまでに説明したシェル統合に頼らず Windows インストーラの復元機能を利用する方法について示します。これをお読みになると、その概念を利用して、実行可能コードの実行を含むどのようなシナリオにも容易に適用することができます。

主なアプリケーション API

話をシナリオの例に進める前に、アプリケーション内から使用できる主な Windows インストーラ API をいくつか見てみましょう。これらの API の使用方法の詳細については、Platform SDK の 「Installer Function Reference」(英語) を参照してください。

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

問題 #1: 自己呼び出しによる復元

最初に、システムからアプリケーションの実行可能ファイルを実際に削除し、ショートカットを使用して Windows インストーラで問題の検出と修復を行い、削除したファイルを再インストールするという非常に簡単なシナリオについて説明しました。このシナリオは、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 に一連の標準テンプレートを追加してほしいという要望を多数受けてきました。顧客の中にはこの機能を強く希望する声がある一方、ほとんどのユーザーにとって必要ではない無関係なデータをインストールすることに懸念を示す声もありました。

顧客のこの新しい要件への対応方法に関するエンジニアの話し合いを耳にしたインストール エンジニアの 1 人がこの話し合いに加わり、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("テンプレートのアンインストール")
      AddHandler newItem.Click, AddressOf UninstallTemplates
      .Add(newItem)
    Else
      newItem = New MenuItem("テンプレートのインストール")
      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 のいくつかを見てきましたが、もちろんすべての API について説明したわけではありません。Windows インストーラ API のすべての機能について調べてみると、嬉しいことに Windows インストーラの比較的利用されていないこれらの機能は簡単に活用できることがわかるでしょう。

 

執筆者紹介

Michael Sanford は 701 Software (http://www.701software.com/Leave-ms) の社長兼チーフ ソフトウェア アーキテクトです。701 社を設立する以前は、Zero G Software によって買収された ActiveInstall Corporation の社長兼 CEO を務めていました。ActiveInstall 社は Windows インストーラ オーサリング ソリューションで評判になった会社です。Michael は、マイクロソフト公認ソリューション開発者 (MCSD)、マイクロソフト公認システム エンジニア (MCSE)、および Windows インストーラの MVP です。氏のブログである Michael's Blog は http://msmvps.com/michael![Leave-ms](images/Cc465454.leave-ms(ja-jp,MSDN.10).gif) でお読みになれます。