Jorge's Quest For Knowledge!

All about Windows Server, ADDS, ADFS & ILM/FIM (It is just like an addiction, The more you have, the more you want to have!)

Archive for the ‘Tooling/Scripting’ Category

(2013-03-03) Testing SYSVOL Replication Latency/Convergence Through PowerShell

Posted by Jorge on 2013-03-03


I wrote AD replication latency/convergence test script that can be found in this blog post.

A few days ago a Microsoft PFE, that probable is using the script above, contacted me asking if I also had a version that could be used to test the latency/convergence of the SYSVOL replication. It was not that difficult to change the AD version in a way to check the SYSVOL replication. You might think I now have one script that does both AD and SYSVOL, but au contraire, there are two scripts. The one above does AD, and the one below does SYSVOL. The behavior/logic for the SYSVOL version of the script is very similar to the AD version of the script.

-

To use the script just copy the contents of the table below and put that in a script that could be called "Check-SYSVOL-Replication-Latency-Convergence.ps1", but this is of course not mandatory. In the explanations and examples I will use this name. Note that this script is not signed in any way by me, so you might need to adjust the PowerShell Execution Policy of the server where you want to execute this script. It is not possible to pass credentials through the script, so the account you are using to execute the script should have the correct permissions to create (and delete afterwards) the temporary text file in the NETLOGON folder or you should execute the script from a PowerShell console window that was started with credentials that do have the correct permissions. In addition, the server where this script is executed must be able to access every DC (RWDC and RODC) in the same AD domain through SMB (port 445). So if your network is not fully routed or you have firewalls deployed between directory servers, and those directory servers are therefore not reachable, they will be marked as not reachable. This prevents the script from trying to contact the directory servers while it is not possible. Last but not least, I just tested this script in a single AD domain AD forest with a few DCs (RWDCs and RODCs). So I do not know how well this is going to perform in an environment with a lot of DCs. If you are going to use this script, the only thing I would like to ask you is to give me feedback about it and your experiences. Thanks!

-

REMARK: This script just takes the SYSVOL into account, which is the default domain DFS namespace. However, the script could be adjusted to support ANY domain DFS namespace! The script supports both NTFRS and DFSR as the replication mechanism.

-

After the script is started, it will give you information about the DCs in the same AD domain as the server where the script is being executed on. As the default AD domain, it will use the AD domain of the server the script is being executed on.

After that, you need to specify on which RWDC from the previous AD domain, the temporary text file will be created:

  • Either specify the term PDC to target the RWDC that hosts the PDC FSMO role
    OR
  • Either specify the FQDN of an existing RWDC in the AD domain
    OR
  • Just press ENTER to search for an RWDC in the AD domain
    • If an RWDC is located right away, then that RWDC will be used
    • If an RODC is located right away, then that cannot be used and the RWDC that hosts the PDC FSMO role will be targeted instead

-

REMARK: In all cases the DC specified will be checked if it exists in the AD domain and if it is an RWDC and not an RODC. If it exists and it is an RWDC, the script continues and if it does not exist or if it is an RODC, the script aborts.

-

The code of the PowerShell script that does this is included below. Screen dumps are also included after the code sample.

-

I have also linked a file with the contents below to this blog post. Get it HERE.

-

!!! DISCLAIMER/REMARKS !!!:

  • The script is freeware, you are free to distribute it, but always refer to this website as the location where you got it
  • This script is furnished "as is". No warranty is expressed or implied!
  • Always test first in lab environment to see if it meets your needs!
  • Use this script at your own risk!
  • I do not warrant this script to be fit for any purpose, use or environment
  • I have tried to check everything that needed to be checked, but I do not guarantee the script does not have bugs.
  • I do not guarantee the script will not damage or destroy your system(s), environment or whatever.
  • I do not accept liability in any way if you screw up, use the script wrong or in any other way where damage is caused to your environment/systems!
  • If you do not accept these terms do not use the script and delete it immediately!

!!! DISCLAIMER/REMARKS !!!:

-

