Tuesday, April 27, 2010

A SharePoint Feature To Configurate Content Deployment Timout Setting

The default timeout setting in SharePoint 2007 content deployment is 10 minutes. For content deployment on a big site, this may not be long enough, and you may get a timeout exception during the content deployment. The bad news is that you can't change the content deployment timeout setting with stsadm.exe, and you must run custom code to talk with SharePoint API to update this setting. Stefan Gossner provides a console application for such task. But console application is so flexible to reality. Not many admins like the idea of running a console application to update a setting for SharePoint in production environment.

It would be good if we can make the configuration change by browser, idealy a page linked to content deployment section in central administration web site. In this practice I will wrap all that as a SharePoint Feature.

First we need to define the feature.xml:
<?xml version="1.0" encoding="utf-8"?>
<Feature Id="02ca3716-5938-4788-ad67-17e6039f93da"
Title="Content Deployment Timeout Configuration"
Description="Timeout setting for content deployment"
Version="1.0.0.0"
Hidden="FALSE"
Scope="Farm"
DefaultResourceFile="core"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="elements.xml"/>
</ElementManifests>
</Feature>
In order to add a link in Content Deployment section in SharePoint central admin, the Feature needs to include a custom action. The elements.xml is as following:
<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Id="Content.Deployment"
GroupId="ContentDeployment"
Location="Microsoft.SharePoint.Administration.Operations"
Sequence="510"
Title="Content deployment timeout setting">
<UrlAction Url="/_admin/CDTimeoutSetting.aspx" />
</CustomAction>
</Elements>
The UrlAction is pointing to the /_admin/CDTimeoutSetting.aspx page which includes a simple UI for timeout update:
<%@ Page Language="C#" MasterPageFile="~/_admin/admin.master" %>

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages.Administration, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Assembly Name="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>

<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="System.Web.UI" %>
<%@ Import Namespace="Microsoft.SharePoint.Publishing.Administration" %>

<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea">
Manage Content Deployment Remote Timeout Setting
</asp:Content>
<asp:Content ID="Content4" runat="server" ContentPlaceHolderID="PlaceHolderMain">
<div style="margin: 15" runat="server" id="divMain">
<div>
<p>
<asp:Label ID="lblInfo" runat="server"></asp:Label>
<asp:Label ID="lblCurrentTimeout" runat="server" Font-Bold="true"></asp:Label>
</p>
<br />
<p>
<asp:Label ID="Label2" runat="server">Set timeout in minutes</asp:Label>
<asp:TextBox ID="txtTimeout" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" Text="* Required" ErrorMessage="Required" ControlToValidate="txtTimeout" Display="Dynamic" ValidationGroup="update"></asp:RequiredFieldValidator>
<asp:RangeValidator ID="RangeValidator1" runat="server" ErrorMessage="Invalid (1-240)" ControlToValidate="txtTimeout" Display="Dynamic" Type="Integer" MinimumValue="1" MaximumValue="7200" ValidationGroup="update"></asp:RangeValidator>
<asp:Button ID="btnUpdate" runat="server" Text="Update" ValidationGroup="update" OnClick="btnUpdate_Click" />
</p>
</div>
</div>
</asp:Content>
<script runat="server">
ContentDeploymentConfiguration config = null;

void btnUpdate_Click(object sender, EventArgs e)
{
if (config == null)
{
config = ContentDeploymentConfiguration.GetInstance();
}
Microsoft.SharePoint.Administration.SPWebApplication wa = SPContext.Current.Site.WebApplication;
wa.FormDigestSettings.Enabled = false;
SPContext.Current.Web.AllowUnsafeUpdates = true;
config.RemoteTimeout = Convert.ToInt32(txtTimeout.Text) * 60;
config.Update();
SPContext.Current.Web.AllowUnsafeUpdates = false;
}

void Page_PreRender(object sender, System.EventArgs e)
{
if (SPContext.Current == null || SPContext.Current.Site == null || SPContext.Current.Web == null)
{
lblInfo.Text = "Invalid SPContext. Please sign in and try again.";
return;
}
if (config == null)
{
config = ContentDeploymentConfiguration.GetInstance();
}
lblCurrentTimeout.Text = string.Format("Current timeout setting is: {0} minutes", Convert.ToString(config.RemoteTimeout / 60));
}
</script>
The manifext.xml for packaking the Feature to a wsp solution package:
<?xml version="1.0"?>
<Solution SolutionId="04a13d4d-21e2-4e72-a65c-163b16e8bbc8" xmlns="http://schemas.microsoft.com/sharepoint/">
<FeatureManifests>
<FeatureManifest Location="ContentDeploymentTimeoutSetter\feature.xml" />
</FeatureManifests>
<TemplateFiles>
<TemplateFile Location="ADMIN\CDTimeoutSetting.aspx" />
</TemplateFiles>
</Solution>
The screen-shot of the central admin configuration page:


