┏第62号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ──<メニュー>─────────────────────── ■.NET Tips ・MSIファイルのWindows Installerデータベースをプログラムで編集  する ・他のサイトで紹介されているWindows Installerに関する役立つ話題 -「プログラムの追加と削除」に表示しない -OrcaでMSIファイルを編集後ファイルのサイズが増える -アンインストールするためのショートカットを作成する -複数のバージョンの同じコンポーネントをインストールする -パッチファイルを作成する -MSIファイル内のファイルを抽出する ・Windows Installer以外のインストーラでWindowsサービスアプリケー  ションをインストールする ・グローバルアセンブリキャッシュにアセンブリをインストールする ─────────────────────────────── ─────────────────────────────── ■.NET Tips ─────────────────────────────── ●MSIファイルのWindows Installerデータベースをプログラムで編集 する 今まで紹介したデプロイメントプロジェクトのTipの幾つかは、MSIフ ァイルのWindows Installerデータベースを編集する必要があり、そ のためにOrcaを使用してきました。しかし現実的な問題として、MSI ファイルを作成するたびにOrcaを使って手作業で編集するのは面倒で すし、危険でもあります。ここではOrcaを使ってMSIファイルのデー タベースを編集するのではなく、スクリプトやプログラムで自動的に 編集するための方法を紹介します。 実はそのサンプルがPlatform SDKにあります。Scriptsフォルダにあ るVBScriptがそれです。多くのサンプルがあり、とても参考になりま す。 [URL]Windows Installer Scripting Examples http://msdn.microsoft.com/library/en-us/msi/setup/windows_installer_scripting_examples.asp この内、「WiRunSQL.vbs」はSQLクエリーによりWindows Installerデー タベースを更新するスクリプトで、まさに求めているものです。 [URL]Execute SQL Statements http://msdn.microsoft.com/library/en-us/msi/setup/execute_sql_statements.asp まずはWiRunSQL.vbsを使うことによりMSIファイルのデータベースを 編集してみましょう。次のようなコマンドラインにより、Propertyテー ブルにProperty=ALLUSERS、Value=2の行を追加できます。 cscript WiRunSQL.vbs "INSERT INTO Property(Property,Value) VALUES('ALLUSERS','2')" 前号にて、「すべてのユーザー/このユーザーのみ」チェックボック スを隠し、「すべてのユーザー」をデフォルトとする方法を紹介しま したが、下のURLの投稿では、WiRunSQL.vbsを使ってこれを実現する 方法が紹介されています。 [URL]Re: Using VS.NET 2003 to build a Setup project for ALLUSERS http://groups.google.com/group/microsoft.public.platformsdk.msi/msg/3398ae8f528dbfc2 なおこの例ではWiRunSQL.vbsを何回も呼び出していますが、WiRunSQL. vbsは複数のSQLクエリーを引数に指定できるようなので、そのように した方がより効率的でしょう。 次に「WiRunSQL.vbs」を参考にして、自分でスクリプトを書いてみま す。このスクリプトではコマンドライン引数として渡されたMSIファ イルのデータベースのPropertyテーブルにProperty=ALLUSERS、Value =2の行を追加しています。 ‥‥▽ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 を変更しています。 ‥‥▽ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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については、次のリンク先で詳しく説明され ています。 [URL]Windows Installer Reference http://msdn.microsoft.com/library/en-us/msi/setup/windows_installer_reference.asp Windows Installer APIのラッパークラスがCode Projectで紹介され ていますので、このようなものを利用させていただくのもよいでしょ う。 [URL]Wrapping the Windows Installer 2.0 API http://www.codeproject.com/csharp/msiinterop.asp また、WiXに同梱されているwix.dllも役に立ちます。 [URL]Windows Installer XML (WiX) toolset http://sourceforge.net/projects/wix/ ─────────────────────────────── ●他のサイトで紹介されているWindows Installerに関する役立つ話題 これまでこのメールマガジンでは8回に分けてデプロイメントプロジ ェクトに関するTipを紹介してきましたが、今回でひとまず終了にし ます。最後に、他のサイトで紹介されているWindows Installerに関 する役立つ話題を幾つか紹介します。詳しい内容は、リンク先をご覧 ください。 ★「プログラムの追加と削除」に表示しない ARPSYSTEMCOMPONENTプロパティを1にします。Windows 2000より前で は、ARPNOREMOVEを1にすることにより非表示となり、Windows 2000以 降では「削除」ボタンが使えなくなります。 [URL]Frequently Asked Questions About Windows Installer http://www.microsoft.com/windows2000/community/centers/management/msi_faq.mspx ★OrcaでMSIファイルを編集後ファイルのサイズが増える OrcaでMSIファイルを編集し保存すると、MSIファイルのサイズが大き くなることがあるようです。サイズを減らすには、「Save As」で保 存しなおします。 [URL]Frequently Asked Questions About Windows Installer http://www.microsoft.com/windows2000/community/centers/management/msi_faq.mspx ★アンインストールするためのショートカットを作成する リンク先が「[SystemFolder]\msiexec.exe」で、コマンドライン引数 が「/x [ProductCode]」のショートカットを作成します。 [URL]How do I create a shortcut to my uninstaller? http://www.installsite.org/pages/en/msifaq/a/1012.htm ★複数のバージョンの同じコンポーネントをインストールする 次のページをどうぞ。 [URL]Support for Multiple Versions of the Same Component with Visual Studio .NET 2003 http://msdn.microsoft.com/library/en-us/dv_vstechart/html/vstch_multivercomponent.asp ★パッチファイルを作成する VS.NETのデプロイメントプロジェクトを使ってMSIファイルを作成し、 アプリケーションを配布する場合は、パッチによるアップデートはお 勧めできません。しかしどうしてもという方は、次のURLをご覧くだ さい。 [URL]How to create installation patches for VS.NET deployment projects http://www.codeproject.com/dotnet/dotnetpatching.asp ★MSIファイル内のファイルを抽出する 下で紹介するツールを使うことにより、MSIファイル内のファイルを 抜き出すことができます。 [URL]Less MSIerables: A tool to Extract the contents of an .msi File http://blogs.pingpoet.com/overflow/archive/2005/06/02/2449.aspx ─────────────────────────────── デプロイメントプロジェクトによる配布は、Windows Installerを使 って行われます。今までWindows Installerに関するTipsをいくつか 紹介しましたが、それでもやはりWindows Installerを好きになれな い(あるいはむしろ嫌いになった)という方もいらっしゃるかもしれ ません。古きよきインストーラの方がよっぽどよいという意見もある でしょう。 幸いにして.NETアプリケーションはXCOPYによる配置が可能です。つ まり、Windows Installerを使う必要は必ずしもないということです。 しかしWindows Installerを使わないと配置が困難なケースが幾つか あります。 ここからはこのようにWindows Installer以外のインストーラを使う 際に実現が難しい問題の解決法を紹介します。 ●Windows Installer以外のインストーラでWindowsサービスアプリ ケーションをインストールする VS.NETとデプロイメントプロジェクトを使用した場合、作成した Windowsサービスアプリケーションのインストーラを作成するのはと ても簡単です。その方法はMSDNで詳しく説明されています。 [URL]チュートリアル : コンポーネント デザイナによる Windows サー ビス アプリケーションの作成 http://www.microsoft.com/japan/msdn/library/ja/vbcon/html/vbwlkWalkthroughCreatingWindowsServiceApplication.asp [URL]サービス アプリケーションへのインストーラの追加 http://www.microsoft.com/japan/msdn/library/ja/vbcon/html/vbtskAddingInstallersToYourServiceApplication.asp [URL]サービスのインストールとアンインストール http://www.microsoft.com/japan/msdn/library/ja/vbcon/html/vbtskInstallingServiceApplication.asp この方法によると、「インストーラの追加」リンクをクリックするこ とにより、プロジェクトにInstallerクラスの派生クラス( ProjectInstallerクラス)を追加し、デプロイメントプロジェクトの 「カスタム動作」にプロジェクトのプライマリ出力を追加するという ものです。つまり、ProjectInstallerクラスにより、サービスのイン ストールとアンインストールを行います。ですので、この ProjectInstallerクラスを処理することができれば、デプロイメント プロジェクトを使う必要がないということになります。 さて、Installerクラスによるカスタム動作がどのように行われるか については、このメールマガジンの第57号で解説しました。結論を言 うと、InstallUtilが使われています。カスタム動作では InstallUtilLib.dllが使われていますが、InstallUtil.exeを使って もおなじです。 [URL]インストーラ ツール (Installutil.exe) http://www.microsoft.com/japan/msdn/library/ja/cptools/html/cpconinstallerutilityinstallutilexe.asp 以上をまとめると、次のような方法でサービスのインストールが可能 と言えます。 まずサービスのプロジェクトに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」とします。) ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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; } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ この「instsrv.exe」をInstallUtil.exeと同じコマンドライン引数で 呼び出すことにより、Installerクラスを処理できます。つまり、 instsrv.exeを配布パッケージに含め、インストールで配置されるよ うにし、インストール時に instsrv.exe (サービスのEXEファイルのパス) を、アンインストール時に instsrv.exe /u (サービスのEXEファイルのパス) を実行すればサービスがインストール・アンインストールされるよう になります。 具体例をひとつ示しましょう。ここでは「Inno Setup」を使います。 [URL]Inno Setup http://www.jrsoftware.org/isinfo.php まず[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を使ってサー ビスをインストール・アンインストールする方法は、次の記事で紹介 されています。 [URL]Installing a Service Programmatically http://www.c-sharpcorner.com/Code/2003/Sept/InstallingWinServiceProgrammatically.asp 補足2:Inno SetupのPascal scriptingを使ってサービスをインストー ル・アンインストールする方法は、次の記事で紹介されています。 [URL]ISX Knowledge Base - Functions to Start, Stop, Install, Remove a NT Service http://www.vincenzo.net/isxkb/modules.php?name=News&file=article&sid=32 ─────────────────────────────── ●グローバルアセンブリキャッシュにアセンブリをインストールする ヘルプの「グローバル アセンブリ キャッシュ」によると、アセンブ リをグローバルアセンブリキャッシュ(GAC)に配置する方法は3通り あります。 [URL]グローバル アセンブリ キャッシュ http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconGlobalAssemblyCache.asp その3つの方法とは、次の通りです。 1.Windows Installerを使ってインストールする 2.グローバルアセンブリキャッシュツール(Gacutil.exe)を使う 3.WindowsエクスプローラのWindows\assemblyディレクトリにアセン ブリをドロップする(注) 注:WindowsエクスプローラのWindows\assemblyディレクトリの表示 には、アセンブリキャッシュビューア(Shfusion.dll)という Windowsのシェル拡張機能が使われています。 [URL]アセンブリ キャッシュ ビューア (Shfusion.dll) http://www.microsoft.com/japan/msdn/library/ja/cptools/html/cpgrfassemblycacheviewershfusiondll.asp インストーラを使ってこれを実現するとなると当然Windowsエクスプ ローラを使った方法は使えませんので、1か2の方法を使う他ありませ ん。さらにここではWindows Installerを使わない方法ということな ので、2の方法しか残りません。よってここではGacutil.exeを使う方 法を考えます。 [URL]グローバル アセンブリ キャッシュ ツール (Gacutil.exe) http://www.microsoft.com/japan/msdn/library/ja/cptools/html/cpgrfglobalassemblycacheutilitygacutilexe.asp 注意:ヘルプの「グローバル アセンブリ キャッシュ」にあるように、 配置時にアセンブリをGACにインストールするには、Windows Installer 2.0以上を使わなければならず、それ以外の方法は開発時 にのみ使用すべきとされています。ここではGacutil.exeを使った方 法を紹介しますが、本来はWindows Installerを使うべきであること を覚えておいてください。 Gacutil.exeを使ってGACにアセンブリをインストールするには「/i」 オプションを、アンインストールするには「/u」オプションを使用し ます。具体的には、アセンブリをGACにインストールするには Gacutil.exe /i (アセンブリのパス) とし、アンインストールするには Gacutil.exe /u (アセンブリ名) とします。(注) 注:このようなアセンブリ名を指定してアンインストールすると、複 数のバージョンのアセンブリがインストールされていた時は、そのす べてがアンインストールされてしまいます。特定のバージョンのアセ ンブリのみをアンインストールするには、完全限定名を指定します。 [URL]アセンブリ名 http://www.microsoft.com/japan/msdn/library/ja/cpguide/html/cpconAssemblyNames.asp ヘルプによると、実際の製品のインストールに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にアセンブリをイ ンストール、アンインストールするプログラムの例を示します。 ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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; } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ このプログラム(「gacinst.exe」とする)の使用法を示します。オ プション/iでインストールします。 gacinst.exe /i (アセンブリファイルのフルパス) またオプション/uでアンインストールします。このとき、アセンブリ 名ではなく、アセンブリファイルのフルパスを指定することに注意し てください。 gacinst.exe /u (アセンブリファイルのフルパス) 参考: [URL]Visual Basic .NET のグローバル アセンブリ キャッシュにア センブリをインストールする方法 http://support.microsoft.com/kb/315682/ja [URL]Demystifying the .NET Global Assembly Cache http://www.codeproject.com/dotnet/DemystifyGAC.asp =============================== ■ここで示したコードの多くはまずC#で書き、それを「C# to VB.NET Translator」でVB.NETのコードに変換し、修正を加えたものです。 [URL]C# to VB.NET Translator http://authors.aspalliance.com/aldotnet/examples/translate.aspx ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2005-Oct 2006)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2004 - 2005 DOBON! All rights reserved. ===============================