Jorge's Quest For Knowledge!

All about Windows Server, ADDS, ADFS & FIM (It Is Just Like An Addiction, The More You Have, The More You Want To Have!)

Archive for the ‘Active Directory Domain Services (ADDS)’ Category

(2014-07-18) Checking New Schema Extensions For Potential Schema Conflicts

Posted by Jorge on 2014-07-18


Justin Hall, who works at Microsoft has published a PowerShell script which checks/validates new schema extensions for potential conflicts. The original script, including additional information can be found here. I have re-written and updated that script to perform additional checks against the AD schema, but also to perform checks within the new extensions themselves. At the same time I also resolved some bugs that I had found in the script.

-

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

-

!!! 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 !!!:

-

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

-

# Abstract: This PoSH Script Checks/Validates (New) Schema Extensions # Written by: Justin Hallo [MSFT] # Original Location: http://gallery.technet.microsoft.com/scriptcenter/0672d181-ab2c-4c92-8466-d93a67412207 # # Re-Written by: Jorge de Almeida Pinto [MVP-DS] # BLOG: http://jorgequestforknowledge.wordpress.com/ # # 2010-05-07: (v0.1): Initial version of the script # 2014-07-07: (v0.2): Re-written script with additional checks in current schema en extensions file and resolved bugs # # -----> !!! DISCLAIMER/REMARKS !!! <------ # * The script is freeware, you are free to distribute it, but always refer to this website (http://jorgequestforknowledge.wordpress.com/) 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 any 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 !!! <------ Param( [Parameter(Mandatory=$true)] [array]$inputLDIFFileList, # [Mandatory] The List Of Input LDIF Files (Comma Separated) With The Schema Extensions To Check For Conflicts [Parameter(Mandatory=$true)] [string]$outputLDIFFile, # [Mandatory] The Output LDIF File With The Results Of The Analyses Of The Schema Extensions [Parameter(Mandatory=$false)] [string]$currentSchemaLDIFFile # [Optional] The LDIF File Containing The AD Schema To Check Against. When Not Specified It will Exported ) $global:errorCount = 0 $global:warningCount = 0 # Configure The Appropriate Screen And Buffer Size To Make Sure Everything Fits Nicely $uiConfig = (Get-Host).UI.RawUI $uiConfig.WindowTitle = "+++ AD SCHEMA EXTENSION CONFLICT ANALYZER +++" $uiConfig.Foregroundcolor = "White" $uiConfigBufferSize = $uiConfig.BufferSize $uiConfigBufferSize.Width = 120 $uiConfigBufferSize.Height = 9999 $uiConfigScreenSizeMax = $uiConfig.MaxPhysicalWindowSize $uiConfigScreenSizeMaxWidth = $uiConfigScreenSizeMax.Width $uiConfigScreenSizeMaxHeight = $uiConfigScreenSizeMax.Height $uiConfigScreenSize = $uiConfig.WindowSize If ($uiConfigScreenSizeMaxWidth -lt 120) { $uiConfigScreenSize.Width = $uiConfigScreenSizeMaxWidth } Else { $uiConfigScreenSize.Width = 120 } If ($uiConfigScreenSizeMaxHeight -lt 75) { $uiConfigScreenSize.Height = $uiConfigScreenSizeMaxHeight - 5 } Else { $uiConfigScreenSize.Height = 75 } $uiConfig.BufferSize = $uiConfigBufferSize $uiConfig.WindowSize = $uiConfigScreenSize Clear-Host Write-Host " *****************************************************" -Foregroundcolor Magenta Write-Host " * *" -Foregroundcolor Magenta Write-Host " * --> AD SCHEMA EXTENSION CONFLICT ANALYZER <-- *" -Foregroundcolor Magenta Write-Host " * *" -Foregroundcolor Magenta Write-Host " * Written By: Justin Hall [MSFT] *" -Foregroundcolor Magenta Write-Host " *(http://gallery.technet.microsoft.com/scriptcenter)*" -Foregroundcolor Magenta Write-Host " * *" -Foregroundcolor Magenta Write-Host " * Re-Written By: Jorge de Almeida Pinto [MVP-DS] *" -Foregroundcolor Magenta Write-Host " * (http://jorgequestforknowledge.wordpress.com/) *" -Foregroundcolor Magenta Write-Host " * *" -Foregroundcolor Magenta Write-Host " *****************************************************" -Foregroundcolor Magenta Function global:DisplayScriptUsage { Write-Host "====================================================" Write-Host "`n EXTENSION CHECKER SCRIPT USAGE `n" Write-Host "====================================================" Write-Host "USAGE SUMMARY" Write-Host "`n -inputLDIFFileList " -Foregroundcolor darkCyan Write-Host "`n [MANDATORY] Provide the path to the input file containing the custom schema extensions here.`n" Write-Host "`n -outputLDIFFile " -Foregroundcolor darkCyan Write-Host "`n [MANDATORY] Provide the path to the output LDF file containing the annotations and suggested corrections. `n" Write-Host "`n -currentSchemaLDIFFile " -Foregroundcolor darkCyan Write-Host "`n [OPTIONAL] This is a required field If the operation is being run on a test DC." Write-Host " If the validate operation is being performed on a production DC, the current schema file need not be provided. The script will extract it.`n" Write-Host "EXAMPLES" -Foregroundcolor darkCyan Write-Host " .\AD-Schema-Extension-Conflict-Analyzer.ps1 -inputLDIFFileList sampleldf1.ldf,sampleldf2.ldf -outputLDIFFile results.ldf -currentSchemaLDIFFile myProductionSchema.ldf `n" Exit } # The function below will check current production schema file and/or the input custom extension file to make sure that the class / attribute being referenced (in systemMayContain, # systemMustContain, mayContain, mustContain, subClassOf , systemAuxiliaryClass , auxiliaryClass , systemPossSuperiors , and possSuperiors) has already been created. # If it has not been created but is still being referenced in one of the attributes above then that is an error and it needs to be flagged. Function global:ConfirmExistenceofReferencedAttributesOrClasses($attrClassList) { $attrsClasses = $attrClassList.Split(",") ForEach($item in $attrsClasses) { If (!($currentSchemaLDIFFileContent | Select-String -pattern $item -quiet)) { # The attribute/class under consideration was not found in current production schema file. We need to check the input file now. May be it is a new addition. $searchString = "CN=" + $item If(!($consolidatedLDIFFileContent | Select-String -pattern $searchString -quiet)) { $searchString = "lDAPDisplayName: " + $item If(!($consolidatedLDIFFileContent | Select-String -pattern $searchString -quiet)) { # No declaration of the attribute/class under consideration was found in the inputFile too. Looks like an error. The attribute/class should have been created before referencing it. Write-Host "[ERROR] : The class or attribute under consideration " $item " is being referenced but has not been created so far. Make sure it is created before using it!" -Foregroundcolor Red $tempError = "### ERROR : The class or attribute under consideration - " + $item + " - is being referenced but has not been created so far. Make sure it is created before using it!" $global:errorCount += 1 Add-Content -path $outputLDIFFile -value $tempError } } } } } # The function below will check current production schema file to look for a particular value of attributeID. attributeID is expected to be unique! Function global:checkUniquenessOfattributeIDCurrentSchema([string]$attributeIDValue) { If($currentSchemaLDIFFileContent | Select-String -pattern $attributeIDValue | ForEach { $_.Line | Select-String $attributeIDValue -quiet}) { Write-Host "[ERROR] : The attributeID value is already used in the schema. Make sure the attributeID OID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The attributeID value is already used in the schema. Make sure the attributeID OID is unique!" $global:errorCount += 1 } } # The function below will check extension file to look for a particular value of attributeID. attributeID is expected to be unique! Function global:checkUniquenessOfattributeIDNewExtensions([string]$attributeIDValue) { If(($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)(governsID:|attributeID:).*$attributeIDValue$" | Measure).Count -gt 1) { Write-Host "[ERROR] : The attributeID value is already used in the extension. Make sure the attributeID OID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The attributeID value is already used in the extension. Make sure the attributeID OID is unique!" $global:errorCount += 1 } } # The function below will check current production schema file to look for a particular value of governsID. governsID is expected to be unique! Function global:checkUniquenessOfgovernsIDCurrentSchema([string]$governsIDValue) { If($currentSchemaLDIFFileContent | Select-String -pattern $governsIDValue | ForEach { $_.Line | Select-String $governsIDValue -quiet}) { Write-Host "[ERROR] : The governsID value is already used in the schema. Make sure the governsID OID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The governsID value is already used in the schema. Make sure the governsID OID is unique!" $global:errorCount += 1 } } # The function below will check extension file to look for a particular value of governsID. governsID is expected to be unique! Function global:checkUniquenessOfgovernsIDNewExtensions([string]$governsIDValue) { If(($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)(governsID:|attributeID:).*$governsIDValue$" | Measure).Count -gt 1) { Write-Host "[ERROR] : The governsID value is already used in the extension. Make sure the governsID OID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The governsID value is already used in the extension. Make sure the governsID OID is unique!" $global:errorCount += 1 } } # The function below will check current production schema file to look for a particular value of mAPIID. mAPIID is expected to be unique! Function global:checkUniquenessOfmAPIIDCurrentSchema([string]$mAPIIDValue) { If($currentSchemaLDIFFileContent | Select-String -pattern "mAPIID" -quiet) { If($currentSchemaLDIFFileContent | Select-String -pattern "^(?i)mAPIID:.*$mAPIIDValue$" | ForEach { $_.Line | Select-String $mAPIIDValue -quiet}) { Write-Host "[ERROR] : The mAPIID value is already used in the schema. Make sure the mAPIID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The mAPIID value is already used in the schema. Make sure the mAPIID is unique!" $global:errorCount += 1 } } } # The function below will check extension file to look for a particular value of mAPIID. mAPIID is expected to be unique! Function global:checkUniquenessOfmAPIIDNewExtensions([string]$mAPIIDValue) { If(($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)mAPIID:.*$mAPIIDValue$" | Measure).Count -gt 1) { Write-Host "[ERROR] : The mAPIID value is already used in the extension. Make sure the mAPIID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The mAPIID value is already used in the extension. Make sure the mAPIID is unique!" $global:errorCount += 1 } } # The function below will go through current production schema file to make sure the attribute/class that is being added is not already present in the current production schema Function global:checkExistenceCurrentSchema([string]$attribValue) { ForEach ($line in $currentSchemaLDIFFileContent) { $SchemaValues = @($line.Split(":")) If ($SchemaValues[1].length -gt 0) { $SchemaValues[1] = $SchemaValues[1].TrimStart() } If ($SchemaValues[0].ToLower() -like "cn" -And $SchemaValues[1].Contains($attribValue)) { Write-Host "[ERROR] : Attempt being made to add an attribute/class which already exists in the schema!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : Attempt being made to add an attribute/class which already exists in the schema!" $global:errorCount += 1 } If ($SchemaValues[0].ToLower() -like "ldapdisplayname" -And $SchemaValues[1].Contains($attribValue)) { Write-Host "[ERROR] : The lDAPDisplayName value is already used in the schema. Make sure the lDAPDisplayName is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The lDAPDisplayName value is already used in the schema. Make sure the lDAPDisplayName is unique!" $global:errorCount += 1 } } } # The function below will check extension file to make sure the attribute/class that is being added is not being added twice Function global:checkExistenceCNNewExtensions([string]$attribValue) { If (($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)cn:.*$attribValue$" | Measure).Count -gt 1) { Write-Host "[ERROR] : Attempt being made to add an attribute/class twice through the extension!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : Attempt being made to add an attribute/class twice through the extension!" $global:errorCount += 1 } } # The function below will check extension file to look for a particular value of lDAPDisplayName. lDAPDisplayName is expected to be unique! Function global:checkExistenceLDAPDisplayNameNewExtensions([string]$attribValue) { If (($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)lDAPDisplayName:.*$attribValue$" | Measure).Count -gt 1) { Write-Host "[ERROR] : The lDAPDisplayName value is already used in the extension. Make sure the lDAPDisplayName is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The lDAPDisplayName value is already used in the extension. Make sure the lDAPDisplayName is unique!" $global:errorCount += 1 } } # The function below will go through current production schema file to look for the attribute $attrName which has been set as rdnAttid for another attribute # The syntax of $attrName should be UNICODE 2.5.5.9 Function global:checkSyntaxOfRdnattidAttribute($attrName) { $encounteredDn = 0 # The variable $encounteredDn will track whether or not we have come across the block that describes the attribute $attrName. # Every "dn: " statement in the current production schema file will be checked till $attrName is encountered. On encountering it $encounteredDn will be set to 1. # Once $attrName has been found its attributeSyntax attribute needs to be verified to make sure its value is 2.5.5.9 ForEach ($line in $currentSchemaLDIFFileContent) { If($encounteredDn -eq 0) { $SchemaValues = @($line.Split(":")) If ($SchemaValues[1].length -gt 0) { $SchemaValues[1] = $SchemaValues[1].TrimStart() } If (!$SchemaValues[0].CompareTo("dn") -AND $SchemaValues[1].Contains($attrName)) { $encounteredDn = 1 Continue } } Else { $SchemaValues = @($line.Split(":")) If ($SchemaValues[1].length -gt 0) { $SchemaValues[1] = $SchemaValues[1].TrimStart(" ") } If (!$SchemaValues[0].CompareTo("attributeSyntax")) { If($SchemaValues[1].CompareTo("2.5.5.9")) { # This means attributeSyntax of the attribute which is the rdnAttid for another attribute is not INTEGER. This is not expected. Write-Host "[ERROR] : AttributeSyntax of rDNAttID attribute should be INTEGER 2.5.5.9. Please correct this!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### WARNING : AttributeSyntax of rDNAttID attribute should be INTEGER 2.5.5.9. Please correct this!" $global:errorCount += 1 } Break } } } } Function global:ValidateExtensions { # Valid Syntaxes for Attributes in the Active Directory Schema $attributeSyntaxList = @{ "2.5.5.1" = "(DISTNAME)"; "2.5.5.2" = "(OBJECT_ID)"; "2.5.5.3" = "(CASE_STRING)"; "2.5.5.4" = "(NOCASE_STRING)"; "2.5.5.5" = "(PRINT_CASE_STRING)"; "2.5.5.6" = "(NUMERIC_STRING)"; "2.5.5.7" = "(DISTNAME_BINARY)"; "2.5.5.8" = "(BOOLEAN)"; "2.5.5.9" = "(INTEGER)"; "2.5.5.10" = "(OCTET_STRING)"; "2.5.5.11" = "(TIME)"; "2.5.5.12" = "(UNICODE)"; "2.5.5.13" = "(ADDRESS)"; "2.5.5.14" = "(DISTNAME_STRING)"; "2.5.5.15" = "(NT_SECURITY_DESCRIPTOR)"; "2.5.5.16" = "(I8)"; "2.5.5.17" = "(SID)" } # The systemFlags Attribute Specifies An Integer Value That Contains Flags That Define Additional Properties Of The Class $systemFlagsList = @{ 0x1 = "FLAG_ATTR_NOT_REPLICATED"; 0x2 = "FLAG_ATTR_REQ_PARTIAL_SET_MEMBER"; 0x4 = "FLAG_ATTR_IS_CONSTRUCTED"; 0x8 = "FLAG_ATTR_IS_OPERATIONAL"; 0x10 = "FLAG_SCHEMA_BASE_OBJECT"; 0x20 = "FLAG_ATTR_IS_RDN"; 0x2000000 = "FLAG_DISALLOW_MOVE_ON_DELETE"; 0x4000000 = "FLAG_DOMAIN_DISALLOW_MOVE"; 0x8000000 = "FLAG_DOMAIN_DISALLOW_RENAME"; 0x10000000 = "FLAG_CONFIG_ALLOW_LIMITED_MOVE"; 0x20000000 = "FLAG_CONFIG_ALLOW_MOVE"; 0x40000000 = "FLAG_CONFIG_ALLOW_RENAME"; 0x80000000 = "FLAG_DISALLOW_DELETE" } # The searchFlags Property Specifies The Characteristics And Behavior Of The Attribute $searchFlagsList = @{ 1 = "fATTINDEX"; 2 = "fPDNTATTINDEX"; 4 = "fANR"; 8 = "fPRESERVEONDELETE"; 16 = "fCOPY"; 32 = "fTUPLEINDEX"; 64 = "fSUBTREEATTINDEX"; 128 = "fCONFIDENTIAL"; 256 = "fNEVERVALUEAUDIT"; 512 = "fRODCFilteredAttribute"; 1024 = "fEXTENDEDLINKTRACKING"; 2048 = "fBASEONLY"; 4096 = "fPARTITIONSECRET"; } # Valid Syntaxes for Attributes in the Active Directory Schema $oMSyntaxList = @{ "0" = "(NO_MORE_SYNTAXES)"; "1" = "(BOOLEAN)"; "2" = "(INTEGER)"; "3" = "(BIT_STRING)"; "4" = "(OCTET_STRING)"; "5" = "(NULL)"; "6" = "(OBJECT_IDENTIFIER_STRING)"; "7" = "(OBJECT_DESCRIPTOR_STRING)"; "8" = "(ENCODING_STRING)"; "10" = "(ENUMERATION)"; "18" = "(NUMERIC_STRING)"; "19" = "(PRINTABLE_STRING)"; "20" = "(TELETEX_STRING)"; "21" = "(VIDEOTEX_STRING)"; "22" = "(IA5_STRING)"; "23" = "(UTC_TIME_STRING)"; "24" = "(GENERALISED_TIME_STRING)"; "25" = "(GRAPHIC_STRING)"; "26" = "(VISIBLE_STRING)"; "27" = "(GENERAL_STRING)"; "64" = "(UNICODE_STRING)"; "65" = "(I8)"; "66" = "(OBJECT_SECURITY_DESCRIPTOR)"; "127" = "(OBJECT )" } # This attribute specifies the unique object ID (OID) for the attribute or class $oMObjectClassList = @{ "KoZIhvcUAQEBBg==" = "1.2.840.113556.1.1.1.6 (REPLICA-LINK)"; "KoZIhvcUAQEBCw==" = "1.2.840.113556.1.1.1.11 (DN-BINARY)"; "KoZIhvcUAQEBDA==" = "1.2.840.113556.1.1.1.12 (DN-STRING)"; "KwwCh3McAIVK" = "1.35.44.2.1011.60.0.746 (DS-DN)"; "KwwCh3McAIU+" = "1.35.44.2.1011.60.0.734 (ACCESS-POINT)"; "KwwCh3McAIVc" = "1.35.44.2.1011.60.0.764 (PRESENTATION-ADDRESS)"; "VgYBAgULHQ==" = "2.6.6.1.2.5.43.61 (OR-NAME)" } # Mapping Between attributeSyntax And OmSyntax $attributeSyntaxToOmSyntaxList = @{ "2.5.5.1" = "127"; "2.5.5.2" = "6"; "2.5.5.3" = "27"; "2.5.5.4" = "20"; "2.5.5.5" = "19,22"; "2.5.5.6" = "18"; "2.5.5.7" = "127"; "2.5.5.8" = "1"; "2.5.5.9" = "2,10"; "2.5.5.10" = "4"; "2.5.5.11" = "23,24"; "2.5.5.12" = "64"; "2.5.5.13" = "127"; "2.5.5.14" = "127"; "2.5.5.15" = "66"; "2.5.5.16" = "65"; "2.5.5.17" = "4" } $linkedAttrsDisplayNameList = @{} $linkedAttrsOIDList = @{} $schemaUpdateNowFlag = "FALSE" $startedAddingClassesFlag = "FALSE" $cnValue = $null $ldapDisplayNameValue = $null # Now Start Scanning The (Combined) Input LDIF File $linecount = 0 Add-Content -path $outputLDIFFile -value "############################################################################################################" Add-Content -path $outputLDIFFile -value "#Scanned LDIF File With Corrections/Suggestions" Add-Content -path $outputLDIFFile -value "############################################################################################################" ForEach($line in $consolidatedLDIFFileContent) { $linecount++ If ($line.StartsWith("#")) { # Also Add Comments To Output Add-Content -path $outputLDIFFile -value $line Continue } Else { # To Get An Array Of Items, Values[0]..Values[x-1] Where [x] Is The Number Of Items Created Add-Content -path $outputLDIFFile -value $line # Split The Lines In a Left Part And A Right Part, Using The : As the Separator And Remove Any Traling Spaces $Values = @($line.Split(":")) If ($values[1].length -gt 0) { $values[1] = $values[1].TrimStart() } Else { Continue } # Show The Info On Screen Write-Host "Line.................#"$linecount Write-Host "Attribute............:"$values[0] Write-Host "Value................:"$values[1] $tempStr = "" $cnValue = $null $lDAPDisplayNameValue = $null # Convert The Left Side To Lower For The Switch Below To Work Correctly And Always Have Expected Results $values[0] = $values[0].ToLower() switch -wildcard ($values[0]) { "attributesyntax" { $attributeSyntaxValue = $values[1].TrimStart() If( !$attributeSyntaxList.ContainsKey($attributeSyntaxValue)) { Write-Host "[ERROR] : The value provided for attribute syntax is invalid. Please correct the value!" -Foregroundcolor Red $tempError = "### ERROR : The value provided for attribute syntax is invalid. Please correct the value!" Add-Content -path $outputLDIFFile -value $tempError $attributeSyntaxValue = $null $global:errorCount += 1 continue } Write-Host "attributeSyntax is...:" $attributeSyntaxList[$values[1]] $tempStr = "# attributeSyntax is:" + $attributeSyntaxList[$values[1]] Add-Content -path $outputLDIFFile -value $tempStr } "systemflags" { $values[1] = $values[1].TrimStart() $flags = $null For ($i = 1; $i -le 0x8000000; $i *= 2) { If ($values[1] -band $i) { If ($flags.length -gt 0) { $flags = $flags + " | " + $systemFlagsList[$i] } Else { If (!$systemFlagsList.ContainsKey($i)) { Write-Host "[ERROR] : The systemFlags value is invalid. Please correct it!" Add-Content -path $outputLDIFFile -value "### ERROR : The systemFlags value is invalid. Please correct it!" $global:errorCount += 1 Continue } $flags = $systemFlagsList[$i] } If ($i -eq 0x10) { # systemFlags contains 0x10 which means base schema object. This needs to be flagged. Write-Host "[WARNING] : This attribute/class has been marked as BASE_SCHEMA_OBJECT. This would need approval from MSFT Active Directory schema team!" -Foregroundcolor Yellow Add-Content -path $outputLDIFFile -value "### WARNING : This attribute/class has been marked as BASE_SCHEMA_OBJECT. This would need approval from MSFT Active Directory schema team!" } } } $tempStr = "# systemFlags is:" + $flags Write-Host "systemFlags is.......:" $flags Add-Content -path $outputLDIFFile -value $tempStr } "searchflags" { $values[1] = $values[1].TrimStart() $flags = ""; For ($i = 1; $i -le 512; $i *= 2) { If ($values[1] -band $i) { If ($flags.length -gt 0) { $flags = $flags + " | " + $searchFlagsList[$i] } Else { $flags = $searchFlagsList[$i] } If ($i -eq 8) { # fPreserveOnDelete Is Set. Make Sure This Is Really Required Add-Content -path $outputLDIFFile -value "### WARNING : This attribute has been marked as fPreserveOnDelete. Please confirm the need to do so!" Write-Host "[WARNING] : This attribute has been marked as fpreserveOnDelete. Please confirm the need to do so!" -Foregroundcolor Yellow $global:warningCount += 1 } If($i -eq 128) { # fConfidential Is Set. Make Sure This Is Really Required Add-Content -path $outputLDIFFile -value "### WARNING : This attribute has been marked as fConfidential. Please confirm the need to do so!" Write-Host "[WARNING] : This attribute has been marked as fConfidential. Please confirm the need to do so!" -Foregroundcolor Yellow $global:warningCount += 1 } } } Write-Host "searchFlags is.......:" $flags $tempStr = "# searchFlags:" + $flags Add-Content -path $outputLDIFFile -value $tempStr } "omsyntax" { $values[1] = $values[1].TrimStart() Write-Host "oMSyntax is..........:" $oMSyntaxList[$values[1]] If(!$oMSyntaxList.ContainsKey($values[1])) { Write-Host "[ERROR] : The value provided for oMSyntax is invalid. Please correct the value!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The value provided for oMSyntax is invalid. Please correct the value!" $global:errorCount += 1 Continue } $tempStr = "# oMSyntax:" + $oMSyntaxList[$values[1]] Add-Content -path $outputLDIFFile -value $tempStr $compareResult = $values[1].CompareTo($attributeSyntaxToOmSyntaxList[$attributeSyntaxValue]) If ($compareResult -ne 0) { $omValues = @($attributeSyntaxToOmSyntaxList[$attributeSyntaxValue].Split(",")) $compareResult = $values[1].CompareTo($omValues[0]) If($compareResult -ne 0) { $compareResult = $values[1].CompareTo($omValues[1]) If($compareResult -ne 0) { Add-Content -path $outputLDIFFile -value "### ERROR : oMSyntax does not seem to match the attributeSyntax value specified earlier for this attribute!" Write-Host "###[ERROR] : oMSyntax does not seem to match the attributeSyntax value specified earlier for this attribute!" -Foregroundcolor Red } } } $attributeSyntaxValue = "" } "omobjectclass" { $values[1] = $values[1].TrimStart() If (!$oMObjectClassList.ContainsKey($values[1])) { Write-Host "[ERROR] : oMObjectClass specified is invalid. Please correct the value and try again!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : oMObjectClass specified is invalid. Please correct the value and try again!" $global:errorCount += 1 Continue } Write-Host "oMObjectClass is.....:" $oMObjectClassList[$values[1]] $tempStr = "# oMObjectClass is:" + $oMObjectClassList[$values[1]] Add-Content -path $outputLDIFFile -value $tempStr $objectClass = $values[1] } "objectclass" { $values[1] = $values[1].TrimStart() If($values[1].CompareTo("classSchema") -eq 0) { If($schemaUpdateNowFlag.CompareTo("TRUE") -ne 0 -and $startedAddingClassesFlag.CompareTo("TRUE") -eq 0) { Write-Host "[ERROR] : schemaUpdateNow needs to be inserted before adding new classes!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : schemaUpdateNow needs to be inserted before adding new classes!" $startedAddingClassesFlag = "TRUE" $global:errorCount += 1 } } } "admindescription" { $values[1] = $values[1].TrimStart() If ($values[1].length -le 1) { Add-Content -path $outputLDIFFile -value "### ERROR : Adequate description has not been specified. Please correct this!" Write-Host "[ERROR] : Adequate description has not been specified. Please correct this!" -Foregroundcolor Red $global:errorCount += 1 } } "systemonly" { $values[1] = $values[1].TrimStart() $systemOnlyCheck = $values[1].CompareTo("TRUE") If ($systemOnlyCheck -eq 0) { # Object Has Been Flagged As systemOnly. Needs To Be Checked Further. Write-Host "[ERROR] : This object has been marked as systemOnly. This needs approval from the MSFT Active Directory schema team!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : This object has been marked as systemOnly. This needs approval from the MSFT Active Directory schema team!" $global:errorCount += 1 } } "attributeid" { $values[1] = $values[1].TrimStart() checkUniquenessOfattributeIDCurrentSchema($values[1]) checkUniquenessOfattributeIDNewExtensions($values[1]) } "governsid" { $values[1] = $values[1].TrimStart() checkUniquenessOfgovernsIDCurrentSchema($values[1]) checkUniquenessOfgovernsIDNewExtensions($values[1]) } "changetype" { $changetypeValue = $values[1].TrimStart() # When you come across a "changeType" that means an attempt is being made to add /modify an existing object $schemaUpdateNowFlag = "FALSE" } "cn" { $cnValue = $values[1].TrimStart() If ($changetypeValue -like "add" -or $changetypeValue -like "ntdsSchemaAdd") { # add operation is being performed for this attribute/class. Make sure that the object doesn't already exist in the schema. checkExistenceCurrentSchema($cnValue) checkExistenceCNNewExtensions($cnValue) } $cnValue = $null #$changetypeValue = "" } "ldapdisplayname" { $lDAPDisplayNameValue = $values[1].TrimStart() If ($changetypeValue -like "add" -or $changetypeValue -like "ntdsSchemaAdd") { # add operation is being performed for this attribute/class. Make sure that the object doesn't already exist in the schema. checkExistenceCurrentSchema($lDAPDisplayNameValue) checkExistenceLDAPDisplayNameNewExtensions($lDAPDisplayNameValue) } $lDAPDisplayNameValue = $null #$changetypeValue = "" } "linkid" { $values[1] = $values[1].TrimStart() If ($values[1] -eq "1.2.840.113556.1.2.50") { $linkedAttrsDisplayNameList.Add($ldapDisplayNameValue, 0); $linkedAttrsOIDList.Add($oid, 0); } Else { If(!$linkedAttrsDisplayNameList.ContainsKey($values[1])) { If(!$linkedAttrsOIDList.ContainsKey($values[1])) { Write-Host "[ERROR] : An attempt is being made to access a forward link that doesn't seem to exist. If this is a hard coded linkID that is not valid. Please follow the guidelines for obtaining a linkID!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : An attempt is being made to access a forward link that doesn't seem to exist. If this is a hard coded linkID that is not valid. Please follow the guidelines for obtaining a linkID!" $global:errorCount += 1 } Else { $linkedAttrsOIDList[$values[1]] = $linkedAttrsOIDList[$values[1]] + 1; } } Else { $linkedAttrsDisplayNameList[$values[1]] = $linkedAttrsDisplayNameList[$values[1]] + 1; Write-Host "A forward link does exist for this back link. Usage is valid!" Add-Content -path $outputLDIFFile -value "### A forward link does exist for this back link. Usage is valid!" } If(($consolidatedLDIFFileContent | Select-String -Pattern "^(?i)linkID:.*$($values[1])$" | Measure).Count -gt 1) { Write-Host "[ERROR] : The linkID value is already used in the extension. Make sure the linkID is unique!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The linkID value is already used in the extension. Make sure the linkID is unique!" $global:errorCount += 1 } } } "mapiid" { $values[1] = $values[1].TrimStart() checkUniquenessOfmAPIIDCurrentSchema($values[1]) checkUniquenessOfmAPIIDNewExtensions($values[1]) } # systemMayContain, systemMustContain,systemAuxiliaryClass, systemPossSuperiors # mayContain, mustContain # subClassOf , # auxiliaryClass # possSuperiors # ConfirmExistenceofReferencedAttributesOrClasses($attrClassList) "systemmaycontain" { If ($changetypeValue -like "modify" -or $changetypeValue -like "ntdsSchemaModify") { Write-Host "[ERROR] : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" $global:errorCount += 1 } Else { Write-Host $changeTypeValue " operation, so system attribute addition is permitted!" ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } } "systemmustcontain" { If ($changetypeValue -like "modify" -or $changetypeValue -like "ntdsSchemaModify") { Write-Host "[ERROR] : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" $global:errorCount += 1 } Else { Write-Host $changeTypeValue " operation, so system attribute addition is permitted!" ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } } "systemauxiliaryclass" { If ($changetypeValue -like "modify" -or $changetypeValue -like "ntdsSchemaModify") { Write-Host "[ERROR] : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" $global:errorCount += 1 } Else { Write-Host $changeTypeValue " operation, so system attribute addition is permitted!" ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } } "systemposssuperiors" { If ($changetypeValue -like "modify" -or $changetypeValue -like "ntdsSchemaModify") { Write-Host "[ERROR] : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : A systemOnly attribute is being added to an existing object. This operation is not allowed. Addition of systemOnly attributes while creating new objects is permitted!" $global:errorCount += 1 } Else { Write-Host $changeTypeValue " operation, so system attribute addition is permitted!" ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } } "m*contain" { $values[1] = $values[1].TrimStart() ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } "subclassof" { $values[1] = $values[1].TrimStart() ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } "*auxiliaryclass" { $values[1] = $values[1].TrimStart() ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } "posssuperiors" { $values[1] = $values[1].TrimStart() ConfirmExistenceofReferencedAttributesOrClasses($values[1]) } "ismemberofpartialattributeset" { $values[1] = $values[1].ToLower() If($values[1].CompareTo("true") -eq 0) { Write-Host "[WARNING] : Attribute has been marked as a member of the partial attribute set. Please confirm this requirement!" -Foregroundcolor Yellow Add-Content -path $outputLDIFFile -value "### WARNING : Attribute has been marked as a member of the partial attribute set. Please confirm this requirement!" $global:warningCount += 1 } } "schemaupgradeinprogress" { $values[1] = $values[1].ToLower() If($values[1].CompareTo("true") -eq 0) { Write-Host "[ERROR] : Invalid element. Please consider removing it!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : Invalid element. Please consider removing it!" $global:errorCount += 1 } } "schemaupdatenow" { $values[1] = $values[1].TrimStart() If($values[1].CompareTo("1") -eq 0) { $schemaUpdateNowFlag = "TRUE" } } "oid" { $oid = $values[1].TrimStart(); } "objectclasscategory" { $values[1] = $values[1].TrimStart() switch ($values[1]) { "structural" { Add-Content -path $outputLDIFFile -value "###ObjectClassCategory : Structural" } "auxiliary" { Add-Content -path $outputLDIFFile -value "###ObjectClassCategory : Auxiliary" } "abstract" { Add-Content -path $outputLDIFFile -value "###ObjectClassCategory : Abstract" } "88" { Add-Content -path $outputLDIFFile -value "###ObjectClassCategory : 88" } default { Write-Host "[ERROR] : The value provided for objectClassCategory " $values[1] " is not valid. Please correct it and try again. Accepted values are Structural, Auxiliary, Abstract, 88!" -Foregroundcolor Red Add-Content -path $outputLDIFFile -value "### ERROR : The value provided for objectClassCategory " $values[1] " is not valid. Please correct it and try again. Accepted values are Structural, Auxiliary, Abstract, 88!" $global:errorCount += 1 } } } "rdnattid" { $values[1] = $values[1].TrimStart() checkSyntaxOfRdnattidAttribute($values[1]) } "changetype" { $values[1]=$values[1].ToLower() switch($values[1]) { "add" { Write-Host "Allowed changeType" } "ntdsschemaadd" { Write-Host "Allowed changeType" } "modify" { Write-Host "Allowed changeType" } "ntdsschemamodify" { Write-Host "Allowed changeType" } "delete" { Write-Host "Allowed changeType" } "ntdsschemadelete" { Write-Host "Allowed changeType" } "ntdsschemamodrdn" { Write-Host "Allowed changeType" } default { Write-Host "[ERROR] : ChangeType " $values[1] " is invalid. Allowed changeType values are - add, ntdsschemaadd, modify, ntdsschemamodify, delete, ntdsschemadelete, ntdsschemamodrdn." Add-Content -path $outputLDIFFile -value "### ERROR : ChangeType value is invalid. Allowed changeType values are - add, ntdsschemaadd, modify, ntdsschemamodify, delete, ntdsschemadelete, ntdsschemamodrdn." $global:errorCount += 1 } } } } Write-Host "*******************************************************************************************************************" -Foregroundcolor DarkCyan } } Write-Host "`n +++ SUMMARY +++" -Foregroundcolor Cyan Write-Host " Errors....: " $errorCount -Foregroundcolor Red Write-Host " Warnings..: " $warningCount -Foregroundcolor Yellow Write-Host "REMARK: This script just helps you to validate new schema extensions and give you more confidence about the new" -Foregroundcolor Red Write-Host "extensions. However, it is in no way a full replacement of designing, validating, testing and implementing the" -Foregroundcolor Red Write-Host "extension. YOU remain responsible for the end-to-end process of correctly implementing any new extension into" -Foregroundcolor Red Write-Host "the schema!" -Foregroundcolor Red # Now that the whole file has been checked go through the $linkedAttrsOIDList and $linkedAttrsDisplayNameList to make sure the reference counts are > 0 # A count of zero means that a forward link was created but a back link was never created for it } # Main Program Function global:ExtensionChecker() { Write-Host "`nValidating Extensions...`n" # Get The Script Path $scriptFolderPath = (Get-Location).Path # Process All Specified Input Files And Get The Combined Content $consolidatedLDIFFileContent = $null $consolidatedLDIFFile = "combinedSchemaExtensions.ldif" Clear-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) ForEach ($inputLDIFFile in $inputLDIFFileList) { Write-Host "`nInput File To Validate..........:" $inputLDIFFile If(!(Test-Path $inputLDIFFile)) { Write-Host "`nInput file provided " $inputLDIFFile " does not exist. Please check the path and try again.`n" -Foregroundcolor Red Exit } $inputLDIFFileContent = Get-Content $inputLDIFFile Add-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) "############################################################################################################" Add-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) "### INPUT FILE: $inputLDIFFile" Add-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) "###---------------------------------------------------------------------------------------------------------" Add-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) $inputLDIFFileContent } Write-Host "`nConsolidated Input File Used....:" $($scriptFolderPath + "\" + $consolidatedLDIFFile) $consolidatedLDIFFileContent = Get-Content $($scriptFolderPath + "\" + $consolidatedLDIFFile) # Create A New Output File For The Results If ($outputLDIFFile -notmatch ":\\") { $outputLDIFFile = $scriptFolderPath + "\" + $outputLDIFFile } Write-Host "`nResults File With Corrections...:" $outputLDIFFile New-Item -ItemType file $outputLDIFFile -force | Out-Null # Export The Schema If Not Schema File Was Specified If (!$currentSchemaLDIFFile) { $ThisADForest = [DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $fqdnSchemaFSMO = $ThisADForest.SchemaRoleOwner.Name Write-Host "`nNo current schema file specified. Attempting to grab the schema from the Schema FSMO ($fqdnSchemaFSMO)..." $currentSchemaLDIFFile = $scriptFolderPath + "\currentSchemaLDIFFile.ldif" CMD.EXE /C "START /Wait LDIFDE.EXE -s $fqdnSchemaFSMO -f $currentSchemaLDIFFile -d #SchemaNamingContext -c #DefaultNamingContext DC=X" } # Get The Content From The Current Schema File Write-Host "`nCurrent Schema File.............:" $currentSchemaLDIFFile Write-Host "" If(!(Test-Path $currentSchemaLDIFFile)) { Write-Host "`nSchema file " $currentSchemaLDIFFile " does not exist. Please check the path and try again.`n" -Foregroundcolor Red Exit } Else { $currentSchemaLDIFFileContent = Get-Content $currentSchemaLDIFFile } # Go For It And Validate The Extensions ValidateExtensions } # Execute Main Program ExtensionChecker($args)

