Introducing WPD Automation

In Windows 7, we introduced WPD Automation, which is a layer above the WPD API that exposes access to common WPD functionality through IDispatch.

This post introduces this new component, and describes what you can and cannot do with WPD Automation, and how you, the developer, can access it from ActiveX and Device Stage™.

What WPD Automation Is

A simple way to access WPD from IDispatch/JScript

WPD provides a C++/COM API that allows you to access a portable device in a protocol-independent manner. Each interface represents a logical grouping of related functionality (e.g. IPortableDeviceProperties contains methods for retrieving and setting object properties). A WPD application would typically acquire and use multiple WPD interfaces to accomplish most tasks. For example, the application would use an IPortableDeviceProperties to get properties of any object on the device, and IPortableDeviceResources to read data from that same object, and so on.

WPD Automation seeks to simplify the programming model of WPD by wrapping the device and its contents as IDispatch objects which are then accessible from JScript. The complexity of WPD is hidden by employing an object-based model: each object is encapsulated by an IDispatch object; properties of that object are mapped to IDispatch properties; tasks on each object are available as IDispatch methods. More details on the WPD Automation Object Model are available on MSDN.

Let’s contrast the two APIs is with an example. The first code snippet demonstrates how to read some properties for an object on the device using the WPD API in C++:

 // Sample code for illustration purposes only, may contain typos.
HRESULT ReadPropertiesFromObject(
         PCWSTR   pszObjectID,
         IPortableDeviceProperties* pProperties)     
{
    // 1. Create an IPortableDeviceKeyCollection to hold the keys we wish to read
    IPortableDeviceKeyCollection* pPropertiesToRead;
    HRESULT hr =  CoCreateInstance(CLSID_PortableDeviceKeyCollection, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pPropertiesToRead));

    if (SUCCEEDED(hr))
    {
        // 2. Populate the IPortableDeviceKeyCollection.
        pPropertiesToRead ->Add(WPD_OBJECT_NAME);
        pPropertiesToRead ->Add(WPD_OBJECT_PERSISTENT_UNIQUE_ID);
        pPropertiesToRead ->Add(WPD_OBJECT_FORMAT);

        // 3. Request the properties from the device.
        hr =  pProperties ->GetValues(pszObjectID,  // The object whose properties we are reading
                                         pPropertiesToRead,   // The properties we want to read
                                         &pObjectProperties); // Result property values for the specified object
         
        // 4. Read the properties we just retrieved.
        if (SUCCEEDED(hr))
        {
            // Read the object name
            LPWSTR pszObjectName = NULL;
            hr = pObjectProperties->GetStringValue(WPD_OBJECT_NAME, &pszObjectName);
            CoTaskMemFree(pszObjectName);
 
            // Read the object format, e.g. WPD_OBEJCT_FORMAT_MP3
            GUID Format;
            hr = pObjectProperties->GetGuidValue(WPD_OBJECT_FORMAT, &Format); 
                 
            // and it goes on . . .
         }

Now, to accomplish the same task in WPD Automation, we call a few lines of JScript on an IDispatch object (wpdObject):

 function ReadPropertiesFromObject(wpdObject)
{
    var name = wpdObject.ObjectName;                 
    var puid = wpdObject.ObjectPersistentUniqueID;  
    var format = wpdObject.ObjectFormat; // “MP3”
    alert(“The format of “ + name + “ is “ + format);
}

As you can see, WPD Automation makes it very quick and easy to programmatically access the device.

A simple way to write custom code to access a device from Device Stage™

One requirement for bringing the power of the WPD API to Device Stage is to provide a way for device vendors (IHVs) to create custom WPD applications without writing, packaging, and delivering a complete Windows application to users. Device Stage integrates with an Internet Explorer™ web browser control that allows IHV-created web content to be viewed from the Device Stage UI. Using WPD Automation, IHVs can embed JScript into their web pages that can then access connected devices on the user’s Windows 7 PCs. These WPD Automation-enabled web pages are referenced by a special category of web tasks known as the HostedSiteWithDevice task.

For example, the following "Register your phone" sample task is a web page (hosted on an IHV’s server) that contains JScript client-side code to query the device properties (name, model, serial number, ...) of the user's cellular phone and populate the results in the "Product Information" section.

Sample HostedSiteWithDevice Task

You can find more details about the above example in the "Windows 7 Device Stage Portable Device Class Development Guide" from the Device Experience Development Kit.

What WPD Automation Isn't

Now that we've described what WPD Automation can do, let’s spend some time describing what it can't do.

Not a way to access WPD from Managed Code

In spite of the use of "Automation" in its name, WPD Automation is not a way to access WPD from managed code.  WPD Automation is designed for allowing a web application to programmatically interact with device functionality through script.

This does not mean that all is lost.  An application can still call the WPD API from managed code, using COM Interop.  Details are covered in earlier blog posts (C# and VB.NET).

Not a way to access WPD from non-JScript scripting languages

WPD Automation can be called from either JScript or Javascript. Other scripting languages (such as VBScript) are not supported.

Not a way to write a generic WPD application

One big difference between the WPD API and WPD Automation is the absence of support in WPD Automation to programmatically discover device capabilities (i.e. the supported properties, supported formats, supported commands, etc.). This was intentional for two reasons: 1) to optimize and simplify the programming model of WPD Automation; and 2) the target developer audience - IHVs writing custom applications only for their devices – know their device capabilities best, and can customize their scripts accordingly.