# Abstract: This PoSH Script Checks The SYSVOL Replication Latency/Convergence # Written By: Jorge de Almeida Pinto [MVP-DS] # Blog: http://jorgequestforknowledge.wordpress.com/ # Start... Clear-Host Write-Host "*******************************************************" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "* --> Test SYSVOL Replication Latency/Convergence <-- *" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "* Written By: Jorge de Almeida Pinto [MVP-DS] *" -ForeGroundColor Yellow Write-Host "* (http://jorgequestforknowledge.wordpress.com/) *" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "*******************************************************" -ForeGroundColor Yellow ########## # Some Constants $continue = $true $cleanupTempObject = $true ########## # The Function To Test The Port Connection Function PortConnectionCheck($fqdnDC,$port,$timeOut) { $tcpPortSocket = $null $portConnect = $null $tcpPortWait = $null $tcpPortSocket = New-Object System.Net.Sockets.TcpClient $portConnect = $tcpPortSocket.BeginConnect($fqdnDC,$port,$null,$null) $tcpPortWait = $portConnect.AsyncWaitHandle.WaitOne($timeOut,$false) If(!$tcpPortWait) { $tcpPortSocket.Close() #Write-Host "Connection Timeout" Return "ERROR" } Else { #$error.Clear() $tcpPortSocket.EndConnect($portConnect) | Out-Null If (!$?) { #Write-Host $error[0] Return "ERROR" } Else { Return "SUCCESS" } $tcpPortSocket.Close() } } ########## # Get The FQDN Of The Local AD Domain From The Server This Script Is Executed On $ADDomainToWriteTo = $(Get-WmiObject -Class Win32_ComputerSystem).Domain ########## # Get List Of DCs In AD Domain, Create And Present In A Table $contextADDomainToWriteTo = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$ADDomainToWriteTo) $ListOfDCsInADDomain = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) $TableOfDCsInADDomain = @() Write-Host "" Write-Host "-------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF DCs IN THE AD DOMAIN '$ADDomainToWriteTo'..." -ForeGroundColor Cyan ForEach ($DC in $ListOfDCsInADDomain) { $TableOfDCsInADDomainObj = "" | Select Name,PDC,SiteName,DStype $TableOfDCsInADDomainObj.Name = $DC.Name $TableOfDCsInADDomainObj.PDC = "FALSE" If ($DC.Roles -ne $null -And $DC.Roles -Contains "PdcRole") { $TableOfDCsInADDomainObj.PDC = "TRUE" $pdcFQDN = $DC.Name $pdcSite = $DC.SiteName } $TableOfDCsInADDomainObj.SiteName = $DC.SiteName $ListOfRWDCsInADDomain = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) | ?{$_.OutboundConnections -ne $null} $DStype = "Read-Only" ForEach ($RWDC In $ListOfRWDCsInADDomain) { If ($RWDC.Name -like $DC.Name) { $DStype = "Read/Write" Break } } $TableOfDCsInADDomainObj.DStype = $DStype $TableOfDCsInADDomain += $TableOfDCsInADDomainObj } $TableOfDCsInADDomain | FT -AutoSize Write-Host " --> Found [$($ListOfDCsInADDomain.count)] DC(s) In AD Domain..." -ForeGroundColor Cyan Write-Host "" ########## # Specify A RWDC From The Selected AD Domain Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "Which RWDC In The AD Domain '$ADDomainToWriteTo' Should Be Used To Create The Object?" -ForeGroundColor Cyan Write-Host "" Write-Host "Available Options Are:" -ForeGroundColor Yellow Write-Host "[*] Specify PDC To Use The DC With The PDC FSMO Role" -ForeGroundColor Yellow Write-Host "[*] Just Press Enter To Locate An RWDC" -ForeGroundColor Yellow Write-Host "[*] Specify The FQDN Of A Specific RWDC" -ForeGroundColor Yellow Write-Host "" $SourceRWDCInADDomain = Read-Host "Please Choose An Option" # If PDC Was Specified Find The RWDC With The PDC FSMO Role And Use That If ($SourceRWDCInADDomain -eq "PDC") { $SourceRWDCInADDomainFQDN = $pdcFQDN $SourceRWDCInADDomainSITE = $pdcSite } # If Nothing Was Specified Automatically Locate An RWDC To Use If ($SourceRWDCInADDomain -eq "") { # Locate Just ONE DC (This Could Be An RWDC Or RODC) $SourceRWDCInADDomainObjectONE = [System.DirectoryServices.ActiveDirectory.DomainController]::findone($contextADDomainToWriteTo) # Locate All RWDCs In The AD Domain $SourceRWDCInADDomainObjectALL = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) | ?{$_.OutboundConnections -ne $null} $UseRWDC = $False # Check If The Single DC Found Is An RWDC Or Not By Checking If It Is In The List Of RWDCs ForEach ($RWDC In $SourceRWDCInADDomainObjectALL) { If ($RWDC.Name -like $SourceRWDCInADDomainObjectONE.Name) { $UseRWDC = $True } } # If The Single DC Found Is An RWDC, Then Use That One If ($UseRWDC -eq $True) { $SourceRWDCInADDomainFQDN = $SourceRWDCInADDomainObjectONE.Name $SourceRWDCInADDomainSITE = $SourceRWDCInADDomainObjectONE.SiteName } # If The Single DC Found Is An RODC, Then Find The RWDC With The PDC FSMO Role And Use That If ($UseRWDC -eq $False) { $SourceRWDCInADDomainFQDN = $pdcFQDN $SourceRWDCInADDomainSITE = $pdcSite } } # If A Specific RWDC Was Specified Then Use That One If ($SourceRWDCInADDomain -ne "" -And $SourceRWDCInADDomain -ne "PDC") { $contextRWDCToWriteTo = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$SourceRWDCInADDomain) $SourceRWDCInADDomainObject = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($contextRWDCToWriteTo) $SourceRWDCInADDomainFQDN = $SourceRWDCInADDomainObject.Name $SourceRWDCInADDomainSITE = $SourceRWDCInADDomainObject.SiteName } # Check If The Selected DC Actually Exists In The AD Domain And Its Is An RWDC And NOT An RODC $RWDCvalidity = $False ForEach ($DC in $ListOfRWDCsInADDomain) { If ($DC.Name -like $SourceRWDCInADDomainFQDN) { $RWDCvalidity = $True } } Write-Host "" Write-Host "Checking Existence And Connectivity Of The Specified RWDC '$SourceRWDCInADDomainFQDN' In The AD Domain '$ADDomainToWriteTo'..." -ForeGroundColor Yellow If ($RWDCvalidity -eq $True) { Write-Host "" Write-Host "The Specified DC '$SourceRWDCInADDomainFQDN' Is An RWDC And It Exists In The AD Domain '$ADDomainToWriteTo'!" -ForeGroundColor Green Write-Host "" Write-Host "Continuing Script..." -ForeGroundColor Green $smbPort = "445" $timeOut = "500" $smbConnectionResult = $null $fqdnDC = $SourceRWDCInADDomainFQDN $smbConnectionResult = PortConnectionCheck $fqdnDC $smbPort $timeOut If ($smbConnectionResult -eq "SUCCESS") { Write-Host "" Write-Host "The Specified RWDC '$SourceRWDCInADDomainFQDN' Is Reachable!" -ForeGroundColor Green Write-Host "" Write-Host "Continuing Script..." -ForeGroundColor Green Write-Host "" } If ($smbConnectionResult -eq "ERROR") { Write-Host "" Write-Host "The Specified RWDC '$SourceRWDCInADDomainFQDN' Is NOT Reachable!" -ForeGroundColor Red Write-Host "" Write-Host "Please Re-Run The Script And Make Sure To Use An RWDC That Is Reachable!" -ForeGroundColor Red Write-Host "" Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" Break } } If ($RWDCvalidity -eq $False) { Write-Host "" Write-Host "The Specified DC '$SourceRWDCInADDomainFQDN' Either Does NOT Exist In The AD Domain '$ADDomainToWriteTo' Or Is NOT And RWDC!" -ForeGroundColor Red Write-Host "" Write-Host "Please Re-Run The Script And Provide The FQDN Of An RWDC Within The AD Domain '$ADDomainToWriteTo' That Does Exist" -ForeGroundColor Red Write-Host "" Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" Break } ########## # Determine SYSVOL Replication Mechanism And SYSVOL/NetLogon Location On Sourcing RWDC Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "SYSVOL REPLICATION MECHANISM..." -ForeGroundColor Cyan Write-Host "" # Get The Default Naming Contexr $defaultNamingContext = (([ADSI]"LDAP://$SourceRWDCInADDomainFQDN/rootDSE").defaultNamingContext) # Find The Computer Account Of The Sourcing RWDC $Searcher = New-Object DirectoryServices.DirectorySearcher $Searcher.Filter = "(&(objectClass=computer)(dNSHostName=$SourceRWDCInADDomainFQDN))" $Searcher.SearchRoot = "LDAP://" + $SourceRWDCInADDomainFQDN + "/OU=Domain Controllers," + $defaultNamingContext $dcObjectPath = $Searcher.FindAll().Path # Check If An NTFRS Subscriber Object Exists To Determine If NTFRS Is Being Used Instead Of DFS-R $SearcherNTFRS = New-Object DirectoryServices.DirectorySearcher $SearcherNTFRS.Filter = "(&(objectClass=nTFRSSubscriber)(name=Domain System Volume (SYSVOL share)))" $SearcherNTFRS.SearchRoot = $dcObjectPath $ntfrsSubscriptionObject = $SearcherNTFRS.FindAll() If ($ntfrsSubscriptionObject.Path -ne $null) { Write-Host "SYSVOL Replication Mechanism Being Used...: NTFRS" # Get The Local Root Path For The SYSVOL $sysvolRootPathOnSourcingRWDC = $ntfrsSubscriptionObject.Properties.frsrootpath } # Check If An DFS-R Subscriber Object Exists To Determine If DFS-R Is Being Used Instead Of NTFRS $SearcherDFSR = New-Object DirectoryServices.DirectorySearcher $SearcherDFSR.Filter = "(&(objectClass=msDFSR-Subscription)(name=SYSVOL Subscription))" $SearcherDFSR.SearchRoot = $dcObjectPath $dfsrSubscriptionObject = $SearcherDFSR.FindAll() If ($dfsrSubscriptionObject.Path -ne $null) { Write-Host "SYSVOL Replication Mechanism Being Used...: DFS-R" -ForeGroundColor Yellow Write-Host "" # Get The Local Root Path For The SYSVOL $sysvolRootPathOnSourcingRWDC = $dfsrSubscriptionObject.Properties."msdfsr-rootpath" } # Determine The UNC Of The Folder To Write The Temp File To $scriptsUNCPathOnSourcingRWDC = "\\" + $SourceRWDCInADDomainFQDN + "\" + $($sysvolRootPathOnSourcingRWDC.Replace(":","$")) + "\Scripts" ########## # Get List Of DCs In AD Domain To Which The Temp Object Will Replicate, Create And Present In A Table Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF DIRECTORY SERVERS THE TEMP OBJECT REPLICATES TO..." -ForeGroundColor Cyan # Put The Selected RWDC Already In the Table [A] Of Directory Servers To Which The Temp Object Will Replicate $TableOfDSServersA = @() $TableOfDSServersAObj = "" | Select Name,SiteName,Reachable $TableOfDSServersAObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersAObj.SiteName = $SourceRWDCInADDomainSITE $TableOfDSServersAObj.Reachable = "TRUE" $TableOfDSServersA += $TableOfDSServersAObj # Put The Selected RWDC Already In the Table [B] Of Directory Servers Where The Replication Starts $TableOfDSServersB = @() $TableOfDSServersBObj = "" | Select Name,SiteName,Time $TableOfDSServersBObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersBObj.SiteName = $SourceRWDCInADDomainSITE $TableOfDSServersBObj.Time = 0.00 $TableOfDSServersB += $TableOfDSServersBObj # Add All Other Remaining DCs In The Targeted AD Domain To The List Of Directory Servers [A] ForEach ($DC In $ListOfDCsInADDomain) { If(!($DC.Name -like $SourceRWDCInADDomainFQDN)) { $TableOfDSServersAObj = "" | Select Name,SiteName,Reachable $TableOfDSServersAObj.Name = $DC.Name $TableOfDSServersAObj.SiteName = $DC.SiteName $smbPort = "445" $timeOut = "500" $smbConnectionResult = $null $fqdnDC = $DC.Name $smbConnectionResult = PortConnectionCheck $fqdnDC $smbPort $timeOut If ($smbConnectionResult -eq "SUCCESS") { $TableOfDSServersAObj.Reachable = "TRUE" } If ($smbConnectionResult -eq "ERROR") { $TableOfDSServersAObj.Reachable = "FALSE" } $TableOfDSServersA += $TableOfDSServersAObj } } $TableOfDSServersA | FT -AutoSize Write-Host " --> Found [$($TableOfDSServersA.count)] Directory Server(s)..." -ForeGroundColor Cyan Write-Host "" ########## # Create The Temp Object On The Targeted RWDC Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "CREATING TEMP TEXT FILE IN SYSVOL/NETLOGON...:" -ForeGroundColor Cyan Write-Host "" $domainNCDN = $defaultNamingContext $tempObjectName = "sysvolReplTempObject" + (Get-Date -f yyyyMMddHHmmss) + ".txt" Write-Host " --> On RWDC.............: $SourceRWDCInADDomainFQDN" -ForeGroundColor Yellow Write-Host " --> With Full Name......: $tempObjectName" -ForeGroundColor Yellow Write-Host " --> With Contents.......: ...!!!...TEMP OBJECT TO TEST SYSVOL REPLICATION LATENCY/CONVERGENCE...!!!..." -ForeGroundColor Yellow Write-Host " --> In AD Domain........: $ADDomainToWriteTo ($domainNCDN)" -ForeGroundColor Yellow "...!!!...TEMP OBJECT TO TEST AD REPLICATION LATENCY/CONVERGENCE...!!!..." | Out-File -FilePath $($scriptsUNCPathOnSourcingRWDC + "\" + $tempObjectName) Write-Host "`n Temp Text File [$tempObjectName] Has Been Created In The NetLogon Share Of RWDC [$SourceRWDCInADDomainFQDN]! `n" -ForeGroundColor Yellow ########## # Go Through The Process Of Checking Each Directory Server To See If The Temp Object Already Has Replicated To It $startDateTime = Get-Date $i = 0 Write-Host " --> Found [$($TableOfDSServersA.count)] Directory Server(s)..." -ForeGroundColor Yellow Write-Host "" While($continue) { $i++ $oldpos = $host.UI.RawUI.CursorPosition Write-Host " ====================== CHECK $i ======================" -ForeGroundColor Yellow Write-Host "" Write-Host " REMARK: Each DC In The List Below Must Be Accessible Through SMB Over TCP (445)" -ForeGroundColor Red Write-Host "" Start-Sleep 1 $replicated = $true # For Each Directory Server In The List/Table [A] Perform A Number Of Steps ForEach ($DSsrv in $TableOfDSServersA) { If ($DSsrv.Name -match $SourceRWDCInADDomainFQDN) { Write-Host " * Contacting DC In AD domain ...[$($DSsrv.Name.ToUpper())]..." -ForeGroundColor Yellow Write-Host " - DC Is Reachable..." -ForeGroundColor Green Write-Host " - Object [$tempObjectName] Exists In The NetLogon Share" (" "*3) -ForeGroundColor Green continue } # If The Directory Server Is A DC In The AD Domain, Then Connect Through LDAP (TCP:445) If ($DSsrv.Name -notmatch $SourceRWDCInADDomainFQDN) { Write-Host "" Write-Host " * Contacting DC In AD domain ...[$($DSsrv.Name.ToUpper())]..." -ForeGroundColor Yellow $connectionResult = $null If ($DSsrv.Reachable -eq "TRUE") { Write-Host " - DC Is Reachable..." -ForeGroundColor Green $objectPath = "\\" + $($DSsrv.Name) + "\Netlogon\" + $tempObjectName $connectionResult = "SUCCESS" } If ($DSsrv.Reachable -eq "FALSE") { Write-Host " - DC Is NOT Reachable..." -ForeGroundColor Red $connectionResult = "FAILURE" } } # If The Connection To The DC Is Successfull If ($connectionResult -eq "SUCCESS") { If (Test-Path -Path $objectPath) { # If The Temp Object Already Exists Write-Host " - Object [$tempObjectName] Now Does Exist In The NetLogon Share" (" "*3) -ForeGroundColor Green If (!($TableOfDSServersB | ?{$_.Name -match $DSsrv.Name})) { $TableOfDSServersBobj = "" | Select Name,SiteName,Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj.SiteName = $DSsrv.SiteName $TableOfDSServersBObj.Time = ("{0:n2}" -f ((Get-Date)-$startDateTime).TotalSeconds) $TableOfDSServersB += $TableOfDSServersBObj } } Else { # If The Temp Object Does Not Yet Exist Write-Host " - Object [$tempObjectName] Does NOT Exist Yet In The NetLogon Share" -ForeGroundColor Red $replicated = $false } } # If The Connection To The DC Is Unsuccessfull If ($connectionResult -eq "FAILURE") { Write-Host " - Unable To Connect To DC/GC And Check For The Temp Object..." -ForeGroundColor Red If (!($TableOfDSServersB | ?{$_.Name -match $DSsrv.Name})) { $TableOfDSServersBobj = "" | Select Name,SiteName,Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj.SiteName = $DSsrv.SiteName $TableOfDSServersBObj.Time = "Fail" $TableOfDSServersB += $TableOfDSServersBObj } } } If ($replicated) { $continue = $false } Else { $host.UI.RawUI.CursorPosition = $oldpos } } ########## # Show The Start Time, The End Time And The Duration Of The Replication $endDateTime = Get-Date $duration = "{0:n2}" -f ($endDateTime.Subtract($startDateTime).TotalSeconds) Write-Host "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")" -ForeGroundColor Yellow Write-Host " End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")" -ForeGroundColor Yellow Write-Host " Duration........: $duration Seconds" -ForeGroundColor Yellow ########## # Delete The Temp Object On The RWDC If ($cleanupTempObject) { Write-Host "" Write-Host " Deleting Temp Text File... `n" -ForeGroundColor Yellow Remove-Item $($scriptsUNCPathOnSourcingRWDC + "\" + $tempObjectName) -Force Write-Host " Temp Text File [$tempObjectName] Has Been Deleted On The Target RWDC! `n" -ForeGroundColor Yellow } ########## # Output The Table [B] Containing The Information Of Each Directory Server And How Long It Took To Reach That Directory Server After The Creation On The Source RWDC $TableOfDSServersB | Sort-Object Time | FT -AutoSize

-

SINGLE AD DOMAIN AD FOREST WHERE ALL DCs ARE REACHABLE:

In this case ALL directory servers are reachable without any problems!

The script uses the AD domain the server is a part of where the script is executed. I chose for the script to find an RWDC as the target RWDC to write the temp object to

image

Figure 1: Using The Current AD Domain As The Target AD Domain And Letting The Script To Locate An RWDC As The Target RWDC To Write The Temp Text File To

-

The temporary text file has been created and the script is now checking it on all directory servers (DCs in AD domain)

If a DC is marked green, then the temporary text file has replicated to it and the script found the temporary text file.

If a DC is marked red, then the temporary text file has not yet replicated to it OR the DC has been marked as unreachable.

When finished it shows the start time, the end time and the duration of time before the temporary text file reached all directory servers. It also removed the temporary text file again to keep stuff clean.

This is a W2K12 AD domain that uses DFS-R as the SYSVOL replication mechanism.

image

Figure 2: Creating The Temp Text File In The NetLogon Share, Enumerating Through Each Directory Server To Determine The Existence Of The Temp Text File And The End Result Of The SYSVOL Replication Latency/Convergence Test

-

SINGLE AD DOMAIN AD FOREST WHERE NOT ALL DCs ARE REACHABLE:

In this case NOT ALL directory servers are reachable. The RODC is separated by a firewall from the RWDCs. The RODC can communicate with both RWDCs, The RWDCs however cannot with the RODC, except for the RPC Endpoint Mapper port and the statically configured AD replication port for change notifications. For DCs that are not reachable, the domain name and the site name of the DC might not always be specified in the table.

The script uses the AD domain the server is a part of where the script is executed. I chose for the script to find an RWDC as the target RWDC to write the temp object to. NOTE the reachability of the RODC!

image

Figure 3: Using The Current AD Domain As The Target AD Domain And Letting The Script To Locate An RWDC As The Target RWDC To Write The Temp Text File To

-

The temporary text file has been created and the script is now checking it on all directory servers (DCs in AD domain)

If a DC is marked green, then the temporary text file has replicated to it and the script found the temporary text file.

If a DC is marked red, then the temporary text file has not yet replicated to it OR the DC has been marked as unreachable.

When finished it shows the start time, the end time and the duration of time before the temporary text file reached all directory servers. It also removed the temporary text file again to keep stuff clean.

This is a W2K12 AD domain that uses DFS-R as the SYSVOL replication mechanism.

However, the script is not able to check that because the firewall does not allow communications to the RODC! Because the RODC is not reachable, it is marked as “Fail” in the timing.

image

Figure 4: Creating The Temp Text File In The NetLogon Share, Enumerating Through Each Directory Server To Determine The Existence Of The Temp Text File And The End Result Of The SYSVOL Replication Latency/Convergence Test

