표시 지정자에 속성 페이지 COM 개체 등록

COM을 사용하여 Active Directory Domain Services 대한 속성 시트 확장 DLL을 만드는 경우 Windows 레지스트리 및 Active Directory Domain Services 확장을 등록해야 합니다. 확장을 등록하면 Active Directory 관리 MMC 스냅인 및 Windows 셸에서 확장을 인식할 수 있습니다.

Windows 레지스트리에 등록

모든 COM 서버와 마찬가지로 Windows 레지스트리에 속성 시트 확장을 등록해야 합니다. 확장은 다음 키 아래에 등록됩니다.

HKEY_CLASSES_ROOT
   CLSID
      <clsid>

<clsid>StringFromCLSID 함수에서 생성된 CLSID의 문자열 표현입니다. <clsid> 키 아래에는 개체를 32비트 in-proc 서버로 식별하는 InProcServer32 키가 있습니다. InProcServer32 키에서 DLL의 위치는 기본값으로 지정되고 스레딩 모델은 ThreadingModel 값에 지정됩니다. 모든 속성 시트 확장은 "아파트" 스레딩 모델을 사용해야 합니다.

Active Directory Domain Services 등록

속성 시트 확장 등록은 하나의 로캘에만 적용됩니다. 속성 시트 확장이 모든 로캘에 적용되는 경우 Display Specifiers 컨테이너의 모든 로캘 하위 구성 요소에 있는 object 클래스 displaySpecifier 개체에 등록해야 합니다. 속성 시트 확장이 특정 로캘에 대해 지역화된 경우 해당 로캘 하위 구성 요소의 displaySpecifier 개체에 등록합니다. 표시 지정자 컨테이너 및 로캘에 대한 자세한 내용은 표시 지정자DisplaySpecifiers 컨테이너를 참조하세요.

속성 시트 확장을 등록할 수 있는 세 가지 표시 지정자 특성이 있습니다. adminPropertyPages, shellPropertyPagesadminMultiselectPropertyPages입니다.

adminPropertyPages 특성은 Active Directory 관리 스냅인에 표시할 관리 속성 페이지를 식별합니다. 사용자가 Active Directory 관리 MMC 스냅인 중 하나에서 적절한 클래스의 개체에 대한 속성을 볼 때 속성 페이지가 나타납니다.

shellPropertyPages 특성은 Windows 셸에 표시할 최종 사용자 속성 페이지를 식별합니다. 사용자가 Windows Explorer 적절한 클래스의 개체에 대한 속성을 볼 때 속성 페이지가 나타납니다. Windows Server 2003 운영 체제부터 Windows 셸은 더 이상 Active Directory Domain Services 개체를 표시하지 않습니다.

adminMultiselectPropertyPages는 Windows Server 2003 운영 체제에서만 사용할 수 있습니다. 사용자가 Active Directory 관리 MMC 스냅인 중 하나에서 적절한 클래스의 둘 이상의 개체에 대한 속성을 볼 때 속성 페이지가 나타납니다.

이러한 모든 특성은 다중 값입니다.

adminPropertyPagesshellPropertyPages 특성의 값에는 다음 형식이 필요합니다.

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

adminMultiselectPropertyPages 특성의 값에는 다음 형식이 필요합니다.

<order number>,<clsid>

"<주문 번호>"는 시트의 페이지 위치를 나타내는 부호 없는 숫자입니다. 속성 시트가 표시되면 각 값의 "<주문 번호>"를 비교하여 값이 정렬됩니다. 둘 이상의 값에 동일한 "<주문 번호>"가 있는 경우 해당 속성 페이지 COM 개체는 Active Directory 서버에서 읽은 순서대로 로드됩니다. 가능하면 기존이 아닌 "<주문 번호>"를 사용해야 합니다. 즉, 속성의 다른 값에서 사용되지 않습니다. 정해진 시작 위치가 없으며 "<주문 번호>" 시퀀스에서 간격이 허용됩니다.

"<clsid>"는 StringFromCLSID 함수에서 생성된 CLSID의 문자열 표현입니다.

