硬體檢測:安裝、停用、usb裝置檢查 和c#實現

來源:互聯網
上載者:User
硬體檢測:安裝、停用、usb裝置檢查 和c#實現

原文地址:http://www.cnblogs.com/SunYu/archive/2010/04/29/1723977.html
codeproject:http://www.codeproject.com/KB/system/HwDetect.aspx

 簡介

現在對於IT的安全來說,熱插撥裝置是個很大的威脅。在這篇文章中,我將試著開發一個使用者應用程式來檢測本機系統上的裝置改變。例如:插入一個USB裝置、Ipod、USB無線網卡等等。這個程式同樣也可以停用任何支援插拔的裝置。在文章的後面,我會簡述一下程式的工作原理和它的局限性。

怎麼來檢測硬體裝置的改變?

事實上,Windows作業系統會對上層程式發送WM_DEVICECHANGE訊息來通知裝置的改變。我們所要作的僅僅是添加一個控制代碼來處理這個事件。

Collapse

BEGIN_MESSAGE_MAP(CHWDetectDlg, CDialog)
    // ... other handlers
    ON_MESSAGE(WM_DEVICECHANGE, OnMyDeviceChange)
END_MESSAGE_MAP()
 
LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
    // for more information, see MSDN help of WM_DEVICECHANGE
    // this part should not be very difficult to understand
    if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        switch( pHdr->dbch_devicetype ) {
            case DBT_DEVTYP_DEVICEINTERFACE:
                PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
                // do something...
                break;
 
            case DBT_DEVTYP_HANDLE:
                PDEV_BROADCAST_HANDLE pDevHnd = (PDEV_BROADCAST_HANDLE)pHdr;
                // do something...
                break;
 
            case DBT_DEVTYP_OEM:
                PDEV_BROADCAST_OEM pDevOem = (PDEV_BROADCAST_OEM)pHdr;
                // do something...
                break;
 
            case DBT_DEVTYP_PORT:
                PDEV_BROADCAST_PORT pDevPort = (PDEV_BROADCAST_PORT)pHdr;
                // do something...
                break;
 
            case DBT_DEVTYP_VOLUME:
                PDEV_BROADCAST_VOLUME pDevVolume = (PDEV_BROADCAST_VOLUME)pHdr;
                // do something...
                break;
        }
    }
    return 0;
}

然而預設情況下,Windows作業系統發送WM_DEVICECHANGE有些限制:

1 只有頂層表單的程式才能收到這個訊息

2 僅僅串口、磁碟發生改變,才對每個程式廣播這個訊息

的確不錯,至少你可以知道移動隨身碟、移動硬碟、光碟片被安裝或彈出了,通過DEV_BROADCAST_VOLUME.dbcv_unitmask你也可以獲得其對應的盤符。但實際上,你不知道底層處理的是哪個物理裝置實際上被安裝到了系統中。

API:RegisterDeviceNotification()

所以,你不得不調用RegisterDeviceNotification()API來註冊其他類型的裝置改變,或是你的程式僅僅是一個服務程式、沒有頂層表單的程式。例如:如下的例子是用來註冊一個裝置類型的介面的:

Collapse

