Directory Services

Restoring Deleted Objects

Windows Server 2003 includes the restore deleted objects feature.

To enable deleted object restoration, at least one domain controller in the domain must be running on Windows Server 2003. By default, only domain administrators can restore deleted objects, though this can be delegated to others.

The following limitations apply to restoring deleted objects:

Permissions Required to Restore a Deleted Object

When an object is deleted, the object security descriptor is retained. Although the owner is identifiable from the security descriptor, for security reasons, only domain administrators are allowed to restore deleted objects. Domain administrators can grant the permission to restore delete objects to other users and groups by granting the user or group the "Reanimate Tombstone" control access right. The "Reanimate Tombstone" control access right is granted at the Naming Context root. Only users that had read access permission on an object and its attributes are permitted to read the object and accessible attributes after the object is deleted.

Note  Granting a user this permission can be a security risk because it could permit the user to restore an account object that has access to resources that the user would not normally have access to. By restoring an account, the user essentially gains control of this account because the user must set the initial password on the account when the account is restored.

To completely restore a deleted object, the user must:

Restoring a Deleted Object

To restore a deleted object, the object must first be located in the Deleted Objects container. For more information about retrieving deleted objects, see Retrieving Deleted Objects.

When the object has been located, the following operations must be completed in a single LDAP operation. This requires the use of the ldap_modify_ext_s function with the LDAP_SERVER_SHOW_DELETED_OID control.

Note  The objectCategory attribute can also be set when the object is restored, but is not required. If no objectCategory value is specified, the default objectCategory for the object's objectClass is used.

After the object is restored, it can be accessed as it was before it was deleted. At this point, any optional attributes that are important should be restored. Any references to the object from other objects in the directory must also be restored.

As a security measure, user objects are disabled when they are restored. User objects must be enabled after restoring the optional attributes to allow the user object to be used.

For more information and a code example that shows how to restore a deleted object, see the RestoreDeletedObject function below.

Example Code

The following code example shows how to restore a deleted object.

//***************************************************************************
//
//  RestoreDeletedObject()
//
//  Restores a deleted object. 
//
//  pwszDeletedDN - Contains the fully-qualified distinguished name of the 
//  deleted object.
//
//  pwszDestContainerDN - Contains the fully-qualified distinguished name of 
//  the folder that the delted object should be moved to.
//
//  Returns S_OK if successful or an HRESULT or LDAP error code otherwise.
//
//***************************************************************************

HRESULT RestoreDeletedObject(LPCWSTR pwszDeletedDN, LPCWSTR pwszDestContainerDN)
{
	if((NULL == pwszDeletedDN) || (NULL == pwszDestContainerDN))
	{
		return E_POINTER;
}

	HRESULT hr = E_FAIL;

	// Build the new distinguished name.
	LPWSTR pwszNewDN = new WCHAR[lstrlenW(pwszDeletedDN) + lstrlenW(pwszDestContainerDN) + 1];
	if(pwszNewDN)
	{
		lstrcpyW(pwszNewDN, pwszDeletedDN);

		// Search for the first 0x0A character. This is the delimiter in the deleted object name.
		LPWSTR pwszChar;
		for(pwszChar = pwszNewDN; *pwszChar; pwszChar = CharNextW(pwszChar))
		{
			if(('\\' == *pwszChar) && ('0' == *(pwszChar + 1)) && ('A' == *(pwszChar + 2)))
			{
				break;
		}
		
	}

		if(0 != *pwszChar)
		{
			// Truncate the name string at the delimiter.
			*pwszChar = 0;

			// Add the last known parent DN to complete the DN.
			lstrcatW(pwszNewDN, L",");
			lstrcatW(pwszNewDN, pwszDestContainerDN);

			PLDAP	 ld;

			// Initialize LDAP.
			ld = ldap_init(NULL, LDAP_PORT);
			if(NULL != ld) 
			{
				ULONG ulRC;
				ULONG version = LDAP_VERSION3;

				// Set the LDAP version.
				ulRC = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, (void*)&version);
				if(LDAP_SUCCESS == ulRC)
				{
					// Establish a connection with the server.
					ulRC = ldap_connect(ld, NULL);
					if(LDAP_SUCCESS == ulRC)
					{ 			
						// Bind to the LDAP server.
						ulRC = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE);
						if(LDAP_SUCCESS == ulRC)
						{
							// Setup the new values.
							LPWSTR rgNewVals[] = {pwszNewDN, NULL};

							/*
							Remove the isDeleted attribute. This cannot be set 
							to FALSE or the restore operation will not work.
							*/
							LDAPModW modIsDeleted = { LDAP_MOD_DELETE, L"isDeleted", NULL };

							/*
							Set the new DN, in effect, moving the deleted 
							object to where it resided before the deletion.
							*/
							LDAPModW modDN = { LDAP_MOD_REPLACE, L"distinguishedName", rgNewVals };
						
							// Initialize the LDAPMod structure.
							PLDAPModW ldapMods[] = 
							{
								&modIsDeleted,
								&modDN,
								NULL
						};

							/*
							Use the LDAP_SERVER_SHOW_DELETED_OID control to 
							modify deleted objects.
							*/
							LDAPControlW showDeletedControl;
							showDeletedControl.ldctl_oid = LDAP_SERVER_SHOW_DELETED_OID_W;
							showDeletedControl.ldctl_value.bv_len = 0;
							showDeletedControl.ldctl_value.bv_val = NULL;
							showDeletedControl.ldctl_iscritical = TRUE;

							// Initialzie the LDAPControl structure
							PLDAPControlW ldapControls[] = { &showDeletedControl, NULL };

							/*
							Modify the specified attributes. This must performed 
							in one step, which is why the LDAP APIs must be used 
							to restore a deleted object.
							*/
							ulRC = ldap_modify_ext_sW(ld, (PWCHAR)pwszDeletedDN, ldapMods, ldapControls, NULL);
							if(LDAP_SUCCESS == ulRC)
							{
								hr = S_OK;
						}
							else if(LDAP_ALREADY_EXISTS == ulRC)
							{
								/*
								An object already exists with the specified name 
								in the specified target container. At this point, 
								a new name must be selected.
								*/
						}
					}
				}
			}

				if(LDAP_SUCCESS != ulRC)
				{
					hr = ulRC;
				
					OutputDebugString(ldap_err2string(ulRC));
			}

				// Release the LDAP session.
				ldap_unbind(ld);
		}
	}
		else
		{
			/*
			If the end of the string is reached before the delimiter is found, just 
			end and fail.
			*/
			hr = E_INVALIDARG;
	}

		delete pwszNewDN;
}
	else
	{
		hr = E_OUTOFMEMORY;
}

	return hr;
}