Creating a Windows Media Services Logging Plug-in

by Gregory Pettibone

Microsoft Corporation

Updated March 2005

Applies to:

   Windows Media Services 9 Series

Summary: This document discusses how to use the Microsoft® Windows Media® Services Software Development Kit (SDK), Microsoft Visual Studio® .NET, and the C# programming language to create a logging plug-in. Although this document describes how to create a logging plug-in, the information provided will be useful to those creating any type of custom plug-in for Windows Media Services.

Introduction

Most of the core features of Windows Media Services are provided by plug-ins. However, the plug-ins included with the server may not fit your requirements. Therefore, you can use the Windows Media Services SDK to create the following types of plug-ins:

  • Authentication plug-ins establish client identity. Windows Media Services supports Anonymous, Digest, NTLM, and Kerberos authentication types.
  • Authorization plug-ins control client access by granting or denying rights to receive content.
  • Cache proxy plug-ins enable you to store content locally and proxy it from other servers.
  • Data source plug-ins enable you to retrieve content from a source such as a network, a push distribution, or a file.
  • Event notification plug-ins handle event notices from clients and from the server.
  • Logging plug-ins enable you to log information about clients and the server.
  • Playlist parser plug-ins read playlists and create playlist objects in memory that the server uses to determine what content it must stream to a client and when to stream it.

This document discusses how to create a logging plug-in, but most of the concepts apply equally well to any custom plug-in. This document contains the following topics:

  • Custom Plug-in Basics. Provides a general overview of the requirements for creating custom plug-ins.
  • Creating a Project. Discusses how to create a C# class library and add a reference to the Windows Media Services primary interop assembly (PIA).
  • Implementing the IWMSBasicPlugin Interface. Discusses how to implement the fundamental interface used by all plug-ins. This includes methods that enable, disable, and initialize the plug-in.
  • Implementing the IWMSEventNotificationPlugin Interface. Discusses how to implement the specialized interface used by all event notification plug-ins. Logging plug-ins are a type of event notification plug-in and must implement this interface.
  • Registering Your Plug-in. Discusses how to register the CLSID of a plug-in in the system registry so that the plug-in can be recognized and used by Windows Media Services.
  • Using Your Plug-in. Discusses how to run your plug-in in the Windows Media Services management console.
  • For More Information. Identifies further resources that contain information about building custom plug-ins for Windows Media Services.

Custom Plug-in Basics

To create a plug-in, you must implement the IWMSBasicPlugin interface and any specialized interface that is dedicated to the particular type of plug-in you are creating. For example, to create a logging plug-in, you must implement the IWMSBasicPlugin interface and the IWMSEventNotificationPlugin interface (logging plug-ins are a specialized form of event notification plug-in). The server uses interfaces implemented by the plug-in to call into the plug-in. In some cases, the plug-in uses interfaces implemented by the server to call back into the server.

The server and plug-in send information to each other by using contexts. Most of the methods that you can use to create plug-ins include one or more parameters that point to contexts. Contexts contain name-value pairs that can be classified in the following manner:

  • The server context describes the global state of the server.
  • The user context describes a client connected to the server.
  • The presentation context describes content requested by a client.
  • The command context describes the requests issued by the client to the server and the response issued by the server to the client.
  • The content description context describes the content being streamed to the client.
  • The cache content information context contains information about a cache.
  • The archive context contains information about a file that has been saved to disk.

For example, a few of the values contained in the server context are identified in the following table.

Name Description
WMS_SERVER Contains an IUnknown pointer that you can use to retrieve a pointer to an IWMSServer interface.
WMS_SERVER_NAME Contains the server domain name.
WMS_SERVER_VERSION_BUILD Contains the build number of the installed version. The format is major.minor.minor-minor.build.

Before your plug-in can be recognized and used by Windows Media Services, it must be registered in the system registry. Depending on the type of plug-in you are creating, you must register its CLSID under the HKEY_CLASSES_ROOT key and under one of the following keys in the HKEY_LOCAL_MACHINE\Software\Microsoft\Windows Media\Server section of the registry:

  • RegisteredPlugins\Authentication
  • RegisteredPlugins\Cache Proxy
  • RegisteredPlugins\Data Source
  • RegisteredPlugins\Event Notification and Authorization
  • RegisteredPlugins\Network Source
  • RegisteredPlugins\Playlist Parser

