This morning I had a conversation with a colleague about Schemalogic. This tool enables you to manage your content types in a central location for your whole farm or even multiple farms.
Based on this concept I thought it might be cool to make a 'master' sitecollection with all site-columns and content-types and reflect those to all other site collections. Don't ask me why I don't use features or other concepts, we're currently evaluating the different possibilities..
This is my quick-and-dirty script to sync the Site Columns and Content Types between sites. It does te following:
1. Create Site Columns if they don't exist
2. Update Site Columns if the are different
3. Create Content Types if they don't exist
4. Add/Remove Site Columns to existing Content Types
5. Remove Site Columns if they are removed from the master site
A number of questions arise when building the script:
- How to handle lookup fields, since they link to a local list. We could ignore these fields or assume the list exists..
- It should remove Content Types if they are removed from the master site (not very complex, just forgot it in this version)
- It should iterate through all site collections instead of just one
Well, I'll continue evaluating the other possibilities, but we'll might go for this method. In meanwhile you are free to use the (beta)code I've written for this concept. Please feel free to comment your findings and suggestions!
static void Main(string[] args)
{
SPSite sourceRoot = new SPSite("http://wss3dev/sites/TEST2");
SPWeb sourceWeb = sourceRoot.OpenWeb();
SPSite targetRoot = new SPSite("http://wss3dev/sites/TEST3");
SPWeb targetWeb = targetRoot.OpenWeb();
SyncSiteColumns(sourceWeb, targetWeb);
SyncContentTypes(sourceWeb, targetWeb);
DeleteSiteColumns(sourceWeb, targetWeb);
}
/// <summary>
/// Syncs content types based on a sourceweb and a targetweb.
/// Add's Content Types if they don't exist.
/// Add's/Removes Fields in existing Content Types
/// </summary>
/// <param name="sourceWeb"></param>
/// <param name="targetWeb"></param>
private static void SyncContentTypes(SPWeb sourceWeb, SPWeb targetWeb)
{
foreach (SPContentType sourceType in sourceWeb.ContentTypes)
{
if (!sourceType.ReadOnly && !sourceType.Sealed && !sourceType.Hidden) // Exclude non-editable types
{
bool update = false;
if (targetWeb.ContentTypes[sourceType.Name] == null)
{
SPContentType parentContentType = targetWeb.ContentTypes[sourceType.Parent.Name];
SPContentTypeCollection collection = targetWeb.ContentTypes;
SPContentType newContentType = new SPContentType(parentContentType, collection, sourceType.Name);
targetWeb.ContentTypes.Add(newContentType);
newContentType = targetWeb.ContentTypes[sourceType.Name];
foreach (SPField field in sourceType.Fields)
{
if (newContentType.FieldLinks[field.InternalName] == null)
{
newContentType.FieldLinks.Add(new SPFieldLink(targetWeb.Fields[field.Title]));
}
}
newContentType.Update(false); // Do not update children, there are no children yet
}
else
{
SPContentType targetType = targetWeb.ContentTypes[sourceType.Name];
foreach (SPField sourceField in sourceType.Fields)
{
if (targetType.FieldLinks[targetWeb.Fields.GetFieldByInternalName(sourceField.InternalName).Id] == null)
{
targetType.FieldLinks.Add(new SPFieldLink(targetWeb.Fields.GetFieldByInternalName(sourceField.InternalName)));
update = true;
}
}
for (int i = (targetType.FieldLinks.Count - 1); i >= 0; i--)
{
if (sourceType.FieldLinks[targetType.FieldLinks[i ].Name] == null)
{
targetType.FieldLinks.Delete(targetType.FieldLinks[i ].Name);
update = true;
}
}
if (update)
targetType.Update(false);
}
}
}
}
/// <summary>
/// Syncs Sitecolumns based on a sourceweb and targetweb
/// Updates a sitecolumn if it is different.
/// Add's a sitecolumn if does not exist.
/// </summary>
/// <param name="sourceWeb"></param>
/// <param name="targetWeb"></param>
private static void SyncSiteColumns(SPWeb sourceWeb, SPWeb targetWeb)
{
foreach (SPField field in sourceWeb.Fields)
{
if (!targetWeb.Fields.ContainsField(field.Title))
{
targetWeb.Fields.AddFieldAsXml(field.SchemaXml);
}
else
{
SPField targetField = targetWeb.Fields.GetFieldByInternalName(field.InternalName);
if (targetField.SchemaXml != field.SchemaXml)
{
targetField.SchemaXml = field.SchemaXml;
targetField.Update(true);
}
}
}
targetWeb.Update();
}
/// <summary>
/// Removes sitecolumns from the target-site which do not exist in the source-site
/// </summary>
/// <param name="sourceWeb"></param>
/// <param name="targetWeb"></param>
private static void DeleteSiteColumns(SPWeb sourceWeb, SPWeb targetWeb)
{
for (int i = (targetWeb.Fields.Count - 1); i >= 0; i--)
{
if (!sourceWeb.Fields.ContainsField(targetWeb.Fields[ i].Title))
{
targetWeb.Fields.Delete(targetWeb.Fields[i ].Title);
}
}
}