-

For more information on SYSVOL replication see:

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Domain Services (ADDS), PowerShell, Replication, SYSVOL, Tooling/Scripting | 2 Comments »

(2013-03-02) Testing Active Directory Replication Latency/Convergence Through PowerShell

Posted by Jorge on 2013-03-02


About two years ago I wrote AD replication latency/convergence test script that can be found in this blog post.

The main differences of this new script compared to the old script are:

  • Only one mode of operation. Verbose information previously only available in verbose is now available in normal mode;
  • I changed the colors of the output. The dark blue text with a black background was terrible!;
  • When the script enumerates all directory servers the object replicates to, it will test the connection to each an every directory server. For DCs in the same AD domain that will be through port TCP:389 and for GCs in other AD domain that will be through port TCP:3268. If the directory server is reachable over the required port it will mark the DC/GC as such and the same applies if the directory server is not reachable because of routing issues or firewalls. The latter is important as the script then does not keep trying to reach those DCs/GCs while they are no reachable.

-

To use the script just copy the contents of the table below and put that in a script that could be called "Check-AD-Replication-Latency-Convergence.ps1", but this is of course not mandatory. In the explanations and examples I will use this name. Note that this script is not signed in any way by me, so you might need to adjust the PowerShell Execution Policy of the server where you want to execute this script. It is not possible to pass credentials through the script, so the account you are using to execute the script should have the correct permissions to create (and delete afterwards) the temporary object in the specific container in some AD domain (default used is ‘CN=Users,DC=<DOMAIN>,DC=<TLD>’) or you should execute the script from a PowerShell console window that was started with credentials that do have the correct permissions. In addition, the server where this script is executed must be able to access every DC (RWDC and RODC) in the same AD domain through LDAP (port 389) and all GCs in other AD domains through LDAP-GC (port 3268). So if your network is not fully routed or you have firewalls deployed between directory servers, and those directory servers are therefore not reachable, they will be marked as not reachable. This prevents the script from trying to contact the directory servers while it is not possible. Last but not least, I just tested this script in a single AD domain AD forest with a few DCs (RWDCs and RODCs) and in a multiple AD domain AD forest, also with a few DCs. So I do not know how well this is going to perform in an environment with a lot of DCs. If you are going to use this script, the only thing I would like to ask you is to give me feedback about it and your experiences. Thanks!

-

After the script is started, it will give you information about AD domains, DCs and GCs. At some point you need to specify in which AD domain, the temporary object will be created:

  • Either specify the FQDN of an existing AD domain in the AD forest
    OR
  • Just press ENTER to use the current AD domain the server is joined to and where the script is being executed on

-

REMARK: In either case the AD domain will be checked if it exists in the AD forest. If it exists, the script continues and if it does not exist, the script aborts.

-

After that, you need to specify on which RWDC from the previous AD domain, the temporary object will be created:

  • Either specify the term PDC to target the RWDC that hosts the PDC FSMO role
    OR
  • Either specify the FQDN of an existing RWDC in the AD domain
    OR
  • Just press ENTER to search for an RWDC in the AD domain
    • If an RWDC is located right away, then that RWDC will be used
    • If an RODC is located right away, then that cannot be used and the RWDC that hosts the PDC FSMO role will be targeted instead

-

REMARK: In all cases the DC specified will be checked if it exists in the AD domain and if it is an RWDC and not an RODC. If it exists and it is an RWDC, the script continues and if it does not exist or if it is an RODC, the script aborts.

-

The code of the PowerShell script that does this is included below. Screen dumps are also included after the code sample.

-

I have also linked a file with the contents below to this blog post. Get it HERE.

-

!!! DISCLAIMER/REMARKS !!!:

  • The script is freeware, you are free to distribute it, but always refer to this website as the location where you got it
  • This script is furnished "as is". No warranty is expressed or implied!
  • Always test first in lab environment to see if it meets your needs!
  • Use this script at your own risk!
  • I do not warrant this script to be fit for any purpose, use or environment
  • I have tried to check everything that needed to be checked, but I do not guarantee the script does not have bugs.
  • I do not guarantee the script will not damage or destroy your system(s), environment or whatever.
  • I do not accept liability in any way if you screw up, use the script wrong or in any other way where damage is caused to your environment/systems!
  • If you do not accept these terms do not use the script and delete it immediately!

!!! DISCLAIMER/REMARKS !!!:

-

