Reanimate Active Directory tombstone objects with PowerShell (using System.DirectoryServices.Protocols)

Introduction

During a couple of days, I was searching a way for reanimating tombstone objects using only the Active Directory module for Windows PowerShell and, for some reasons, I did not want to use any additional Quest cmdlets or SDM Software cmdlets.

 

 

Retrieve tombstone objects with PowerShell

To get all tombstone objects within a domain, you just have to type the command: get-adobject -filter ‘isDeleted -eq $true’ –IncludeDeletedObjects

 

The option –IncludeDeletedObjects permits to explore the hidden container “cn=deleted objects” of the domain. The filter ‘isDeleted -eq $true’ will focus on tombstone objects.

As Reminder, the major difference between a tombstone object and a standard object is the attribute isDeleted (set to true for tombstone objects) and the parent container (a tombstone object is always placed into the container deleted objects).

Remark: the parent container of an object is partially defined by its distinguishedName.

 

 

Reanimate tombstone objects with PowerShell

Now we want to simply reanimate a specific tombstone object by changing its parent container and its isDeleted attribute but we are not able to manage them with the set-adobject cmdlet. There is no –IncludeDeletedObjects option for it and the tombstone object is just “not found”.

 

Finally, I have found a .Net function (Resurrecting tombstones in Active Directory or ADAM) that I have converted to PowerShell and it is working fine from my side.

{codecitation style= »brush: PowerShell »}

function Reanimate-Object()

{

Param

(

[Parameter(Mandatory=$true)]

[String] $Dn,

 

[Parameter(Mandatory=$true)]

[String] $NewDn,

 

[Parameter(Mandatory=$true)]

[String] $LdapServer,

 

[Int] $LdapPort = 389

)

 

try

{

# Delete the attribute isDeleted

$LdapAttrIsDeleted = New-Object -TypeName System.DirectoryServices.Protocols.DirectoryAttributeModification

$LdapAttrIsDeleted.Name = « isdeleted »

$LdapAttrIsDeleted.Operation = « Delete »

 

# Replace the attribute distinguishedName

$LdapAttrDn = New-Object -TypeName System.DirectoryServices.Protocols.DirectoryAttributeModification

$LdapAttrDn.Name = »distinguishedname »

$LdapAttrDn.Add($NewDn) | Out-Null

$LdapAttrDn.Operation = « Replace »

 

# Create modify request

$LdapModifyRequest = New-Object -TypeName System.DirectoryServices.Protocols.ModifyRequest($Dn,@($LdapAttrIsDeleted,$LdapAttrDn))

$LdapModifyRequest.Controls.Add((New-Object System.DirectoryServices.Protocols.ShowDeletedControl)) | Out-Null

 

# Establish connection to Active Directory

$LdapConnection = new-object System.DirectoryServices.Protocols.LdapConnection(new-object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($LdapServer))

 

# Send modify request

[System.DirectoryServices.Protocols.DirectoryResponse]$modifyResponse = $ldapConnection.SendRequest($ldapModifyRequest)

 

# Return result

if ( $modifyResponse.ResultCode -eq « Success » )

{

Write-Host « object reanimated as $($NewDN). »

}

else

{

Write-Host « $($modifyResponse.ErrorMessage) »

}

}

catch

{

Write-Host « $($_.Exception.Message) »

}

}

{/codecitation}

 

To use this function, you just have to:

  • Load the .Net assemblies and the Active Directory module for Windows Powershell.
  • Retrieve the specified tombstone object.
  • Defined the new distinguishedName attribute based on the actual CN and lastKnownParent attribute.
  • Call the function with the correct parameters.

{codecitation style= »brush: PowerShell »}

# Load Active Directory Module for Windows PowerShell

Import-Module ActiveDirectory

 

# Load assemblies

[System.Reflection.assembly]::LoadWithPartialName(« system.directoryservices ») | Out-Null

[System.Reflection.assembly]::LoadWithPartialName(« system.directoryservices.protocols ») | Out-Null

 

# Retrieve deleted object

$ADObject = Get-ADObject -Filter ‘sAMAccountName -eq « aaugagneur »‘ -IncludeDeletedObjects -Property *

 

# Define regular expression to capture CN attribute

[regex] $RegexDnTombstones = « (?<CN>CN=.*)\\0ADEL.* »

 

# Define new distinguishedName attribute based on captured CN attribute and lastKnownParent attribute

$NewDn = (($RegexDnTombstones.Match($ADObject.distinguishedName)).Groups[« CN »].value)+ », »+($ADObject.lastKnownParent)

 

# Call function

Reanimate-Object -Dn $ADObject.distinguishedName -NewDn $NewDn -LdapServer « localhost »

{/codecitation}

 

 

If you want to explore deeper S.DS.P you can check another example from Microsoft Script Center: Using System.DirectoryServices.Protocols from Powershell

Start-Job: « The directory name is invalid »

The problem could occur when you try to start a background process with the « start-job » cmdlet and with the « -credential » parameter.

 

The job is in failure with the error message « there is an error while laucnhing the background process. Error reported: The directory name is invalid« .

 

After a quick search, I have found this microsoft KB which is describing clearly my problem. In fact, the default working directory defined for PowerShell is pointing to an user’s mapped home drive (you can verify it through the properties of the Powershell’s shortcut).

 

So, the first thing to do is to change the parameter « Start in » (for this, you will need local administrator rights). For example, I have defined it to « %WINDIR%« .

 

After closing and reopening the PowerShell’s console, when you retry to execute the command, it’s working fine!

Definitely, that was a problem of access with the working directory. So locally, you just have to change the parameter « Start in » of the PowerShell’s shortcut. But, in a large environment and especially if user’s home directories are pointing to a network share (or a mapped drive), you are not probably able to change this property. The solution is to use the .Net property « [environment]::Directory » which permits to set the fully qualified path for a specific working directory.

The command line would be « start-job -credential ‘DOMAIN\username’ -ScriptBlock { write-host ‘Is it working like that?’ } -ArgumentList([environment]::CurrentDirectory=’c:\temp’)«