1.  DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
2.  ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
3.  NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
4.  NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
5.  // assume we want to be notified with USBSTOR
6.  // to get notified with all interface on XP or above
7.  // ORed 3rd param with DEVICE_NOTIFY_ALL_INTERFACE_CLASSES and dbcc_classguid will be ignored
8.  NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USBSTOR;
9.  HDEVNOTIFY hDevNotify = RegisterDeviceNotification(this->GetSafeHwnd(),
        amp;NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
10. if( !hDevNotify ) {
11.     // error handling...
12.     return FALSE;
13. }

請注意第8行,NotificationFilter.dbcc_classguid關注的就是你關心的一類裝置。

參考這個blog: Doron Holan's blog

一個支援隨插即用的裝置,有2個不同的GUID相關,一個裝置介面GUID, 一個是裝置類GUID

裝置類GUID:定義了廣泛意義上一類裝置的GUID,如果你開啟裝置管理員[我的電腦右鍵—>裝置管理員],預設的是按照“類型”排列的,每一個“類型”就是一個裝置類,同時每一個裝置類有一個唯一的ID就是裝置類GUID。裝置GUID定義了此類裝置的表徵圖、預設的安全設定、安裝屬性(例如使用者不能手動安裝這類裝置,而必須通過PNP來遍曆),以及其他的設定資訊。裝置類GUID沒有定義對應的I/O介面(請參考術語表),而更像是裝置的分組。我認為一個比較好的例子是連接埠類。串口COM和並口LPT 都是連接埠類的一部分,但其各有各的I/O介面,而且彼此互不相容.一個裝置僅僅屬於一個裝置類。我們可以通過裝置驅動的INF檔案的開頭來查看該裝置的裝置類GUID。

裝置介面GUID:定義了相互關聯I/O介面的GUID,每一個介面GUID的具體執行個體都支援基本的I/O設定。裝置介面GUID也是對應的驅動程式基於PNP狀態來註冊、啟用、禁用裝置。如果需要,一個裝置甚至可以註冊多個同樣GUID的執行個體(假使每個都有相同的名字)[註:在實際的程式中,多次插拔USB口,確實會驅出相同的串口,例如port12,port12,port12…],儘管在現實世界中完全不需要這樣。一個簡單的I/O關聯結口是鍵盤裝置,每個鍵盤裝置的介面GUID必須相同。

可以通過如下的註冊表路徑來查看當前裝置類GUID 裝置介面GUID

  • //HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class
  • //HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/DeviceClasses

常用裝置的介面GUID如下:

裝置介面名稱

GUID

USB Raw Device/USB裝置

{a5dcbf10-6530-11d2-901f-00c04fb951ed}

Disk Device/磁碟裝置

{53f56307-b6bf-11d0-94f2-00a0c91efb8b}

Network Card/網卡

{ad498944-762f-11d0-8dcb-00c04fc3358c}

Human Interface Device (HID)/人機介面裝置

{4d1e55b2-f16f-11cf-88cb-001111000030}

Palm/手持功能

{784126bf-4190-11d4-b5c2-00c04f687a67}

DEV_BROADCAST_DEVICEINTERFACE的解碼

如下是修改處理捕獲對應事件的函數:

Collapse

LRESULT CHWDetectDlg::OnMyDeviceChange(WPARAM wParam, LPARAM lParam)
{
    ....
    ....
    if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam )
    {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        switch( pHdr->dbch_devicetype )
        {
            case DBT_DEVTYP_DEVICEINTERFACE:
                PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
                UpdateDevice(pDevInf, wParam);
                break;
    ....
    ....
}

從MSDN中,我們知道

Collapse

typedef struct _DEV_BROADCAST_DEVICEINTERFACE {
    DWORD dbcc_size;
    DWORD dbcc_devicetype;
    DWORD dbcc_reserved;
    GUID dbcc_classguid;
    TCHAR dbcc_name[1];
} DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;

我們似乎可以通過dbcc_name知道那個裝置安裝到了當前系統。J,答案是不對,dbcc_name僅僅是作業系統內部使用來做為ID的,其實不易讀的,例如下面的這個dbcc_name:

 

//?/USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}

  • //?/USB: USB 意思是這是一個USB裝置類
  • Vid_04e8&Pid_053b: Vid/Pid 是一個廠商ID和產品ID(但這是由裝置類指定的,USB裝置類使用VID/PID,不同的裝置類使用不同的命名規範)
  • 002F9A9828E0F06: 不清楚是怎麼產生的,是唯一裝置ID
  • {a5dcbf10-6530-11d2-901f-00c04fb951ed}:裝置介面類GUID

現在,我們來解出裝置描述資訊或是裝置別名,有2種辦法:

1 直接讀註冊表, //HKLM/SYSTEM/CurrentControlSet/Enum/USB/Vid_04e8&Pid_503b/0002F9A9828E0F06

2 使用 SetupDiXxx 系列API

API:SetupDiXxx()

Windows定義了一組API,讓使用者通過編程的辦法來擷取對應的硬體裝置資訊。例如,我們可以通過dbcc_name來獲得裝置描述資訊或是裝置別名。下面是這個辦法都具體步驟:

1 首先通過SetupDiGetClassDevs()來獲得裝置資訊集 HDEVINFO,這個操作等同於是一個擷取目錄控制代碼的過程。

2 接著使用SetupDiEnumDeviceInfo()來遍曆出這個裝置資訊集內的所有裝置,這個操作等同於把目錄列表的過程。對於每個遍曆出的,我們可以獲得SP_DEVINFO_DATA,這個等同於是檔案控制代碼。

3 在上面的枚舉過程中,使用SetupDiGetDeviceInstanceId()來讀取每個裝置的執行個體ID,這個操作等同於是讀檔案的屬性,一個裝置的執行個體ID類似這個:”USB/Vid_04e8&Pid_503b/0002F9A9828E0F06”,和dbcc_name非常像。

4 如果裝置的執行個體ID等同於dbcc_name,則通過SetupDiGetDeviceRegistryProperty()來擷取裝置描述資訊或是裝置別名資訊。

程式如下:

Collapse

void CHWDetectDlg::UpdateDevice(PDEV_BROADCAST_DEVICEINTERFACE pDevInf, WPARAM wParam)
{
    // dbcc_name:
    // //?/USB#Vid_04e8&Pid_503b#0002F9A9828E0F06#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
    // convert to
    // USB/Vid_04e8&Pid_503b/0002F9A9828E0F06
    ASSERT(lstrlen(pDevInf->dbcc_name) > 4);
    CString szDevId = pDevInf->dbcc_name+4;
    int idx = szDevId.ReverseFind(_T('#'));
    ASSERT( -1 != idx );
    szDevId.Truncate(idx);
    szDevId.Replace(_T('#'), _T('//'));
    szDevId.MakeUpper();
 
    CString szClass;
    idx = szDevId.Find(_T('//'));
    ASSERT(-1 != idx );
    szClass = szDevId.Left(idx);
 
    // if we are adding device, we only need present devices
    // otherwise, we need all devices
    DWORD dwFlag = DBT_DEVICEARRIVAL != wParam
        ? DIGCF_ALLCLASSES : (DIGCF_ALLCLASSES | DIGCF_PRESENT);
    HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, szClass, NULL, dwFlag);
    if( INVALID_HANDLE_VALUE == hDevInfo )
    {
        AfxMessageBox(CString("SetupDiGetClassDevs(): ")
            + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
        return;
    }
 
    SP_DEVINFO_DATA* pspDevInfoData =
        (SP_DEVINFO_DATA*)HeapAlloc(GetProcessHeap(), 0, sizeof(SP_DEVINFO_DATA));
    pspDevInfoData->cbSize = sizeof(SP_DEVINFO_DATA);
    for(int i=0; SetupDiEnumDeviceInfo(hDevInfo,i,pspDevInfoData); i++)
    {
        DWORD DataT ;
        DWORD nSize=0 ;
        TCHAR buf[MAX_PATH];
 
        if ( !SetupDiGetDeviceInstanceId(hDevInfo, pspDevInfoData, buf, sizeof(buf), &nSize) )
        {
            AfxMessageBox(CString("SetupDiGetDeviceInstanceId(): ")
                + _com_error(GetLastError()).ErrorMessage(), MB_ICONEXCLAMATION);
            break;
        }
 
        if ( szDevId == buf )
        {
            // device found
            if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                // do nothing
            } else if ( SetupDiGetDeviceRegistryProperty(hDevInfo, pspDevInfoData,
                SPDRP_DEVICEDESC, &DataT, (PBYTE)buf, sizeof(buf), &nSize) ) {
                // do nothing
            } else {
                lstrcpy(buf, _T("Unknown"));
            }
            // update UI
            // .....
            // .....
            break;
        }
    }
 
    if ( pspDevInfoData ) HeapFree(GetProcessHeap(), 0, pspDevInfoData);
    SetupDiDestroyDeviceInfoList(hDevInfo);
}
禁用裝置

假使你有一個正確的HDEVINFO和SP_DEVINFO_DATA(實際上,我們保持dbcc_name座位樹節點的tag,當按右鍵某一個節點的時候,可以通過調用SetupDiGetClassDevs和SetupDiEnumDeviceInfo來獲得所需東西),按照如下的步驟即可禁用一個裝置:

1 給SP_PROPCHANGE_PARAMS結構體賦上正確的值

2 把上面賦完值的SP_PROPCHANGE_PARAMS作為參數傳入到SetupDiSetClassInstallParams()

3 調用SetupDiCallClassInstaller(),傳遞參數DIF_PROPEFRTYCHANGE

實際上,DIF也是按位做與運算後相容的,你也可以去傳遞不同的DIF參數來調用SetupDiSetClassInstallParams()。 更多資訊,請參考MSDN”Handling DIF Codes”

Collapse

SP_PROPCHANGE_PARAMS spPropChangeParams ;
spPropChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
spPropChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE ;
spPropChangeParams.Scope = DICS_FLAG_GLOBAL ;
spPropChangeParams.HwProfile = 0; // current hardware profile
spPropChangeParams.StateChange = DICS_DISABLE
 
if( !SetupDiSetClassInstallParams(hDevInfo, &spDevInfoData,
    // note we pass spPropChangeParams as SP_CLASSINSTALL_HEADER
    // but set the size as sizeof(SP_PROPCHANGE_PARAMS)
    (SP_CLASSINSTALL_HEADER*)&spPropChangeParams, sizeof(SP_PROPCHANGE_PARAMS)) )
{
    // handle error
}
else if(!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, &spDevInfoData))
{
    // handle error
}
else
{
    // ok, show disable success dialog
    // note, after that, the OS will post DBT_DEVICEREMOVECOMPLETE for the disabled device
}
附錄:

我使用這個程式,已經多次測試了USB的無線網卡的,插入、拔出測試。

局限性:

1 明顯的,必須先運行該程式,才能檢測硬體裝置。例如:裝置在作業系統啟動前就已經串連,或者在這個程式運行前的串連都不會被檢測。但這個問題,可以通過儲存當前系統配置到遠端電腦上,等啟動完這個程式後再堅持不同的配置來解決

2 我們可以禁用裝置,換而言之這也是我們所有能做到。我們不能訪問裝置底層控制。 我認為可以通過重新用基於核心的過濾驅動來實現,則可以解決這個問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.