Authorization and logging plug-ins are specialized types of event notification plug-ins, and their CLSIDs must appear under the RegisteredPlugins\Event Notification and Authorization subkey.

The remaining sections of this document describe the steps you must take to create a custom logging plug-in by using the C# programming language. The example code illustrates how to retrieve client statistics logged by the server, but leaves logging those statistics to a file or database as an exercise.

Creating a Project

To create an event notification plug-in by using the C# programming language, you must create a project and add a reference to the Windows Media Services PIA.

To create a project and reference to the Windows Media Services PIA:

  1. Start Microsoft Visual Studio .NET.

  2. On the File menu, click New, and select Project.

  3. Select Visual C# Projects in the New Projects dialog box. Select Class Library in the Templates pane and provide a name for your project. Click OK.

  4. In the Solution Explorer view, right-click the name of your project and click Add Reference.

  5. On the .NET tab of the Add Reference dialog box, select Microsoft.WindowsMediaServices, click Select, and click OK.

  6. Open the file that contains the class implementation that was generated by the wizard and add the following statements to the top of the file:

    using Microsoft.WindowsMediaServices.Interop;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
    

    The Microsoft.WindowsMediaServices.Interop namespace includes all of the methods and properties exposed by the Windows Media Services SDK. The System.Runtime.InteropServices namespace includes the GuidAttribute class that you can use to assign an explicit globally unique identifier (GUID) to your C# object. The Microsoft.Win32® namespace includes the RegistryKey class that you can use to create and delete keys in the system registry.

  7. Change the class and constructor name to something more user friendly than Class1, for example, LogPlugin:

    public class LogPlugin
    {
        public LogPlugin()
        {
          //
          // TODO: Add constructor logic here
          //
        }
    }
    
  8. All plug-ins used by the server are COM objects and require that their GUIDs be placed in the system registry. You can use the Guidgen.exe utility and the GuidAttribute class in System.Runtime.InteropServices to explicitly assign a GUID to your C# plug-in object. This is illustrated by the following example:

    [GuidAttribute("E37396E7-72DF-4f08-9216-29FDD58B5DC6")]
    public class LogPlugin
    {
    

At this point, you can implement the interfaces required for a logging plug-in. This is discussed in the following two sections.

Implementing the IWMSBasicPlugin Interface

To create a logging plug-in, you must implement the IWMSBasicPlugin interface and the IWMSEventNotification interface. The first step in C# is to derive your class from both interfaces, as follows:

Note   A logging plug-in is a specialized form of event notification plug-in. Therefore, you must implement the same interfaces for both plug-in types.

[GuidAttribute("E37396E7-72DF-4f08-9216-29FDD58B5DC6")]
public class LogPlugin : IWMSBasicPlugin, IWMSEventNotificationPlugin
{

Next, you must implement each of the methods in the IWMSBasicPlugin interface. The InitializePlugin method is called by the server when it loads your plug-in. You can use this method to perform whatever initialization of the logging plug-in is necessary. For example, you can retrieve the IWMSServer object from the context and store it for later use as illustrated by the following example:

void IWMSBasicPlugin.InitializePlugin(IWMSContext ServerContext,
                                      WMSNamedValues NamedValues,
                                      IWMSClassObject ClassFactory)
  {
    Type t = typeof(IWMSServer);
    Guid guid = t.GUID;
    Object m_Server;
    ServerContext.GetAndQueryIUnknownValue(WMSDefines.WMS_SERVER,
                                           WMSDefines.WMS_SERVER_ID,
                                           ref guid,
                                           out m_Server,
                                           0);
  }

The EnablePlugin method is called by the server to enable the plug-in. This method is called when an administrator enables a plug-in by using the Windows Media Services management console. The following example illustrates the minimum implementation required:

void IWMSBasicPlugin.EnablePlugin(ref int lFlags,
                                  ref int lHEartbeatPeriod)
{
}

The DisablePlugin method is called by the server to inhibit further operation of the plug-in. This method is called when an administrator disables a plug-in by using the Windows Media Services management console. The following example illustrates the minimum implementation required:

void IWMSBasicPlugin.DisablePlugin()
{
}

The ShutdownPlugin method is called before the server stops running. The following example illustrates the minimum implementation required:.

void IWMSBasicPlugin.ShutdownPlugin()
{
}

The GetCustomAdminInterface method is called by the server to retrieve the administration interface, if any, that you have created to enable an administrator to configure your plug-in through either the Windows Media Services MMC or the Web administration interface included with the server. The following example illustrates the minimum implementation for a plug-in that has no property pages:

object IWMSBasicPlugin.GetCustomAdminInterface()
{
  return 0;
}

The OnHeartBeat method is called by the server at the you defined by using the EnablePlugin method. The following example illustrates the minimum implementation:

void IWMSBasicPlugin.OnHeartbeat()
{
}

Implementing the IWMSEventNotificationPlugin Interface

The IWMSEventNotification interface contains two methods that you must implement to create a custom logging plug-in. The GetHandledEvents method is called by the server to retrieve an array of the event notices that your plug-in can handle. Typically, a logging plug-in handles only the WMS_EVENT_LOG or WMS_EVENT_REMOTE_CACHE_LOG event. The server raises the WMS_EVENT_LOG event when it logs client activity. It raises the WMS_EVENT_REMOTE_CACHE_LOG event for one of the following reasons:

  • A player has submitted a rendering log upstream to indicate that it has played a file from its local cache.
  • A cache proxy server has submitted a remote log upstream to indicate that it has streamed a file to a client from cache.
  • A cache proxy server has submitted a log upstream to indicate that it has proxied content from an upstream server to a downstream client.

The following example illustrates how to specify that your plug-in can handle the WMS_EVENT_LOG notice:

object IWMSEventNotificationPlugin.GetHandledEvents()
{
  int[] iHandledEvents = new int[1];
  iHandledEvents[0] = (int) WMS_EVENT_TYPE.WMS_EVENT_LOG;
  return(iHandledEvents);
}

After your plug-in registers to receive the WMS_EVENT_LOG event notice, the server calls the plug-in's OnEvent implementation whenever that event is raised.

The server writes client logging statistics to an internal buffer and puts a pointer to the buffer in the WMS_COMMAND_CONTEXT_BODY context value. The buffer contains XML in the following format:

<xml>
   <Summary>....</Summary>
   <c-ip>...</c-ip>
   <Date>...</Date>
   <Time>   </Time>
   <c-dns>...</c-dns>

   ...

</xml>

The <Summary> tag contains a single W3C-compliant, space-delimited string in which each item represents a field of logged data. The order of the fields in the summary string corresponds to the W3C fields identified by the remaining XML tags. The information in the <Summary> string can differ from that given for the remaining XML tags. The other XML tags contain the data actually sent by the client. However, in some cases that information is missing, and the server substitutes the appropriate values when creating the <Summary> string.

The following example implementation of the OnEvent method illustrates how to retrieve the <Summary> string. The example retrieves a pointer to the internal buffer from the command context, uses the pointer to retrieve a string containing all of the XML data logged by the server, and parses the XML data to isolate the W3C summary.

Note   Writing the log data to a file or a database is left as an exercise.

// Variables used in the OnEvent implementation.
static readonly string strSummaryOpenTag = "<Summary>";
static readonly string strSummaryCloseTag = "</Summary>";
string strSummary;

// The server calls OnEvent when it raises the WMS_EVENT_LOG event.
void IWMSEventNotificationPlugin.OnEvent(
                                         ref WMS_EVENT Event,
                                         IWMSContext UserCtx,
                                         IWMSContext PresentationCtx,
                                         IWMSCommandContext CommandCtx )
{
  try
    {
    // Retrieve the command request context.
    IWMSContext CmdRequest;
    CommandCtx.GetCommandRequest(out CmdRequest);

    // Use the command context to retrieve the command
    // context body.
    object ContextBody;
    CmdRequest.GetIUnknownValue(WMSDefines.WMS_COMMAND_CONTEXT_BODY,
                                WMSDefines.WMS_COMMAND_CONTEXT_BODY_ID,
                                out ContextBody,
                                0);

    // The logging statistics are stored in a buffer. Retrieve
    // a pointer to the buffer and use the pointer to retrieve
    // a string containing the statistics.
    INSSBuffer NSBuffer = (INSSBuffer)ContextBody;
    if( null == NSBuffer) return;
    uint bufSize;
    System.IntPtr pBuf = new IntPtr();
    NSBuffer.GetBufferAndLength(out pBuf, out bufSize );
    string sContext = Marshal.PtrToStringUni( pBuf, (int) bufSize / 2 );

    // Separate the <Summary>...</Summary> tag pair from the
    // rest of the XML string contained in the buffer.
    // Find the beginning index of the <Summary> tag.
    int iBegin = sContext.IndexOf(strSummaryOpenTag);
    if( -1 == iBegin )return;
    iBegin += strSummaryOpenTag.Length ;

    // Find the ending index of the </Summary> tag.
    int iEnd = sContext.IndexOf( strSummaryCloseTag );
    if( -1 == iEnd )return;

    if( iEnd > iBegin )
    {
      // Retrieve the W3C-compliant, space-delimited logging data
      strSummary = sContext.Substring( iBegin, iEnd - iBegin );

      // TODO: Parse the summary string and log the required
      // information to a file, a database, or some other destination.
    }
  }

  catch (COMException comExc)
  {
      // TODO: Handle COM exceptions.
  }
  catch(Exception e)
  {
      // TODO: Handle exceptions.
  }
}

Registering Your Plug-in

When you build your plug-in, Visual Studio .NET creates an assembly with a .dll file name extension. If you use the Regasm.exe utility to register type information from the assembly, the following entries are automatically added to the system registry:

HKEY_CLASSES_ROOT\progid
   (default) = progId

HKEY_CLASSES_ROOT\progid\CLSID
   (default) = clsid

HKEY_CLASSES_ROOT\CLSID\{clsid}
   (default) = progid

HKEY_CLASSES_ROOT\CLSID\{clsid}\InProcServer32
   (default) = %systemroot%\mscoree.dll
   Assembly = assembly name and version information
   Class = class name
   ThreadingModel = Both
   RuntimeVersion = version_of_the_runtime

HKEY_CLASSES_ROOT\CLSID\{clsid}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}

HKEY_CLASSES_ROOT\CLSID\{clsid}\ProgId
   (default) = progid

However, to enable Windows Media Services to recognize and use your plug-in, you must provide additional registry information. You can use the ComRegisterFunctionAttribute class to identify a registration function for Regasm.exe to call during the registration process. The following example creates subkeys in the HKEY_CLASSES_ROOT and HKEY_LOCAL_MACHINE keys to add registry information needed by the server:

// Variables used by the registration function.
static readonly string sCLSID = "E37396E7-72DF-4f08-9216-29FDD58B5DC6";
static readonly string sSubKey1 = "SOFTWARE\\Microsoft\\Windows Media" +
                            "\\Server\\RegisteredPlugins" +
                            "\\Event Notification and Authorization" +
                            "\\{" + sCLSID + "}";
static readonly string sSubKey2 = "CLSID\\{" + sCLSID + "}\\Properties";

// Register the plug-in.
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
  try
  {
    RegistryKey regHKLM = Registry.LocalMachine;
    regHKLM = regHKLM.CreateSubKey(sSubKey1);
    regHKLM.SetValue(null, "Sample C# Logging Plug-in");

    RegistryKey regHKCR = Registry.ClassesRoot;
    regHKCR = regHKCR.CreateSubKey(sSubKey2);

    // Specify properties.
    regHKCR.SetValue("Name", "Sample CSLogging Plug-in");
    regHKCR.SetValue("CopyRight", "Copyright 2002 . All rights reserved");
    regHKCR.SetValue("Description", "Logs client and server events");
    regHKCR.SetValue("SubCategory", "Logging");
    regHKCR.SetValue("UnsupportedLoadTypes ", "2");
  }
  catch (COMException comExc)
  {
      // TODO: Handle COM exceptions.
  }
  catch(Exception e)
  {
      // TODO: Handle exceptions.
  }
}

