web.config automated deployment

Apr 4, 2012 at 10:13 PM

So I see a minor failing in this deployment as it requires that you need to modify your web.config on each of your servers in the farm in order to deploy the solution.  Instead you should have some code that does this for you and rolls it out to any new servers that are joined to the farm.  As such, I have created the following patch.  The code is not pretty, but it works and it will automatically be deployed when the MyLinksMenuStapler activates.  It also removes the directive when the feature is deactivated.

I did also include a generated key with this patch as I had to generate one for deployment.  

For some reason not all the code is showing up in my preview here below, but it is all there in the source.  I can send the patch file or commit it directly to a branch in the repository.

 

Index: CONFIG/config.mylinksmenu.xml
===================================================================
--- CONFIG/config.mylinksmenu.xml	(revision 0)
+++ CONFIG/config.mylinksmenu.xml	(working copy)
@@ -0,0 +1,9 @@
+?<!--?xml version="1.0" encoding="utf-8" ?-->
+
+  
+    add[@assembly="$SharePoint.Project.AssemblyFullName$"]
+    configuration/system.web/compilation/assemblies
+    <![CDATA[<add assembly="$SharePoint.Project.AssemblyFullName$" />]]></Value>
+    <ModType>EnsureChildNode</ModType>
+  </ConfigEntry>
+</ConfigEntries>
\ No newline at end of file
Index: CONFIG/SharePointProjectItem.spdata
===================================================================
 - CONFIG/SharePointProjectItem.spdata	(revision 0)
+++ CONFIG/SharePointProjectItem.spdata	(working copy)
@@ -0,0 +1,4 @@
+?
+<ProjectItem Type="Microsoft.VisualStudio.SharePoint.MappedFolder" SupportedTrustLevels="FullTrust" SupportedDeploymentScopes="Package" xmlns="http://schemas.microsoft.com/VisualStudio/2010/SharePointTools/SharePointProjectItemModel">
+  <ProjectItemFolder Target="CONFIG" Type="RootFile" />
+</ProjectItem>
\ No newline at end of file
Index: Features/MyLinksMenu/MyLinksMenu.feature
===================================================================
 - Features/MyLinksMenu/MyLinksMenu.feature	(revision 14325)
+++ Features/MyLinksMenu/MyLinksMenu.feature	(working copy)
@@ -1,5 +1,8 @@
 ?
 <feature xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="71ad2dbb-db04-4323-b002-cffb64e57a98" description="Adds the My Links menu to the site." featureId="71ad2dbb-db04-4323-b002-cffb64e57a98" imageUrl="" scope="Site" solutionId="00000000-0000-0000-0000-000000000000" title="My Links Menu" version="" deploymentPath="MyLinksMenu" xmlns="http://schemas.microsoft.com/VisualStudio/2008/SharePointTools/FeatureModel">
+  <activationDependencies>
+    <referencedFeatureActivationDependency minimumVersion="" itemId="9a9769bd-e608-4b02-8b77-5b9e8d90ef25" />
+  </activationDependencies>
   <projectItems>
     <projectItemReference itemId="33e0d2bc-74f5-49d8-bc8c-27f46537c034" />
   </projectItems>
Index: Features/MyLinksMenuStapler/MyLinksMenuStapler.EventReceiver.cs
===================================================================
 - Features/MyLinksMenuStapler/MyLinksMenuStapler.EventReceiver.cs	(revision 0)