# Abstract: This PoSH Script Checks The AD Replication Latency/Convergence # Written By: Brandon Shell # Blog: http://bsonposh.com/ # Re-Written by: Jorge de Almeida Pinto [MVP-DS] # Blog: http://jorgequestforknowledge.wordpress.com/ ########## # Start... Clear-Host Write-Host "*******************************************************" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "* --> Test AD Replication Latency/Convergence <-- *" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "* Written By: Brandon Shell [MVP-PoSH] *" -ForeGroundColor Yellow Write-Host "* (http://bsonposh.com/) *" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "* Re-Written By: Jorge de Almeida Pinto [MVP-DS] *" -ForeGroundColor Yellow Write-Host "* (http://jorgequestforknowledge.wordpress.com/) *" -ForeGroundColor Yellow Write-Host "* *" -ForeGroundColor Yellow Write-Host "*******************************************************" -ForeGroundColor Yellow ########## # Some Constants $continue = $true $cleanupTempObject = $true ########## # The Function To Test The Port Connection Function PortConnectionCheck($fqdnDC,$port,$timeOut) { $tcpPortSocket = $null $portConnect = $null $tcpPortWait = $null $tcpPortSocket = New-Object System.Net.Sockets.TcpClient $portConnect = $tcpPortSocket.BeginConnect($fqdnDC,$port,$null,$null) $tcpPortWait = $portConnect.AsyncWaitHandle.WaitOne($timeOut,$false) If(!$tcpPortWait) { $tcpPortSocket.Close() #Write-Host "Connection Timeout" Return "ERROR" } Else { #$error.Clear() $tcpPortSocket.EndConnect($portConnect) | Out-Null If (!$?) { #Write-Host $error[0] Return "ERROR" } Else { Return "SUCCESS" } $tcpPortSocket.Close() } } ########## # Get List Of AD Domains In The AD Forest, Create And Present In A Table $ThisADForest = [DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $ListOfADDomainsInADForest = $ThisADForest.Domains $TableOfADDomainsInADForest = @() Write-Host "" Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF DOMAINs IN THE AD FOREST..." -ForeGroundColor Cyan Write-Host "" Write-Host "Forest Mode/Level...: "$ThisADForest.ForestMode ForEach ($Domain in $ListOfADDomainsInADForest) { $TableOfADDomainsInADForestObj = "" | Select Name,RootDomain,DomainMode,CurrentDomain $TableOfADDomainsInADForestObj.Name = $Domain.Name $TableOfADDomainsInADForestObj.RootDomain = "FALSE" If ($ThisADForest.RootDomain -like $Domain.Name) { $TableOfADDomainsInADForestObj.RootDomain = "TRUE" } $TableOfADDomainsInADForestObj.DomainMode = $Domain.DomainMode If ($Domain.Name -like $Env:USERDNSDOMAIN) { $TableOfADDomainsInADForestObj.CurrentDomain = "TRUE" } Else { $TableOfADDomainsInADForestObj.CurrentDomain = "FALSE" } $TableOfADDomainsInADForest += $TableOfADDomainsInADForestObj } $TableOfADDomainsInADForest | FT -AutoSize Write-Host " --> Found [$($ListOfADDomainsInADForest.count)] AD Domain(s) In AD Forest..." -ForeGroundColor Cyan Write-Host "" ########## # Get List Of GCs In AD Forest, Create And Present In A Table $ListOfGCsInADForest = $ThisADForest.GlobalCatalogs $TableOfGCsInADForest = @() Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF GCs IN THE AD FOREST '$ThisADForest'..." -ForeGroundColor Cyan ForEach ($GC in $ListOfGCsInADForest) { $TableOfGCsInADForestObj = "" | Select Name,Domain,SiteName $TableOfGCsInADForestObj.Name = $GC.Name $TableOfGCsInADForestObj.Domain = $GC.Domain $TableOfGCsInADForestObj.SiteName = $GC.SiteName $TableOfGCsInADForest += $TableOfGCsInADForestObj } $TableOfGCsInADForest | FT -AutoSize Write-Host " --> Found [$($ListOfGCsInADForest.count)] GC(s) In AD Forest..." -ForeGroundColor Cyan Write-Host "" ########## # Determine Which AD Domain To Use For The Temp Object. This Does Assume The Correct Permissions In That AD Domain To Create/Delete The Object! Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "In Which AD Domain Should The Temp Object Be Created?" -ForeGroundColor Cyan Write-Host "" $ADDomainToWriteTo = Read-Host "Please Provide FQDN Or Just Press ENTER For Current AD Domain" # If No FQDN Of An AD Domain Is Specified, Then Use The Local AD Domain If ($ADDomainToWriteTo -eq "") { $ADDomainToWriteTo = $(Get-WmiObject -Class Win32_ComputerSystem).Domain } # If The FQDN Of An AD Domain Is Specified, Then Check If It Exists In This AD Forest If ($ADDomainToWriteTo -ne "") { $ADdomainvalidity = $False ForEach ($Domain in $ListOfADDomainsInADForest) { If ($Domain.Name -like $ADDomainToWriteTo) { $ADdomainvalidity = $True } } Write-Host "" Write-Host "Checking Existence Of The Specified AD Domain '$ADDomainToWriteTo' In The AD Forest..." -ForeGroundColor Yellow If ($ADdomainvalidity -eq $True) { Write-Host "" Write-Host "The Specified AD Domain '$ADDomainToWriteTo' Exists In The AD Forest!" -ForeGroundColor Green Write-Host "" Write-Host "Continuing Script..." -ForeGroundColor Green } If ($ADdomainvalidity -eq $False) { Write-Host "" Write-Host "The Specified AD Domain '$ADDomainToWriteTo' Does Not Exist In The AD Forest!" -ForeGroundColor Red Write-Host "" Write-Host "Please Re-Run The Script And Provide The FQDN Of An AD Domain That Does Exist In The AD Forest" -ForeGroundColor Red Write-Host "" Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" Break } } Write-Host "" ########## # Get List Of DCs In AD Domain, Create And Present In A Table $contextADDomainToWriteTo = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$ADDomainToWriteTo) $ListOfDCsInADDomain = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) $TableOfDCsInADDomain = @() Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF DCs IN THE AD DOMAIN '$ADDomainToWriteTo'..." -ForeGroundColor Cyan ForEach ($DC in $ListOfDCsInADDomain) { $TableOfDCsInADDomainObj = "" | Select Name,Domain,GC,FSMO,SiteName,DStype $TableOfDCsInADDomainObj.Name = $DC.Name $TableOfDCsInADDomainObj.Domain = $ADDomainToWriteTo $TableOfDCsInADDomainObj.GC = "FALSE" ForEach ($GC in $ListOfGCsInADForest) { If ($DC.Name -like $GC.Name) { $TableOfDCsInADDomainObj.GC = "TRUE" } } If ($DC.Roles -ne $null) { If ($DC.Roles -Contains "PdcRole") { $pdcFQDN = $DC.Name $pdcDomain = $ADDomainToWriteTo $pdcSite = $DC.SiteName } ForEach ($FSMO In $DC.Roles) { If ($FSMO -eq "SchemaRole") {$FSMO = "SCH"} If ($FSMO -eq "NamingRole") {$FSMO = "DNM"} If ($FSMO -eq "PdcRole") {$FSMO = "PDC"} If ($FSMO -eq "RidRole") {$FSMO = "RID"} If ($FSMO -eq "InfrastructureRole") {$FSMO = "INF"} $TableOfDCsInADDomainObj.FSMO += $FSMO+"/" } $TableOfDCsInADDomainObj.FSMO = ($TableOfDCsInADDomainObj.FSMO).SubString(0,$TableOfDCsInADDomainObj.FSMO.Length-1) } Else { $TableOfDCsInADDomainObj.FSMO = "....." } $TableOfDCsInADDomainObj.SiteName = $DC.SiteName $ListOfRWDCsInADDomain = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) | ?{$_.OutboundConnections -ne $null} $DStype = "Read-Only" ForEach ($RWDC In $ListOfRWDCsInADDomain) { If ($RWDC.Name -like $DC.Name) { $DStype = "Read/Write" Break } } $TableOfDCsInADDomainObj.DStype = $DStype $TableOfDCsInADDomain += $TableOfDCsInADDomainObj } $TableOfDCsInADDomain | FT -AutoSize Write-Host " --> Found [$($ListOfDCsInADDomain.count)] DC(s) In AD Domain..." -ForeGroundColor Cyan Write-Host "" ########## # Specify A RWDC From The Selected AD Domain Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "Which RWDC In The AD Domain '$ADDomainToWriteTo' Should Be Used To Create The Object?" -ForeGroundColor Cyan Write-Host "" Write-Host "Available Options Are:" -ForeGroundColor Yellow Write-Host "[*] Specify PDC To Use The DC With The PDC FSMO Role" -ForeGroundColor Yellow Write-Host "[*] Just Press Enter To Locate An RWDC" -ForeGroundColor Yellow Write-Host "[*] Specify The FQDN Of A Specific RWDC" -ForeGroundColor Yellow Write-Host "" $SourceRWDCInADDomain = Read-Host "Please Choose An Option" # If PDC Was Specified Find The RWDC With The PDC FSMO Role And Use That If ($SourceRWDCInADDomain -eq "PDC") { $SourceRWDCInADDomainFQDN = $pdcFQDN $SourceRWDCInADDomainDOMAIN = $pdcDomain $SourceRWDCInADDomainSITE = $pdcSite } # If Nothing Was Specified Automatically Locate An RWDC To Use If ($SourceRWDCInADDomain -eq "") { # Locate Just ONE DC (This Could Be An RWDC Or RODC) $SourceRWDCInADDomainObjectONE = [System.DirectoryServices.ActiveDirectory.DomainController]::findone($contextADDomainToWriteTo) # Locate All RWDCs In The AD Domain $SourceRWDCInADDomainObjectALL = [System.DirectoryServices.ActiveDirectory.DomainController]::findall($contextADDomainToWriteTo) | ?{$_.OutboundConnections -ne $null} $UseRWDC = $False # Check If The Single DC Found Is An RWDC Or Not By Checking If It Is In The List Of RWDCs ForEach ($RWDC In $SourceRWDCInADDomainObjectALL) { If ($RWDC.Name -like $SourceRWDCInADDomainObjectONE.Name) { $UseRWDC = $True } } # If The Single DC Found Is An RWDC, Then Use That One If ($UseRWDC -eq $True) { $SourceRWDCInADDomainFQDN = $SourceRWDCInADDomainObjectONE.Name $SourceRWDCInADDomainDOMAIN = $SourceRWDCInADDomainObjectONE.Domain $SourceRWDCInADDomainSITE = $SourceRWDCInADDomainObjectONE.SiteName } # If The Single DC Found Is An RODC, Then Find The RWDC With The PDC FSMO Role And Use That If ($UseRWDC -eq $False) { $SourceRWDCInADDomainFQDN = $pdcFQDN $SourceRWDCInADDomainDOMAIN = $pdcDomain $SourceRWDCInADDomainSITE = $pdcSite } } # If A Specific RWDC Was Specified Then Use That One If ($SourceRWDCInADDomain -ne "" -And $SourceRWDCInADDomain -ne "PDC") { $contextRWDCToWriteTo = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$SourceRWDCInADDomain) $SourceRWDCInADDomainObject = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($contextRWDCToWriteTo) $SourceRWDCInADDomainFQDN = $SourceRWDCInADDomainObject.Name $SourceRWDCInADDomainDOMAIN = $SourceRWDCInADDomainObject.Domain $SourceRWDCInADDomainSITE = $SourceRWDCInADDomainObject.SiteName } # Check If The Selected DC Actually Exists In The AD Domain And Its Is An RWDC And NOT An RODC $RWDCvalidity = $False ForEach ($DC in $ListOfRWDCsInADDomain) { If ($DC.Name -like $SourceRWDCInADDomainFQDN) { $RWDCvalidity = $True } } Write-Host "" Write-Host "Checking Existence And Connectivity Of The Specified RWDC '$SourceRWDCInADDomainFQDN' In The AD Domain '$ADDomainToWriteTo'..." -ForeGroundColor Yellow If ($RWDCvalidity -eq $True) { Write-Host "" Write-Host "The Specified DC '$SourceRWDCInADDomainFQDN' Is An RWDC And It Exists In The AD Domain '$ADDomainToWriteTo'!" -ForeGroundColor Green Write-Host "" Write-Host "Continuing Script..." -ForeGroundColor Green $ldapPort = "389" $timeOut = "500" $ldapConnectionResult = $null $fqdnDC = $SourceRWDCInADDomainFQDN $ldapConnectionResult = PortConnectionCheck $fqdnDC $ldapPort $timeOut If ($ldapConnectionResult -eq "SUCCESS") { Write-Host "" Write-Host "The Specified RWDC '$SourceRWDCInADDomainFQDN' Is Reachable!" -ForeGroundColor Green Write-Host "" Write-Host "Continuing Script..." -ForeGroundColor Green Write-Host "" } If ($ldapConnectionResult -eq "ERROR") { Write-Host "" Write-Host "The Specified RWDC '$SourceRWDCInADDomainFQDN' Is NOT Reachable!" -ForeGroundColor Red Write-Host "" Write-Host "Please Re-Run The Script And Make Sure To Use An RWDC That Is Reachable!" -ForeGroundColor Red Write-Host "" Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" Break } } If ($RWDCvalidity -eq $False) { Write-Host "" Write-Host "The Specified DC '$SourceRWDCInADDomainFQDN' Either Does NOT Exist In The AD Domain '$ADDomainToWriteTo' Or Is NOT And RWDC!" -ForeGroundColor Red Write-Host "" Write-Host "Please Re-Run The Script And Provide The FQDN Of An RWDC Within The AD Domain '$ADDomainToWriteTo' That Does Exist" -ForeGroundColor Red Write-Host "" Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" Break } ########## # Get List Of DCs/GCs In AD Domain/Forest To Which The Temp Object Will Replicate, Create And Present In A Table Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "LIST OF DIRECTORY SERVERS THE TEMP OBJECT REPLICATES TO..." -ForeGroundColor Cyan # Put The Selected RWDC Already In the Table [A] Of Directory Servers To Which The Temp Object Will Replicate $TableOfDSServersA = @() $TableOfDSServersAObj = "" | Select Name,Domain,GC,SiteName,Reachable $TableOfDSServersAObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersAObj.Domain = $ADDomainToWriteTo $TableOfDSServersAObj.GC = "FALSE" ForEach ($GC in $ListOfGCsInADForest) { If ($SourceRWDCInADDomainFQDN -like $GC.Name) { $TableOfDSServersAObj.GC = "TRUE" } } $TableOfDSServersAObj.SiteName = $SourceRWDCInADDomainSITE $TableOfDSServersAObj.Reachable = "TRUE" $TableOfDSServersA += $TableOfDSServersAObj # Put The Selected RWDC Already In the Table [B] Of Directory Servers Where The Replication Starts $TableOfDSServersB = @() $TableOfDSServersBObj = "" | Select Name,Domain,GC,SiteName,Time $TableOfDSServersBObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersBObj.Domain = $ADDomainToWriteTo $TableOfDSServersBObj.GC = "FALSE" ForEach ($GC in $ListOfGCsInADForest) { If ($SourceRWDCInADDomainFQDN -like $GC.Name) { $TableOfDSServersBObj.GC = "TRUE" } } $TableOfDSServersBObj.SiteName = $SourceRWDCInADDomainSITE $TableOfDSServersBObj.Time = 0.00 $TableOfDSServersB += $TableOfDSServersBObj # Add All Other Remaining DCs In The Targeted AD Domain To The List Of Directory Servers [A] ForEach ($DC In $ListOfDCsInADDomain) { If(!($DC.Name -like $SourceRWDCInADDomainFQDN)) { $TableOfDSServersAObj = "" | Select Name,Domain,GC,SiteName,Reachable $TableOfDSServersAObj.Name = $DC.Name $TableOfDSServersAObj.Domain = $DC.Domain $TableOfDSServersAObj.GC = "FALSE" ForEach ($GC in $ListOfGCsInADForest) { If ($DC.Name -like $GC.Name) { $TableOfDSServersAObj.GC = "TRUE" } } $TableOfDSServersAObj.SiteName = $DC.SiteName $ldapPort = "389" $timeOut = "500" $ldapConnectionResult = $null $fqdnDC = $DC.Name $ldapConnectionResult = PortConnectionCheck $fqdnDC $ldapPort $timeOut If ($ldapConnectionResult -eq "SUCCESS") { $TableOfDSServersAObj.Reachable = "TRUE" } If ($ldapConnectionResult -eq "ERROR") { $TableOfDSServersAObj.Reachable = "FALSE" } $TableOfDSServersA += $TableOfDSServersAObj } } # Add All Other Remaining GCs In The AD Forest To The List Of Directory Servers [A] ForEach ($GC In $ListOfGCsInADForest) { $ToBeAdded = $True ForEach ($DC In $ListOfDCsInADDomain) { If($DC.Name -like $GC.Name) { $ToBeAdded = $False } } If ($ToBeAdded) { $TableOfDSServersAObj = "" | Select Name,Domain,GC,SiteName,Reachable $TableOfDSServersAObj.Name = $GC.Name $TableOfDSServersAObj.Domain = $GC.Domain $TableOfDSServersAObj.GC = "TRUE" $TableOfDSServersAObj.SiteName = $GC.SiteName $gcPort = "3268" $timeOut = "500" $gcConnectionResult = $null $fqdnGC = $GC.Name $gcConnectionResult = PortConnectionCheck $fqdnGC $gcPort $timeOut If ($gcConnectionResult -eq "SUCCESS") { $TableOfDSServersAObj.Reachable = "TRUE" } If ($gcConnectionResult -eq "ERROR") { $TableOfDSServersAObj.Reachable = "FALSE" } $TableOfDSServersA += $TableOfDSServersAObj } } $TableOfDSServersA | FT -AutoSize Write-Host " --> Found [$($TableOfDSServersA.count)] Directory Server(s)..." -ForeGroundColor Cyan Write-Host "" ########## # Create The Temp Object On The Targeted RWDC Write-Host "-----------------------------------------------------------------------------" -ForeGroundColor Cyan Write-Host "CREATING TEMP CONTACT OBJECT IN AD...:" -ForeGroundColor Cyan Write-Host "" $domainNCDN = (([ADSI]"LDAP://$SourceRWDCInADDomainFQDN/rootDSE").defaultNamingContext) $container = "CN=Users," + $domainNCDN $tempObjectName = "adReplTempObject" + (Get-Date -f yyyyMMddHHmmss) Write-Host " --> On RWDC.............: $SourceRWDCInADDomainFQDN" -ForeGroundColor Yellow Write-Host " --> With Full Name......: $tempObjectName" -ForeGroundColor Yellow Write-Host " --> With Description....: ...!!!...TEMP OBJECT TO TEST AD REPLICATION LATENCY/CONVERGENCE...!!!..." -ForeGroundColor Yellow Write-Host " --> In AD Domain........: $ADDomainToWriteTo ($domainNCDN)" -ForeGroundColor Yellow Write-Host " --> In Container........: $container" -ForeGroundColor Yellow $tempObject = ([ADSI]"LDAP://$SourceRWDCInADDomainFQDN/$container").Create("contact","CN=$tempObjectName") $tempObject.Put("Description","...!!!...TEMP OBJECT TO TEST AD REPLICATION LATENCY/CONVERGENCE...!!!...") $tempObject.SetInfo() $tempObjectDN = $tempObject.distinguishedname Write-Host "`n Temp Contact Object [$tempObjectDN] Has Been Created On RWDC [$SourceRWDCInADDomainFQDN]! `n" -ForeGroundColor Yellow ########## # Go Through The Process Of Checking Each Directory Server To See If The Temp Object Already Has Replicated To It $startDateTime = Get-Date $i = 0 Write-Host " --> Found [$($TableOfDSServersA.count)] Directory Server(s)..." -ForeGroundColor Yellow Write-Host "" While($continue) { $i++ $oldpos = $host.UI.RawUI.CursorPosition Write-Host " ====================== CHECK $i ======================" -ForeGroundColor Yellow Write-Host "" Write-Host " REMARK: Each DC In The List Below Must Be Accessible Through LDAP Over TCP (389)" -ForeGroundColor Red Write-Host " REMARK: Each GC In The List Below Must Be Accessible Through LDAP-GC Over TCP (3268)" -ForeGroundColor Red Write-Host "" Start-Sleep 1 $replicated = $true # For Each Directory Server In The List/Table [A] Perform A Number Of Steps ForEach ($DSsrv in $TableOfDSServersA) { If ($DSsrv.Name -match $SourceRWDCInADDomainFQDN) { Write-Host " * Contacting DC In AD domain ...[$($DSsrv.Name.ToUpper())]..." -ForeGroundColor Yellow Write-Host " - DC Is Reachable..." -ForeGroundColor Green Write-Host " - Object [$tempObjectDN] Exists In The Database" (" "*3) -ForeGroundColor Green continue } # If The Directory Server Is A DC In The AD Domain, Then Connect Through LDAP (TCP:389) If ($DSsrv.Domain -like $ADDomainToWriteTo) { Write-Host "" Write-Host " * Contacting DC In AD domain ...[$($DSsrv.Name.ToUpper())]..." -ForeGroundColor Yellow $connectionResult = $null If ($DSsrv.Reachable -eq "TRUE") { Write-Host " - DC Is Reachable..." -ForeGroundColor Green $objectPath = [ADSI]"LDAP://$($DSsrv.Name)/$tempObjectDN" $connectionResult = "SUCCESS" } If ($DSsrv.Reachable -eq "FALSE") { Write-Host " - DC Is NOT Reachable..." -ForeGroundColor Red $connectionResult = "FAILURE" } } # If The Directory Server Is A GC In Another AD Domain, Then Connect Through LDAP-GC (TCP:3268) If (!($DSsrv.Domain -like $ADDomainToWriteTo)) { Write-Host "" Write-Host " * Contacting GC In Other AD Domain ...[$($DSsrv.Name.ToUpper())]..." -ForeGroundColor Yellow $connectionResult = $null If ($DSsrv.Reachable -eq "TRUE") { Write-Host " - DC Is Reachable..." -ForeGroundColor Green $objectPath = [ADSI]"GC://$($DSsrv.Name)/$tempObjectDN" $connectionResult = "SUCCESS" } If ($DSsrv.Reachable -eq "FALSE") { Write-Host " - DC Is NOT Reachable..." -ForeGroundColor Red $connectionResult = "FAILURE" } } # If The Connection To The DC Is Successfull If ($connectionResult -eq "SUCCESS") { If ($objectPath.name) { # If The Temp Object Already Exists Write-Host " - Object [$tempObjectDN] Now Does Exist In The Database" (" "*3) -ForeGroundColor Green If (!($TableOfDSServersB | ?{$_.Name -match $DSsrv.Name})) { $TableOfDSServersBobj = "" | Select Name,Domain,GC,SiteName,Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj.Domain = $DSsrv.Domain $TableOfDSServersBObj.GC = $DSsrv.GC $TableOfDSServersBObj.SiteName = $DSsrv.SiteName $TableOfDSServersBObj.Time = ("{0:n2}" -f ((Get-Date)-$startDateTime).TotalSeconds) $TableOfDSServersB += $TableOfDSServersBObj } } Else { # If The Temp Object Does Not Yet Exist Write-Host " - Object [$tempObjectDN] Does NOT Exist Yet In The Database" -ForeGroundColor Red $replicated = $false } } # If The Connection To The DC Is Unsuccessfull If ($connectionResult -eq "FAILURE") { Write-Host " - Unable To Connect To DC/GC And Check For The Temp Object..." -ForeGroundColor Red If (!($TableOfDSServersB | ?{$_.Name -match $DSsrv.Name})) { $TableOfDSServersBobj = "" | Select Name,Domain,GC,SiteName,Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj.Domain = $DSsrv.Domain $TableOfDSServersBObj.GC = $DSsrv.GC $TableOfDSServersBObj.SiteName = $DSsrv.SiteName $TableOfDSServersBObj.Time = "Fail" $TableOfDSServersB += $TableOfDSServersBObj } } } If ($replicated) { $continue = $false } Else { $host.UI.RawUI.CursorPosition = $oldpos } } ########## # Show The Start Time, The End Time And The Duration Of The Replication $endDateTime = Get-Date $duration = "{0:n2}" -f ($endDateTime.Subtract($startDateTime).TotalSeconds) Write-Host "`n Start Time......: $(Get-Date $startDateTime -format "yyyy-MM-dd HH:mm:ss")" -ForeGroundColor Yellow Write-Host " End Time........: $(Get-Date $endDateTime -format "yyyy-MM-dd HH:mm:ss")" -ForeGroundColor Yellow Write-Host " Duration........: $duration Seconds" -ForeGroundColor Yellow ########## # Delete The Temp Object On The RWDC If ($cleanupTempObject) { Write-Host "" Write-Host " Deleting Temp Contact Object... `n" -ForeGroundColor Yellow ([ADSI]"LDAP://$SourceRWDCInADDomainFQDN/$container").Delete("contact","CN=$tempObjectName") Write-Host " Temp Contact Object [$tempObjectDN] Has Been Deleted On The Target RWDC! `n" -ForeGroundColor Yellow } ########## # Output The Table [B] Containing The Information Of Each Directory Server And How Long It Took To Reach That Directory Server After The Creation On The Source RWDC $TableOfDSServersB | Sort-Object Time | FT -AutoSize