The screen-shot of timeout setting page:

Friday, April 23, 2010

SharePoint Feature List Page

In some cases, once the site is created and properly configured, system admins don't want end users, not even site collection administrators, to touch the SharePoint Features for security and other reasons. We built a custom SharePoint solution with custom site definition. System admin requested to set all our custom Features to be hidden so that those Features won't be able to be activated or deactivated by users. Admins themselves like to use stsadm or some other tools to control those features.

All set and everything is okay, except you can not see those Feature status in site settings' pages. So I created a Feature List application page specific for this:



Code:
<%@ Page Language="C#" Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" %>

<%@ Assembly Name="System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" %>
<%@ Assembly Name="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>

<%@ Import Namespace="System.Web.UI" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Collections.Specialized" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="Microsoft.SharePoint.Administration" %>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>SharePoint Feature List</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-language" content="en">

<script runat="server">
public class FeatureItem
{
private string _name, _scope, _id;
private bool _hidden;
public string Name { get { return _name; } set { _name = value; } }
public string Scope { get { return _scope; } set { _scope = value; } }
public string ID { get { return _id; } set { _id = value; } }
public bool Hidden { get { return _hidden; } set { _hidden = value; } }
public FeatureItem(string name, string scope, string id, bool hidden)
{
Name = name;
Scope = scope;
ID = id;
Hidden = hidden;
}
}

protected override bool AllowNullWeb { get { return false; } }
protected override bool RequireSiteAdministrator { get { return false; } }

void Page_Load(object sender, System.EventArgs e)
{
if (SPContext.Current == null || SPContext.Current.Site == null || SPContext.Current.Web == null)
{
lblInfo.Text = "Invalid SPContext. Please sign in and try again.";
return;
}

PopulateFeatures();
}

