cmv has asked for the wisdom of the Perl Monks concerning the following question:

Folks-

On a windows machine I need to be able to generate a Device Interface Path (DIP - my acronym) for a device that exists on a machine.

Node Re^3: WindowsRegistry{ServiceName} V.S. wmic{ServiceName} describes how you can get the DIP (labeled ServiceName), when the device appears in the registry (many thanks to all the monks who showed this to me). This node also shows you how to see all the devices (including the ones with no entry in the registry) using the "wmic nic list brief" command (thanks to goibhniu for this one).

How can you get a DIP for a devices not appearing in the registry?

Here's how to do this with the Windows Driver calls SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces, and SetupDiGetDeviceInterfaceDetail (please excuse my license with syntax here...):

$DevInfoSet = SetupDiGetClassDevs(...); $DevInterfaceData = SetupDiEnumDeviceInterfaces($DevInfoSet); $DevDetails = SetupDiGetDeviceInterfaceDetail_($DevInfoSet, $DevInterf +aceData);
What you end up with in $DevDetails is described here (the thing we're trying to get is labeled DevicePath).

Two questions:
1.) Is there a way to do this in perl?
2.) Is there a different/better way to do this in perl?

Thanks for letting me stand on the shoulders of giants!

-Craig

UPDATE:
Using the wmic.exe command, you can get the information as follows:

wmic nicconfig list /format:value
The GUID value is $SettingID, so the path would be:
//./$SettingID

