.NET で Windows コントロールパネルのアイテムを作成する

# はじめに

以前に Delphi で Windows コントロールパネルのアプレットを作成しました。
これを VB.NET で作り直せないかと思っていました。でもちゃんと作るのは面倒そうなんですね。

そんなとき次の記事を見つけました。
[How to Register Executable Control Panel Items (Windows)]
(https://msdn.microsoft.com/en-us/library/windows/desktop/hh127450.aspx)

通常の実行ファイルをコントロールパネルに登録できるんですね。
アプレットでなくてこれでいいんじゃないかな。
これを参考にしてプログラムを書いてみました。

# コントロールパネルのアイテムに登録できる実行ファイルを作成

通常のアプリケーションプログラムを作成します。
その実行ファイルがコントロールパネルのアイテムとして登録できるようにします。

## プロジェクトを作る

まず、「Windows フォーム アプリケーション」のプロジェクトを作成します。

## プロジェクトにクラスを追加する

プロジェクトに「クラス」を新規追加します。ここではクラス名を「Register」にします。

以下の属性を指定しておきます。

```csharp:Register.cs
public class Register
{
    private const string ApplicationName = "SampleApp.CplSample";
    private const string DisplayName = "CPL Sample";
    private const string Description = "ControlPanel Item Sample";
    private const string Category = "0";
```

```vb:Register.vb
Public Class Register

    Private Const ApplicationName = "SampleApp.CplSample"
    Private Const DisplayName = "CPL Sample"
    Private Const Description = "ControlPanel Item Sample"
    Private Const Category = "0"
```

* ApplicationName　アイテムの名称。「control.exe /name ●●●●」のように実行するときに使用できる。
* DisplayName　アイテムのアイコンに表示される名称
* Description　アイテムのアイコンにマウスポインタを置いたときに表示される説明
* Category　アイテムが表示されるコントロールパネルのカテゴリ。以下のページを参照。
[Assigning Control Panel Categories (Windows)]
(https://msdn.microsoft.com/en-us/library/windows/desktop/cc144183.aspx)

## COM サーバ登録機能を実装する

作成しているアプリケーションプログラムが COM サーバとして登録できるようにします。

まず、アセンブリを COM 参照可能にします。以下の手順で作業します。

> プロジェクトの「プロパティ」を開く。
> 「アプリケーション」タブを選択。
> 「アセンブリ情報」を押下。
> ダイアログボックスで「アセンブリを COM 参照可能にする」にチェック。

次に、アセンブリに署名します。以下の手順で作業します。

> プロジェクトの「プロパティ」を開く。
> 「署名」タブを選択。
> 「アセンブリに署名する」をチェック。
> 「厳密な名前のキーファイルを選択」で「新規作成」を選択。
> ダイアログボックスで「キーファイル名」に任意のファイル名を指定。
> 「キーファイルをパスワードで保護する」は必須ではない。

クラスに GUID を設定します。
GUID はクラスごとに新規作成して下さい。

```csharp:Register.cs
[Guid("99999999-9999-9999-9999-9999999999")]
public class Register
```

```vb:Register.vb
<Guid("99999999-9999-9999-9999-9999999999")>
Public Class Register
```

GUID を作成するのはこちら
[Create GUID Tool in Visual Studio - TechNet Articles - United States (English) - TechNet Wiki]
(http://social.technet.microsoft.com/wiki/contents/articles/33047.create-guid-tool-in-visual-studio.aspx)

## 登録および解除のためのメソッドを実装する

登録および解除のためのメソッドを実装します。

```csharp:Register.cs
using Microsoft.Win32;
using System.Reflection;
using System.Runtime.InteropServices;

public class Register
{
    [ComRegisterFunction]
    public static void Regist(Type t)
    {
        try
        {
            if (t.GUID == Guid.Empty)
            {
                throw new ArgumentException("clsid must not be empty");
            }
            string clsid = t.GUID.ToString("B");
            string path = Assembly.GetExecutingAssembly().Location;

            using (RegistryKey key = Registry.LocalMachine.CreateSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\NameSpace\\" + clsid))
            {
                if (key != null)
                {
                    key.SetValue(null, DisplayName);
                }
            }
            using (RegistryKey key = Registry.ClassesRoot.CreateSubKey("CLSID\\" + clsid))
            {
                if (key != null)
                {
                    key.SetValue(null, DisplayName);
                    key.SetValue("InfoTip", Description);
                    key.SetValue("System.ApplicationName", ApplicationName);
                    key.SetValue("System.ControlPanel.Category", Category);
                }
            }
            using (RegistryKey key = Registry.ClassesRoot.CreateSubKey("CLSID\\" + clsid + "\\DefaultIcon"))
            {
                if (key != null)
                {
                    key.SetValue(null, path);
                }
            }
            using (RegistryKey key = Registry.ClassesRoot.CreateSubKey("CLSID\\" + clsid + "\\Shell\\Open\\Command"))
            {
                if (key != null)
                {
                    key.SetValue(null, path, RegistryValueKind.ExpandString);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }

    [ComUnregisterFunction]
    public static void Unregist(Type t)
    {
        try
        {
            if (t.GUID == Guid.Empty)
            {
                throw new ArgumentException("clsid must not be empty");
            }
            string clsid = t.GUID.ToString("B");
            string path = Assembly.GetExecutingAssembly().Location;

            Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ControlPanel\\NameSpace\\" + clsid, false);
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\" + clsid, false);
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\" + clsid + "\\DefaultIcon", false);
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\\" + clsid + "\\Shell\\Open\\Command", false);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }
```

```vb:Register.vb
Imports Microsoft.Win32
Imports System.Reflection
Imports System.Runtime.InteropServices

Public Class Register

    <ComRegisterFunction()>
    Public Shared Sub Regist(ByVal t As Type)
        Try
            If t.GUID = Guid.Empty Then
                Throw New ArgumentException("clsid must not be empty")
            End If
            Dim clsid As String = t.GUID.ToString("B")
            Dim path As String = Assembly.GetExecutingAssembly.Location

            Using key As RegistryKey = Registry.LocalMachine.CreateSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace\" & clsid)
                If key IsNot Nothing Then
                    key.SetValue(Nothing, DisplayName)
                End If
            End Using
            Using key As RegistryKey = Registry.ClassesRoot.CreateSubKey("CLSID\" & clsid)
                If key IsNot Nothing Then
                    key.SetValue(Nothing, DisplayName)
                    key.SetValue("InfoTip", Description)
                    key.SetValue("System.ApplicationName", ApplicationName)
                    key.SetValue("System.ControlPanel.Category", Category)
                End If
            End Using
            Using key As RegistryKey = Registry.ClassesRoot.CreateSubKey("CLSID\" & clsid & "\DefaultIcon")
                If key IsNot Nothing Then
                    key.SetValue(Nothing, path)
                End If
            End Using
            Using key As RegistryKey = Registry.ClassesRoot.CreateSubKey("CLSID\" & clsid & "\Shell\Open\Command")
                If key IsNot Nothing Then
                    key.SetValue(Nothing, path, RegistryValueKind.ExpandString)
                End If
            End Using

        Catch ex As Exception
            Console.WriteLine(ex.Message)
            Throw ex
        End Try
    End Sub

    <ComUnregisterFunction()>
    Public Shared Sub Unregist(ByVal t As Type)
        Try
            If t.GUID = Guid.Empty Then
                Throw New ArgumentException("clsid must not be empty")
            End If
            Dim clsid As String = t.GUID.ToString("B")

            Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace\" & clsid, False)
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\" & clsid, False)
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\" & clsid & "\DefaultIcon", False)
            Registry.ClassesRoot.DeleteSubKeyTree("CLSID\" & clsid & "\Shell\Open\Command", False)

        Catch ex As Exception
            Console.WriteLine(ex.Message)
            Throw ex
        End Try
    End Sub
```

## ビルドする

ビルドするとアセンブリファイルが作成されます。これを COM サーバとして Windows に登録します。

## COM サーバとして登録する

C# または VB.NET で作成したアセンブリファイルを COM サーバとして登録するには、.NET Framework に同梱されている「regasm」ツールを使います。

```
regasm ContextMenuExtension.dll /codebase
```

# おわりに

こんな記事を見かけました。

[ついに！Windows 10から｢コントロールパネル｣が消える | ギズモード・ジャパン] (https://www.gizmodo.jp/2020/07/microsoft-will-axe-control-panel-from-windows-10-were.html)

これが本当になると、この記事も意味なくなってしまいますね。