text: // 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;
  }
  
  
  