無効になったアドインを有効化してからExcelを起動する

##はじめに
業務アプリケーションで、ExcelやWordのアドインをVSTOで開発しています。
ExcelやWordは他の.NETアプリケーションからコマンド起動するのですが、何らかの原因でアドインが無効になってしまう事があり、業務に支障が出ていました。

無効になったアドインを有効化してからExcelやWordを起動する方法をまとめます。

## アドインが読み込まれない原因
アドインが読み込まれない場合、以下のいずれかに登録されてしまっている事が原因です。
ExcelやWordの[ファイル]-[オプション]-[アドイン]で確認することができます。

- アクティブでないアプリケーションアドイン
- 無効なアプリケーションアドイン

![無効-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196121/ce120a81-ea86-0c89-1614-a451378fae2a.png)


### アクティブでないアプリケーションアドイン
以下が原因で、**アクティブでないアプリケーションアドイン**に登録される事があります。

- アドインの読み込み時にエラーがスローされた
- アドインの動作に時間がかかった
- アドインの動作時にエラーがスローされた

Microsoftのドキュメントでは、**ソフトに無効化された VSTO アドイン**という表現が使われています。
https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-re-enable-a-vsto-add-in-that-has-been-disabled#soft-disabled-vsto-add-ins


### 無効なアプリケーションアドイン
以下が原因で、**無効なアプリケーションアドイン**に登録される事があります。
ExcelやWordがクラッシュする、よほどのエラーケースという事ですね。

- アドインが原因でExcel/Wordが予期せず終了した

Microsoftのドキュメントでは、**ハードに無効化された VSTO アドイン**という表現が使われています。
https://docs.microsoft.com/ja-jp/visualstudio/vsto/how-to-re-enable-a-vsto-add-in-that-has-been-disabled#hard-disabled-vsto-add-ins

## アドインが管理されているレジストリ
それぞれ、以下のレジストリエントリで管理されています。

### アクティブでないアプリケーションアドイン
**アクティブでないアプリケーションアドイン**に登録されているかどうかは、以下のレジストリを調べることで判断できます。
「Excel」の部分は「Word」「Outlook」でも同様です。

```
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\Excel\Addins\<アドイン名>
LoadBehavior の値が "2"
（アクティブなアプリケーションアドインは"3"）
```

LoadBehavior の値が "3"以外の場合、"3"に更新する、という処理で有効化できます。

参考：LoadBehavior の値
https://docs.microsoft.com/ja-jp/visualstudio/vsto/registry-entries-for-vsto-add-ins#loadbehavior-values

### 無効なアプリケーションアドイン
**無効なアプリケーションアドイン**に登録されているかどうかは、以下のレジストリを調べることで判断できます。
「Excel」の部分は「Word」「Outlook」でも同様です。

```
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\<バージョン>\Excel\Resiliency\DisabledItems
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\<バージョン>\Excel\Resiliency\CrashingAddinList
```

※ <バージョン> には以下の数字が入ります
Outlook 2013 : 15.0
Microsoft 365 Apps / Outlook 2019 / Outlook 2016 : 16.0

これらのレジストリの値の名前はランダムで、値にはアドイン名がバイナリ値で格納されています。
![無効-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196121/6bf5d45a-03b6-5b61-b439-1ed1ecfcb421.png)
アドイン名の判断が難しい場合は、CrashingAddinList や DisabledItems キーごと削除するでも良いかもしれません。


## アドインを有効化するコード
上記のレジストリをチェックし、アドインを有効化するコードです。参考になれば幸いです。

```C#
using Microsoft.Win32;

private void checkAddin() {
    var app = "Excel";
    var addin = "hogeAddin";

    //
    // 「アクティブでないアプリケーションアドイン」のチェック
    //
    try
    {
        var regKey = $@"SOFTWARE\Microsoft\Office\{app}\Addins\{addin}";
        using (var key = Registry.CurrentUser.OpenSubKey(regKey, true))
        {
            if (key == null)
            {
                MessageBox.Show("アドインがインストールされていません。");
                return;
            }

            if (!int.Equals(key.GetValue("LoadBehavior", -1), 3)) {
                // アクティブになっていないので「3」を設定する
                key.SetValue("LoadBehavior", 3, RegistryValueKind.DWord);
            }
        }
    }
    catch (Exception e)
    {
        // エラーは無視
        Console.Error.WriteLine("レジストリ[LoadBehavior]チェック:Exception\n" + e.ToString());
    }

    //
    // 「無効なアプリケーションアドイン」のチェック
    //
    try
    {
        using (var key = Registry.CurrentUser.OpenSubKey($@"SOFTWARE\Microsoft\Office\{getOfficeVer()}\{app}\Resiliency\DisabledItems", true))
        {
            if (key != null)
            {
                key.GetValueNames()
                .ToList()
                .ForEach(val =>
                {
                    var data = (byte[])key.GetValue(val, null);
                    if (data != null)
                    {
                        // キーで読みだした値から「0」を除去して結合。ASCIIから文字列を取得し、アドイン名が含まれている場合は該当のレジストリを消去。
                        if (System.Text.Encoding.UTF8.GetString(data.ToList().Where(v => v != 0).ToArray()).Contains($"{addin}"))
                        {
                            key.DeleteValue(val);
                        }
                    }
                });
            }
        }
    }
    catch (Exception e)
    {
        // エラーは無視
        Console.Error.WriteLine("レジストリ[DisabledItems]チェック:Exception\n" + e.ToString());
    }

    try
    {
        using (var key = Registry.CurrentUser.OpenSubKey($@"SOFTWARE\Microsoft\Office\{getOfficeVer()}\{app}\Resiliency\CrashingAddinList", true))
        {
            if (key != null)
            {
                key.GetValueNames()
                .ToList()
                .ForEach(val =>
                {
                    var data = (byte[])key.GetValue(val, null);
                    if (data != null)
                    {
                        // キーで読みだした値から「0」を除去して結合。ASCIIから文字列を取得し、アドイン名が含まれている場合は該当のレジストリを消去。
                        if (System.Text.Encoding.UTF8.GetString(data.ToList().Where(v => v != 0).ToArray()).Contains($"{addin}"))
                        {
                            key.DeleteValue(val);
                        }
                    }
                });
            }
        }
    }
    catch (Exception e)
    {
        // エラーは無視
        Console.Error.WriteLine("レジストリ[CrashingAddinList]チェック:Exception\n" + e.ToString());
    }
}
```