-

SINGLE AD DOMAIN AD FOREST WHERE ALL DCs ARE REACHABLE:

In this case ALL directory servers are reachable without any problems!

I just pressed ENTER to use the current AD domain.

image

Figure 1: Using The Current AD Domain As The Target AD Domain

-

I specified the term PDC to target the RWDC that hosts the PDC FSMO role

image

Figure 2: Using The RWDC That Hosts The PDC FSMO Role As The Target RWDC To Write The Temp Object To

-

The temporary object has been created and the script is now checking it on all directory servers (DCs in AD domain and GCs in other AD domains in AD forest if any)

If a DC is marked green, then the temporary AD object has replicated to it and the script found the temporary AD object.

If a DC is marked red, then the temporary AD object has not yet replicated to it OR the DC/GC has been marked as unreachable.

image

Figure 3: Creating The Temp Object In AD And Enumerating Through Each Directory Server To Determine The Existence Of The Temp Object

-

When finished it shows the start time, the end time and the duration of time before the temporary AD object reached all directory servers. It also removed the temporary AD object again to keep stuff clean.

This is a W2K12 AD forest/domain that has change notification enabled on all AD site links. So after a change has occurred on a certain RWDC it will notify the very first replication partner after 15 seconds (=default) and each other replication partner with pauses of 3 seconds (=default).

SO looking at the example above the following happened:

  • RWDC ‘R2FSRWDC1.ADDMZ.LAN’ is where the creation of the temporary AD object originated;
  • After about 15 seconds, RWDC ‘R2FSRWDC1.ADDMZ.LAN’ notified RWDC ‘R2FSRWDC2.ADDMZ.LAN’ and RWDC ‘R2FSRWDC2.ADDMZ.LAN’ inbound replicated the temporary AD object from RWDC ‘R2FSRWDC1.ADDMZ.LAN’;
  • About 3 seconds later, RWDC ‘R2FSRWDC1.ADDMZ.LAN’ notified RODC ‘R2FSRODC5.ADDMZ.LAN’ and RODC ‘R2FSRODC5.ADDMZ.LAN’ inbound replicated the temporary AD object from RWDC ‘R2FSRWDC1.ADDMZ.LAN’.

image

Figure 4: The End Result Of The AD Replication Latency/Convergence Test

-

SINGLE AD DOMAIN AD FOREST WHERE NOT ALL DCs ARE REACHABLE:

In this case NOT ALL directory servers are reachable. The RODC is separated by a firewall from the RWDCs. The RODC can communicate with both RWDCs, The RWDCs however cannot with the RODC, except for the RPC Endpoint Mapper port and the statically configured AD replication port for change notifications. For DCs that are not reachable, the domain name and the site name of the DC might not always be specified n the table.

I just pressed ENTER to use the current AD domain.

image

Figure 5: Using The Current AD Domain As The Target AD Domain

-

I chose for the script to find an RWDC as the target RWDC to write the temp object to. NOTE the reachability of the RODC!

image

Figure 6: Letting The Script To Locate An RWDC As The Target RWDC To Write The Temp Object To

-

The temporary object has been created and the script is now checking it on all directory servers (DCs in AD domain and GCs in other AD domains in AD forest if any)

If a DC is marked green, then the temporary AD object has replicated to it and the script found the temporary AD object.

If a DC is marked red, then the temporary AD object has not yet replicated to it OR the DC/GC has been marked as unreachable.

image

Figure 7: Creating The Temp Object In AD And Enumerating Through Each Directory Server To Determine The Existence Of The Temp Object

-

When finished it shows the start time, the end time and the duration of time before the temporary AD object reached all directory servers. It also removed the temporary AD object again to keep stuff clean.

This is a W2K12 AD forest/domain that has change notification enabled on all AD site links. So after a change has occurred on a certain RWDC it will notify the very first replication partner after 15 seconds (=default) and each other replication partner with pauses of 3 seconds (=default).

SO looking at the example above the following happened:

  • RWDC ‘R2FSRWDC1.ADDMZ.LAN’ is where the creation of the temporary AD object originated;
  • After about 15 seconds, RWDC ‘R2FSRWDC1.ADDMZ.LAN’ notified RWDC ‘R2FSRWDC2.ADDMZ.LAN’ and RWDC ‘R2FSRWDC2.ADDMZ.LAN’ inbound replicated the temporary AD object from RWDC ‘R2FSRWDC1.ADDMZ.LAN’;
  • MOST LIKELY, about 3 seconds later, RWDC ‘R2FSRWDC1.ADDMZ.LAN’ notified RODC ‘R2FSRODC5.ADDMZ.LAN’ and RODC ‘R2FSRODC5.ADDMZ.LAN’ inbound replicated the temporary AD object from RWDC ‘R2FSRWDC1.ADDMZ.LAN’. However, the script is not able to check that because the firewall does not allow communications to the RODC! Because the RODC is not reachable, it is marked as “Fail” in the timing.

image

Figure 8: The End Result Of The AD Replication Latency/Convergence Test

-

MULTIPLE AD DOMAIN AD FOREST WHERE ALL DCs ARE REACHABLE:

In this case ALL directory servers are reachable without any problems!

I just pressed ENTER to use the current AD domain.

image

Figure 9: Using The Current AD Domain As The Target AD Domain

-

I chose for the script to find an RWDC as the target RWDC to write the temp object to.

image

Figure 10: Letting The Script To Locate An RWDC As The Target RWDC To Write The Temp Object To

-

The temporary object has been created and the script is now checking it on all directory servers (DCs in AD domain and GCs in other AD domains in AD forest if any)

If a DC is marked green, then the temporary AD object has replicated to it and the script found the temporary AD object.

If a DC is marked red, then the temporary AD object has not yet replicated to it OR the DC/GC has been marked as unreachable.

image

Figure 11: Creating The Temp Object In AD And Enumerating Through Each Directory Server To Determine The Existence Of The Temp Object

-

When finished it shows the start time, the end time and the duration of time before the temporary AD object reached all directory servers. It also removed the temporary AD object again to keep stuff clean.

This is a W2K12 AD forest/domain that has change notification enabled on all AD site links. So after a change has occurred on a certain RWDC it will notify the very first replication partner after 15 seconds (=default) and each other replication partner with pauses of 3 seconds (=default).

SO looking at the example above the following happened:

  • RWDC ‘R1FSRWDC1.ADCORP.LAB’ is where the creation of the temporary AD object originated;
  • After about 15 seconds, RWDC ‘R1FSRWDC1.ADCORP.LAB’ notified RWDC ‘R1FSRWDC2.ADCORP.LAB’ and RWDC ‘R1FSRWDC2.ADCORP.LAB’ inbound replicated the temporary AD object from RWDC ‘R1FSRWDC1.ADCORP.LAB’;
  • About 3 seconds later, RWDC ‘R1FSRWDC1.ADCORP.LAB’ notified RWDC ‘C1FSRWDC2.CHILD.ADCORP.LAB’ and RWDC “C1FSRWDC2.CHILD.ADCORP.LAB’ inbound replicated the temporary AD object from RWDC ‘R1FSRWDC1.ADCORP.LAB’;
  • After about 15 seconds of RWDC ‘C1FSRWDC2.CHILD.ADCORP.LAB’ receiving the object, it notified RWDC ‘C1FSRWDC1.CHILD.ADCORP.LAB’ and RWDC ‘C1FSRWDC1.CHILD.ADCORP.LAB’ inbound replicated the temporary AD object from RWDC ‘C1FSRWDC2.CHILD.ADCORP.LAB’;

image

Figure 12: The End Result Of The AD Replication Latency/Convergence Test

-

For more information on AD replication see:

-

As check out this blog post to find the script version to check latency/convergence of the SYSVOL

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Domain Services (ADDS), PowerShell, Replication, Tooling/Scripting | 4 Comments »

(2013-02-08) Parsing A Pending Exports Drop File And Viewing Changes Offline

Posted by Jorge on 2013-02-08


The following is quite a good post about parsing a pending exports drop file and viewing it in the PowerShell GridView.