-

Below you will see the example output of the script. On purpose I created many conflicts, so that you are able to see what happens when you find any.

image

Figure 1: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 2: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 3: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 4: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 5: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 6: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 7: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 8: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 9: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 10: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 11: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 12: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 13: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 14: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 15: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 16: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

image

Figure 17: Sample/Example Out Of The "AD Schema Extension Conflict Analyzer" PowerShell Script

-

Cheers,

Jorge

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

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

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

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

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

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

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

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

Posted in Active Directory Domain Services (ADDS), PowerShell, Schema, Tooling/Scripting | Leave a Comment »

(2014-07-17) Exporting And Importing Password Settings Objects

Posted by Jorge on 2014-07-17


If you need to export and import your Password Settings Object from one AD domain to the other, you can use the following PowerShell commands:

# SOURCE ENVIRONMENT Import-Module ActiveDirectory Get-ADFineGrainedPasswordPolicy -Filter * -Properties * | Export-CliXml C:\TEMP\AllPSOs.xml # TARGET ENVIRONMENT Import-Module ActiveDirectory Import-CliXml C:\TEMP\AllPSOs.xml | %{ New-ADFineGrainedPasswordPolicy -Name $_.Name ` -ComplexityEnabled $_.ComplexityEnabled ` -Description $_.Description ` -DisplayName $_.DisplayName ` -LockoutDuration $_.LockoutDuration ` -LockoutObservationWindow $_.LockoutObservationWindow ` -LockoutThreshold $_.LockoutThreshold ` -MaxPasswordAge $_.MaxPasswordAge ` -MinPasswordAge $_.MinPasswordAge ` -MinPasswordLength $_.MinPasswordLength ` -PasswordHistoryCount $_.PasswordHistoryCount ` -Precedence $_.Precedence ` -ReversibleEncryptionEnabled $_.ReversibleEncryptionEnabled Add-ADFineGrainedPasswordPolicySubject ` -Identity $_.Name ` -Subjects $_.AppliesTo }

-

Cheers,

Jorge

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

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

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

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

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

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

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

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

Posted in Active Directory Domain Services (ADDS), Fine Grained Password Policies, PowerShell, Tooling/Scripting | Leave a Comment »

(2014-07-16) Active Directory Schema Deactivations (Defunct)

Posted by Jorge on 2014-07-16


When thinking about adjusting the AD schema (also applies to the ADLDS schema) you can categorize those adjustments into the following categories:

Schema Extensions

The most common schema adjustment is a schema extension. With a schema extension, new attribute and object class definitions are added to the schema, to be able to store data with specific requirements. A schema extension is easier and has a lower impact as it does not affect existing applications. However, it does have higher risk, as schema extensions cannot be undone. It must be done correctly the first time. TEST!

-

Schema Modifications

Schema modifications are less common than schema extensions. Changing, the schema definition of either object classes or attributes can adversely affect the application(s) using those definitions. For example, if an application expects to find specific information about all users in the enterprise by querying a GC, the application will be negatively impacted if the corresponding attributes are suddenly configured not to replicate to GCs. However, depending on the type of change, it can positively improve the functioning of application(s). For example, if one or more applications are using one or more specific attributes in queries and those attributes are not (yet) indexed, configuring those attributes to be indexed will improve the performance of those applications. Any change of any kind to existing schema definitions must carefully be evaluated and tested to fully understand all the consequences! TEST!

