Extending Cluster Administrator in Windows Server 2003

 

Mohan Rao Cavale
Microsoft Corporation

January, 2003

Applies to:
   Windows® Server 2003
   Windows® Server 2003, Enterprise Edition
   Microsoft Visual Studio®

Summary: Demonstrates how to effectively extend the Windows Server 2003, Enterprise Edition Cluster Administrator user interface to include application management functionality. (23 printed pages.)

Contents

IntroductionBarriers to Manageability
.NET Cluster Service Architecture
Cluster Administrator
Cluster Administrator Extension DLL
Conclusion

Introduction

Too often, high availability and scalability result in the loss of application manageability. Employing cluster technology to achieve reliable and scalable solutions requires managing applications that may, or may not, be running within a cluster. Frequently, this results in managing applications with different means—running in a cluster and running outside of a cluster. The latest release of clustering technology in Windows Server 2003, Enterprise Edition, moves toward the goal of allowing developers to manage their applications seamlessly; whether running inside or outside of a cluster.

Microsoft Cluster Server (MSCS) provides a cluster management tool, Cluster Administrator, to configure, control, and monitor clusters. Cluster Administrator allows administrators to manage clusters, establish, initiate, failover, handle maintenance, and monitor cluster activity through a convenient graphical interface. Rather than requiring administrators to manage the cluster with Cluster Administrator, and manage applications with their own separate tools, an interface has been created for developers to extend Cluster Administrator. By implementing extension DLLs, developers can extend the functionality of Cluster Administrator to include specific application management features, allowing their application to be managed from within Cluster Administrator.

Barriers to Manageability

If an application uses APIs that behave differently when called from within a cluster than they do when called outside of a cluster, then it needs to be managed differently inside of a cluster. Consider a file share. When running outside of a cluster, file shares can be created from the command line (net share) or from Windows Explorer. Permissions, security, and so forth can be set. However, if changes are made to a file share in a clustered environment using Windows Explorer, when the application moves to a different node, those changes are lost. Instead, if the application is in a cluster, a file share must be created and changed using the cluster file share resource type—which is different than when running outside of a cluster.

The ideal solution is to make all relevant APIs, including file management, cluster-aware. Then, if a user executes a net share command from the command line, the Net Share API detects whether it is running in a cluster. If it is, the API checks for the existence of a file share resource. If a file share resource is not present, the Net Share API creates one. Likewise, a change to the file share from Windows Explorer also detects the presence of a cluster, and uses the file share resource, if appropriate.

.NET Cluster Service Architecture

Microsoft Cluster Server is comprised of three key components: the Cluster Service, the Resource Monitor, and Resource DLLs.

The Cluster Service is the core component of the .NET Cluster Service and runs as a high-priority system service. The Resource Monitor is an interface between the Cluster Service and the cluster resources, and runs as an independent process. The Resource Monitor and Resource DLL communicate using the Resource API, which is a collection of entry points, callback functions, and related structures and macros used to manage resources.

Figure 1. Cluster Service Flowchart

Cluster Administrator

The Cluster Administrator is the central point for managing clusters. It provides a graphical interface that allows administrators to manage cluster objects, handle maintenance, and monitor cluster activity.

Figure 2. Cluster Administrator

Cluster Administrator Extension DLL

A Cluster Administration Extension DLL provides application-specific management features from within the Cluster Administrator, thus allowing users to manage their applications in the same manner whether the application is running from within, or outside of, a cluster. Developers can provide application management features within the Cluster Administrator framework, or simply link to their existing management tool.

Developers extend the functionality of Cluster Administrator by writing an extension DLL. The Cluster Administrator application communicates with extension DLLs through a defined set of COM interfaces. Extension DLLs must implement a specific set of interfaces and be registered on each node of the cluster.

