Platform SDK: Active Directory, ADSI, and Directory Services

Registering Node Types for Active Directory Manager

A critical part of extending Active Directory Manager is registering the node type that you want to extend. Note that Active Directory Manager does not register all the possible node types that it can display. Because the schema is extensible (new types of objects can be added as classes to the schema and those objects can be represented in the Active Directory Manager), the Active Directory Manager does not attempt to add every single class defined in the schema as a node type. For Active Directory Manager, the GUID for the node type is the GUID stored in the schemaIDGUID property of the classSchema object that represents the type of object that you want to extend. For example, if you wanted to add a context menu item to the Tasks menu of user objects in Active Directory Manager, you would use the COM string representation of GUID stored in the schemaIDGUID property of the user classSchema object as the node type GUID.

If the node type you want to extend is not registered, you must add the appropriate node type keys to the registry. The following keys must exist for the node type:

HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\NodeTypes\{nodetype}
 
HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\SnapIns\{snapinCLSID}\NodeTypes\{nodetype}

For Active Directory Manager, the {nodetype} key is the schemaIDGUID of the object class whose node you want to extend. The {snapinCLSID} key is the class ID of the Active Directory Manager snap-in, which is {E355E538-1C2E-11D0-8C37-00C04FD8FE93}.

For example, the following registry entries must exist for a taskpad extension snap-in (with CLSID of {897C0FB4-8850-11D2-9523-00C04F8607E2}) to extend the user objects:

HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\NodeTypes\{BF967ABA-0DE6-11D0-A285-00AA003049E2}
 
HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\NodeTypes\{BF967ABA-0DE6-11D0-A285-00AA003049E2}\extensions\Task\{897C0FB4-8850-11D2-9523-00C04F8607E2}
 
HKEY_LOCAL_MACHINE\Software\Microsoft\MMC\SnapIns\{E355E538-1C2E-11D0-8C37-00C04FD8FE93}\NodeTypes\{BF967ABA-0DE6-11D0-A285-00AA003049E2} 

For information about reading the schemaIDGUID of classes and registering them as MMC node types for Active Directory Manager, see the sample code below. For more detail on MMC node types, see MMC node types.

The following program registers a class as a node type and adds an extension snap-in as a context menu extension to that node type:

  1. Finds the user classSchema object and gets its schemaIDGUID property, which will be used to register the node type for the user object class. This GUID is converted using the GetCOMGUIDStr function.
  2. Uses RegisterNodeType to register the user object class as a node type.
  3. Uses AddExtensionToNodeType to add an extension snap-in as a context menu extension with the CLSID of {275C0FB4-8850-11D2-9523-00C04F8607E2} to the user class object node type (which was previously created by RegisterNodeType).
#include <wchar.h>
#include <activeds.h>
 
 
#define MMC_REG_NODETYPES L"software\\microsoft\\mmc\\nodetypes"
#define MMC_REG_SNAPINS L"software\\microsoft\\mmc\\snapins"
#define MMC_REG_SNAPINS L"software\\microsoft\\mmc\\snapins"
 
//MMC Extension subkeys
 
#define MMC_REG_EXTENSIONS L"Extensions"
#define MMC_REG_NAMESPACE L"NameSpace"
#define MMC_REG_CONTEXTMENU L"ContextMenu"
#define MMC_REG_TOOLBAR L"ToolBar"
#define MMC_REG_PROPERTYSHEET L"PropertySheet"
#define MMC_REG_TASKPAD L"Task"
 
//DSADMIN key
#define MMC_DSADMIN_CLSID L"{E355E538-1C2E-11D0-8C37-00C04FD8FE93}"
 
 
 
WCHAR * GetDirectoryObjectAttrib(IDirectoryObject *pDirObject,LPWSTR pAttrName);
 
HRESULT GetCOMGUIDStr(LPOLESTR *ppAttributeName,IDirectoryObject *pDO, LPOLESTR *ppGUIDString);
 
HRESULT  RegisterNodeType( LPOLESTR pszSchemaIDGUID );
 
HRESULT  AddExtensionToNodeType(LPOLESTR pszSchemaIDGUID,
                    LPOLESTR pszExtensionType,
                    LPOLESTR pszExtensionSnapinCLSID,
                    LPOLESTR pszRegValue
                    );
 
 
