Posts RSS Comments RSS 253 Posts and 411 Comments till now

Getting the UpToDateness Vector (UTDV) in Powershell

A year or so ago I needed to get the UTDV Table (defined in detail HERE by Laura Hunter of ShutUpLaura.) At the time, the only way I knew how to get this information was to get the replUpToDateVector attribute from the NC on the target DC. I could have used Repadmin shown below, but that wouldn’t have been any fun. I did however end up with the massive script at the bottom of this post. It required decoding the UTDV table and resolving the Invocation ID to a name or “DeletedDSA.”

Fast forward to the present.

During the course of a long conversation on an ActiveDir thread (“Domain Controller Version”,) joe (aka joeware) and I got into side conversation about getting the UTDV table (ironically due to a statement/question in the thread by the same Laura, mentioned earlier.)

This led me to a wonderful little .NET method (GetReplicationCursors) on DirectoryServices.ActiveDirectory.DomainController class. This was so much easier I wanted to kick myself for not finding it sooner.

In any case, it was a great learning experience so I wanted to share it you. Enjoy my pain and victory.

Using Repadmin

repadmin /showutdvec [dc]  "[ DN for Naming Context ]"

The easy way.
Using System.DirectoryServices.ActiveDirectory.DomainController

Write-Host
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$NC = "DC={0}" -f ($domain.Name -replace "\.",",DC=")
foreach($dc in $domain.DomainControllers)
{
    Write-Host "$($DC.Name)"
    Write-Host "================"
    $UDTV =  $dc.GetReplicationCursors($NC)
    $GUID = @{n=‘DSA’;e={if($_.SourceServer){$_.SourceServer}else{$_.SourceInvocationId}}}
    $UDTV | Select-Object $GUID,
                          UpToDatenessUsn,
                          LastSuccessfulSyncTime | Sort DSA -desc | Format-Table -auto
    Write-Host
}

The Hard way.
I initially did all the work myself using this function. This gets the Up To Dateness vector and decodes it. It also translates the Invocation ID (if possible.)

param([string]$server)