+++ Features/MyLinksMenuStapler/MyLinksMenuStapler.EventReceiver.cs	(working copy)
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Xml;
+using Microsoft.SharePoint;
+using Microsoft.SharePoint.Administration;
+using Microsoft.SharePoint.Utilities;
+
+namespace MyLinksMenu.Features.MyLinksMenuStapler
+{
+    /// <summary>
+    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
+    /// </summary>
+    /// <remarks>
+    /// The GUID attached to this class may be used during packaging and should not be modified.
+    /// </remarks>
+
+    [Guid("b48559e0-a9c3-4485-a102-64b663d390c4")]
+    public class MyLinksMenuStaplerEventReceiver : SPFeatureReceiver
+    {
+        // Uncomment the method below to handle the event raised after a feature has been activated.
+
+        public override void FeatureActivated(SPFeatureReceiverProperties properties)
+        {
+            SPWebApplication webApplication = (SPWebApplication)properties.Feature.Parent;
+            ApplyWebConfigModification(webApplication, "config.MyLinksMenu.xml", ConfigMode.Install);
+        }
+
+
+        // Uncomment the method below to handle the event raised before a feature is deactivated.
+
+        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
+        {
+            SPWebApplication webApplication = (SPWebApplication)properties.Feature.Parent;
+            ApplyWebConfigModification(webApplication, "config.MyLinksMenu.xml", ConfigMode.Remove);
+        }
+
+
+        // Uncomment the method below to handle the event raised after a feature has been installed.
+        //public override void FeatureInstalled(SPFeatureReceiverProperties properties)
+        //{
+        //}
+
+
+        // Uncomment the method below to handle the event raised before a feature is uninstalled.
+        //public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
+        //{
+        //}
+
+        // Uncomment the method below to handle the event raised when a feature is upgrading.
+
+        //public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, System.Collections.Generic.IDictionary<string, string> parameters)
+        //{
+        //}
+
+        public static void ApplyWebConfigModification(SPWebApplication webApplication, String filename, ConfigMode configMode)
+        {
+            foreach (ModificationEntry mod in GetConfigurationMods(filename))
+                AddRemoveMod(mod, webApplication, configMode);
+
+        }
+
+        private static void AddRemoveMod(ModificationEntry mod, SPWebApplication chosenWebApp, ConfigMode configMode)
+        {
+            SPWebConfigModification newModification = CreateModification(mod);
+            List<SPWebConfigModification> modsToDelete = chosenWebApp.WebConfigModifications.Where(
+                configModification => IsConfigModificationsEqual(newModification, configModification)).ToList();
+
+            if (modsToDelete.Count > 0)
+            {
+                foreach (SPWebConfigModification modToDelete in modsToDelete)
+                {
+                    chosenWebApp.WebConfigModifications.Remove(modToDelete);
+                }
+
+                chosenWebApp.Update(true);
+                chosenWebApp.WebService.ApplyWebConfigModifications();
+            }
+
+            if (configMode == ConfigMode.Install)
+            {
+                chosenWebApp.WebConfigModifications.Add(newModification);
+                chosenWebApp.Update(true);
+                chosenWebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
+            }
+        }
+
+        private static bool IsConfigModificationsEqual(SPWebConfigModification modification, SPWebConfigModification configModification)
+        {
+            if (modification.Name != configModification.Name)
+                return false;
+
+            if (modification.Owner != configModification.Owner)
+                return false;
+
+            if (modification.Path != configModification.Path)
+                return false;
+
+            if (modification.Sequence != configModification.Sequence)
+                return false;
+
+            if (modification.Type != configModification.Type)
+                return false;
+
+            if (modification.Value != configModification.Value)
+                return false;
+
+            return true;
+        }
+
+        private static SPWebConfigModification CreateModification(ModificationEntry modEntry)
+        {
+            SPWebConfigModification mod = new SPWebConfigModification(modEntry.Name, modEntry.Path);
+            mod.Owner = modEntry.Owner;
+            mod.Sequence = 0;
+            mod.Type = modEntry.ModType;
+            mod.Value = modEntry.Value;
+            return mod;
+        }
+
+        private static IEnumerable<ModificationEntry> GetConfigurationMods(string filename)
+        {
+            List<ModificationEntry> mods = new List<ModificationEntry>();
+
+            List<ModificationEntry> xmlMods = GetCurrentMods(filename);
+
+            if (xmlMods != null && xmlMods.Count > 0)
+            {
+                foreach (ModificationEntry xmlMod in xmlMods)
+                {
+                    mods.Add(xmlMod);
+                }
+            }
+
+            return mods;
+        }
+
+        private static XmlDocument GetFileDoc(string filename)
+        {
+            XmlDocument configEntryDocument = new XmlDocument();
+            configEntryDocument.PreserveWhitespace = true;
+            string path = SPUtility.GetGenericSetupPath("CONFIG") + "\\" + filename;
+            configEntryDocument.Load(path);
+
+            return configEntryDocument;
+        }
+
+        private static List<ModificationEntry> GetCurrentMods(string filename)
+        {
+
+            List<ModificationEntry> mods = new List<ModificationEntry>();
+            XmlDocument fileDoc = GetFileDoc(filename);
+            XmlNode installNode = fileDoc.SelectSingleNode("/ConfigEntries");
+            if (installNode != null && installNode.HasChildNodes && installNode.Attributes != null)
+            {
+                XmlAttribute ownerAttrib = installNode.Attributes["owner"];
+                String owner = ownerAttrib.Value;
+                mods.AddRange(installNode.ChildNodes.OfType<XmlElement>().Select(node => GetModFromNode(node, owner)));
+            }
+
+            return mods;
+        }
+
+        private static ModificationEntry GetModFromNode(XmlNode node, String owner)
+        {
+            ModificationEntry mod = new ModificationEntry();
+            mod.Owner = owner;
+
+            foreach (XmlNode childNode in node.ChildNodes)
+            {
+                if (childNode is XmlElement)
+                {
+                    XmlElement element = childNode as XmlElement;
+                    if (element.Name == "Name")
+                    {
+                        mod.Name = GetElementValue(element.InnerText);
+                        continue;
+                    }
+
+                    if (element.Name == "Path")
+                    {
+                        mod.Path = GetElementValue(element.InnerText);
+                        continue;
+                    }
+
+                    if (element.Name == "Value")
+                    {
+                        mod.Value = GetElementValue(element.InnerText);
+                        continue;
+                    }
+
+                    if (element.Name == "ModType")
+                    {
+                        mod.ModType = (SPWebConfigModification.SPWebConfigModificationType)Enum.Parse(typeof(SPWebConfigModification.SPWebConfigModificationType), GetElementValue(element.InnerText), true);
+                        continue;
+                    }
+                }
+            }
+
+            return mod;
+        }
+
+        private static string GetElementValue(string rawValue)
+        {
+            if (string.IsNullOrEmpty(rawValue))
+                return rawValue;
+
+            string elementValue = rawValue.Replace("<![CDATA[", "");
+            elementValue = elementValue.Replace("]]>", "");
+
+            return elementValue;
+        }
+
+        public enum ConfigMode
+        {
+            Install,
+            Remove
+        }
+
+        private struct ModificationEntry
+        {
+            public string Name;
+            public string Path;
+            public string Value;
+            public string Owner;
+            public SPWebConfigModification.SPWebConfigModificationType ModType;
+
+            public ModificationEntry(string name, string path, string value, string owner, SPWebConfigModification.SPWebConfigModificationType modType)
+            {
+                Name = name;
+                Path = path;
+                Value = value;
+                ModType = modType;
+                Owner = owner;
+            }
+        }
+    }
+}
Index: Features/MyLinksMenuStapler/MyLinksMenuStapler.feature
===================================================================
--- Features/MyLinksMenuStapler/MyLinksMenuStapler.feature	(revision 14325)
+++ Features/MyLinksMenuStapler/MyLinksMenuStapler.feature	(working copy)
@@ -1,5 +1,5 @@
 ?<!--?xml version="1.0" encoding="utf-8"?-->
