.NETプログラミング研究 第62号

.NET Tips

MSIファイルのWindows Installerデータベースをプログラムで編集する

今まで紹介したデプロイメントプロジェクトのTipの幾つかは、MSIファイルのWindows Installerデータベースを編集する必要があり、そのためにOrcaを使用してきました。しかし現実的な問題として、MSIファイルを作成するたびにOrcaを使って手作業で編集するのは面倒ですし、危険でもあります。ここではOrcaを使ってMSIファイルのデータベースを編集するのではなく、スクリプトやプログラムで自動的に編集するための方法を紹介します。

実はそのサンプルがPlatform SDKにあります。ScriptsフォルダにあるVBScriptがそれです。多くのサンプルがあり、とても参考になります。

この内、「WiRunSQL.vbs」はSQLクエリーによりWindows Installerデータベースを更新するスクリプトで、まさに求めているものです。

まずはWiRunSQL.vbsを使うことによりMSIファイルのデータベースを編集してみましょう。次のようなコマンドラインにより、PropertyテーブルにProperty=ALLUSERS、Value=2の行を追加できます。

cscript WiRunSQL.vbs <MSIファイル名> "INSERT INTO Property(Property,Value) VALUES('ALLUSERS','2')"

前号にて、「すべてのユーザー/このユーザーのみ」チェックボックスを隠し、「すべてのユーザー」をデフォルトとする方法を紹介しましたが、下のURLの投稿では、WiRunSQL.vbsを使ってこれを実現する方法が紹介されています。

なおこの例ではWiRunSQL.vbsを何回も呼び出していますが、WiRunSQL.vbsは複数のSQLクエリーを引数に指定できるようなので、そのようにした方がより効率的でしょう。

次に「WiRunSQL.vbs」を参考にして、自分でスクリプトを書いてみます。このスクリプトではコマンドライン引数として渡されたMSIファイルのデータベースのPropertyテーブルにProperty=ALLUSERS、Value=2の行を追加しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
Option Explicit
 
Const msiOpenDatabaseModeTransact = 1
 
Dim msiPath : msiPath = Wscript.Arguments(0)
 
Dim installer
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
Dim database
Set database = installer.OpenDatabase(msiPath, msiOpenDatabaseModeTransact)
 
Dim query
query = "INSERT INTO Property(Property, Value) VALUES('ALLUSERS', '2')"
Dim view
Set view = database.OpenView(query)
view.Execute
database.Commit

もう一つサンプルを示します。この例では、PropertyテーブルにProperty=ALLUSERSの列があるか調べ、なければ追加し、あればValueを変更しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
Option Explicit
 
Const msiOpenDatabaseModeTransact = 1
 
Const msiViewModifyInsert = 1
Const msiViewModifyUpdate = 2
 
Dim msiPath : msiPath = Wscript.Arguments(0)
 
Dim installer
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
Dim database
Set database = installer.OpenDatabase(msiPath, msiOpenDatabaseModeTransact)
 
Dim query
query = "Select * FROM Property WHERE Property='ALLUSERS'"
Dim view
Set view = database.OpenView(query)
view.Execute
Dim record
Set record = view.Fetch
Dim viewModify
viewModify = msiViewModifyUpdate
'ALLUSERSがない時
If record Is Nothing Then
    Set record = installer.CreateRecord(2)
    viewModify = msiViewModifyInsert
End If
record.StringData(1) = "ALLUSERS"
record.StringData(2) = "2"
view.Modify viewModify, record
database.Commit

さて、これと同じことをVB.NETやC#で行うには、どのようにすればよいのでしょうか?

まずはじめに考えられる方法は、上と同様にCOMを使う方法でしょう。ところが、実際に「Microsoft Windows Installer Object Library」(msi.dll)を参照に追加してみると分かるのですが、事前バインディングではInstallerオブジェクトが取得できないため、うまくいきません。遅延バインディングにすれば、大丈夫のようです。

または、Windows Installer APIを直接呼び出してもよいでしょう。Windows Installer APIについては、次のリンク先で詳しく説明されています。

Windows Installer APIのラッパークラスがCode Projectで紹介されていますので、このようなものを利用させていただくのもよいでしょう。

また、WiXに同梱されているwix.dllも役に立ちます。

他のサイトで紹介されているWindows Installerに関する役立つ話題

これまでこのメールマガジンでは8回に分けてデプロイメントプロジェクトに関するTipを紹介してきましたが、今回でひとまず終了にします。最後に、他のサイトで紹介されているWindows Installerに関する役立つ話題を幾つか紹介します。詳しい内容は、リンク先をご覧ください。

