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

(2014-07-24) Possible Issues When Running Both W2K3 And W2K12R2 DCs For The Same AD Domain

Posted by Jorge on 2014-07-24


If you are upgrading your AD from W2K3 to W2K12R2, you might be experiencing issues when running both OS versions at the same time. The guys at ASKDS have written a great blog post about this.

Click on the following link to read all about it and what you can do about it.

It turns out that weird things can happen when you mix Windows Server 2003 and Windows Server 2012 R2 domain controllers

-

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

(2014-07-19) Understanding Claim Rule Language In AD FS 2.0 And Higher

Posted by Jorge on 2014-07-19


In addition to previous blog posts that I had found regarding the claims rule language, I found yet another great article about the same topic. Kudos and credits of course go to the writer/contributors of the wiki post/article.

-

SOURCE: Understanding Claim Rule Language in AD FS 2.0 & Higher

-

<QUOTE SOURCE=”Understanding Claim Rule Language in AD FS 2.0 & Higher”>

Introduction

Claims Rules follow a basic pipeline.  The rules define which claims are accepted, processed, and eventually sent to the relying party.  Claim Rules are defined as a property of the Claims Provider Trust (incoming claims) and the Relying Party Trust (outgoing claims).  Basic claim passing and transformations can be handled using the built in Claim Rule Templates.

-

Understanding Claim Sets

It is important to understand claim sets as part of the claims pipeline.  When claims come in, they are a part of the incoming claim set.  After claims are processed by claim rules, they become part of the outgoing claim set.  An important piece to understand is there is an incoming and outgoing claim set for the Claims Provider Trust and for the Relying Party Trust, so there are two places claims can be processed before leaving AD FS.

  1. Claims come into the Claims Provider Trust as the incoming claim set
  2. Claims are processed using claim rules and become part of the outgoing claim set
  3. The outgoing claim set is passed to the Relying Party Trust as the incoming claim set for the Relying Party Trust
  4. The set of claims are processed using claim rules and become part of the final outgoing claim set

Read more about the claims pipeline here

-

General Syntax of the Claim Rule Language

There are two parts to each rule:

  • Condition statement
  • Issuance statement

-

If the condition statement is true, the issuance statement will be executed.  If the condition statement is false, the engine will move on to the next rule.

Example: Simple Claims Rule Syntax

