I was recently working on a client project where there was an environment already setup with several custom content types and custom site columns that had been created. We wanted to reverse engineer those content types and columns so that we could create a Feature that could be used to deploy them into the various environments. I poked around a bit and couldn’t find anything that would simply give me the CAML that I needed for my Feature. So I decided to create a couple of new commands to help me do this quickly: gl-exportcontenttypes and gl-exportsitecolumns.

I’ll cover gl-exportsitecolumns in a follow up post. Looking at the code you can see that there’s really not much going on. I simply grab the content type(s) to export and get the SchemaXml property. One thing of note is that the SchemaXml property returns back XML that contains the complete field definition – because this won’t work in a Feature I replace the <Field /> elements with a corresponding <FieldRef /> element. If the field definitions are needed then you can simply provide the includefielddefinitions parameter. Note that I’m outputting everything in one file but if you intend to use this in a Feature you’ll want to break this up into multiple files. For the field definitions I also just output the SchemaXml of the SPField objects associated with the content type (note that there will be some attributes that you’ll need to pull when using the resultant output in a Feature). To get the list bindings I loop through all the lists throughout the site collection and add a ContentTypeBindings element for each list that contains a reference to the identified content types.

  1public class ExportContentTypes : SPOperation
  2{
  3   private const string ENCODED_SPACE = "_x0020_";
  4   /// <summary>
  5   /// Initializes a new instance of the <see cref="CopyContentTypes"/> class.
  6   /// </summary>
  7   public ExportContentTypes()
  8   {
  9       SPParamCollection parameters = new SPParamCollection();
 10       parameters.Add(new SPParam("url", "url", true, null, new SPUrlValidator(), "Please specify the source site collection."));
 11       parameters.Add(new SPParam("name", "n", false, null, new SPNonEmptyValidator()));
 12       parameters.Add(new SPParam("group", "g", false, null, new SPNonEmptyValidator()));
 13       parameters.Add(new SPParam("listname", "list", false, null, new SPNonEmptyValidator()));
 14       parameters.Add(new SPParam("outputfile", "output", false, null, new SPDirectoryExistsAndValidFileNameValidator()));
 15       parameters.Add(new SPParam("includelistbindings", "ilb"));
 16       parameters.Add(new SPParam("includefielddefinitions", "ifd"));
 17       parameters.Add(new SPParam("excludeparentfields", "epf"));
 18       parameters.Add(new SPParam("removeencodedspaces", "res"));
 19
 20       StringBuilder sb = new StringBuilder();
 21       sb.Append("\r\n\r\nExports Content Types to an XML file.\r\n\r\nParameters:");
 22       sb.Append("\r\n\t-url <url containing the content types>");
 23       sb.Append("\r\n\t-outputfile <file to output results to>");
 24       sb.Append("\r\n\t[-name <name of an individual content type to export>]");
 25       sb.Append("\r\n\t[-group <content type group name to filter results by>]");
 26       sb.Append("\r\n\t[-listname <name of a list to export content types from>]");
 27       sb.Append("\r\n\t[-includelistbindings]");
 28       sb.Append("\r\n\t[-includefielddefinitions]");
 29       sb.Append("\r\n\t[-excludeparentfields]");
 30       sb.Append("\r\n\t[-removeencodedspaces (removes '_x0020_' in field names)]");
 31       Init(parameters, sb.ToString());
 32   }
 33
 34   #region ISPStsadmCommand Members
 35
 36   /// <summary>
 37   /// Gets the help message.
 38   /// </summary>
 39   /// <param name="command">The command.</param>
 40   /// <returns></returns>
 41   public override string GetHelpMessage(string command)
 42   {
 43       return HelpMessage;
 44   }
 45
 46   /// <summary>
 47   /// Runs the specified command.
 48   /// </summary>
 49   /// <param name="command">The command.</param>
 50   /// <param name="keyValues">The key values.</param>
 51   /// <param name="output">The output.</param>
 52   /// <returns></returns>
 53   public override int Run(string command, StringDictionary keyValues, out string output)
 54   {
 55       output = string.Empty;
 56
 57       InitParameters(keyValues);
 58
 59       string url = Params["url"].Value.TrimEnd('/');
 60       string contentTypeName = null;
 61       if (Params["name"].UserTypedIn)
 62           contentTypeName = Params["name"].Value;
 63       string contentTypeGroup = null;
 64       if (Params["group"].UserTypedIn)
 65           contentTypeGroup = Params["group"].Value.ToLowerInvariant();
 66       if (Params["group"].UserTypedIn && Params["listname"].UserTypedIn)
 67           throw new SPSyntaxException("The parameters group and listname are incompatible");
 68       bool excludeParentFields = Params["excludeparentfields"].UserTypedIn;
 69       bool removeEncodedSpaces = Params["removeencodedspaces"].UserTypedIn;
 70
 71       StringBuilder sb = new StringBuilder();
 72       XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
 73       xmlWriter.Formatting = Formatting.Indented;
 74
 75       Dictionary<Guid, SPField> ctFields = new Dictionary<Guid, SPField>();
 76
 77       using (SPSite site = new SPSite(url))
 78       {
 79           using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(url)])
 80           {
 81               SPContentTypeCollection availableContentTypes;
 82
 83               if (Params["listname"].UserTypedIn)
 84               {
 85                   SPList list = web.Lists[Params["listname"].Value];
 86                   availableContentTypes = list.ContentTypes;
 87               }
 88               else
 89               {
 90                   availableContentTypes = web.AvailableContentTypes;
 91               }
 92               List<SPContentType> contentTypes = new List<SPContentType>();
 93               try
 94               {
 95                   xmlWriter.WriteStartElement("Elements");
 96                   xmlWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/sharepoint/");
 97
 98                   // Gather up all the content types we want to export out.
 99                   if (contentTypeName != null)
100                   {
101                       SPContentType ct = availableContentTypes[contentTypeName];
102                       if (ct == null)
103                       {
104                           output += "The content type specified could not be found.";
105                           return 0;
106                       }
107                       else
108                       {
109                           contentTypes.Add(ct);
110                       }
111                   }
112                   else
113                   {
114                       // Loop through all the source content types and create them at the target.
115                       foreach (SPContentType ct in availableContentTypes)
116                       {
117                           if (contentTypeGroup == null || ct.Group.ToLowerInvariant() == contentTypeGroup)
118                           {
119                               contentTypes.Add(ct);
120                           }
121                       }
122                   }
123
124                   if (Params["includefielddefinitions"].UserTypedIn)
125                   {
126                       // If we're including field definitions then we want to show them first as they'll need to appear first when using within a Feature
127                       foreach (SPContentType ct in contentTypes)
128                       {
129                           SPContentType parentCT = ct.Parent;
130                           foreach (SPField field in ct.Fields)
131                           {
132                               // If the parent content type contains the current field and the user wants to exclude parent fields then continue to the next field.
133                               if (parentCT != null && excludeParentFields && parentCT.Fields.ContainsField(field.InternalName))
134                                   continue;
135
136                               if (!ctFields.ContainsKey(field.Id))
137                                   ctFields.Add(field.Id, field);
138                           }
139                       }
140                       xmlWriter.WriteString("\r\n\r\n");
141                       foreach (SPField field in ctFields.Values)
142                       {
143                           string schema = field.SchemaXml;
144                           if (field.InternalName.Contains(ENCODED_SPACE) && removeEncodedSpaces)
145                           {
146                               schema = schema.Replace(string.Format("Name=\"{0}\"", field.InternalName),
147                                                       string.Format("Name=\"{0}\"", field.InternalName.Replace(ENCODED_SPACE, string.Empty)));
148                           }
149
150                           xmlWriter.WriteRaw(schema);
151                           xmlWriter.WriteString("\r\n");
152                       }
153                   }
154
155                   xmlWriter.WriteString("\r\n");
156
157                   foreach (SPContentType ct in contentTypes)
158                   {
159                       WriteContentTypeXml(ct, excludeParentFields, removeEncodedSpaces, xmlWriter);
160                   }
161
162                   if (Params["includelistbindings"].UserTypedIn)
163                       GetListBindings(web, contentTypes, xmlWriter);
164
165                   xmlWriter.WriteEndElement(); // Elements
166               }
167               finally
168               {
169                   xmlWriter.Flush();
170                   xmlWriter.Close();
171               }
172           }
173       }
174
175       File.WriteAllText(Params["outputfile"].Value, sb.ToString());
176       return 1;
177   }
178
179
180   #endregion
181
182
183   /// <summary>
184   /// Writes the content type XML.
185   /// </summary>
186   /// <param name="ct">The ct.</param>
187   /// <param name="excludeParentFields">if set to <c>true</c> [exclude parent fields].</param>
188   /// <param name="removeEncodedSpaces">if set to <c>true</c> [remove encoded spaces].</param>
189   /// <param name="xmlWriter">The XML writer.</param>
190   private static void WriteContentTypeXml(SPContentType ct, bool excludeParentFields, bool removeEncodedSpaces, XmlTextWriter xmlWriter)
191   {
192       SPContentType parentCT = ct.Parent;
193       XmlDocument xmlDoc = new XmlDocument();
194       xmlDoc.LoadXml(ct.SchemaXml);
195       XmlElement fieldRefsElement = xmlDoc.CreateElement("FieldRefs");
196       xmlDoc.DocumentElement.AppendChild(fieldRefsElement);
197       foreach (XmlElement field in xmlDoc.SelectNodes("//Fields/Field"))
198       {
199           // If the parent content type contains the current field and the user wants to exclude parent fields then continue to the next field.
200           if (parentCT != null && excludeParentFields && parentCT.Fields.ContainsField(field.GetAttribute("Name")))
201               continue;
202
203           XmlElement fieldRefElement = xmlDoc.CreateElement("FieldRef");
204           fieldRefElement.SetAttribute("ID", field.GetAttribute("ID"));
205
206           string name = field.GetAttribute("Name");
207           if (name.Contains(ENCODED_SPACE) && removeEncodedSpaces)
208               name = name.Replace(ENCODED_SPACE, string.Empty);
209
210           fieldRefElement.SetAttribute("Name", name);
211           fieldRefsElement.AppendChild(fieldRefElement);
212       }
213       xmlDoc.DocumentElement.RemoveChild(xmlDoc.SelectSingleNode("//Fields"));
214
215       xmlWriter.WriteString("\r\n");
216       xmlWriter.WriteRaw(xmlDoc.OuterXml);
217   }
218
219   /// <summary>
220   /// Gets the list bindings.
221   /// </summary>
222   /// <param name="web">The web.</param>
223   /// <param name="contentTypes">The content types.</param>
224   /// <param name="xmlWriter">The XML writer.</param>
225   private static void GetListBindings(SPWeb web, List<SPContentType> contentTypes, XmlTextWriter xmlWriter)
226   {
227       foreach (SPList list in web.Lists)
228       {
229           foreach (SPContentType listCT in list.ContentTypes)
230           {
231               foreach (SPContentType ct in contentTypes)
232               {
233                   //if ((listCT.Scope != ct.Scope && listCT.Parent.Id == ct.Id) || listCT.Id == ct.Id)
234                   if (listCT.Name == ct.Name && listCT.Group == ct.Group)
235                   {
236                       xmlWriter.WriteStartElement("ContentTypeBinding");
237                       xmlWriter.WriteAttributeString("ContentTypeId", ct.Id.ToString());
238                       xmlWriter.WriteAttributeString("ListUrl", list.RootFolder.ServerRelativeUrl);
239                       xmlWriter.WriteEndElement(); // ContentTypeBinding
240                   }
241               }
242           }
243       }
244
245       foreach (SPWeb subWeb in web.Webs)
246       {
247           try
248           {
249               GetListBindings(subWeb, contentTypes, xmlWriter);
250           }
251           finally
252           {
253               subWeb.Dispose();
254           }
255       }
256   }
257257: }

The syntax of the command can be seen below:

C:\>stsadm -help gl-exportcontenttypes

stsadm -o gl-exportcontenttypes

Exports Content Types to an XML file.

Parameters:
        -url <url containing the content types>
        -outputfile <file to output results to>
        [-name <name of an individual content type to export>]
        [-group <content type group name to filter results by>]
        [-listname <name of a list to export content types from>]
        [-includelistbindings]
        [-includefielddefinitions]
        [-excludeparentfields]
        [-removeencodedspaces (removes '_x0020_' in field names)]

Here’s an example of how to dump the XMl for all the content types in a particular group:

stsadm -o gl-exportcontenttypes -url "http://intranet" -outputfile c:\contentTypes.xml -group "Custom Content Types" -includelistbindings -includefielddefinitions