SharePoint Internals – Hristo Pavlov’s Blog

17 July, 2008

Managing ‘Managed Paths’ through code

Filed under: SharePoint — Tags: , — hristopavlov @ 3:24 am

Today I needed to check through code whether a managed path is defined and couldn’t find any sample code or information on the net so I decided to investigate this myself.

I starting from the place where I know managed paths are shown and this is the “Defined Managed Paths” page in the Central Administration. The page is actually _admin/scprefix.aspx. I located this page in the 12 hive under 12\TEMPLATE\ADMIN and opened it up in Notepad to see which was the code behind class. It was Microsoft.SharePoint.ApplicationPages.SscPrefixPage:

<%@ Assembly Name=”Microsoft.SharePoint.ApplicationPages.Administration, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”%>

<%@ Page Language=”C#” Inherits=”Microsoft.SharePoint.ApplicationPages.SscPrefixPage” MasterPageFile=”~/_admin/admin.master”      %>

I then located and opened the Microsoft.SharePoint.ApplicationPages.Administration assembly in Reflector and found the code behind class. After examining it for a while I found that there is a public class available that represents the collection of all managed paths inside a web application and this class is Microsoft.SharePoint.Administration.SPPrefixCollection. Then I noticed that this class has a constructor that accepts an SPWebApplication and initializes the managed paths for this web application. Unfortunately the constructor was with internal visibility. I almost started writing reflection code to call it but first decided to check the “Analyze” option in Reflector and see if any other classes are calling this constructor. Then I discovered that (logically) the SPWebApplication calls it and even has a member that represents the managed paths. This member is called Prefixes. Looking at the methods of the SPPrefixCollection class I found that checking for existing manages paths, adding new ones or deleting existing ones is supported. This is exactly what I was looking for.

// Adds the specified prefix to the collection.

public SPPrefix Add(string strPrefix, SPPrefixType type);

 

// Returns a Boolean value that indicates whether the collection contains the specified prefix.

public bool Contains(string strPrefix);

 

// Deletes the specified prefix from the collection.

public void Delete(string strPrefix);

 

// Deletes the specified array of prefixes from the collection.

public void Delete(string[] strPrefixes);

Now I can go and finish my code and start packing up for my 2 weeks holiday. Oh Yeah!

15 July, 2008

Specifying Properties when Activating Features through Code

The SharePoint object model allows features to be activated and deactivated using code but doesn’t allow you to specify the feature properties. Passing feature properties could be very useful to configure your feature receiver to run in a specific way. The properties I am referring to are the same properties that can be specified when activating the feature via a site definition XML.

As in many other cases if you are using Reflection you could pass the properties when activating the feature. The code below will allow you to do this. Just call any of the public methods that suits you:

public static SPFeature ActivateFeature(SPFeatureCollection features, string featureXml)

{

    return ActivateFeature(features, featureXml, false);

}

 

public static SPFeature ActivateFeature(SPFeatureCollection features, string featureXml, bool throwException)

{

    XmlDocument xmlDoc = new XmlDocument();

    xmlDoc.LoadXml(featureXml);

 

    return ActivateFeature(features, xmlDoc, throwException);

}

 

public static SPFeature ActivateFeature(SPFeatureCollection features, XmlDocument xmlDoc, bool throwException)

{

    return ActivateFeature(features, xmlDoc.DocumentElement, throwException);

}

 

public static SPFeature ActivateFeature(SPFeatureCollection features, XmlNode featureNode, bool throwException)

{

    if(featureNode != null &&

        “Feature”.Equals(featureNode.Name))

    {

        Guid featureId = new Guid(featureNode.Attributes[“ID”].Value);

        XmlNode properties = featureNode.SelectSingleNode(“./Properties”);

 

        if (properties != null)

            returnActivateFeature(features, featureId, properties, throwException);

    }

 

    return null;

}

 

public static SPFeature ActivateFeature(

    SPFeatureCollection features,

    string featureName,

    XmlNode propertiesNode,

    bool throwException)

{

    SPFeatureDefinition featureDef = SPFarm.Local.FeatureDefinitions[featureName];

    return ActivateFeature(features, featureDef.Id, propertiesNode, throwException);

}

 

public static SPFeature ActivateFeature(

    SPFeatureCollection features,

    Guid featureId,

    XmlNode propertiesNode,

    bool throwException)

