MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Monday, February 18, 2008

Import Site Columns

In my last post I wrote about a command that I created to export site columns to an xml file because I needed the data for a Feature I was working on. But what if you just want to create a column using the exported results and for whatever reason you don't want to create a Feature? I figured it would take me about 2 minutes to throw together an import command that would take in the results of the export so may as well add it to the collection. So now you can use the gl-importsitecolumns command to import what you exported using gl-exportsitecolumns (useful if you're just trying to move some fields around though I'd still recommend you do this via a Feature). Note that this command will blow up if a column already exists with the same name. The code for this is really simple - just loop through the XML and call the SPFieldCollection's AddFieldAsXml method: 

   1:  string url = Params["url"].Value;
   2:   
   3:  XmlDocument xmlDoc = new XmlDocument();
   4:  xmlDoc.Load(Params["inputfile"].Value);
   5:   
   6:   
   7:  // Get the source content type and fields.
   8:  using (SPSite site = new SPSite(url))
   9:  using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(url)])
  10:  {
  11:      foreach (XmlElement fieldNode in xmlDoc.SelectNodes("//Field"))
  12:      {
  13:          web.Fields.AddFieldAsXml(fieldNode.OuterXml);
  14:      }
  15:  }
The syntax of the command can be seen below:
C:\>stsadm -help gl-importsitecolumns

stsadm -o gl-importsitecolumns

Imports one or more site fields (columns) to a site.

Parameters:
        -url <url>
        -inputfile <file to import fields from>
Here's an example of how to import site columns that were exported using the exportsitecolumns command:
stsadm -o gl-importsitecolumns -url "http://intranet" -inputfile c:\fields.xml

Sunday, February 17, 2008

Export Site Columns

As I mentioned in my previous post about the gl-exportcontenttypes command I need to be able to quickly and easily get the CAML necessary to recreate site columns in a Feature. To do this I created a quick and dirty command called gl-exportsitecolumns. The code for this is extremely simple - I just get the SPField objects of interest based on the parameters passed in and dump out the SchemaXml property - that's it:

   1:  string url = Params["url"].Value;
   2:  string fieldTitle = Params["fielddisplayname"].Value;
   3:  string fieldName = Params["fieldinternalname"].Value;
   4:  string groupName = Params["group"].Value;
   5:  bool useTitle = Params["fielddisplayname"].UserTypedIn;
   6:  bool useName = Params["fieldinternalname"].UserTypedIn;
   7:  bool useGroup = Params["group"].UserTypedIn;
   8:  bool all = !(useTitle || useName || useGroup);
   9:   
  10:  StringBuilder sb = new StringBuilder();
  11:  XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
  12:  xmlWriter.Formatting = Formatting.Indented;
  13:   
  14:  try
  15:  {
  16:   xmlWriter.WriteStartElement("Elements");
  17:   
  18:   // Get the source content type and fields.
  19:   using (SPSite site = new SPSite(url))
  20:   {
  21:    using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(url)])
  22:    {
  23:     SPFieldCollection fields = web.Fields;
  24:     foreach (SPField field in fields)
  25:     {
  26:      if (all ||
  27:       (useGroup && groupName.ToLowerInvariant() == field.Group.ToLowerInvariant()) ||
  28:       (useName && fieldName.ToLowerInvariant() == field.InternalName.ToLowerInvariant()) ||
  29:       (useTitle && fieldTitle.ToLowerInvariant() == field.Title.ToLowerInvariant()))
  30:      {
  31:       xmlWriter.WriteString("\r\n");
  32:       xmlWriter.WriteRaw(field.SchemaXml);
  33:      }
  34:     }
  35:    }
  36:   }
  37:   
  38:   xmlWriter.WriteString("\r\n");
  39:   xmlWriter.WriteEndElement(); // Elements
  40:  }
  41:  finally
  42:  {
  43:   xmlWriter.Flush();
  44:   xmlWriter.Close();
  45:  }
  46:  File.WriteAllText(Params["outputfile"].Value, sb.ToString());
The syntax of the command can be seen below:
C:\>stsadm -help gl-exportsitecolumns

stsadm -o gl-exportsitecolumns


Exports one or more site fields (columns) to a file.

Parameters:
        -url <url>
        {[-fielddisplayname <field display name> / -fieldinternalname <field internal name>]
         [-group <site column group name to filter results by>]}
        -outputfile <file to output field schema to>
Here's an example of how to dump the XML for all the site columns in a particular group:
stsadm -o gl-exportsitecolumns -url "http://intranet" -outputfile c:\fields.xml -group "Custom Site Columns"

Export Content Types

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.

   1: public 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:     }
 257: }
 258:  
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