Blog Hero Image

Using WMDRM on your Application

Jun 20, 2012

Windows Media DRM (WMDRM) is a Digital Rights Management service for the Windows Media Platform, it’s used to manage multimedia files in a way that the distributor can control how that content is used. The content delivered with the WMDRM encryption is not universally accessible but limited to those users running Microsoft Windows.

This service supports portable media players running Media Transfer Protocol (MTP) that implement WMDRM-PD.

To create a Windows Portable Devices application, you must have the Windows Software Development Kit (SDK) installed on your computer.

The SDK provides the following interfaces for devices that support Windows Media Digital Rights Management 10 for Portable Devices (WMDRM-PD):

  • IWMDRMDeviceApp, which enables an application to meter, synchronize licenses, and update DRM components on the device
  • IWMDRMDeviceApp2, which extends IWMDRMDeviceApp and enables an application to query a specific DRM status or capability on the device.

The WMDRM API offers a sample application we can use to work and interact with portable devices. To build this application we need to complete the next steps:

  • Install the Microsoft Windows SDK for Windows Vista (or superior)
  • Install the Windows Media Format SDK version 11 (or greater)

The console application has the following files we can use to modify and customize according to what we want to achieve:

  • WpdApiDrmSample.vcproj: This is the main project file for VC++ projects generated using an Application Wizard. It contains information about the version of Visual C++ that generated the file, and information about the platforms, configurations, and project features selected with the Application Wizard.
  • WpdApiDrmSample.cpp: This is the main application source file.
  • DeviceEnumeration.cpp: This file contains examples of how to enumerate portable devices.
  • ContentEnumeration.cpp: This file contains examples of how to enumerate content on a portable device.
  • ContentTransfer.cpp: This file contains examples of how to transfer content to a portable device.
  • WmdmEnumeration.cpp: This file contains examples of how to initialize WMDM on a portable device that supports WMDM.
  • WmdrmDeviceApp.cpp: This file contains examples of how to call IWMDRMDeviceApp methods on a portable device.
  • WMDMProgress.h: This file contains an example implementation of IWMDMProgress3, used when updating the device's secure clock
  • AppKey.h, AppKey.cpp: These files define the application certificate and key used for protected content transfers. To authenticate your application, modify AppKey.cpp to set your application's private key and certificate values obtained from Windows Media Licensing.

Getting data form your device using WMDRM API

To initialize a WMDRM interface, the application calls CoCreateInstance to obtain an instance of IWMDRMDeviceApp or IWMDRMDeviceApp2 for the extended DRM queries, as shown in the following example:

hr = CoCreateInstance(CLSID_WMDRMDeviceApp,
    NULL,
    CLSCTX_INPROC_SERVER,
    IID_IWMDMDeviceApp2,
    (void**)ppWMDRMDeviceApp2);    
 
if (hr != S_OK)
{
     printf (“! Failed to CoCreate IWMDRMDeviceApp2\n”);
}

The next step is to obtain an IWMDMDevice instance to use as an input parameter for the IWMDRMDeviceApp and IWMDRMDeviceApp2 methods.

The process of deriving the WPD device instance is covered in detail by the WPD Sample Application in the Windows SDK and MSDN® documentation and will not be repeated here.

Each WPD device is identified by a unique Plug and Play Device Identifier (PnPDeviceID) string. Similarly, each WMDM device is also identified by a unique string known as the WMDM canonical name. If the two strings can be correlated for a given WPD MTP device, a matching IWMDMDevice instance can be retrieved from the list of enumerated WMDM devices.

To associate the PnPDeviceID string of a WPD device with its WMDM canonical name we have to follow the next steps:

1. Replace the device interface globally unique identifier (GUID) with the WMDM interface GUID for the current WPD PnPDeviceID.

//
// Step 1: Converting a WPD PnP Device ID to a WMDM PnP Device ID
// (Error checking is omitted for brevity)
//
 
hr = pIPortableDevice->GetPnPDeviceID(&pszPnPDeviceID);
 
CAtlStringW strWMDMPnPID = pszPnPDeviceID;
 
// Truncate the WPD interface GUID
int pos = strCurrentWMDMPnPID.ReverseFind(L’{‘);
strWMDMPnPID = strWMDMPnPID.Left(pos);
 
// Append the WMDM interface GUID
strWMDMPnPID += L”{f33fdc04-d1ac-4e8e-9a30-19bbd4b108ae}”;

2. Enumerate devices by using the WMDM API and get the canonical name for each. Convert the canonical name to a WMDM PnPDeviceID.

//
// Step 2: Enumerating the WMDM Canonical Names
// (Error checking is omitted for brevity)
//
 
hr = pWMDeviceManager2->EnumDevices2(&pWMDMEnumDevice);
if (hr == S_OK)
{
  IWMDMDevice* pWMDMDevice = NULL;
  ULONG        ulFetched   = 0;
 
  // Traverse the list of enumerated WMDM devices
 
  while(pWMDMEnumDevice->Next(1, &pWMDMDevice, &ulFetched) == S_OK)
  {
     WCHAR          wszCanonicalName[MAX_PATH] = {0};
     IWMDMDevice2*  pWMDMDevice2 = NULL;
 
     hr = pWMDMDevice->QueryInterface(IID_PPV_ARGS(&pWMDMDevice2));
 
     // Get the canonical name for the current WMDM device
 
     hr = pWMDMDevice2->GetCanonicalName(wszCanonicalName,
                   ARRAYSIZE(wszCanonicalName);
 
     CAtlStringW   strTestPnPID = wszCanonicalName;
 
     // Truncate the WMDM virtual device index
 
     int pos = strTestPnPID.ReverseFind(L’$’)
     strTestPnPID = strTestPnPID.Left(pos);
 
     //
     // Do Step 3 here …
     //
  }
}

3. Call IPortableDevice::Open on the WMDM PnPDeviceID obtained from enumerating the WMDM canonical names to get the PnPDeviceID.

Although the PnPDeviceID returned from IPortableDevice::GetPnPDeviceID is the same literal string as the original WMDM PnPDeviceID from Step 1, this test “closes the loop” by ensuring that both WPD and WMDM can access the same WMDM device identifiers.

//
// Step 3: Verify that the WMDM Device ID converted from the
// Canonical Name can be accessed by WPD
// (Error checking is omitted for brevity)
//
 
IPortableDevice* pPortableDeviceTest = NULL;
IPortableDeviceValues* pClientInfo   = NULL;
 
LPWSTR           pszWPDPnPDeviceID   = NULL;
 
//
// CoCreate a new instance of IPortableDevice (not shown) …
// CoCreate and populate client information (not shown) …
//
 
hr = pIPortableDeviceTest->Open(strTestPnPID, pClientInfo);
 
if (hr == S_OK)
{
    hr = pIPortableDeviceTest->GetPnPDeviceID(&pszWPDPnPDeviceID);
 
     // Compare the result with our WMDM PnP ID
 
     if (strWMDMPnPID.CompareNoCase(pszWPDPnPDeviceID) == 0)
     {
         // Found a matching IWMDMDevice instance
     }
}

4. Now that you have found the IWMDMDevice pointer that has the correct canonical name, you can use that IWMDMDevice as the input parameter to call IWMDRMDeviceApp or IWMDRMDeviceApp2 methods on that device.

For example, the following code snippet queries if the current device supports WMDRM:

hr = pWMDRMDeviceApp2->QueryDeviceStatus2(pWMDMDevice,
                      WMDRM_QUERY_DEVICE_ISWMDRM,&dwStatus);
 
if (hr == S_OK)
{
   bool bSupportsWMDRM = (dwStatus > 0)?true:false;
}

Latest insights