Not a way to access all functionality of the WPD API

WPD Automation supports accessing the storage and device service functionality on a portable device, including property management, object enumeration, object transfer, and invoking device service methods. WPD Automation does not support querying of capabilities (see above), bulk properties, updating secondary resources, and accessing functional objects other than services or storages (such as the rendering information or image capture functional objects).

Accessing WPD Automation

There are two ways to access WPD Automation: 1) from Device Stage and 2) from an ActiveX control. Both methods retrieve the Device Object, the entry point object for WPD Automation.

Using WPD Automation from Device Stage™

Device Stage integrates with WPD Automation by providing access to the Device Object through the window.external property.

 var deviceObject = window.external;

// Once the deviceObject is retrieved, we can proceed with using WPD Automation
var manufacturer = deviceObject.DeviceManufacturer;
var storages = deviceObject.Storages;

It is very important to note that the Device Stage secure scripting host imposes additional requirements on the definition of a HostedSiteWithDeviceTask. In particular, the webpage must be authenticated with an SSL certificate trusted by the client machine for the allowedDomain attribute specified in the task; otherwise accessing any WPD Automation properties and methods from script will result in "access denied" errors.

These requirements are covered in the "Windows 7 Device Stage Reference Guide" of the Device Experience Development Kit.

Using WPD Automation from an ActiveX™ Control

WPD Automation can also be instantiated outside of Device Stage by writing and registering an ActiveX object that contains a utility method that can be invoked in script return the Device Object. For more details on writing ActiveX objects, click here.

In the utility method:

  1. Call the WPD APIs to retrieve the device's Plug-and-play (PnP) identifier.
  2. CoCreate CLSID_PortableDeviceDispatchFactory and call the GetDeviceDispatch method, providing the PnP identifier, and retrieving an IDispatch object representing the Device Object.
  3. Return the Device Object in the method results.
 // Sample code for illustration purposes only, may contain typos.
// Returns the IDispatch object so that the device can be accessed from WPD Automation
IFACEMETHODIMP CMyDeviceFactory::GetMyDeviceObject(IDispatch** ppDevice)
{
    IPortableDeviceManager *pDeviceManager = NULL;

    HRESULT hr = CoCreateInstance(CLSID_PortableDeviceManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDeviceManager);
    if (SUCCEEDED(hr))
    {
       // To simplify this example, we are only looking for the first device encountered
       // (A real-life example may, for example, search all connected devices to find a match using device properties.)
       PWSTR pszPnPID = NULL;
       DWORD cPnPIDs = 1;
       hr = pDeviceManager->GetDevices(&pszPnPID, &cPnPIDs);

       if (SUCCEEDED(hr) &&  cPnPIDs == 1)
       {
           // CoCreate CLSID_PortableDeviceDispatchFactory
           IPortableDeviceDispatchFactory *pDispatchFactory = NULL;

           hr = CoCreateInstance(CLSID_PortableDeviceDispatchFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDispatchFactory);
           if(SUCCEEDED(hr))
           {
             // Pass the PnP identifier as a parameter to IPortableDeviceDispatchFactory::GetDeviceDispatch
             // to retrieve the Device Object’s as an IDispatch object.
             hr = pDispatchFactory->GetDeviceDispatch(pszPnPID, ppDevice); 
           }
       . . .

After installing and registering the ActiveX Control, you can now create a new ActiveX object in JScript and call the utility method to get to the Device Object.

 // Initialize our custom ActiveX control
var deviceFactory = new ActiveXObject("MyTestControl.MyDeviceFactory");
// Get the WPD Automation Device Object from our custom ActiveX control by calling GetMyDeviceObject.
var deviceObject = deviceFactory.GetMyDeviceObject();

// Once the deviceObject is retrieved, we can proceed with using WPD Automation
var manufacturer = deviceObject.DeviceManufacturer;
var storages = deviceObject.Storages;

References

The Device Experience Development Kit: https://www.microsoft.com/whdc/device/DeviceExperience/Dev-Kit.mspx

The WPD Automation Object Model: https://msdn.microsoft.com/en-us/library/dd389295(VS.85).aspx

Using IPortableDeviceDispatchFactory to instantiate WPD Automation: https://msdn.microsoft.com/en-us/library/dd389215(VS.85).aspx

 

This posting is provided "AS IS" with no warranties and confers no rights.