Jump to content

[Solved] Check for a specific Hardware ID in WMI Object


EmilyLove
 Share

Recommended Posts

First, I have to say im terrible at com objects. I got this code somewhere (I forgot where) and somehow managed to modify it to fit my needs. The script currently reads from the list of hardware id that is currently connected to the system and checks for a certain id in that list. This is pretty resource intensive on my system. Adding sleep to it makes it more manageable, however it takes a lot longer to detect newly connected hardware. How do I get it to only check for a specific id? The ID I'm looking for is "USB\VID_045E&PID_02E4&IGA_00"

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Compression=4
#AutoIt3Wrapper_UseUpx=y
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
;Written By: Amin Babaeipanah
;Modified By: BetaLeaf
opt("TrayIconHide",1)
$winTitle = "Xbox One Headset Auto Connection"
$WorkingDirectory = @TempDir & "\BetaLeaf Software\" & $winTitle
DirCreate(@TempDir & "\BetaLeaf Software\" & $winTitle)
FileInstall("nircmdc.exe", @TempDir & "\BetaLeaf Software\" & $winTitle & "\nircmdc.exe", 1)
Setup()
While 1
    If GetDevices("") = 1 Then
        For $i = 0 To 2
            ShellExecute($WorkingDirectory & "\NIRCMDC.exe", 'setdefaultsounddevice "Headphones" ' & $i & '"', @ScriptDir, "", @SW_HIDE) ;Set Default Playback Device to Slot $Slot.
        Next
        For $i = 0 To 2
            ShellExecute($WorkingDirectory & "\NIRCMDC.exe", 'setdefaultsounddevice "Headset Microphone" ' & $i & '"', @ScriptDir, "", @SW_HIDE) ;Set Default Recording Device to Slot $Slot.
        Next
        Do
            Sleep(5000)
        Until GetDevices("") = 2
    EndIf
    Sleep(5000)
WEnd
Func GetDevices($name)
    Local $objWMIService = ObjGet('winmgmts:\\localhost\root\CIMV2')
    Local $colItems = $objWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%" & $name & "%'", "WQL", 48)
    If IsObj($colItems) Then
        For $objItem In $colItems
            Select
                Case StringInStr($objItem.PNPDeviceID, "USB\VID_045E&PID_02E4&IGA_00") <> 0
                    Return 1
            EndSelect
            sleep(50)
        Next
    EndIf
    Return 2
EndFunc   ;==>GetDevices
Func Setup()
    Local $StartwithWindows = RegRead("HKCU\Software\BetaLeaf Software\Xbox One Headset Auto Connection", "StartwithWindows")
    If @error Then
        Local $ret = MsgBox(64 + 4, $winTitle, "Would you like this program to Start with Windows?")
        If $ret = 6 Then
            If RegWrite("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $winTitle, "REG_SZ", '"' & @ScriptFullPath & '"') = 0 Then MsgBox(0, $winTitle, "Could not write to registry. Error: " & @error)
            If RegWrite("HKCU\Software\BetaLeaf Software\Xbox One Headset Auto Connection", "StartwithWindows", "REG_DWORD", "1") = 0 Then MsgBox(16, $winTitle, "Could not write to registry. Error: " & @error)
        ElseIf $ret = 7 Then
            If RegDelete("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $winTitle) = 2 Then MsgBox(0, $winTitle, "Could not delete registry key. Error: " & @error)
            If RegWrite("HKCU\Software\BetaLeaf Software\Xbox One Headset Auto Connection", "StartwithWindows", "REG_DWORD", "0") = 0 Then MsgBox(16, $winTitle, "Could not write to registry. Error: " & @error)
        EndIf
    Else
        Local $Run = RegRead("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $winTitle)
        Select
            Case @error = -1
                If RegDelete("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $winTitle) = 2 Then MsgBox(0, $winTitle, "Could not delete registry key. Error: " & @error)
                If RegWrite("HKCU\Software\BetaLeaf Software\Xbox One Headset Auto Connection", "StartwithWindows", "REG_DWORD", "0") = 0 Then MsgBox(16, $winTitle, "Could not write to registry. Error: " & @error)
            Case $Run <> ""
                If RegWrite("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", $winTitle, "REG_SZ", '"' & @ScriptFullPath & '"') = 0 Then MsgBox(0, $winTitle, "Could not write to registry. Error: " & @error)
                If RegWrite("HKCU\Software\BetaLeaf Software\Xbox One Headset Auto Connection", "StartwithWindows", "REG_DWORD", "1") = 0 Then MsgBox(16, $winTitle, "Could not write to registry. Error: " & @error)
            Case Else
                MsgBox(16, $winTitle, "Could not read registry. Error: " & @error)
        EndSelect
    EndIf
EndFunc   ;==>Setup

Edit: Solution at 

 

Edited by BetaLeaf

 

 

Link to comment
Share on other sites

43 minutes ago, Tripredacus said:

Have you cut out the WMI query and run it on its own to verify that it is what is causing the slowdown?

The only question I have, is you are specifying additionally "WQL" and 48 in your query, but I don't know what that does.

I have no idea. I don't even know how I got it to work at all. I do not understand com objects yet. I'm pretty sure it's the function getdevices that causes the slowdown. Adding sleep(10) in the for next loop reduces the slowdown but increases search time exponentially. I have a lot of devices connected to this computer.

 

 

Link to comment
Share on other sites

"WQL" = SQL for WMI

48 = wbemFlagForwardOnly (32 (0x20)) + wbemFlagReturnImmediately (16 (0x10))

ref - https://msdn.microsoft.com/en-us/library/aa393866(v=vs.85).aspx

As our martian friend suggests, break out the code you suspect or put some timing diagnostics in the existing code.

kylomas

Forum Rules         Procedure for posting code

"I like pigs.  Dogs look up to us.  Cats look down on us.  Pigs treat us as equals."

- Sir Winston Churchill

Link to comment
Share on other sites

I still can't get it to work. What am I doing wrong?

Local $colItems = $objWMIService.ExecQuery("select * from Win32_PnPEntity Where PNPDeviceID = 'USB\\VID_045E&PID_02E4&IGA_00'", "WQL", 48)
Local $colItems = $objWMIService.ExecQuery('select * from Win32_PnPEntity Where PNPDeviceID = "USB\\VID_045E&PID_02E4&IGA_00"', "WQL", 48)

I've also tried checking for @errors and using Consolewrite to check values. I'm not getting anything that will lead me to a solution. Values are blank with the above code. On the existing code I have, it returns every device id connected to this computer when I place the check in the For next loop.

 

 

Link to comment
Share on other sites

I brought up the WQL and 48 because I've never used those in a WMI query.

For "breakout" purposes, I personally keep a dedicated au3 for just doing WMI testing. Here is one example...

#include <file.au3>
#include <array.au3>
#Include <String.au3>

Global $bin
Global $sWMIService, $objWMIService, $colDisks, $sNumber
$sWMIService = "winmgmts:\\" & @ComputerName & "\root\CIMV2"
$objWMIService = ObjGet($sWMIService)
IF IsObj($objWMIService) Then
$colDisks = $objWMIService.ExecQuery("SELECT * FROM Win32_BaseBoard")
    If IsObj($colDisks) Then
        For $oItem In $colDisks
            $sNumber = $oItem.Manufacturer
            MsgBox (4096, "Manufacturer", "looks at this Manufacturer: " & $sNumber & ".")
        Next
    Else
        MsgBox (4096, "Error", "colDisks data is empty")
    EndIf
EndIf

Now my labels and declarations may not match up, as I use it as a scratch pad ALOT, but it should work or you get the idea.

I have only 1 similar au3 I can find at the moment that uses LIKE, but I am not looking specifically for a hardware ID in the query because it can be troublesome. Instead I can look for the description that Windows has on the item. In this example, I am looking for devices that have Fingerprint in their name. Because I know that the 2 things I am looking for will always be using this term.

$colItems = $objWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%Fingerprint%'")

I use the WMI to find me all instances of Fingerprint, and store that info. I am using LIKE because I am looking for 2 different results that have that word in it. If you are looking for an exact match, you do not need to use LIKE.

When designing what type of WMI query you should use, it is extremely helpful to be able to see the tables. For Windows 7 (or IE8/9 and older) MS had made a browser plugin called WMI Explorer. It does not work well now, and it is possible there are other WMI viewers for current versions of Windows. Otherwise, if you have a testing au3 to run tests either as singular messagebox on a match scenario, or output an entire WMI table for viewing, this can be done also.

We can compare it directly to doing *sql queries. In those situations, you know the structure of the database and what the data is supposed to be, then you build your query to get the appropriate data to be returned to do whatever. WMI is no different. Aside from the simple things such as seeing what 1 field happens to have in it, you should really see what everything is in order to build your query correctly and/or use the data.

An idea: if you feel there is a problem with the slashes being in the data, you can trim the USB\\ part off your comparison so you end up not using it at all.

EDIT: here is a test au3 I make to help me see what the HardwareIDs are. It will find how many instance have mouse, then show me the hardwareID. It finds 2 instances, but the field is empty.

#include <file.au3>
#include <array.au3>
#Include <String.au3>

Global $bin
Global $sWMIService, $objWMIService, $colDisks, $DiskDrive, $DriveLetter, $DriveType, $VolumeName, $sNumber, $sName
$sWMIService = "winmgmts:\\" & @ComputerName & "\root\CIMV2"
$objWMIService = ObjGet($sWMIService)
IF IsObj($objWMIService) Then
$colDisks = $objWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE Description LIKE '%mouse%'")
    If IsObj($colDisks) Then
        For $oItem In $colDisks
            $sNumber = $oItem.HardwareID
            MsgBox (4096, "Mouse HwID", "look at this HardwareID: " & $sNumber & ".")
        Next
    EndIf
EndIf

Here is a link to the class, I didn't see it posted here yet.

https://msdn.microsoft.com/en-us/library/aa394353(v=vs.85).aspx

Edited by Tripredacus
Link to comment
Share on other sites

I think your problem comes from the operator "= ". Replace it by "like" and add "%" at this end of the string. Look  :

Local $oDevice, $PNPDeviceID, $sHardwareIDs, $oWMIService = ObjGet("winmgmts:\\.\root\CIMV2")
If Not IsObj($oWMIService) Then Exit MsgBox(16, "Error", "WMI error")
Local $oPNPDevices = $oWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE PNPDeviceID  LIKE 'USB\\VID%'", "WQL", 48)
If IsObj($oPNPDevices) Then
    For $oDevice In $oPNPDevices
        $sHardwareIDs = ""
        $PNPDeviceID = $oDevice.PNPDeviceID
        $aHardwareID = $oDevice.HardwareID
        For $i = 0 To UBound($aHardwareID) - 1
            $sHardwareIDs &= $aHardwareID[$i] & @CRLF
        Next
        MsgBox (4096, "PNPDeviceID found", "PNPDeviceID : " & $PNPDeviceID & @CRLF & "HardwareID : " & $sHardwareIDs)
    Next
EndIf

As you can see, HardwareID is no a string but an array of strings. See here : https://msdn.microsoft.com/en-us/library/aa394353(v=vs.85).aspx

Also,  you can check out your query result with a graphic tool, it would be easier. Try with WMIExlorer

Edited by jguinch
Link to comment
Share on other sites

FWIW - I use this to enumerate cimv2 class  properties

#include <array.au3>

local $ret = _WMI('win32_pnpentity')
;local $ret = _WMI('Win32_bios')

If @error Then Exit (ConsoleWrite(@LF & '!> @ERROR    = ' & @error & @LF & '!> @EXTENDED = ' & @extended & @LF & '!> $RET      = ' & $ret & @LF & @LF))
If IsArray($ret) Then _ArrayDisplay($ret)
If IsString($ret) Then ConsoleWrite($ret & @CRLF)


; #FUNCTION# ====================================================================================================================
; Name...........: _WMI
; Description ...: Returns formatted output from a query against any class of the CIMV2 namespace using WMI.
; Syntax.........: _WMI($sWQL, $bReturnType, $sDelimiter, $sTargetPC)
; Parameters ....: $sWQL            - Any class in the WMI CIMV2 namespace (e.g. Win32_Process, Win32_BIOS, Win32_Desktop, etc.)
;                                     or any WQL 'Select' query.  If only the class is specified then all columns
;                                     for all items are returned.
;                  $bReturnType     - Indicates return format.  $bReturnType can be a string or integer.
;                                   |0 - Return a 2D array with item names in col 0 (on the left).
;                                   |1 - Return a 2D array with item names in row 0 (on top).      (DEFAULT)
;                                   |2 - Return a @CRLF delimited string of item names (row #1) followed by items.  Each value is
;                                        delimited by $sDelimiter.
;                  $sDelimiter      - The delimiter used to seperate values/property names within an item.  The default is
;                                     '`' because other common delimiter characters (e.g. '|', ',', '=', ':', '~' etc. appear
;                                     in some values returned by various WQL queries.  This can be a string of characters (e.g. '//').
;                  $sTargetPC       - Name of the PC to run the query on.
; Return values .: Success          - A 2D array or string depending on $bReturnType.
;                  Failure          - Sets @ERROR and returns a value as follows:
;                                   |1 - $sWQL is blank                     Return value - None
;                                   |2 - $bReturnType invalid               Return value - The parm supplied
;                                   |3 - $sDelimiter not specified          Return value - None
;                                   |4 - WMI ObjGet failure                 Return value - None
;                                   |5 - ExecQuery failure                  Return value - $sWQL
;                                   |6 - No items returned                  Return value - $sWQL
;
; Author ........: kylomas
; Modified.......: 02/02/2015 V01R01M01 initial release
; Remarks .......:
; Related .......: WMI is required
; Link ..........:
; Example .......: Yes
; ===============================================================================================================================

Func _WMI($sWQL, $bReturnType = 1, $sDelimiter = '`', $sTargetPC = @ComputerName)

    ; flush parms
    Switch $bReturnType
        Case 0, 1, 2, '0', '1', '2'
        Case -1, Default
            $bReturnType = 1
        Case Else
            Return SetError(2, 0, $bReturnType)
    EndSwitch

    Switch True
        Case $sDelimiter = -1 Or $sDelimiter = Default
            $sDelimiter = '`'
        Case StringLen($sDelimiter) > 0
        Case Else
            Return SetError(3, 0, $sDelimiter)
    EndSwitch

    If StringLen($sWQL) = 0 Then Return SetError(1)
    If StringInStr($sWQL, 'select') = 0 Then $sWQL = 'select * from ' & $sWQL

    Local $oWMI = '', $oItem = '', $oItems = '', $P = ''

    ; Get WMI instance
    ; Do NOT use $wbemFlagReturnImmediately or $wbeForwardOnly as this disables the "Count" property
    $oWMI = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\" & $sTargetPC & "\root\CIMV2")
    If Not IsObj($oWMI) Then Return SetError(4)

    ; Run query
    $oItems = $oWMI.ExecQuery($sWQL)
    If Not IsObj($oItems) Then Return SetError(5, 0, $sWQL)
    If $oItems.count = 0 Then Return SetError(6, 0, $sWQL)

    Local $str = ''

    ; format header / use this method as opposed to the "itemindex" method for XP compatibility
    For $oItem In $oItems
        For $P In $oItem.Properties_()
            $str &= '<' & $P.Name & '>' & $sDelimiter
        Next
        $str = StringTrimRight($str, StringLen($sDelimiter)) & @CRLF
        ExitLoop
    Next

    ; format each detail item
    For $oItem In $oItems
        For $P In $oItem.Properties_()
            Switch True
                Case $P.isarray
                    $aTmp1 = $P.Value
                    $str &= _ArrayToString($aTmp1) & $sDelimiter
                Case $P.CIMTYPE = 101
                    $str &= CNVTDate($P.Value) & $sDelimiter
                Case Else
                    $str &= $P.Value & $sDelimiter
            EndSwitch
        Next
        $str = StringTrimRight($str, StringLen($sDelimiter)) & @CRLF
    Next
    $str = StringTrimRight($str, 2)

    Switch $bReturnType
        Case '1', 1, -1, Default
            ; return 2D array / column names are at top (row 0)
            Local $aTmp1 = StringSplit($str, @CRLF, 3), $aTmp2
            Local $aRet[UBound($aTmp1)][UBound(StringSplit($aTmp1[0], $sDelimiter, 3))]
            For $1 = 0 To UBound($aTmp1) - 1
                $aTmp2 = StringSplit($aTmp1[$1], $sDelimiter, 3)
                For $2 = 0 To UBound($aTmp2) - 1
                    $aRet[$1][$2] = $aTmp2[$2]
                Next
            Next
            Return $aRet
        Case '0', 0
            ; return 2D array / column names are to the left (col 0)
            Local $aTmp1 = StringSplit($str, @CRLF, 3), $aTmp2
            Local $aRet[UBound(StringSplit($aTmp1[0], $sDelimiter, 3))][UBound($aTmp1)]
            For $1 = 0 To UBound($aTmp1) - 1
                $aTmp2 = StringSplit($aTmp1[$1], $sDelimiter, 3)
                For $2 = 0 To UBound($aTmp2) - 1
                    $aRet[$2][$1] = $aTmp2[$2]
                Next
            Next
            Return $aRet
        Case '2', 2
            ; return string
            Return $str
        Case Else
            Return SetError(7, 0, $bReturnType)
    EndSwitch

EndFunc   ;==>_WMI

Func CNVTDate($dtmDate)
    ; reformat date to mm/dd/yyyy hh:mm:ss and zero fill single digit values
    Return StringRegExpReplace(StringRegExpReplace($dtmDate, '(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).*', '$2/$3/$1 $4:$5:$6'), '(?<!\d)(\d/)', '0$1')
EndFunc   ;==>CNVTDate

Array values are separated by a '|'

kylomas

Forum Rules         Procedure for posting code

"I like pigs.  Dogs look up to us.  Cats look down on us.  Pigs treat us as equals."

- Sir Winston Churchill

Link to comment
Share on other sites

32 minutes ago, jguinch said:

This works :

Local $oDevice, $PNPDeviceID, $oWMIService = ObjGet("winmgmts:\\" & @ComputerName & "\root\CIMV2")
If Not IsObj($oWMIService) Then Exit MsgBox(16, "Error", "WMI error")
Local $oPNPDevices = $oWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE PNPDeviceID  LIKE 'USB\\VID%'", "WQL", 48)
If IsObj($oPNPDevices) Then
    For $oDevice In $oPNPDevices
        $PNPDeviceID = $oDevice.PNPDeviceID
        MsgBox (4096, "PNPDeviceID found", "PNPDeviceID : " & $PNPDeviceID)
    Next
EndIf

Did you try with % ?

Where PNPDeviceID like "USB\\VID_045E&PID_02E4&IGA_00%"

Also,  you can check out your query result with a graphic tool, it would be easier. Try with WMIExlorer

The WMIExplorer tool helped me figure out the issue. The problem was I was searching for

SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%%'

a Win32_PnPEntity where it's name could be anything. Thus, when I tried to specify a name,

SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%USB\\VID_045E&PID_02E4&IGA_00%' 

it returned nothing because I wasn't inputting a name, I was inputting a DeviceID. Changing it to 

SELECT * FROM Win32_PnPEntity WHERE DeviceID Like '%USB\\VID_045E&PID_02E4&IGA_00%'

gave me the device I was looking for.

 

This is going in my Xbox-One-Headset-AutoConnection script on Github. Would you like to be mentioned as a Contributor, @jguinch?

Edited by BetaLeaf

 

 

Link to comment
Share on other sites

@BetaLeaf : as you wish,  it does not matter to me. It's just a pleasure to help ;)

edit : @kylomas : I like your WMI function, nice and usefull !

Edited by jguinch
Link to comment
Share on other sites

Hi @BetaLeaf :)

I know it's solved, but i have a suggestion that should be better for your cpu and for the execution time of the code.

First an example, showing  time difference between what i understand is currently the solution and my proposal.

#include <APISysConstants.au3>
#include <WinAPISys.au3>

$oWMIService = ObjGet("winmgmts:\\" & @ComputerName & "\root\CIMV2")
$vTime = TimerInit()
$oPNPDevices = $oWMIService.ExecQuery("SELECT * FROM Win32_PnPEntity WHERE DeviceID  LIKE '%USB\\VID_045E&PID_02E4%'", "WQL", 48)
ConsoleWrite(TimerDiff($vTime)&@CRLF)
$vTime = TimerInit()
$aData = _WinAPI_EnumRawInputDevices()
$tText = DllStructCreate('wchar[256]')
$sPattern = "[Vv][Ii][Dd]_045E&[Pp][Ii][Dd]_02E4"
For $i=0 To UBound($aData, 1)-1
    _WinAPI_GetRawInputDeviceInfo($aData[$i][0], $tText, 256, $RIDI_DEVICENAME)
    If StringRegExp(DllStructGetData($tText, 1), $sPattern) Then ConsoleWrite(DllStructGetData($tText, 1)&@CRLF)
Next
ConsoleWrite(TimerDiff($vTime)&@CRLF)

 

Here's the example with WM_DEVICECHANGE to save cpu usage:

#include <APISysConstants.au3>
#include <WinAPISys.au3>

Global Const $sVID = "045E"
Global Const $sPID = "02E4"

$bSwitch = False; used to check if action needs to be taken, on WM_DEVICECHANGE

$DBT_DEVICEARRIVAL = "0x00008000"
$DBT_DEVNODES_CHANGED = 0x0007

$WM_DEVICECHANGE = 0x0219

Opt("GuiOnEventMode", 1)
$hWnd = GUICreate("", 700, 320)
GUISetOnEvent(-3, "_MyExit", $hWnd)
GUISetState()

Alternative();call to check for device, before event listening

GUIRegisterMsg($WM_DEVICECHANGE , "WM_DEVICECHANGE")

While 1
    Sleep(10)
WEnd

Func WM_DEVICECHANGE($hWnd, $Msg, $wParam, $lParam)
    If $wParam = $DBT_DEVNODES_CHANGED Then Alternative()
EndFunc

Func Alternative()
    Local $aDevices =  _WinAPI_EnumRawInputDevices()
    Local $tText = DllStructCreate('wchar[256]')
    Local $sPattern = "[Vv][Ii][Dd]_"&$sVID&"&[Pp][Ii][Dd]_"&$sPID
    For $i=0 To UBound($aDevices, 1)-1
        _WinAPI_GetRawInputDeviceInfo($aDevices[$i][0], $tText, 256, $RIDI_DEVICENAME)
        If StringRegExp(DllStructGetData($tText, 1), $sPattern) Then
;~          ConsoleWrite(DllStructGetData($tText, 1)&@CRLF)
            If $bSwitch Then Return
            $bSwitch = True
            ConsoleWrite("element inserted, action taken"&@CRLF)
            ;do stuff here
            Return
        EndIf
    Next
    If Not $bSwitch Then Return
    $bSwitch = False
    ConsoleWrite("element removed, action taken"&@CRLF)
EndFunc

Func _MyExit()
    Exit
EndFunc

And time could be reduced further if the for loop did not need _WinAPI_EnumRawInputDevices, but rather processed each match as needed from the GetRawInputDeviceList dllcall

Edited by genius257
Chaged $sVID and $sPID values
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...