Saturday, June 20, 2009

Update SharePoint web.config with Powershell

Had a need to update 48 web.config files in a SharePoint farm. Purpose was to add Novell E-Directory as an authentication provider for SharePoint Farm in our DMZ consisting of 4 web applications (8 IIS websites by virtue of extending) load balanced across 6 Web Front Ends (more on that in a future post).

To add the provider, there are several web.config additions...and I'm sick of writing complex checklists for our operations folks to carry out the changes. I was also a little worried about them missing some of the additions. I prefer to have them perform one step -- double-click "webconfigupdate.bat" -- and be done rather than manually having them open, edit, and save 48 web.config files.

Powershell and the SPWebConfigModification Class to the rescue.


Credit needs to go to following -- I was able to merge the ideas from the code in the first two posts to get my final powershell script:

  • This is post by Raymond Mitchell that got me started. It's great sample Powershell script for single web app. Although, I needed to loop through every web app in my farm.
  • This is the post by Gary Lapointe where I found how to use Powershell to loop through web apps on a farm – it's not the first time Gary's blog helped me understand some administration scripting.
  • Mark Wagner has some nice guidance on this topic. Helped me understand errors I received.
  • Reza Alirezaei has a great post on SPWebConfigModification's Top 6 Issues which helped me confirm much of the oddness that resulted when testing.
  • Gotta thank "The Groom," a team member who got the SPWebConfigModification class working in a Solution he deployed. I tried the SPWebConfigModification class once before but abandoned it because of issues I was seeing based on Reza's post. The Groom's success inspired to give it another try.

Before starting, SPWebConfigModification Class requires XPath values for its "Path" and "Name" Properties. The Path and Name values are how the Class finds the location in the web.config to add, update, and remove values.

The BIT-101 XPath Query Tool is a nice, free, on-line tool for finding correct XPath to enter. I used the tool by simply copying my web.config – with new elements and attributes I wanted in my final web.config -- and pasting it into this java based tool. I than queried for the new elements and attributes to determine the XPath I would use in my script.

There are some nice examples here to help you understand XPath if you're not familiar.

Here's the code I threw into a Powershell .ps1 file I named webconfigchange.ps1:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
$websvcs = $farm.Services where -FilterScript {$_.GetType() -eq [Microsoft.SharePoint.Administration.SPWebService]}
#use powershell method for suppressing errors for reason on next line -- updates will run successfully even though errors are thrown -- comment out erroractionpreference variable when debugging
#If more than one call to the ApplyWebConfigModifications() method is made in close succession you will get the following error due to timer job:
#A web configuration modification operation is already running.
$erroractionpreference = "SilentlyContinue"


#loop through each portal web app
foreach ($websvc in $websvcs)
{
foreach ($webapp in $websvc.WebApplications)
{
#update web.config for all web apps in farm excluding mysites and ssp
if ($webapp.name -like "*my*") {write-output "skipping mysites..."}
elseif ($webapp.name -like "*ssp*") {write-output "skipping SSP..."}
else
{
#add peoplepicker node for IDM – IDM is name we will use for Authentication Provider for Novell Identity Management
#here's where you'll need to understand XPath – this variable captures the xpath query – later in script, I'll optionally use Name and Path instead of Argumentlist
$ppMod = New-Object -TypeName "Microsoft.SharePoint.Administration.SPWebConfigModification" -ArgumentList "add[@key='IDM']", "configuration/SharePoint/PeoplePickerWildcards"
$ppMod.Sequence = 0
$ppMod.Owner = "SimpleSampleUniqueOwnerValue"
$ppMod.Type = "EnsureChildNode"
#this is actual value which will be added or modified
$ppMod.Value = "<add key='IDM' value='*' />"
$webapp.WebConfigModifications.Add($ppMod)


#add membership node
$memberMod = New-Object -TypeName "Microsoft.SharePoint.Administration.SPWebConfigModification" -ArgumentList "membership", "configuration/system.web"
$memberMod.Sequence = 0
$memberMod.Owner = "SimpleSampleUniqueOwnerValue"
$memberMod.Type = "EnsureChildNode"
$memberMod.Value = "<membership defaultProvider='IDM'></membership>"
$webapp.WebConfigModifications.Add($memberMod)


#add providers node
$providerMod = New-Object -TypeName "Microsoft.SharePoint.Administration.SPWebConfigModification" -ArgumentList "providers", "configuration/system.web/membership"
$providerMod.Sequence = 0
$providerMod.Owner = "SimpleSampleUniqueOwnerValue"
$providerMod.Type = "EnsureChildNode"
$providerMod.Value = "<providers></providers>"
$webapp.WebConfigModifications.Add($providerMod)


#add IDM LDAP attributes – here, we're using Path and Name properties instead of Argumentlist
$ldapMod = New-Object Microsoft.SharePoint.Administration.SPWebConfigModification
$ldapMod.Path = "configuration/system.web/membership/providers"
$ldapMod.Name = "add[@name='IDM'][@type='Microsoft.Office.Server.Security.LDAPMembershipProvider, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C'][@server='<your Ldap server>'][@port='389'][@useSSL='false'][@useDNAttribute='false'][@userDNAttribute='cn'][@userNameAttribute='mail'][@userContainer='<your Ldap path>'][@userObjectClass='person'][@userFilter='(ObjectClass=*)'][@scope='Subtree'][@otherRequiredUserAttributes='sn,givenname,cn']"
$ldapMod.Sequence = 0
$ldapMod.Owner = "SimpleSampleUniqueOwnerValue"
$ldapMod.Type = "EnsureChildNode"
$ldapMod.Value = "<add name='IDM' type='Microsoft.Office.Server.Security.LDAPMembershipProvider, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C' server='<your Ldap server>' port='389' useSSL='false' useDNAttribute='false' userDNAttribute='cn' userNameAttribute='mail' userContainer='<your Ldap path>' userObjectClass='person' userFilter='(ObjectClass=*)' scope='Subtree' otherRequiredUserAttributes='sn,givenname,cn' />"
$webapp.WebConfigModifications.Add($ldapMod)


#save changes and apply to farm
$method = [Microsoft.Sharepoint.Administration.SPServiceCollection].GetMethod("GetValue", [Type]::EmptyTypes)
$closedMethod = $method.MakeGenericMethod([Microsoft.Sharepoint.Administration.SPWebService])
$services = $webapp.Farm.Services
$service = $closedMethod.Invoke($services, [Type]::EmptyTypes)
$service.ApplyWebConfigModifications()
$webapp.Update()
}
}
}


I call the above script to run from a .bat file with below commands:

powershell.exe Set-ExecutionPolicy RemoteSigned
REM command to run script from the ps1 file
powershell.exe -noexit .\webconfigchange.ps1

So there it is. With double-click of batch file, 48 web.config files are modified. If I ever want to remove the values, I can run same ps1 but replace every "WebConfigModifications.Add" entry with "WebConfigModifications.Remove." Another thing I like about SPWebConfigModification Class is that all the changes are stored in the SharePoint configuration database so if I ever add new front ends, the web.config values are automatically populated.

No comments:

Post a Comment