Archive

Archive for March, 2014

SharePoint: How to add a new SPGroup through the code

March 9th, 2014 No comments

    The below listed CreateSpGroup methods along with a few subsidiary ones could be used to add a new SPGroup to a SPWeb

public static SPGroup CreateSpGroup(SPWeb spWeb, string groupName, 
                                    string description, bool isAssociated)
{
  SPGroup res = GetGroupByName(spWeb, groupName);
  if (res == null)
  {
    spWeb.SiteGroups.Add(groupName, spWeb.Site.SystemAccount, null, description);
    res = spWeb.SiteGroups[groupName];

    if (isAssociated)
    {
      spWeb.AssociatedGroups.Add(res);

      const string createdGroupsProperty = "vti_createdassociategroups";
      var createdAssociatedGroupsStr = spWeb.AllProperties[createdGroupsProperty] as string;
      if (!string.IsNullOrEmpty(createdAssociatedGroupsStr))
      {
        var createdAssociatedGroups = ParseAssociateGroupsFromString(createdAssociatedGroupsStr);
        if (!createdAssociatedGroups.Contains(res.ID))
        {
          createdAssociatedGroups.Add(res.ID);
          spWeb.AllProperties[createdGroupsProperty] = 
                               ConvertAssociateGroupsToString(createdAssociatedGroups);
        }
      }
      spWeb.Update();
    }
  }
  return res;
}

public static SPGroup CreateSpGroup(SPWeb spWeb, string groupName)
{
	return CreateSpGroup(spWeb, groupName, string.Empty, false);
}

public static List<int> ParseAssociateGroupsFromString(string str)
{
  var list = new List<int>();
  if (!string.IsNullOrEmpty(str))
    foreach (string str2 in str.Split(new[] { ';' }))
    {
        int num2;
        if ((!string.IsNullOrEmpty(str2) && 
            int.TryParse(str2, NumberStyles.Integer, CultureInfo.InvariantCulture, out num2)) && 
                    ((num2 > 0) && !list.Contains(num2)))
          list.Add(num2);
    }
  return list;
}

public static string ConvertAssociateGroupsToString(List<int> groupIds)
{
	var builder = new StringBuilder();
	for (int i = 0; i < groupIds.Count; i++)
	{
		if (builder.Length > 0)
			builder.Append(';');
		builder.Append(groupIds[i].ToString(CultureInfo.InvariantCulture));
	}
	return builder.ToString();
}

public static SPGroup GetGroupByName(SPWeb spWeb, string groupName)
{
	foreach (SPGroup group in spWeb.SiteGroups)
		if (groupName.Equals(group.Name, StringComparison.OrdinalIgnoreCase))
			return group;
	return null;
}

At first the CreateSpGroup method makes sure whether the target group doesn’t exist by calling the GetGroupByName. If such group has been found, it’s returned, otherwise a new empty group is being created. The SPWeb.Site.SystemAccount aka Site Collection administrator will be the owner of the new group.

The isAssociated flag indicates if the group has to be associated with the site. Let’s linger on associated groups a bit longer.

When a site (or sub-site with unique permissions) is created, three default groups associated with the site become available. Their names usually are “YourSiteName Owners”, “YourSiteName Members” and “YourSiteName Visitors”, where the YourSiteName is the name you gave to your site. You can see them in the Groups section at Quick Launch area when going to the “People and Groups” page (Site Actions -> Site Settings -> People and groups). Through the code those groups are accessible as SPWeb.AssociatedOwnerGroup, SPWeb.AssociatedMemberGroup and SPWeb.AssociatedVisitorGroup respectively. We also can get their IDs from the AllProperties collection of a SPWeb instance like SPWeb.AllProperties[“vti_associateownergroup”], SPWeb.AllProperties[“vti_associatemembergroup”] and SPWeb.AllProperties[“vti_associatevisitorgroup”] respectively. We are able to associate our own custom groups with the site by adding them to the SPWeb.AssociatedGroups collection, so that our groups would appear in the Quick Launch at the “People and Groups” page. So, that’s the first thing the CreateSpGroup does in case the isAssociated is set to true.

