Configuring LDAP for FBA in SharePoint 2010 or SharePoint 2013 with PowerShell
This post provides PowerShell script to easily configure forms based authentication using LDAP in SharePoint 2010 or 2013.
A long time ago, I wrote a post that shows how to configure the SQL Server Provider for FBA in SharePoint 2010. I also wrote an accompanying post on how to automate configuring FBA with SqlMembershipProvider in SharePoint 2010 using PowerShell. Those posts also apply to SharePoint 2013 as well, you just need to change the path to the STS web.config to point to the path in SharePoint 2013.
Today I was troubleshooting a configuration that uses LDAP to authenticate users using the LdapMembershipProvider, enabling LDAP authentication in SharePoint using FBA. Admittedly, I stumbled a few times due to the slight differences between the settings in Central Administration, the STS, and the application’s web.config settings. Once I got it working, I decided to create a PowerShell script that would let me easily configure an LDAP provider for SharePoint 2010 or SharePoint 2013 and take the guesswork out of it while making the process less error-prone.
What Does It Do?
The code isn’t terribly difficult, and follows the directions from the TechNet article Configure forms-based authentication for a claims-based Web application (SharePoint Server 2010) closely. It uses PowerShell to update the web.config for the web application, Central Administration, and the STS for every server in your farm.
The “main” part of the script is at the bottom of the page. This is where we ask for all servers in the farm that are application servers (not SQL, Active Directory, or Exchange servers) and iterate through all of them. Note that the currently logged in account must have read-write permissions to all of the servers in the farm and have access to the C drive on each. Also notice that a backup file is created in the directory using the form “yyyy MM dd HH mm.web.config.bak” so that, should something go wrong, you can easily revert back to a previous version of the configuration file.
I did not edit the script to overwrite previous changes, I leave that as an exercise to the reader. The good news is that you can just go back to a previous backup file (one of the .bak files that this script creates) and simply rename it to web.config and you are back to where you started.
What Do I Need to Change?
The only part of the script that you should need to change are the variables at the bottom of the page.
#Update with path to Central Administration web.config
$pathToCentralAdminConfig = "c:\inetpub\wwwroot\wss\VirtualDirectories\37552\web.config"
#Update with path to web application's web.config
$pathToWebApplicationConfig = "c:\inetpub\wwwroot\wss\VirtualDirectories\DevTeam.Contoso.lab80\web.config"
#Update with the correct LDAP server
$ldapServer = "dc.contoso.lab"
#Update with the correct container name
$userContainer = "OU=EMPLOYEES,DC=CONTOSO,DC=LAB"
The article Configure forms-based authentication for a claims-based Web application (SharePoint Server 2010) has a good walkthrough of the settings and changes. The ldapServer variable is the LDAP server that you will authenticate against, and the userContainer variable is the container that holds your users.
If you want to find out what the correct container name is, Mirjam van Olst provides a great trick in her blog post “Configuring claims and forms based authentication for use with an LDAP provider in SharePoint 2010”. Just go to Active Directory Users and Computers, right-click the container name and choose All Tasks / Resultant Set of Policy (Planning).
The resulting screen will provide the container name.
The configuration for SharePoint 2010 and SharePoint 2013 are identical, except the path to the STS web.config. If you are using SharePoint 2013, you will also want to update the path to the STS web.config in the Main function within the script.
$stsConfigPath = "\\$name\c$\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\WebServices\SecurityToken\web.config"
Note also that the script assumes a membership provider name of “LdapMember” and role provider name of “LdapRole”. These are the values you will enter into Central Administration when configuring FBA.
Show Me The Code!
The code is available as an attachment to this post as well. As usual, this code is provided as-is, no warranties, use at your own risk.
function CreateBackupFile($xmlDoc, $path)
{
$date = Get-Date
$dateString = $date.ToString("yyyy MM dd H mm")
$backupPath = $path.Replace("web.config", "$dateString.web.config.bak")
$xmlDoc.Save($backupPath)
}
function AddPeoplePickerWildcard($xmlDoc)
{
$newPeoplePickerNode = $xmlDoc.selectSingleNode("/configuration/SharePoint/PeoplePickerWildcards/add[@key='LdapMember']");
if(!$newPeoplePickerNode)
{
$peoplePickerNode = $xmlDoc.selectSingleNode("/configuration/SharePoint/PeoplePickerWildcards")
$newPeoplePickerMemberNode = $xmlDoc.CreateNode("element", "add", "")
$peoplePickerKeyAttr = $xmlDoc.CreateAttribute("key");
$peoplePickerKeyAttr.Value = "LdapMember";
$newPeoplePickerMemberNode.Attributes.Append($peoplePickerKeyAttr)
$peoplePickerValueAttr = $xmlDoc.CreateAttribute("value");
$peoplePickerValueAttr.Value = "*"
$newPeoplePickerMemberNode.Attributes.Append($peoplePickerValueAttr)
$peoplePickerNode.AppendChild($newPeoplePickerMemberNode)
$newPeoplePickerRoleNode = $xmlDoc.CreateNode("element","add","")
$peoplePickerKeyAttr = $xmlDoc.CreateAttribute("key");
$peoplePickerKeyAttr.Value = "LdapRole";
$newPeoplePickerRoleNode.Attributes.Append($peoplePickerKeyAttr)
$peoplePickerValueAttr = $xmlDoc.CreateAttribute("value");
$peoplePickerValueAttr.Value = "*"
$newPeoplePickerRoleNode.Attributes.Append($peoplePickerValueAttr)
$peoplePickerNode.AppendChild($newPeoplePickerRoleNode)
}
}
function AddMembership($xmlDoc, $ldapServer, $userContainer, $userFilter)
{
$membershipAddNode = $xmlDoc.selectSingleNode("/configuration/system.web/membership/providers/add[@name='LdapMember']")
if(!$membershipAddNode)
{
#The membership node doesn't exist
$membershipNode = $xmlDoc.selectSingleNode("/configuration/system.web/membership")
$providerNode = $null
if(!$membershipNode)
{
$membershipNode = $xmlDoc.CreateNode("element", "membership", "")
$providerNode = $xmlDoc.CreateNode("element","providers","")
$membershipNode.AppendChild($providerNode)
$xmlDoc.selectSingleNode("/configuration/system.web").AppendChild($membershipNode)
}
$providerNode = $xmlDoc.selectSingleNode("/configuration/system.web/membership/providers")
$membershipAddNode = $xmlDoc.CreateNode("element","add","")
$membershipNameAttr = $xmlDoc.CreateAttribute("name")
$membershipNameAttr.Value = "LdapMember"
$membershipAddNode.Attributes.Append($membershipNameAttr)
$membershipTypeAttr = $xmlDoc.CreateAttribute("type")
$membershipTypeAttr.Value = "Microsoft.Office.Server.Security.LdapMembershipProvider, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
$membershipAddNode.Attributes.Append($membershipTypeAttr)
$membershipServerAttr = $xmlDoc.CreateAttribute("server")
$membershipServerAttr.Value = $ldapServer
$membershipAddNode.Attributes.Append($membershipServerAttr)
$membershipPortAttr = $xmlDoc.CreateAttribute("port")
$membershipPortAttr.Value = "389"
$membershipAddNode.Attributes.Append($membershipPortAttr)
$membershipuseSSLAttr = $xmlDoc.CreateAttribute("useSSL")
$membershipuseSSLAttr.Value = "false"
$membershipAddNode.Attributes.Append($membershipuseSSLAttr)
$membershipuserDNAttribute = $xmlDoc.CreateAttribute("userDNAttribute")
$membershipuserDNAttribute.Value = "distinguishedName"
$membershipAddNode.Attributes.Append($membershipuserDNAttribute)
$membershipuserNameAttribute = $xmlDoc.CreateAttribute("userNameAttribute")
$membershipuserNameAttribute.Value = "sAMAccountName"
$membershipAddNode.Attributes.Append($membershipuserNameAttribute)
$membershipuserContainer = $xmlDoc.CreateAttribute("userContainer")
$membershipuserContainer.Value = $userContainer
$membershipAddNode.Attributes.Append($membershipuserContainer)
$membershipuserObjectClass = $xmlDoc.CreateAttribute("userObjectClass")
$membershipuserObjectClass.Value = "person"
$membershipAddNode.Attributes.Append($membershipuserObjectClass)
$membershipuserFilter = $xmlDoc.CreateAttribute("userFilter")
$membershipuserFilter.Value = $userFilter
$membershipAddNode.Attributes.Append($membershipuserFilter)
$membershipScope = $xmlDoc.CreateAttribute("scope")
$membershipScope.Value = "Subtree"
$membershipAddNode.Attributes.Append($membershipScope)
$membershipotherRequiredUserAttributes = $xmlDoc.CreateAttribute("otherRequiredUserAttributes")
$membershipotherRequiredUserAttributes.Value = "sn,givenname,cn"
$membershipAddNode.Attributes.Append($membershipotherRequiredUserAttributes)
$providerNode.AppendChild($membershipAddNode)
}
}
function AddRoles($xmlDoc, $ldapServer, $userContainer, $userFilter, $groupFilter)
{
#Check to see if it was already created, and if not, create it
$rolesAddNode = $xmlDoc.selectSingleNode("/configuration/system.web/roleManager/providers/add[@name='LdapRole']")
if(!$rolesAddNode)
{
$rolesNode = $xmlDoc.selectSingleNode("/configuration/system.web/roleManager")
$providerNode = $null
if(!$rolesNode)
{
$rolesNode = $xmlDoc.CreateNode("element", "roleManager", "")
$providerNode = $xmlDoc.CreateNode("element","providers","")
$rolesNode.AppendChild($providerNode)
$xmlDoc.selectSingleNode("/configuration/system.web").AppendChild($rolesNode)
}
$rolesNode = $xmlDoc.selectSingleNode("/configuration/system.web/roleManager")
$rolesEnabledAttr = $xmlDoc.CreateAttribute("enabled");
$rolesEnabledAttr.Value = "true";
$rolesNode.Attributes.Append($rolesEnabledAttr)
$providerNode = $xmlDoc.selectSingleNode("/configuration/system.web/roleManager/providers")
$rolesAddNode = $xmlDoc.CreateNode("element","add","")
$roleNameAttr = $xmlDoc.CreateAttribute("name")
$roleNameAttr.Value = "LdapRole"
$rolesAddNode.Attributes.Append($roleNameAttr)
$roleTypeAttr = $xmlDoc.CreateAttribute("type")
$roleTypeAttr.Value = "Microsoft.Office.Server.Security.LdapRoleProvider, Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
$rolesAddNode.Attributes.Append($roleTypeAttr)
$rolesserverAttr = $xmlDoc.CreateAttribute("server")
$rolesserverAttr.Value = $ldapServer
$rolesAddNode.Attributes.Append($rolesserverAttr)
$rolesPortAttr = $xmlDoc.CreateAttribute("port")
$rolesPortAttr.Value = "389"
$rolesAddNode.Attributes.Append($rolesPortAttr)
$rolesuseSSL = $xmlDoc.CreateAttribute("useSSL")
$rolesuseSSL.Value = "false"
$rolesAddNode.Attributes.Append($rolesuseSSL)
$rolesgroupContainer = $xmlDoc.CreateAttribute("groupContainer")
$rolesgroupContainer.Value = $userContainer
$rolesAddNode.Attributes.Append($rolesgroupContainer)
$rolesgroupNameAttribute = $xmlDoc.CreateAttribute("groupNameAttribute")
$rolesgroupNameAttribute.Value = "cn"
$rolesAddNode.Attributes.Append($rolesgroupNameAttribute)
$rolesgroupNameAlternateSearchAttribute = $xmlDoc.CreateAttribute("groupNameAlternateSearchAttribute")
$rolesgroupNameAlternateSearchAttribute.Value = "samAccountName"
$rolesAddNode.Attributes.Append($rolesgroupNameAlternateSearchAttribute)
$rolesgroupMemberAttribute = $xmlDoc.CreateAttribute("groupMemberAttribute")
$rolesgroupMemberAttribute.Value = "member"
$rolesAddNode.Attributes.Append($rolesgroupMemberAttribute)
$rolesuserNameAttribute = $xmlDoc.CreateAttribute("userNameAttribute")
$rolesuserNameAttribute.Value = "sAMAccountName"
$rolesAddNode.Attributes.Append($rolesuserNameAttribute)
$rolesdnAttribute = $xmlDoc.CreateAttribute("dnAttribute")
$rolesdnAttribute.Value = "distinguishedName"
$rolesAddNode.Attributes.Append($rolesdnAttribute)
$rolesgroupFilter = $xmlDoc.CreateAttribute("groupFilter")
$rolesgroupFilter.Value = $groupFilter
$rolesAddNode.Attributes.Append($rolesgroupFilter)
$rolesuserFilter = $xmlDoc.CreateAttribute("userFilter")
$rolesuserFilter.Value = $userFilter
$rolesAddNode.Attributes.Append($rolesuserFilter)
$rolesscope = $xmlDoc.CreateAttribute("scope")
$rolesscope.Value = "Subtree"
$rolesAddNode.Attributes.Append($rolesscope)
$providerNode.AppendChild($rolesAddNode)
}
}
function ProcessCentralAdmin($path, $ldapServer, $userContainer)
{
$content = Get-Content -Path $path
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$xd.LoadXml($content)
CreateBackupFile $xd $path
#Add People Picker Wildcard
AddPeoplePickerWildcard $xd
#Add Roles
AddRoles $xd $ldapServer $userContainer "((ObjectClass=person)" "((ObjectClass=group)"
$roleNode = $xd.selectSingleNode("/configuration/system.web/roleManager")
$defaultRoleProviderAttr = $xd.CreateAttribute("defaultProvider")
$defaultRoleProviderAttr.Value = "AspNetWindowsTokenRoleProvider"
$roleNode.Attributes.Append($defaultRoleProviderAttr)
#Add Membership
AddMembership $xd $ldapServer $userContainer "(ObjectClass=person)"
$membershipNode = $xd.selectSingleNode("/configuration/system.web/membership")
$defaultMembershipProviderAttr = $xd.CreateAttribute("defaultProvider")
$defaultMembershipProviderAttr.Value = "LdapMember"
$membershipNode.Attributes.Append($defaultMembershipProviderAttr)
$xd.Save($path)
}
function ProcessWebApplication($path, $ldapServer, $userContainer)
{
$content = Get-Content -Path $path
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$xd.LoadXml($content)
CreateBackupFile $xd $path
#Add People Picker Wildcard
AddPeoplePickerWildcard $xd
#Add Membership
AddMembership $xd $ldapServer $userContainer "(&(ObjectClass=person))"
#Add Roles
AddRoles $xd $ldapServer $userContainer "(&(ObjectClass=person))" "(&(ObjectClass=group))"
$xd.Save($path)
}
function ProcessSTS($path, $ldapServer, $userContainer)
{
$content = Get-Content -Path $path
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$xd.LoadXml($content)
CreateBackupFile $xd $path
#People picker wildcard is not necessary in STS config
#Check to see if the system.web element exists, and if not, create it
$sysWebNode = $xd.SelectSingleNode("/configuration[system.web]")
if(!$sysWebNode)
{
$config = $xd.SelectSingleNode("/configuration");
$sysWebNode = $xd.CreateNode("element","system.web","")
$config.AppendChild($sysWebNode)
}
#Add Membership
AddMembership $xd $ldapServer $userContainer "(&(ObjectClass=person))"
#Set LdapMember as default in STS
$membershipNode = $xd.selectSingleNode("/configuration/system.web/membership")
$defaultMembershipProviderAttr = $xd.CreateAttribute("defaultProvider")
$defaultMembershipProviderAttr.Value = "LdapMember"
$membershipNode.Attributes.Append($defaultMembershipProviderAttr)
#Add Roles
AddRoles $xd $ldapServer $userContainer "(&(ObjectClass=person))" "(&(ObjectClass=group))"
#Set LdapRole as default in STS
$roleNode = $xd.selectSingleNode("/configuration/system.web/roleManager")
$defaultRoleProviderAttr = $xd.CreateAttribute("defaultProvider")
$defaultRoleProviderAttr.Value = "LdapRole"
$roleNode.Attributes.Append($defaultRoleProviderAttr)
$xd.Save($path)
}
function Main($pathToWebApplicationConfig, $pathToCentralAdminConfig, $ldapServer, $userContainer)
{
$servers = Get-SPServer | ?{$_.Role -eq "Application"}
foreach($server in $servers)
{
$name = $server.Name
$webAppConfigPath = $pathToWebApplicationConfig.ToLower().Replace("c:\", "\\$name\c$\")
$centralAdminConfigPath = $pathToCentralAdminConfig.ToLower().Replace("c:\", "\\$name\c$\")
$stsConfigPath = "\\$name\c$\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken\web.config"
ProcessWebApplication $webAppConfigPath $ldapServer $userContainer
if(Test-Path $centralAdminConfigPath)
{
ProcessCentralAdmin $centralAdminConfigPath $ldapServer $userContainer
}
ProcessSTS $stsConfigPath $ldapServer $userContainer
}
}
#Update with path to Central Administration web.config
$pathToCentralAdminConfig = "c:\inetpub\wwwroot\wss\VirtualDirectories\37552\web.config"
#Update with path to web application's web.config
$pathToWebApplicationConfig = "c:\inetpub\wwwroot\wss\VirtualDirectories\DevTeam.Contoso.lab80\web.config"
#Update with the correct LDAP server
$ldapServer = "dc.contoso.lab"
#Update with the correct container name
$userContainer = "OU=EMPLOYEES,DC=CONTOSO,DC=LAB"
Main $pathToWebApplicationConfig $pathToCentralAdminConfig $ldapServer $userContainer
Summary
That’s it! I have used this several times in my test environment and it seems to work for either an existing or new environment, but you’ll want to test to make sure any assumptions I make in the script about availability of elements are accurate.
For More Information
Configure forms-based authentication for a claims-based Web application (SharePoint Server 2010)
Configuring claims and forms based authentication for use with an LDAP provider in SharePoint 2010
Comments
Anonymous
January 31, 2013
I just created a new SharePoint 2013 farm and had it configured for FBA using LDAP in less than 2 minutes. Longest amount of time spent was waiting on the Central Admin UI to complete. Hoorah!Anonymous
May 28, 2014
Hi Kirk, Your script looks great, and hopefully we can use this with one of my customers. However, their AD implementation makes use of a specific security group for their SharePoint users located within a different OU than the actual user accounts OU. Given this configuration, what would I need to do in order to point to the group container of authorized SharePoint users rather than the accounts container? Thanks for the advice in advance... -TimAnonymous
October 14, 2014
Hi...Are you sure this works...I struggled in every step and could not resolve the issues in the script. Thanks Dev