-

Schema Deactivations

It is not possible to delete schema definitions. However, when running at least W2K3 DCs, it is possible to deactivate schema definitions (attributes and object classes) if those are not required anymore. Are you even running at least forest functional level "Windows Server 2003", then you will be able to reuse the object identifier (governsId and attributeId values), the ldapDisplayName, and the schemaIdGUID that were associated with the defunct attribute or object class. This allows you to redefine the schema definition if it was mistakenly added to the schema.

Only custom schema definitions (non-default AD) can be deactivated. It is not possible to deactivate default AD schema definitions. To be able to deactivate an attribute, the attribute must not be defined on any active object class in any way (mayContain, mustContain, systemMayContain, systemMustContain, rDNAttId). To be able to deactivate an object class, the object class must not be defined on any other active object class in any way (auxiliaryClass, possSuperiors, systemAuxiliaryClass, systemPossSuperiors, subClassOf).

When an attribute or object class is deactivated, the corresponding data stored in AD is not removed automatically. Although data can be deleted afterwards, it is preferred to remove it manually prior to the deactivation as it cannot be edited (except deletion) anymore afterwards. Therefore, before deactivating an attribute, make sure to clear all attribute values on any object using that attribute. When deactivating an abstract or auxiliary object class, make sure to also clear all attribute values on any object using that abstract or auxiliary object class, and when also those attributes are not used in another object class in any way. When deactivating an structural object class, make sure to delete all instances of that object class from the directory service.