Replies are listed 'Best First'.
Re: Generating a windows Device Interface Path in perl?
by syphilis (Archbishop) on Sep 22, 2007 at 06:54 UTC
    Win32::API and Inline::C should be able to help out here. Here's an Inline::C demo (based on some C code I found in my msdn documentation):
    use warnings; use Inline C => Config => LIBS => '-lSetupAPI', BUILD_NOISY => 1; use Inline C => <<'EOC'; #include <windows.h> #include <setupapi.h> #include <stdio.h> #include <devguid.h> #include <regstr.h> int wrap_SetupDiGetClassDevs() { HDEVINFO hDevInfo; // Create a HDEVINFO with all present devices. hDevInfo = SetupDiGetClassDevs(NULL, 0, // Enumerator 0, DIGCF_PRESENT | DIGCF_ALLCLASSES ); if (hDevInfo == INVALID_HANDLE_VALUE) croak("INVALID_HANDLE_VALUE returned"); return hDevInfo; } void wrap_SetupDiDestroyDeviceInfoList(int hDevInfo) { SetupDiDestroyDeviceInfoList(hDevInfo); } void foo(int hDevInfo) { INLINE_STACK_VARS; SP_DEVINFO_DATA DeviceInfoData; DWORD i, DataT, buffersize; LPTSTR buffer; // Enumerate through all devices in Set. DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); INLINE_STACK_RESET; for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i, &DeviceInfoData);i++) { buffer = NULL; buffersize = 0; // // Call function with null to begin with, // then use the returned buffer size // to Alloc the buffer. Keep calling until // success or an unknown failure. // while (!SetupDiGetDeviceRegistryProperty( hDevInfo, &DeviceInfoData, SPDRP_DEVICEDESC, &DataT, (PBYTE)buffer, buffersize, &buffersize)) { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { // Change the buffer size. if (buffer) LocalFree(buffer); buffer = LocalAlloc(LPTR,buffersize); } else croak("ERROR 1 in sub foo"); } INLINE_STACK_PUSH(sv_2mortal(newSVpv(buffer, 0))); if (buffer) LocalFree(buffer); } if ( GetLastError()!=NO_ERROR && GetLastError()!=ERROR_NO_MORE_ITEMS ) croak("ERROR 2 in sub foo"); INLINE_STACK_DONE; INLINE_STACK_RETURN(i); } EOC # perl code starts here: my $handle = wrap_SetupDiGetClassDevs(); my @devices = foo($handle); # clean up wrap_SetupDiDestroyDeviceInfoList($handle); print $_, "\n" for @devices;
    Feel free to modify the code so that it does what you want. I don't think MinGW contains the requisite C header files, so you would need an MS compiler to run that code. And because the code passes a handle around, the compiler probably needs to be VC 6 (not VC 7 or 8) - assuming that your perl was built with VC 6.

    So ... it may not be all that useful to you ... but it kept me occupied for a while :-)

    Cheers,
    Rob

      This code was very helpful. It does have some drawbacks, such as casting pointers to and from int, and not enumerating interfaces.

      Below is a require file that builds on the ideas and exports a single function, which I use to provide a sensible UI for CDROM devices. Of course, it can be modified for other uses. I don't claim it's perfect, but I thought that in exchange for the excellent starting point, I should give back. Note that it is customized for CDROMs; for other disks, you'll want to retrieve the partition number and for non-storage, do something else...

      The code may be freely used and modified - no warranty, but please follow the usual rules: credit the source, add a comment if you make any modifications, and do not remove the copyright. Enjoy.

      # CD drive information # Copyright (C) 2018 Timothe Litt litt _at acm ddot org # # This code may be freely used and modified. There is no # warranty - use at your own risk. You may not remove # the copyright. If you make any changes, add a comment # identifying yourself and the change. # use 5.10.0; use warnings; use strict; package CdInfo; our $VERSION = '1.001'; my $gitid = '$Format:%H$'; our $cdate = '$Format:%cD$'; $VERSION .= ", commit $gitid" if( $gitid !~ /^\044Format:.*\$$/ ); use Exporter; our @ISA = qw/Exporter/; our @EXPORT_OK = qw/cdinfo/; use File::Find; my $libpath; my $incpath; $libpath = '' unless( $libpath ); $incpath = '' unless( $incpath ); BEGIN { find( { wanted => sub { /^libsetupapi\.a$/i && ($libpath ||= $File::Find::dir); /^setupapi\.h$/i && ($incpath ||= $File::Find::dir); }, follow => 1, follow_skip => 2 }, @INC ); die( "No MinGW libsetupapi $libpath $incpath\n" ) unless( $libpath && $incpath ); } use Inline( C => Config => libs => "-L$libpath -lSetupAPI", INC => "-I$incpath", # print_info => 1, # BUILD_NOISY => 1, ); use Inline 'C'; Inline->init; 1; __DATA__ __C__ #include <windows.h> #include <stdio.h> #include <initguid.h> #include <devguid.h> #include <regstr.h> #include <setupapi.h> #include <WinioCtl.h> /* Error codes can be looked-up at * https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v +=vs.85).aspx */ /* Returns an array that may be suitable for initializing a hash. * Each CDROM device returns two elements. * The second is always the "Friendly" name of the device. * item selects the first: * 0 - The storage device number (note: not persisted across reboots) * 1 - The device path (suitable for calling CreateFile) * * '' is returned if he value can't be obtained or item is out of rang +e. * Multiple errors will, of course, be replaced by the last entry if * used as a hash initializer. In that case, to get all the errors, * inspect the array. */ void cdinfo( int item ) { INLINE_STACK_VARS; HDEVINFO hDevInfo; SP_DEVINFO_DATA DeviceInfoData; DWORD i, n = 0, DataT; INLINE_STACK_RESET; static GUID GUID_DEVINTERFACE_CDROM = { 0x53F56308, 0xB6BF, 0x11D0, {0x94, 0xF2, 0x00, 0xA0, 0xC9, 0x1E, 0xFB, 0x8B } }; /* Create a set of all present CDROM devices. */ hDevInfo = SetupDiGetClassDevs( &GUID_DEVINTERFACE_CDROM, NULL, NU +LL, DIGCF_PRESENT | DIGCF_DEVICEINTERF +ACE ); if( hDevInfo == INVALID_HANDLE_VALUE) { char buf[132]; sprintf( buf, "cdinfo: SetupDiGetClassDevs error %08x", GetLast +Error() ); croak( buf ); } /* Enumerate all devices in set. */ memset( &DeviceInfoData, 0, sizeof(DeviceInfoData ) ); DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); i = 0; while( SetupDiEnumDeviceInfo( hDevInfo, i++, &DeviceInfoData ) ) { SP_DEVICE_INTERFACE_DATA ifdata; DWORD j = 0; memset( &ifdata, 0, sizeof( ifdata )); ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while( SetupDiEnumDeviceInterfaces( hDevInfo, &DeviceInfoData, &GUID_DEVINTERFACE_CDROM, +j++, &ifdata ) ) { PSP_DEVICE_INTERFACE_DETAIL_DATA detail = NULL; LPTSTR buffer = NULL; DWORD buffersize = 0; SP_DEVINFO_DATA DevInfoData; HANDLE devhandle; STORAGE_DEVICE_NUMBER devnum; DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); /* Obtain device path name (\\?\bus...) */ if( !SetupDiGetDeviceInterfaceDetail( hDevInfo, &ifdata, NULL, 0, &buffersize +, NULL ) && GetLastError() != ERROR_INSUFFICIENT_BUFFER ) { char buf[132]; sprintf( buf, "cdinfo: SetupDiGetDeviceInterfaceDetail erro +r %08x", GetLastError() ); croak( buf ); } detail = (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc( LPTR, buffersize ); detail->cbSize = sizeof( SP_DEVICE_INTERFACE_DETAIL_DATA +); if( !SetupDiGetDeviceInterfaceDetail( hDevInfo, &ifdata, detail, buffersize, + NULL, &DevInfoData ) ) { char buf[132]; sprintf( buf, "cdinfo: SetupDiGetDeviceInterfaceDetail err +or %08x", GetLastError() ); if( detail ) LocalFree( detail ); croak( buf ); } switch( item ) { case 0: devhandle = CreateFile( detail->DevicePath, 0, FILE_SHARE_DELETE|FILE_SHARE_ +READ| FILE_SHARE_ +WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ) +; if( devhandle == INVALID_HANDLE_VALUE || ! DeviceIoControl( devhandle, IOCTL_STORAGE_GET_DEVICE_NUMBE +R, NULL, 0, &devnum, sizeof( devn +um ), &buffersize, NULL ) ) { INLINE_STACK_PUSH(sv_2mortal(newSVpv("", 0))); } else { INLINE_STACK_PUSH(sv_2mortal(newSVuv(devnum.Devic +eNumber))); } if( devhandle != INVALID_HANDLE_VALUE ) CloseHandle( devhandle ); break; case 1: INLINE_STACK_PUSH(sv_2mortal(newSVpv(detail->DevicePa +th, 0))); break; default: INLINE_STACK_PUSH(sv_2mortal(newSVpv("", 0))); break; } ++n; LocalFree( detail ); detail = NULL; buffersize = 0; while( !SetupDiGetDeviceRegistryProperty( hDevInfo, &DevInfoData, SPDRP_FRIENDLYNAME, &DataT, (PBYTE)buffer, buffersize, &buffersize)) { if( GetLastError() == ERROR_INSUFFICIENT_BUFFER ) { /* Increase the buffer size. */ if( buffer) LocalFree(buffer); buffer = (LPTSTR)LocalAlloc( LPTR, buffersize ); } else { char buf[132]; sprintf( buf, "cdinfo: SetupDiGetDeviceRegistryPropert +y error %08x", GetLastError() ); if( buffer) LocalFree(buffer); croak(buf); } } SetLastError( 0 ); INLINE_STACK_PUSH(sv_2mortal(newSVpv(buffer, 0))); ++n; if( buffer) LocalFree(buffer); buffer = NULL; buffersize = 0; } if( GetLastError() != NO_ERROR && GetLastError() != ERROR_NO_MORE_ITEMS ) { char buf[132]; sprintf( buf, "cdinfo: SetupDiEnumDeviceInterfaces error %08x", GetLastError() ); croak(buf); } } if( GetLastError() != NO_ERROR && GetLastError() != ERROR_NO_MORE_ITEMS ) { char buf[132]; sprintf( buf, "cdinfo: SetupDiEnumDeviceInfo error %08x", GetLastEr +ror() ); croak(buf); } SetupDiDestroyDeviceInfoList(hDevInfo); INLINE_STACK_DONE; INLINE_STACK_RETURN(n); }
      Rob-

      Wow! Great response. I've never knew about Inline before -- pretty cool stuff. Thanks for the time and effort!

      I can see me playing around with this for a while, but I wonder if there isn't already some windows command that either does this, or gets me a bit closer!?! If not, it may be most efficient just to build one, and call that from the perl script.

      Either way, I sure am learning a lot!

      -Craig

Re: Generating a windows Device Interface Path in perl? SOLVED!
by goibhniu (Hermit) on Sep 24, 2007 at 19:32 UTC

    Congrats and ++ for your solution.


    I humbly seek wisdom.
A reply falls below the community's threshold of quality. You may see it by logging in.