Récupération d’objets supprimés
Les objets supprimés sont stockés dans le conteneur Objets supprimés. Le conteneur Objets supprimés n’est normalement pas visible, mais le conteneur Objets supprimés peut être lié par un membre du groupe Administrateurs. Le contenu du conteneur Objets supprimés peut être énuméré et des attributs d’objet supprimés individuels peuvent être obtenus à l’aide de l’interface IDirectorySearch avec la préférence de recherche ADS_SEARCHPREF_TOMBSTONE .
Le conteneur Objets supprimés peut être obtenu en liant au GUID bien connu GUID_DELETED_OBJECTS_CONTAINER défini dans Ntdsapi.h. Pour plus d’informations sur la liaison à des GUID connus, consultez Liaison à des objets Well-Known à l’aide de WKGUID.
Spécifiez l’option ADS_FAST_BIND lors de la liaison au conteneur Objets supprimés. Cela signifie que les interfaces ADSI utilisées pour travailler avec un objet dans services de domaine Active Directory, telles que IADs et IADsPropertyList, ne peuvent pas être utilisées sur le conteneur Objets supprimés. Pour plus d’informations et un exemple de code qui montre comment lier au conteneur Objets supprimés, consultez l’exemple de fonction GetDeletedObjectsContainer ci-dessous.
Énumération des objets supprimés
L’interface IDirectorySearch est utilisée pour rechercher des objets supprimés.
Pour énumérer les objets supprimés
- Obtenez l’interface IDirectorySearch pour le conteneur Objets supprimés. Pour ce faire, vous devez lier au conteneur Objets supprimés et demander l’interface IDirectorySearch . Pour plus d’informations et un exemple de code qui montre comment lier au conteneur Objets supprimés, consultez l’exemple de fonction GetDeletedObjectsContainer suivant.
- Définissez la préférence de recherche ADS_SEARCHPREF_SEARCH_SCOPEsur ADS_SCOPE_ONELEVEL à l’aide de la méthode IDirectorySearch::SetSearchPreference . La préférence ADS_SCOPE_SUBTREE peut également être utilisée, mais le conteneur Objets supprimés n’est qu’un seul niveau. L’utilisation de ADS_SCOPE_SUBTREE est donc redondante.
- Définissez la préférence de recherche ADS_SEARCHPREF_TOMBSTONE sur TRUE. La recherche inclut alors des objets supprimés.
- Définissez la préférence de recherche ADS_SEARCHPREF_PAGESIZE sur une valeur inférieure ou égale à 1 000. Cette opération est facultative, mais si cela n’est pas fait, plus de 1 000 objets supprimés peuvent être récupérés.
- Définissez le filtre de recherche dans l’appel IDirectorySearch::ExecuteSearch sur « (isDeleted=TRUE) ». La recherche récupère alors uniquement les objets avec l’attribut isDeleted défini sur TRUE.
Pour obtenir un exemple de code montrant comment énumérer des objets supprimés, consultez l’exemple de fonction EnumDeletedObjects suivant.
La recherche peut être affinée en ajoutant au filtre de recherche comme indiqué dans LDAP Dialect. Par exemple, pour rechercher tous les objets supprimés dont le nom commence par « Jeff », le filtre de recherche est défini sur « (&(isDeleted=TRUE)(cn=Jeff*)) ».
Étant donné que la plupart de leurs attributs sont supprimés lorsque les objets supprimés sont supprimés, il n’est pas possible de lier directement à un objet supprimé. L’option ADS_FAST_BIND doit être spécifiée lors de la liaison à un objet supprimé. Cela signifie que les interfaces ADSI utilisées pour travailler avec un objet services de domaine Active Directory, comme IADs et IADsPropertyList, ne peuvent pas être utilisées sur un conteneur d’objets supprimés.
Recherche d’un objet supprimé spécifique
Il est également possible de trouver un objet supprimé spécifique. Si l’objetGUID de l’objet est connu, il peut être utilisé pour rechercher l’objet avec cet objet spécifiqueGUID. Pour plus d’informations et un exemple de code qui montre comment rechercher un objet supprimé spécifique, consultez FindDeletedObjectByGUID ci-dessous.
GetDeletedObjectsContainer
L’exemple de code C++ suivant montre comment établir une liaison au conteneur Objets supprimés.
//***************************************************************************
//
// 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
L’exemple de code C++ suivant montre comment énumérer les objets dans le conteneur Objets supprimés.
//***************************************************************************
//
// 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
L’exemple de code C++ suivant montre comment rechercher un objet supprimé spécifique de la propriété objectGUID de l’objet.
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;
}