-

For additional information about adjusting the AD schema, please see:

-

In this blog post I’m going to focus on schema deactivations, and the effects when querying for information.

-

In any of three articles, available through the links above, you will read something similar to:

Effects of deactivating a schema object on schema updates

After a class salesUser is deactivated, any subsequent addition or modification of instances of salesUser fails as if salesUser has been deleted from the system; the same error codes are returned as if salesUser never existed at all. For example, creating a new instance of salesUser fails, and trying to modify or rename an existing instance of salesUser fails. Similarly, if an attribute hasPager is deactivated, hasPager is treated as nonexistent for new object creations, and attempting to modify (add or replace) the value of hasPager in an existing object fails.

However, you can still search for and delete existing instances of deactivated schema objects. For example, you can still search for all existing instances of salesUser and delete them, if necessary. Note that what you are doing is searching for and deleting objects that were created in the directory using the deactivated class, salesUser, not deleting the actual class definition from the schema. Similarly, you can search for all instances that have a value for the attribute hasPager and delete hasPager from the existing objects. This facilitates cleanup after a schema object is deactivated. If you decide that a class is not needed anymore, you can deactivate it so that no one can use it for any modifications. You can then clean up the existing instances of the class by searching for all instances and deleting them. Active Directory does not perform any automatic cleanup of data instances after a schema object is deactivated.

Similarly, you can deactivate an attribute and clean up all its instances. Note that, for a deactivated attribute, you can delete only the entire attribute from an object, not certain values of the attribute. For example, if hasPager is a multivalue attribute and an object has more than one value for hasPager, you cannot delete only one value of hasPager from the object.

-

I do not fully agree with the statements above! And….I’m going to explain WHY! Keep reading as I’m going to use examples.

-

I extend the AD schema with:

  • An attribute called "xTRAOccupation" and configured it on the user objectClass
  • An objectClass called "car" and configured it with attributes already existing in AD

-

See the schema definitions below…

image

Figure 1: AD Schema Extended With New Attribute Called "xTRAOccupation"

-

image

Figure 2: AD Schema Extended With New Object Class Called "car"

-

Querying AD for all instances of the "user" objectClass with any value in the new attribute definition, results in….

image

Figure 3: All Instances Of The "user" Object Class With Any Value In The New Attribute Called "xTRAOccupation" (8 Objects)

-

Querying AD for all instances of the "car" objectClass, results in….

image

Figure 4: All Instances Of The "car" Object Class (2 Objects)

-

Now lets start and deactivate the newly created "car" objectClass, by setting isDefunct to TRUE, refresh/reload the AD schema and then perform the same query as shown in figure 4.

Now, querying AD for all instances of the "car" objectClass, results in….

image

Figure 5: All Instances Of The "car" Object Class After Deactivation (0 Objects)

-

The objectClass has been deactivated, so you cannot query AD anymore for its name. The only way to query for those instances is to not use the the lDAPDisplayName, but rather use the OID defined in the "governsID" attribute (see figure 2). Combining ADFIND with ADMOD to delete the instances found will succeed. Of course you can use any other tool/script to perform the same actions

image

Figure 6: All Instances Of The "car" Object Class After Deactivation (Using The OID Instead Of The lDAPDisplayName) (2 Objects)

-

Now lets start and deactivate the newly created "xTRAOccupation" attribute, by setting isDefunct to TRUE, refresh/reload the AD schema and then perform the same query as shown in figure 3.

Now, querying AD for all instances of the "user" objectClass with any value in the "xTRAOccupation" attribute definition, results in….

image

Figure 7: All Instances Of The "user" Object Class With Any Value In The Deactivated "xTRAOccupation" Attribute (0 Objects)

-

Although the existing instances of the "user" objectClass have the data/values in the attribute "xTRAOccupation", you cannot query AD for that attribute because it has been unassociated from the "user" objectClass, which was a requirement to be able to deactivate the attribute.

-