{

    ConstructorInfo propCollConstr =

        typeof(SPFeaturePropertyCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];

    SPFeaturePropertyCollection properties = (SPFeaturePropertyCollection)propCollConstr.Invoke(new object[] { null });

 

    MethodInfo buildPropertyCollectionFromXmlNodeInternal =

        typeof(SPFeaturePropertyCollection).GetMethod(

            “BuildPropertyCollectionFromXmlNode”,

            BindingFlags.Instance | BindingFlags.NonPublic,

            null,

            new Type[] { typeof(XmlNode) },

            null);

 

    try

    {

        buildPropertyCollectionFromXmlNodeInternal.Invoke(properties, new object[] { propertiesNode });

    }

    catch (TargetInvocationException tex)

    {

        throw tex.InnerException;

    }

 

    return ActivateFeature(features, featureId, properties, throwException);

}

 

public static SPFeature ActivateFeature(

    SPFeatureCollection features,

    Guid featureId,

    IDictionary<string, string> activationProps,

    bool throwException)

{

    ConstructorInfo propCollConstr =

        typeof(SPFeaturePropertyCollection).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];

    SPFeaturePropertyCollection properties = (SPFeaturePropertyCollection)propCollConstr.Invoke(new object[] { null });

 

    foreach (string key in activationProps.Keys)

        properties.Add(new SPFeatureProperty(key, activationProps[key]));

 

    return ActivateFeature(features, featureId, properties, throwException);

}

 

private static SPFeature ActivateFeature(

    SPFeatureCollection features,

    Guid featureId,

    SPFeaturePropertyCollection properties,

    bool throwException)

{

    MethodInfo getFeatureInternal =

        typeof(SPFeatureCollection).GetMethod(

            “GetFeature”,

            BindingFlags.Instance | BindingFlags.NonPublic,

            null,

            new Type[] { typeof(Guid) },

            null);

 

    try

    {

        SPFeature alreadyActivatedFeature = (SPFeature)getFeatureInternal.Invoke(features, new object[] { featureId });

        if (alreadyActivatedFeature != null)

            // The feature is already activated. No action required

            return null;

    }

    catch (TargetInvocationException tex)

    {

        throwtex.InnerException;

    }

 

    MethodInfo addInternal =

        typeof(SPFeatureCollection).GetMethod(

            “Add”,

            BindingFlags.Instance | BindingFlags.NonPublic,

            null,

            new Type[] { typeof(Guid), typeof(SPFeaturePropertyCollection), typeof(bool) },

            null);

 

    try

    {

        object result = addInternal.Invoke(features, new object[] { featureId, properties, throwException });

 

        return result as SPFeature;

    }

    catch (TargetInvocationException tex)

    {

        throw tex.InnerException;

    }

}

If you decide to pass the features XML then it should have the same format as in your site definitions under the SiteFeatures or WebFeatures elements, for example you can pass the XML below to call the ActivateFeature(SPFeatureCollection features, string featureXml) from the code above.

<Feature ID=78277796-98D9-4276-B7D2-E3374AAC43D8>

      <Properties>

            <Property Key=MyProperty1 Value=FALSE />

            <Property Key=MyProperty2 Value=6ECFC841-7FFF-4E06-9D50-0678CC43696D />

            <Property Key=MyProperty3 Value=TRUE />

            <Property Key=MyProperty4 Value=Custom Data/>

      </Properties>

</Feature>

And if it worries you to call SharePoint internal methods using reflection then – don’t worry, this code will not do anything different than what SharePoint would do when activating your features. I have checked that. Oh and if you want exceptions that have occured in your feature receiver to be brought to you then make sure you pass true for throwException.

10 July, 2008

Be careful when manipulating your SPWeb’s RoleAssignments

This is an interesting “feature” of the SharePoint 2007 permission model that I came across today. Well imagine you have a site and a couple of lists in your site and all of them have unique permissions. If you check the value of the HasUniqueRoleAssignments property of the site and the lists they will all return “true“. So what does this tell you?  Well they are unique and independent and if you change the role assignments of the site this wouldn’t affect the role assignments of the lists and vice-versa, right? Well not exactly …

For a user to have any permissions granted for a list, this same user should also have site permissions granted. When you add someone as a list “Reader” for example then SharePoint will also add “Limited Access” permissions for the same user at the site level. SharePoint will create a new SPRoleAssignment at the site level, or will use an existing one if this user already has any permissions defined at the site level, and then will add a “Limited AccessSPRoleDefinition to the role assignment. It will do this without asking you or telling you about it.

