Internet Explorer を自作アドオンで拡張する（.NET Framework使用）

#はじめに
Internet Explorer（インターネットエクスプローラー）はオワコン、かと思いきや、サポート終了は未定[^1]で、サーバーエディションのOSにEdgeがまだ搭載されず、の状況です。
RPA も盛況で自動操作がいろいろできると便利であると思いますので、拡張方法を簡単にまとめてみました。

[^1]: [「IEを使わないで」ではなくて「IEを既定にしないで」](https://www.atmarkit.co.jp/ait/articles/1903/05/news011.html) - 山市良のうぃんどうず日記＠IT

#手順
Visual Studio 2019 で作成する例です。
##プロジェクト作成
###プロジェクトテンプレートを指定して作成
プロジェクトテンプレートとして、クラスライブラリ (.NET Framework) を選択して作成します。ソリューション名、フレームワークなどは任意です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100206/7c6ee490-2e42-45f8-4f59-79078471eb94.png)

###プロジェクトのプロパティでCOM公開設定
プロジェクトのプロパティ「アプリケーション」から「アセンブリ情報」ボタンをクリックして「アセンブリをCOM参照可能にする」へチェックを入れてOKボタンにて保存します。

###参照設定
「ソリューションエクスプローラー」の「参照」を右クリックして「参照の追加」を選択し、「COM」からつぎの2つを追加します。このほか、メッセージボックス表示のための ```System.Windows.Forms``` アセンブリなども必要に応じて追加します。

- Microsoft HTML Object （DOM を操作しないなら不要）
- Microsoft Internet Controls


##コーディング
[IObjectWithSite](https://docs.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-iobjectwithsite) インターフェイスをCOM 公開します。

ソリューションエクスプローラーで、ソリューションにインターフェイスを追加し、ファイル名を IObjectWithSite.cs とします。

```csharp:IObjectWithSite.cs
using System;
using System.Runtime.InteropServices;

namespace SampleIEAddOn
{
    [ComVisible(true),
     InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
     Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]

    public interface IObjectWithSite
    {
        [PreserveSig]
        int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
        [PreserveSig]
        int GetSite(ref Guid guid, out IntPtr ppvSite);
    }
}
```

次に、ソリューションエクスプローラーで、自動生成されている Class1.cs の名前を IEAddOn.cs に変更します。
「ツール」「GUID の作成」から独自にGUIDを生成して（書式は5番）、IObjectWithSite インターフェイスの実装を兼ねて次のようにコーディングします。

```csharp:IEAddOn.cs

using System;
using System.Linq;
using System.Runtime.InteropServices;
using mshtml;
using SHDocVw;
using Microsoft.Win32;

namespace SampleIEAddOn
{
    [ComVisible(true),
     ClassInterface(ClassInterfaceType.None),
     Guid("????????-????-????-????-????????????")] // <- 生成したGUID
    public class IEAddOn : IObjectWithSite
    {
        #region COMとBHOの登録と解除 - regasm ツールで呼ばれます。
        private const string BHOKEYNAME = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";

        [ComRegisterFunction]
        public static void RegisterBHO(Type type)
        {
            RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);

            if (registryKey == null)
                registryKey = Registry.LocalMachine.CreateSubKey(BHOKEYNAME);

            string guid = type.GUID.ToString("B");
            RegistryKey ourKey = registryKey.OpenSubKey(guid);

            if (ourKey == null)
                ourKey = registryKey.CreateSubKey(guid);

            ourKey.SetValue("Alright", 1);
            registryKey.Close();
            ourKey.Close();
        }

        [ComUnregisterFunction]
        public static void UnregisterBHO(Type type)
        {
            RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(BHOKEYNAME, true);
            string guid = type.GUID.ToString("B");

            if (registryKey != null)
                registryKey.DeleteSubKey(guid, false);
        }
        #endregion


        private WebBrowser webBrowser;

        public int GetSite(ref Guid guid, out IntPtr ppvSite)
        {
            IntPtr punk = Marshal.GetIUnknownForObject(webBrowser);
            int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
            Marshal.Release(punk);

            return hr;
        }

        public int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site)
        {
            if (site != null)
            {
                webBrowser = site as WebBrowser;
                webBrowser.DocumentComplete += WebBrowser_DocumentComplete; // DocumentCompleteイベントを利用する例
                webBrowser.BeforeNavigate2 += WebBrowser_BeforeNavigate2; // BeforeNavigate2イベントを利用する例
            }
            else
            {
                webBrowser.DocumentComplete -= WebBrowser_DocumentComplete;
                webBrowser.BeforeNavigate2 -= WebBrowser_BeforeNavigate2;
                webBrowser = null;
            }

            return 0;
        }

        private void WebBrowser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
        {
            System.Windows.Forms.MessageBox.Show("ナビゲート開始"); // 参照に System.Windows.Forms アセンブリを追加
        }

        private void WebBrowser_DocumentComplete(object pDisp, ref object URL)
        {
            var document = (HTMLDocument)webBrowser.Document;
            var titleText = document.getElementsByTagName("title")
                .Cast<IHTMLElement>()
                .FirstOrDefault()?.innerText ?? "空のタイトル";
            System.Windows.Forms.MessageBox.Show(titleText); // 参照に System.Windows.Forms アセンブリを追加
        }
    }
}

```



##アドオンとして登録
管理者権限で regasm ツールを使用して（スタートメニュー Visual Studio 2019 内の "Developer Command Prompt for VS2019" からが便利）、レジストリ登録します。厳密名の使用をお勧めされるので、本格開発では検討しましょう。

- 登録
```regasm /codebase <.dllファイルのフルパス>```

- 登録解除
```regasm /unregister <.dllファイルのフルパス>```

##試す
Internet Explorer（インターネットエクスプローラー）を起動してみます。初回「不明な発行元 '.....' が使えるようになりました」と下部に表示されるので「有効にする」を選択します。

サンプルでは、メッセージボックスが表示されます。うざったいと思いますので、結果を確認出来たら「アドオンの管理」から無効にするか、アンインストール(unregister)してください。


#参考にした情報源（感謝）

- https://www.codeproject.com/Articles/19971/How-to-attach-to-Browser-Helper-Object-BHO-with-C - Jia.Chenさん (CodeProject)
- http://eternalwindows.jp/browser/bho/bho00.html - EternalWindows