void PopulateFeatures()
{
List<FeatureItem> featureList = new List<FeatureItem>();
string scope = ddlScope.SelectedValue;

Dictionary<string, SPFeatureCollection> activeFeatures = new Dictionary<string, SPFeatureCollection>();
activeFeatures.Add(SPFeatureScope.Farm.ToString(), Microsoft.SharePoint.Administration.SPWebService.ContentService.Features);
activeFeatures.Add(SPFeatureScope.WebApplication.ToString(), SPContext.Current.Site.WebApplication.Features);
activeFeatures.Add(SPFeatureScope.Site.ToString(), SPContext.Current.SiteFeatures);
activeFeatures.Add(SPFeatureScope.Web.ToString(), SPContext.Current.WebFeatures);

if (ddlActive.SelectedValue == "Active")
{
if (scope == "All")
{
foreach (SPFeatureCollection features in activeFeatures.Values)
{
foreach (SPFeature item in features)
{
try
{
string name = string.IsNullOrEmpty(item.Definition.DisplayName) ? item.Definition.Id.ToString() : item.Definition.DisplayName;
FeatureItem fi = new FeatureItem(name,
item.Definition.Scope.ToString(), item.Definition.Id.ToString(), item.Definition.Hidden);
featureList.Add(fi);
}
catch (Exception ex)
{
string name = string.IsNullOrEmpty(item.Definition.DisplayName) ? item.Definition.Id.ToString() : item.Definition.DisplayName;
FeatureItem fi = new FeatureItem(ex.Message, string.Empty, string.Empty, false);
featureList.Add(fi);
}

}
}
}
else
{
SPFeatureCollection features = activeFeatures[scope];
foreach (SPFeature item in features)
{
try
{
string name = string.IsNullOrEmpty(item.Definition.DisplayName) ? item.Definition.Id.ToString() : item.Definition.DisplayName;
FeatureItem fi = new FeatureItem(name,
item.Definition.Scope.ToString(), item.Definition.Id.ToString(), item.Definition.Hidden);
featureList.Add(fi);
}
catch (Exception ex)
{
string name = string.IsNullOrEmpty(item.Definition.DisplayName) ? item.Definition.Id.ToString() : item.Definition.DisplayName;
FeatureItem fi = new FeatureItem(ex.Message, string.Empty, string.Empty, false);
featureList.Add(fi);
}
}
}
}
else //Inavtive
{
foreach (SPFeatureDefinition definition in SPFarm.Local.FeatureDefinitions)
{
Guid featureID = Guid.NewGuid();
string name = string.Empty;
try
{
featureID = definition.Id;
name = string.IsNullOrEmpty(definition.DisplayName) ? featureID.ToString() : definition.DisplayName;
bool isActive = false;
if (activeFeatures[definition.Scope.ToString()] != null)
isActive = (activeFeatures[definition.Scope.ToString()][featureID] != null);

if (!isActive && (scope == "All" || definition.Scope.ToString() == scope))
{
FeatureItem fi = new FeatureItem(name, definition.Scope.ToString(), definition.Id.ToString(), definition.Hidden);
featureList.Add(fi);
}
}
catch (Exception e)
{
FeatureItem fi = new FeatureItem(name + " : " + e.Message, "Invalid", featureID.ToString(), true);
featureList.Add(fi);
}
}
}

featureList.Sort(delegate(FeatureItem item1, FeatureItem item2)
{
return item1.Name.CompareTo(item2.Name);
});
lblTotal.Text = string.Format("Total count: {0}", featureList.Count);

gvFeature.DataSource = featureList;
gvFeature.DataBind();
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div style="margin: 15">
<p>
<asp:Label ID="Label2" runat="server" Text="Feature Status:"></asp:Label>
<asp:DropDownList ID="ddlActive" runat="server" AutoPostBack="true">
<asp:ListItem Selected="True">Active</asp:ListItem>
<asp:ListItem>Inactive</asp:ListItem>
</asp:DropDownList>&nbsp;
<asp:Label ID="Label3" runat="server" Text="Feature Scope:"></asp:Label>
<asp:DropDownList ID="ddlScope" runat="server" AutoPostBack="true">
<asp:ListItem>All</asp:ListItem>
<asp:ListItem>Farm</asp:ListItem>
<asp:ListItem>WebApplication</asp:ListItem>
<asp:ListItem>Site</asp:ListItem>
<asp:ListItem Selected="True">Web</asp:ListItem>
</asp:DropDownList>&nbsp;
<asp:Label ID="lblTotal" runat="server"></asp:Label>
</p>
<p>
<asp:Label ID="lblInfo" runat="server"></asp:Label>
<asp:GridView ID="gvFeature" runat="server" AutoGenerateColumns="False" BorderWidth="1px"
BackColor="White" CellPadding="4" BorderStyle="Solid" BorderColor="#3366CC"
Font-Size="Small" AlternatingRowStyle-ForeColor="ActiveCaption">
<HeaderStyle ForeColor="White" BackColor="#003399" HorizontalAlign="Left">
</HeaderStyle>
<Columns>
<asp:BoundField HeaderText="Feature Name" DataField="Name" ReadOnly="true" ItemStyle-Width="380px">
</asp:BoundField>
<asp:BoundField HeaderText="Scope" DataField="Scope" ReadOnly="true" ItemStyle-Width="120px">
</asp:BoundField>
<asp:BoundField HeaderText="Hidden" DataField="Hidden" ReadOnly="true" ItemStyle-Width="120px">
</asp:BoundField>
<asp:BoundField HeaderText="FeatureID" DataField="ID" ReadOnly="true" ItemStyle-Font-Size="X-Small" ItemStyle-Width="240px">
</asp:BoundField>
</Columns>
<EmptyDataTemplate>
<asp:Label ID="lblEmptyMessage" runat="server" Text="No feature found."></asp:Label>
</EmptyDataTemplate>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>

Saturday, April 10, 2010

SharePoint Content Deployment And Modified Field

I have talked about SharePoint Modified field in here and here. What about content deployment?

After content deployment (full or incremental), the Modified time of a list item in the target site will be the same as the last published time of the original list item. For example, sourceSite.list1.item1 has two latest versions:
Modified: 2010-01-01 11:00AM (version 5.2)
Modified: 2010-01-01 10:00AM (version 5.0)
Suppose a full content deployment is conducted at 2010-02-02, then the targetSite.list1.item1 will have one version:
Modified: 2010-01-01 10:00AM (version 1.0)
Note that the versions in target site are not the same as original's. It always starts from version 1.0, and increases to 2.0, 3.0...