int main(int argc, char* argv[])
{
LPOLESTR szPath = new OLECHAR[MAX_PATH];
HRESULT hr = S_OK;
IADs *pObject = NULL;
VARIANT var;
IDirectoryObject *pDO = NULL;
LPOLESTR pAttributeName = L"schemaIDGUID";
LPOLESTR pGUIDString = NULL;
 
 
wcscpy(szPath, L"LDAP://");
CoInitialize(NULL);
//Get rootDSE and the schema container's DN.
//Bind to current user's domain using current user's security context.
hr = ADsOpenObject(L"LDAP://rootDSE",
            NULL,
            NULL,
            ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
            IID_IADs,
            (void**)&pObject);
 
if (SUCCEEDED(hr))
{
    hr = pObject->Get(L"schemaNamingContext",&var);
    if (SUCCEEDED(hr))
    {
        wcscat(szPath, L"cn=user,");
        wcscat(szPath,var.bstrVal);
        if (pObject)
        {
            pObject->Release();
            pObject = NULL;
        }
        hr = ADsOpenObject(szPath,
                NULL,
                NULL,
                ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
                IID_IDirectoryObject,
                (void**)&pDO);
        if (SUCCEEDED(hr))
        {
            hr = GetCOMGUIDStr(&pAttributeName,
                        pDO,
                        &pGUIDString);
        if (SUCCEEDED(hr))
        {
            wprintf(L"schemaIDGUID: %s\n", pGUIDString);
            hr = RegisterNodeType( pGUIDString);
            wprintf(L"hr %x\n", hr);
            hr = AddExtensionToNodeType(pGUIDString,
                        MMC_REG_CONTEXTMENU,
                        L"{275C0FB4-8850-11D2-9523-00C04F8607E2}",
                        L"MySnapin"
                        );
 
            }
        }
    }
}
if (pDO)
    pDO->Release();
 
VariantClear(&var);
 
// Uninitialize COM
CoUninitialize();
return 0;
}
 
 
////////////////////////////////////////////////////////////////////////////////////////////////////
/*  
    GetDirectoryObjectAttrib()    - Returns the value of the attribute named in pAttrName
                                    from the IDirectoryObject passed
    Parameters
    
        IDirectoryObject *pDirObject    - Object from which to retrieve an attribute value
        LPWSTR pAttrName                - Name of attribute to retrieve
*/
WCHAR * GetDirectoryObjectAttrib(IDirectoryObject *pDirObject,LPWSTR pAttrName)
{
    HRESULT   hr;
    ADS_ATTR_INFO   *pAttrInfo=NULL;
    DWORD   dwReturn;
    static WCHAR pwReturn[1024];
 
    pwReturn[0] = 0l;
 
    hr = pDirObject->GetObjectAttributes( &pAttrName, 
                                            1, 
                                            &pAttrInfo, 
                                            &dwReturn ); 
    if ( SUCCEEDED(hr) )
    {
        for(DWORD idx=0; idx < dwReturn;idx++, pAttrInfo++ )   
        {
            if ( _wcsicmp(pAttrInfo->pszAttrName,pAttrName) == 0 )       
            {
                wcscpy(pwReturn,pAttrInfo->pADsValues->CaseIgnoreString);
                break;
            }
        }
        FreeADsMem( pAttrInfo );
    }
    return pwReturn;
}
 
 
HRESULT GetCOMGUIDStr(LPOLESTR *ppAttributeName,IDirectoryObject *pDO, LPOLESTR *ppGUIDString)
{
    HRESULT hr = S_OK;
    PADS_ATTR_INFO  pAttributeEntries;
    VARIANT varX;
    DWORD dwAttributesReturned = 0;
    hr = pDO->GetObjectAttributes(  ppAttributeName, //objectGUID
                                  1, //Only objectGUID
                                  &pAttributeEntries, // Returned attributes
                                  &dwAttributesReturned //Number of attributes returned
                                );
    if (SUCCEEDED(hr) && dwAttributesReturned>0)
    {
        //Make sure that we got the right type--GUID is ADSTYPE_OCTET_STRING
        if (pAttributeEntries->dwADsType == ADSTYPE_OCTET_STRING)
        {
            LPGUID pObjectGUID = (GUID*)(pAttributeEntries->pADsValues[0].OctetString.lpValue);
            //OLE str to fit a GUID
            LPOLESTR szDSGUID = new WCHAR [39];
            //Convert GUID to string.
            ::StringFromGUID2(*pObjectGUID, szDSGUID, 39); 
        *ppGUIDString = (OLECHAR *)CoTaskMemAlloc (sizeof(OLECHAR)*(wcslen(szDSGUID)+1));
        if (*ppGUIDString)
            wcscpy(*ppGUIDString, szDSGUID);
        else
            hr=E_FAIL;
    }
    else
        hr = E_FAIL;
    //Free the memory for the attributes.
    FreeADsMem(pAttributeEntries);
    VariantClear(&varX);
    }
    return hr;
}
 
 
 
HRESULT  RegisterNodeType(LPOLESTR pszSchemaIDGUID)  
{ 
    LONG     lResult; 
    HKEY     hKey; 
    HKEY     hSubKey, hNewKey; 
    DWORD    dwDisposition; 
    LPOLESTR szRegSubKey = new OLECHAR[MAX_PATH];
 
        // first, open the HKEY_LOCAL_MACHINE 
        lResult  = RegConnectRegistry( NULL, HKEY_LOCAL_MACHINE, &hKey ); 
        if ( ERROR_SUCCESS == lResult )
    {
        //go to the MMC_REG_NODETYPES subkey 
            lResult  = RegOpenKey( hKey, MMC_REG_NODETYPES, &hSubKey ); 
            if ( ERROR_SUCCESS == lResult ) 
        {
            // Create a key for the node type of the class represented by pszSchemaIDGUID
            lResult  = RegCreateKeyEx( hSubKey,                // handle of an open key 
                        pszSchemaIDGUID,       // address of subkey name 
                        0L ,                    // reserved 
                        NULL, 
                        REG_OPTION_NON_VOLATILE,// special options flag 
                        KEY_ALL_ACCESS, 
                        NULL, 
                        &hNewKey, 
                        &dwDisposition );
            RegCloseKey( hSubKey ); 
        if ( ERROR_SUCCESS == lResult ) 
        {
            hSubKey = hNewKey; 
                // Create an extensions key 
            lResult  = RegCreateKeyEx( hSubKey,                 
                    MMC_REG_EXTENSIONS,                
                                0L ,                     
                                NULL, 
                                REG_OPTION_NON_VOLATILE, 
                                KEY_ALL_ACCESS, 
                                NULL, 
                                &hNewKey, 
                                &dwDisposition );
            //go to the MMC_REG_SNAPINS subkey 
            RegCloseKey( hSubKey ); 
            //Build the subkey path to the NodeTypes key of dsadmin
            wcscpy(szRegSubKey, MMC_REG_SNAPINS); //Snapins key
            wcscat(szRegSubKey, L"\\");
            wcscat(szRegSubKey, MMC_DSADMIN_CLSID); //CLSID for DSADMIN
            wcscat(szRegSubKey, L"\\NodeTypes");
            lResult  = RegOpenKey( hKey, szRegSubKey, &hSubKey ); 
            if ( ERROR_SUCCESS == lResult ) 
            {
                // Create a key for the node type of the class represented by pszSchemaIDGUID
                lResult  = RegCreateKeyEx( hSubKey,                // handle of an open key 
                                pszSchemaIDGUID,       // address of subkey name 
                                0L ,                    // reserved 
                                NULL, 
                                REG_OPTION_NON_VOLATILE,// special options flag 
                                KEY_ALL_ACCESS, 
                                NULL, 
                                &hNewKey, 
                                &dwDisposition );
                    RegCloseKey( hSubKey );
                }
 
            }
        }
    }
    RegCloseKey( hSubKey ); 
    RegCloseKey( hNewKey );
    RegCloseKey( hKey );
        return lResult; 
} 
 
HRESULT  AddExtensionToNodeType(LPOLESTR pszSchemaIDGUID,
                        LPOLESTR pszExtensionType,
                        LPOLESTR pszExtensionSnapinCLSID,
                        LPOLESTR pszRegValue
                        ) 
{
        LONG     lResult; 
        HKEY     hKey; 
        HKEY     hSubKey, hNewKey; 
        DWORD    dwDisposition;
    LPOLESTR szRegSubKey = new OLECHAR[MAX_PATH];
    HRESULT hr = S_OK;
 
        // first, open the HKEY_LOCAL_MACHINE 
        lResult  = RegConnectRegistry( NULL, HKEY_LOCAL_MACHINE, &hKey ); 
        if ( ERROR_SUCCESS == lResult )
    {
        //Build the subkey path to the NodeType specified by pszSchemaIDGUID
    wcscpy(szRegSubKey, MMC_REG_NODETYPES);
    wcscat(szRegSubKey, L"\\");
    wcscat(szRegSubKey, pszSchemaIDGUID);
    //go to the subkey 
        lResult  = RegOpenKey( hKey, szRegSubKey, &hSubKey ); 
        if ( ERROR_SUCCESS != lResult ) 
    {
        // Create the key for the nodetype if it doesn't already exist.
        hr = RegisterNodeType(pszSchemaIDGUID);
            if ( ERROR_SUCCESS != lResult ) 
            return E_FAIL;
            lResult  = RegOpenKey( hKey, szRegSubKey, &hSubKey ); 
    }
    // Create an extensions key if one doesn't already exist
    lResult  = RegCreateKeyEx( hSubKey,
                    MMC_REG_EXTENSIONS,
                               0L ,
                               NULL,
                               REG_OPTION_NON_VOLATILE,
                               KEY_ALL_ACCESS,
                               NULL,
                               &hNewKey,
                               &dwDisposition );
    RegCloseKey( hSubKey ); 
    if ( ERROR_SUCCESS == lResult ) 
    {
        hSubKey = hNewKey; 
        // Create an extension type subkey if one doesn't already exist
        lResult  = RegCreateKeyEx( hSubKey,
                    pszExtensionType,
                           0L ,
                           NULL,
                           REG_OPTION_NON_VOLATILE,
                           KEY_ALL_ACCESS,
                           NULL,
                           &hNewKey,
                           &dwDisposition );
        RegCloseKey( hSubKey );
        if ( ERROR_SUCCESS == lResult )
            {
            hSubKey = hNewKey;
            // Add your snap-in to the 
            //extension type key if it hasn't been already.
            lResult  = RegSetValueEx( hSubKey,
                pszExtensionSnapinCLSID,
                           0L ,
                           REG_SZ,
                           (const BYTE*)pszRegValue,
                           (wcslen(pszRegValue)+1)*sizeof(OLECHAR)
                    );
            }
 
        }
 }
    RegCloseKey( hSubKey );
    RegCloseKey( hNewKey );
    RegCloseKey( hKey );
    return lResult; 
}