| Platform SDK: Active Directory, ADSI, and Directory Services |
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:
#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;
}