在顯示規範中註冊屬性頁 COM 物件
當您使用 COM 建立 Active Directory 網域服務 的屬性表擴充 DLL 時,也必須向 Windows 登錄註冊延伸模組,並 Active Directory 網域服務。 註冊擴充功能可讓Active Directory系統管理 MMC 嵌入式管理單元和 Windows 殼層辨識延伸模組。
在 Windows 登錄中註冊
和所有 COM 伺服器一樣,必須在 Windows 登錄中註冊屬性表延伸模組。 延伸模組會在下列金鑰下註冊。
HKEY_CLASSES_ROOT
CLSID
<clsid>
<clsid> 是 StringFromCLSID 函式所產生的 CLSID 字串表示法。 在 <clsid> 機碼下,有一個 InProcServer32 索引鍵,會將對象識別為 32 位內部伺服器。 在 InProcServer32 機碼下,DLL 的位置是在預設值中指定,而線程模型是在 ThreadingModel 值中指定。 所有屬性表延伸模組都必須使用 「Apartment」 線程模型。
向 Active Directory 網域服務 註冊
屬性表延伸模組註冊專屬於一個地區設定。 如果屬性表延伸模組套用至所有地區設定,則必須在 Display Specifiers 容器中所有地區設定子控件的 object 類別 displaySpecifier 物件中註冊。 如果屬性表延伸已針對特定地區設定當地語系化,請在該locale子容器的displaySpecifier物件中註冊它。 如需顯示規範容器和地區設定的詳細資訊,請參閱 顯示規範 和 DisplaySpecifiers 容器。
屬性表延伸模組可登錄的三個顯示規範屬性。 這些是 adminPropertyPages、shellPropertyPages 和 adminMultiselectPropertyPages。
adminPropertyPages 屬性會識別要顯示在Active Directory系統管理嵌入式管理單元中的系統管理屬性頁。當用戶檢視其中一個 Active Directory 系統管理 MMC 嵌入式管理單元中適當類別對象的屬性時,就會顯示屬性頁。
shellPropertyPages 屬性會識別要顯示在 Windows 殼層中的使用者屬性頁。 當使用者在 Windows 檔案總管中檢視適當類別對象的屬性時,就會顯示屬性頁。 從 Windows Server 2003 作業系統開始,Windows 殼層不再顯示來自 Active Directory 網域服務 的物件。
adminMultiselectPropertyPages 僅適用於 Windows Server 2003 操作系統。 當用戶檢視其中一個 Active Directory 系統管理 MMC 嵌入式管理單元中適當類別之多個物件的屬性時,就會顯示屬性頁。
所有這些屬性都是多重值。
adminPropertyPages 和 shellPropertyPages 屬性的值需要下列格式。
<order number>,<clsid>,<optional data>
adminMultiselectPropertyPages 屬性的值需要下列格式。
<order number>,<clsid>
「<訂單編號>」是一個不帶正負號的數位,代表工作表中的頁面位置。 當屬性表顯示時,值會使用每個值的「<訂單號碼>」的比較來排序。 如果多個值具有相同的「<訂單號碼>」,這些屬性頁 COM 物件會依從 Active Directory 伺服器讀取的順序載入。 可能的話,您應該使用非現有的「<訂單號碼>」;也就是說,屬性中的其他值不會使用。 沒有規定的起始位置,而且「訂單號碼>」序列中允許<間距。
“clsid>” 是 StringFromCLSID 函式所產生的 CLSID 字串表示法。<
「<選擇性數據>」是不需要的字串值。 這個值可由屬性頁 COM 物件使用傳遞給其 IShellExtInit::Initialize 方法的 IDataObject 指標來擷取。 屬性頁 COM 物件會呼叫具有 CFSTR_DSPROPERTYPAGEINFO剪貼簿格式的 IDataObject::GetData,以取得此數據。 這會提供包含 DSPROPERTYPAGEINFO 結構的 HGLOBAL。DSPROPERTYPAGEINFO 結構包含包含“<選擇性數據的> Unicode 字串”。 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 殼層,這表示顯示規範的變更會在使用者註銷後生效,然後再次登入。 針對系統管理嵌入式管理單元,變更會在載入式管理單元或主控台檔案時生效。
將值新增至屬性表延伸屬性
下列程式描述如何在其中一個屬性表延伸屬性下註冊屬性表延伸模組。
在其中一個屬性表延伸屬性下註冊屬性表延伸模組
- 請確定延伸模組不存在於屬性值中。
- 在屬性頁排序列表結尾新增值。 這會將屬性值的「<訂單編號」部分設定為最高現有訂單編號>之後的下一個值。
- IADs::P utEx 方法可用來將新值新增至 屬性。 lnControlCode 參數必須設定為 ADS_PROPERTY_APPEND,讓新的值附加至現有的值,因此不會覆寫現有的值。 之後必須呼叫 IADs::SetInfo 方法,才能將變更認可至目錄。
請注意,可以註冊多個物件類別的相同屬性表延伸模組。
IADs::P utEx 方法可用來將新值新增至 屬性。 lnControlCode 參數必須設定為 ADS_PROPERTY_APPEND,讓新的值附加至現有的值,因此不會覆寫現有的值。 必須在 之後呼叫 IADs::SetInfo 方法,才能認可目錄的變更。
下列程式代碼範例會將屬性表延伸新增至計算機預設地區設定中的群組類別。 請注意,AddPropertyPageToDisplaySpecifier 函式會驗證現有值中的屬性表延伸 CLSID、取得最高順序編號,並使用 IADs::P utEx 搭配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;
}