-

SOURCE: FIM – CS Export XML Parser

-

<QUOTE SOURCE=”FIM – CS Export XML Parser”>

When implementing a new management agent or performing a mass change in FIM, I always want to be sure that any object exports are identified and intentional for all add / updates / deletes. The Sync engine does a great job at showing this for a small amount of objects. But what if you have hundreds, or even thousands object adds/updates/deletes? What if you wanted to validate that a specific attribute or object isn’t getting modified? How can we extract out that information into a sortable/manageable form? Looking around on the forums and online, I’ve seen tools to report the statistics, but not the actual data. The closest I’ve seen was from http://www.wapshere.com/missmiis/using-powershell-to-parse-a-csexport-file

Say, I want to be able to see the following below but in a more manageable form.

image

Figure 1: Pending Exports (Adds, Modifies And Deletes)

-

image

Figure 2: Pending Export (Add) Of A Specific Objects

-

(And, say I want to see what’s happening for the 102 updates, 1 adds, and 1 deletes)

image

Figure 3: Run Profile Statistics For The “Export (Drop File And Stop Run)” Run Profile

-

At first I looked around to see what FIM was capable of in reporting this information. Seems there were 2 main options. Either the Management Agent’s Drop File & Stop on Export or the CSExport.exe utility that comes with the Sync Engine. Both these options creates a XML file, however they are subtly different from each other. Below are the two described in detail.

-

CSEXPORT.EXE

This is the most powerful tool for exporting data out from the connector space. It comes with the FIM 2010 product and has a nice usage help to export only the information your after. This tool will export all the connector space objects into an XML file containing “tower” holograms. These tower holograms represent what is pending import/export/es-crowed/unapplied, etc..

An example of the command for exporting all pending export data from the FIM Service MA is shown below:

csexport.exe "FIM Service" "pendingExport.xml" /f :x /o:h

-

clip_image001

Figure 4: Using CSEXPORT

-

You can find this utility in the “Bin” folder of where the sync engine service is installed. Depending on the number of objects pending export, this will give you a XML document filtered on the pending exports (similar to searching the connector-space on the MA for Pending Export). The hard part is making meaningful (and accurate) use out of the generated XML document.

This is where I started experimenting, creating modifications in Test and exporting the data to determine how the objects are represented in XML document. Unfortunately there is no documentation I can find on the exact behavior of these holograms (such as what happens when you have an export error, and pending modifications). I ended up writing a c# console XML parser, based off my testing in DEV. I plan on making it more customizable, but for now it seems to work ok. The utility I wrote will take any pending export file as generated from the CSExport.exe program and transform the XML into a CSV format (quote encased, pipe-delimited). The first three columns “ID, ObjectType, OperationType” exist for all objects and should be self explanatory. The rest of the columns show all add/update/delete attributes for any objects being added/updated/deleted. Blank means that it doesn’t apply to the object or it’s not being updated.

The utility can be ran to “-showmodifications” or just display what the values will be updated to. The difference is that the showmodifications will display the “Old Value” as seen in the Sync Engine whenever possible (see image below)

clip_image003

Figure 4: Viewing The CSV Produced By The CSExportParse Utility In ShowModifications Mode (Download Link available later)

-

*note, there are some side effects with viewing the “-showmodifications” output due to the way line breaks are outputted, I’ll blog more about that later*

When ran without the “-showmodifications” switch the output will be similar to the following image below:

clip_image005

Figure 5: Viewing The CSV Produced By The CSExportParse Utility In Normal Mode (Download Link available later)

-

As you can see in the images above, I imported the file into excel and can easily filter and group the data to show all changes queued up.

-

Download: CS Export Parser v1.0

*requires the .NET framework 3.5

-

Drop File and Stop Run (Export – Run Profile)

At first I decided to give parsing this information in PowerShell from the Log Export a try. I wanted to write a script that would transform the XML file into CSV format which can be sorted, queried, and manipulated in other programs, such as Excel. The result will be a CSV file with at least 3 headers:

  1. “ID” – The connector-space “anchor” or unique identifier in the connector space
  2. “ObjectType” – The type of object (Person,Group,Contact) of the CS object
  3. “OperationType” – Defines what is happening to the object [being created/being updated/being deleted]

All additional header columns listed in the file are the attributes names themselves being added/updated and pertain only to the OpertationType of add / updates. *Note, the max number of columns added is based off the max number of attributes being added/modified from a single object. Also, anytime an object has an attribute value of blank (and the operation is update/add) it means that that particular attribute isn’t going to be modified in target system. Remember this shows what’s being updated, if the value is blank for a field, it doesn’t mean it’s being removed, just that the attribute wasn’t touched for that object.

The XML XML information (in my opinion) is simpler to interpret then the csexport. However, one annoyingly thing I discovered is that objectTypes don’t seem to be defined for any updates or deletes (just adds). Therefore you would need to look back on the object’s ID to determine what type of object it is, this isn’t really the case for the CSExport as you can export almost any information (including the objectType) to build the report. Another limitation is that in my testing for custom management agents ECMA, it shows all update operations as “Replace” and doesn’t give any information as to what is being modified clip_image006

The script below can be used to generate the CSV file. *Note it doesn’t work with the CSExport utility.

Keep in mind that this is a work in progress…

NOTE FROM JORGE: the version of the code below can be used in a PowerShell script. I have been having issues with the script below when large amounts of data need to be read (e.g. 300000 lines) I have to split it in multiple files of for example 40000 lines to be able to read it. I still do not understand why this happens. The original script missed a declaration for $I = 0 in the update section

# http://blog.fractalengine.com/fim-log-export-xml-parser/ # File Name: FIM-Sync-Engine-Parse-Export-Run-Log.ps1 Param( [Parameter(Mandatory=$true)] [string] $filePath, [Switch] $showModifications ) function ProcessExportXML { $global:MainList = @() $global:Headers = @("ID","ObjectType","OperationType") if (!(Test-Path $filePath)) { throw "Invalid path: $filePath" } try { [System.Xml.XmlDocument] $xml = New-Object System.Xml.XmlDocument $xml.Load($filePath) #have to load the namespaces $namespaceURI = $xml.mmsml.xmlns $ns = @{"e"=$namespaceURI} getAdds -xml $xml -ns $ns getDeletes -xml $xml -ns $ns getUpdates -xml $xml -ns $ns -mods $showModifications $report = $global:MainList | select $global:Headers return $report } catch { } } function getUpdates($xml, $ns, $mods) { try { $updates = $xml | select-xml "//e:delta[@operation='update']" -Namespace $ns if ($updates -ne $null) { $totalUPD = $(if ($updates.count -gt 0) { $updates.count;} else { 1; }) $iUPD = 0 $updates | % { $node = $_.Node write-progress -Activity "Processing Updates" -Status $node.dn -PercentComplete ($iUPD / $totalUPD * 100) $obj = New-Object -TypeName System.Object $obj | Add-Member -MemberType NoteProperty -Name "ID" -Value $node.dn $obj | Add-Member -MemberType NoteProperty -Name "ObjectType" -Value $null $obj | Add-Member -MemberType NoteProperty -Name "OperationType" -Value $node.operation #get attributes $node.attr | % { $item = $_ if ($global:Headers -notcontains $item.name) { $global:Headers += $item.name } switch ($item.operation) { "update" { if ($mods) { #need to fix this, not sure how to handle multivalue attributes with modifications if ($item.multivalued -eq $true) { $stringBuilder = "" $valuesAdded = ($item.value | where { $_.operation -eq "add"}) $valuesRemoved = ($item.value | where { $_.operation -eq "delete"}) $valuesRemoved | % { $attributeValue = $_."#text" $stringBuilder += "Removed: [$attributeValue]`r`n" } $valuesAdded | % { $attributeValue = $_."#text" $stringBuilder += "Added: [$attributeValue]`r`n" } $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $stringBuilder.trim() } else { $stringBuilder = "" $old = ($item.value | where { $_.operation -eq "delete"})."#text" $new = ($item.value | where { $_.operation -eq "add"})."#text" $stringBuilder = "Old: [$old]`r`nNew: [$new]" $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $stringBuilder.trim() } } else { if ($item.multivalued -eq $true) { $stringBuilder = "" $valuesAdded = ($item.value | where { $_.operation -eq "add"}) $valuesAdded | % { $attributeValue = $_."#text" $stringBuilder += "$attributeValue;" } $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $stringBuilder.trim() } else { $obj | Add-Member -MemberType NoteProperty -Name $item.name -value (($item.value | where { $_.operation -eq "add" })."#text") } } } "delete" { $obj | Add-Member -MemberType NoteProperty -Name $item.name -value "[deleted]" } "add" { if ($item.multivalued -eq $true) { $stringBuilder = "" $item.value | % { $stringBuilder += "$_;" } $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $stringBuilder } else { $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $item.value } } } } $global:MainList += $obj $iUPD++ } } } catch { write-host "Error! $_ " } } function getAdds($xml,$ns) { try { $adds = $xml | select-xml "//e:delta[@operation='add']" -Namespace $ns if ($adds -ne $null) { $totalADD = $(if ($adds.count -gt 0) { $adds.count;} else { 1; }) $iADD = 0 $adds | % { $node = $_.Node write-progress -Activity "Processing Adds" -Status $node.dn -PercentComplete ($iADD / $totalADD * 100) $obj = New-Object -TypeName System.Object $obj | Add-Member -MemberType NoteProperty -Name "ID" -Value $node.dn $obj | Add-Member -MemberType NoteProperty -Name "ObjectType" -Value $node."primary-objectclass" $obj | Add-Member -MemberType NoteProperty -Name "OperationType" -Value $node.operation #get attributes $node.attr | % { $item = $_ if ($global:Headers -notcontains $item.name) { $global:Headers += $item.name } if ($item.multivalued -eq $true) { $stringBuilder = "" $item.value | % { $stringBuilder += "$_;" } $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $stringBuilder } else { $obj | Add-Member -MemberType NoteProperty -Name $item.name -value $item.value } } $global:MainList += $obj $iADD++ } } } catch { write-host "Error! $_" } } function getDeletes($xml, $ns) { try { $deletes = $xml | select-xml "//e:delta[@operation='delete']" -Namespace $ns if ($deletes -ne $null) { $totalDEL = $(if ($deletes.count -gt 0) { $deletes.count;} else { 1; }) $iDEL = 0 $deletes | % { $node = $_.Node write-progress -Activity "Processing Deletes" -Status $node.dn -PercentComplete ($iDEL / $totalDEL * 100) $obj = New-Object -TypeName System.Object $obj | Add-Member -MemberType NoteProperty -Name "ID" -value $node.dn $obj | Add-Member -MemberType NoteProperty -Name "ObjectType" -Value $null $obj | Add-Member -MemberType NoteProperty -Name "OperationType" -Value $node.operation $global:MainList += $obj $iDEL++ } } } catch { write-host "Error! $_" } } ProcessExportXML

-

To use this you would simple run the “Log Export” from your MA, then copy this code into a powershell console and specify the filePath to the XML document generated from the MA.

FIM-Sync-Engine-Parse-Export-Run-Log.ps1 -filePath "<Source XML File>" FIM-Sync-Engine-Parse-Export-Run-Log.ps1 -filePath "<Source XML File>" | Out-GridView OR FIM-Sync-Engine-Parse-Export-Run-Log.ps1 -filePath "<Source XML File>" -showModifications FIM-Sync-Engine-Parse-Export-Run-Log.ps1 -filePath "<Source XML File>" -showModifications| Out-GridView

-

The “ShowModifications” mode will show the “old & new” attribute values in each cell. Once done you can pipe the $report to an Export-csv cmdlet to write it’s contents out to a file, or just a out-gridview to display it within a powershell gridview. This is useful if you plan to use excel to filter and sort the objects.  With the first command you will see something like this

clip_image001[4]

Figure 6: Viewing The Output In PowerShell GridView When Running In Normal Mode

-

With the “-showModifications” switch it will look more like the following

clip_image002

Figure 7: Viewing The Output In PowerShell GridView When Running In ShowModifications Mode

-

</QUOTE SOURCE=”FIM – CS Export XML Parser”>

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Forefront Identity Manager (FIM) Sync, PowerShell, Tooling/Scripting | Leave a Comment »

(2013-02-08) Parsing A CSExport Generated XML File Into A Scoped CSV File

Posted by Jorge on 2013-02-08


I needed to research the CSExport output. However, that’s an XML file and not really helpful in diagnosing the data in it. Converting it to a CSV, so that it can be loaded into Excel is much much better. So after some google magic, I found Carol Wapshere’s script. However, that script had two issues I needed to solve. At first it does not support multi-values and I needed to diagnose the proxyAddresses attribute. And secondly, because the Get-Content CMDlet is being used to read the XML file it chokes if the XML file size is too large. The error can be seen below.

