Script Restore Adtree
Script Restore Adtree
.Synopsis
Recursively restores an object and all it's child objects from Active Directory
Recycle Bin
.DESCRIPTION
Recursively restores either:
- Any deleted child from a certain object, e.i. any objects delted from within
an OU.
- A deleted item and all it's deleted child objects, i.e. a whole OU Structure.
.EXAMPLE
Restore-ADTree.ps1 -Identity OU=Org,DC=lab,DC=lcl
Will restore any objects deleted from the Organizational Unit Org and any of
their child objects.
.EXAMPLE
Restore-ADTree.ps1 -Identity OU=Org,DC=lab,DC=lcl -TimeFilter '2014-10-17 08:00'
Will restore any objects deleted from the Organizational Unit Org and any of
their child objects
that were deleted after the time specified.
.EXAMPLE
Restore-ADTree.ps1 -lastKnownRDN Org
Will restore the object with lastknownRDN 'Org' and all its deleted child
objects.
.LINK
http://blog.simonw.se
.NOTES
AUTHOR: Jimmy Andersson, Knowledge Factory
DATE: 2014-03-20
CHANGE DATE: 2014-10-20
VERSION: 1.0 - First version by Jimmy Andersson
2.0 - Rewrite by Simon Wåhlin
Added PowerShell best practices
Now supports filter by datetime
Supports -WhatIf and -Confirm
Will handle conflicts by only restoring the first (oldest)
deleted object
Added error handling
#>
[Cmdletbinding(SupportsShouldProcess)]
Param (
# Specifies LastKnownRDN of object to be restored.
[Parameter(Mandatory,ParameterSetName='LastKnown')]
[String]
$lastKnownRDN,
[Parameter(ParameterSetName='Identity')]
[Parameter(ParameterSetName='LastKnown')]
[switch]
$PassThru
)
Begin
{
Import-Module ActiveDirectory -Verbose:$false
$FilterDateTime = Get-Date $TimeFilter.ToUniversalTime() -f 'yyyyMMddHHmmss.0Z'
function Restore-Tree
<#
.Synopsis
Recursive function doing the actual restoring
#>
{
[CmdletBinding(SupportsShouldProcess)]
Param
(
[Parameter()]
[String]
$strObjectGUID,
[Parameter()]
[String]
$strNamingContext,
[Parameter()]
[String]
$strDelObjContainer,
[Parameter()]
[String]
$TimeFilter = '16010101000000.0Z',
[Parameter()]
[Switch]
$IncludeLiveChildren,
[Parameter()]
[switch]
$PassThru
)
Begin
{
}
Process
{
Try
{
# Check if object exists already:
Write-Verbose -Message ''
Write-Verbose -Message "Processing object $strObjectGUID"
$objRestoredParent = Get-ADObject -Identity $strObjectGUID
-Partition $strNamingContext -ErrorAction Stop
if($IncludeLiveChildren)
{
Write-Verbose -Message "Searching for live child objects to $
($objRestoredParent.distinguishedName)"
$Param = @{
SearchScope = 'Onelevel'
SearchBase = $objRestoredParent.distinguishedName
ldapFilter = '(objectClass=*)'
ResultPageSize = 300
ResultSetSize = $Null
ErrorAction = 'SilentlyContinue'
}
$objChildren = Get-ADObject @Param
if($objRestoredParent)
{
$strFilter = "(&(WhenChanged>=$TimeFilter)(lastknownParent=$
($objRestoredParent.distinguishedName.Replace('\0','\\0'))))"
$Param = @{
SearchScope = 'Subtree'
SearchBase = $strDelObjContainer
includeDeletedObjects = $true
ldapFilter = $strFilter
ResultPageSize = 300
ResultSetSize = $null
Properties = @('msDS-LastKnownRDN', 'WhenChanged')
ErrorAction = 'SilentlyContinue'
}
$objChildren = Get-ADObject @Param
# If multiple objects are conflicting, select only the oldest one
# to get newer objects, timeFilter will be used
$objChildren = $objChildren |
Group-Object -Property msDS-LastKnownRDN |
foreach {
$_.Group |
Sort-Object -Property WhenChanged |
Select-Object -First 1
}
if ($objChildren)
{
Write-Verbose -Message 'Processing found child objects...'
foreach ($objChild in $objChildren)
{
$Param = @{
strObjectGUID = $objChild.objectGUID
strNamingContext = $strNamingContext
strDelObjContainer = $strDelObjContainer
IncludeLiveChildren = $IncludeLiveChildren
TimeFilter = $TimeFilter
PassThru = $PassThru
}
Restore-Tree @Param
}
}
else
{
Write-Verbose -Message 'No deleted child objects found.'
}
}
}
}
}
Process
{
$strDelObjContainer = (Get-ADDomain).DeletedObjectsContainer
Switch ($PSCmdlet.ParameterSetName)
{
'Identity'
{
$Param = @{
Identity = $Identity
Partition = $Partition
includeDeletedObjects = $true
Properties = @('lastknownparent', 'whenChanged', 'isDeleted')
}
$objSearchResult = Get-ADObject @Param
}
'LastKnown'
{
$FilterArray = @("(msds-
lastknownRDN=$lastKnownRDN)","(WhenChanged>=$FilterDateTime)")
if($PSBoundParameters.ContainsKey('lastknownParent'))
{
$FilterArray += "(lastknownParent=$lastKnownParent)"
}
if ($objSearchResult)
{
if ($objSearchResult.Count -gt 1)
{
Write-Warning -Message 'Search returned more than one object, please
refine search parameters.'
Write-Warning -Message ''
Write-Warning -Message ("`n{0}" -f ($objSearchResult | Format-Table
msDS-LastKnownRDN, lastknownparent, whenChanged -AutoSize | Out-String))
Write-Warning -Message ''
Throw
}
else
{
$Param = @{
strObjectGUID = $objSearchResult.objectGUID
strNamingContext = $partition
IncludeLiveChildren = $includelivechildren
strDelObjContainer = $strDelObjContainer
TimeFilter = $FilterDateTime
PassThru = $PassThru
}
Restore-Tree @Param
}
}
else
{
Write-Warning -Message 'No objects matching specified search terms.'
}
}