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

image

The resulting screen will provide the container name.

image

 

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

Update-ConfigWithLdapFBA.zip