SNAGHTML32e8542f

Figure 1: Error Reading An XML File File That’s Too Large With The Get-Content CMDlet

-

I added logic to the script to also process multi-valued attributes when applicable and instead of using the Get-Content CMDlet, I read the XML file as shown in the script below. I was not able to specify the “MaxCharactersInDcoument” property when using the Get-Content CMDlet. The XML file I was reading was about 500 MB and it contained about 1000000000 characters. The method I use below does not have a limit and allows the configuration “MaxCharactersInDcoument” property if needed.

# Original Script By Carol Wapsphere (http://www.wapshere.com/missmiis/using-powershell-to-parse-a-csexport-file) # Script Rewrite By Jorge de Almeida Pinto (http://jorgequestforknowledge.wordpress.com/) # # Takes an XML file created by CSEXPORT, and produces a CSV file more suitable for opening in Excel. # Supports both single-valued attributes and multi-valued attributes # # Before using the script: # * Define the attributes of interest # * Define the object types of interest. * means every object Param( [Parameter(Mandatory=$true)] [string] $sourceXML, [string] $targetCSV ) # List Of Attributes To Put In The CSV File. CHANGE THIS AS NEEDED! # The First 5 Attributes Are Available For All Connector Spaces $csvHeaderColumns = @("dn","connector-state","connector-type","mv-guid","object-type","givenName","sn","displayName","mail","mailNickname","legacyExchangeDN","proxyAddresses") # Object Types Of Interest $objectTypes = @("user") # Read The Source XML File [System.Xml.XmlDocument] $xmlCSExportDoc = New-Object System.Xml.XmlDocument $xmlCSExportDoc.load($sourceXML) # Check If CSV File Already Exists If (Test-Path $targetCSV) { Remove-Item -Path $targetCSV -Force } # Write The CSV Header To The CSV File ForEach ($csvHeaderColumn In $csvHeaderColumns) { If ($csvHeader -eq "" -Or $csvHeader -eq $null) { $csvHeader = $csvHeaderColumn } Else { $csvHeader = $csvHeader + "," + $csvHeaderColumn } } Add-Content $targetCSV $csvHeader # Get The Information For The Scoped Objects ForEach ($csObject In $xmlCSExportDoc."cs-objects"."cs-object") { If ($objectTypes -Contains $csObject."object-type" -Or $objectTypes -Contains "*") { $csObjectHashTable = @{} $csObjectHashTable.Add("dn",$csObject."cs-dn") $csObjectHashTable.Add("connector-state",$csObject."connector-state") $csObjectHashTable.Add("object-type",$csObject."object-type") If ($csObject.connector -eq "0") { $csObjectHashTable.Add("connector-type","disconnector") $csObjectHashTable.Add("mv-guid","") ForEach ($csObjectAttribute In $csObject."unapplied-export-hologram".entry.attr) { If ($csObjectAttribute.multivalued -eq "false") { $csObjectHashTable.Add($csObjectAttribute.name,$csObjectAttribute.value) } If ($csObjectAttribute.multivalued -eq "true" -And $csObjectAttribute.type -ne "binary") { $multivaluedAttrValues = "" If ($csObjectAttribute.value -ne "" -And $csObjectAttribute.value -ne $null) { ForEach ($value in $csObjectAttribute.value) { If ($multivaluedAttrValues -eq "" -Or $multivaluedAttrValues -eq $null) { $multivaluedAttrValues = $value } Else { $multivaluedAttrValues += ";" + $value } } $csObjectHashTable.Add($csObjectAttribute.name,$multivaluedAttrValues) } Else { $csObjectHashTable.Add($csObjectAttribute.name,"") } } } } Else { $csObjectHashTable.Add("connector-type","connector") $csObjectHashTable.Add("mv-guid",$csObject."mv-link"."#text") ForEach ($csObjectAttribute In $csObject."synchronized-hologram".entry.attr) { If ($csObjectAttribute.multivalued -eq "false") { $csObjectHashTable.Add($csObjectAttribute.name,$csObjectAttribute.value) } If ($csObjectAttribute.multivalued -eq "true" -And $csObjectAttribute.type -ne "binary") { $multivaluedAttrValues = "" If ($csObjectAttribute.value -ne "" -And $csObjectAttribute.value -ne $null) { ForEach ($value in $csObjectAttribute.value) { If ($multivaluedAttrValues -eq "" -Or $multivaluedAttrValues -eq $null) { $multivaluedAttrValues = $value } Else { $multivaluedAttrValues += ";" + $value } } $csObjectHashTable.Add($csObjectAttribute.name,$multivaluedAttrValues) } Else { $csObjectHashTable.Add($csObjectAttribute.name,"") } } } } $csvLine = "" ForEach ($csvHeaderColumn in $csvHeaderColumns) { If ($csObjectHashTable.Contains($csvHeaderColumn)) { If ($csvLine -eq "") { $csvLine = "`"" + $csObjectHashTable.Item($csvHeaderColumn) + "`"" } Else { $csvLine += "," + "`"" + $csObjectHashTable.Item($csvHeaderColumn) + "`"" } } Else { If ($csvLine -eq "") { $csvLine = "," } Else { $csvLine += "," } } } Add-Content $targetCSV $csvline } }

-

When using the script you need to make adjustments to it before actually using it. This is shown below.

image

Figure 2: Configuring The Object Types And The Attributes To Be Parsed Into The CSV File

-

[1] you need to define the attribute list you want to parse in an array. The first 5 attributes are available for all object types in every connector.

[2] you need to define the object types you want to parse in an array.

To execute the script, see below.

image

Figure 3: Using The Script

-

.\FIM-Sync-Engine-Parse-CSExport.ps1 -sourceXML <Source XML> -targetCSV <Target CSV>

-

The CSV file in Excel should then look similar to

image

Figure 4: The CSV File In Excel

-

Happy diagnosing!

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in CSExport, Forefront Identity Manager (FIM) Sync, PowerShell, Tooling/Scripting | 1 Comment »

(2013-02-06) Using/Testing Queries Within/Against The FIM Portal

Posted by Jorge on 2013-02-06


To test your XPATH queries in the FIM Portal you can either use a test SET, a test Search Scope, the Quest PowerShell CMDlets for FIM or use the internal FIM PowerShell CMDlets. When testing XPATH queries I just want to see if the XPATH actually works AND if it returns the correct/expected results. With that you can say that the attention is on the XPATH query configuration itself than anything else. Because of that in the past I used a test dynamic SET to test my XPATH quer(y)(ies) as that was the easiest way to go. However, if you are also interested in returning specific attribute values, than you were better of using a Search Scope.

-

In both cases and in addition to what you wanted to do it could give you more work than you would want to have. Fear no more!

-

The guys at Predica have created a tool called the FIM Explorer. You can download the FIM Explorer from the Codeplex.

-

With the FIM Explorer you can:

  • Execute any XPATH query against the FIM Service. By default it enumerates all attributes for which you have permissions, but you can also choose which specific ones to return;
  • Look for objects through their ObjectID (Resource ID);
  • Display the results in a table;
  • Look at the properties of a single object by double-clicking the ObjectID in the “Resource ID” column of the corresponding object;
  • Navigate through references in linked attributes by clicking on the ObjectID of the reference;
  • Export the results in the tabel to an XML (same XML format as when using FIM Configuration CMDlets);
  • Import the result XML of either the FIM Explorer or the FIM Configuration CMDlets to display these in a table. This can be useful for "offline" analysis;
  • Run on any computer, as long as .NET Framework 4.5 is installed.

-

If you download the pre-compiled version of the FIM Explorer, you are good to go. Just before using the FIM Explorer you need to configure its configuration file called “Predica.FimExplorer.exe.config”. When you open that file you should see something similar to the picture below.

image

Figure 1: The Configuration File Of The FIM Explorer

-

If you are running the FIM Explorer on the server that has the FIM service installed, you can either specify “http://localhost/” or the actual FQDN that points to the FIM service (e.g. “http://fimsvc.adcorp.lab/”). If you are running the FIM Explorer on a computer that does not have the FIM service installed, then you must specify the FQDN that points to the FIM service (e.g. “http://fimsvc.adcorp.lab/”). In addition, you can also specify credentials. If you leave the credentials (“fimUser” and “fimPassword”) empty as shown in figure 1, then the FIM Explorer will connect by using the credentials of the current logged on user. As soon as you specify credentials, the FIM Explorer will use those credentials. That’s quite handy as with this you can easily test your XPATH queries AND you can test your request based permissions MPRs to see what a specific user is able to query for.

Instead of changing the credentials just before starting the FIM Explorer, I have written a quick and dirty PowerShell script that will look for the FIM Explorer configuration, display it to you, and allow you to easily change the configured credentials. When done, it will start the FIM Explorer right away. The PowerShell script should be located in the same folder as the FIM Explorer itself. See the script below:

# Predica.FimExplorer.ps1 Clear-Host $currentFolder = (Get-Location).Path $predicaFimExplorerConfig = New-Object XML $predicaFimExplorerConfig.Load("$currentFolder\Predica.FimExplorer.exe.config") $fimSvcAddress = $predicaFimExplorerConfig.configuration.appSettings.add[0].value $fimUser = $predicaFimExplorerConfig.configuration.appSettings.add[1].value $fimPassword = $predicaFimExplorerConfig.configuration.appSettings.add[2].value Write-Host "" Write-Host "Current FIM Svc Address...: $fimSvcAddress" Write-Host "Current User Account......: $fimUser" Write-Host "Current Password..........: $fimPassword" Write-Host "" $chosenUser = Read-Host "Current Logged On User (C), Other User (O) Or Specified User (S)" If ($chosenUser.ToUpper() -eq "C" -Or $chosenUser.ToUpper() -eq "") { Write-Host "" Write-Host "Using Credentials Of Current Logged On User" Write-Host "" $predicaFimExplorerConfig.configuration.appSettings.add[1].value = "" $predicaFimExplorerConfig.configuration.appSettings.add[2].value = "" } If ($chosenUser.ToUpper() -eq "O") { Write-Host "" Write-Host "Please Specify The Custom Credentials To Use..." Write-Host "" Write-Host "What is The User Name?" Write-Host " (<Domain NBT>\<sAMAccountName>) (<sAMAccountName>@<Domain FQDN>)" $credsUserName = Read-Host "User Name" Write-Host "" Write-Host "What's The Password?" $credsPassword = Read-Host "Password?" $predicaFimExplorerConfig.configuration.appSettings.add[1].value = "$credsUserName" $predicaFimExplorerConfig.configuration.appSettings.add[2].value = "$credsPassword" } If ($chosenUser.ToUpper() -eq "S") { Write-Host "" Write-Host "Using Credentials Of Already Specified User" Write-Host "" } $predicaFimExplorerConfig.Save("$currentFolder\Predica.FimExplorer.exe.config") Start-Sleep -s 3 &"$currentFolder\Predica.FimExplorer.exe"

-

However, when you use an unsigned PowerShell script (as the one above) and the server execution policy is configured as such it does not allow to execute unsigned PowerShell scripts, you are not able to use the script. In that case you can start the script through a batch file by specifying you want to bypass the execution policy. The PowerShell script should be located in the same folder as the FIM Explorer itself. See the script below:

REM Predica.FimExplorer.cmd @ECHO OFF PowerShell.exe -ExecutionPolicy Bypass -file "%0\..\Predica.FimExplorer.ps1"

This all looks like as shown in the picture below:

image

Figure 2: Starting The FIM Explorer Through A Script And Specifying Other Credentials

-

As soon as you start the FIM Explorer, it looks like the picture below.

image

Figure 3: Running The FIM Explorer

-

When looking at Figure 3….

[A] Here you can specify your custom XPATH query (/Person, /Person[AccountName=’ADM.ROOT’]) (also have a look at this post and this post)

[B] Or you just click on of the specified object types and return all objects of a specific type

[C] By default you get all attributes back for which you have permissions, or you select all the attributes you want to be returned in the response

[D] After specifying and selecting everything you want, you click the “Run Query” button.

[E] And over here all the objects matching your XPATH query with all attributes that you wanted to be returned (all or specific)

[F] Clicking “Export To XML” exports the result to an XML file that uses the same format as when using FIM Configuration CMDlets. Clicking “Import XML” allows you to import a result XML of either the FIM Explorer or the FIM Configuration CMDlets to display these in a table. This can be useful for "offline" analysis;

-

However, if your result is too large you will end up with the following nice error! Smile This basically tells you to increase the message size quota for incoming messages.

image

Figure 4: Message Size Quota Error When Limit Is Exceeded

-