c:[Type == "http://contoso.com/department"%5D
=>issue(Type = “http://adatum.com/department”, Value = c.Value);

This example takes an incoming claim http://contoso.com/department and issues a new claim http://adatum.com/department with the same value as the incoming claim.  These claim types are URIs in the HTTP format but can also be in the URN format. URIs are not URLs and do not need to be actual pages on the Internet or intranet.

-

Condition Statements

Condition statements look at all incoming claims and determine if there is one that matches the condition. The following properties can be queried in an incoming claim:

  • Type
  • Value
  • Issuer
  • OriginalIssuer
  • ValueType

-

The format for querying an incoming claim is c:[query] where the variable c represents a claim in the incoming claim set.  The query can be more specific and check for more than one property.  See some of the examples below to get an idea of how the format works.  The two examples below are not complete syntax, as they are missing the issuance statement.

Example: Check for an incoming claim type http://contoso.com/department

c:[type == http://contoso.com/department]

-

Example: Check for an incoming claim type http://contoso.com/department with a value of sales

c:[type == "http://contoso.com/department&quot;, value == "sales"]

-

Condition statements are optional in the claims rule language.  By leaving the condition statement blank, the claim rule will always evaluate as true.

Example: Issue a claim http://contoso.com/partner with the value of adatum to all incoming claim sets

=>issue(Type = “http://contoso.com/partner”, Value = "adatum");

-

Issuance Statements

There are two types of issuance statements to use.

  • Add – adds the claim to the incoming claim set
  • Issue – adds the claim to the outgoing claim set

-

The ADD issuance statement is used to add additional claims to the incoming claim set so that subsequent claim rules can use them for processing. The ISSUE issuance statement is used to add claims to the outgoing claim.

Example: Issue a claim http://contoso.com/department to the outgoing claim set

=> issue(type = "http://contoso.com/department&quot;, value = "marketing");

-

Example: Add a claim http://contoso.com/partner to the incoming claim set

=> add(type = "http://contoso.com/partner&quot;, value = "adatum");

-

Example: Check for an incoming claim type http://contoso.com/email and if found, issue a claim http://contoso.com/role with the value of Exchange User

c:[type == "http://contoso.com/emailaddress"%5D
=> issue(type = "http://contoso.com/role&quot;, value = "Exchange User");

-

The entire incoming claim can be passed on or certain values inside the claim can be used in the outgoing claim. Use the variable c in the issuance statement to pass the entire claim or parts of the claim.

Example: Check for an incoming claim type http://contoso.com/role and if found, issue the exact same claim to the outgoing claim set

c:[type == "http://contoso.com/role"%5D
=> issue(claim = c);

-

Example: Check for an incoming claim type http://contoso.com/role and if found, issue a claim http://adatum.com/role with the same value of the incoming claim

c:[type == "http://contoso.com/role"%5D
=> issue(type = "http://adatum.com/role&quot;, value = c.Value);

-

Multiple Conditions

Another possibility is to have multiple conditions, and if all conditions evaluate to true, run the issuance statement.  Each condition is joined using the && special operator.  There is not a logical OR operator.  To accomplish an OR, create separate claim rules.

Example: Check for an incoming claim type http://contoso.com/role with a value of Editor and separate incoming claim type  http://contoso.com/role with a value of Manager. If both are found, issue a claim http://contoso.com/role with the value of Managing Editor

c1:[type == "http://contoso.com/role&quot;, value=="Editor"] &&
c2:[type == "http://contoso.com/role&quot;, value=="Manager"]
=> issue(type = "http://contoso.com/role&quot;, value = "Managing Editor");

-

Combining Values

The values of each individual incoming claim can be accessed and joined using the special operator + in the issuance statement.

Example: Check for an incoming claim type http://contoso.com/location and separate incoming claim type http://contoso.com/role. If both are found, issue a claim http://contoso.com/targetedrole combining the values of the incoming roles

c1:[type == "http://contoso.com/location"%5D &&
c2:[type == "http://contoso.com/role"%5D
=> issue(type = "http://contoso/targetedrole&quot;, value = c1.Value + " " c2.Value);

-

Example Incoming Claims:

"http://contoso.com/location&quot; is “Seattle”

“http://contoso.com/role” is “Editor”

-

Example Outgoing Claim:

“http://contoso.com/targetedrole” is “Seattle Editor”

-

Aggregate Functions

Typical claims rules will issue an output claim for each match it finds.  Aggregate functions will issue or add a single claim regardless of the number of matches. The EXISTS function serves this purpose.

-
EXISTS

Example: Claims rule without an Aggregate Function

c:[type == "http://contoso.com/emailaddress"%5D
=> issue(type = "http://contoso.com/role&quot;, value = "Exchange User");

-

This example would issue multiple http://contoso.com/role claims if the incoming claim set had multiple email addresses.  If that is not desired, use the EXISTS function as shown below.

Example: Check for any incoming claims with the type http://contoso.com/emailaddress and if any are found, issue a single claim type http://contoso.com/role with the value of Exchange User:

EXISTS([type == "http://contoso.com/emailaddress"%5D)
=> issue(type = "http://contoso/role&quot;, value = "Exchange User");

-

NOT EXISTS

There is also an option to use NOT EXISTS to issue claims if there is no incoming claim that matches the condition. This can be useful for subsequent rules that combine values.

Example: Claim rule that will only work if the incoming claim set has both claims

c1:[type == "http://contoso.com/location"%5D &&
c2:[type == "http://contoso.com/role"%5D
=> issue(type = "http://contoso/targetedrole&quot;, value = c1.Value + " " c2.Value);

-

This claim rule will only trigger if the incoming claim set has a http://contoso.com/location and a http://contoso.com/role incoming claim. If the location claim is not present, the outgoing set will not contain a http://contoso.com/targetedrole claim. If it is desired that all outgoing claim sets have this particular claim, the NOT EXISTS function can be used in a separate claim rule.

Example: Claim rule that uses the NOT EXISTS Aggregate Function

NOT EXISTS([type == "http://contoso.com/location"%5D)
=> add(type = "http://contoso/location&quot;, value = "Unknown");

-

Here is an example set of incoming and outgoing claims if the NOT EXISTS Aggregate function claim rule is included with the multiple condition claim rule.

Example Incoming Claims:

“http://contoso.com/role” is “Editor”

-

Example Outgoing Claim:

“http://contoso.com/targetedrole” is “Unknown Editor”

-

Another good use for the NOT EXISTS aggregate function is to restrict access to certain applications based on group membership.

Example: Issuance Authorization claim rule that uses the NOT EXISTS Aggregate Function

NOT EXISTS([type == "http://contoso.com/group&quot;,  Value =~ "^(?i)ADFSUser"])
=> issue(type = "http://schemas.microosft.com/authorization/claims/deny&quot;, value = "DenyUsersWithClaim");

This claim rule will deny users access to the relying party if they are not a member of a group that starts with ADFSUser.  It group name evaluation is not case sensitive. The syntax uses Regular Expressions (regex) which is explained in more detail in the next section.

-

COUNT

Another aggregate function available in AD FS 2.0 is the COUNT function.  The claim will only be issued if the condition statement is true.

Example: Claim Rule that uses the COUNT Aggregate Function

COUNT([type == http://contoso.com/proxyAddresses"%5D) >= 2
=> issue(type = "http://contoso.com/MultipleEmails&quot;, value = "True");

This claim rule will issue the claim if the user has two or more proxy address claims.

-

Using Regular Expressions

Regular Expressions (regex) can be used in the condition or issuance statements.  In a condition statement, regex allows similar matches to evaluate true.  In issuance statements, regex allows parts of the string values to be used in the outgoing claim. Regular Expressions use special characters to perform various tasks inside a string.

Character Description Examples
$ Matches the end of a string contoso.com$ matches a string that ends with "contoso.com"
bob@contoso.com would evaluate true
bob@contoso2.com would evaluate false
^ Matches the beginning of a string ^bob matches a string that starts with "bob"
bob.smith@contoso.com would evaluate true
bonny.smith@contoso.com would evaluate false

-

Example: Using the $ expression.  Matches strings that end in "contoso.com"

c:[type == "http://contoso.com/email&quot;, Value =~ "contoso.com$"]
=> issue (claim = c);

-

Example: Using the ^ expression.  Matches strings that start with "bob"

c:[type == "http://contoso.com/email&quot;, Value =~ "^bob"]
=> issue (claim = c);

-

Example: Matches strings that contain "bob"

c:[type == "http://contoso.com/email&quot;, Value =~ "bob"]
=> issue (claim = c);

-

The string matching in the above examples are case-sensitive.  To perform a string match that ignores case, use a pattern (?i) in front of the string.

Example: Matches strings that contain "bob" regardless of case

c:[type == "http://contoso.com/email&quot;, Value =~ "(?i)bob"]
=> issue (claim = c);

-

For more advanced RegEx examples, view this article: http://social.technet.microsoft.com/wiki/contents/articles/16161.ad-fs-2-0-using-regex-in-the-claims-rule-language.aspx

-

Querying Attribute Stores

Active Directory is the default store created when AD FS 2.0 is installed.  SQL attribute stores and LDAP attribute stores can also be defined.  The condition statement remains the same, but the issuance statement changes depending on which attribute store is used.

-

SQL Attribute Stores

If user data is located in a SQL database, the Claim Rule Language can query the database and generate claims based on the information in the database.

Example: Claim rule using a SQL Attribute Store

c:[type == "http://contoso.com/emailaddress"%5D
=> issue (store = "Custom SQL Store", types = ("http://contoso.com/age&quot;, "http://contoso.com/purchasinglimit&quot;), query = "SELECT age,purchasinglimit FROM users WHERE email={0}",param = c.value);

This rule looks for an incoming http://contoso.com/emailaddress claim, then queries the SQL store Custom SQL Store for the age and purchasing limit associated with the value of the claim (email address).  It then issues two claims, http://contoso.com/age and http://contoso.com/purchasinglimit with the values stored in the SQL database.
As the example shows, multiple claims can be issued from a single rule.  The query is a standard transact-SQL statement.  The {0} variable is associated with the first param value.  If there are multiple param values, they will be associated in order {0}, {1}, {2}, etc.

-

LDAP Attribute Stores
If user data is located in a LDAP store, the Claim Rule Language can query it and generate claims based on the information in the store.
Example: Claim rule using an LDAP Attribute Store

c:[type == "http://contoso.com/emailaddress"%5D
=> issue (store = "Custom LDAP Store", types = ("http://contoso.com/age&quot;, "http://contoso.com/purchasinglimit&quot;), query = "mail={0};age,purchasinglimit", param = c.value);

The example shown is similar to the SQL attribute example.  The difference is the query parameter.

Format of an LDAP query in a claim rule: QUERY = "<query_filter>;<attributes>"

Read more about Attribute Stores here.

-

Links to Additional Content

There are many good articles that supplement the data in this article.

-

Advanced Topics

Here are some articles that go over more advanced topics:

</QUOTE SOURCE=”Understanding Claim Rule Language in AD FS 2.0 & Higher”>

-

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 Federation Services (ADFS), Claims Rule Language | Leave a Comment »

(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 »

 
%d bloggers like this: