中堅プログラマーの備忘録

忘れっぽくなってきたので備忘録として・・・

【C#.net】USBメモリの挿入を検知して、取り外しまで行う。

1.概要

最近は情報漏洩の問題でセキュリティ機能付きUSBメモリの使用が推奨されています。
私の会社でも普通のUSBメモリは使えないという状況です。
なかなか厳しい世の中になりました・・・

前置きはこれくらいにして
今回はUSBメモリが挿入されたらイベントを取得して
特定の処理を行い、取り外すという一連の流れを
プログラムからやってみようと思います。

USBメモリの挿入を検知したら自動的にデータを更新し安全に取り外す

サーバーからデータをダウンロードして更新する・・・
というのが一般的な中このアナログ的な作業は重要だったりします。

2.実現方法

挿入の検知

これはウィンドウプロシージャである【WndProc】メソッドで行います。

ウィンドウプロシージャとは

ウインドウから送られてくるメッセージを処理する関数
になります。

.NetのWindowsフォームではこの【WndProc】メソッドが
Windowsメッセージ → .Netのイベント
への置き換えをやってくれます。


使い方としては【WndProc】メソッドを自分のフォームでオーバーライドすればいいということになります。


ここからは実際に送られてきたメッセージに対しての処理について記載していきます。

まずはメッセージのID番号(Msg)から【WM_DEVICECHANGE(0x219)】を拾います。
これは何かというと

デバイスまたはコンピューターのハードウェア構成への変更をアプリケーションに通知します。

ということなので、これにはUSBの挿入、取り外しなどが含まれています。

なのでここからUSBの挿入である【DBT_DEVICEARRIVAL(0x80000)】を
メッセージのID番号(Msg)の追加情報(WParam)から拾います。

今回は使用しませんがUSBの取り外し完了は【DBT_DEVICEREMOVECOMPLETE(0x8004)】になります。


ここまででUSBが挿入されたというイベントが取得出来ました。
更にUSBメモリであるということを判断するために
別の追加情報である(LParam)から【DBT_DEVTYP_VOLUME(0x00000002)】であるか取得し
デバイスの種類がボリュームであることを確認します。


ここまでがUSBメモリ挿入検知になります。

ドライブレターの取得

取り外しについてはドライブレターから行うつもりなので
(LParam)からドライブレターを取得し
論理ボリュームに関する情報が含まれている
【DEV_BROADCAST_VOLUME】構造体を使います。

typedef struct _DEV_BROADCAST_VOLUME {
  DWORD dbcv_size;
  DWORD dbcv_devicetype;
  DWORD dbcv_reserved;
  DWORD dbcv_unitmask;
  WORD  dbcv_flags;
} DEV_BROADCAST_VOLUME;

この構造体の中にある【dbcv_unitmask】が
ドライブレターをビットで表しているもので

bit0が立っている → Aドライブ
bit1が立っている → Bドライブ
ということになります。

取り外し

取り外しに関しては下記のサイトのスクリプトをそのまま使わせて頂きました。
www.codeproject.com

bool res = RemoveDriveTools.RemoveDrive("D:");

のようにすれば簡単に取り外しが行えます。
素晴らしい!

3スクリプト

スクリプトは下記のとおりとなりました。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace USBinsertRemove
{
    public partial class Form1 : Form
    {
        private const int WM_DEVICECHANGE = 0x219;              // デバイスまたはコンピューターのハードウェア構成への変更
        private const int DBT_DEVICEARRIVAL = 0x8000;           // USBの挿入
        private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;    // USBの取り外し
        private const int DBT_DEVTYP_VOLUME = 0x00000002;       // デバイスの種類がボリューム
        
        // 論理ボリュームに関する情報の構造体
        [StructLayout(LayoutKind.Sequential)]
        public struct DEV_BROADCAST_VOLUME
        {
            public int dbcv_size;
            public int dbcv_devicetype;
            public int dbcv_reserved;
            public int dbcv_unitmask;
        }
        public Form1()
        {
            InitializeComponent();
        }

        private static string GetDriveLetter(int dnum)
        {
            string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

            int pos = (dnum / 2) - 1;

            string letter = drives.Substring(pos, 1) + ":";

            return letter;
        }

        protected override void WndProc(ref Message m)
        {
            // 
            if (m.Msg == WM_DEVICECHANGE)
            {
                if (m.WParam.ToInt64() == DBT_DEVICEARRIVAL)
                {
                    // USB等の挿入
                    int devType = Marshal.ReadInt32(m.LParam, 4);
                    if (devType == DBT_DEVTYP_VOLUME)           // デバイスの種類がボリュームか?
                    {
                        // ボリュームの情報を取得
                        DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
                        // ドライブレターの取得
                        string dLetter = GetDriveLetter(vol.dbcv_unitmask);


                        // ここに処理を記載するか、重ければ外部で


                        // ボリュームの取り外し
                        bool res = RemoveDriveTools.RemoveDrive(dLetter);
                        if (res == true)
                        {
                            Console.WriteLine("取り外し成功");
                        }
                        else
                        {
                            Console.WriteLine("取り外し失敗");
                        }

                    }
                }
                else if (m.WParam.ToInt64() == DBT_DEVICEREMOVECOMPLETE)
                {
                    // USB等デバイス取り外し完了
                }
            }

            base.WndProc(ref m);
        }
    }
}

4.まとめ

今回は【RemoveDriveTools】を使わせて頂くことで
やりたいことが実現出来ました。本当に感謝です。

ただ問題としてx86向けでビルドだと問題なかったのですが
x64向けだと動作しないということがありました。

これについては少し改造させて頂き、無事に動作させることが出来ました。

このような素晴らしいコードを公開してもらえるというのは感謝しかありません。