What's the deal with WSDAPI's WS-Discovery interfaces?

I briefly discussed WSDAPI's WS-Discovery interfaces in part 4 of WSDAPI 101, but I didn't describe in detail which interface to use to solve a specific problem.  So to fill in that gap, I'll cover each of the situations that map to these objects and a recommendation for which WSDAPI object to use.

Layered specifications: DPWS and WS-Discovery
But before I get into that, it's important to understand how Devices and Discovery fit together.

Devices are high-level objects defined by the Devices Profile for Web Services (DPWS).  DPWS details lots of different things devices can do: they can host services (e.g., a print service), they can participate in WS-Eventing, and they must use WS-Discovery.  Devices and their services are complicated objects that allow you to actually do something useful (like print a document).

Discovery is described in WS-Discovery, and is a low-level WS specification designed to allow clients to discover services.  WS-Discovery is much simpler than DPWS, and doesn't even have a notion of a Device.  In fact, Devices participate in WS-Discovery on behalf of all of the services on the device; so a DPWS Device is merely a service in the world of WS-Discovery, and it has a type of "dpws:Device."

Now that we've got that out of the way, let's dive into the scenarios.

Scenario 1: I want to build a (virtual) device
If you want to build a DPWS Device that announces itself using WS-Discovery, the only interface you need is IWSDDeviceHost.  The WSDAPI Device Host handles everything for you except for the logic inside services that you provide (e.g., that printer service I keep mentioning).  If you have an IWSDDeviceHost, you do not need an IWSDiscoveryPublisher (described below in scenario 4).

Scenario 2: I want to control a specific device
The scenario here is that you know the identity of a specific device and you want to send messages to it.  The device's identity is important: you need to know the Device ID; without getting into too much detail, this is typically either a UUID of the form "urn:uuid: [uuid goes here]"  or an HTTP address.  If you do know the identity, you want to use IWSDDeviceProxy directly.  The WSDAPI Device Proxy won't really perform any discovery for you--with the exception that it will automatically use WS-Discovery to turn UUIDs into an address it can send a request to.

If you don't know the device's identity that you want to use (which is pretty typical--you only know that you want to use a printer, right?) then you should read on through scenario 3.

Scenario 3: I want to enumerate devices on the subnet
This is one of the most common cases out there: you want to build an application that searches for devices and populates a UI for users to select, or you want to send commands to every device of a certain type on the local subnet.

It turns out that in this case, you should use Function Discovery (FD) to issue the search; FD will call into WSDAPI and does lots of things for you to ensure your application issues the search properly and applies the right rules to WS-Discovery traffic.  FD then returns the results to you in the form of individual IFunctionInstance(s).  From there, you can go back to scenario 2 to send messages to a specific device that you've just discovered.

Specifically, you want to CoCreateInstance IFunctionDiscovery and use CreateInstanceCollectionQuery to build a query for the devices you're interested in.  Hint: use FCTN_CATEGORY_WSDISCOVERY for pszCategory and add a constraint for PROVIDERWSD_QUERYCONSTRAINT_TYPE to search for a specific device type.  You can read all about FD+WSADPI on MSDN.

Now, advanced users may use the IWSDiscoveryProvider interface to perform WS-Discovery queries directly but this is not recommended in most cases because unlike the top-level WSDAPI interface (IWSDDeviceHost and IWSDDeviceProxy) and Function Discovery, IWSDiscoveryProvider is a raw API to the WS-Discovery traffic.  It's easy to build a non-compliant or inefficient WS-Discovery client by using IWSDiscoveryProvider, whereas all of the difficulty is managed for you in the higher-level interfaces.  IWSDiscoveryProvider and Function Discovery do essentially the same thing except that no knowledge of protocols or gnarly logic is required to use Function Discovery.

Scenario 4: I want to publish a (virtual) device with WS-Discovery
See Scenario 1 above, and use IWSDDeviceHost.  IWSDiscoveryPublisher is another advanced interface that is similarly not recommended in most cases if you want to build a full DPWS Device.  The discovery publisher is the raw service-side WS-Discovery counterpart to the discovery provider described in scenario 3.  If you want to advertise a WSDAPI Device, the IWSDDeviceHost handles this automatically.

So why are IWSDiscoveryProvider and IWSDiscoveryPublisher not recommended?
It turns out that in most cases, these two interfaces are either unnecessary, or don't provide the rich functionality and robustness that Function Discovery provides.  That said, if you really want direct access to WS-Discovery and understand how the protocol works, you absolutely should use these two interfaces to send and receive WS-Discovery messages, and the API is powerful enough for you to tweak all of the configurable content inside the WS-Discovery messages.

The cases where you should use IWSDiscoveryProvider and IWSDiscoveryPublisher
So if you've got an application that works exclusively at the WS-Discovery layer and doesn't involve a full DPWS Device, these two raw APIs are probably exactly what you're looking for.  In fact, they're the only WS-Discovery APIs Microsoft ships for unmanaged implementations, so if you're accessing WS-Discovery from inside a Windows svchost or a traditional C++ program, they're probably the way to go.  Alternatively, those of you using managed code may want to use Vipul's WS-Discovery sample for WCF.

The moral of this story is that unless you really want to get dirty with the WS-Discovery protocol, you should probably consider Function Discovery, IWSDDeviceProxy, and IWSDDeviceHost as they provide nearly the same functionality, but without requiring you to implement everything to make it happen.