One more often-mentioned feature of associated groups is that they allegedly can be deleted automatically when the site/sub-site they are associated with is being deleted. Here, however, there is a pitfall: all groups are SiteCollection-level objects, therefore removing the site/sub-site, the groups (including associated ones) are still available as they are linked to the site collection. SharePoint, however, treats the associated default groups in a special way, namely the groups specified in the SPWeb.AssociatedOwnerGroup, SPWeb.AssociatedMemberGroup and SPWeb.AssociatedVisitorGroup will be actually deleted with the site/sub-site jointly. As regards custom associated groups, in the general case, they will never be automatically deleted with the site. So, don’t count on it. If, however, you are still eager to make your custom group deletable by default, you don’t have any choice but to override one of the associated default groups with the custom one. That’s not a complete solution though, you also have to add the id of the custom group to the spWeb.AllProperties[“vti_createdassociategroups”]. I came to this, having reviewed such methods as Microsoft.SharePoint.Utilities.SPUtilityInternal.CreateDefaultSharePointGroups and Microsoft.SharePoint.ApplicationPages.DeleteWebPage.OnLoad by means of .Net Reflector. So, in case the isAssociated is set to true, the second thing the CreateSpGroup does is adding group’s id to the “vti_createdassociategroups” property. After that the only step you have to do to make your custom group deletable is something like this

spWeb.AssociatedVisitorGroup = CreateSpGroup(spWeb, "my own custom group", string.Empty, true);
//spWeb.AssociatedMemberGroup  = CreateSpGroup(spWeb, "my own custom group", string.Empty, true);
//spWeb.AssociatedOwnerGroup   = CreateSpGroup(spWeb, "my own custom group", string.Empty, true);

Note that, doing that, the overridden default group still remains and is linked to the Site Collection. In case it is not needed any more, remove it. So, before overriding the default group, you’d better save the reference to it and then, after overriding, remove it by calling SPWeb.SiteGroups.Remove.

Nevertheless I suggest you not override associated default groups as you are always able to customize them the way you need instead: change name and description, add users you want and so on.

One more important thing to be aware of is, deleting a site/sub-site, you likely just move it to the Site Collection Recycle Bin (unless the Recycle Bin is off). That means the associated default groups will not be deleted right away, they will be deleted only at that moment when the site is being erased from the Recycle Bin. Unlike the site they are associated with, the groups will not be moved to the Recycle Bin. Moreover SharePoint doesn’t even mark them as “being deleted”. Thus during the Recycle Bin retention period (30 days by default) the associated default groups, being the SiteCollection-level objects, remain available and can be used by someone else. So, somebody who rashly uses the default groups of the “deleted” site may be unpleasantly surprised some day as the groups suddenly disappear. To avoid this, I personally (of course if I have no doubts that the site/sub-site has to vanish completely) erase it from Recycle Bin right away or ask Site Collection administrators to do this as soon as possible.

Let’s go back to the rest of the methods listed above. The ParseAssociateGroupsFromString method parses the value of vti_createdassociategroups into an array of groups’ IDs. The ConvertAssociateGroupsToString, in turn, makes inverse operation and turns the array into a string to save it in the vti_createdassociategroups property. The format of the vti_createdassociategroups is very simple, it’s a set of groups’ IDs separated with semicolons like 1;2;3. Nothing is easier than to implement parse of such string. I, however, decided to use the methods found in SharePoint‘s interior. So, ParseAssociateGroupsFromString and ConvertAssociateGroupsToString are internal methods defined in the Microsoft.SharePoint.ApplicationPages.PeopleGroupPageBase and captured by .Net Reflector.

The GetGroupByName is intended to look for a group with a specified name and used to check if the group about to be created exists.

As I said before, the groups are SiteCollection-level objects, so groups have to be created under privileged account at least under Site Collection administrator. So, the CreateSpGroup could be called as shown below:

// use of system account
string siteUrl = "some site url";
SPUserToken spUserToken = GetSystemToken(siteUrl);
using (SPSite spSite = new SPSite(siteUrl, spUserToken))
{
	using (SPWeb spWeb = spSite.OpenWeb())
	{
		SPGroup newGroup = CreateSpGroup(spWeb, "new group", string.Empty, true);
		// do something with the new group
	}
}

*Note: the GetSystemToken method is described here – SharePoint: How to get SystemAccount token.

or

// use of application pool identity
SPSecurity.RunWithElevatedPrivileges(delegate
{
    using (SPSite spSite = new SPSite("some site url"))
        using (SPWeb spWeb = spSite.OpenWeb())
        {
            SPGroup newGroup = CreateSpGroup(spWeb, "new group", string.Empty, true);
            // do something with the new group
        }
});