-
+
   
     
   
Index: key.pfx
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: key.pfx
===================================================================
--- key.pfx	(revision 0)
+++ key.pfx	(working copy)

Property changes on: key.pfx
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: MyLinksMenu.csproj
===================================================================
--- MyLinksMenu.csproj	(revision 14325)
+++ MyLinksMenu.csproj	(working copy)
@@ -38,7 +38,7 @@
     true
   
   
-    key.snk
+    key.pfx
   
   
     
@@ -75,6 +75,9 @@
     
       MyLinks.ascx.cs
     
+    
+      MyLinksMenuStapler.feature
+    
     
       ManageMyLinks.aspx
       ASPXCodeBehind
@@ -94,6 +97,9 @@
     
   
   
+    
+      {1898ee8d-44b7-4754-841e-2c2096b66990}
+    
     
       {9f366dee-e4cc-4596-abbe-e4e4dafe1f86}
     
@@ -106,7 +112,7 @@
     
       {33e0d2bc-74f5-49d8-bc8c-27f46537c034}
     
-    
+    
     
       {c32c2704-3fb0-4db2-900c-2b4c66a7cf0d}
     
@@ -122,6 +128,7 @@
   
   
   
+    
     
       ASPXCodeBehind
     
Index: MyLinksMenu.sln
===================================================================
--- MyLinksMenu.sln	(revision 0)
+++ MyLinksMenu.sln	(working copy)
@@ -0,0 +1,26 @@
+?
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyLinksMenu", "MyLinksMenu.csproj", "{F5C22940-2F76-41C3-BBDC-70297833DE0C}"
+EndProject
+Global
+	GlobalSection(SubversionScc) = preSolution
+		Svn-Managed = True
+		Manager = AnkhSVN - Subversion Support for Visual Studio
+	EndGlobalSection
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F5C22940-2F76-41C3-BBDC-70297833DE0C}.Release|Any CPU.Deploy.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
Index: Package/Package.package
===================================================================
--- Package/Package.package	(revision 14325)
+++ Package/Package.package	(working copy)
@@ -7,5 +7,6 @@
   
     
     
+    
   
 
\ No newline at end of file
Apr 4, 2012 at 10:25 PM

My ignorance has led me astray somewhat, I found the submit a patch link there, I will put it up there so it can be better utilized.

Apr 4, 2012 at 10:33 PM

One thing I will say about this is that I re-used the stapling feature and added an event receiver to it.  I could break that out into it's own feature as well if that made more sense.

Coordinator
Apr 5, 2012 at 1:10 PM

I've been meaning to implement something like this for a while. I'll test out your code and try to roll it in to the release sometime soon. Thanks!