Export (0) Print
Expand All

AD CS: PKISync.ps1 Script for Cross-forest Certificate Enrollment

Updated: August 31, 2010

Applies To: Windows Server 2008 R2, Windows Server 2008 R2 with SP1

PKISync.ps1 copies objects in the source forest to the target forest.

In cross-forest AD CS deployments, use PKISync.ps1 during initial deployment and to keep resource and account forest PKI objects synchronized.

  1. Click Copy Code at the top of the code section.

  2. Start Notepad.

  3. On the Edit menu, click Paste.

  4. On the File menu, click Save.

  5. Type a path for the file, type the file name PKISync.ps1, and click Save.

#
# This script allows updating PKI objects in Active Directory for the 
# cross-forest certificate enrollment
#
#This sample script is not supported under any Microsoft standard support 
#program or service. This sample script is provided AS IS without warranty of 
#any kind. Microsoft further disclaims all implied warranties including,
#without limitation, any implied warranties of merchantability or of fitness
#for a particular purpose. The entire risk arising out of the use or 
#performance of the sample scripts and documentation remains with you. In no 
#event shall Microsoft, its authors, or anyone else involved in the creation,
#production, or delivery of the scripts be liable for any damages whatsoever 
# (including, without limitation, damages for loss of business profits, business
#interruption, loss of business information, or other pecuniary loss) arising 
#out of the use of or inability to use this sample script or documentation, 
#even if Microsoft has been advised of the possibility of such damages.
#

# Command line variables
# 
$SourceForestName = ""
$TargetForestName = ""
$SourceDC = ""
$TargetDC = ""

$ObjectType = "all"
$ObjectCN = $null

$DryRun = $FALSE
$DeleteOnly = $FALSE
$OverWrite = $FALSE

function ParseCommandLine()
{
    if (2 -gt $Script:args.Count)
    {
        write-warning "Not enough arguments"
        Usage 
        exit 87
    }
    
    for($i = 0; $i -lt $Script:args.Count; $i++)
    {
        switch($Script:args[$i].ToLower())
        {
            -sourceforest 
            {
                $i++
                $Script:SourceForestName = $Script:args[$i]
            }
            -targetforest 
            {
                $i++
                $Script:TargetForestName = $Script:args[$i]
            }                 
            -cn 
            {
                $i++
                $Script:ObjectCN = $Script:args[$i]
            }
            -type 
            {
                $i++
                $Script:ObjectType = $Script:args[$i].ToLower()
            }
            -f 
            {
                $Script:OverWrite = $TRUE
            }
            -whatif
            {
                $Script:DryRun = $TRUE
            }
            -deleteOnly
            {
                $Script:DeleteOnly = $TRUE
            }
            -targetdc
            {
                $i++
                $Script:TargetDC = $Script:args[$i]
            }
            -sourcedc
            {
                $i++
                $Script:SourceDC = $Script:args[$i]
            }
            default 
            {
                write-warning ("Unknown parameter: " + $Script:args[$i])
                Usage
                exit 87
            }
        }
    }
}

function Usage()
{
    write-host ""
    write-host "Script to copy or delete PKI objects (default is copy)"
    write-host ""
    write-host "  Copy Command:"
    write-host ""
    write-host "  .\PKISync.ps1 -sourceforest <SourceForestDNS> -targetforest <TargetForestDNS> [-sourceDC <SourceDCDNS>] [-targetDC <TargetDCDNS>] [-type <CA|Template|OID> [-cn <ObjectCN>]] [-f] [-whatif]"
    write-host ""
    write-host "  Delete Command:"
    write-host ""
    write-host "  .\PKISync.ps1 -targetforest <TargetForestDNS> [-targetDC <TargetDCDNS>] [-type <CA|Template|OID> [-cn <ObjectCN>]] [-deleteOnly] [-whatif]"
    write-host ""
    write-host "-sourceforest           -- DNS of the forest to process object from"
    write-host "-targetforest           -- DNS of the forest to process object to"
    write-host "-sourcedc               -- DNS of the DC in the source forest to process object from"
    write-host "-targetdc               -- DNS of the DC in the target forest to process object to"
    write-host "-type                   -- Type of object to process, if omitted then all object types are processed"
    write-host "                           CA         -- Process CA object(s)"
    write-host "                           Template   -- Process Template object(s)"
    write-host "                           OID        -- Process OID object(s)"
    write-host '-cn                     -- Common name of the object to process, do not include the cn= (ie "User" and not "CN=User"'
    write-host "                           This option is only valid if -type <> is also specified"
    write-host "-f                      -- Force overwrite of existing objects when copying. Ignored when deleting."
    write-host "-whatif                 -- Display what object(s) will be processed without processing"
    write-host "-deleteOnly             -- Will delete object in the target forest if it exists"            
    write-host ""
    write-host ""    
}

