Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8899

デバイスのUSB接続状況を監視するアプリの作成

$
0
0

はじめに

Windowsを使っていると、時々USBデバイスを外した時の音が聞こえるようになりました。(ポロン↓、ポンポロロン↑)
接続の音も聞こえているため、おそらくは接続が一瞬切れているのだと思います。
すぐ接続されるため作業に大きな支障はないのですが、音が何度も聞こえるのは不快で仕方がないです。
ですが、普段からいくつものデバイス(WiFi、マウス、キーボード、ゲームパッドなど)を付けており、いつ発生するかも分からない現象を一つ一つチェックするのは困難でした。
イベントビューアから探すも、他サイトに書かれたイベント情報には載っておらず・・・

もういっそ自分で作ってみようと思い、デバイス情報の監視プログラム(C#)を作ってみました。
(C#である理由は普段使う言語だからであり、機能上の理由はないです)

最初に結論から

作成したプログラムを実行した結果がこちらです。
デバイス監視.jpg

・最初の1,2行 キーボードを手動で付け外しした時
・3,4,5,6行  散々悩まされた接続音が鳴った時
・7,8行    手動でWiFiを外した時
の表示です。
Del:デバイスを外した時の表示
Add:デバイスを付けた時の表示
これにより、WiFi周りの端子が原因だと判明しました。

試しにWiFiを他のUSB端子に接続すると音がならなくなり、ゲームパッドを問題のUSBにつなげると
今度はゲームパッドのIDで音が鳴り始めたので、USB端子側が壊れていることが確定しました。
(今のところ1端子が使えなくても何とかなるので、直すのは放置)

ちなみに今回作成したプログラムは、デバイスの接続状況が変わった時だけ動くように作っているので、起動したまま放置してても特に処理は食わないと思います。

あと、私の環境では問題なかったのですが、もしかしたら管理者権限での実行が必要かもしれません。
(どこかのサイトでWindowsメッセージの処理をするには管理者権限が必要、というのを見た気がするので)

プログラムの説明(構成とデザイン)

今回は「Windowsフォームアプリケーション」で作成しました。
windowsform.PNG

構成は初期フォーム(Form1)上に全ての処理を実装する、シンプルなものです。
フォームデザインも、処理結果を表示するtextBoxを一つ配置するだけです。
(textBoxの設定:Multiline:True、ScrollBar:Vertical)
formDesign.PNG

プログラムの説明(メイン処理)

※プログラムは最後に全文を載せてますので、直接見た方が早いという方はそちらをどうぞ

1.USBデバイス情報を取得する関数・クラスを作成

参考サイト コードログ c# – 接続されたUSBデバイスのリストを取得する
(今回使用するのはIDだけなので、参考サイトで取得しているPNPDeviceIDなどは削除。
 種類など詳しく表示したい場合は、コードログさんのサイトの参考記事などをたどればありそう)

USBDeviceInfo(デバイス情報の格納クラス)
classUSBDeviceInfo{publicUSBDeviceInfo(stringdeviceID){this.DeviceID=deviceID;}publicstringDeviceID{get;privateset;}}
GetUSBDevices(デバイス情報取得関数)
staticList<USBDeviceInfo>GetUSBDevices(){List<USBDeviceInfo>devices=newList<USBDeviceInfo>();ManagementObjectCollectioncollection;// WMIライブラリのWin32_USBHubクラスを使用して管理情報を取得using(varsearcher=newManagementObjectSearcher(@"Select * From Win32_USBHub"))collection=searcher.Get();// 取得したUSBHub情報から、ID情報を取り出し、デバイスリストに追加foreach(vardeviceincollection){devices.Add(newUSBDeviceInfo((string)device.GetPropertyValue("DeviceID")));}collection.Dispose();returndevices;}

2.デバイスの一覧から、変化したデバイスを画面出力する処理を作成

前回のIDと今回のIDを比較し、差があれば表示しています。

CheckDevice(前回と今回のデバイス一覧の差分を検出し、画面に出力する関数)
List<USBDeviceInfo>usbDevicesBefore=newList<USBDeviceInfo>();intnumBeforeDevices=0;/// <summary>/// 接続デバイスを取得。前回の取得内容と異なる場合、リストを更新してMessgaeBoxに表示。/// Add:新しく検出されたデバイス Del:検出されなくなったデバイス/// </summary>privatevoidCheckDevice(){varusbDevices=GetUSBDevices();// デバイスを取得stringnowTime=DateTime.Now.ToString("HH:mm:ss.ff ");// 取得時刻設定if(usbDevices.Count>numBeforeDevices){//デバイス数が増加(接続)foreach(varusbDeviceinusbDevices){// 取得したデバイスの中で、前回の一覧に無いものを検出し、画面出力boolbExistDevice=false;foreach(varusbDeviceBeforeinusbDevicesBefore){if(usbDevice.DeviceID==usbDeviceBefore.DeviceID){bExistDevice=true;break;}}if(!bExistDevice){// デバイスIDを出力stringsTemp=string.Format("Add Device ID: {0}",usbDevice.DeviceID);AddMessage(nowTime+sTemp+Environment.NewLine);}}}elseif(usbDevices.Count<numBeforeDevices){//デバイス数が減少(取り外し)foreach(varusbDeviceBeforeinusbDevicesBefore){// 前回のデバイスの中で、今回取得した一覧に無いものを検出し、画面出力boolbExistDevice=false;foreach(varusbDeviceinusbDevices){if(usbDevice.DeviceID==usbDeviceBefore.DeviceID){bExistDevice=true;break;}}if(!bExistDevice){stringsTemp=string.Format("Del Device ID: {0}",usbDeviceBefore.DeviceID);AddMessage(nowTime+sTemp+Environment.NewLine);}}}AddMessage(Environment.NewLine);//可読性のため空行追加usbDevicesBefore=usbDevices;//デバイス一覧を更新numBeforeDevices=usbDevices.Count;}

3.USB接続のイベントを検出する関数を作成

参考サイト ohyajapanの日記 C#でリムーバブルメディアの着脱を検知する方法 その1
Windowsメッセージを検出する関数をオーバーライドし、検出したメッセージがデバイス変化の値の時、2で作成したデバイスチェック関数をTask(非同期処理)を使って呼び出します。
Taskを利用するのは、デバイスチェックに処理時間がかかり、メッセージの検出が漏れるのを防止するためです。
※なお、今回は実装を簡易にするためにこのような作りとなっていますが、下記サイトにあるように、Taskの戻り値も使わない投げっぱなし処理は推奨されてないようです。
参考サイト Taskを極めろ!async/await完全攻略

WndProc(Windowsメッセージ取得関数、オーバーライドで実装)
publicconstintWM_DEVICECHANGE=0x00000219;//デバイス変化のWindowsイベントの値protectedoverridevoidWndProc(refMessagem){base.WndProc(refm);switch(m.Msg){caseWM_DEVICECHANGE://デバイス状況の変化イベントTask.Run(()=>CheckDevice());//デバイスをチェックbreak;}}

あと、起動時にデバイスリストを取得するため、form1関数にも追加します。

Form1
publicForm1(){InitializeComponent();Task.Run(()=>CheckDevice());//起動時に現在接続済みのデバイスを検出。追加。}

4.テキストボックスへの出力処理

CheckDevice関数で使っているAddMessage関数ですが、これはWindowsフォームのTextBoxへの出力処理です。
CheckDevice関数はTask(非同期処理)で呼び出しているため、メインスレッドで動いているTextBoxへの直接操作ができません。(textBox_Msg.AppendText()関数など)
そのため、invoke処理を使ってtextBoxの処理ができるようにしています。
参考サイト C#のWindowsフォームアプリケーションでメインスレッドのGUIを更新する方法

AddMessage
publicvoidAddMessage(stringstr){if(this.InvokeRequired)this.Invoke(newAction<string>(AddMessage),str);elsethis.textBox_Msg.AppendText(str);}

以上、全関数の説明でした。

おわりに

今回のプログラムに必要な下記の処理はC#で実装したのは初めてだったので、良い勉強になりました。
・ThreadではなくTaskを使用
・Windowsイベントの検出
・Windowsの管理情報の取得

なにか、同じように困っている方の問題解決に繋がればと思います。
また、至らない点や気になる点などありましたら、コメントいただければ幸いです。

課題点としては、現状だとデバイスIDしか表示されないので、IDが分かった後で、他の端子を抜き差しして同じIDを探さないといけないので、少々手間です。もう少し、接続しているデバイスの種類などを表示できると、分かりやすいツールになりそうですね。
その辺はWin32_USBHubあたりを調べると色々出てきそうですが、なかなか骨が折れそうです(笑)

また、今回はUSB端子が原因とみて実装しましたが、内部HDDなどはこれでは検出できないかもしれません。そのときはWindowsメッセージを処理する関数をいじって、音がなった時間にどんなメッセージが検出されるかを見ていけば、糸口がつかめそうです。

以上、ここまで読んでくださりありがとうございました。
(下にform1.csの全文を載せてます)

form1プログラム全文

メインコード(展開)
Form1.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Windows.Forms;usingSystem.Management;usingSystem.Threading.Tasks;namespaceWindowsDeviceEventMonitor{publicpartialclassForm1:Form{publicconstintWM_DEVICECHANGE=0x00000219;//デバイス変化のWindowsイベントの値List<USBDeviceInfo>usbDevicesBefore=newList<USBDeviceInfo>();intnumBeforeDevices=0;publicForm1(){InitializeComponent();Task.Run(()=>CheckDevice());//起動時に現在接続済みのデバイスを検出。追加。}/// <summary>/// 接続デバイスを取得。前回の取得内容と異なる場合、リストを更新してMessgaeBoxに表示。/// Add:新しく検出されたデバイス Del:検出されなくなったデバイス/// </summary>privatevoidCheckDevice(){varusbDevices=GetUSBDevices();// デバイスを取得stringnowTime=DateTime.Now.ToString("HH:mm:ss.ff ");// 取得時刻設定if(usbDevices.Count>numBeforeDevices)//デバイス数が増加(接続){foreach(varusbDeviceinusbDevices){// 取得したデバイスの中で、前回の一覧に無いものを検出し、画面出力boolbExistDevice=false;foreach(varusbDeviceBeforeinusbDevicesBefore){if(usbDevice.DeviceID==usbDeviceBefore.DeviceID){bExistDevice=true;break;}}if(!bExistDevice){// デバイスIDを出力stringsTemp=string.Format("Add Device ID: {0}",usbDevice.DeviceID);AddMessage(nowTime+sTemp+Environment.NewLine);}}}elseif(usbDevices.Count<numBeforeDevices)//デバイス数が減少(取り外し){foreach(varusbDeviceBeforeinusbDevicesBefore){// 前回のデバイスの中で、今回取得した一覧に無いものを検出し、画面出力boolbExistDevice=false;foreach(varusbDeviceinusbDevices){if(usbDevice.DeviceID==usbDeviceBefore.DeviceID){bExistDevice=true;break;}}if(!bExistDevice){stringsTemp=string.Format("Del Device ID: {0}",usbDeviceBefore.DeviceID);AddMessage(nowTime+sTemp+Environment.NewLine);}}}AddMessage(Environment.NewLine);//可読性のため空行追加usbDevicesBefore=usbDevices;//デバイス一覧を更新numBeforeDevices=usbDevices.Count;}/// <summary>/// 接続されているデバイスを取得/// </summary>/// <returns>デバイス情報のリスト</returns>staticList<USBDeviceInfo>GetUSBDevices(){List<USBDeviceInfo>devices=newList<USBDeviceInfo>();ManagementObjectCollectioncollection;// WMIライブラリのWin32_USBHubクラスを使用して管理情報を取得using(varsearcher=newManagementObjectSearcher(@"Select * From Win32_USBHub"))collection=searcher.Get();// 取得したUSBHub情報から、ID情報を取り出し、デバイスリストに追加foreach(vardeviceincollection){devices.Add(newUSBDeviceInfo((string)device.GetPropertyValue("DeviceID")));}collection.Dispose();returndevices;}/// <summary>/// Windows メッセージを処理する関数(オーバーライド)/// </summary>/// <param name="m"></param>protectedoverridevoidWndProc(refMessagem){base.WndProc(refm);switch(m.Msg){caseWM_DEVICECHANGE://デバイス状況の変化イベント// AddMessage(m.ToString() + Environment.NewLine);Task.Run(()=>CheckDevice());//デバイスをチェックbreak;}}/// <summary>/// Task、Thread側からフォームのTextBoxにテキストを追加する処理/// </summary>/// <param name="str">textBoxへ追加する文字列</param>publicvoidAddMessage(stringstr){if(this.InvokeRequired)this.Invoke(newAction<string>(AddMessage),str);elsethis.textBox_Msg.AppendText(str);}}/// <summary>/// デバイス情報を格納するクラス/// </summary>classUSBDeviceInfo{publicUSBDeviceInfo(stringdeviceID){this.DeviceID=deviceID;}publicstringDeviceID{get;privateset;}}}


Viewing all articles
Browse latest Browse all 8899

Trending Articles