User Profiles now (SharePoint 2010) is a SharePoint service application that use a Microsoft Forefront Identity 2010 as a underlying tool for SharePoint profiles synchronization.
This is a worst change in SharePoint from previous version. Now, It’s very slow, very hard-configurable and causes huge amount of problems during configure procedures and further work. To verify this you can read these guides: Technet: Configure profile synchronization, Technet: Maintain profile synchronization.
Quote: Incorrect permissions are the most common cause of errors in configuring profile synchronization
Quote: Farm Account must Be a member of the Administrators group on the synchronization server (see the Profile Synchronization Planning worksheet). You can remove this permission after you have configured the User Profile Synchronization service.
Wonderful, isn’t it?
And now We’ll talk about setting up an Active Directory connection with user profiles. You can configure such connection with administrative pages of User Profile Service Application, but these pages are very slow and buggy.
Note: For example, if Active Directory has some granular security settings you may get a timeout error as described here. You can configure timeouts with powershell (here, last paragraph), but that doesn’t help in this case.
We must choose another way: let’s automate the creation of connections with great help of PowerShell gods.
First of all, the core of our script is the UserProfileConfigManager class that contains the ConnectionManager property with several methods for adding new connections with user profiles.
We are interested in the AddActiveDirectoryConnection method, of course. This is a very “simple” method with 8 parameters (!) .
public DirectoryServiceConnection AddActiveDirectoryConnection(
ConnectionType type,
string displayName,
string server,
bool useSSL,
string accountDomain,
string accountUsername,
SecureString accountPassword,
List<DirectoryServiceNamingContext> namingContexts,
string spsClaimProviderTypeValue,
string spsClaimProviderIdValue
)
And here there is another type DirectoryServiceNamingContext which has importance for us.
Its constructors have large signatures too – 8 and 9 parameters:
public DirectoryServiceNamingContext(
string distinguishedName,
string domainName,
bool isDomain,
Guid objectId,
List<string> containersIncluded,
List<string> containersExcluded,
List<string> preferredDomainControllers,
bool useOnlyPreferredDomainControllers
)
Thanks God, we have intuitive parameter names and types. So, let’s code our script:
Add-SPActiveDirectoryConnection.ps1:
function Get-SPServiceContext(
[Microsoft.SharePoint.Administration.SPServiceApplication]$profileApp)
{
if($profileApp -eq $null)
{
# Get first User Profile Service Application
$profileApp = @(Get-SPServiceApplication |
? { $_.TypeName -eq "User Profile Service Application" })[0]
}
return [Microsoft.SharePoint.SPServiceContext]::GetContext(
$profileApp.ServiceApplicationProxyGroup,
[Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
}
function Convert-ToList($inputObject, [System.String]$Type)
{
begin
{
if($type -eq $null -or $type -eq '')
{
$type = [string]
}
$list = New-Object System.Collections.Generic.List[$type]
}
process { $list.Add($_) }
end
{
return ,$list
}
}
function Get-DC($domainName)
{
return ("DC=" + $domainName.Replace(".", ",DC="))
}
# Types
$DirectoryServiceNamingContextType = [Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext, Microsoft.Office.Server.UserProfiles, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]
# Globals
$connectionName = "example.com"
$domainName = "example.com"
$accountName = "EXAMPLE\User"
$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force
$partitions = @{
"example.com" = @("DC=example,DC=com");
};
# Main()
# Prepare Parameters
$userDomain = $accountName.Substring(0, $accountName.IndexOf("\"))
$userName = $accountName.Substring($accountName.IndexOf("\") + 1)
$dnContexts = $partitions.GetEnumerator() |
% {
$domainName = $_.Key
$containers = $_.Value | Convert-ToList
$partition = [ADSI]("LDAP://" + (Get-DC $domainName))
$partitionId = New-Object Guid($partition.objectGUID)
New-Object $DirectoryServiceNamingContextType(
$partition.distinguishedName,
$domainName,
<# isDomain: #> $false,
<# objectId: #> $partitionId,
<# containersIncluded: #> $containers,
<# containersExcluded: #> $null,
<# preferredDomainControllers: #> $null,
<# useOnlyPreferredDomainControllers: #> $false)
} | Convert-ToList -Type $DirectoryServiceNamingContextType
$partition = [ADSI]("LDAP://CN=Configuration," + (Get-DC $domainName))
$partitionId = New-Object Guid($partition.objectGUID)
$containers = @($partition.distinguishedName) | Convert-ToList
$dnContext = New-Object $DirectoryServiceNamingContextType(
$partition.distinguishedName,
$domainName,
<# isDomain: #> $true,
<# objectId: #> $partitionId,
<# containersIncluded: #> $containers,
<# containersExcluded: #> $null,
<# preferredDomainControllers: #> $null,
<# useOnlyPreferredDomainControllers: #> $false)
$dnContexts.Add($dnContext)
# Create Active Directory Connection
$serviceContext = Get-SPServiceContext
$configManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext)
if($configManager.ConnectionManager.Contains($connectionName) -eq $false)
{
$configManager.ConnectionManager.AddActiveDirectoryConnection(
[Microsoft.Office.Server.UserProfiles.ConnectionType]::ActiveDirectory,
$connectionName, $domainName, <# useSSL: #> $false,
$userDomain, $userName, $password,
<# namingContexts #> $dnContexts,
<# spsClaimProviderTypeValue: #> $null,
<# spsClaimProviderIdValue: #> $null)
}
else
{
Write-Host "Connection '$connectionName' already exist. Delete it before run this script."
}
That’s All.
And, of course, I hate it!
Awesome stuff.
ReplyDeleteHow do we set the context for specific domain t import.. when i ran the script.. i see the lis of domains.. but if i wanted to selectthe context to specific domain.. how do i do it?
If you have a domain forest, and/or you need to select some organization units than you need to set partitions via the $partitions variable (from Globals section of the script above).
ReplyDeleteFor example:
$partitions = @{
"example.com" = @("OU=Users,DC=example,DC=com", "OU=Guests,DC=example,DC=com");
"north.example.com" = @("OU=Users,DC=north,DC=example,DC=com", "OU=External,DC=north,DC=example,DC=com");
};
For this case we select two organization units in the root domain "example.com" and two OU's in the subdomain north.example.com.
Hi, great post. I need to select only a couple of OU's from the domain, but the script still selects every OU in the containers section. My code is below, have I missed something?
Delete$partitions = @{
"adslocal.net" = @("OU=Admin Users,OU=Windows 7,OU=Users,OU=London,DC=contoso,DC=com","OU=Admin Users,OU=Windows 7,OU=Users,OU=Munich,DC=contoso,DC=com");
};
Excellent !!!
ReplyDeleteBTW, Before i try, By doing this will it reflect in UI while populating containers?
I mean UI will reflect the context selected?
Yes, of course. This is a reimplementation of the standard mechanism which used by the UI-page (EditDSServer.aspx). In the UI you will see (when you click "Populate Containers") selected containers as expected.
ReplyDeleteExcellent Andrew, It worked for me..
ReplyDeleteJust to understand more on the code. What exactly are we doing in the script for following lines
$partition = [ADSI]("LDAP://CN=Configuration," + (Get-DC $domainName))
$partitionId = New-Object Guid($partition.objectGUID)
You are welcome, Mandy.
ReplyDeleteThis code adds the special configuration partition from an active directory which contains the domain forest's configuration and the active directory's scheme.
http://technet.microsoft.com/en-us/library/cc772886%28WS.10%29.aspx
Excelent! Just not fully working yet... I have everything setup in the script as far a params go, but i get this error: Exception calling "AddActiveDirectoryConnection" with "10" argument(s): "Unable
ReplyDeleteto process Create message"
At F:\scripts\CreateUPSConnections.ps1:104 char:66
+ $configManager.ConnectionManager.AddActiveDirectoryConnection <<<< (
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Hi there, I'm also getting the AddActiveDirectoryConnection" with "10" argument
ReplyDeleteError - did you find a way to resolve this?
Hello, All!
ReplyDeleteIf you get some strange exception You must check you permissions on User Profile Service Application or just run this script with Server Farm Account.
With best regards, Andrew MossHater.
Thanks for putting this script together, it's exactly what I'm looking for. I've integrated into my larger install and config script but I keep hitting a problem. This:
ReplyDeletereturn Microsoft.SharePoint.SPServiceContext]::GetContext($profileApp.ServiceApplicationProxyGroup, [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
Always comes back with a 0000 SiteSubscriptionID. It's part of Get-SPServiceContext and gets run without parameters. Everything seems to go fine until it hits this return statement. Any ideas?
Nice script, but I receive an error:
ReplyDeleteNew-Object : Exception calling ".ctor" with "1" argument(s): "The located assembly's manifest definition does not match
the assembly reference. (Exception from HRESULT: 0x80131040)"
At D:\Add-SPActiveDirectoryConnection.ps1:102 char:28
+ $configManager = New-Object <<<< Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext)
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
Hi Andrew,
ReplyDeleteI also got the expection when AddActiveDirectoryConnection - but I traced it ;-)
The problem occurs when you have a setup with subdomains - then you do not correctly retrieve the Configuration container :-)
To fix it, change the following lines under #Global:
$connectionName = "example.com"
$forestName = "example.com"
$accountName = "EXAMPLE\SyncUser"
$passclear = 'P@ssW0rd'
$password = ConvertTo-SecureString $passclear -AsPlainText -Force
and then, further down, instead of
$partition = [ADSI]("LDAP://" + (Get-DC $domainName))
use
$partition = new-object DirectoryServices.DirectoryEntry("LDAP://" + (Get-DC $domainName), $accountName, $passclear)
to bind as the User that is used for the sync (your admin/farm account may not have access to the OU)
Last but not least, when creating the configuration context, use the following line
$partition = new-object DirectoryServices.DirectoryEntry"LDAP://CN=Configuration," + (Get-DC $forestName), $accountName, $passclear)
instead of
$partition = [ADSI]("LDAP://CN=Configuration," + (Get-DC $domainName))
That way, domain-name will not be overwritten with some perhaps invalid value and you use the credentials of the sync user (which should have access to the configuration container as per MS-spec.
Hope that helps someon, it fixed the problem for me!
Really cool stuff.
ReplyDeleteHowever, I'm wondering:
Does the management agent run profiles (DS_EXPORT, DS_FULLSYNC, etc) are created by this method? No ones are created on my side and I found no information on internet.
Do I miss something or creating the connection in the wrong way ?
Thank you
Hello,
ReplyDeleteReally cool stuff. Howerver, one question
Does the call to AddActiveDirectoryConnection create the management agent run profiles such as DS_FULLSYNC, DS_FULLIMPORT, DS_DELTASYNC ?
These ones are automatically created when creating the active directory connection from sharepoint central admin, but no ones show up when trying to create it programmatically.
Regards
Weird... I got a script IDENTICAL to this one from PSS back in october. Did you create this script yourself?
ReplyDeleteGet-SPServiceContext is returning a SiteSubcriptionId of 00000000-0000-0000-0000-000000000000?
ReplyDeleteThen receiving "object reference not set to"... error on a the next method. Any ideas what may be incorrect?
Thanks
New-Object : Exception calling ".ctor" with "0" argument(s): "Value cannot be null.
Parameter name: serviceContext"
At C:\Users\Public\Documents\Adduserprofileconnection.ps1:107 char:28
+ $configManager = New-Object <<<< Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext)
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
Using Farm Admin account yet getting the same erorr as mentioned by Tiffany... Like 114: char 66
ReplyDeleteSometimes work fine. But sometimes not. If not exception looks like:
ReplyDeleteNew-Object : Exception calling ".ctor" with "1" argument(s): "The located assem
bly's manifest definition does not match the assembly reference. (Exception fro
m HRESULT: 0x80131040)"
At line:1 char:32
+ $configManager = New-Object <<<< Microsoft.Office.Server.UserProfiles.Us
erProfileConfigManager($serviceContext)
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvoca
tionException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.Power
Shell.Commands.NewObjectCommand
But if i try to re-run powershell window script works well. That's not so good. i tried iisreset, reload sharepoint powershell snapin, reload next assemblies:
Microsoft.SharePoint.Administration.SPServiceApplication
Microsoft.SharePoint.SPSiteSubscriptionIdentifier
Microsoft.SharePoint.SPServiceContext
Microsoft.Office.Server.UserProfiles
Microsoft.Office.Server.UserProfiles.UserProfileConfigManager
and nothing helps me... May be you can show me the light in that dark hole :)
Hmm I am still receiving the error:
ReplyDeleteException calling "AddActiveDirectoryConnection" with "10" argument(s): "Unable to process Create message"
I am running the AddActiveDirectoryConnection call under my farm account.
I have also double checked the farm accounts permissions on the User Profile Service and it has all permissions.
Any more ideas?
Great scripts. I needed to use it because the UI was not able to expand one of the OU's on the main level.
ReplyDeleteAn addition to your script is to add the exclude partions. Probably the following list can be added as default.
OU=Administrative,DC=contoso,DC=local
CN=Computers,DC=contoso,DC=local
OU=Domain Controllers,DC=contoso,DC=local
CN=ForeignSecurityPrincipals,DC=contoso,DC=local
CN=Managed Service Accounts,DC=contoso,DC=local
CN=Program Data,DC=contoso,DC=local
CN=System,DC=contoso,DC=local
CN=Users,DC=contoso,DC=local
CN=Builtin,DC=contoso,DC=local
CN=Infrastructure,DC=contoso,DC=local
CN=LostAndFound,DC=contoso,DC=local
CN=NTDS Quotas,DC=contoso,DC=local
DC=DomainDnsZones,DC=contoso,DC=local
DC=ForestDnsZones,DC=contoso,DC=local
CN=Configuration,DC=contoso,DC=local
Kind regards,
Joran Markx
Hello Andrew,
ReplyDeleteI need to add many different OU's to be configured in one single connection name. Is it possible to do this using ur script?
Thanks,
Raja
Cheers for the post. I feel your hate! :)
ReplyDeleteWe have very specific OUs we need to setup and and synch, however, no matter what I enter in the partitions globals, the connection gui shows ALL domain containers as being selected. I have change the timeouts just in case ...
Hi Great Post , I'm also getting the AddActiveDirectoryConnection" with "10" argument error has anyone managed to resolve this I have used the Farm Admin account to run the script
ReplyDeleteHi, a really good post. I need to get my script to only select certain ou's and not every object in the domain like it is doing right now. I have set the $partitions to select only two OU's like below but it still selects every object from every OU. Any suggestions?
ReplyDelete$partitions = @{
"example.com" = @("OU=Users,DC=example,DC=com", "OU=Guests,DC=example,DC=com");
Hi, great work and how to select a sub domain. My update but didn't work
ReplyDelete$partitions = @{
"example.com" = @("sDC=sub,DC=example,DC=com");
Please advice