Setting WPD properties

The IPortableDeviceProperties::SetValues API can be used to set WPD object properties. The documentation covers the API parameters as well, so we'll skip to the meatier sample.

The SetValues API can be used to set multiple properties at the same time. You can, of course, also just set one property at a time. For illustration, we'll set two properties in one go. We'll pick the WPD_DEVICE_FRIENDLY_NAME and WPD_DEVICE_SYNC_PARTNER properties on the DEVICE object.

 HRESULT UpdateDeviceProperties(IPortableDevice* pDevice,
                               LPCWSTR pwszFriendlyName,
                               LPCWSTR pwszSyncPartner)
{
    HRESULT hr = S_OK;

      // Get the Properties interface through the Content interface 
    CComPtr<IPortableDeviceContent> spContent;
    if (hr == S_OK)
    {
        hr = pDevice->Content(&spContent);
    }

    CComPtr<IPortableDeviceProperties> spProperties;
    if (hr == S_OK)
    {
        hr = spContent->Properties(&spProperties);
    }

    // Create an IPortableDeviceValues instance to place the new values into
    CComPtr<IPortableDeviceValues> spValues;
    if (hr == S_OK)
    {
        hr = CoCreateInstance(CLSID_PortableDeviceValues,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IPortableDeviceValues,
                              (VOID**)&spValues);
    }

    // Add the new device friendly name property value to the collection
    if (hr == S_OK)
    {
        hr = spValues->SetStringValue(WPD_DEVICE_FRIENDLY_NAME, pwszFriendlyName);        
    }

    // Add the new sync partner property value to the collection
    if (hr == S_OK)
    {
        hr = spValues->SetStringValue(WPD_DEVICE_SYNC_PARTNER, pwszSyncPartner);        
    }

    // Update the properties on the DEVICE object
    CComPtr<IPortableDeviceValues> spResults;
    if (hr == S_OK)
    {
        hr = spProperties->SetValues(L"DEVICE", spValues, &spResults);
        printf("SetValues returned hr=0x%08X\n", hr);
    }

    // Display the results of the operation
    if (SUCCEEDED(hr) && spResults != NULL)
    {
        HRESULT hrTemp = S_OK;
        HRESULT hrSetValue = S_OK;

        hrTemp = spResults->GetErrorValue(WPD_DEVICE_FRIENDLY_NAME, &hrSetValue);
        if (hrTemp == S_OK)
        {
            printf("\tSetting WPD_DEVICE_FRIENDLY_NAME returned hr=0x%08X\n", hrSetValue);
        }

        hrTemp = spResults->GetErrorValue(WPD_DEVICE_SYNC_PARTNER, &hrSetValue);
        if (hrTemp == S_OK)
        {
            printf("\tSetting WPD_DEVICE_SYNC_PARTNER returned hr=0x%08X\n", hrSetValue);
        }        
    }

    return hr;
}

We first obtain the IPortableDeviceProperties interface needed to execute the SetValues API. Next we have to create an IPortableDeviceValues instance to stuff the new values into. If you recall from the earlier post, an IPortableDeviceValues interface is just a map of property-keys and values.

Once we have our collection of values to set ready, we can use the SetValues API to send them to the driver for processing. The driver returns S_OK if all of the supplied values could be set. If any of the values could not be set, then the driver returns S_FALSE. S_FALSE does not indicate a catastrophic failure. It means that the client should check the returned IPortableDeviceValues results collection for more information.

Retrieving the result for each property is fairly easy since we use the standard methods exposed by IPortableDeviceValues. The results collection is always a collection of VT_ERRORs, so we can safely call GetErrorValue for any of the keys we are interested in retrieving the error for. If you have a large number of properties being set, you should use IPortableDeviceValues::GetAt in a loop to iterate through the results collection instead of retrieving the error values one at a time.

Setting property values for other objects follows the same pattern - just use the appropriate object ID parameter for the SetValues call.