「プログラムの追加と削除」に表示しない

ARPSYSTEMCOMPONENTプロパティを1にします。Windows 2000より前では、ARPNOREMOVEを1にすることにより非表示となり、Windows 2000以降では「削除」ボタンが使えなくなります。

OrcaでMSIファイルを編集後ファイルのサイズが増える

OrcaでMSIファイルを編集し保存すると、MSIファイルのサイズが大きくなることがあるようです。サイズを減らすには、「Save As」で保存しなおします。

アンインストールするためのショートカットを作成する

リンク先が「[SystemFolder]\msiexec.exe」で、コマンドライン引数が「/x [ProductCode]」のショートカットを作成します。

複数のバージョンの同じコンポーネントをインストールする

次のページをどうぞ。

パッチファイルを作成する

VS.NETのデプロイメントプロジェクトを使ってMSIファイルを作成し、アプリケーションを配布する場合は、パッチによるアップデートはお勧めできません。しかしどうしてもという方は、次のURLをご覧ください。

MSIファイル内のファイルを抽出する

下で紹介するツールを使うことにより、MSIファイル内のファイルを抜き出すことができます。

デプロイメントプロジェクトによる配布は、Windows Installerを使って行われます。今までWindows Installerに関するTipsをいくつか紹介しましたが、それでもやはりWindows Installerを好きになれない(あるいはむしろ嫌いになった)という方もいらっしゃるかもしれません。古きよきインストーラの方がよっぽどよいという意見もあるでしょう。

幸いにして.NETアプリケーションはXCOPYによる配置が可能です。つまり、Windows Installerを使う必要は必ずしもないということです。しかしWindows Installerを使わないと配置が困難なケースが幾つかあります。

ここからはこのようにWindows Installer以外のインストーラを使う際に実現が難しい問題の解決法を紹介します。

Windows Installer以外のインストーラでWindowsサービスアプリケーションをインストールする

VS.NETとデプロイメントプロジェクトを使用した場合、作成したWindowsサービスアプリケーションのインストーラを作成するのはとても簡単です。その方法はMSDNで詳しく説明されています。

この方法によると、「インストーラの追加」リンクをクリックすることにより、プロジェクトにInstallerクラスの派生クラス(ProjectInstallerクラス)を追加し、デプロイメントプロジェクトの「カスタム動作」にプロジェクトのプライマリ出力を追加するというものです。つまり、ProjectInstallerクラスにより、サービスのインストールとアンインストールを行います。ですので、このProjectInstallerクラスを処理することができれば、デプロイメントプロジェクトを使う必要がないということになります。

さて、Installerクラスによるカスタム動作がどのように行われるかについては、このメールマガジンの第57号で解説しました。結論を言うと、InstallUtilが使われています。カスタム動作ではInstallUtilLib.dllが使われていますが、InstallUtil.exeを使ってもおなじです。

以上をまとめると、次のような方法でサービスのインストールが可能と言えます。

まずサービスのプロジェクトにProjectInstallerクラスを追加するところまではヘルプと同じです。あとはデプロイメントプロジェクトのカスタム動作の代わりにInstallUtil.exeを使って、インストール時に

InstallUtil.exe (サービスのEXEファイルのパス)

を、アンインストール時に

InstallUtil.exe /u (サービスのEXEファイルのパス)

を実行すればよいということになります。(つまり、インストール時とアンインストール時に実行ファイルを起動できないインストーラでは残念ながら無理です。)

ここで新たな問題が発生します。InstallUtil.exeはどこにあるのでしょうか?InstallUtil.exeを配布パッケージに含めることができれば確実ですが、残念ながらInstallUtil.exeは再配布が許可されていません。

そこでここでは、InstallUtil.exeは共通言語ランタイムがインストールされているディレクトリにあるものとして、これを実行するようにします。

共通言語ランタイムがインストールされているディレクトリのパスは、.NET Frameworkバージョン1.1であれば、

(Windowsディレクトリ)\Microsoft.NET\Framework\v1.1.4322

であると決め付けたり、レジストリキー

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework

の「sdkInstallRootv1.1」の値を調べて知ることができます。しかし.NET Frameworkが使えるのであれば、RuntimeEnvironment.GetRuntimeDirectoryメソッドを使うのが確実でしょう。

