Starting with Windows Vista The SetupDi and the WMI are not longer the only APIs to enumerate devices and receive notifications about hardware changes, with the introduction of the Function Discovery API you can access the installed devices using a unified API and interfaces for gathering functionality, properties, and notifications from various device types like PnP, PnP-X, Registry, NetBIOS and custom (third-party) providers.
Delphi XE3 include the translation of the headers for the Function Discovery API in the Winapi.Functiondiscovery unit. In this post I will show the basic code to enumerate the hardware devices.
To get a collection of the devices (function instances), you must use use the IFunctionDiscovery.GetInstanceCollection method. from here to get each function instance in the collection in order, use the IFunctionInstanceCollection.Item method and finally use the IFunctionInstance.OpenPropertyStore and IPropertyStore.GetValue methods to retrieve the value of each property.
{$APPTYPE CONSOLE} {$R *.res} uses System.Win.ComObj, Winapi.Windows, Winapi.Activex, Winapi.PropSys, Winapi.Functiondiscovery, System.SysUtils; procedure Enumerate; var LFunctionDiscovery : IFunctionDiscovery; hr : HResult; i : integer; LFunctionInstance : IFunctionInstance; ppIFunctionInstanceCollection : IFunctionInstanceCollection; ppIPropertyStore : IPropertyStore; pv : TPropVariant; pdwCount : DWORD; pszCategory: PWCHAR; begin //create an instance to the IFunctionDiscovery interface LFunctionDiscovery := CreateComObject(CLSID_FunctionDiscovery) as IFunctionDiscovery; try //set the provider to search pszCategory:=FCTN_CATEGORY_PNP; //get the devices collection hr := LFunctionDiscovery.GetInstanceCollection(pszCategory, nil, true, ppIFunctionInstanceCollection); //get the collection count if Succeeded(hr) and Succeeded(ppIFunctionInstanceCollection.GetCount(pdwCount)) then begin if pdwCount=0 then Writeln(Format('No items was found for the %s category',[pszCategory])) else for i := 0 to pdwCount - 1 do begin //get the n Item of the collection if Succeeded(ppIFunctionInstanceCollection.Item(i, LFunctionInstance)) then begin //init the propertiess store LFunctionInstance.OpenPropertyStore(STGM_READ, ppIPropertyStore); //read the properties values if Succeeded(ppIPropertyStore.GetValue(PKEY_NAME, pv)) then Writeln(Format('Name %s',[pv.pwszVal])); if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_InstanceId, pv)) then Writeln(Format('Instance Id %s',[pv.pwszVal])); if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_Driver, pv)) then Writeln(Format('Device Driver %s',[pv.pwszVal])); if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_Model, pv)) then Writeln(Format('Model %s',[pv.pwszVal])); if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_Manufacturer, pv)) then Writeln(Format('Manufacturer %s',[pv.pwszVal])); if Succeeded(ppIPropertyStore.GetValue(PKEY_Device_LocationInfo, pv)) then Writeln(Format('Location %s',[pv.pwszVal])); Writeln; end else RaiseLastOSError; end; end else RaiseLastOSError; finally LFunctionDiscovery:=nil; end; end; begin try if (Win32MajorVersion >= 6) then // available on Vista (or later) begin if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then try Enumerate; finally CoUninitialize; end; end else Writeln('Windows version not compatible'); except on E:Exception do Writeln(E.Classname, ':', E.Message); end; Writeln('Press Enter to exit'); Readln; end.
As you can see the code is very straightforward, Now the next sample show how retrieves all the properties of each device.
{$APPTYPE CONSOLE} {$R *.res} uses System.Win.ComObj, Winapi.Windows, Winapi.Activex, Winapi.PropSys, Winapi.Functiondiscovery, System.Generics.Collections, System.SysUtils; procedure Enumerate2; var LFunctionDiscovery : IFunctionDiscovery; hr : HResult; i,j : integer; LFunctionInstance : IFunctionInstance; ppIFunctionInstanceCollection : IFunctionInstanceCollection; ppIPropertyStore : IPropertyStore; pv : TPropVariant; pdwCount : DWORD; cProps: DWORD; pszCategory: PWCHAR; pkey: TPropertyKey; ListKeys : TDictionary<TPropertyKey, string>; KeyName : string; begin //create a list with TPropertyKey descriptions ListKeys:=TDictionary<TPropertyKey, string>.Create; try ListKeys.Add(PKEY_NAME, 'Name'); { Device properties } { These PKEYs correspond to the old setupapi SPDRP_XXX properties } ListKeys.Add(PKEY_Device_DeviceDesc, 'Device Desc'); ListKeys.Add(PKEY_Device_HardwareIds, 'Hardware Id'); ListKeys.Add(PKEY_Device_CompatibleIds, 'Compatible Id'); ListKeys.Add(PKEY_Device_Service, 'Device Service'); ListKeys.Add(PKEY_Device_Class, 'Class'); ListKeys.Add(PKEY_Device_ClassGuid, 'Class GUID'); ListKeys.Add(PKEY_Device_ConfigFlags, 'ConfigFlags'); ListKeys.Add(PKEY_Device_Manufacturer, 'Manufacturer'); ListKeys.Add(PKEY_Device_FriendlyName, 'Friendly Name'); ListKeys.Add(PKEY_Device_LocationInfo, 'Location Info'); ListKeys.Add(PKEY_Device_PDOName, 'PDO Name'); ListKeys.Add(PKEY_Device_Capabilities, 'Capabilities'); ListKeys.Add(PKEY_Device_UINumber, 'UI Number'); ListKeys.Add(PKEY_Device_UpperFilters, 'Upper Filters'); ListKeys.Add(PKEY_Device_LowerFilters, 'Lower Filters'); ListKeys.Add(PKEY_Device_BusTypeGuid, 'Bus Type Guid'); ListKeys.Add(PKEY_Device_LegacyBusType, 'Legacy Bus Type'); ListKeys.Add(PKEY_Device_BusNumber, 'Bus Number'); ListKeys.Add(PKEY_Device_EnumeratorName, 'Enumerator Name'); ListKeys.Add(PKEY_Device_Security, 'Security'); ListKeys.Add(PKEY_Device_SecuritySDS, 'Security SDS'); ListKeys.Add(PKEY_Device_DevType, 'Dev Type'); ListKeys.Add(PKEY_Device_Exclusive, 'Exclusive'); ListKeys.Add(PKEY_Device_Characteristics, 'Characteristics'); ListKeys.Add(PKEY_Device_Address, 'Address'); ListKeys.Add(PKEY_Device_UINumberDescFormat, 'UI Number Desc. Format'); ListKeys.Add(PKEY_Device_PowerData, 'Power Data'); ListKeys.Add(PKEY_Device_RemovalPolicy, 'Removal Policy'); ListKeys.Add(PKEY_Device_RemovalPolicyDefault, 'Removal Policy Default'); ListKeys.Add(PKEY_Device_RemovalPolicyOverride, 'Removal Policy Override'); ListKeys.Add(PKEY_Device_InstallState, 'Install State'); ListKeys.Add(PKEY_Device_LocationPaths, 'Location Paths'); ListKeys.Add(PKEY_Device_BaseContainerId, 'BaseContainer Id'); { Device properties } { These PKEYs correspond to a device's status and problem code } ListKeys.Add(PKEY_Device_DevNodeStatus, 'Dev Node Status'); ListKeys.Add(PKEY_Device_ProblemCode, 'Problem Code'); { Device properties } { These PKEYs correspond to device relations } ListKeys.Add(PKEY_Device_EjectionRelations, 'Ejection Relations'); ListKeys.Add(PKEY_Device_RemovalRelations, 'Removal Relations'); ListKeys.Add(PKEY_Device_PowerRelations, 'Power Relations'); ListKeys.Add(PKEY_Device_BusRelations, 'Bus Relations'); ListKeys.Add(PKEY_Device_Parent, 'Parent'); ListKeys.Add(PKEY_Device_Children, 'Children'); ListKeys.Add(PKEY_Device_Siblings, 'Sibling'); ListKeys.Add(PKEY_Device_TransportRelations, 'Transport Relations'); { Other Device properties } ListKeys.Add(PKEY_Device_Reported, 'Reported'); ListKeys.Add(PKEY_Device_Legacy, 'Legacy'); ListKeys.Add(PKEY_Device_InstanceId, 'Instance Id'); ListKeys.Add(PKEY_Device_ContainerId, 'Container Id'); ListKeys.Add(PKEY_Device_ModelId, 'Model Id'); ListKeys.Add(PKEY_Device_FriendlyNameAttributes, 'Friendly Name Attributes'); ListKeys.Add(PKEY_Device_ManufacturerAttributes, 'Manufacturer Attributes'); ListKeys.Add(PKEY_Device_PresenceNotForDevice, 'Presence Not For Device'); ListKeys.Add(PKEY_Numa_Proximity_Domain, 'Numa Proximity Domain'); ListKeys.Add(PKEY_Device_DHP_Rebalance_Policy, 'DHP Rebalance Policy'); ListKeys.Add(PKEY_Device_Numa_Node, 'Numa Node'); ListKeys.Add(PKEY_Device_BusReportedDeviceDesc, 'Bus Reported Device Desc'); ListKeys.Add(PKEY_Device_InstallInProgress, 'Install In Progress'); { Device driver properties } ListKeys.Add(PKEY_Device_DriverDate, 'Driver Date'); ListKeys.Add(PKEY_Device_DriverVersion, 'Driver Version'); ListKeys.Add(PKEY_Device_DriverDesc, 'Driver Desc'); ListKeys.Add(PKEY_Device_DriverInfPath, 'Driver Inf Path'); ListKeys.Add(PKEY_Device_DriverInfSection, 'Driver Inf Section'); ListKeys.Add(PKEY_Device_DriverInfSectionExt, 'Driver Inf Section Ext'); ListKeys.Add(PKEY_Device_MatchingDeviceId, 'Matching DeviceId'); ListKeys.Add(PKEY_Device_DriverProvider, 'Driver Provider'); ListKeys.Add(PKEY_Device_DriverPropPageProvider, 'Driver Prop Page Provider'); ListKeys.Add(PKEY_Device_DriverCoInstallers, 'Driver CoInstallers'); ListKeys.Add(PKEY_Device_ResourcePickerTags, 'Resource Picker Tags'); ListKeys.Add(PKEY_Device_ResourcePickerExceptions, 'Resource Picker Exceptions'); ListKeys.Add(PKEY_Device_DriverRank, 'Driver Rank'); ListKeys.Add(PKEY_Device_DriverLogoLevel, 'Driver Logo Level'); ListKeys.Add(PKEY_Device_NoConnectSound, 'No Connect Sound'); ListKeys.Add(PKEY_Device_GenericDriverInstalled, 'Generic Driver Installed'); ListKeys.Add(PKEY_Device_AdditionalSoftwareRequested, 'Additional Software Requested'); {Add more TPropertyKey here} //create a instance for the IFunctionDiscovery interface LFunctionDiscovery := CreateComObject(CLSID_FunctionDiscovery) as IFunctionDiscovery; try //set the provider pszCategory:=FCTN_CATEGORY_PNP; //get all the instances for the current provider hr := LFunctionDiscovery.GetInstanceCollection(pszCategory, nil, true, ppIFunctionInstanceCollection); if Succeeded(hr) then if Succeeded(ppIFunctionInstanceCollection.GetCount(pdwCount)) then begin if pdwCount=0 then Writeln(Format('No items was found for the %s category',[pszCategory])) else for i := 0 to pdwCount - 1 do begin if Succeeded(ppIFunctionInstanceCollection.Item(i, LFunctionInstance)) then begin //open the properties if Succeeded(LFunctionInstance.OpenPropertyStore(STGM_READ, ppIPropertyStore)) then begin //get the num of properties for the current instance ppIPropertyStore.GetCount(cProps); for j := 0 to cProps - 1 do begin //get the TPropertyKey for the current index if Succeeded(ppIPropertyStore.GetAt(j, pkey)) then // get the value for the curent TPropertyKey if Succeeded(ppIPropertyStore.GetValue(pkey, pv)) then begin //resolves the key description or use the TGUID if is not found KeyName:=pkey.fmtid.ToString; if ListKeys.ContainsKey(pkey) then KeyName:=ListKeys.Items[pkey]; //depending of the type of the property display the info case pv.vt of VT_BOOL : Writeln(Format('%-40s %s',[KeyName , BoolToStr(pv.boolVal, True)])); VT_UINT : Writeln(Format('%-40s %d',[KeyName ,pv.ulVal])); VT_INT : Writeln(Format('%-40s %d',[KeyName ,pv.iVal])); VT_I4, VT_UI4 : Writeln(Format('%-40s %d',[KeyName ,pv.ulVal])); VT_EMPTY : Writeln(Format('%-40s %s',[KeyName ,'(Empty)'])); VT_LPWSTR : Writeln(Format('%-40s %s',[KeyName ,pv.pwszVal])); VT_CLSID : Writeln(Format('%-40s %s',[KeyName ,pv.puuid^.ToString])); else Writeln(Format('%-40s %s',[KeyName ,'(Type Unknow)'])); end; PropVariantClear(pv); end; end; Writeln; end; end else RaiseLastOSError; end; end else RaiseLastOSError else RaiseLastOSError; finally LFunctionDiscovery:=nil; end; finally ListKeys.Free; end; end; begin try if (Win32MajorVersion >= 6) then // available on Vista (or later) begin if Succeeded(CoInitializeEx(nil, COINIT_MULTITHREADED)) then try Enumerate2; finally CoUninitialize; end; end else Writeln('Windows version not compatible'); except on E:Exception do Writeln(E.Classname, ':', E.Message); end; Writeln('Press Enter to exit'); Readln; end.
This is just a basic sample of the use of the Function Discovery API, in the next post I will show another features of this API.
September 10, 2012 at 1:26 pm
Super cool, thx.
Pingback: Exploring Delphi XE3 – WinApi Additions – Winapi.Functiondiscovery Part 2 « The Road to Delphi – a Blog about programming
Pingback: Exploring Delphi XE3 – WinApi Additions – Winapi.Functiondiscovery Part 3 « The Road to Delphi – a Blog about programming
Pingback: The Hacker’s Corner » Blog Archive » Record Helpers and Function Discovery