Restore AD Attributes

Back in the year that is one long COVID induced fog, I wrote about how one can mount a old copy of an Active Directory Domain Services database from something such as a file level backup of a DC. The impetus for this was a script that wreaked havoc on a large number of end users attributes in the AD object. Restoring from the AD recycle bin wouldn’t help in this case as the object itself isn’t deleted. Therefore, we need to selectively copy attributes from an old copy of the object.

Fortunately for you fellow reader, I have prepared a PowerShell script to do just that. If you want to skip the breakdown of the script, scroll down the bottom of this post. Otherwise, “come with me if you want to live” … and by live, I mean save some script running butts.

First up, lets set some mandatory parameters that point us to the mounted old AD database, the current/live AD database, the OU path of the affected users and lastly, a location to save a copy of the affected users properties prior to the restore (always have a plan B):

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$false,
                   Position=0)]
        [string]$OldAd,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$false,
                   Position=1)]
        [string]$NewAd,

        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$false,
                   Position=2)]
        [string]$AdOuPath,
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$false,
                   Position=3)]
        [string]$BkpPath

Next up, we are going to begin the grunt of the work by parsing our live AD environment and grab our list of users, export them to CSV for our plan B and use that list to build an array:

    Begin
    {
        # Build list of Users
        Get-ADUser -Filter * -SearchBase $AdOuPath -Server $NewAd | Select samaccountname | Export-Csv -Path $BkpPath\Users.csv -NoTypeInformation
        $UserList = Import-Csv -Path $BkpPath\Users.csv
    }

Continuing that theme, we are going to punch through each user in the list, grab all the properties from their object and write them out to individual text files for later use if necessary:

    Process
    {
        foreach ($User in $UserList)
        {
            #Backup First
            Get-ADUser -Identity $User.SamAccountName -Properties * -Server $NewAd | Out-File "$BkpPath\$($User.SamAccountName)_before.txt"

Now on to the good bits…

We are doing the same thing as before but against the mounted old AD database and write the properties of interest to us (in the example, extensionAttribute1-6,9,13-14 and publicDelegates/BL properties) to a ordered hash table:

            #Get Old Values
            $OldProps = Get-ADUser -Identity $User.SamAccountName -Properties * -Server $OldAd

            #Build Hash Tables
            [hashtable]$OldValues = [ordered]@{
                extensionAttribute1 = $OldProps.extensionAttribute1
                extensionAttribute2 = $OldProps.extensionAttribute2
                extensionAttribute3 = $OldProps.extensionAttribute3
                extensionAttribute4 = $OldProps.extensionAttribute4
                extensionAttribute5 = $OldProps.extensionAttribute5
                extensionAttribute6 = $OldProps.extensionAttribute6
                extensionAttribute9 = $OldProps.extensionAttribute9
                extensionAttribute13 = $OldProps.extensionAttribute13
                extensionAttribute14 = $OldProps.extensionAttribute14
                publicDelegates = [array]$OldProps.publicDelegates
                publicDelegatesBL = [array]$OldProps.publicDelegatesBL
            }

As it says on the tin, we are taking the properties saved within the hashtable, going through them one by one and setting them on the AD object in the live AD database:

            #Set Old Values
    
            foreach ($O in $OldValues.GetEnumerator())
            {
                Set-ADUser -Identity $User.samaccountname -Add @{$($O.Key)=$($O.Value)} -Server $NewAd -Verbose 
            }

At this point, the objective has been met but we have some house keeping to finish up the script. We export out all the properties of each user to text files, now featuring our newly set properties so we can do before and after comparisons with ease and we clear out a variable containing the hash table:

            #Export New Values
            Get-ADUser -Identity $User.SamAccountName -Properties * -Server $NewAd | Out-File "$BkpPath\$($User.SamAccountName)_after.txt"
    
            #Reset Hash Tables
            $OldValues.Clear()

That’s it. Only thing left to do is wrap it all up as a function and tie and nice PowerShell themed bow around it for sharing with the wider world.

Hopefully this helps if you ever find yourself in a similar scenario as we did. Scroll on down for the shiny, boxed up version.

Godspeed sysadmin

<#
.Synopsis
Restore select Ad Attributes from a mounted AD DS database
.DESCRIPTION
This cmdlet will take a OU path as input, takes a backup of the attributes of all users
in said OU, import the attributes from a currently mounted AD DS database into a
hashtable, loop through each user in the OU and apply said attributes. Finally, a export
of the users current attributes is saved in the same path as the backup for before/after
comparisons
.EXAMPLE
Restore-AdAttributes -OldAd contosodc01:777 -NewAd contosodc01 -$AdOuPath "OU=Users,DC=contoso,DC=co" -BkpPath "C:\Temp\Backup\"
#>
function Restore-AdAttributes
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$false,
Position=0)]
[string]$OldAd,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$false,
Position=1)]
[string]$NewAd,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$false,
Position=2)]
[string]$AdOuPath,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$false,
Position=3)]
[string]$BkpPath
)
Begin
{
# Build list of Users
Get-ADUser Filter * SearchBase $AdOuPath Server $NewAd | Select samaccountname | Export-Csv Path $BkpPath\Users.csv NoTypeInformation
$UserList = Import-Csv Path $BkpPath\Users.csv
}
Process
{
foreach ($User in $UserList)
{
#Backup First
Get-ADUser Identity $User.SamAccountName Properties * Server $NewAd | Out-File "$BkpPath\$($User.SamAccountName)_before.txt"
#Get Old Values
$OldProps = Get-ADUser Identity $User.SamAccountName Properties * Server $OldAd
#Build Hash Tables
[hashtable]$OldValues = [ordered]@{
extensionAttribute1 = $OldProps.extensionAttribute1
extensionAttribute2 = $OldProps.extensionAttribute2
extensionAttribute3 = $OldProps.extensionAttribute3
extensionAttribute4 = $OldProps.extensionAttribute4
extensionAttribute5 = $OldProps.extensionAttribute5
extensionAttribute6 = $OldProps.extensionAttribute6
extensionAttribute9 = $OldProps.extensionAttribute9
extensionAttribute13 = $OldProps.extensionAttribute13
extensionAttribute14 = $OldProps.extensionAttribute14
publicDelegates = [array]$OldProps.publicDelegates
publicDelegatesBL = [array]$OldProps.publicDelegatesBL
}
#Set Old Values
foreach ($O in $OldValues.GetEnumerator())
{
Set-ADUser Identity $User.samaccountname Add @{$($O.Key)=$($O.Value)} Server $NewAd Verbose
}
#Export New Values
Get-ADUser Identity $User.SamAccountName Properties * Server $NewAd | Out-File "$BkpPath\$($User.SamAccountName)_after.txt"
#Reset Hash Tables
$OldValues.Clear()
}
}
}
Restore-AdAttributes
James Written by:

Be First to Comment

Helpful? Have a question on the above?