To resolve the error, or in other words get rid of it you need to increased the message size quota. To do that you need to get the uncompiled version of the FIM Explorer. Click DOWNLOAD Save the ZIP somewhere and after that unpack it. You are not there yet, you still need to download the FIMClient solution used by the FIM Explorer. Click ZIP and save the file and after that unpack it. Copy the contents (folders ‘lib’ and ‘src’ and the three files) into the FIMClient folder of the uncompiled version of the FIM Explorer. You now need Visual Studio. Double-click on the file “Predica.FimExplorer.sln” to open it in Visual Studio.

-

image 

Figure 5: The “Predica.FimExplorer” Solution

-

Navigate to: “Solution ‘Predica.FimExplorer’” –> “FimClient” –> “Fim2010Client.Client” –> “_Predica” –> “CodeInit” –> “Bindings.cs” and double-click on “Bindings.cs”. Now search for “MaxReceivedMessageSize”.

image

Figure 6: Changing The Value For “MaxReceivedMessageSize”

-

Change the value to a value of your liking. In this case I just doubled the valued. Save the file “Bindings.cs”. Then right-click on “Solution ‘Predica.FimExplorer’” and select the option “Rebuild Solution”.

Now copy:

  • “..\FimClient\src\_external\fim2010client\Microsoft.ResourceManagement.Client\bin\Debug\Microsoft.ResourceManagement.Client.Predica.dll” to folder that contains the FIM Explorer
  • “..\src\UI.WPF\bin\Debug\Predica.FimExplorer.exe” to folder that contains the FIM Explorer

-

Now double-click on the file “Predica.FimExplorer.cmd” to start the FIM Explorer.

-

This is quite cool to easily test your XPATH queries! Great job guys!

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Forefront Identity Manager (FIM) Portal, PowerShell, Search Scopes, SET, Tooling/Scripting, Xpath | Leave a Comment »

(2013-01-28) Fixing The ProxyAddresses Attribute In AD With PowerShell

Posted by Jorge on 2013-01-28


At a customer of mine (no names are or will be mentioned to protect the innocent!) I’m rebuilding their FIM GAL Sync solution. After running the Full Import (Stage Only) Run Profile I started the Full Synchronization Run Profile and during that Run Profile the FIM Sync Engine started to complain about incorrect/unexpected values in the proxyAddresses attribute of CONTACT objects in the target OU of a connected AD forest.

-

After investigating the data health I found out that just over 1700 contacts had a proxyAddress value as shown in the picture below

image

Figure 1: Contact Object With An Incorrect X500 Address

-

Because there were too many objects to do it by hand, creating a PowerShell script was the next step.

I required the following three PowerShell scripts:

  1. Export all the proxyAddresses values of the contacts objects with an X500 address as shown in the figure 1 (a safe measure)
  2. Remove the incorrect X500 address from the proxyAddresses values of the affected contact objects (the cleanup)
  3. Reimport all the proxyAddresses values prior to the removal of the incorrect X500 address (risk mitigating action)

-

[1]

Get-ADObject -SearchBase "OU=CONTACTS-PARTNER.LAN,OU=Org-Users,DC=ADCORP,DC=LAB" -LDAPFilter "(&(objectClass=contact)(proxyAddresses=X500:))" -Properties DistinguishedName,ObjectGUID,proxyaddresses | Select DistinguishedName,ObjectGUID,@{Name='proxyAddresses';Expression={[string]::join(";", $($_.proxyAddresses))}} | Export-Csv -Path .\ContactsWithBrokenX500Addresses.csv

-

[2]

Get-ADObject -SearchBase "OU=CONTACTS-PARTNER.LAN,OU=Org-Users,DC=ADCORP,DC=LAB" -LDAPFilter "(&(objectClass=contact)(proxyAddresses=X500:))" -Properties DistinguishedName,ObjectGUID,proxyaddresses | %{Set-ADObject -Identity $_.ObjectGUID -Remove @{proxyAddresses='X500:'}}

-

[3]

Import-Csv ContactsWithBrokenX500Addresses.csv | ForEach-Object{ $guid = $_.ObjectGUID $proxyAddresses = $_.proxyaddresses -split ';' Set-ADObject -Identity $guid -Replace @{proxyAddresses=$proxyAddresses} }

-

The most tricky part was getting all the values from the multi-valued proxyAddresses attribute and export that to a CSV file

-

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: http://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in PowerShell, Tooling/Scripting | 1 Comment »

(2013-01-12) Active Directory Schema Discovery PowerShell Script/Tool

Posted by Jorge on 2013-01-12


Alexandre Augagneur, another directory services MVP, has created/released a PowerShell script that investigates the schema of your AD.

It tells you all kinds of things such as:

  • Count of attributes created on a specific date;
  • The date and time the AD schema was created;
  • The version of the AD schema;
  • The version of the Exchange schema;
  • The version of the Lync schema;
  • The number of schema objects found;
  • The number of unknown schema objects found;
  • A full list of schema objects/attributes (this can be exported to CSV to be imported into Excel).

-

You can connect to a local AD forest or a remote AD forest. However for the remote AD forest, your local AD forest must at least have a one-way trust where your local AD forest is the trusted AD forest and the remote AD forest is the trusting AD forest. Before using the tool/script you must download and load a CSV file that contains all the information about the schema objects/attributes and their version information.

image

Figure 1: The Active Directory Schema Discovery Script/Tool

-

image

Figure 2: The CSV File That Needs To Be Download

-

image

Figure 3: The CSV That Was Exported From The Tool To Be Imported Into Excel

-

You can get the script/tool through the following link: Active Directory Schema Discover 1.0

-

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
http://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Domain Services (ADDS), PowerShell, Schema, Tooling/Scripting | 4 Comments »

(2012-11-10) Testing/Understanding The Authentication Protocol Your Web Site Is Using

Posted by Jorge on 2012-11-10


Last Wednesday I was at a customer for a workshop/presentation/demo and talking about ADFS, federation, federated identities, authentication, SSO, claims,  Kerberos and NTLM. The day ended how to execute the next steps in troubleshooting both Kerberos and NTLM authentication because of the authentication issues this customer was experiencing.

-

In the past I already blogged about troubleshooting Kerberos/NTLM Authentication. Also see: (2012-01-26) Troubleshooting Authentication Problems – Kerberos Or NTLM

-

After arriving home and having some dinner I searched on the internet for “testing Kerberos authentication” and that’s when I found Michel Barneveld’s blog that contained a blog post about the Kerberos Authentication Tester.

-

This allows you to test the authentication protocol being used by a specific website. The main features are:

  • It shows what authentication method is used in a web request: None, Basic, NTLM or Kerberos
  • It shows the SPN used in case of Kerberos
  • It shows the HTTP status
  • It shows the HTTP Headers of the request
  • It shows the version of NTLM used (v1 or v2)
  • It has a detailed view with a complete breakdown of the Authorization header. (Yep, went through all the RFCs to dissect the Kerberos and NTLM packages)
  • It shows your current Kerberos tickets and allows you to remove them (like klist.exe)

-

I tried this tool against my test/demo environment and below you will find some screen dumps.

-

On The Settings TAB you can specific the credentials that should be used when targeting the specified website.

image

Figure 1: Settings TAB – Credentials And Proxy

-

On the Test TAB, after specifying a URL and clicking on [Test], it will output the HTTP Headers. In this case I was targeting a Kerberos Based Web Site.

image

Figure 2a: Test TAB – Output HTTP Headers For Kerberos Authentication

-

image

Figure 2b: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

image

Figure 2c: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

image

Figure 2d: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

image

Figure 2e: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

image

Figure 2f: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

image

Figure 2g: Test TAB – Output HTTP Headers For Kerberos Authentication (Continued)

-

In case of Kerberos Authentication, you can see the total list of Kerberos Tickets, including the one for the Kerberos Based Web Site.

image

Figure 3: Tickets TAB – List Of Kerberos Tickets

-

On the Test TAB, after specifying a URL and clicking on [Test], it will output the HTTP Headers. In this case I was targeting an NTLM Based Web Site.

image

Figure 4a: Test TAB – Output HTTP Headers For NTLM Authentication (Continued)

-

image

Figure 4b: Test TAB – Output HTTP Headers For NTLM Authentication (Continued)

-

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
http://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Kerberos AuthN, NTLM AuthN, Tooling/Scripting | Leave a Comment »

(2012-09-24) The MA Run Scheduler From The IDM Explorer Blog

Posted by Jorge on 2012-09-24


Søren Granfeldt, the IDM guy at the Identity Management Explorer has created a replacement for MSFT’s MAsequencer. MSFT’s MAsequencer is available from the MIIS 2003 Resource Kit (now that’s old stuff!) but it is not compatible with FIM 2010 (R2). Søren basically created a similar command line version that does work with FIM 2010 (R2). To help Søren develop the tool with cool features, he needs feedback of what you think of it including must/nice to haves. So, if you do use his MARunScheduler, make sure to provide feedback. You can do that through the following page: http://blog.goverco.com/p/marunscheduler.html

Thanks!

-

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
http://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Forefront Identity Manager (FIM) Sync, Tooling/Scripting | Leave a Comment »

(2012-07-19) Active Directory Replication Status Tool

Posted by Jorge on 2012-07-19


The Active Directory Replication Status Tool (ADREPLSTATUS) analyzes the replication status for domain controllers in an Active Directory domain or forest.

The screen dumps below are from my test/demo environment running with W2K12 RWDCs. The first two screen dumps show you the healthy environment and all other screen dumps show you issues within that same environment after turning off some DCs. This AD forest has two AD domains (ADCORP.LAB and CHLD.ADCORP.LAB) and each AD domain has two RWDCs.

image

Figure 1: Active Directory Replication Status Tool – Healthy Environment

-

image

Figure 2: Active Directory Replication Status Tool – Healthy Environment

-

image

Figure 3: Active Directory Replication Status Tool – Environment With Issues

-

image

Figure 4: Active Directory Replication Status Tool – Environment With Issues

-

image

Figure 5: Active Directory Replication Status Tool – Environment With Issues

-

image

Figure 6: Active Directory Replication Status Tool – Environment With Issues

-

If you click on an error code it will take you to another page showing more detailed information about that specific error (see figure 9 and 10 as an example)

image

Figure 7: Active Directory Replication Status Tool – Environment With Issues

-

If you click on an error code it will take you to another page showing more detailed information about that specific error (see figure 9 and 10 as an example)

image

Figure 8: Active Directory Replication Status Tool – Errors Summary

-

image

Figure 9: Active Directory Replication Status Tool – Detailed Information About An Error

-

image

Figure 10: Active Directory Replication Status Tool – Detailed Information About An Error

-

image

Figure 11: Active Directory Replication Status Tool – Technical Information About AD Replication

-

image

Figure 12: Active Directory Replication Status Tool – Technical Information About AD Replication

-

Overview

The Active Directory Replication Status Tool (ADREPLSTATUS) analyzes the replication status for domain controllers in an Active Directory domain or forest. ADREPLSTATUS displays data in a format that is similar to REPADMIN /SHOWREPL * /CSV imported into Excel but with significant enhancements. Specific capabilities for this tool include:

  • Expose Active Directory replication errors occurring in a domain or forest
  • Prioritize errors that need to be resolved in order to avoid the creation of lingering objects in Active Directory forests
  • Help administrators and support professionals resolve replication errors by linking to Active Directory replication troubleshooting content on Microsoft TechNet
  • Allow replication data to be exported to source or destination domain administrators or support professionals for offline analysis

-

System requirements

  • Supported operating systems:
    • Windows 7, Windows 8, Windows Server 2003, Windows Server 2008, Windows Server 2008 R2, Windows Server 2012, Windows Vista, Windows XP
  • ADREPLSTATUS does not install on server core installs of Windows
  • Windows 2000 not supported due to lack of support for .NET Framework 4.0
  • Domain membership requirements:
    • Must be joined to the Active Directory domain or forest you intend to monitor
  • .NET Framework requirements:
    • .NET Framework 4.0 (you may be prompted to install .NET Framework 3.5.1 first on Windows Server 2008)
  • Required User Credentials:
    • Target forest/domain user account
  • Supported DC OS versions that can be monitored by ADREPLSTATUS:
    • Windows Server 2003 (R2)
    • Windows Server 2008 (R2) 
    • Windows Server 2012 RTM
    • Windows 2000 not tested and is therefore not supported.
  • Other Requirements:
    • ADREPLSTATUS will not work when the following security setting is enabled on the operating system:
      • System cryptography: Use FIPS 140 compliant cryptographic algorithms, including encryption, hashing and signing algorithms

-

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
http://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Domain Services (ADDS), Replication, Tooling/Scripting | 8 Comments »

 
%d bloggers like this: