OK - I know I'm slow to be blogging about SPWebConfigModification, but figured that I'd add a little value with a healthy dose of powershell.
There are a number of approaches to using SPWebConfigModification to update the web.config (see
here,
here and
here), but all of these essentially require deploying / retracting a feature to modify / retract the config modifications. It may just be that I'm a little picky, but I really don't want to have to deploy code to make a configuration change - kinda defeats the point of configuration IMHO. Of course, I really don’t want to log onto a bunch of different servers, and make manual updates to each, as the chance of my fat-fingering something is pretty high.
Of late - whenever I'm dealing with something painful, I invariably turn to powershell and this was no exception. A quick translation of this example from
Mark Wagner's blog:
-
- SPWebApplication webApp = new SPSite("http://localhost").WebApplication;
-
-
-
- SPWebConfigModification modification = new SPWebConfigModification("mode", "system.web/customErrors");
- modification.Owner = "SimpleSampleUniqueOwnerValue";
- modification.Sequence = 0;
- modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
- modification.Value = "Off";
-
-
- webApp.WebConfigModifications.Add(modification);
-
-
- webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
-
-
- webApp.Update();
Yields:
- $site = new-object Microsoft.SharePoint.SPSite -argumentList "http://localhost"
- $webApplication = $site.WebApplication
- $modification = New-Object -TypeName "Microsoft.SharePoint.Administration.SPWebConfigModification" -ArgumentList "mode", "system.web/customErrors"
- $modification.Value = "Off"
- $modification.Owner = "SimpleSampleUniqueOwnerValue"
- $modification.Sequence = 0
- $modification.Type = "EnsureAttribute"
- $webApplication.WebConfigModifications.Add($modification)
- $method = [Microsoft.Sharepoint.Administration.SPServiceCollection].GetMethod("GetValue", [Type]::EmptyTypes)
- $closedMethod = $method.MakeGenericMethod([Microsoft.Sharepoint.Administration.SPWebService])
- $services = $webApplication.Farm.Services
- $service = $closedMethod.Invoke($services, [Type]::EmptyTypes)
- $service.ApplyWebConfigModifications()
- $webApplication.Update()
- $site.Dispose()
This is atypically more complex than the equivalent c# code above - almost entirely due to the generic "GetValue" method being called.
Now, the only issue outstanding is to wrap it all up in a reusable script. I started down the path of using xml to define the configuration values, basically using
Ryan's xsd . This does, unfortunately, get rather verbose due to the need to define each attribute in a given node. Once again - this was beginning to feel painful and my thoughts turned again to powershell - why not define the variables using powershell itself? If the configuration is a powershell script, I can simply dot-source it, and I'm good to go.
Of course - I still need a convention to define the configuration, and eventually I settled on this:
- $mods =
- ,@{
- "name" = 'add[@key="TestConfig"]';
- "value" = '<add key="TestConfig" value="1000" />';
- "owner" = "hgreen";
- "path" = "/configuration/appSettings";
- "sequence" = "0";
- "type" = "EnsureChildNode";
- }
$mods is a list of Dictionary objects - each of which holds the relevant values to be used in the construction of a SPWebConfigModification. Defaults are provided for owner and sequence, the rest should all be provided by the configuration script. The driving force here was to keep the configuration script purely configuration. This defines the modifications, and nothing else.
The final script:
- param(
- $application = "$(throw 'application is a mandatory parameter.')",
- $fileName = "$(throw 'fileName is a mandatory parameter.')",
- [switch]$retract
- )
-
- trap [Exception] {
- Write-Error $("TRAPPED: " + $_.Exception.GetType().FullName);
- Write-Error $("TRAPPED: " + $_.Exception.Message);
- if ($site -ne $null)
- { $site.Dispose() }
-
- }
-
- function Coalesce-Args
- {
- ([object[]]($args | Where-Object {$_}) + ,$null)[0]
- }
-
- [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Sharepoint")
-
- $mods = $null
- . (Resolve-Path $fileName)
- $site = new-object Microsoft.SharePoint.SPSite -argumentList $application
- $webApplication = $site.WebApplication
-
-
- foreach($mod in $mods)
- {
- $name = $mod.name
- $value = $mod.value
- $principal = [Security.Principal.WindowsIdentity]::GetCurrent()
- $owner = Coalesce-Args $mod.owner $principal.Name
- $path = $mod.path
- $sequence = Coalesce-Args $mod.sequence 0
- $type = $mod.type
-
- $modification = New-Object -TypeName "Microsoft.SharePoint.Administration.SPWebConfigModification" -ArgumentList $name, $path
- $modification.Value = $value
- $modification.Owner = $owner
- $modification.Sequence = $sequence
- $modification.Type = $type
-
- if ($retract)
- {
- $webApplication.WebConfigModifications.Remove($modification)
- }
- else
- {
- $webApplication.WebConfigModifications.Add($modification)
- }
-
- }
-
- $method = [Microsoft.Sharepoint.Administration.SPServiceCollection].GetMethod("GetValue", [Type]::EmptyTypes)
- $closedMethod = $method.MakeGenericMethod([Microsoft.Sharepoint.Administration.SPWebService])
- $services = $webApplication.Farm.Services
- $service = $closedMethod.Invoke($services, [Type]::EmptyTypes)
- $service.ApplyWebConfigModifications()
- $webApplication.Update()
-
- $site.Dispose()
This is available as a download
here, this includes the sample configuration script shown above.