The Cluster Administrator extension functions allow pages to be added to property sheets and wizards, as well as add items to context menus for any type of cluster object in the Cluster Administrator user interface. This allows the implementation of configuration and management procedures in an environment that is familiar to most administrators.

Note Cluster-unaware applications are those that do not provide their own resource DLLs. They can still be configured into the cluster environment—.NET Cluster Service includes a generic resource DLL for this purpose—however, they cannot extend the Cluster Administrator.

Creating an Extension DLL

The easiest way to build a Cluster Administrator extension DLL for a given resource type is to run the Resource Type AppWizard from Visual Studio 6.0. When creating a new resource type resource type, the Cluster Resource Type Wizard automatically generates the code necessary to compile a fully functional extension DLL. It is not necessary to modify the generated code in any way to produce an extension DLL that allows administrators to manage the resource type through Cluster Administrator Cluster Administrator.

Figure 3. Resource Type AppWizard

The Cluster Resource Type Wizard prompts the user for application-specific properties during the resource type creation. Properties can be of type string, BOOL, DWORD, long, or binary.

Figure 4. Cluster Resource Type Wizard

Properties entered on this page display when looking at the properties for any resource of that type in Cluster Administrator. On the Parameters tab, the properties are available for reading and modifying. If properties were not entered while running the Cluster Resource Type AppWizard, then the Parameters tab will not be available when looking at the properties for a resource.

Figure 5. App Resource Properties

How is the Parameters tab constructed? The AppWizard-generated skeleton for the Cluster Extension DLL always includes a property page to display any resource type properties. If the user enters properties while running the AppWizard, then that page is modified to display them according to their type and the page is added to the property sheet for that resource type. However, even if no properties are entered during the AppWizard, the property page is still generated, but it is never added to the property sheet collection for display.

With all of this background work being done, is there a need to further extend it? Code produced by the Cluster Resource Type Wizard should be changed under the following circumstances:

  • To add functionality to the default extension DLL generated for a resource type. Perhaps there are other application management features that cannot be described as a single property, but should be displayed within the Cluster Administrator property sheet.
  • To extend an object other than a resource type (for example, a Group).

The following demonstrates how to modify this code to add additional property pages and items to context-sensitive menus to an extension DLL.

Customizing Your Cluster Administrator Extension DLL

Once the skeleton is generated, use the defined COM interfaces and methods to customize the behavior. The following example is based upon the ClipSrvEx sample shipped with the Platform SDK. The code is modified to provide both property pages and context-sensitive menus for the ClipBook Server resource type.

In order to modify any AppWizard generated extension DLL, it is critical to become familiar with the central class, CExtObject. CExtObject is an ATL class that implements the necessary interfaces to support the extension of Cluster Administrator. As generated for any resource type, CExtObject already contains the necessary code to support property pages and wizard pages. The following section in the header file for CExtObject shows the interfaces that it supports.

