AutoHotkey Community

It is currently May 27th, 2012, 10:02 am

All times are UTC [ DST ]




Post new topic Reply to topic  [ 4 posts ] 
Author Message
PostPosted: August 17th, 2011, 4:03 am 
Hi All,

A common request on the forum has been to detect the attachment or removal of hardware devices, particularly USB devices such as thumb drives, ethernet adaptors etc. I recently had this need myself so put together a complete example that can:

- Enumerate the hardware on the system (including their types and descriptions)
- Wait for device changes
- Provide the details for the devices that have changed (description etc)

This is performed using the setupapi functions and the WM_DEVICECHANGE notification mechanism provided by Windows.

The sample app is as follows (brings up a Window with the list of hardware and a scrolling window with hardware notifications):

Code:

; Show a list of hardware in the system then follow hardware change notifications
;
; Demonstrates the use of RegisterDeviceNotification and WM_DEVICECHANGE messages
; to detect device changes as well as the setupapi functions to find information
; about the devices in question

#SingleInstance force
#NoTrayIcon

; Monitor for WM_DEVICECHANGE
OnMessage(0x219, "MsgMonitor")

hWnd := GetAHKWin()

Gui, Add, Text,, Current Harware List:
Gui, Add, Edit, HScroll w800 h500 vInfoOut -Wrap ReadOnly HwndInfoHwnd
Gui, Add, Text,, Harware Notifications:
Gui, Add, Edit, HScroll w800 h300 vLogOut -Wrap ReadOnly HwndLogHwnd
;Gui, Add, Button, Default gCapture vCaptureButton w150, &Capture
Gui, Show, , %A_ScriptName%

DEVICE_NOTIFY_WINDOW_HANDLE := 0x0
DBT_DEVTYP_DEVICEINTERFACE  := 5
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES := 0x00000004
DBT_DEVNODES_CHANGED       := 0x0007
DBT_DEVICEREMOVECOMPLETE   := 0x8004
DBT_DEVICEARRIVAL          := 0x8000
DBT_DEVTYP_DEVICEINTERFACE := 0x00000005
DIGCF_DEFAULT := 0x00000001  ; only valid with DIGCF_DEVICEINTERFACE; only the device that is associated with the system default device interface
DIGCF_PRESENT := 0x00000002   ; only devices that are currently present in a system
DIGCF_ALLCLASSES := 0x00000004   ; list of installed devices for all device setup classes or all device interface classes
DIGCF_PROFILE := 0x00000008   ; only devices that are a part of the current hardware profile
DIGCF_DEVICEINTERFACE := 0x00000010   ; devices that support device interfaces for the specified device interface classes.
DIGCF_INTERFACEDEVICE := DIGCF_DEVICEINTERFACE   ; obsolete, only for backwards compatibility
SPINT_ACTIVE  := 0x00000001
SPINT_DEFAULT := 0x00000002
SPINT_REMOVED := 0x00000004
SPDRP_FRIENDLYNAME := 0x0000000C
SPDRP_DEVICEDESC   := 0x00000000
SPDRP_CLASS        := 0x00000007
SPDRP_MFG          := 0x0000000B
SPDRP_ENUMERATOR_NAME := 0x00000016
SPDRP_SERVICE      := 0x00000004
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME := 0x0000000E
SPDRP_LOCATION_INFORMATION := 0x0000000D

VarSetCapacity(DevHdr, 32, 0) ; Actual size is 29, but the function will fail with less than 32
NumPut(32, DevHdr, 0, "UInt") ; sizeof(_DEV_BROADCAST_DEVICEINTERFACE) (should be 29)
NumPut(DBT_DEVTYP_DEVICEINTERFACE, DevHdr, 4, "UInt") ; DBT_DEVTYP_DEVICEINTERFACE
;PutGuid(&DevHdr + 8, "{1AD9E4F0-F88D-4360-BAB9-4C2D55E564CD}")
Addr := &DevHdr
Flags := DEVICE_NOTIFY_WINDOW_HANDLE|DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
Msg = %Msg%RegisterDeviceNotification(%hWnd%, %Addr%, %Flags%)`r`n
Ret := DllCall("RegisterDeviceNotification", "UInt", hWnd, "UInt", Addr, "UInt", Flags)
if (!Ret)
{
   ErrMsg := FormatMessageFromSystem(A_LastError)
   Msg = %Msg%Ret %Ret% ErrorLevel %ErrorLevel% %A_LastError% %ErrMsg%
}
Msg .= "`r`n"
SetInfo(Msg)

