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.
-33.636934
-70.679350