Query and Restore Deleted AD Objects
Query and Restore Deleted AD Objects
A successful deletion followed by a GUID search returning zero is not contradictory. Ordinary searches hide deleted objects. Conversely, finding a deleted object does not automatically mean complete restoration remains possible.
The examples use LAB-DC1 in corp.test.checo.cc to query by exact GUID and verify Recycle Bin restoration. Query and restoration use different test objects. The restore procedure assumes Recycle Bin was enabled before the object was deleted.
1. Query deleted objects
The query lab first checked RootDSE support for ShowDeletedControl, OID 1.2.840.113556.1.4.417, among 40 advertised controls. It queried a target and a live control, then deleted the target.
| Query | ShowDeletedControl | Result | Count |
|---|---|---|---|
| Target before deletion | No | Success | 1 |
| Target after deletion | No | Success | 0 |
| Same connection/filter after deletion | Yes | Success | 1 |
| Remove the control again | No | Success | 0 |
| Live comparison user | No | Success | 1 |
Zero entries here was not NoSuchObject. The search base was the existing domain naming context; the target was simply outside default visibility. Binding/querying its now-missing old DN is a different operation with potentially different errors.
Use the exact historical GUID and a fixed DC for a read-only AD-module check:
Import-Module ActiveDirectory
$dc = 'LAB-DC1.corp.test.checo.cc'
$id = [guid]'a0e653e5-1aa7-41b6-9d85-78a9ea66d038'
Get-ADObject -Identity $id -Server $dc -IncludeDeletedObjects -Properties `
isDeleted,isRecycled,lastKnownParent,msDS-lastKnownRDN,objectGUID,objectSID,whenChangedThe target was a0e653e5-1aa7-41b6-9d85-78a9ea66d038. Deletion advanced uSNChanged from 167986 to 167990, set isDeleted=True, and preserved lastKnownParent. Its DN retained the old RDN with \0ADEL:<GUID> beneath Deleted Objects. It was not a new same-name user.
2. ShowDeleted control
The original repair added ShowDeletedControl to the same SearchRequest, leaving Base, Scope, Filter, and connection unchanged. GUID filtering uses escaped binary GUID bytes, not an improvised textual DN.
This reviewed example uses the current Windows identity with Negotiate signing/sealing. The caller still needs access to the deleted container/object; the control does not bypass its DACL. It only reads:
Add-Type -AssemblyName System.DirectoryServices.Protocols
$dc = 'LAB-DC1.corp.test.checo.cc'
$guid = [guid]'a0e653e5-1aa7-41b6-9d85-78a9ea66d038'
$filter = '(objectGUID=' + (($guid.ToByteArray() | ForEach-Object {
'\' + $_.ToString('x2')
}) -join '') + ')'
$c = New-Object System.DirectoryServices.Protocols.LdapConnection($dc)
$c.AuthType = [System.DirectoryServices.Protocols.AuthType]::Negotiate
$c.SessionOptions.Signing = $true
$c.SessionOptions.Sealing = $true
try {
$c.Bind()
$base = (Get-ADRootDSE -Server $dc).defaultNamingContext
$q = New-Object System.DirectoryServices.Protocols.SearchRequest(
$base,$filter,[System.DirectoryServices.Protocols.SearchScope]::Subtree,
[string[]]@('objectGUID','isDeleted','lastKnownParent'))
$q.SizeLimit = 3
$q.TimeLimit = [TimeSpan]::FromSeconds(15)
$control = New-Object System.DirectoryServices.Protocols.ShowDeletedControl
$control.IsCritical = $true
[void]$q.Controls.Add($control)
$r = $c.SendRequest($q)
[pscustomobject]@{ ResultCode=$r.ResultCode; Count=$r.Entries.Count }
foreach ($entry in $r.Entries) { $entry.DistinguishedName }
} finally {
$c.Dispose()
}The recorded fixed query returned Count=1 and GuidMatch=True; removing the control returned zero again while the live control remained one. Preserve unsupported critical-control errors rather than treating them as an empty set. Broad deleted-object enumeration also requires object paging; this exact-GUID search intentionally limits results to three. See retrieving deleted objects.
3. Recycle Bin state
The restore lab showed two scopes: the forest configuration Partitions object and the test DC’s NTDS Settings. Two is this environment’s observed count, not a universal forest Boolean.
Check the actual forest scope and retain the list:
Import-Module ActiveDirectory
$dc = 'LAB-DC1.corp.test.checo.cc'
$root = Get-ADRootDSE -Server $dc
$feature = Get-ADOptionalFeature -Identity 'Recycle Bin Feature' -Server $dc
$forestScope = 'CN=Partitions,' + $root.configurationNamingContext
[pscustomobject]@{
Feature = $feature.DistinguishedName
ForestScope = $forestScope
EnabledForForest = @($feature.EnabledScopes) -contains $forestScope
}
$feature.EnabledScopesRepeated enabling returned The specified value already exists; disabling returned The specified method is not supported. Scopes remained unchanged. That records an already-enabled feature and unsuccessful state-changing calls, not a rule to swallow any duplicate-value error as deployment success.
Enabling Recycle Bin is an irreversible forest decision, not the fix for one hidden query result. It also does not retroactively make pre-enablement deletions fully restorable. See Microsoft’s enable/use guidance.
4. Restore and verify
The restore lab created one user with two test-group memberships:
| State | GUID | SID suffix | pwdLastSet | Groups |
|---|---|---|---|---|
| Before deletion | 7fc14e90-009a-4d73-97d4-91665471b05a | 4985 | 134383295727825939 | G1, G2 |
| Deleted | Unchanged | Unchanged | Retained in record | Active group lists no longer showed the deleted user |
| Restored | Unchanged | Unchanged | 134383295727825939 | G1, G2 restored |
uSNChanged advanced from 243928 to 243935 and then 243940; it is not supposed to revert. The DN returned to CN=kb-t41-restore,OU=KB-T4,... and Description, Department, Title, and Office matched the test values.
Unchanged identity differs fundamentally from recreating a same-name account. An unchanged pwdLastSet is property evidence, not an additional historical login test with the old password. Business recovery still needs appropriate authentication/access validation.
After confirming a recoverable deleted state, an existing original parent, and no naming conflict:
$dc = 'LAB-DC1.corp.test.checo.cc'
$id = [guid]'7fc14e90-009a-4d73-97d4-91665471b05a'
$deleted = Get-ADObject -Identity $id -Server $dc -IncludeDeletedObjects -Properties `
isDeleted,isRecycled,lastKnownParent,msDS-lastKnownRDN,objectSID
$deleted | Format-List DistinguishedName,ObjectGUID,ObjectSID,isDeleted,isRecycled,lastKnownParent
Get-ADObject -Identity $deleted.lastKnownParent -Server $dc
Restore-ADObject -Identity $id -Server $dc -WhatIf
# After verifying the exact object, state, and destination:
Restore-ADObject -Identity $id -Server $dc
Get-ADUser -Identity $id -Server $dc -Properties memberOf,pwdLastSet,Description,Department,Title,Office |
Select-Object DistinguishedName,ObjectGUID,SID,Enabled,pwdLastSet,memberOf,Description,Department,Title,OfficeWhatIf helps review the intended target; it does not validate state, all permissions, or post-restore behavior. Replace the historical GUID with the confirmed object. Read from the same DC for comparison and separately account for replication and clients using other DCs.
5. Parent OU and target path
Another control deleted a user and its parent OU. Restoring the user first failed with a parent-related error; restoring the OU first and then the user succeeded. That establishes an ordering requirement for restoring the original layout, not the only possible recovery sequence in every case.
If the original OU is no longer needed, choose an existing TargetPath explicitly:
$target = Read-Host 'Existing destination OU DN'
Get-ADOrganizationalUnit -Identity $target -Server $dc
Restore-ADObject -Identity $id -Server $dc -TargetPath $target -WhatIf
# Execute only after reviewing the new policy/permission scope.A new location changes the DN and can change inherited rights, GPO scope, or path-dependent application behavior. A preserved SID does not guarantee every access result is unchanged. Read an OU’s deletion-protection state after recovery rather than assuming it becomes enabled. See Restore-ADObject TargetPath and default parent.
6. Recovery limits
Distinguish the Recycle Bin deleted stage, subsequent recycled stage, and non-Recycle-Bin tombstone behavior. Attribute retention and recovery paths differ. Neither “still in the database means fully recoverable” nor a universal 180-day lifetime is justified.
This experiment covers short-term delete/restore after prior enablement, not retention expiry or restoration from the recycled stage. When queries return nothing, inspect DC, caller rights, controls, and state before recreating a name. When an object is found, inspect recoverable state, identity, destination, links, properties, and the current ACL.
Restoration changes state and is not a read-only probe with an automatic rollback. Export current target/affected-group state first. If the wrong target was restored, stop downstream changes and investigate rather than blindly deleting the newly restored object again.
