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}
}
tshell :: Jul.28.2008 ::
All ::
No Comments »