class CExtObject :
   public IWEExtendPropertySheet,
   public IWEExtendWizard,
   public ISupportErrorInfo,
   public CComObjectRoot,
   public CComCoClass< CExtObject, &CLSID_CoClipSrvEx >
{
public:
   CExtObject( void );
BEGIN_COM_MAP( CExtObject )
   COM_INTERFACE_ENTRY_IID( IID_IWEExtendPropertySheet, IWEExtendPropertySheet )
   COM_INTERFACE_ENTRY_IID( IID_IWEExtendWizard, IWEExtendWizard )
   COM_INTERFACE_ENTRY_IID( IID_ISupportErrorInfo, ISupportErrorInfo )
END_COM_MAP()

The IWEExtendPropertySheet allows the creation of property sheet pages and adds them to the Cluster Administrator user interface. It has one method, CreatePropertySheetPages, which is required for implementation by the extension DLL. This method is called by the Cluster Administrator whenever the user requests to view the properties for resources, thus allowing the ability to add tabs to the property sheet.

public:
   STDMETHOD( CreatePropertySheetPages )(
               IN IUnknown *               piData,
               IN IWCPropertySheetCallback *   piCallback
               );

The first parameter, piData, is a pointer to an interface that allows the retrieval of information to create the new property pages. By calling IUnknown::QueryInterface with the piData pointer, the following interfaces are available:

  • IGetClusterUIInfo
  • IGetClusterDataInfo
  • IGetClusterObjectInfo

IGetClusterUIInfo is used to retrieve information about Cluster Administrator's user interface, to be utilized for displaying property pages. The interface allows the retrieval of the name of the cluster, the local identifier, a handle to the font used, and a handle to an icon used in the upper-left corner.

IGetClusterUIInfo *   _pi;

_hr = piData->QueryInterface( IID_IGetClusterUIInfo, 
                           reinterpret_cast< LPVOID * >( &_pi ) );
if ( _hr == NOERROR )
{
   m_lcid = _pi->GetLocale();
   m_hfont = _pi->GetFont();
   m_hicon = _pi->GetIcon();
   _pi->Release();
}

IGetClusterDataInfo is used by the extension DLL to retrieve information about a cluster. The interface allows the retrieval of the name of the cluster, a handle to the cluster, and the number of selected objects (selected in the Cluster Administrator user interface).

IGetClusterDataInfo *   _pi;

_hr = piData->QueryInterface( IID_IGetClusterDataInfo, 
                           reinterpret_cast< LPVOID * >( &_pi ) );
if ( _hr == NOERROR )
{
   m_hcluster = _pi->GetClusterHandle();
   m_cobj = _pi->GetObjectCount();
   _pi->Release();
}

IGetClusterObjectInfo is used to retrieve information about a cluster object. The interface allows the retrieval of a cluster object’s name and type.

_hr = PiData()->QueryInterface( IID_IGetClusterObjectInfo, 
                           reinterpret_cast< LPVOID * >( &_piGcoi ) );

// Read the object data.
try
{
   // Get the type of the object.
   _cot = _piGcoi->GetObjectType( 0 );
   switch ( _cot )
   {
   case CLUADMEX_OT_RESOURCE:
      {
         // Action specific to objects of type resource
      }
      break;
      default:
         _hr = E_NOTIMPL;
         throw &_exc;
   } 
   // Get the name of the object
   _hr = HrGetObjectName( _piGcoi );
} 
catch ( CException * _pe )
{
   _pe->Delete();
} 
_piGcoi->Release();

The second parameter, piCallback, is the pointer to the interface that has the method that must be called to add a properly constructed property page to the Cluster Administrator.

IWEExtendWizard allows the creation of custom Cluster Administrator wizard pages. Wizard pages are used whenever the user selects Configure Application from the File Menu or a context menu. Wizard pages are also used whenever the user creates a new resource from the Cluster Administrator. Adding wizard pages allows the prompting of the user for specific information as they configure an application or create a resource using the application's custom resource type. IWEExtendWizard has one method, CreateWizardPages.

public:
   STDMETHOD( CreateWizardPages )(
               IN IUnknown *         piData,
               IN IWCWizardCallback *   piCallback
               );

Identical to the IWEExtendPropertySheet, the first parameter, piData, is a pointer to an interface that allows the retrieval of information necessary to create the new property pages.

The second parameter, piCallback, is the pointer to the interface that has the method that must be called to add a properly constructed wizard page to the Cluster Administrator.

An extension DLL has the opportunity to add property pages, wizard pages, or context menu items for resources, resource types, or groups. A single extension DLL can extend one, or more than one, type of object. For example, an extension DLL could add an item to the context menu for a resource, or a property page for a resource type. Because of this, it is important for the extension DLL, at the time it is called, to be able to determine the object for which the interface is being extended. CExtObject contains several member variables and methods to facilitate capturing information about the cluster object for which the Administrator interface is being extended.

The member variable m_podObjData is of type CObjData, which serves as a base class for several object-specific classes that contain information specific to the cluster object being extended.

class CObjData
{
public:
   CString               m_strName;
   CLUADMEX_OBJECT_TYPE   m_cot;

   virtual ~CObjData(void) { }
};  

All objects have a name and an enumerated type. Derived classes add more specific information, as appropriate. For example, when the extension DLL is called for a resource, the information captured includes a handle to the resource and its resource type name. For a group, however, only a handle to the group object is recorded.

class CGroupData : public CObjData
{
public:
   HGROUP      m_hgroup;

};

class CResData : public CObjData
{
public:
   HRESOURCE   m_hresource;
   CString   m_strResTypeName;

};

Capturing the information about the cluster object is performed in a set of utility functions contained in the class.

HRESULTHrGetUIInfo( IN IUnknown * piData );

HRESULTHrSaveData( IN IUnknown * piData );

HRESULTHrGetObjectInfo( void );

HRESULTHrGetObjectName( IN IGetClusterObjectInfo * pi );

HRESULTHrGetResourceTypeName( IN IGetClusterResourceInfo * pi );

These methods query the Cluster (using the interfaces available on the piData parameter) for the type of object being extended, and, based upon the object type, uses the appropriate interface to gather object-specific information.

Adding a Property Page

To add a property page to the cluster extension DLL, start by creating a new dialog in the Visual Studio Resource Editor. The wizard has already created two dialog resources, IDD_PP_CLIPSRV_PARAMETERS for the Property page, and IDD_WIZ_CLIPSRV_PARAMETERS for the Wizard page. The easiest way to create a new Property page of the correct size is to create a copy of the IDD_PP_CLIPSRV_PARAMETERS resource and make the modifications to that copy.

Figure 6. CLIPSRVEX Resources in Visual Studio Resource Editor Window

To create a class for the new property page, right-click on the Dialog resource and then select Class Wizard. Once the Class Wizard displays the list of files to include, click OK. When prompted, select the Create a New Class Option, and then click OK.

Figure 7. Adding a Class Dialog Box

In the Class information area, select the Name text box and then type the desired class name. Next, select the Base class text box, type CPropertyPage, and then click OK.

Figure 8. New Class Dialog Box

When the Class Wizard finishes generating the code, edit the .cpp and .h files for the new class, changing its base class from CPropertyPage to CBasePropertyPage. CBasePropertyPage is generated by the Cluster Resource Type AppWizard, and provides the base functionality for extension DLL property pages.

The new property page class is now ready for adding application-specific management functionality. Once that is completed, the only thing left to do is to add the page to the property sheet. Each time the user chooses to view the properties for any resource of the application's resource types, the Cluster Administrator calls the implementation of CreatePropertySheetPages. CreatePropertySheetPages allows the opportunity to add property pages to the property sheet. Do so by calling AddPropertySheetPage for each property page to be viewed.

Note   The Class Wizard has already created a fully functional version of CreatePropertySheetPages. For objects of type resource, the implementation iterates through an internal collection of property pages (rgpprtcResPSPages), and calls AddPropertyPage for each element.

switch ( PodObjData()->m_cot )
{
   case CLUADMEX_OT_RESOURCE:
      _pprtc = g_rgpprtcResPSPages[ IstrResTypeName() ];
      break;
   default:
      _hr = E_NOTIMPL;
      throw &_exc;
      break;
} // switch:  object type

// Create each page.
for ( _irtc = 0 ; _pprtc[ _irtc ] != NULL ; _irtc++ )
{
   // Create the property page and call AddPropertyPage
}

To display the new property page, ensure that it is part of the global collection, which exists as a static member of CExtObject. By adding the application's property page class to the collection, it is shown whenever the properties are viewed for resources of the resource type.

static CRuntimeClass * g_rgprtcResPSPages[]   = {
   RUNTIME_CLASS( CClipSrvParamsPage ),
   RUNTIME_CLASS( CMyPropPage ),
   NULL
   };
static CRuntimeClass ** g_rgpprtcResPSPages[]   = {
   g_rgprtcResPSPages,
   };
static CRuntimeClass ** g_rgpprtcResWizPages[]   = {
   g_rgprtcResPSPages,
   };

Registering an Extension DLL

Before the Cluster Administrator utilizes the extension DLL, it must be registered. Though the extension DLL is a COM object, more is required than simply running regsvr32.exe. The extension DLL must be registered with the Cluster Administrator using cluster.exe. From a command prompt, enter cluster /regadminext <dllname>. This causes the DLL’s entry point DllRegisterServer to be called for normal COM registration, and invokes DllRegisterCluAdminExtension to allow the DLL to be registered with the Cluster Administrator. The default implementation of DllRegisterCluAdminExtension registers the extension DLL for resource types.

STDAPI DllRegisterCluAdminExtension( IN HCLUSTER hCluster )
{
   HRESULT      hr;
   HRESULT      hrReturn = S_OK;
   LPCWSTR      pwszResTypes = g_wszResourceTypeNames;

   while ( *pwszResTypes != L'\0' )
   {
      wprintf( L"  %s\n", pwszResTypes );
      hr = RegisterCluAdminResourceTypeExtension(
               hCluster,
               pwszResTypes,
               &CLSID_CoClipSrvEx
               );
      if ( hr != S_OK )
      {
         hrReturn = hr;
      } // if:  error registering the extension
      pwszResTypes += lstrlenW( pwszResTypes ) + 1;
   }  // while:  more resource types

   return hrReturn;
}

Registration of extension DLLs for the cluster results in the AdminExtensions registry key being added or updated for a given object. In the example, the AdminExtensions key is added to the ClipBook Server resource type in the registry. The string value holds the CLASSID of the extension DLL.

Figure 9. Registry Editor Window

Debugging an Extension DLL

Extension DLLs are easily debugged using Visual Studio. Open the Project Settings for the extension DLL project, and then set the executable for the debug session to C:\WINDOWS\Cluster\CluAdmin.exe. This enables the setting breakpoints and step-through of the application at run time.

Adding Context-Sensitive Menus

Context menus appear whenever the user right-clicks on any object in the Cluster Administrator. The Cluster Administrator provides an interface for developers to add their own items to the context menu, and an interface to support notification whenever the user clicks on one of these items. IWEExtendContextMenu allows the creation of context menu items and adds them to a Cluster Administrator context menu. IWEInvokeCommand allows a response to a user's selection of the context menu items.

Therefore, adding context menus to the project requires modifying the CExtObject to include support for the two interfaces.

class CExtObject :
   public IWEExtendPropertySheet,
   public IWEExtendWizard,
   public IWEExtendContextMenu,
   public IWEInvokeCommand,
   public ISupportErrorInfo,
   public CComObjectRoot,
   public CComCoClass< CExtObject, &CLSID_CoClipSrvEx >
{
public:
   CExtObject( void );
BEGIN_COM_MAP( CExtObject )
   COM_INTERFACE_ENTRY_IID( IID_IWEExtendPropertySheet, IWEExtendPropertySheet )
   COM_INTERFACE_ENTRY_IID( IID_IWEExtendWizard, IWEExtendWizard )
   COM_INTERFACE_ENTRY_IID( IID_IWEExtendContextMenu, IWEExtendContextMenu )
   COM_INTERFACE_ENTRY_IID( IID_IWEInvokeCommand, IWEInvokeCommand )
   COM_INTERFACE_ENTRY_IID( IID_ISupportErrorInfo, ISupportErrorInfo )
END_COM_MAP()

IWEExtendContextMenu requires implementation of the AddContextMenuItems method, called each time the user right-clicks on an object. IWEInvokeCommand requires implementation of the InvokeCommand method, called whenever the user clicks on an item that has been added to the context menu by the extension DLL.

// IWEExtendContextMenu
public:
   STDMETHOD(AddContextMenuItems)(
               IN IUnknown *         piData,
               IN IWCContextMenuCallback *   piCallback
               );

// IWEInvokeCommand
public:
   STDMETHOD(InvokeCommand)(
               IN ULONG nCommandID,
               IN IUnknown *         piData
               );

The implementation of these methods is straightforward. The AddContextMenuItems takes two parameters, piData and piCallBack. Identical to IWEExtendPropertySheet, the first parameter, piData, is a pointer to an interface that allows the retrieval of information about the cluster.

The second parameter, piCallback, is the pointer to the IWCContextMenuCallback interface that has the AddExtensionMenuItem method that must be called to add an item to the context menu. AddExtensionMenuItem takes five parameters:

  • Name of the item, whose text appears in the context menu.
  • Text to display on the status bar when the item is selected.
  • Numeric identifier of the command.
  • Submenu command ID; this must be 0 (subcommands are not supported).
  • Bitmask flag describing the new menu item.

The following sample does not distinguish between types of resource objects. Instead, a context menu item is added for any object type for which this extension DLL is registered.

STDMETHODIMP CExtObject::AddContextMenuItems(
   IN IUnknown *         piData,
   IN IWCContextMenuCallback *   piCallback
   )
{
   HRESULT            _hr      = NOERROR;
   CStackException      _exc( FALSE /*bAutoDelete*/ );

   AFX_MANAGE_STATE( AfxGetStaticModuleState() );

   // Validate the parameters.
   if ( (piData == NULL) || (piCallback == NULL) )
   {
      return E_INVALIDARG;
   } // if:  all interfaces not specified

   try 
   {
      // Get info about displaying UI.
      _hr = HrGetUIInfo( piData );
      if ( _hr != NOERROR )
      {
         throw &_exc;
      } // if:  error getting UI info

      // Save the data.
      _hr = HrSaveData( piData );
      if ( _hr != NOERROR )
      {
         throw &_exc;
      } // if:  error saving data from host

      m_piMenuCallback = piCallback;

      BSTR bstrCommandName = SysAllocString( OLESTR("MyCmd") );
      BSTR bstrStatusBar = SysAllocString( OLESTR("MyCmdStatusBar") );

      _hr = piCallback->AddExtensionMenuItem( bstrCommandName, bstrStatusBar,
                                           809, 0, 0 );

      SysFreeString( bstrCommandName );
      SysFreeString( bstrStatusBar );
   } // try
   catch ( CMemoryException * _pme )
   {
      _pme->Delete();
      _hr = E_OUTOFMEMORY;
   } // catch:  anything
   catch ( CException * _pe )
   {
      _pe->Delete();
      if ( _hr == NOERROR )
      {
         _hr = E_FAIL;
      } // if:  _hr hasn't beeen set yet
   } // catch:  anything

   if ( _hr != NOERROR )
   {
      piCallback->Release();
      if ( m_piMenuCallback == piCallback )
      {
         m_piMenuCallback = NULL;
      } // if: already saved interface pointer
      piData->Release();
      m_piData = NULL;
   } // if:  error occurred

   return _hr;

}

The InvokeCommand method is called when the user clicks on one of the items in the context menu. The first parameter contains the identifier of the command, specified when the use of AddExtensionMenuItem added the item to the context menu. The second parameter is a pointer to an interface that allows the retrieval of information about the cluster.

STDMETHODIMP CExtObject::InvokeCommand(
   IN ULONG      nCmd,
   IN IUnknown *   piData
   )
{
   HRESULT            _hr      = NOERROR;
   // Your application management functionality here…
   return _hr;
}

Managing Groups

The extension DLL, as produced by the Cluster Resource Type AppWizard, supports extending resource types. However, the Cluster Administrator also supports extending resources, nodes, groups, networks, network interfaces, and cluster objects. The following example demonstrates the modification of the code to support adding property pages to groups.

Note   Cluster Administrator does not support extending the interface for individual groups. Instead, the interface is extended at the group level, for all groups.

The CreatePropertySheetPages method must be modified to allow the adding of pages to objects of type group. In the example, the same collection of property pages is added as that for the resource type. However, these could instead be an entirely different collection specific to groups.

switch ( PodObjData()->m_cot )
{
   case CLUADMEX_OT_RESOURCE:
   case CLUADMEX_OT_GROUP:
      _pprtc = g_rgpprtcResPSPages[ IstrResTypeName() ];
      break;
   default:
      _hr = E_NOTIMPL;
      throw &_exc;
      break;
} // switch:  object type

The helper routing HrGetObjectInfo must also be modified to allow for the retrieval of information about a group object.

case CLUADMEX_OT_GROUP:
{
   IGetClusterGroupInfo *   _pi;

   m_podObjData = new CGroupData;

   // Get an IGetClusterResourceInfo interface pointer.
   _hr = PiData()->QueryInterface( IID_IGetClusterGroupInfo, 
                                 reinterpret_cast< LPVOID * >( &_pi ) );
   if ( _hr != NOERROR )
   {
      throw &_exc;
   } // if:  error querying for interface

   PrdGroupDataRW()->m_hgroup = _pi->GetGroupHandle( 0 );
   ASSERT( PrdGroupDataRW()->m_hgroup != NULL );
   if ( PrdGroupDataRW()->m_hgroup == NULL )
   {
      _hr = E_INVALIDARG;
   } // if  invalid resource handle
   _pi->Release();
   if ( _hr != NOERROR )
   {
      throw &_exc;
   } // if:  error occurred above
}
break;

All that is left is to register the extension with the Cluster Administrator for objects of type group. One method is to modify the registry by hand, adding the AdminExtensions key to HKLM\Cluster\Groups. The key must have the value of the extension DLL CLASSID. The recommended approach is to modify the DllRegisterCluAdminExtension entry point in the DLL to include registration for objects of type group. That way, whenever cluster.exe is used with the regadminext switch, the extension DLL is registered for the correct object type. The AppWizard generates code, located in RegExt.cpp, to support registration of extension DLLs for all relevant types.

To have the extension DLL registered for groups, a call is added to RegisterCluAdminAllGroupsExtension to our entry point. Likewise, if not having the extension DLL used for resource types, this can be removed with the call to RegisterCluAdminResourceTypeExtension. This results in the extension DLL operating only for groups, and not for the ClipBook Server resource type.

STDAPI DllRegisterCluAdminExtension( IN HCLUSTER hCluster )
{
   HRESULT      hr;
   HRESULT      hrReturn = S_OK;
   LPCWSTR      pwszResTypes = g_wszResourceTypeNames;

   while ( *pwszResTypes != L'\0' )
   {
      wprintf( L"  %s\n", pwszResTypes );
      hr = RegisterCluAdminResourceTypeExtension (
               hCluster,
               pwszResTypes,
               &CLSID_CoClipSrvEx
               );
      if ( hr != S_OK )
      {
         hrReturn = hr;
      } // if:  error registering the extension
      pwszResTypes += lstrlenW( pwszResTypes ) + 1;
   }  // while:  more resource types

   hr = RegisterCluAdminAllGroupsExtension(
               hCluster,
               &CLSID_CoClipSrvEx
               );

   return hrReturn;

}  //*** DllRegisterCluAdminExtension()

Conclusion

Developers should not only create applications that run well inside of a cluster as well as outside of a cluster; they should also strive to reduce the effort required by users to manage their applications. For specific application management needs, Windows Server 2003, Enterprise Edition Cluster Server facilitates integrating application management with cluster administration tools, providing users with a central point of administration.