Now if you decide to delete the role assignment for this user at the site level, guess what will happen? Well SharePoint will also delete the role assignment for the same user in all lists that have (not so) unique role assignments. And this means deleting any and all permissions granted to this user in any of the lists. Again it will not ask you and will not tell you it has done it. It would have been nice if there was an exception thrown but there isn’t.

So what this means for you. Well if you are dealing with permissions and you want to re-apply or change the site level permissions then if you were thinking about first removing all role assignments and then re-adding them this will actually also delete all role assignments from all lists in this site even if those lists have unique role assignments. So if that’s not what you expected how to get around it? Well instead of removing all the role assignments at the site level, just remove the role definition bindings from each of the role assignments:

foreach (SPRoleAssignment roleAssignment in web.RoleAssignments)

{

    roleAssignment.RoleDefinitionBindings.RemoveAll();

    roleAssignment.Update();

}

Doing so will NOT delete the “Limited Access” role definition binding from the site role assignments and your list permissions will remain untouched. Actually SharePoint doesn’t allow you to add or remove “Limited Access” permission directly and manages this internally.

7 July, 2008

2 July, 2008

Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))

Filed under: SharePoint — Tags: , , — hristopavlov @ 4:32 am

I got this error in a production environment today. The actual stack trace I was getting was something like this:

System.Runtime.InteropServices.COMException : Operation aborted (Exception from HRESULT: 0x80004004 (E_ABORT))
at Microsoft.SharePoint.Library.SPRequestInternalClass.UpdateRoleAssignment(String bstrUrl, Guid& pguidScopeId, Int32 lPrincipalID, Object& pvarArrIdRolesToAdd, Object& pvarArrIdRolesToRemove)
at Microsoft.SharePoint.Library.SPRequest.UpdateRoleAssignment(String bstrUrl, Guid& pguidScopeId, Int32 lPrincipalID, Object& pvarArrIdRolesToAdd, Object& pvarArrIdRolesToRemove)

No need to say that we had to resolve this issue as quick as possible. Searching the Internet gave me many references to this exact error message (such as http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2863393&SiteID=1 or http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1335022&SiteID=1) with the top causes of the problem being:

1 – The SQL Transaction Log database is full

2 – The windows account used to access the database doesn’t have enough permissions to the database.

However none of them was the case for us. Trying to get more info on the issue I run SPTraceView which saved the day. After getting the error message in SharePoint, SPTraceView immediately showed the error below:

Computer xxxxxxxxxxx, Date 02 Jul 2008

Server

Time

Severity

Product

Category

Message

xxxxxxxxxxxx

13:07:09.77811

Unexpected

Windows SharePoint Services

Database

Unexpected query execution failure, error code 916. Additional error information from SQL Server is included below. “The server principal “xxxxxxx\Svc_Moss_AppPool” is not able to access the database “Profiler_Trace” under the current security context.” Query text (if available): “{?=call proc_SecUpdateUserActiveStatus(?,?)}”

 
The message immediately rang a bell. We had started some SQL Server profiling and created some profiling triggers few days ago and looks like the application pool account didn’t have enough rights to our temporary profile database. That’s what I call a great catch that shows the power of SPTraceView. So if you haven’t got this tool in your arsenal yet, you should get it NOW from here.

1 July, 2008

Getting your ‘People Picker’ to pick AD groups

Filed under: SharePoint — Tags: , , , — hristopavlov @ 3:06 am

The Microsoft.SharePoint.WebControls.PeopleEditor control can be easily used in your web parts or other web based SharePoint solutions to provide a “People Picker” functionality. To find out how to use it see for example Ishai’s post here:

http://www.sharepoint-tips.com/2007/10/using-sharepoint-people-picker.html

However one of the things I needed to do was to use the control not just pick people but also to pick AD domain groups. This functionality is provided by the control but is not enabled by default when you create a new PeopleEditor class instance. In order to enable it you need to set the SelectionSet string property of the control to a comma separated list of the entities you want your people picker control to pick. Possible values are User for AD User, SecGroup for AD Group, DL for AD distribution list and SPGroup for a SharePoint group.

So if you want your people picker control to pick both AD users and AD groups you will need to set SelectionSet to “User,SecGroup” .

 

Blog at WordPress.com.