This is the third part of a series of posts around using MSBuild for deployments of SharePoint applications, and in this post I introduce a custom MSBuild task to perform a custom Export of content from my Development environment.
Creating a custom MSBuild task to perform a controlled export from SharePoint
One of the key features of MCMS which was dropped in the MOSS platform was a tool to enable the export of the *code* part of a site. In MCMS, we could choose to export templates to an SDO, and import them into a destination website without affecting content. This gave us the ability to do full lifecycle management of our code, which is something that is hard (out of the box) with MOSS.So in order to replicate this kind of functionality, we could use a tool such as Chris O'Brien's excellent SP Content Deployment Wizard (http://www.codeplex.com/SPDeploymentWizard). This allows us to choose exactly which Export Objects we want to include in the export, and save that to (a cab) file. That's great for one off deployments, but I needed to have a way of scripting this so that I could automate it via TFS builds.
To do this, I created a class library which inherited from Microsoft.Build.Utilities.Task. I could have implemented the ITask interface, but I prefer directly inheriting from Task, since this gives me access to a Log object with no additional code.
The full code is at the end of this post, and I'm not going to step through it: it is mostly about setting up the SPExportSettings object and then executing it using SPExport.Run(). Rather than hard code these settings, you can take advantage of an inbuit XML schema in SharePoint: "deployment-exportsettings-schema", and provide an XML document with all of the settings you need. That way you can decouple your (generic) MSBuild task from the project you are working on at the current time.
In the example XML file (below), I have the Master Page Gallery and the Style Library as my two Deployment Objects, and when my custom task runs, I get an export CMP file with just these objects (and their dependencies and children – see the "IncludeDescendants" and "ExcludeChildren" flags in the configuration).
<?xml version="1.0" encoding="utf-8" ?>
<ExportSettings
xmlns="urn:deployment-exportsettings-schema"
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
SiteUrl="http://ben:20010" FileLocation="Export" BaseFileName="Export.cmp" IncludeSecurity="All"
IncludeVersions="CurrentVersion" ExportPublicSchema="true" ExportMethod="ExportAll" ExcludeDependencies="false" >
<ExportObjects>
<!– Master Pages –>
<DeploymentObject Id="323d4797-b5af-4271-92cd-eac348ebf7d5" ParentId="bab096c8-7bf6-435b-b541-4f2ecd8aace7"
Type="List" IncludeDescendants="All" ExcludeChildren="false" />
<!– Style Library –>
<DeploymentObject Id="d6635d08-ccb0-489e-9903-6dd07b7622b4" ParentId="bab096c8-7bf6-435b-b541-4f2ecd8aace7"
Type="List" IncludeDescendants="All" ExcludeChildren="false" />
</ExportObjects>
</ExportSettings>
These parameters are documented in MSDN here.
Importing this into a Build Server farm is simple too; I can simply call the “stsadm -o import” command on my build server – see Part 2 of this series for more information on remote execution of STSADM via WinRS.
This gives us a very extensible model. I can store the configuration file in a project in TFS, and then run my custom task as part of an automated build without any user interaction. My *.csproj file can have the following:
<UsingTask AssemblyFile="C:BenTFSSPCDemoBenRobb.MSBuildinReleaseBenRobb.MSBuild.dll" TaskName="BenRobb.MSBuild.SPExportTask" />
<Target Name="AfterBuild">
<SPExportTask ConfigurationFile="ExportSettings.xml" />
<Copy SourceFiles="@(ExportSourceFiles)" DestinationFolder="\mossbasicDeploymentsSPExportsDemo4" />
<Exec Command="C:WindowsSystem32winrs -r:mossbasic C:DeploymentsCommandsDemo4Demo4_Import.bat" />
</Target>
The <UsingTask> node imports my custom task, and then I can refer to the class I need directly by class name. You do need to be careful about naming clashes with out of the box tasks.
Hopefully this series of posts will have given people ideas about how to extend MSBuild to take care of common SharePoint development tasks, and give credence to the idea that Continuous Integration and streamlined deployment processes are possible when buidling applications for SharePoint.
Sample code:
using System;
using System.Xml;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using Microsoft.SharePoint.Deployment;
namespace BenRobb.MSBuild
{
public class SPExportTask : Task
{
private XmlDocument _settingsFile = new XmlDocument();
private string _configurationFile = string.Empty;
/// <summary>
/// The ConfigurationFile is the (project relative) path to the XML file
/// which sets up the boundaries of the SPExport. It is required for this
/// task to run.
/// </summary>
[Required]
public string ConfigurationFile
{
get { return _configurationFile; }
set { _configurationFile = value; }
}
/// <summary>
/// Execute() provides the entry point to the MSBuild task. This method calls
/// getExportSettings to configure the SPExport, and then runs the
/// export.
/// </summary>
/// <returns></returns>
public override bool Execute()
{
Log.LogMessage("Exporting from SharePoint site, using configuration file {0}", _configurationFile);
_settingsFile.Load(_configurationFile);
XmlElement xSettings = _settingsFile.DocumentElement;
if (xSettings != null)
{
SPExportSettings exportSettings = getExportSettings(xSettings);
using (SPExport export = new SPExport(exportSettings))
{
export.Run();
}
}
return true;
}
/// <summary>
/// getExportSettings goes through the supplied XML fragment and uses
/// this to manage the parameters which control the export from SharePoint.
///
/// It also calls addExportObjects to build up the object list which will
/// be exported.
/// </summary>
/// <param name="xSettings">The XML element for setting parameters for SPExport</param>
/// <returns>A populated SPExportSettings object</returns>
private SPExportSettings getExportSettings(XmlElement xSettings)
{
SPExportSettings exportSettings = new SPExportSettings();
exportSettings.SiteUrl = xSettings.Attributes["SiteUrl"].Value;
exportSettings.FileLocation =
xSettings.Attributes["FileLocation"].Value;
exportSettings.BaseFileName = xSettings.Attributes["BaseFileName"].Value;
exportSettings.FileCompression = true; exportSettings.ExportMethod = (SPExportMethodType)Enum.Parse(typeof(SPExportMethodType), xSettings.Attributes["ExportMethod"].Value, true);
exportSettings.ExcludeDependencies = Convert.ToBoolean(xSettings.Attributes["ExcludeDependencies"].Value);
exportSettings.IncludeSecurity = (SPIncludeSecurity)Enum.Parse(typeof(SPIncludeSecurity), xSettings.Attributes["IncludeSecurity"].Value, true);
exportSettings.IncludeVersions = (SPIncludeVersions)Enum.Parse(typeof(SPIncludeVersions), xSettings.Attributes["IncludeVersions"].Value, true);
exportSettings.TestRun = false;
SPExportObjectCollection eoc = exportSettings.ExportObjects;
addExportObjects(eoc);
exportSettings.Validate();
exportSettings.OverwriteExistingDataFile = true;
exportSettings.CommandLineVerbose = true;
return exportSettings;
}
/// <summary>
/// This method iterates through the ExportObject elements within the configuration XML
/// file and adds them to the supplied SPExportObjectCollection.
/// </summary>
/// <param name="eoc">The SPExportObjectCollection to add the SPExportObject
/// objects which are defined in the configuration XML file to.</param>
private void addExportObjects(SPExportObjectCollection eoc)
{
XmlNode xSettings = _settingsFile.DocumentElement.GetElementsByTagName("ExportObjects")[0];
foreach (XmlNode xExportObject in xSettings.SelectNodes("*"))
{
Guid guidID = new Guid(xExportObject.Attributes["Id"].Value);
SPDeploymentObjectType deploymentObjectType = (SPDeploymentObjectType)Enum.Parse(typeof(SPDeploymentObjectType), xExportObject.Attributes["Type"].Value);
Guid parentID = new Guid(xExportObject.Attributes["ParentId"].Value);
bool b_excludeChildren = Convert.ToBoolean(xExportObject.Attributes["ExcludeChildren"].Value);
SPExportObject exportObject = new SPExportObject();
exportObject.Id = guidID;
exportObject.ParentId = parentID;
exportObject.Type = deploymentObjectType;
exportObject.ExcludeChildren = b_excludeChildren;
exportObject.IncludeDescendants = (SPIncludeDescendants)Enum.Parse(typeof(SPIncludeDescendants), xExportObject.Attributes["IncludeDescendants"].Value);
eoc.Add(exportObject);
}
}
}
}