ここでは、InstallUtil.exeを起動させるために次のようなプログラムをC#で作成し、使用することにします。(ここでは、この実行ファイル名を「instsrv.exe」とします。)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
public class InstallUtil
{
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            ShowError("引数が不正です。");
            return;
        }
 
        //installutil.exeのフルパスを取得
        string installutilPath =
            System.IO.Path.Combine(
            System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(),
            "installutil.exe");
        if (!System.IO.File.Exists(installutilPath))
        {
            ShowError("installutil.exeが見つかりませんでした。");
            return;
        }
 
        //installutil.exeに渡すコマンドラインを作成
        string installutilArg = "";
        foreach (string arg in args)
        {
            installutilArg += " " +
                (arg.IndexOf(" ") > -1 ? "\"" + arg + "\"" : arg);
        }
 
        //installutil.exeを起動
        System.Diagnostics.Process p;
        try
        {
            p = System.Diagnostics.Process.Start(
                installutilPath, installutilArg);
            p.WaitForExit();
        }
        catch
        {
            ShowError("installutil.exeの起動に失敗しました。");
            return;
        }
 
        if (p.ExitCode != 0)
        {
            ShowError("installutil.exeがエラーコード(" +
                p.ExitCode.ToString() + ")を返しました。");
            return;
        }
 
        System.Environment.ExitCode = 0;
    }
 
    private static void ShowError(string msg)
    {
        System.Windows.Forms.MessageBox.Show(
            null,
            msg + "\nサービスのインストール/アンインストールに失敗しました。",
            "エラー",
            System.Windows.Forms.MessageBoxButtons.OK,
            System.Windows.Forms.MessageBoxIcon.Error);
        System.Environment.ExitCode = 1;
    }
}

この「instsrv.exe」をInstallUtil.exeと同じコマンドライン引数で呼び出すことにより、Installerクラスを処理できます。つまり、instsrv.exeを配布パッケージに含め、インストールで配置されるようにし、インストール時に

instsrv.exe (サービスのEXEファイルのパス)

を、アンインストール時に

instsrv.exe /u (サービスのEXEファイルのパス)

を実行すればサービスがインストール・アンインストールされるようになります。

具体例をひとつ示しましょう。ここでは「Inno Setup」を使います。

まず[Files]セクションを次のようにします(パスなどは適当に変更してください)。ここでは「WindowsService1.exe」がサービスのEXEファイル名です。instsrv.exeも配布対象にすることを忘れないでください。

[Files]
Source: "C:\WindowsService1.exe"; DestDir: "{app}"
Source: "C:\instsrv.exe"; DestDir: "{app}"

次にインストール時とアンインストール時にinstsrv.exeを起動するために、[Run]と[UninstallRun]セクションを次のようにします。

[Run]
Filename: "{app}\instsrv.exe"; Parameters: "/LogFile= ""{app}\WindowsService1.exe"""

[UninstallRun]
Filename: "{app}\instsrv.exe"; Parameters: "/u /LogFile= ""{app}\WindowsService1.exe"""

これでサービスのインストールとアンインストールが行われるようになるでしょう。

補足1:このようにinstallutil.exeではなく、Win32 APIを使ってサービスをインストール・アンインストールする方法は、次の記事で紹介されています。

補足2:Inno SetupのPascal scriptingを使ってサービスをインストール・アンインストールする方法は、次の記事で紹介されています。

グローバルアセンブリキャッシュにアセンブリをインストールする

ヘルプの「グローバル アセンブリ キャッシュ」によると、アセンブリをグローバルアセンブリキャッシュ(GAC)に配置する方法は3通りあります。

その3つの方法とは、次の通りです。

  1. Windows Installerを使ってインストールする
  2. グローバルアセンブリキャッシュツール(Gacutil.exe)を使う
  3. WindowsエクスプローラのWindows\assemblyディレクトリにアセンブリをドロップする(注)

注:WindowsエクスプローラのWindows\assemblyディレクトリの表示には、アセンブリキャッシュビューア(Shfusion.dll)というWindowsのシェル拡張機能が使われています。

インストーラを使ってこれを実現するとなると当然Windowsエクスプローラを使った方法は使えませんので、1か2の方法を使う他ありません。さらにここではWindows Installerを使わない方法ということなので、2の方法しか残りません。よってここではGacutil.exeを使う方法を考えます。

注意:ヘルプの「グローバル アセンブリ キャッシュ」にあるように、配置時にアセンブリをGACにインストールするには、Windows Installer 2.0以上を使わなければならず、それ以外の方法は開発時にのみ使用すべきとされています。ここではGacutil.exeを使った方法を紹介しますが、本来はWindows Installerを使うべきであることを覚えておいてください。