#
# Build a list of attributes to copy for some object type
#
function GetSchemaSystemMayContain($ForestContext, $ObjectType)
{
    #
    # first get all attributes that are part of systemMayContain list
    #
    $SchemaDE = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]::FindByName($ForestContext, $ObjectType).GetDirectoryEntry()
    $SystemMayContain = $SchemaDE.systemMayContain

    #
    # if schema was upgraded with adprep.exe, we need to check mayContain list as well
    #
    if($null -ne $SchemaDE.mayContain)
    {
        $MayContain = $SchemaDE.mayContain
        foreach($attr in $MayContain)
        {
            $SystemMayContain.Add($attr)
        }
    }
        
    #
    # special case some of the inherited attributes
    #
    if (-1 -eq $SystemMayContain.IndexOf("displayName"))
    {
        $SystemMayContain.Add("displayName")
    }
    if (-1 -eq $SystemMayContain.IndexOf("flags"))
    {
        $SystemMayContain.Add("flags")
    }
    if ($objectType.ToLower().Contains("template") -and -1 -eq $SystemMayContain.IndexOf("revision"))
    {
        $SystemMayContain.Add("revision")
    }
    
    return $SystemMayContain
}

#
# Copy or delete all objects of some type
#
function ProcessAllObjects($SourcePKIServicesDE, $TargetPKIServicesDE, $RelativeDN)
{
    $SourceObjectsDE = $SourcePKIServicesDE.psbase.get_Children().find($RelativeDN)
    $ObjectCN = $null
    
    foreach($ChildNode in $SourceObjectsDE.psbase.get_Children())
    {
        # if some object failed, we will try to continue with the rest
        trap
        {
            # CN maybe null here, but its ok. Doing best effort. 
            write-warning ("Error while coping an object. CN=" + $ObjectCN)
            write-warning $_
            write-warning $_.InvocationInfo.PositionMessage
            continue
        }

        $ObjectCN = $ChildNode.psbase.Properties["cn"]
        ProcessObject $SourcePKIServicesDE $TargetPKIServicesDE $RelativeDN $ObjectCN
        $ObjectCN = $null
    }
    
}

#
# Copy or delete an object
#
function ProcessObject($SourcePKIServicesDE, $TargetPKIServicesDE, $RelativeDN, $ObjectCN)
{
    $SourceObjectContainerDE = $SourcePKIServicesDE.psbase.get_Children().find($RelativeDN)
    $TargetObjectContainerDE = $TargetPKIServicesDE.psbase.get_Children().find($RelativeDN)

    #
    # when copying make sure there is an object to copy
    #
    if($FALSE -eq $Script:DeleteOnly)
    {
        $DSSearcher =  [System.DirectoryServices.DirectorySearcher]$SourceObjectContainerDE
        $DSSearcher.Filter = "(cn=" +$ObjectCN+")"
        $SearchResult = $DSSearcher.FindAll()
        if (0 -eq $SearchResult.Count)
        {
            write-host ("Source object does not exist: CN=" + $ObjectCN + "," + $RelativeDN)
            return
        }
        $SourceObjectDE = $SourceObjectContainerDE.psbase.get_Children().find("CN=" + $ObjectCN)
    }
    
    #
    # Check to see if the target object exists, if it does delete if overwrite is enabled.
    # Also delete is this a deletion only operation.
    #
    $DSSearcher =  [System.DirectoryServices.DirectorySearcher]$TargetObjectContainerDE
    $DSSearcher.Filter = "(cn=" +$ObjectCN+")"
    $SearchResult = $DSSearcher.FindAll()
    if ($SearchResult.Count -gt 0)
    {
        $TargetObjectDE = $TargetObjectContainerDE.psbase.get_Children().find("CN=" + $ObjectCN)

        if($Script:DeleteOnly) 
        {
            write-host ("Deleting: " + $TargetObjectDE.DistinguishedName)
            if($FALSE -eq $DryRun)
            {
                $TargetObjectContainerDE.psbase.get_Children().Remove($TargetObjectDE)
            }
            return
        } 
        elseif ($Script:OverWrite)
        {
            write-host ("OverWriting: " + $TargetObjectDE.DistinguishedName)
            if($FALSE -eq $DryRun)
            {
                $TargetObjectContainerDE.psbase.get_Children().Remove($TargetObjectDE)
            }
        } 
        else 
        {
            write-warning ("Object exists, use -f to overwrite. Object: " + $TargetObjectDE.DistinguishedName)
            return
        }
    } 
    else 
    {
        if($Script:DeleteOnly) 
        {
            write-warning ("Can't delete object. Object doesn't exist. Object: " + $ObjectCN + ", " + $TargetObjectContainerDE.DistinguishedName)
            return
        } 
        else 
        {        
            write-host ("Copying Object: " + $SourceObjectDE.DistinguishedName)
        }
    }
    
    #
    # Only update the object if this is not a dry run
    #
    if($FALSE -eq $DryRun -and $FALSE -eq $Script:DeleteOnly)
    {
        #Create new AD object   
        $NewDE = $TargetObjectContainerDE.psbase.get_Children().Add("CN=" + $ObjectCN, $SourceObjectDE.psbase.SchemaClassName)

        #Obtain systemMayContain for the object type from the AD schema
        $ObjectMayContain = GetSchemaSystemMayContain $SourceForestContext $SourceObjectDE.psbase.SchemaClassName
        #Copy attributes defined in the systemMayContain for the object type
        foreach($Attribute in $ObjectMayContain)
        {
            $AttributeValue = $SourceObjectDE.psbase.Properties[$Attribute].Value
            if ($null -ne $AttributeValue)
            {
                $NewDE.psbase.Properties[$Attribute].Value = $AttributeValue
                $NewDE.psbase.CommitChanges()
            }
        }
        #Copy secuirty descriptor to new object. Only DACL is copied. 
        $BinarySecurityDescriptor = $SourceObjectDE.psbase.ObjectSecurity.GetSecurityDescriptorBinaryForm()
        $NewDE.psbase.ObjectSecurity.SetSecurityDescriptorBinaryForm($BinarySecurityDescriptor, [System.Security.AccessControl.AccessControlSections]::Access)
        $NewDE.psbase.CommitChanges()
    }
}

