How to restore a tree of objects from the Active Directory Recycle Bin
Active Directory Recycle Bin - This article is part of a series.
In a previous post I detailed how to setup the Active Directory Recycle Bin and restore single deleted objects back in your Active Directory. Restoring a whole tree of objects though (i.e. Organisational Unit and the objects inside it) is no easy process.
Thankfully Microsoft via the community have provided a example script to do this:
Param (
$lastKnownRDN,
$lastKnownParent,
$identity,
$partition,
[switch] $includelivechildren,
[switch] $whatIf,
[switch] $verbose
)
###############################
## Display Help ##
###############################
function Display-Help {
""
"Usage:"
""
"Restore-ADTree -lastKnownRDN "
"[-lastKnownParent ]"
"[-partition ]"
"[-includeLiveChildren]"
"[-whatif]"
"[-verbose]"
"OR"
""
"Restore-ADTree -identity "
"[-partition ]"
"[-includeLiveChildren]"
"[-whatif]"
"[-verbose]"
""
"Examples:"
""
"Restore-ADTree -lastknownRDN Accounting"
""
"Restore-ADTree -lastKnownRDN Accounting -lastknownParent ""DC=CONTOSO,DC=COM"" "
""
"Restore-ADTree -identity b48290aa-e14f-4417-9c03-560a546d18b9"
""
"Restore-ADTree -identity ""OU=Accounting,DC=CONTOSO,DC=COM"" "
""
}
###############################
## Validate Parameters ##
###############################
if (!(($identity) -or ($lastKnownRDN))){
display-help
break
}
if (($identity) -and (($lastKnownRDN) -or ($lastKnownParent))){
display-help
break
}
####################################################
## INCOMPLETE Get RDNType given objClass ##
####################################################
Function Get-RDNType {
Param($objClass)
switch ($objClass){
"container"{Return "CN"}
"OrganizationalUnit" {Return "OU"}
default {Return "CN"}
}
}
############################################
## Restore-Tree Recursive Function ##
############################################
function Restore-Tree($strObjectGUID ,$strNamingContext, $bIncludeLiveChildren, $bWhatIf ,$strRestoredPrevParentDN)
{
$objRestoredParent = $null
$objRestoredParent = get-adobject -identity $strObjectGUID -partition $strNamingContext
if ($objRestoredParent){
if (!($bWhatIf)){
Write-Host ""Not restoring live object $objRestoredParent.distinguishedName ."" -ForeGroundColor Yellow
} else {
Write-Host ""Will not restore live object $objRestoredParent.distinguishedName ."" -ForeGroundColor Yellow
}
$strLiveSearchBase = $objRestoredParent.distinguishedName
$RestoredDN = $objRestoredParent.distinguishedName
} else {
if (!($bWhatIf)){
Restore-ADobject -identity $strObjectGUID -partition $strNamingContext -errorVariable errRestore
if (($errRestore)){
$objRestoredParent = get-adobject -identity $strObjectGUID -partition $strNamingContext -includeDeletedObjects
Write-Host ""Restore of object $objRestoredParent.distinguishedName failed.`n Error: $errRestore"" -ForeGroundColor Red
Exit Function
} else {
$objRestoredParent = get-adobject -identity $strObjectGUID -partition $strNamingContext
$RestoredDN = $objRestoredParent.distinguishedName
Write-Host ""Successfully restored object $objRestoredParent.DistinguishedName"" -ForeGroundColor Green
}
} else {
$objRestoredParent = get-adobject -identity $strObjectGUID -partition $strNamingContext -properties msds-lastknownRDN,lastKnownParent,whenChanged -includeDeletedObjects
$RestoredDN = $(Get-RDNType($objRestoredParent.ObjectClass)) + "=" + $objRestoredParent.("msds-lastKnownRDN") + "," + $strRestoredPrevParentDN
Write-Host ""Will restore deleted object $RestoredDN"" -ForeGroundColor Green
if ($verbose){
Write-Host ""Deleted DN: $objRestoredParent.distinguishedName `n whenDeleted: $objRestoredParent.("whenChanged") ""
}
}
$strLiveSearchBase = $null
}
if (($strLiveSearchBase) -and ($bIncludeLiveChildren)) {
$strFilter = "(objectClass=*)"
$objChildren = get-adobject -SearchScope onelevel -SearchBase $strLiveSearchBase -ldapFilter $strFilter -ResultPageSize 300 -ResultSetSize 10000
if ($objChildren -ne $null){
foreach ($objChild in $objChildren)
{
Restore-Tree $objChild.objectGUID $strNamingContext $bIncludeLiveChildren $bWhatIf $RestoredDN
}
}
}
$strSearchBase = "CN=Deleted Objects,"+$strNamingContext
$strFilter = "(lastknownParent=" + $objRestoredParent.distinguishedName.Replace("\0","\\0") + ")"
$objChildren = get-adobject -SearchScope subtree -SearchBase $strSearchBase -includedeletedobjects -ldapFilter $strFilter -ResultPageSize 300 -ResultSetSize 10000
if ($objChildren -ne $null){
Write-Host ""Restoring deleted children of $RestoredDN""
foreach ($objChild in $objChildren)
{
Restore-Tree $objChild.objectGUID $strNamingContext $bIncludeLiveChildren $bWhatIf $RestoredDN
}
}
}
######################
## Main Function ##
######################
$ErrorActionPreference = "SilentlyContinue"
if (!($partition)){
$strNamingContext = [string] (get-adrootDSE).defaultNamingContext
} else {
$strNamingContext = $partition
}
$strDelObjContainer = "CN=Deleted Objects,"+$strNamingContext
if ($identity){
$objSearchResult = get-adobject -identity $identity -partition $strNamingContext -includeDeletedObjects
} else {
$strFilter = "(msds-lastknownRDN=" + $lastKnownRDN + ")"
$objSearchResult = get-adobject -SearchScope subtree -SearchBase $strDelObjContainer -includedeletedobjects -ldapFilter $strFilter -properties lastknownparent,whenChanged,isDeleted
}
If (!($objSearchResult)) {
Write-Host "Search for tree root returned 0 objects.Exiting without making changes.";Exit
} Else {
$objMeasure = $objSearchResult | Measure-Object
If ($objMeasure.Count -gt 1) {
Write-Host "Search for tree root returned more than one object.Rerun command and select one of below objects" -ForeGroundColor Yellow
foreach ($objRoot in $objSearchResult) { $objRoot}
break
}
}
if ($objSearchResult.isDeleted) {
$PrevParent = $objSearchResult.("lastKnownParent")
$bRootIsDeleted = $true
} else {
$PrevParent = $objSearchResult.distinguishedName
$bRootIsDeleted = $false
}
Restore-Tree $objSearchResult.objectGUID $strNamingContext $includelivechildren $whatIf $PrevParent -errorVariable errRestore
Copy the above into a Powershell Script file named restoreadtree.ps1. There are a number of parameters we can use to perform the restore depending on your knowledge of the deleted object.
If you know the exact path of the Organisational Unit:
.\restoradtree.ps1 -identity ""OU=Admins,DC=DXPETTI,DC=COM""
This would restore the Organisational Unit “Admins” that sat at the top level of the DXPETTI.COM Active Directory domain.
Or if you couldn’t remember where an OU sat but still remember the name:
.\restoreadtree.ps1 -lastKnownRDN Admins
This would restore the Organisational Unit Admins despite not remembering the full path.
But what if you had multiple OUs with the same name? If you remember a part of the path then:
.\restoreadtree.ps1 -lastKnownRDN Admins -lastKnownParent ""OU=Users,DC=DXPETTI,DC=COM""
The above would restore the “Admins” OU that sat under (directly or otherwise) the User OU in the root level of the DXPETTI.com Active Directory domain.
Happy AD Recycle Bin restoring!