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