Inscription de l’objet COM de page de propriétés dans un spécificateur d’affichage

Lorsque vous utilisez COM pour créer une DLL d’extension de feuille de propriétés pour services de domaine Active Directory, vous devez également inscrire l’extension auprès du Registre Windows et de services de domaine Active Directory. L’inscription de l’extension permet aux composants logiciels enfichables MMC d’administration Active Directory et à l’interpréteur de commandes Windows de reconnaître l’extension.

Inscription dans le Registre Windows

Comme tous les serveurs COM, une extension de feuille de propriétés doit être inscrite dans le Registre Windows. L’extension est inscrite sous la clé suivante.

HKEY_CLASSES_ROOT
   CLSID
      <clsid>

<clsid> est la représentation sous forme de chaîne du CLSID tel que produit par la fonction StringFromCLSID . Sous la <clé clsid> , il existe une clé InProcServer32 qui identifie l’objet en tant que serveur in-proc 32 bits. Sous la clé InProcServer32 , l’emplacement de la DLL est spécifié dans la valeur par défaut et le modèle de thread est spécifié dans la valeur ThreadingModel . Toutes les extensions de feuille de propriétés doivent utiliser le modèle de thread « Apartment ».

Inscription auprès de services de domaine Active Directory

L’inscription de l’extension de feuille de propriétés est spécifique à un paramètre régional. Si l’extension de feuille de propriétés s’applique à tous les paramètres régionaux, elle doit être inscrite dans l’objet displaySpecifier de la classe d’objet dans tous les sous-conteneurs de paramètres régionaux du conteneur Spécificateurs d’affichage. Si l’extension de la feuille de propriétés est localisée pour certains paramètres régionaux, inscrivez-la dans l’objet displaySpecifier de ce sous-conteneur de paramètres régionaux. Pour plus d’informations sur le conteneur de spécificateurs d’affichage et les paramètres régionaux, consultez Display Specifiers and DisplaySpecifiers Container.

Une extension de feuille de propriétés peut être inscrite sous trois attributs de spécificateur d’affichage. Il s’agit de adminPropertyPages, shellPropertyPages et adminMultiselectPropertyPages.

L’attribut adminPropertyPages identifie les pages de propriétés d’administration à afficher dans les composants logiciels enfichables d’administration Active Directory. La page de propriétés s’affiche lorsque l’utilisateur affiche les propriétés des objets de la classe appropriée dans l’un des composants logiciels enfichables MMC d’administration Active Directory.

L’attribut shellPropertyPages identifie les pages de propriétés de l’utilisateur final à afficher dans l’interpréteur de commandes Windows. La page de propriétés s’affiche lorsque l’utilisateur affiche les propriétés des objets de la classe appropriée dans le Explorer Windows. À compter des systèmes d’exploitation Windows Server 2003, l’interpréteur de commandes Windows n’affiche plus d’objets de services de domaine Active Directory.

AdminMultiselectPropertyPages est uniquement disponible sur les systèmes d’exploitation Windows Server 2003. La page de propriétés s’affiche lorsque l’utilisateur affiche les propriétés de plusieurs objets de la classe appropriée dans l’un des composants logiciels enfichables MMC d’administration Active Directory.

Tous ces attributs sont à valeurs multiples.

Les valeurs des attributs adminPropertyPages et shellPropertyPages nécessitent le format suivant.

<order number>,<clsid>,<optional data>

Les valeurs de l’attribut adminMultiselectPropertyPages nécessitent le format suivant.

<order number>,<clsid>

Le «< numéro> de commande » est un numéro non signé qui représente la position de la page dans la feuille. Lorsqu’une feuille de propriétés est affichée, les valeurs sont triées à l’aide d’une comparaison du «< numéro> de commande » de chaque valeur. Si plusieurs valeurs ont le même «< numéro> de commande », ces objets COM de page de propriétés sont chargés dans l’ordre dans lequel ils sont lus à partir du serveur Active Directory. Si possible, vous devez utiliser un «< numéro> de commande » qui n’existe pas, c’est-à-dire qu’il n’est pas utilisé par les autres valeurs de la propriété. Il n’y a pas de position de départ prescrite, et les écarts sont autorisés dans la séquence «< numéro> d’ordre ».