Begin{
    function Get-InvocationIDFromBytes{
        Param([byte[]]$GuidArray)
        $GUIDSection1 = @(1..4)
        $GUIDSection2 = @(1..2)
        $GUIDSection3 = @(1..2)
        $GUIDSection4 = @(1..2)
        $GUIDSection5 = @(1..6)
   
        $start = 0
       
        $InvocationIDString = ""
       
        foreach($byte in $GuidArray)
        {
            $InvocationIDString = $InvocationIDString + "\" + ([string]::Format("{0:X2}",$byte))
        }
       
        [system.Array]::Copy($GuidArray,$start,$GUIDSection1,0,4)
        [system.Array]::Copy($GuidArray,($start+4),$GUIDSection2,0,2)
        [system.Array]::Copy($GuidArray,($start+6),$GUIDSection3,0,2)
        [system.Array]::Copy($GuidArray,($start+8),$GUIDSection4,0,2)
        [system.Array]::Copy($GuidArray,($start+10),$GUIDSection5,0,6)
        [system.Array]::Reverse($GUIDSection1)
        [system.Array]::Reverse($GUIDSection2)
        [system.Array]::Reverse($GUIDSection3)
       
        [string]$GuidString1 = $GUIDSection1 | %{[string]::Format("{0:X2}",$_)}
        [string]$GuidString2 = $GUIDSection2 | %{[string]::Format("{0:X2}",$_)}
        [string]$GuidString3 = $GUIDSection3 | %{[string]::Format("{0:X2}",$_)}
        [string]$GuidString4 = $GUIDSection4 | %{[string]::Format("{0:X2}",$_)}
        [string]$GuidString5 = $GUIDSection5 | %{[string]::Format("{0:X2}",$_)}
       
        $GuidString1 = $GuidString1.replace(" ","")
        $GuidString2 = $GuidString2.replace(" ","")
        $GuidString3 = $GuidString3.replace(" ","")
        $GuidString4 = $GuidString4.replace(" ","")
        $GuidString5 = $GuidString5.replace(" ","")
       
        $InvocationGUID = "{0}-{1}-{2}-{3}-{4}" -f $GuidString1,$GuidString2,$GuidString3,$GuidString4,$GuidString5
       
        $name = Get-NameFromInvocationID $InvocationIDString
        if(!$DCInvocationID.$InvocationGUID)
        {
            if($name)
            {
                $DCInvocationID.add($InvocationGUID,($InvocationIDString,$name))
            }
            else
            {
                $DCInvocationID.add($InvocationGUID,$InvocationIDString)
            }
        }
        return $InvocationGUID
    }
    function Get-USNFromBytes{
        Param([byte[]]$USNArray)
        [system.Array]::Reverse($USNArray)
        [string]$USN = $USNArray | %{[string]::Format("{0:X2}",$_)}
        $usn = $usn.replace(" ","")
        $usn = [int]"0x$usn"
        $usn.PadRight(12)
    }
    function Get-DateFromBytes{
        Param([byte[]]$dateArray)
        [system.Array]::Reverse($dateArray)
        $temp = @(1..5)
        [system.Array]::Copy($dateArray,3,$temp,0,5)
        [string]$date = $temp | %{[string]::Format("{0:X2}",$_)}
        $date = $date.replace(" ","")
        $date = [int64]"0x$date"
        $date = $date + "0000000"
        [system.DateTime]::FromFileTime($date)
    }
    function Get-NameFromInvocationID{
        Param($InvocationID)
        [string]$config = ([adsi]"LDAP://RootDSE").ConfigurationNamingContext
        $de = new-Object System.DirectoryServices.DirectoryEntry("LDAP://$Config")
        $filter = "(&(invocationID=$InvocationID)(objectcategory=ntdsdsa))"
        $ds = new-Object System.DirectoryServices.DirectorySearcher($de,$filter)
        $hresult = $ds.findone()
        if($hresult.Path)
        {
            $ResultDE = $hresult.GetDirectoryEntry()
            $name = ($ResultDE.psbase.parent).DNSHostName
        }
        $name
    }
    function Decode-UpToDateVectorTable{
        Param([byte[]]$table)
       
        $guid = @(1..16)
        $USN = @(1..8)
        $Date = @(1..8)
        $UTDTable = @()
        $name = $null
       
        for($i=16;$i -lt $table.count;$i+=32)
        {
            [system.Array]::Copy($table,$i,$Guid,0,16)
            [system.Array]::Copy($table,$i+16,$USN,0,8)
            [system.Array]::Copy($table,$i+16+8,$Date,0,8)
           
            $GUIDString = Get-InvocationIDFromBytes $GUID
            $USNString  = Get-USNFromBytes $usn
            $dateString = Get-DateFromBytes $Date
            $UTDEntry = "" | Select-Object Name,USN,Date
            [system.Array]$dcname = $DCInvocationID.$GUIDString
            if($dcname[1])
            {
                $UTDEntry.Name = $dcname[1]
                $UTDEntry.USN =  $USNString
                $UTDEntry.Date = $DateString
                $UTDTable += $UTDEntry
            }
            else
            {
                $UTDEntry.Name = $GUIDString
                $UTDEntry.USN =  $USNString
                $UTDEntry.Date = $DateString
                $UTDTable += $UTDEntry
            }
        }  
        $UTDTable
    }
    $DCInvocationID = @{}
    $utdTables = @()
    $process = @()
}
Process{
    if($_)
    {
        $process += $_
    }
}
End{
    if($server){$process += $server}
    foreach($srv in $process)
    {
        $table = ([adsi]"LDAP://$srv").replUpToDateVector[0]
        if($process.count -gt 1)
        {
            $utdTableEntry = "" | Select-Object Name,UpToDatenessVector
            $utdTableEntry.Name = $srv
            $utdTableEntry.UpToDatenessVector = Decode-UpToDateVectorTable $table | Sort-Object Name -des
            $utdTables += $utdTableEntry
        }
        else
        {
            Decode-UpToDateVectorTable $table | Sort-Object Name -des
        }
    }
    if($utdTables){return $utdTables}
}

Trackback this post | Feed on Comments to this post

Leave a Reply

You must be logged in to post a comment.