526 lines
15 KiB
C++
526 lines
15 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
#include "FunDiscovery.h"
|
|
#include <objbase.h>
|
|
#include <strsafe.h>
|
|
|
|
// Property Keys are GUIDs which don't mean much to a human.
|
|
// In order to display the Property Key's description instead of the
|
|
// GUID, we have defined a lookup table of key/description pairs.
|
|
//
|
|
// THIS IS ONLY FOR DISPLAY PURPOSES
|
|
//
|
|
|
|
#define KEY_ENTRY(x) { &PKEY_##x, L#x }
|
|
|
|
struct KEYENTRY
|
|
{
|
|
const PROPERTYKEY * key;
|
|
const WCHAR * pszDescription;
|
|
};
|
|
|
|
const KEYENTRY g_Keys[] = {
|
|
KEY_ENTRY(ComputerName),
|
|
KEY_ENTRY(FD_Visibility),
|
|
KEY_ENTRY(Device_InstanceId),
|
|
KEY_ENTRY(Device_Interface),
|
|
KEY_ENTRY(Device_HardwareIds),
|
|
KEY_ENTRY(Device_CompatibleIds),
|
|
KEY_ENTRY(Device_Service),
|
|
KEY_ENTRY(Device_Class),
|
|
KEY_ENTRY(Device_ClassGuid),
|
|
KEY_ENTRY(Device_Driver),
|
|
KEY_ENTRY(Device_ConfigFlags),
|
|
KEY_ENTRY(Device_Manufacturer),
|
|
KEY_ENTRY(Device_FriendlyName),
|
|
KEY_ENTRY(Device_LocationInfo),
|
|
KEY_ENTRY(Device_PDOName),
|
|
KEY_ENTRY(Device_Capabilities),
|
|
KEY_ENTRY(Device_UINumber),
|
|
KEY_ENTRY(Device_UpperFilters),
|
|
KEY_ENTRY(Device_LowerFilters),
|
|
KEY_ENTRY(Device_BusTypeGuid),
|
|
KEY_ENTRY(Device_LegacyBusType),
|
|
KEY_ENTRY(Device_BusNumber),
|
|
KEY_ENTRY(Device_EnumeratorName),
|
|
KEY_ENTRY(Device_QueueSize),
|
|
KEY_ENTRY(Device_Status),
|
|
KEY_ENTRY(Device_Comment),
|
|
KEY_ENTRY(Device_Model),
|
|
KEY_ENTRY(Device_DeviceDesc),
|
|
KEY_ENTRY(Device_BIOSVersion),
|
|
KEY_ENTRY(PNPX_DomainName),
|
|
KEY_ENTRY(PNPX_ShareName),
|
|
KEY_ENTRY(PNPX_GlobalIdentity),
|
|
KEY_ENTRY(PNPX_Types),
|
|
KEY_ENTRY(PNPX_Scopes),
|
|
KEY_ENTRY(PNPX_XAddrs),
|
|
KEY_ENTRY(PNPX_MetadataVersion),
|
|
KEY_ENTRY(PNPX_Manufacturer),
|
|
KEY_ENTRY(PNPX_ManufacturerUrl),
|
|
KEY_ENTRY(PNPX_ModelName),
|
|
KEY_ENTRY(PNPX_ModelNumber),
|
|
KEY_ENTRY(PNPX_ModelUrl),
|
|
KEY_ENTRY(PNPX_Upc),
|
|
KEY_ENTRY(PNPX_PresentationUrl),
|
|
KEY_ENTRY(PNPX_FriendlyName),
|
|
KEY_ENTRY(PNPX_FirmwareVersion),
|
|
KEY_ENTRY(PNPX_SerialNumber),
|
|
KEY_ENTRY(PNPX_DeviceCategory),
|
|
KEY_ENTRY(PNPX_PhysicalAddress),
|
|
KEY_ENTRY(PNPX_NetworkInterfaceGuid),
|
|
KEY_ENTRY(PNPX_NetworkInterfaceLuid),
|
|
KEY_ENTRY(PNPX_Installable),
|
|
KEY_ENTRY(PNPX_Associated),
|
|
KEY_ENTRY(PNPX_IpAddress),
|
|
KEY_ENTRY(PNPX_CompatibleTypes),
|
|
KEY_ENTRY(PNPX_ServiceId),
|
|
KEY_ENTRY(PNPX_ServiceTypes),
|
|
KEY_ENTRY(PNPX_ServiceAddress),
|
|
KEY_ENTRY(WNET_Scope),
|
|
KEY_ENTRY(WNET_Type),
|
|
KEY_ENTRY(WNET_DisplayType),
|
|
KEY_ENTRY(WNET_Usage),
|
|
KEY_ENTRY(WNET_LocalName),
|
|
KEY_ENTRY(WNET_RemoteName),
|
|
KEY_ENTRY(WNET_Comment),
|
|
KEY_ENTRY(WNET_Provider),
|
|
KEY_ENTRY(DriverPackage_VendorWebSite)
|
|
};
|
|
|
|
#define ARRAY_SIZE( x ) ( sizeof( x ) / sizeof( x[0] ))
|
|
|
|
// Constructor
|
|
CMyFDHelper::CMyFDHelper( ):
|
|
m_lRefCount( 1 ),
|
|
m_hAdd( NULL ),
|
|
m_hRemove( NULL ),
|
|
m_hChange( NULL )
|
|
{
|
|
}
|
|
|
|
// Destructor
|
|
CMyFDHelper::~CMyFDHelper( )
|
|
{
|
|
if( NULL != m_hAdd )
|
|
::CloseHandle( m_hAdd );
|
|
if( NULL != m_hRemove )
|
|
::CloseHandle( m_hRemove );
|
|
if( NULL != m_hChange )
|
|
::CloseHandle( m_hChange );
|
|
|
|
if( NULL != m_pFunDisc )
|
|
{
|
|
m_pFunDisc->Release( );
|
|
m_pFunDisc = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
HRESULT CMyFDHelper::Initialize( )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// Create events for signaling notifications
|
|
if( S_OK == hr )
|
|
{
|
|
m_hAdd = ::CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if( NULL == m_hAdd )
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
m_hRemove = ::CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if( NULL == m_hRemove )
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
if( S_OK == hr )
|
|
{
|
|
m_hChange = ::CreateEvent( NULL, FALSE, FALSE, NULL );
|
|
if( NULL == m_hChange )
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
// CoCreate function discovery
|
|
if ( S_OK == hr )
|
|
hr = CoCreateInstance( CLSID_FunctionDiscovery,
|
|
NULL,
|
|
CLSCTX_ALL,
|
|
IID_IFunctionDiscovery,
|
|
(LPVOID*) &m_pFunDisc );
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Outputs a list of all instances in the Category
|
|
HRESULT CMyFDHelper::ListFunctionInstances( const WCHAR* pszCategory )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
IFunctionInstanceCollectionQuery * pQuery = NULL;
|
|
IFunctionInstanceCollection * pCollection = NULL;
|
|
IFunctionInstance * pInstance = NULL;
|
|
IPropertyStore * pStore = NULL;
|
|
|
|
DWORD dwCount = 0;
|
|
|
|
//
|
|
// Create an instance collection query. This CMyFDHelper class
|
|
// implements CFunctionDiscoveryNotificationWrapper and
|
|
// therefore IFunctionDiscoveryNotification. One of the parameters
|
|
// to CreateInstanceCollectionQuery is a IFunctionDiscoveryNotification
|
|
// pointer. This object is sent query events.
|
|
//
|
|
if( S_OK == hr )
|
|
hr = m_pFunDisc->CreateInstanceCollectionQuery( pszCategory,
|
|
NULL,
|
|
TRUE,
|
|
this,
|
|
NULL,
|
|
&pQuery);
|
|
|
|
// Execute the query. If it's a local query, for example PnP
|
|
// or the registry, hr will be set to S_OK and the collection
|
|
// will be populated with instances. If it's a network query
|
|
// hr will be set to E_PENDING and the collection will be
|
|
// empty. All instances from a network query are returned
|
|
// via notifications.
|
|
|
|
if( S_OK == hr )
|
|
hr = pQuery->Execute( &pCollection );
|
|
|
|
// If this is a local query, we expect S_OK
|
|
if( S_OK == hr )
|
|
hr = pCollection->GetCount( &dwCount );
|
|
|
|
// Loop through all instances returned in the collection
|
|
// This is done only in the case of a local query
|
|
|
|
for( DWORD i = 0; ( S_OK == hr && ( i < dwCount )); i++ )
|
|
{
|
|
if( S_OK == hr )
|
|
hr = pCollection->Item( i, &pInstance );
|
|
|
|
if( S_OK == hr )
|
|
hr = pInstance->OpenPropertyStore( STGM_READ, &pStore );
|
|
|
|
if( S_OK == hr )
|
|
hr = DisplayProperties( pStore );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
wprintf(L"Found %d instances on your system.\n\n", dwCount );
|
|
|
|
if( NULL != pQuery )
|
|
pQuery->Release( );
|
|
if( NULL != pCollection )
|
|
pCollection->Release( );
|
|
if( NULL != pInstance )
|
|
pInstance->Release( );
|
|
if( NULL != pStore )
|
|
pStore->Release( );
|
|
|
|
// If it's a network query, we expected E_PENDING from the above
|
|
// call to pQuery->Execute( &pCollection )
|
|
// Instances will be returned via notifications, return S_OK
|
|
if( E_PENDING == hr )
|
|
hr = S_OK;
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Waits for the specified amount of time for an add, remove
|
|
// or change notification
|
|
HRESULT CMyFDHelper::WaitForChange( DWORD dwTimeout,
|
|
const WCHAR* pszCategory,
|
|
QueryUpdateAction eAction )
|
|
{
|
|
|
|
DWORD dwEventIndex;
|
|
HRESULT hr = S_OK;
|
|
|
|
IFunctionInstanceCollectionQuery * pQuery = NULL;
|
|
IFunctionInstanceCollection * pCollection = NULL;
|
|
|
|
HANDLE hEvent = NULL;
|
|
|
|
// Reset each event incase a notification was received by FD before
|
|
// this sample was interested.
|
|
|
|
if( m_hAdd )
|
|
::ResetEvent( m_hAdd );
|
|
if( m_hRemove)
|
|
::ResetEvent( m_hRemove );
|
|
if( m_hChange )
|
|
::ResetEvent( m_hChange );
|
|
|
|
// Create a query to recieve notifications
|
|
if( S_OK == hr )
|
|
hr = m_pFunDisc->CreateInstanceCollectionQuery(
|
|
pszCategory,
|
|
NULL,
|
|
TRUE,
|
|
this,
|
|
NULL,
|
|
&pQuery);
|
|
|
|
// Add a query constraint
|
|
|
|
// If we're querying the PnP provider category this constraint
|
|
// will tell the PnP provider not to populate the collection with current
|
|
// PnP devices. We will only get notifications as devices are added
|
|
// and removed
|
|
|
|
// If this method is called with a different category, the provider
|
|
// will ignore the constraint. It is PnP provider specific.
|
|
if( S_OK == hr )
|
|
hr = pQuery->AddQueryConstraint(
|
|
PROVIDERPNP_QUERYCONSTRAINT_NOTIFICATIONSONLY,
|
|
L"TRUE" );
|
|
|
|
// Execute the query. As long as the query exists
|
|
// we will recieve notifications.
|
|
if( S_OK == hr )
|
|
hr = pQuery->Execute( &pCollection );
|
|
|
|
// If it's a network query, we expect the E_PENDING
|
|
if( E_PENDING == hr )
|
|
hr = S_OK;
|
|
|
|
if( QUA_ADD == eAction )
|
|
hEvent = m_hAdd;
|
|
else if( QUA_REMOVE == eAction )
|
|
hEvent = m_hRemove;
|
|
else if( QUA_CHANGE == eAction )
|
|
hEvent = m_hChange;
|
|
else
|
|
hEvent = NULL;
|
|
|
|
// Block and wait for a notification
|
|
if(( S_OK == hr ) && ( NULL != hEvent ))
|
|
hr = ::CoWaitForMultipleHandles( 0,
|
|
dwTimeout,
|
|
1,
|
|
&hEvent,
|
|
&dwEventIndex );
|
|
|
|
if( RPC_S_CALLPENDING == hr )
|
|
wprintf(L"Timeout!\n");
|
|
|
|
// One device may correspond to multiple function instances
|
|
// This sleep allows the OnUpdate call to output information
|
|
// about each Function Instance.
|
|
|
|
// THIS SLEEP IS MERELY FOR DISPLAY PURPOSES
|
|
Sleep( 1000 );
|
|
|
|
if( NULL != pCollection )
|
|
pCollection->Release( );
|
|
if( NULL != pQuery )
|
|
pQuery->Release( );
|
|
|
|
return hr;
|
|
}
|
|
|
|
// This method is the main notification sink
|
|
// In this implementation it displays the name of the
|
|
// added removed or modify device
|
|
HRESULT CMyFDHelper::OnUpdate( QueryUpdateAction eAction,
|
|
FDQUERYCONTEXT fdqcQueryContext,
|
|
IFunctionInstance *pInstance)
|
|
{
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
|
|
if( NULL == pInstance )
|
|
return E_INVALIDARG;
|
|
|
|
HRESULT hr = S_OK;
|
|
IPropertyStore * pStore;
|
|
|
|
PROPVARIANT pvName;
|
|
::PropVariantInit( &pvName );
|
|
|
|
// Open the property store
|
|
hr = pInstance->OpenPropertyStore( STGM_READ, &pStore );
|
|
|
|
// In PnP the device's friendly name could be in one of the
|
|
// following property keys. Check each.
|
|
if( S_OK == hr )
|
|
hr = pStore->GetValue(PKEY_Device_DeviceDesc, &pvName);
|
|
if ( !pvName.pwszVal )
|
|
hr = pStore->GetValue(PKEY_Device_FriendlyName, &pvName);
|
|
|
|
// If there is no friendly name, get the provider instance
|
|
// ID instead. This ID is unique to the provider and is
|
|
// used to identify the instance.
|
|
if ( S_OK == hr && ( !pvName.pwszVal ))
|
|
{
|
|
pvName.vt = VT_LPWSTR;
|
|
hr = pInstance->GetProviderInstanceID(&(pvName.pwszVal));
|
|
}
|
|
|
|
if(( S_OK == hr ) && ( pvName.vt == VT_LPWSTR ))
|
|
{
|
|
if( QUA_ADD == eAction )
|
|
{
|
|
wprintf( L"Added: %s\n\n", pvName.pwszVal );
|
|
::SetEvent( m_hAdd );
|
|
}
|
|
else if( QUA_REMOVE == eAction )
|
|
{
|
|
wprintf( L"Removed: %s\n\n", pvName.pwszVal );
|
|
::SetEvent( m_hRemove );
|
|
}
|
|
else if( QUA_CHANGE == eAction )
|
|
{
|
|
wprintf( L"Changed: %s\n\n", pvName.pwszVal );
|
|
::SetEvent( m_hChange );
|
|
}
|
|
}
|
|
|
|
::PropVariantClear( &pvName );
|
|
pStore->Release( );
|
|
|
|
return hr;
|
|
}
|
|
|
|
// Required for the implementation of IFunctionDiscoveryNotification
|
|
HRESULT CMyFDHelper::OnError(HRESULT hr,
|
|
FDQUERYCONTEXT fdqcQueryContext,
|
|
const WCHAR *pszProvider)
|
|
{
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
|
|
if( NULL != pszProvider )
|
|
wprintf( L"%s encountered 0x%x.", pszProvider, hr );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Required for the implementation of IFunctionDiscoveryNotification
|
|
HRESULT CMyFDHelper::OnEvent(
|
|
DWORD dwEventID,
|
|
FDQUERYCONTEXT fdqcQueryContext,
|
|
const WCHAR *pszProvider)
|
|
{
|
|
UNREFERENCED_PARAMETER(dwEventID);
|
|
UNREFERENCED_PARAMETER(fdqcQueryContext);
|
|
|
|
if( NULL != pszProvider )
|
|
wprintf( L"%s sent OnEvent notification.", pszProvider );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// Outputs all properties in a property store
|
|
HRESULT CMyFDHelper::DisplayProperties( IPropertyStore * pPStore )
|
|
{
|
|
if ( NULL == pPStore )
|
|
return E_INVALIDARG;
|
|
|
|
HRESULT hr = S_OK;
|
|
DWORD cProps = 0;
|
|
|
|
//
|
|
// Get the number of properties in the store and loop
|
|
// through them all
|
|
//
|
|
hr = pPStore->GetCount(&cProps);
|
|
for (DWORD p = 0; ( S_OK == hr && p < cProps ); p++)
|
|
{
|
|
PROPERTYKEY key;
|
|
PROPVARIANT val;
|
|
|
|
hr = pPStore->GetAt(p, &key);
|
|
if( S_OK == hr )
|
|
{
|
|
hr = pPStore->GetValue(key, &val);
|
|
}
|
|
if ( S_OK == hr )
|
|
{
|
|
// Loop through that table defined at the beginning of this file
|
|
// to find the key name
|
|
|
|
WCHAR szKeyNameBuff[MAX_PATH];
|
|
const WCHAR * pszKeyName = NULL;
|
|
for (DWORD z = 0; z < ARRAY_SIZE(g_Keys) && NULL == pszKeyName; z++)
|
|
{
|
|
if (IsEqualPropertyKey(*g_Keys[z].key, key))
|
|
{
|
|
pszKeyName = g_Keys[z].pszDescription;
|
|
}
|
|
}
|
|
if (NULL == pszKeyName)
|
|
{
|
|
WCHAR szGuidKey[MAX_PATH];
|
|
::StringFromGUID2(key.fmtid, szGuidKey, ARRAY_SIZE(szGuidKey));
|
|
StringCchPrintfW(szKeyNameBuff,
|
|
ARRAY_SIZE(szKeyNameBuff),
|
|
L"%s-%08x",
|
|
szGuidKey,
|
|
key.pid);
|
|
|
|
pszKeyName = szKeyNameBuff;
|
|
}
|
|
switch(val.vt)
|
|
{
|
|
case VT_EMPTY:
|
|
wprintf(L"%30s = (empty)\n", pszKeyName);
|
|
break;
|
|
case VT_LPWSTR:
|
|
wprintf(L"%30s = %s\n", pszKeyName, val.pwszVal);
|
|
break;
|
|
case VT_UI4:
|
|
wprintf(L"%30s = %08x\n", pszKeyName, val.ulVal);
|
|
break;
|
|
case VT_I4:
|
|
wprintf(L"%30s = %08x\n", pszKeyName, val.ulVal);
|
|
break;
|
|
case VT_INT:
|
|
wprintf(L"%30s = %08x\n", pszKeyName, val.intVal);
|
|
break;
|
|
case VT_UINT:
|
|
wprintf(L"%30s = %08x\n", pszKeyName, val.uintVal);
|
|
break;
|
|
case VT_BOOL:
|
|
wprintf(L"%30s = %s\n",
|
|
pszKeyName,
|
|
val.boolVal?L"TRUE":L"FALSE" );
|
|
break;
|
|
case VT_LPWSTR | VT_VECTOR:
|
|
for (ULONG i = 0; i < val.calpwstr.cElems; i++)
|
|
{
|
|
wprintf(L"%30s = %s\n",
|
|
pszKeyName,
|
|
val.calpwstr.pElems[i]);
|
|
pszKeyName = L"";
|
|
}
|
|
break;
|
|
case VT_CLSID:
|
|
{
|
|
WCHAR szGuid[MAX_PATH];
|
|
::StringFromGUID2(*val.puuid, szGuid, ARRAY_SIZE(szGuid));
|
|
wprintf(L"%30s = %s\n", pszKeyName, szGuid);
|
|
}
|
|
break;
|
|
default:
|
|
wprintf(L"%30s = Variant Type %08x Unknown\n",
|
|
pszKeyName,
|
|
val.vt);
|
|
break;
|
|
}
|
|
::PropVariantClear(&val);
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
|