MakeGuid(GUID_DEVINTERFACE_KEYBOARD, "{884B96C3-56EF-11D1-BC8C-00A0C91405DD}")
MakeGuid(GUID_DEVINTERFACE_MOUSE, "{378DE44C-56EF-11D1-BC8C-00A0C91405DD}")

; List all mouse devices
;ListDevices(&GUID_DEVINTERFACE_MOUSE)

; List all devices
ListDevices()

ListDevices(GUIDAddr = 0, OutputToLog = 0, FindDevicePath = "")
{
   global SPDRP_FRIENDLYNAME, SPDRP_DEVICEDESC, SPDRP_MFG, SPDRP_CLASS, SPDRP_SERVICE, SPDRP_ENUMERATOR_NAME, DIGCF_DEVICEINTERFACE, DIGCF_ALLCLASSES, GUID_DEVINTERFACE_KEYBOARD, GUID_DEVINTERFACE_MOUSE, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME, SPDRP_LOCATION_INFORMATION
   
   hMod :=   DllCall("LoadLibrary", "str", "setupapi.dll")

   if (GUIDAddr == 0)
      hDev :=   DllCall("setupapi\SetupDiGetClassDevsA", "Uint", 0, "Uint", 0, "Uint", 0, "Uint", DIGCF_DEVICEINTERFACE|DIGCF_ALLCLASSES)
   else
      hDev :=   DllCall("setupapi\SetupDiGetClassDevsA", "Uint", GuidAddr, "Uint", 0, "Uint", 0, "Uint", DIGCF_DEVICEINTERFACE)

   Loop
   {
      VarSetCapacity(DevInfoData, 28)
      NumPut(28, DevInfoData, 0, "UInt")
     
      if (GUIDAddr == 0)
      {
         Ret := DllCall("setupapi\SetupDiEnumDeviceInfo", "UInt", hDev, "UInt", A_Index - 1, "UInt", &DevInfoData, "UInt")
         if (Ret == 0)
            break
         Name := "Unknown"
      }
      else
      {
         VarSetCapacity(DID, 28,0)
         NumPut(28,DID,0)
         Ret := DllCall("setupapi\SetupDiEnumDeviceInterfaces", "Uint", hDev, "Uint", 0, "Uint", GUIDAddr, "Uint", A_Index-1, "Uint", &DID)
         if (Ret <> 1)
         {
            if (A_LastError <> 259)
            {
               ErrMsg := FormatMessageFromSystem(A_LastError)
               sRes = %sRes%Ret %Ret% ErrorLevel %ErrorLevel% %A_LastError% %ErrMsg%
            }
            break
         }
         
         VarSetCapacity(RightSize, 4)
         
         Ret := DllCall("setupapi\SetupDiGetDeviceInterfaceDetailA", "Uint", hDev, "Uint", &DID, "UInt", 0, "UInt", 0, "UInt", &RightSize, "UInt", 0)
         if (Ret <> 1 && A_LastError <> 122)
         {
            ErrMsg := FormatMessageFromSystem(A_LastError)
            sRes = %sRes%Ret %Ret% ErrorLevel %ErrorLevel% %A_LastError% %ErrMsg%
         }

         SetSize := NumGet(RightSize,0,"UInt")

         VarSetCapacity(DevInfoData, 28)
         NumPut(28, DevInfoData, 0, "UInt")
         
         VarSetCapacity(DevIntDetData, SetSize)
         NumPut(5,DevIntDetData,0,"UInt")
         Ret := DllCall("setupapi\SetupDiGetDeviceInterfaceDetailA", "Uint", hDev, "Uint", &DID, "UInt", &DevIntDetData, "UInt", SetSize, "UInt", &RightSize, "UInt", &DevInfoData)
         if (Ret <> 1)
         {
            ErrMsg := FormatMessageFromSystem(A_LastError)
            sRes = %sRes%Ret %Ret% ErrorLevel %ErrorLevel% %A_LastError% %ErrMsg%`n
         }
         Name := GetString(&DevIntDetData+4)
      }

      if (FindDevicePath <> "" && FindDevicePath <> Name)
         continue
         
      sRes .= "Device " . A_Index . " - DevInfoData Size " . NumGet(DevInfoData, 0, "UInt") . " - Device Setup Class GUID " . GetGuid(&DevInfoData + 4) . " - " . NumGet(DevInfoData, 20, "UInt") . "`n"   
      if (Name <> "Unknown")
         sRes .= "   Name: " . Name "`n"

      VarSetCapacity(DevName, 1024)
      Ret := DllCall("setupapi\SetupDiGetDeviceInstanceIdA", "Uint", hDev, "Uint", &DevInfoData, "Str", DevName, "UInt", 1024, "UInt", 0, "UInt")
      if (Ret <> 1)
      {
         ErrMsg := FormatMessageFromSystem(A_LastError)
         sRes = %sRes%Ret %Ret% ErrorLevel %ErrorLevel% %A_LastError% %ErrMsg%
      }
      sRes .= "   Device Instance Id:    " . DevName . "`n"   
     
      sRes .= "   SPDRP_FRIENDLYNAME:    " . GetRegistryProperty(hDev, DevInfoData, SPDRP_FRIENDLYNAME) . "`r`n"
      sRes .= "   SPDRP_DEVICEDESC:      " . GetRegistryProperty(hDev, DevInfoData, SPDRP_DEVICEDESC) . "`r`n"
      sRes .= "   SPDRP_MFG:             " . GetRegistryProperty(hDev, DevInfoData, SPDRP_MFG) . "`r`n"
      sRes .= "   SPDRP_CLASS:           " . GetRegistryProperty(hDev, DevInfoData, SPDRP_CLASS) . "`r`n"
      sRes .= "   SPDRP_SERVICE:         " . GetRegistryProperty(hDev, DevInfoData, SPDRP_SERVICE) . "`r`n"
      sRes .= "   SPDRP_ENUMERATOR_NAME: " . GetRegistryProperty(hDev, DevInfoData, SPDRP_ENUMERATOR_NAME) . "`r`n"     
      sRes .= "   SPDRP_PHYSICAL_DEVICE_OBJECT_NAME: " . GetRegistryProperty(hDev, DevInfoData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME) . "`r`n"     
      sRes .= "   SPDRP_LOCATION_INFORMATION: " . GetRegistryProperty(hDev, DevInfoData, SPDRP_LOCATION_INFORMATION) . "`r`n"
   }

   DllCall("setupapi\SetupDiDestroyDeviceInfoList", "Uint", hDev)
   DllCall("FreeLibrary", "Uint", hMod)
   Msg .= sRes
   
   if (!OutputToLog)
      AppendInfo(Msg)
   else
      AppendLog(Msg)
}

SetInfo(Text)
{
   global InfoHwnd
   GuiControlGet, InfoOut
   NewText := InfoOut . Text
   GuiControl, , InfoOut, %NewText%
   return
}

AppendInfo(Text)
{
   global InfoHwnd
   GuiControlGet, InfoOut
   NewText := InfoOut . Text
   GuiControl, , InfoOut, %NewText%
   ; WM_VSCROLL (0x115), SB_BOTTOM (7)
   ;MsgBox, %InfoHwnd%
   SendMessage, 0x115, 0x0000007, 0, , ahk_id %InfoHwnd%
   return
}

SetLog(Text)
{
   global LogHwnd
   GuiControlGet, LogOut
   NewText := LogOut . Text
   GuiControl, , LogOut, %NewText%
   return
}

AppendLog(Text)
{
   global LogHwnd
   GuiControlGet, LogOut
   NewText := LogOut . Text
   GuiControl, , LogOut, %NewText%
   ; WM_VSCROLL (0x115), SB_BOTTOM (7)
   ;MsgBox, %LogHwnd%
   SendMessage, 0x115, 0x0000007, 0, , ahk_id %LogHwnd%
   return
}

GetRegistryProperty(hDev, ByRef DevInfoData, Prop)
{
   VarSetCapacity(NameType, 4, 0)
   VarSetCapacity(Name, 1024, 0)
   Ret:= DllCall("setupapi\SetupDiGetDeviceRegistryPropertyA", "UInt", hDev, "UInt", &DevInfoData, "UInt", Prop, "UInt", &NameType, "Str", Name, "UInt", 1024, "UInt", 0, "UInt")
   if (Ret <> 1)
   {
      ErrMsg := FormatMessageFromSystem(A_LastError)
      Res := "Not Found, Ret " Ret " ErrorLevel " ErrorLevel " " A_LastError " " ErrMsg
   }
   else
      Res := Name

   return Res
}

Mem2Hex( pointer, len )
{
    A_FI := A_FormatInteger
    SetFormat, Integer, Hex
    Loop, %len%  {
        Hex := *Pointer+0
        StringReplace, Hex, Hex, 0x, 0x0
        StringRight Hex, Hex, 2         
        hexDump := hexDump . hex
        Pointer ++
    }
    SetFormat, Integer, %A_FI%
    StringUpper, hexDump, hexDump
    Return hexDump
}

; GUID_DEVINTERFACE_MONITOR =              {E6F07B5F-EE97-4a90-B076-33F57BF4EAA7}
; GUID_DEVINTERFACE_VIDEO_OUTPUT_ARRIVAL = {1AD9E4F0-F88D-4360-BAB9-4C2D55E564CD}
; GUID_DISPLAY_DEVICE_ARRIVAL =            {1CA05180-A699-450A-9A0C-DE4FBE3DDD89}
; GUID_DEVINTERFACE_DISPLAY_ADAPTER =      {5B45201D-F2F2-4F3B-85BB-30FF1F953599}

MakeGuid(ByRef Out, GUID)
{
   VarSetCapacity(Out, 16)
   Out := "                "
   PutGuid(&Out, GUID)
   ;Msg := "GUID " . GUID . " Dump " . HexDump(&Out, 16)
   ;MsgBox, %Msg%
   ; The return value is not reliable because a string would be trimmed
   return Out
}

PutGuid(ToAddr, GUID)
{
    ; Strip a start bracket
    if (SubStr(GUID, 1, 1) = "{")
        GUID := SubStr(GUID, 2, StrLen(GUID) - 1)
    NumPut("0x" . SubStr(GUID, 1,  8), ToAddr+0, 0,  "UInt")   ; DWORD Data1
    NumPut("0x" . SubStr(GUID, 10, 4), ToAddr+0, 4,  "UShort") ; WORD  Data2
    NumPut("0x" . SubStr(GUID, 15, 4), ToAddr+0, 6,  "UShort") ; WORD  Data3
    NumPut("0x" . SubStr(GUID, 20, 2), ToAddr+0, 8,  "UChar")  ; BYTE  Data4[1]
    NumPut("0x" . SubStr(GUID, 22, 2), ToAddr+0, 9,  "UChar")  ; BYTE  Data4[2]
    NumPut("0x" . SubStr(GUID, 25, 2), ToAddr+0, 10, "UChar")  ; BYTE  Data4[3]
    NumPut("0x" . SubStr(GUID, 27, 2), ToAddr+0, 11, "UChar")  ; BYTE  Data4[4]
    NumPut("0x" . SubStr(GUID, 29, 2), ToAddr+0, 12, "UChar")  ; BYTE  Data4[5]
    NumPut("0x" . SubStr(GUID, 31, 2), ToAddr+0, 13, "UChar")  ; BYTE  Data4[6]
    NumPut("0x" . SubStr(GUID, 33, 2), ToAddr+0, 14, "UChar")  ; BYTE  Data4[7]
    NumPut("0x" . SubStr(GUID, 35, 2), ToAddr+0, 15, "UChar")  ; BYTE  Data4[8]
}

GetGuid(FromAddr)
{
;PutGuid(&GUID_DEVINTERFACE_KEYBOARD, "{884B96C3-56EF-11D1-BC8C-00A0C91405DD}")
    Guid := GetPaddedHex(FromAddr+0, "UInt", 8) . "-" . GetPaddedHex(FromAddr+4, "UShort", 4) . "-" . GetPaddedHex(FromAddr+6, "UShort", 4) . "-"
            . GetPaddedHex(FromAddr+8, "UChar", 2) . GetPaddedHex(FromAddr+9, "UChar", 2) . "-" . GetPaddedHex(FromAddr+10, "UChar", 2) . GetPaddedHex(FromAddr+11, "UChar", 2)
            . GetPaddedHex(FromAddr+12, "UChar", 2) . GetPaddedHex(FromAddr+13, "UChar", 2) . GetPaddedHex(FromAddr+14, "UChar", 2) . GetPaddedHex(FromAddr+15, "UChar", 2)
    return Guid
}

; Get padded hex value from an address, pad with leading 0s up to 16
GetPaddedHex(FromAddr, Type, PadLen)
{
    Old := A_FormatInteger
    SetFormat, IntegerFast, Hex
    Pad := "0000000000000000" .
    ; Get the number in hex
    HexStr := NumGet(FromAddr+0, 0, Type)
    ;MsgBox, %HexStr% %Type%
    ; Strip leading 0x
    HexStr := SubStr(HexStr, 3, StrLen(HexStr) - 2)
    ; Pad
    if (StrLen(HexStr) < PadLen)
      HexStr := SubStr(Pad, 1, PadLen - StrLen(HexStr)) . HexStr
    SetFormat, IntegerFast, %Old%
    return HexStr
}

MsgMonitor(wParam, lParam, msg)
{
   global DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE, DBT_DEVTYP_DEVICEINTERFACE
   AppendLog("WM_DEVICECHANGE Message " . msg . " arrived:`nWPARAM: " . GetHex(wParam) . "`nLPARAM: " . GetHex(lParam) . "`n")
   ;if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE)
   if ((wParam == DBT_DEVICEREMOVECOMPLETE) || (wParam == DBT_DEVICEARRIVAL))
   {
      ; lParam points to a DEV_BROADCAST_HDR structure
      dbch_size       := NumGet(lParam+0, 0, "UInt")
      dbch_devicetype := NumGet(lParam+0, 4, "UInt")
      AppendLog("   dbch_size = " . dbch_size . ", dbch_devicetype = " . GetHex(dbch_devicetype) . "`n")
      if (dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
      {
         ; lParam points to a DEV_BROADCAST_DEVICEINTERFACE structure
         dbcc_name := GetString(lParam+28)
         AppendLog("   dbcc_classguid = " . GetGuid(lParam+12) . "`n")
         AppendLog("   dbcc_name = " . dbcc_name . "`n")
         ; AppendLog(HexDump(lParam+0, dbch_size))
         ListDevices(lParam+12, 1, dbcc_name)
      }
   }
   AppendLog("`r`n")
}

HexDump( pointer, len )
{
    A_FI := A_FormatInteger
    SetFormat, Integer, Hex
    Start := 0
    Loop, %len%  {
        Count := ((Start + 16) <= Len) ? 16 : Len - Start       
        hexLine := ""
        stringLine := ""
        Loop, %Count% {
           Hex := *Pointer+0
           Char := Chr(Hex)
           StringReplace, Hex, Hex, 0x, 0x0
           StringRight Hex, Hex, 2         
           hexLine .= hex . " "
           if (Asc(Char) >= 32 && Asc(Char) < 127)
               stringLine .= Char
           else
               stringLine .= "."
           Pointer ++       
        }
        Start += Count
        hexDump .= hexLine . stringLine . "`r`n"
        if (Start >= len)
           break
    }
    SetFormat, Integer, %A_FI%
    ; StringUpper, hexDump, hexDump
    Return hexDump
}

FormatMessageFromSystem(ErrorCode)
{
   VarSetCapacity(Buffer, 2000, 32 )
   DllCall("FormatMessage"
      , "UInt", 0x1000      ; FORMAT_MESSAGE_FROM_SYSTEM
      , "UInt", 0
      , "UInt", ErrorCode
      , "UInt", 0x800 ;LANG_SYSTEM_DEFAULT (LANG_USER_DEFAULT=0x400)
      , "UInt", &Buffer
      , "UInt", 500
      , "UInt", 0)
     
   ; Strip any newlines
   Buffer := RegExReplace(Buffer, "\r\n", " ")

   Return Buffer
}

GetHex(Num)
{
    Old := A_FormatInteger
    SetFormat, IntegerFast, Hex
    Num += 0
    Num .= ""
    SetFormat, IntegerFast, %Old%
    return Num
}

GetAHKWin()
{
    Gui +LastFound 
    hwnd := WinExist()
    return hwnd
}

GetString(Addr)
{
   OutString := ""
   VarSetCapacity(OutString, 1024, 0)
   Loop {
      Char := *Addr+0
      ;MsgBox, %Char%
      if (Char == 0)
         break
      OutString .= Chr(Char)
      Addr ++
   }
   return OutString
}


I hope this is useful to others as well.

Cheers,
Shaun


Report this post
Top
  
Reply with quote  
 Post subject:
PostPosted: August 17th, 2011, 10:34 am 
Offline

Joined: April 11th, 2011, 10:12 am
Posts: 393
Location: West Yorkshire, England
Thanks for this, I'm sure it will come in use, great script :D . . .

_________________
Image

Hope this helps / Thanks <-------- (Delete Appropriate)

Chris


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: August 17th, 2011, 9:54 pm 
Offline

Joined: October 15th, 2007, 7:23 pm
Posts: 252
Thanks, this already came in handy for me. Works on Win 7 64 bit Home and XP SP3.

I adapted it to share a specific external drive when attached (using NET.exe SHARE). I was able to reduce the script size considerably for this limited purpose.

Well, done!

dmatch


Report this post
Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: October 11th, 2011, 9:09 am 
Offline

Joined: October 11th, 2010, 12:30 pm
Posts: 406
hi Shaun, it's a great script. I have a question; I have a troublesome mouse, it gets freezes now and than due to driver conflicts. How can I detect unresponding USB mouse with this script? I have to unplug it every time when it gets frozen. If I can detect when it gets unresponsive I ll write a script to reenable it with devcon


Report this post
Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC [ DST ]


Who is online

Users browsing this forum: sks and 21 guests


You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group