The preceding example also illustrates that you can add plug-in properties under a Properties subkey of the HKEY_CLASSES_ROOT\CLSID key. These include name, copyright, and subcategory information. For authorization, logging, and playlist parser plug-ins, the subcategory information identifies the node under which the name of your plug-in will appear in the Windows Media Services management console. For a logging plug-in, set the subcategory to Logging.

It is recommended that you use the ComUnRegisterFuntionAttribute class to identify a registration function for Regasm.exe to call when registry entries are removed. This is illustrated by the following example:

// Unregister the plug-in.
[ComUnregisterFunctionAttribute]
public static void UnRegisterFunction(Type t)
{
  try
  {
  RegistryKey regHKLM = Registry.LocalMachine;
  regHKLM.DeleteSubKey(sSubKey1);

  RegistryKey regHKCR = Registry.ClassesRoot;
  regHKCR.DeleteSubKeyTree(sSubKey2);
  }
  catch (COMException comExc)
  {
      // TODO: Handle COM exceptions.
  }
  catch(Exception e)
  {
      // TODO: Handle exceptions.
  }
}

You can register the assembly manually or by using Visual Studio. To register the assembly manually, copy it to <%systemroot%>\system32\windows media\server, and run the Regasm.exe utility to register it and create a type library:

    regasm CSLoggingPlugin.dll /tlb