Gacutil.exeを使ってGACにアセンブリをインストールするには「/i」オプションを、アンインストールするには「/u」オプションを使用します。具体的には、アセンブリをGACにインストールするには

Gacutil.exe /i (アセンブリのパス)

とし、アンインストールするには

Gacutil.exe /u (アセンブリ名)

とします。(注)

注:このようなアセンブリ名を指定してアンインストールすると、複数のバージョンのアセンブリがインストールされていた時は、そのすべてがアンインストールされてしまいます。特定のバージョンのアセンブリのみをアンインストールするには、完全限定名を指定します。

ヘルプによると、実際の製品のインストールにGacutil.exeを使用する場合は、参照カウントをサポートするオプション「/r」を使用しろということです。/rオプションでは3つのインストールスキームタイプのうち、1つを使用します。インストールするアプリケーションが「アプリケーションの追加と削除」に追加される場合は、インストールスキームタイプとしてUNINSTALL_KEYを使用できます。「アプリケーションの追加と削除」に追加しない場合は、FILEPATHを使用できます。また独自に管理する場合は、OPAQUEを使用します。

具体例を示しましょう。ここではOPAQUEを使用します。次のようにしてGACにアセンブリをインストールします。"MyApplication1"と"my application 1"の部分はインストールするアプリケーションによって適当に変更します。

Gacutil.exe /ir (アセンブリのパス) OPAQUE "MyApplication1" "my application 1"

アンインストールするには、次のようにします。

Gacutil.exe /ur (アセンブリ名) OPAQUE "MyApplication1" "my application 1"

Gacutil.exeの使い方は理解できたものとし、次に進みましょう。インストーラからこのGacutil.exeを呼び出す方法は、先ほどの「Windows Installer以外のインストーラでWindowsサービスアプリケーションをインストールする」で紹介したInstallUtil.exeを呼び出す方法と全く同じです。つまり、そちらで示したコードの「InstallUtil.exe」を「Gacutil.exe」に変更するだけで使用できるでしょう。よってGacutil.exeを呼び出す方法はそちらを参考にしていただくこととし、ここでは説明しません。

さて、実はGacutil.exeを使う以外にも方法があります。.NET Framework 1.1からはSystem.EnterpriseServices.Internal名前空間PublishクラスのGacInstallとGacRemoveメソッドでアセンブリのGACへのインストール、アンインストールを行うことができます。ただしこの方法では残念ながら参照カウントの追加ができないようなので、Gacutil.exeを使った方がより優れているといえるでしょう。

以下にGacInstall、GacRemoveメソッドを使ってGACにアセンブリをインストール、アンインストールするプログラムの例を示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
public class GacInst
{
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            ShowError("引数が不正です。");
            return;
        }
 
        System.EnterpriseServices.Internal.Publish pub =
            new System.EnterpriseServices.Internal.Publish();
        if (args[0] == "/i")
        {
            try
            {
                //GACにインストール
                pub.GacInstall(args[1]);
            }
            catch
            {
                ShowError("GACへのインストールに失敗しました。");
                return;
            }
        }
        else if (args[0] == "/u")
        {
            try
            {
                //GACからアンインストール
                pub.GacRemove(args[1]);
            }
            catch
            {
                ShowError("GACからのアンインストールに失敗しました。");
                return;
            }
        }
        else
        {
            ShowError("引数が不正です。");
            return;
        }
 
        System.Environment.ExitCode = 0;
    }
 
    private static void ShowError(string msg)
    {
        System.Windows.Forms.MessageBox.Show(
            null,
            msg + "\nGACへのインストール/アンインストールに失敗しました。",
            "エラー",
            System.Windows.Forms.MessageBoxButtons.OK,
            System.Windows.Forms.MessageBoxIcon.Error);
        System.Environment.ExitCode = 1;
    }
}

このプログラム(「gacinst.exe」とする)の使用法を示します。オプション/iでインストールします。

gacinst.exe /i (アセンブリファイルのフルパス)

またオプション/uでアンインストールします。このとき、アセンブリ名ではなく、アセンブリファイルのフルパスを指定することに注意してください。

gacinst.exe /u (アセンブリファイルのフルパス)

参考:


ページ情報
  • 作成日 : 2010-03-19 (金) 01:42:32
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-19 (金) 01:42:32
  • 最終編集者 : DOBON!
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]