So, the moral of the story here is:

  • Clean up your data of attributes to be deactivated BEFORE deactivating the corresponding attributes!!!
  • Clean up your data of objectClasses to be deactivated BEFORE deactivating the corresponding objectClasses by using the lDAPDisplayName of the objectClass in the query OR clean up your data of objectClasses to be deactivated AFTER deactivating the corresponding objectClasses by using the OID of the deactivated objectClass in the AD query!!!

-

image

Figure 8: Deleting All Instances Of The "car" Object Class Using The OID Of The Deactivated Object Class IN The AD Query

-

Querying for the deleted objects that have a deactivated objectClass….

image

Figure 9: Querying All Deleted Instances Of The "car" Object Class Using The OID Of The Deactivated Object Class In The AD Query

-

Now….lets say I want to reanimate the deleted object instances of the deactivated objectClass. Is that possible? No, because the objectClass is deactivated it cannot be reused again! See below!

image

Figure 10: Reanimating All Instances Of The "car" Object Class Using The OID Of The Deactivated Object Class IN The AD Query

-

After reactivating the objectClass called "car" by configuring the attribute isDefunct to <NOT SET>, refreshing/reloading the AD schema and then trying the undelete, results in…

image

Figure 11: Reanimating All Instances Of The "car" Object Class Using The lDAPDisplayName Of The Reactivated Object Class In The AD Query

-

NOTE: You could also have used the OID of the reactivated objectClass in the AD query!

-

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

Posted in Active Directory Domain Services (ADDS), Deactivate/Defunct, Schema | Leave a Comment »

(2014-04-04) Domain Join through an RODC instead of an RWDC (Update 1)

Posted by Jorge on 2014-04-04


In the blog post (2009-01-01) Domain Join through an RODC instead of an RWDC I explained the so called read-only domain join against an RODC. In that blog post you will find a VBS script that helps you achieve that goal. Prior to the VBS script you see multiple ways of pre-creating the computer and having the password of the computer account replicate to the RODC.

-

In this blog post I provide a PowerShell script (don’t forget the execution policy on the server!) that performs the read-only domain join. You can get the PowerShell script through this link, or you can copy it from below.

-

Param( [string]$fqdnADdomain, # The FQDN Of the AD domain [string]$fqdnRODC, # The FQDN of the RODC to use [string]$computerAccountPWD # The password for the computer account ) Clear-Host Write-Host "*******************************************************************" -ForeGroundColor Yellow Write-Host "* --> Performing Read-Only Domain Join Against RODC <-- *" -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 # Checking If All Parameters Are Available And Correct If (!($fqdnADdomain)) { Write-Host "" Write-Host "No FQDN Of An AD Domain Has Been Specified" -ForeGroundColor Red Write-Host "The FQDN Of An AD Domain Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } If (!($fqdnRODC)) { Write-Host "" Write-Host "No FQDN Of An RODC Has Been Specified" -ForeGroundColor Red Write-Host "The FQDN Of An RODC Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } If (!($computerAccountPWD)) { Write-Host "" Write-Host "No Computer Account Password Has Been Specified" -ForeGroundColor Red Write-Host "The Computer Account Password Specified During Pre-Creation Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } # Defining Required Constants Set-Variable JOIN_DOMAIN -option Constant -value 1 # Joins a computer to a domain. If this value is not specified, the join is a computer to a workgroup Set-Variable MACHINE_PASSWORD_PASSED -option Constant -value 128 # The machine, not the user, password passed. This option is only valid for unsecure joins Set-Variable NETSETUP_JOIN_READONLY -option Constant -value 2048 # Use an RODC to perform the domain join against # Cumulative Value To Use $readOnlyDomainJoinOption = $JOIN_DOMAIN + $MACHINE_PASSWORD_PASSED + $NETSETUP_JOIN_READONLY # Getting Info From The Local Computer $localComputerSystem = Get-WMIObject Win32_ComputerSystem $computerName = $localComputerSystem.Name # Present The Gathered Information Write-Host "" Write-Host "" Write-Host "Trying To Perform A Read-Only Domain Join Using The Following Information..." -ForeGroundColor Yellow Write-Host "" Write-Host "FQDN AD Domain............: "$fqdnADdomain -ForeGroundColor Yellow Write-Host "FQDN RODC.................: "$fqdnRODC -ForeGroundColor Yellow Write-Host "Computer Name.............: "$computerName -ForeGroundColor Yellow Write-Host "Computer Account Password.: "$computerAccountPWD -ForeGroundColor Yellow Write-Host "" # Performing The Read-Only Domain Join $errorCode = $localComputerSystem.JoinDomainOrWorkGroup($fqdnADdomain+"\"+$fqdnRODC,$computerAccountPWD,$null,$null,$readOnlyDomainJoinOption) # Error Handling # List of 'system error codes' (http://msdn.microsoft.com/en-us/library/ms681381.aspx) and # List of 'network management error codes' (http://msdn.microsoft.com/en-us/library/aa370674(VS.85).aspx) $errorDescription = switch ($($errorCode.ReturnValue)) { 0 {"SUCCESS: The Operation Completed Successfully."} 5 {"FAILURE: Access Is Denied."} 53 {"FAILURE: The Network Path Was Not Found."} 64 {"FAILURE: The Specified Network Name Is No Longer Available."} 87 {"FAILURE: The Parameter Is Incorrect."} 1326 {"FAILURE: Logon failure: Unknown Username Or Bad Password."} 1355 {"FAILURE: The Specified Domain Either Does Not Exist Or Could Not Be Contacted."} 2691 {"FAILURE: The Machine Is Already Joined To The Domain."} default {"FAILURE: Unknown Error!"} } If ($($errorCode.ReturnValue) -eq "0") { Write-Host "Domain Join Result Code...: "$($errorCode.ReturnValue) -ForeGroundColor Green Write-Host "Domain Join Result Text...: "$errorDescription -ForeGroundColor Green } Else { Write-Host "Domain Join Result Code...: "$($errorCode.ReturnValue) -ForeGroundColor Red Write-Host "Domain Join Result Text...: "$errorDescription -ForeGroundColor Red } # Finishing Up Write-Host "" Write-Host "REMARK:" -ForeGroundColor Cyan Write-Host "The Computer Account Password Will Be Reset Shortly After The Domain Join!" -ForeGroundColor Cyan Write-Host "" Write-Host "###### FINISHED ######" Write-Host "-----------------------------------------------------" If ($($errorCode.ReturnValue) -eq "0") { Write-Host "" Write-Host "!!! THE COMPUTER WILL REBOOT AUTOMATICALLY IN 2 MINUTES !!!" -ForeGroundColor Cyan Write-Host "" Write-Host "!!! TO STOP THE REBOOT USE THE COMMAND: SHUTDOWN /A !!!" -ForeGroundColor Cyan SHUTDOWN /R /T 120 }

-

Have fun!

-

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), Domain Join, PowerShell, Read-Only Domain Controller, Tooling/Scripting | Leave a Comment »

(2014-03-27) Determining Users Configured With "Trusted For Delegation"

Posted by Jorge on 2014-03-27


You may need to be able to query AD and find all users accounts that have been configure with any of the three following delegation options:

  1. Trust This User For Delegation To Any Service (Kerberos Only) – A.K.A. "Open Delegation"
  2. Trust This User For Delegation To Specified Services Only – Use Any Authenticaton Protocol – A.K.A. "Constrained Delegation"
  3. Trust This User For Delegation To Specified Services Only – Use Kerberos Only – A.K.A. "Constrained Delegation"

-

[AD.1] Querying ALL Users with "Trusted For Delegation To Any Service (Kerberos Only)"

"Trusted For Delegation To Any Service (Kerberos Only)" translates to the "TRUSTED_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "524288".

Import-Module ActiveDirectory Get-ADUser -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | %{$_.DistinguishedName}

-

[AD.2a] Querying ALL Users with "Trusted For Delegation To Specific Services – Any AuthN (At Least One Service Specified)"

"Trusted For Delegation To Specific Services – Any AuthN" translates to the "TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "16777216".

Import-Module ActiveDirectory Get-ADUser -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(userAccountControl:1.2.840.113556.1.4.803:=16777216)(msDS-AllowedToDelegateTo=*))" | %{$_.DistinguishedName}

-

[AD.2b] Querying ALL Users with "Trusted For Delegation To Specific Services – Any AuthN (No Service Specified, Empty List)"

"Trusted For Delegation To Specific Services – Any AuthN" translates to the "TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "16777216".

Import-Module ActiveDirectory Get-ADUser -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(userAccountControl:1.2.840.113556.1.4.803:=16777216)(!(msDS-AllowedToDelegateTo=*)))" | %{$_.DistinguishedName}

REMARK: Some systems/applications/appliances may use this scenario to for any protocol and still use open delegation. One example is a Riverbed Steelhead Appliance which is able to optimize network traffic for different protocols. For the WHY I refer to the documentation of the systems/applications/appliances.

-

[AD.3a] Querying ALL Users with "Trusted For Delegation To Specific Services – Kerberos AuthN (At Least One Service Specified)"

"Trusted For Delegation To Specific Services – Kerberos AuthN" DOES NOT translates to any bit on the userAccountControl attribute.

Import-Module ActiveDirectory Get-ADUser -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(!(|(userAccountControl:1.2.840.113556.1.4.803:=524288)(userAccountControl:1.2.840.113556.1.4.803:=16777216)))(msDS-AllowedToDelegateTo=*))" | %{$_.DistinguishedName}

-

[AD.3b] Querying ALL Users with "Trusted For Delegation To Specific Services – Kerberos AuthN (No Service Specified, Empty List)"

It is not possible to query this as "Trusted For Delegation To Specific Services" expects a list of at least one service for which delegation is allowed and in this case it does not translate to any bit on the userAccountControl attribute. Because of that it would return any computer account which basically is a false result!

