Abrufen gelöschter Objekte
Gelöschte Objekte werden im Container Gelöschte Objekte gespeichert. Der Container "Gelöschte Objekte" ist normalerweise nicht sichtbar, aber der Container "Gelöschte Objekte" kann von einem Mitglied der Administratorgruppe gebunden werden. Der Inhalt des Containers "Gelöschte Objekte" kann aufgelistet werden, und einzelne gelöschte Objektattribute können mithilfe der IDirectorySearch-Schnittstelle mit der Sucheinstellung ADS_SEARCHPREF_TOMBSTONE abgerufen werden.
Der Container Deleted Objects kann durch Bindung an die bekannte GUID abgerufen werden , die in Ntdsapi.h definiert GUID_DELETED_OBJECTS_CONTAINER. Weitere Informationen zur Bindung an bekannte GUIDs finden Sie unter Bindung an Well-Known Objekte mithilfe von WKGUID.
Geben Sie bei der Bindung an den Container Deleted Objects die Option ADS_FAST_BIND an. Dies bedeutet, dass die ADSI-Schnittstellen, die für die Arbeit mit einem Objekt in Active Directory Domain Services verwendet werden, z. B. IADs und IADsPropertyList, nicht für den Container Deleted Objects verwendet werden können. Weitere Informationen und ein Codebeispiel, das zeigt, wie Sie an den Container Deleted Objects gebunden werden, finden Sie in der Beispielfunktion GetDeletedObjectsContainer unten.
Auflisten gelöschter Objekte
Die IDirectorySearch-Schnittstelle wird verwendet, um nach gelöschten Objekten zu suchen.
So enumerieren Sie gelöschte Objekte
- Rufen Sie die IDirectorySearch-Schnittstelle für den Container Deleted Objects ab. Dies wird erreicht, indem Sie an den Container Deleted Objects binden und die IDirectorySearch-Schnittstelle anfordern. Weitere Informationen und ein Codebeispiel, das zeigt, wie sie an den Container Deleted Objects gebunden werden, finden Sie im folgenden Beispiel für die GetDeletedObjectsContainer-Funktion .
- Legen Sie die ADS_SEARCHPREF_SEARCH_SCOPE Sucheinstellung mit der IDirectorySearch::SetSearchPreference-Methode auf ADS_SCOPE_ONELEVEL fest. Die Einstellung ADS_SCOPE_SUBTREE kann auch verwendet werden, aber der Container "Gelöschte Objekte" ist nur eine Ebene, sodass die Verwendung ADS_SCOPE_SUBTREE redundant ist.
- Legen Sie die ADS_SEARCHPREF_TOMBSTONE Sucheinstellung auf TRUE fest. Dies führt dazu, dass die Suche gelöschte Objekte einschließt.
- Legen Sie ADS_SEARCHPREF_PAGESIZE Sucheinstellung auf einen Wert kleiner oder gleich 1000 fest. Dies ist optional, aber wenn dies nicht geschieht, können nicht mehr als 1.000 gelöschte Objekte abgerufen werden.
- Legen Sie den Suchfilter im IDirectorySearch::ExecuteSearch-Aufruf auf "(isDeleted=TRUE)" fest. Dies führt dazu, dass die Suche nur Objekte abruft, deren Attribut isDeleted auf TRUE festgelegt ist.
Einen Codebeispielcode, der zeigt, wie gelöschte Objekte aufgelistet werden, finden Sie im folgenden EnumDeletedObjects-Funktionsbeispiel .
Die Suche kann weiter eingeschränkt werden, indem sie dem Suchfilter hinzugefügt wird, wie in LDAP-Dialekt dargestellt. Um beispielsweise nach allen gelöschten Objekten mit einem Namen zu suchen, der mit "Jeff" beginnt, wird der Suchfilter auf "(&(isDeleted=TRUE)(cn=Jeff*))" festgelegt.
Da bei gelöschten Objekten die meisten Attribute entfernt werden, wenn sie gelöscht werden, ist es nicht möglich, direkt an ein gelöschtes Objekt zu binden. Die option ADS_FAST_BIND muss beim Binden an ein gelöschtes Objekt angegeben werden. Dies bedeutet, dass die ADSI-Schnittstellen, die zum Arbeiten mit einem Active Directory Domain Services-Objekt wie IADs und IADsPropertyList verwendet werden, nicht für einen gelöschten Objektcontainer verwendet werden können.
Suchen nach einem bestimmten gelöschten Objekt
Es ist auch möglich, ein bestimmtes gelöschtes Objekt zu finden. Wenn die objectGUID des Objekts bekannt ist, kann es verwendet werden, um nach dem Objekt mit diesem spezifischen objectGUID zu suchen. Weitere Informationen und ein Codebeispiel, das zeigt, wie Sie ein bestimmtes gelöschtes Objekt finden, finden Sie unten unter FindDeletedObjectByGUID .
GetDeletedObjectsContainer
Das folgende C++-Codebeispiel zeigt, wie sie an den Container Deleted Objects gebunden werden.
//***************************************************************************
//
// GetDeletedObjectsContainer()
//
// Binds to the Deleted Object container.
//
//***************************************************************************
HRESULT GetDeletedObjectsContainer(IADsContainer **ppContainer)
{
if(NULL == ppContainer)
{
return E_INVALIDARG;
}
HRESULT hr;
IADs *pRoot;
*ppContainer = NULL;
// Bind to the rootDSE object.
hr = ADsOpenObject(L"LDAP://rootDSE",
NULL,
NULL,
ADS_SECURE_AUTHENTICATION,
IID_IADs,
(LPVOID*)&pRoot);
if(SUCCEEDED(hr))
{
VARIANT var;
VariantInit(&var);
// Get the current domain DN.
hr = pRoot->Get(CComBSTR("defaultNamingContext"), &var);
if(SUCCEEDED(hr))
{
// Build the binding string.
LPWSTR pwszFormat = L"LDAP://<WKGUID=%s,%s>";
LPWSTR pwszPath;
pwszPath = new WCHAR[wcslen(pwszFormat) + wcslen(GUID_DELETED_OBJECTS_CONTAINER_W) + wcslen(var.bstrVal)];
if(NULL != pwszPath)
{
swprintf_s(pwszPath, pwszFormat, GUID_DELETED_OBJECTS_CONTAINER_W, var.bstrVal);
// Bind to the object.
hr = ADsOpenObject(pwszPath,
NULL,
NULL,
ADS_FAST_BIND | ADS_SECURE_AUTHENTICATION,
IID_IADsContainer,
(LPVOID*)ppContainer);
delete pwszPath;
}
else
{
hr = E_OUTOFMEMORY;
}
VariantClear(&var);
}
pRoot->Release();
}
return hr;
}
EnumDeletedObjects
Das folgende C++-Codebeispiel zeigt, wie die Objekte im Container "Gelöschte Objekte" aufgelistet werden.
//***************************************************************************
//
// EnumDeletedObjects()
//
// Enumerates all of the objects in the Deleted Objects container.
//
//***************************************************************************
HRESULT EnumDeletedObjects()
{
HRESULT hr;
IADsContainer *pDeletedObjectsCont = NULL;
IDirectorySearch *pSearch = NULL;
hr = GetDeletedObjectsContainer(&pDeletedObjectsCont);
if(FAILED(hr))
{
goto cleanup;
}
hr = pDeletedObjectsCont->QueryInterface(IID_IDirectorySearch, (LPVOID*)&pSearch);
if(FAILED(hr))
{
goto cleanup;
}
ADS_SEARCH_HANDLE hSearch;
// Only search for direct child objects of the container.
ADS_SEARCHPREF_INFO rgSearchPrefs[3];
rgSearchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
rgSearchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;
rgSearchPrefs[0].vValue.Integer = ADS_SCOPE_ONELEVEL;
// Search for deleted objects.
rgSearchPrefs[1].dwSearchPref = ADS_SEARCHPREF_TOMBSTONE;
rgSearchPrefs[1].vValue.dwType = ADSTYPE_BOOLEAN;
rgSearchPrefs[1].vValue.Boolean = TRUE;
// Set the page size.
rgSearchPrefs[2].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
rgSearchPrefs[2].vValue.dwType = ADSTYPE_INTEGER;
rgSearchPrefs[2].vValue.Integer = 1000;
// Set the search preference
hr = pSearch->SetSearchPreference(rgSearchPrefs, ARRAYSIZE(rgSearchPrefs));
if(FAILED(hr))
{
goto cleanup;
}
// Set the search filter.
LPWSTR pszSearch = L"(cn=*)";
// Set the attributes to retrieve.
LPWSTR rgAttributes[] = {L"cn", L"distinguishedName", L"lastKnownParent"};
// Execute the search
hr = pSearch->ExecuteSearch( pszSearch,
rgAttributes,
ARRAYSIZE(rgAttributes),
&hSearch);
if(SUCCEEDED(hr))
{
// Call IDirectorySearch::GetNextRow() to retrieve the next row of data
while(S_OK == (hr = pSearch->GetNextRow(hSearch)))
{
ADS_SEARCH_COLUMN col;
UINT i;
// Enumerate the retrieved attributes.
for(i = 0; i < ARRAYSIZE(rgAttributes); i++)
{
hr = pSearch->GetColumn(hSearch, rgAttributes[i], &col);
if(SUCCEEDED(hr))
{
switch(col.dwADsType)
{
case ADSTYPE_CASE_IGNORE_STRING:
case ADSTYPE_DN_STRING:
case ADSTYPE_PRINTABLE_STRING:
case ADSTYPE_NUMERIC_STRING:
case ADSTYPE_OCTET_STRING:
wprintf(L"%s: ", rgAttributes[i]);
for(DWORD x = 0; x < col.dwNumValues; x++)
{
wprintf(col.pADsValues[x].CaseIgnoreString);
if((x + 1) < col.dwNumValues)
{
wprintf(L",");
}
}
wprintf(L"\n");
break;
}
pSearch->FreeColumn(&col);
}
}
wprintf(L"\n");
}
// Close the search handle to cleanup.
pSearch->CloseSearchHandle(hSearch);
}
cleanup:
if(pDeletedObjectsCont)
{
pDeletedObjectsCont->Release();
}
if(pSearch)
{
pSearch->Release();
}
return hr;
}
FindDeletedObjectByGUID
Das folgende C++-Codebeispiel zeigt, wie Sie ein bestimmtes gelöschtes Objekt aus der objectGUID-Eigenschaft des Objekts finden.
HRESULT FindDeletedObjectByGUID(IADs *padsDomain, GUID *pguid)
{
HRESULT hr;
IDirectorySearch *pSearch = NULL;
LPWSTR pwszGuid = NULL;
hr = padsDomain->QueryInterface(IID_IDirectorySearch, (LPVOID*)&pSearch);
if(FAILED(hr))
{
goto cleanup;
}
ADS_SEARCH_HANDLE hSearch;
// Search the entire tree.
ADS_SEARCHPREF_INFO rgSearchPrefs[2];
rgSearchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
rgSearchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;
rgSearchPrefs[0].vValue.Integer = ADS_SCOPE_SUBTREE;
// Search for deleted objects.
rgSearchPrefs[1].dwSearchPref = ADS_SEARCHPREF_TOMBSTONE;
rgSearchPrefs[1].vValue.dwType = ADSTYPE_BOOLEAN;
rgSearchPrefs[1].vValue.Boolean = TRUE;
// Set the search preference.
hr = pSearch->SetSearchPreference(rgSearchPrefs, 2);
if(FAILED(hr))
{
goto cleanup;
}
// Set the search filter.
hr = ADsEncodeBinaryData((LPBYTE)pguid, sizeof(GUID), &pwszGuid);
if(FAILED(hr))
{
goto cleanup;
}
LPWSTR pwszFormat = L"(objectGUID=%s)";
LPWSTR pwszSearch = new WCHAR[lstrlenW(pwszFormat) + lstrlenW(pwszGuid) + 1];
if(NULL == pwszSearch)
{
goto cleanup;
}
swprintf_s(pwszSearch, pwszFormat, pwszGuid);
FreeADsMem(pwszGuid);
// Set the attributes to retrieve.
LPWSTR rgAttributes[] = {L"cn", L"distinguishedName", L"lastKnownParent"};
// Execute the search.
hr = pSearch->ExecuteSearch( pwszSearch,
rgAttributes,
3,
&hSearch);
if(SUCCEEDED(hr))
{
// Call IDirectorySearch::GetNextRow() to retrieve the next row of data.
while(S_OK == (hr = pSearch->GetNextRow(hSearch)))
{
ADS_SEARCH_COLUMN col;
UINT i;
// Enumerate the retrieved attributes.
for(i = 0; i < ARRAYSIZE(rgAttributes); i++)
{
hr = pSearch->GetColumn(hSearch, rgAttributes[i], &col);
if(SUCCEEDED(hr))
{
switch(col.dwADsType)
{
case ADSTYPE_CASE_IGNORE_STRING:
case ADSTYPE_DN_STRING:
case ADSTYPE_PRINTABLE_STRING:
case ADSTYPE_NUMERIC_STRING:
wprintf(L"%s: ", rgAttributes[i]);
for(DWORD x = 0; x < col.dwNumValues; x++)
{
wprintf(col.pADsValues[x].CaseIgnoreString);
if((x + 1) < col.dwNumValues)
{
wprintf(L",");
}
}
wprintf(L"\n");
break;
case ADSTYPE_OCTET_STRING:
wprintf(L"%s: ", rgAttributes[i]);
for(DWORD x = 0; x < col.dwNumValues; x++)
{
GUID guid;
LPBYTE pb = col.pADsValues[x].OctetString.lpValue;
WCHAR wszGUID[MAX_PATH];
// Convert the octet string into a GUID.
guid.Data1 = *((long*)pb);
pb += sizeof(guid.Data1);
guid.Data2 = *((short*)pb);
pb += sizeof(guid.Data2);
guid.Data3 = *((short*)pb);
pb += sizeof(guid.Data3);
CopyMemory(&guid.Data4, pb, sizeof(guid.Data4));
// Convert the GUID into a string.
StringFromGUID2(guid, wszGUID, MAX_PATH);
wprintf(wszGUID);
if((x + 1) < col.dwNumValues)
{
wprintf(L",");
}
OutputDebugStringW(wszGUID);
OutputDebugStringW(L"\n");
{
DWORD a;
for(a = 0, *wszGUID = 0; a < col.pADsValues[x].OctetString.dwLength; a++)
{
swprintf_s(wszGUID + lstrlenW(wszGUID), L"%02X", *(LPBYTE)(col.pADsValues[x].OctetString.lpValue + a));
}
OutputDebugStringW(wszGUID);
OutputDebugStringW(L"\n");
}
}
wprintf(L"\n");
break;
}
pSearch->FreeColumn(&col);
}
}
wprintf(L"\n");
}
// Close the search handle to cleanup.
pSearch->CloseSearchHandle(hSearch);
}
cleanup:
if(pwszSearch)
{
delete pwszSearch;
}
if(pSearch)
{
pSearch->Release();
}
return hr;
}