# 
# Get parent container for all PKI objects in the AD
#
function GetPKIServicesContainer([System.DirectoryServices.ActiveDirectory.DirectoryContext] $ForestContext, $dcName)
{
    $ForObj = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
    $DE = $ForObj.RootDomain.GetDirectoryEntry()
    
    if("" -ne $dcName)
    {
        $newPath = [System.Text.RegularExpressions.Regex]::Replace($DE.psbase.Path, "LDAP://\S*/", "LDAP://" + $dcName + "/")
        $DE = New-Object System.DirectoryServices.DirectoryEntry $newPath 
    } 

    $PKIServicesContainer = $DE.psbase.get_Children().find("CN=Public Key Services,CN=Services,CN=Configuration")
    return $PKIServicesContainer
}

#########################################################
# Main script code
#########################################################

#
# All errors are fatal by default unless there is another 'trap' with 'continue'
#
trap
{
    write-error "The script has encoutnered a fatal error. Terminating script."
    break
}

ParseCommandLine

#
# Get a hold of the containers in each forest
#
write-host ("Target Forest: " + $TargetForestName.ToUpper())
$TargetForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext Forest, $TargetForestName
$TargetPKIServicesDE = GetPKIServicesContainer $TargetForestContext $Script:TargetDC

# Only need source forest when copying
if($FALSE -eq $Script:DeleteOnly)
{
    write-host ("Source Forest: " + $SourceForestName.ToUpper())
    $SourceForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext Forest, $SourceForestName
    $SourcePKIServicesDE = GetPKIServicesContainer $SourceForestContext $Script:SourceDC
} 
else 
{
    $SourcePKIServicesDE = $TargetPKIServicesDE
}

if("" -ne $ObjectType) {write-host ("Object Category to process: " + $ObjectType.ToUpper())}

#
# Process the command
#
switch($ObjectType.ToLower())
{
    all
    {
        write-host ("Enrollment Serverices Container")
    ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=Enrollment Services"
        write-host ("Certificate Templates Container")
        ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=Certificate Templates"
        write-host ("OID Container")
        ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=OID"
    }
    ca
    {
        if($null -eq $ObjectCN)
        {
            ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=Enrollment Services"
        } 
        else 
        {
            ProcessObject $SourcePKIServicesDE $TargetPKIServicesDE "CN=Enrollment Services" $ObjectCN
        }
    }
    oid
    {
        if($null -eq $ObjectCN)
        {
            ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=OID"
        } 
        else 
        {
            ProcessObject $SourcePKIServicesDE $TargetPKIServicesDE "CN=OID" $ObjectCN
        }
    }
    template
    {
        if($null -eq $ObjectCN)
        {
            ProcessAllObjects $SourcePKIServicesDE $TargetPKIServicesDE "CN=Certificate Templates"
        } 
        else 
        {
            ProcessObject $SourcePKIServicesDE $TargetPKIServicesDE "CN=Certificate Templates" $ObjectCN
        }    
    }
    default
    {
        write-warning ("Unknown object type: " + $ObjectType.ToLower())
        Usage
        exit 87
    }
} 

Insert subsection body here.

Was this page helpful?
(1500 characters remaining)
Thank you for your feedback

Community Additions

ADD
Show:
© 2014 Microsoft