Le «< clsid> » est la représentation sous forme de chaîne du CLSID tel qu’il est produit par la fonction StringFromCLSID .

Les «< données> facultatives » sont une valeur de chaîne qui n’est pas requise. Cette valeur peut être récupérée par l’objet COM de la page de propriétés à l’aide du pointeur IDataObject passé à sa méthode IShellExtInit::Initialize . L’objet COM de la page de propriétés obtient ces données en appelant IDataObject::GetData avec le format CFSTR_DSPROPERTYPAGEINFO Presse-papiers. Cela fournit un HGLOBAL qui contient une structure DSPROPERTYPAGEINFO La structure DSPROPERTYPAGEINFO contient une chaîne Unicode qui contient les «< données> facultatives ». Les «< données> facultatives » ne sont pas autorisées avec l’attribut adminMultiselectPropertyPages . L’exemple de code C/C++ suivant montre comment récupérer les «< données> facultatives ».

fe.cfFormat = RegisterClipboardFormat(CFSTR_DSPROPERTYPAGEINFO);
fe.ptd = NULL;
fe.dwAspect = DVASPECT_CONTENT;
fe.lindex = -1;
fe.tymed = TYMED_HGLOBAL;
hr = pDataObj->GetData(&fe, &stm);
if(SUCCEEDED(hr))
{
    DSPROPERTYPAGEINFO *pPageInfo;
    
    pPageInfo = (DSPROPERTYPAGEINFO*)GlobalLock(stm.hGlobal);
    if(pPageInfo)
    {
        LPWSTR  pwszData;

        pwszData = (LPWSTR)((LPBYTE)pPageInfo + pPageInfo->offsetString);
        pwszData = NULL;
        
        GlobalUnlock(stm.hGlobal);
    }

    ReleaseStgMedium(&stm);
}

Une extension de feuille de propriétés peut implémenter plusieurs pages de propriétés ; L’une des utilisations possibles des «< données> facultatives » consiste à nommer les pages à afficher. Cela permet de choisir d’implémenter plusieurs objets COM, un pour chaque page ou un seul objet COM pour gérer plusieurs pages.

L’exemple de code suivant est un exemple de valeur qui peut être utilisé pour les attributs adminPropertyPages, shellPropertyPages ou adminMultiselectPropertyPages .

1,{6dfe6485-a212-11d0-bcd5-00c04fd8d5b6}

Important

Pour l’interpréteur de commandes Windows, les données du spécificateur d’affichage sont récupérées lors de l’ouverture de session de l’utilisateur et mises en cache pour la session utilisateur. Pour les composants logiciels enfichables d’administration, les données du spécificateur d’affichage sont récupérées lors du chargement du composant logiciel enfichable et mises en cache pendant toute la durée de vie du processus. Pour l’interpréteur de commandes Windows, cela indique que les modifications apportées à l’affichage des spécificateurs prennent effet une fois qu’un utilisateur se déconnecte, puis se reconnecte. Pour les composants logiciels enfichables d’administration, les modifications prennent effet lors du chargement du composant logiciel enfichable ou du fichier de console.

 

Ajout d’une valeur aux attributs d’extension de feuille de propriétés

La procédure suivante explique comment inscrire une extension de feuille de propriétés sous l’un des attributs d’extension de la feuille de propriétés.

Inscription d’une extension de feuille de propriétés sous l’un des attributs d’extension de feuille de propriétés

  1. Vérifiez que l’extension n’existe pas déjà dans les valeurs d’attribut.
  2. Ajoutez la nouvelle valeur à la fin de la liste de classement de la page de propriétés. La partie «< numéro> de commande » de la valeur d’attribut est définie sur la valeur suivante après le numéro de commande existant le plus élevé.
  3. La méthode IADs::P utEx est utilisée pour ajouter la nouvelle valeur à l’attribut. Le paramètre lnControlCode doit être défini sur ADS_PROPERTY_APPEND afin que la nouvelle valeur soit ajoutée aux valeurs existantes et ne remplace donc pas les valeurs existantes. La méthode IADs::SetInfo doit être appelée par la suite pour valider la modification dans le répertoire.

N’oubliez pas que la même extension de feuille de propriétés peut être inscrite pour plusieurs classes d’objets.

La méthode IADs::P utEx est utilisée pour ajouter la nouvelle valeur à l’attribut. Le paramètre lnControlCode doit être défini sur ADS_PROPERTY_APPEND, afin que la nouvelle valeur soit ajoutée aux valeurs existantes et ne remplace donc pas les valeurs existantes. La méthode IADs::SetInfo doit être appelée après pour valider la modification dans le répertoire.

L’exemple de code suivant ajoute une extension de feuille de propriétés à la classe de groupe dans les paramètres régionaux par défaut de l’ordinateur. N’oubliez pas que la fonction AddPropertyPageToDisplaySpecifier vérifie le CLSID de l’extension de feuille de propriétés dans les valeurs existantes, obtient le numéro de commande le plus élevé et ajoute la valeur de la page de propriétés à l’aide de IADs::P utEx avec le code de contrôle ADS_PROPERTY_APPEND .

//  Add msvcrt.dll to the project.
//  Add activeds.lib to the project.
//  Add adsiid.lib to the project.

#include "stdafx.h"
#include <wchar.h>
#include <objbase.h>
#include <activeds.h>
#include "atlbase.h"

HRESULT AddPropertyPageToDisplaySpecifier(LPOLESTR szClassName, //  ldapDisplayName of the class.
                                          CLSID *pPropPageCLSID //  CLSID of property page COM object.
                                         );

HRESULT BindToDisplaySpecifiersContainerByLocale(LCID *locale,
                                                 IADsContainer **ppDispSpecCont
                                                 );

HRESULT GetDisplaySpecifier(IADsContainer *pContainer, LPOLESTR szDispSpec, IADs **ppObject);

//  Entry point for the application.
void wmain(int argc, wchar_t *argv[ ])
{

    wprintf(L"This program adds a sample property page to the display specifier for group class in the local computer's default locale.\n");

    // Initialize COM.
    CoInitialize(NULL);
    HRESULT hr = S_OK;

    //  Class ID for the sample property page.
    LPOLESTR szCLSID = L"{D9FCE809-8A10-11d2-A7E7-00C04F79DC0F}";
    LPOLESTR szClass = L"group";
    CLSID clsid;
    //  Convert to GUID.
    hr = CLSIDFromString(
        szCLSID,  //  Pointer to the string representation of the CLSID.
        &clsid    //  Pointer to the CLSID.
        );

    hr = AddPropertyPageToDisplaySpecifier(szClass, //  ldapDisplayName of the class.
                                           &clsid //  CLSID of property page COM object.
                                          );
    if (S_OK == hr)
        wprintf(L"Property page registered successfully\n");
    else if (S_FALSE == hr)
        wprintf(L"Property page was not added because it was already registered.\n");
    else
        wprintf(L"Property page was not added. HR: %x.\n");

    //  Uninitialize COM.
    CoUninitialize();
    return;
}

//  Adds a property page to Active Directory admin snap-ins.
HRESULT AddPropertyPageToDisplaySpecifier(LPOLESTR szClassName, //  ldapDisplayName of class.
                                          CLSID *pPropPageCLSID //  CLSID of property page COM object.
                                         )
{
    HRESULT hr = E_FAIL;
    IADsContainer *pContainer = NULL;
    LPOLESTR szDispSpec = new OLECHAR[MAX_PATH];
    IADs *pObject = NULL;
    VARIANT var;
    CComBSTR sbstrProperty = L"adminPropertyPages";
    LCID locale = NULL;
    //  Get the display specifier container using default system locale.
    //  When adding a property page COM object, specify the locale
    //  because the registration is locale-specific.
    //  This means if you created a property page
    //  for German, explicitly add it to the 407 container,
    //  so that it will be used when a computer is running with locale
    //  set to German and not the locale set on the
    //  computer where this application is running.
    hr = BindToDisplaySpecifiersContainerByLocale(&locale,
                                                  &pContainer
                                                 );
    //  Handle fail case where dispspec object
    //  is not found and give option to create one.

    if (SUCCEEDED(hr))
    {
        //  Bind to display specifier object for the specified class.
        //  Build the display specifier name.
#ifdef _MBCS
        wcscpy_s(szDispSpec, szClassName);
        wcscat_s(szDispSpec, L"-Display");
        hr = GetDisplaySpecifier(pContainer, szDispSpec, &pObject);
#endif _MBCS#endif _MBCS
        if (SUCCEEDED(hr))
        {
            //  Convert GUID to string.
            LPOLESTR szDSGUID = new WCHAR [39];
            ::StringFromGUID2(*pPropPageCLSID, szDSGUID, 39); 

            //  Get the adminPropertyPages property.
            hr = pObject->GetEx(sbstrProperty, &var);
            if (SUCCEEDED(hr))
            {
                LONG lstart, lend;
                SAFEARRAY *sa = V_ARRAY(&var);
                VARIANT varItem;
                //  Get the lower and upper bound.
                hr = SafeArrayGetLBound(sa, 1, &lstart);
                if (SUCCEEDED(hr))
                {
                    hr = SafeArrayGetUBound(sa, 1, &lend);
                }
                if (SUCCEEDED(hr))
                {
                    //  Iterate the values to verify if the prop page CLSID
                    //  is already registered.
                    VariantInit(&varItem);
                    BOOL bExists = FALSE;
                    UINT uiLastItem = 0;
                    UINT uiTemp = 0;
                    INT iOffset = 0;
                    LPOLESTR szMainStr = new OLECHAR[MAX_PATH];
                    LPOLESTR szItem = new OLECHAR[MAX_PATH];
                    LPOLESTR szStr = NULL;
                    for (long idx=lstart; idx <= lend; idx++)
                    {
                        hr = SafeArrayGetElement(sa, &idx, &varItem);
                        if (SUCCEEDED(hr))
                        {
#ifdef _MBCS
                            //  Verify that the specified CLSID is already registered.
                            wcscpy_s(szMainStr,varItem.bstrVal);
                            if (wcsstr(szMainStr,szDSGUID))
                                bExists = TRUE;
                            //  Get the index which is the number before the first comma.
                            szStr = wcschr(szMainStr, ',');
                            iOffset = (int)(szStr - szMainStr);
                            wcsncpy_s(szItem, szMainStr, iOffset);
                            szItem[iOffset]=0L;
                            uiTemp = _wtoi(szItem);
                            if (uiTemp > uiLastItem)
                                uiLastItem = uiTemp;
                            VariantClear(&varItem);
#endif _MBCS
                        }
                    }
                    //  If the CLSID is not registered, add it.
                    if (!bExists)
                    {
                        //  Build the value to add.
                        LPOLESTR szValue = new OLECHAR[MAX_PATH];
                        //  Next index to add at end of list.
#ifdef _MBCS
                        uiLastItem++;
                        _itow_s(uiLastItem, szValue, 10);   
                        wcscat_s(szValue,L",");
                        //  Add the class ID for the property page.
                        wcscat_s(szValue,szDSGUID);
                        wprintf(L"Value to add: %s\n", szValue);
#endif _MBCS

                        VARIANT varAdd;
                        //  Only one value to add
                        LPOLESTR pszAddStr[1];
                        pszAddStr[0]=szValue;
                        ADsBuildVarArrayStr(pszAddStr, 1, &varAdd);

                        hr = pObject->PutEx(ADS_PROPERTY_APPEND, sbstrProperty, varAdd);
                        if (SUCCEEDED(hr))
                        {
                            //  Commit the change.
                            hr = pObject->SetInfo();
                        }
                    }
                    else
                        hr = S_FALSE;
                }
            }
            VariantClear(&var);
        }
        if (pObject)
            pObject->Release();
    }

    return hr;
}

//  This function returns a pointer to the display specifier container 
//  for the specified locale.
//  If locale is NULL, use the default system locale and then return the locale in the locale param.
HRESULT BindToDisplaySpecifiersContainerByLocale(LCID *locale,
                                                 IADsContainer **ppDispSpecCont
                                                )
{
    HRESULT hr = E_FAIL;

    if ((!ppDispSpecCont)||(!locale))
        return E_POINTER;

    //  If no locale is specified, use the default system locale.
    if (!(*locale))
    {
        *locale = GetSystemDefaultLCID();
        if (!(*locale))
            return E_FAIL;
    }

    //  Verify that it is a valid locale.
    if (!IsValidLocale(*locale, LCID_SUPPORTED))
        return E_INVALIDARG;

    LPOLESTR szPath = new OLECHAR[MAX_PATH*2];
    IADs *pObj = NULL;
    VARIANT var;

    hr = ADsOpenObject(L"LDAP://rootDSE",
                       NULL,
                       NULL,
                       ADS_SECURE_AUTHENTICATION, // Use Secure Authentication.
                       IID_IADs,
                       (void**)&pObj);

    if (SUCCEEDED(hr))
    {
        //  Get the DN to the config container.
        hr = pObj->Get(CComBSTR("configurationNamingContext"), &var);
        if (SUCCEEDED(hr))
        {
#ifdef _MBCS
            //  Build the string to bind to the DisplaySpecifiers container.
            swprintf_s(szPath,L"LDAP://cn=%x,cn=DisplaySpecifiers,%s", *locale, var.bstrVal);
            //  Bind to the DisplaySpecifiers container.
            *ppDispSpecCont = NULL;
            hr = ADsOpenObject(szPath,
                               NULL,
                               NULL,
                               ADS_SECURE_AUTHENTICATION, // Use Secure Authentication.
                               IID_IADsContainer,
                               (void**)ppDispSpecCont);
#endif _MBCS

            if(FAILED(hr))
            {
                if (*ppDispSpecCont)
                {
                    (*ppDispSpecCont)->Release();
                    (*ppDispSpecCont) = NULL;
                }
            }
        }
    }
    //   Cleanup.
    VariantClear(&var);
    if (pObj)
        pObj->Release();

    return hr;
}

HRESULT GetDisplaySpecifier(IADsContainer *pContainer, LPOLESTR szDispSpec, IADs **ppObject)
{
    HRESULT hr = E_FAIL;
    CComBSTR sbstrDSPath;
    IDispatch *pDisp = NULL;

    if (!pContainer)
    {
        hr = E_POINTER;
        return hr;
    }

    //  Verify other pointers.
    //  Initialize the output pointer.
    (*ppObject) = NULL;

    //  Build relative path to the display specifier object.
    sbstrDSPath = "CN=";
    sbstrDSPath += szDispSpec;

    //  Use child object binding with IADsContainer::GetObject.
    hr = pContainer->GetObject(CComBSTR("displaySpecifier"),
        sbstrDSPath,
        &pDisp);
    if (SUCCEEDED(hr))
    {
        hr = pDisp->QueryInterface(IID_IADs, (void**)ppObject);
        if (FAILED(hr))
        {
            //  Clean up.
            if (*ppObject)
                (*ppObject)->Release();
        }
    }

    if (pDisp)
        pDisp->Release();

    return hr;

}