"<선택적 데이터>"는 필요하지 않은 문자열 값입니다. 이 값은 IShellExtInit::Initialize 메서드에 전달된 IDataObject 포인터를 사용하여 속성 페이지 COM 개체에서 검색할 수 있습니다. 속성 페이지 COM 개체는 CFSTR_DSPROPERTYPAGEINFO 클립보드 형식으로 IDataObject::GetData를 호출하여 이 데이터를 가져옵니다. DSPROPERTYPAGEINFO 구조체가 포함된 HGLOBAL을 제공합니다. DSPROPERTYPAGEINFO 구조체에는 "<선택적 데이터>"가 포함된 유니코드 문자열이 포함되어 있습니다. adminMultiselectPropertyPages 특성에는 "<선택적 데이터>"가 허용되지 않습니다. 다음 C/C++ 코드 예제에서는 "<선택적 데이터>"를 검색하는 방법을 보여줍니다.

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);
}

속성 시트 확장은 둘 이상의 속성 페이지를 구현할 수 있습니다. "<선택적 데이터>"를 사용할 수 있는 한 가지 방법은 표시할 페이지의 이름을 지정하는 것입니다. 이렇게 하면 각 페이지에 하나씩 여러 COM 개체를 구현하거나 단일 COM 개체를 선택하여 여러 페이지를 처리하도록 선택할 수 있습니다.

다음 코드 예제는 adminPropertyPages, shellPropertyPages 또는 adminMultiselectPropertyPages 특성에 사용할 수 있는 예제 값입니다.

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

중요

Windows 셸의 경우 표시 지정자 데이터는 사용자 로그온 시 검색되고 사용자 세션에 대해 캐시됩니다. 관리 스냅인의 경우 스냅인이 로드될 때 표시 지정자 데이터가 검색되고 프로세스의 수명 동안 캐시됩니다. Windows 셸의 경우 사용자가 로그오프한 다음 다시 로그온한 후 표시 지정자에 대한 변경 내용이 적용됨을 나타냅니다. 관리 스냅인의 경우 스냅인 또는 콘솔 파일이 로드될 때 변경 내용이 적용됩니다.

 

속성 시트 확장 특성에 값 추가

다음 절차에서는 속성 시트 확장 특성 중 하나에서 속성 시트 확장을 등록하는 방법을 설명합니다.

속성 시트 확장 특성 중 하나에 속성 시트 확장 등록

  1. 확장이 특성 값에 아직 없는지 확인합니다.
  2. 속성 페이지 순서 목록의 끝에 새 값을 추가합니다. 즉, 특성 값의 "<주문 번호>" 부분을 가장 높은 기존 주문 번호 다음 값으로 설정합니다.
  3. IADs::P utEx 메서드는 특성에 새 값을 추가하는 데 사용됩니다. 새 값이 기존 값에 추가되고 기존 값을 덮어쓰지 않도록 lnControlCode 매개 변수를 ADS_PROPERTY_APPEND 설정해야 합니다. 디렉터리에 변경 내용을 커밋하려면 나중에 IADs::SetInfo 메서드를 호출해야 합니다.

둘 이상의 개체 클래스에 대해 동일한 속성 시트 확장을 등록할 수 있습니다.

IADs::P utEx 메서드는 특성에 새 값을 추가하는 데 사용됩니다. 새 값이 기존 값에 추가되고 기존 값을 덮어쓰지 않도록 lnControlCode 매개 변수를 ADS_PROPERTY_APPEND 설정해야 합니다. 디렉터리에 변경 내용을 커밋하려면 IADs::SetInfo 메서드를 호출해야 합니다.

다음 코드 예제에서는 컴퓨터의 기본 로캘에서 그룹 클래스에 속성 시트 확장을 추가합니다. AddPropertyPageToDisplaySpecifier 함수는 기존 값에서 속성 시트 확장 CLSID를 확인하고, 가장 높은 순서 번호를 가져오고, ADS_PROPERTY_APPEND 제어 코드와 함께 IADs::P utEx를 사용하여 속성 페이지의 값을 추가합니다.

//  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;

}