-

REMARK: I used PowerShell here, but of course you can use the same LDAP filter with any other LDAP Querying tool such as ADFIND. Remember that you may need to amend the LDAP filter to target the correct object type!

-

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), Delegation, Kerberos AuthN, NTLM AuthN | Leave a Comment »

(2014-03-26) Determining Computers Configured With "Trusted For Delegation"

Posted by Jorge on 2014-03-26


You may need to be able to query AD and find all computer accounts that have been configure with any of the three following delegation options:

  1. Trust This User For Delegation To Any Service (Kerberos Only) – A.K.A. "Open Delegation"
  2. Trust This User For Delegation To Specified Services Only – Use Any Authenticaton Protocol – A.K.A. "Constrained Delegation"
  3. Trust This User For Delegation To Specified Services Only – Use Kerberos Only – A.K.A. "Constrained Delegation"

-

[AD.1] Querying ALL Computers with "Trusted For Delegation To Any Service (Kerberos Only)"

"Trusted For Delegation To Any Service (Kerberos Only)" translates to the "TRUSTED_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "524288".

Import-Module ActiveDirectory Get-ADComputer -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(userAccountControl:1.2.840.113556.1.4.803:=524288)" | %{$_.DistinguishedName}

-

[AD.2a] Querying ALL Computers with "Trusted For Delegation To Specific Services – Any AuthN (At Least One Service Specified)"

"Trusted For Delegation To Specific Services – Any AuthN" translates to the "TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "16777216".

Import-Module ActiveDirectory Get-ADComputer -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(userAccountControl:1.2.840.113556.1.4.803:=16777216)(msDS-AllowedToDelegateTo=*))" | %{$_.DistinguishedName}

-

[AD.2b] Querying ALL Computers with "Trusted For Delegation To Specific Services – Any AuthN (No Service Specified, Empty List)"

"Trusted For Delegation To Specific Services – Any AuthN" translates to the "TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION" bit on the userAccountControl attribute, which in its turn translates to decimal value "16777216".

Import-Module ActiveDirectory Get-ADComputer -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(userAccountControl:1.2.840.113556.1.4.803:=16777216)(!(msDS-AllowedToDelegateTo=*)))" | %{$_.DistinguishedName}

REMARK: Some systems/applications/appliances may use this scenario to for any protocol and still use open delegation. One example is a Riverbed Steelhead Appliance which is able to optimize network traffic for different protocols. For the WHY I refer to the documentation of the systems/applications/appliances.

-

[AD.3a] Querying ALL Computers with "Trusted For Delegation To Specific Services – Kerberos AuthN (At Least One Service Specified)"

"Trusted For Delegation To Specific Services – Kerberos AuthN" DOES NOT translates to any bit on the userAccountControl attribute.

Import-Module ActiveDirectory Get-ADComputer -Server "<FQDN of DC>" -SearchBase "<DN of Domain NC>" -LdapFilter "(&(!(|(userAccountControl:1.2.840.113556.1.4.803:=524288)(userAccountControl:1.2.840.113556.1.4.803:=16777216)))(msDS-AllowedToDelegateTo=*))" | %{$_.DistinguishedName}

-

[AD.3b] Querying ALL Computers with "Trusted For Delegation To Specific Services – Kerberos AuthN (No Service Specified, Empty List)"

It is not possible to query this as "Trusted For Delegation To Specific Services" expects a list of at least one service for which delegation is allowed and in this case it does not translate to any bit on the userAccountControl attribute. Because of that it would return any computer account which basically is a false result!

-

REMARK: I used PowerShell here, but of course you can use the same LDAP filter with any other LDAP Querying tool such as ADFIND. Remember that you may need to amend the LDAP filter to target the correct object type!

-

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), Kerberos AuthN, NTLM AuthN | Leave a Comment »

(2014-03-25) An Account With "Trusted For Delegation" – What Are The Risks?

Posted by Jorge on 2014-03-25


Sometimes when implementing some system/application/appliance that needs a service account, that service account may need to be configured with "Trusted For Delegation". The latter has three flavors as shown in figure 1 below.

Figure 1: The Delegation TAB After Configuring A Service Principal Name

-

As you can see in figure 1, you have 4 options you can configure, being:

  1. Trust This User For Delegation To Any Service (Kerberos Only) – A.K.A. "Open Delegation"
  2. Trust This User For Delegation To Specified Services Only – Use Kerberos Only – A.K.A. "Constrained Delegation"
  3. Trust This User For Delegation To Specified Services Only – Use Any Authenticaton Protocol – A.K.A. "Constrained Delegation"
  4. Do Not Trusted This User For Delegation – (this is obvious, isn’t it?!)

-

Now, you may wonder: "what are the risks?". Keep reading! Smile

-

An AD user account or computer account with such powers is worthless on its own. You need to have a system/application/appliance using such an account that is being targeted by end users and that is providing some kind of service.

With regards to the system/application/appliance that has that account configured you are fully trusting the code of the system/application/appliance to act on a user’s behalf for any OR specific services it is performing delegation for. With that in mind you should also think about how likely is it for the system/application/appliance to be "misused" during an attack? As risk mitigations you can think of measures such as physical access controls (secure rooms) and network access controls (firewalls) access controls, but also having the latest patches/hotfixes applied that are recommended by the vendor.
It also comes down to the question if you trust the administrators and/or vendor of the system/application/appliance to not misuse those high privileges. Having trusted administrators with the correct delegation of control supported (pro-active measure) with the correct auditing measures (re-active measure) helps to "secure" the system/application/appliance in a proactive and re-active way.

In addition, if even possible AND after testing, you may be able to configure the following user rights for the service account on different Windows systems to mitigate risks:
(
User Rights)

  • Deny Access To This Computer From The Network
  • Deny Log On As A Batch Job
  • Deny Log On As A Service
  • Deny Log On Locally
  • Deny Log On Through Terminal Services

-

Again in addition, you can configure the account with a very secure password and if needed/possible use the (at least) four-eyes principle to make sure nobody ever knows the complete password but just part of it.

-

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), Delegation, Kerberos AuthN, NTLM AuthN | 1 Comment »

(2014-02-25) Updates For Exchange 2007, 2010 And 2013 Released To Support W2K12R2 OS, AD, DFL/FFL

Posted by Jorge on 2014-02-25


Today Microsoft released updates for Exchange 2007, 2010 and 2013 to be supported:

  • On a W2K12R2 server
  • In an AD with W2K12R2 DCs
  • In an AD where DFL/FFL is W2K12R2

-

Exchange 2007

  • Technical details can be read here.
  • SP3 RU13 and later provides that support. Get RU13 from here.

Exchange 2010

  • Technical details can be read here.
  • SP3 RU5 and later provides that support. Get RU5 from here.

Exchange 2013

  • Technical details can be read here.
  • SP1 and later provides that support. Get SP1 from here.

-

The supportability matrix is available here.

-

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), Exchange Server, Windows Server | Leave a Comment »

(2014-02-17) Testing SYSVOL Replication Latency/Convergence Through PowerShell (Update 3)

Posted by Jorge on 2014-02-17


I have updated the PowerShell script for testing/determining SYSVOL Replication Latency/Convergence.

Detailed information about the script can be read here.

-

This script requires PowerShell v2.0 or higher

The script supports: W2K3(R2) DCs, W2K8(R2) DCs and W2K12(R2) DCs

The script supports: NTFRS or DFS-R Replication for the SYSVOL

-

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

-

!!! 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 !!!:

-

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

-