To register the assembly by using Visual Studio, in the Solution Explorer, right-click the name of the assembly and click Properties. In the property pages dialog box, click Configuration Properties and click Build. Change the Register for COM Interop property to true. This process automatically configures the system registry during the build process.

Using Your Plug-in

To use your custom logging plug-in, perform the following steps:

  1. Build your plug-in by using Visual Studio .NET.
  2. Register the plug-in either manually or automatically (during the build process).
  3. Refresh your Windows Media server so that it can recognize the plug-in.
  4. Enable the plug-in at either the server level or publishing point level by right-clicking the plug-in name and clicking Enable.
  5. Start Windows Media Player and play an item from your server.

When the player has finished playing the content, it sends a log to the server. The server raises the WMS_EVENT_LOG event notice and calls the IWMSEventNotificationPlugin.OnEvent method implemented by your logging plug-in. You can either set breakpoints in your plug-in to view the process, or finish implementing the sample discussed in this document so that it sends client logging information to a file or database.

For More Information

To learn more about creating custom plug-ins, see the Windows Media Services SDK Help on MSDN® Online.

The Windows Media Services SDK is included in the Platform SDK, which can be downloaded from the Microsoft Web site.

The information contained in this document represents the current view of Microsoft Corporation on the issues discussed as of the date of publication. Because Microsoft must respond to changing market conditions, it should not be interpreted to be a commitment on the part of Microsoft, and Microsoft cannot guarantee the accuracy of any information presented after the date of publication.

This White Paper is for informational purposes only. MICROSOFT MAKES NO WARRANTIES, EXPRESS OR IMPLIED, AS TO THE INFORMATION IN THIS DOCUMENT.

Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

© 2002 Microsoft Corporation. All rights reserved.

Microsoft, MS-DOS, Windows, Windows Media, Windows NT, ActiveSync, ActiveX, Direct3D, DirectDraw, DirectInput, DirectMusic, DirectPlay, DirectShow, DirectSound, DirectX, FrontPage, JScript, Microsoft Press, MSN, NetShow, Outlook, PowerPoint, Visual Basic, Visual C++, Visual InterDev, Visual J++, Visual Studio, WebTV, Win32, and Win32s are either registered trademarks or trademarks of Microsoft Corporation in the U.S.A. and/or other countries.

The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

Portions, Copyright © 1994-2000 World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. https://www.w3.org/