Where are the WPD property keys in C#?

If you followed the exercise from our first C# post, you must have noticed that the PortableDeviceApi and PortableDeviceTypes typelibs don't expose the WPD property keys such as WPD_OBJECT_ID, WPD_OBJECT_FORMAT, etc. This can be a major blocker since for starters, we need to specify a basic set of client information properties to open a connection to the device.

If you used C++, the WPD property keys definitions could be brought in by linking against PortableDeviceGuids.lib. Linking against a C lib is not an option for a C# application. It's not even possible to generate a typelib for a C lib. The only option is to manually define each of the property keys all over again in your C# application.

The C++ PROPERTYKEY data type maps to the PortableDeviceApiLib._tagpropertykey interop data type. We can look up the PROPERTYKEY declaration in PortableDevice.h and use that to generate an equivalent C# property key object.

Let's take a look at WPD_OBJECT_ID - the C++ definition (from PortableDevice.h) is:

 // 
// WPD_OBJECT_ID 
//   [ VT_LPWSTR ] Uniquely identifies object on the Portable Device. 
DEFINE_PROPERTYKEY( WPD_OBJECT_ID , 0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C , 2 ); 

Mapping this to C# would result in something like:

 class PortableDevicePKeys
{
    static PortableDevicePKeys()
    {
        WPD_OBJECT_ID.fmtid = new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
        WPD_OBJECT_ID.pid = 2;
    }

    public static PortableDeviceApiLib._tagpropertykey WPD_OBJECT_ID;
}

We can then reference the property as PortableDevicePKeys.WPD_OBJECT_ID whenever required.

Making life easier

Since defining each of these property keys by hand may cause severe RSI, here's a little script that will generate the definitions for you given PortableDevice.h. In addition to generating the property keys, it will also generate the GUID definitions (such as for WPD_FUNCTIONAL_CATEGORY_STORAGE, WPD_OBJECT_FORMAT_WMA, etc.)

 //
// Name: genCSinc.js
// Copyright: Microsoft 2006
// Revision: 1.0
//
// This script can be used to generate a C# .cs file that contains
// the equivalent WPD property-keys and GUIDs as defined in
// portabledevice.h
// This script is provided as-is and Microsoft does not assume any
// liability. This script may be redistributed as long as the file
// contains these terms of use unmodified.
//
// Usage:
// Switch to the folder where portabledevice.h is present and then run
//     cscript //nologo genCSinc.js
// This will generate portabledeviceconstants.cs. This .cs file can
// then be included in a C# project. 
// To use the propertykeys and the GUIDs, add "using PortableDeviceConstants;"
// to the target file.
// Propertykeys can then be referenced as PortableDevicePKeys.desired_prop_name
//     e.g. PortableDevicePKeys.WPD_PROPERTY_COMMON_HRESULT
// GUIDs can be referenced as PortableDeviceGuids.desired_GUID_name
//     e.g. PortableDeviceGuids.WPD_OBJECT_FORMAT_ALL
//

//
// Use FSO to read/write input/output
//
var fso = new ActiveXObject("Scripting.FileSystemObject");

var f = null;
var fOut = null;
var sIn = "portabledevice.h";
var sOut = "portabledeviceconstants.cs";

//
// Check for input file
//
try
{
    f = fso.OpenTextFile(sIn);
}
catch(e)
{
    WScript.Echo("Expected portabledevice.h to be in the current folder!");
}

//
// Check for output file
//
try
{
    fOut = fso.OpenTextFile(sOut, 2, true);
}
catch(e)
{
    WScript.Echo("Cannot open " + sOut + " for writing!");
}

//
// Write out header
//
fOut.Write("\
using System;\r\n\
\r\n\
namespace PortableDeviceConstants\r\n\
{\r\n\
    class PortableDevicePKeys\r\n\
    {\r\n\
        static PortableDevicePKeys()\r\n\
        {\r\n\
");    

//
// RegEx declarations for PKEYs and GUIDs
//
//e.g. DEFINE_PROPERTYKEY( WPD_CLIENT_MINOR_VERSION , 0x204D9F0C, 0x2292, 0x4080, 0x9F, 0x42, 0x40, 0x66, 0x4E, 0x70, 0xF8, 0x59 , 4 ); 
var rePKEY = /\s*DEFINE_PROPERTYKEY\(\s*(\w+)\s*,\s*(.+)\s*,\s*(\d+)\s*\);/;
var arrPKEY = new Array();

//e.g. DEFINE_GUID(WPD_EVENT_DEVICE_RESET, 0x7755CF53, 0xC1ED, 0x44F3, 0xB5, 0xA2, 0x45, 0x1E, 0x2C, 0x37, 0x6B, 0x27 ); 
var reGUID = /\s*DEFINE_GUID\((\w+),\s(.+)\s\);/;
var arrGUID = new Array();

//
// Parse the file
//
while (!f.AtEndOfStream)
{
    var l = f.ReadLine();

    //
    // Check for PKEYs
    //
    if (l.match(rePKEY))
    {
        //
        // Write out initializations for the PKEYs
        //
        var sName = l.replace(rePKEY, "$1");
        var sGUID = l.replace(rePKEY, "$2");
        var sPID = l.replace(rePKEY, "$3");

        //WPD_CLIENT_NAME.fmtid = new Guid(0x204D9F0C, 0x2292, 0x4080, 0x9F, 0x42, 0x40, 0x66, 0x4E, 0x70, 0xF8, 0x59);
        //WPD_CLIENT_NAME.pid = 2; 
        
        fOut.Write("            " + sName + ".fmtid = new Guid( " + sGUID + ");\r\n");
        fOut.Write("            " + sName + ".pid = " + sPID + ";\r\n");
        fOut.Write("\r\n");

        //
        // Save the PKEY name for declaration at class level
        //
        arrPKEY.push(sName);
    }
    else if (l.match(reGUID))
    {
        //
        // Save the GUIDs since they go into a second class
        //
        var sName = l.replace(reGUID, "$1");
        var sGUID = l.replace(reGUID, "$2");

        arrGUID.push("public static Guid " + sName + " = new Guid( " + sGUID + " );");
    }
}

//
// Write out declarations for PKEYs
//
fOut.Write("        }\r\n\r\n");

for (var i = 0; i < arrPKEY.length; i++)
{
    fOut.Write("        " + "public static PortableDeviceApiLib._tagpropertykey " + arrPKEY[i] + ";\r\n");
}

fOut.Write("\
    } // class PortableDevicePKeys\r\n\
");

//
// Write out GUIDs
//
fOut.Write("\r\n\r\n");
fOut.Write("\
    class PortableDeviceGuids\r\n\
    {\r\n\
");

for (var i = 0; i < arrGUID.length; i++)
{
    fOut.Write("        " + arrGUID[i] + "\r\n");
}

//
// Write out footer
//
fOut.Write("\
    } // class PortableDeviceGuids\r\n\
} // namespace PortableDeviceConstants\r\n\
");

WScript.Echo("Done: " + sOut + " now contains C# WPD constants");

Copy the script from the text-box above, paste it into Notepad and save it as gencsinc.js. Copy PortableDevice.h to the same folder as gencsinc.js and then run gencsinc.js using "cscript gencsinc.js". This will generate a PortableDeviceConstants.cs file which you may add to your C# project.

Once the generated file is added to your project, add "using PortableDeviceConstants; " to your target C# source. To reference property keys, you can simply use PortableDevicePKeys.propertyname (e.g. PortableDevicePKeys.WPD_OBJECT_ID) and to reference GUIDs, you can simply use PortableDeviceGuids.guidname (e.g. PortableDeviceGuids.WPD_OBJECT_FORMAT_WMA).