# Abstract: This PoSH Script Checks The SYSVOL Replication Latency/Convergence # Written By: Jorge de Almeida Pinto [MVP-DS] # Blog: http://jorgequestforknowledge.wordpress.com/ # # 2013-03-02: (v0.1): Initial version of the script # 2014-02-01: (v0.2): Updated to also work on W2K3, added STOP option, added few extra columns to output extra info of DCs, better detection of unavailable DCs, and screen adjustment section added # 2014-02-09: (v0.3): Solved a bug with regards to the detection/location of RWDCs and RODCs # 2014-02-11: (v0.4): Added additional logic to determine if a DC is either an RWDC or RODC when it fails using the first logic and changed the layout a little bit # # REQUIRES: PowerShell v2.0 or higher # REQUIRES: At least 2 RWDCs # SUPPORTS: W2K3(R2), W2K8(R2), W2K12(R2) DCs and most likely higher # SUPPORTS: NTFRS or DFS-R Replication for the SYSVOL # # -----> !!! DISCLAIMER/REMARKS !!! <------ # * The script is freeware, you are free to distribute it, but always refer to this website (http://jorgequestforknowledge.wordpress.com/) 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 any 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 !!! <------ # Clear The Screen Clear-Host # Configure The Appropriate Screen And Buffer Size To Make Sure Everything Fits Nicely $uiConfig = (Get-Host).UI.RawUI $uiConfig.WindowTitle = "+++ CHECKING SYSVOL REPLICATION LATENCY/CONVERGENCE +++" $uiConfig.ForegroundColor = "Yellow" $uiConfigBufferSize = $uiConfig.BufferSize $uiConfigBufferSize.Width = 150 $uiConfigBufferSize.Height = 9999 $uiConfigScreenSizeMax = $uiConfig.MaxPhysicalWindowSize $uiConfigScreenSizeMaxWidth = $uiConfigScreenSizeMax.Width $uiConfigScreenSizeMaxHeight = $uiConfigScreenSizeMax.Height $uiConfigScreenSize = $uiConfig.WindowSize If ($uiConfigScreenSizeMaxWidth -lt 150) { $uiConfigScreenSize.Width = $uiConfigScreenSizeMaxWidth } Else { $uiConfigScreenSize.Width = 150 } If ($uiConfigScreenSizeMaxHeight -lt 75) { $uiConfigScreenSize.Height = $uiConfigScreenSizeMaxHeight - 5 } Else { $uiConfigScreenSize.Height = 75 } $uiConfig.BufferSize = $uiConfigBufferSize $uiConfig.WindowSize = $uiConfigScreenSize # Start... Write-Host " *******************************************************" -ForeGroundColor Magenta Write-Host " * *" -ForeGroundColor Magenta Write-Host " * --> Test SYSVOL Replication Latency/Convergence <-- *" -ForeGroundColor Magenta Write-Host " * *" -ForeGroundColor Magenta Write-Host " * Written By: Jorge de Almeida Pinto [MVP-DS] *" -ForeGroundColor Magenta Write-Host " * (http://jorgequestforknowledge.wordpress.com/) *" -ForeGroundColor Magenta Write-Host " * *" -ForeGroundColor Magenta Write-Host " *******************************************************" -ForeGroundColor Magenta ########## # 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() $ErrorActionPreference = "SilentlyContinue" $tcpPortSocket.EndConnect($portConnect) | Out-Null If (!$?) { #Write-Host $error[0] Return "ERROR" } Else { Return "SUCCESS" } $tcpPortSocket.Close() $ErrorActionPreference = "Continue" } } ########## # 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 Directory Servers In AD Forest $ThisADForest = [DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() $configNCDN = $ThisADForest.schema.Name.Substring(("CN=Schema,").Length) $searchRootNTDSdsa = [ADSI]"LDAP://CN=Sites,$configNCDN" $searcherNTDSdsaRW = New-Object System.DirectoryServices.DirectorySearcher($searchRootNTDSdsa) $searcherNTDSdsaRO = New-Object System.DirectoryServices.DirectorySearcher($searchRootNTDSdsa) $searcherNTDSdsaRW.Filter = "(objectCategory=NTDSDSA)" $searcherNTDSdsaRO.Filter = "(objectCategory=NTDSDSARO)" $objNTDSdsaRW = $searcherNTDSdsaRW.FindAll() $objNTDSdsaRO = $searcherNTDSdsaRO.FindAll() $TableOfRWDCsInADForest = @() $objNTDSdsaRW | %{ $ntdsDN = $_.Properties.distinguishedname $nbtRWDCName = $ntdsDN[0].Substring(("CN=NTDS Settings,CN=").Length) $nbtRWDCName = $nbtRWDCName.Substring(0,$nbtRWDCName.IndexOf(",")) $nbtRWDCSite = $ntdsDN[0].Substring(("CN=NTDS Settings,CN=$nbtRWDCName,CN=Servers,CN=").Length) $nbtRWDCSite = $nbtRWDCSite.Substring(0,$nbtRWDCSite.IndexOf(",")) $TableOfRWDCsInADForestObj = "" | Select "DS Name","Site Name" $TableOfRWDCsInADForestObj."DS Name" = $nbtRWDCName $TableOfRWDCsInADForestObj."Site Name" = $nbtRWDCSite $TableOfRWDCsInADForest += $TableOfRWDCsInADForestObj } $TableOfRODCsInADForest = @() $objNTDSdsaRO | %{ $ntdsDN = $_.Properties.distinguishedname $nbtRODCName = $ntdsDN[0].Substring(("CN=NTDS Settings,CN=").Length) $nbtRODCName = $nbtRODCName.Substring(0,$nbtRODCName.IndexOf(",")) $nbtRODCSite = $ntdsDN[0].Substring(("CN=NTDS Settings,CN=$nbtRODCName,CN=Servers,CN=").Length) $nbtRODCSite = $nbtRODCSite.Substring(0,$nbtRODCSite.IndexOf(",")) $TableOfRODCsInADForestObj = "" | Select "DS Name","Site Name" $TableOfRODCsInADForestObj."DS Name" = $nbtRODCName $TableOfRODCsInADForestObj."Site Name" = $nbtRODCSite $TableOfRODCsInADForest += $TableOfRODCsInADForestObj } $TableOfDCsInADForest = $TableOfRWDCsInADForest + $TableOfRODCsInADForest ########## # 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) $ListOfRWDCsInADDomain = $ListOfDCsInADDomain | ?{$_.InboundConnections -ne $null -and !($_.InboundConnections -match "RODC Connection")} $ListOfRODCsInADDomain = $ListOfDCsInADDomain | ?{$_.InboundConnections -match "RODC Connection"} $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,"Site Name","DS Type","IP Address","OS Version" $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 } If ( $DC.SiteName -ne $null -And $DC.SiteName -ne "") { $TableOfDCsInADDomainObj."Site Name" = $DC.SiteName } Else { If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 1) { $TableOfDCsInADDomainObj."Site Name" = ($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))})."Site Name" } If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 0) { $TableOfDCsInADDomainObj."Site Name" = "<Fail>" } If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -gt 1) { $TableOfDCsInADDomainObj."Site Name" = "<Fail>" } } $DStype = $null If ($DStype -eq $null) { ForEach ($RWDC In $ListOfRWDCsInADDomain) { If ($RWDC.Name -like $DC.Name) { $DStype = "Read/Write" BREAK } } } If ($DStype -eq $null) { ForEach ($RODC In $ListOfRODCsInADDomain) { If ($RODC.Name -like $DC.Name) { $DStype = "Read-Only" BREAK } } } If ($DStype -eq $null) { $DStype = "<Unknown>" If (($TableOfRWDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 1) { $DStype = "Read/Write" } If (($TableOfRODCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 1) { $DStype = "Read-Only" } } $TableOfDCsInADDomainObj."DS Type" = $DStype If ($DC.IPAddress -ne $null -And $DC.IPAddress -ne "") { $TableOfDCsInADDomainObj."IP Address" = $DC.IPAddress } Else { $TableOfDCsInADDomainObj."IP Address" = "<Fail>" } If ($DC.OSVersion -ne $null -And $DC.OSVersion -ne "") { $TableOfDCsInADDomainObj."OS Version" = $DC.OSVersion } Else { $TableOfDCsInADDomainObj."OS Version" = "<Fail>" } $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 "[*] Specify 'STOP' To End The Script" -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 = $ListOfRWDCsInADDomain $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" -And $SourceRWDCInADDomain -ne "STOP") { $contextRWDCToWriteTo = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$SourceRWDCInADDomain) $SourceRWDCInADDomainObject = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($contextRWDCToWriteTo) $SourceRWDCInADDomainFQDN = $SourceRWDCInADDomainObject.Name $SourceRWDCInADDomainSITE = $SourceRWDCInADDomainObject.SiteName } # If STOP Was Specified Then End The Script If ($SourceRWDCInADDomain -eq "STOP") { Write-Host "" Write-Host "'STOP' Was Specified..." -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" EXIT } # 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 # The following appears NOT to work on W2K3, but it does upper-level OSes # $dcObjectPath = $Searcher.FindAll().Path # The following appears to work on all OSes $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 -ne $null) { Write-Host "SYSVOL Replication Mechanism Being Used...: NTFRS" # Get The Local Root Path For The SYSVOL # The following appears NOT to work on W2K3, but it does upper-level OSes # $sysvolRootPathOnSourcingRWDC = $ntfrsSubscriptionObject.Properties.frsrootpath # The following appears to work on all OSes $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 -ne $null) { Write-Host "SYSVOL Replication Mechanism Being Used...: DFS-R" -ForeGroundColor Yellow Write-Host "" # Get The Local Root Path For The SYSVOL # The following appears NOT to work on W2K3, but it does not upper-level OSes. NOT really needed, because W2K3 does not support DFS-R for SYSVOL! # $sysvolRootPathOnSourcingRWDC = $dfsrSubscriptionObject.Properties."msdfsr-rootpath" # The following appears to work on all OSes $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,"Site Name",Reachable $TableOfDSServersAObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersAObj."Site Name" = $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,"Site Name",Time $TableOfDSServersBObj.Name = ("$SourceRWDCInADDomainFQDN [SOURCE RWDC]").ToUpper() $TableOfDSServersBObj."Site Name" = $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,"Site Name",Reachable $TableOfDSServersAObj.Name = $DC.Name If ($DC.SiteName -ne $null -And $DC.SiteName -ne "") { $TableOfDSServersAObj."Site Name" = $DC.SiteName } Else { If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 1) { $TableOfDSServersAObj."Site Name" = ($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))})."Site Name" } If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -eq 0) { $TableOfDSServersAObj."Site Name" = "<Fail>" } If (($TableOfDCsInADForest | ?{$_."DS Name" -eq $($($DC.Name).Substring(0,$($DC.Name).IndexOf(".")))} | Measure-Object).Count -gt 1) { $TableOfDSServersAObj."Site Name" = "<Fail>" } } $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 At Least 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 Successful 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,"Site Name",Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj."Site Name" = $DSsrv."Site Name" $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 Unsuccessful 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,"Site Name",Time $TableOfDSServersBobj.Name = $DSsrv.Name $TableOfDSServersBObj."Site Name" = $DSsrv."Site Name" $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, EXCEPT ONE:

In this case ALL directory servers, except for one RODC, are reachable without any problems! That RODC basically only exists as a pre-created RODC account

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

image_thumb13

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

-

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

image_thumb16

Figure 2: Showing The Replication Mechanism Used For The SYSVOL, Incl. A List Of Directory Servers In The AD Domain

-

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.

image_thumb19

Figure 3: 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

-

Also check out this blog post to find the script version to check latency/convergence of AD

-

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 »

 
%d bloggers like this: