SharePoint Internals – Hristo Pavlov’s Blog

13 November, 2008

The MAX_PATH in SharePoint

After working with some really long path names in a SharePoint document library with many nested folders and subfolders I got some interesting findings that I’d like to share with you.

Firstly according to Microsoft the maximum allowed characters in a file path should not exceed 260 as per this KB article:

http://support.microsoft.com/kb/894630

But the article is not specific in how you count the characters. After doing the tests myself this is what I found. Firstly you have to convert the path into UTF-16, but this will only make a difference if you are using non Latin characters in your URL. Secondly you will have to URL-Decode the path i.e. replace all %20 with a space and the other %xx escape sequences with their corresponding ASCII character. And finally you need to remove the server name and the first leading slash from the URL. If the path to your file is:

 http://myserver.domain.com/managed-path/sitecoll/web%201/web%202/lib%201/folder%201/…./file1.doc

you need to count:

managed-path/sitecoll/web 1/web 2/lib 1/folder 1/…./file1.doc

you will be only able to upload the file in SharePoint if the number of counted characters does not exceed 260. The same results have been reported by Tim Jones in this blog entry:

http://simplyaprogrammer.com/2008/05/importing-files-into-sharepoint.html

But things don’t end here. SharePoint has a tight integration with Microsoft Office and allows you to check out and edit Office documents when you click on the links inside document libraries. Usually (with Office 2003 and Office 2007) when you click on a Office document you get a pop-up like the one below:

longpath2

However when you are trying to open files which full path length is getting closer to the maximum allowed by SharePoint you may start getting the standard IE file download dialog instead of the nicer Read only/Edit dialog:

longpath

The reason for this happening is that the component that is showing up the dialog and is responsible for the integration has a different limits on the maximum path than SharePoint. This component is an ActiveX control called “SharePoint.OpenDocuments” and is installed when you install MS Office if you choose the “Windows SharePoint Services Support” install option.

The limit for this component is again 260 characters, but this is 260 URL-Encoded characters for the full URL including the server name where a space (%20) counts for 3 characters rather than 1.

So this means that SharePoint will allow you to upload documents that will have a path that is too long for the MS Office SharePoint Integration to be able open them.

23 October, 2008

The trial period for this product has expired

Filed under: SharePoint — Tags: , , — hristopavlov @ 7:00 am

This is an error that we discovered was happening in one of the legitimate licensed MOSS boxes today. We were getting it when trying to check in or publish a page. The exact error message was was:

The trial period for this product has expired.   at Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage.HandleUnexpectedException(PublishingPage newPage, Exception exception)
   at Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage.NewPageItemSave(String pageName, PageLayout pageLayout)
   at Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage.CreateStandardPage(String pageName)
   at Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage.ButtonCreatePage_Click(Object sender, EventArgs e)
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Doing a search didn’t reveal much but there were other people experiencing the same problem. Some of them saying that they were getting an error when trying  to create a publishing site from the SharePoint GUI but no error when doing it via STSADM. Our further tests showed that there was an OfficeServerSettings.dll involved: 

[DllNotFoundException: Unable to load DLL ‘OfficeServerSettings.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)]
   Microsoft.Office.Server.Administration.NativeMethods.GetExpirationTime(Int64& expirationTime) +0
   Microsoft.Office.Server.Administration.Licensing.GetExpirationFileTimeFromRegistry() +156

[LicensingException: An error was encountered getting expiration info.]
   Microsoft.Office.Server.Administration.Licensing.GetExpirationFileTimeFromRegistry() +580

We found people suggesting various things, including uninstalling AV updates and changing the registry permissions:

http://joelsef.blogspot.com/2007/12/trial-period-for-this-product-has.html

http://blogs.mgtechgroup.com/markc/archive/2007/08/09/MOSS-quottrial-period-for-this-product-has-expiredquot-error.aspx

http://mindsharpblogs.com/ben/archive/2006/09/23/1299.aspx

http://blogs.msdn.com/joelo/archive/2008/01/02/evaluation-version-expired-but-not-really.aspx

What worked for us was adding permissions to the HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\Office Server\12.0 key to the WSS_WPG and WSS_ADMIN_WPG groups.

20 October, 2008

A Very Fast Method to Get the Site Collection’s Web Structure

Filed under: SharePoint — Tags: , , , , , , , , — hristopavlov @ 5:09 am

This is how you can get the full structure a SharePoint site collection without the need of creating memory consuming and slow SPWeb objects.

Before all let me just mention that actually the fastest possible way would be if you connect directly to the Content Database and run a query in the Webs table for the given site collection Id.

Other than this all ways to get the structure using the SharePoint object model will have to rely on the unmanaged COM objects implemented in OWSSVR.DLL to get the info. The question is how we can do this without actually creating an SPWeb object. The standard way to get the structure would be to create a new SPSite and then to loop through all subwebs to get the job done but this takes a looong time and wastes memory.

The code below uses an SPSite object to initialize the information about all the webs in the site, with an unmanaged call to GetAllWebsOfSite() of OWSSVR, and then reads the returned info from the private fields of the SPSite.AllWebs member without creating SPWeb objects. You will (probably) need to have access to all those sites for this method to work. Here is the code:

public class SPWebEntry

{

    private string m_Url;

    private string m_Title;

    private Guid m_Id;

 

    internal List<SPWebEntry> SubWebsList = new List<SPWebEntry>();

    internal SPWebEntry ParentWebEntry = null;

 

    internal SPWebEntry(string url, string title, Guid id)

    {

        m_Url = url;

        m_Title = title;

        m_Id = id;

    }

 

    public string Url

    {

        get { return m_Url; }

    }

 

    public string Title

    {

        get { return m_Title; }

    }

 

    public Guid Id

    {

        get { return m_Id; }

    }

 

    public IEnumerable<SPWebEntry> SubWebs

    {

        get { return SubWebsList; }

    }

 

    public SPWebEntry ParentWeb

    {

        get { return ParentWebEntry; }

    }

}

 

public class SPQuickSiteStructure

{

    private SPWebEntry m_RootWeb;

 

    public SPWebEntry RootWeb

    {

        get { return m_RootWeb; }

    }

 

    public SPQuickSiteStructure(string siteCollectionUrl)

    {

        using (SPSite site = new SPSite(siteCollectionUrl))

        {

            // This will make an unmanaged call to get all webs including their Guid and Titles but will not create any SPWeb objects

            string[] allSubWebUrls = site.AllWebs.Names;

 

            FieldInfo fi = typeof(SPWebCollection).GetField(“m_strTitles”, BindingFlags.Instance | BindingFlags.NonPublic);

            string[] allSubWebTitles = (string[])fi.GetValue(site.AllWebs);

 

            fi = typeof(SPWebCollection).GetField(“m_guidWebIds”, BindingFlags.Instance | BindingFlags.NonPublic);

            Guid[] allSubWebGuids = (Guid[])fi.GetValue(site.AllWebs);

 

            m_RootWeb = new SPWebEntry(

                    allSubWebUrls[0],

                    allSubWebTitles[0],

                    allSubWebGuids[0]);

 

            SPWebEntry currWeb = RootWeb;

 

            for (int i = 1; i < allSubWebUrls.Length; i++)

            {

                SPWebEntry web = new SPWebEntry(

                    allSubWebUrls[i],

                    allSubWebTitles[i],

                    allSubWebGuids[i]);

 

                while (

                    currWeb != null &&

                    (web.Url.Length < currWeb.Url.Length ||

                     web.Url.Substring(0, currWeb.Url.Length) != currWeb.Url))

                {

                    currWeb = currWeb.ParentWeb;

                }

 

                web.ParentWebEntry = currWeb;

                if (currWeb != null) currWeb.SubWebsList.Add(web);

                currWeb = web;

            }

        }

    }

}

To use it, simply create a new SPQuickSiteStructure object passing the site collection url and then do a standard loop through the SubWebs property starting from the SPQuickSiteStructure.RootWeb.

One thing to watch out for is the security. If you are going to show those sites to a user you’ll need to check whether the user is allowed to see the sites.

Thanks to Ishai for pointing out that the web urls are actually exposed by the public property Names, something that I missed initially. So the SPSite.AllWebs.Names gives you the site relative urls of all the webs but if you also want the web Titles and the web Guids you still need to go with the above code. Also the Names are returned as a flat list rather than a tree structure and you’ll need to build the structure yourself if you are not using the SPQuickSiteStructure class.

10 October, 2008

SPSite.LastSecurityModifiedDate

Filed under: SharePoint — Tags: , , , — hristopavlov @ 6:55 am

This is a great property in the SharePoint object model that I discovered today, that indicates the time of the last security related change done to an object in the site collection. This could be a change to any securable object (web, list or list item) or modification to the permission level rights etc. It will work regardless of whether the subwebs and lists use unique permissions or inherited permissions. Adding new objects that don’t inherit permissions also means adding permissions and will result in an update of the flag. The same applies for braking/restoring permission inheritance.

You can use the SPSite.LastSecurityModifiedDate and the SPSite.LastContentModifiedDate properties as indicators of when something has changed for example to invalidate any custom built caching mechanism as we did in out current project and this lead to exceptional performance improvement.

The values of those fields are coming directly from the content database from the Sites.LastSecurityChange and Sites.LastContentChange fields.

29 September, 2008

Monitoring web service calls under SharePoint

Filed under: ASP.NET, SharePoint — Tags: , , , — hristopavlov @ 7:57 am

I had this third party web part today that was calling some web services and we were suspecting that the calls were taking a lot of time and were slowing down the response of our SharePoint pages. Running a profiling session with the help of Fiddler we could only see the requests to SharePoint and to the default.aspx page which was taking 30+ seconds to complete but we were not seeing any calls to the suspected web services that may have been happening in the code behind of the default.aspx.

Knowing that Fiddler is actually an HTTP proxy that changes the system configuration and as a result all web browser request get redirected through it I realized that I could probably redirect all .NET HTTP requests to use the Fiddler proxy and this way could identify and monitor them. The ASP.NET web.config file provides a way for the proxy settings to be defined for the whole web application (in our case this is SharePoint). To do this add the section below to your SharePoint’s web.config file:

<system.net>

      <defaultProxy>

            <proxy usesystemdefault = false proxyaddress=http://127.0.0.1:8888 bypassonlocal=false />

      </defaultProxy>

</system.net>

As Fiddler will start on the local machine on port 8888 the lines above will define this proxy to be always used by all HTTP calls made by SharePoint and all web parts and this includes web service calls.

After doing this change I could now see in Fiddler the calls to the web service done from this web part and see that they were actually taking the 30+ seconds delay in which the default.aspx page was waiting for the web service call to complete.

Well now I need to figure out why this is happening and can it be fixed but at least it is now clear which web method is being called as this information is displayed under the “Text View” inspector in Fiddler !

22 September, 2008

SharePoint Code Acceptance Checklist

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

Microsoft have released and recently updated the ‘Sample code acceptance checklist for IT organizations’. This is a checklist with things to look for when doing a code review of SharePoint applications. It can be used as a best practices guide. The article also contains various links to other SharePoint related “Best Practices” documents so bookmark this link now:

http://technet.microsoft.com/en-us/library/cc707802.aspx

18 September, 2008

Implementing Custom Security Rights in SharePoint

Filed under: SharePoint — Tags: , , , , , — hristopavlov @ 11:55 pm

This is a blog entry about a customization that some of you will call a ‘hack’ but which power in my opinion is far greater that the support issues it poses. However before I start I want to make a disclaimer that if you follow my article you do it on your own risk and I am not going to provide support other than what you can find in this article. What I describe below has been implemented successfully in at least one large enterprise project I’ve worked on and works great!

What is the whole fuss about

Well you know how in SharePoint you have those users that can be granted various permission levels, which permission levels contain predefined user rights such as: Manage Lists, Override Check out, Add Items, Edit Items … etc … all those different rights that exist in SharePoint OOB. Those user rights are pretty much fixed and defined by Microsoft. Imagine you are building a custom application on the top of SharePoint and you want to define your own custom user rights. You can always do this in alternative ways but what if you wanted to extend the user rights in SharePoint by defining your own rights and this way keeping the security centralized? This would allow the new user rights to be managed by SharePoint the same way it manages the rest of the permissions. You would be able to grant the custom rights via the SharePoint Permission Levels from the UI and from the object model. You will be able to use the object model to test whether any of your custom rights are granted to a user and most improtantly you will have your custom rights and permissions going togather with the SharePoint permissions. And if anyone has the “Full Control” access granted they will also automatically get your new custom rights. Wouldn’t that be nice? Well this article is about how you can implement this!

How the user rights work

The rights used by SharePoint when defining Permission Levels are all defined in the Microsoft.SharePoint.SPBasePermissions enum. The enum is a flags enum each member of which is a 64 bit integer number that has one of its bits set. This allows a total of 64 different rights to be defined. The permissions granted to a SharePoint user is a single 64 bit permission mask represented as a SPBasePermissions object which corresponding bits/flags will be set for the user rights that have been granted to the user. You can cast this SPBasePermissions object to a 64 bit integer and check whether a specific bit = flag = user right is set.  At the moment the number of flags used by SharePoint is only 33 or 64, which means there is enough room for us to define an extra 31 custom user rights!

Our plan will be:

– Find a free enum value for our new user right
– Define a new Permission Level (using code) that contains our new user right
– Grant the new user right from the SharePoint UI
– Write code that checks whether a user has the new user right granted

How we implement custom user rights

The first step of our plan is to find a free enum value that is not already in use. We pretty much need to go through the existing members of the SPBasePermissions enum and pick one which is not used and which we don’t use already. The complete list of all possible values is shown below.

[Flags]

public enum SPBasePermissions

{

    EmptyMask =                 0x0000000000000000,

    ViewListItems =             0x0000000000000001,

    AddListItems =              0x0000000000000002,

    EditListItems =             0x0000000000000004,

    DeleteListItems =           0x0000000000000008,

    ApproveItems =              0x0000000000000010,

    OpenItems =                 0x0000000000000020,

    ViewVersions =              0x0000000000000040,

    DeleteVersions =            0x0000000000000080,

    CancelCheckout =            0x0000000000000100,

    ManagePersonalViews =       0x0000000000000200,

    UNUSED-VALUE =              0x0000000000000400,

    ManageLists =               0x0000000000000800,

    ViewFormPages =             0x0000000000001000,

    UNUSED-VALUE =              0x0000000000002000,

    UNUSED-VALUE =              0x0000000000004000,

    UNUSED-VALUE =              0x0000000000008000,

    Open =                      0x0000000000010000,

    ViewPages =                 0x0000000000020000,

    AddAndCustomizePages =      0x0000000000040000,

    ApplyThemeAndBorder =       0x0000000000080000,

    ApplyStyleSheets =          0x0000000000100000,

    ViewUsageData =             0x0000000000200000,

    CreateSSCSite =             0x0000000000400000,

    ManageSubwebs =             0x0000000000800000,

    CreateGroups =              0x0000000001000000,

    ManagePermissions =         0x0000000002000000,

    BrowseDirectories =         0x0000000004000000,

    BrowseUserInfo =            0x0000000008000000,

    AddDelPrivateWebParts =     0x0000000010000000,

    UpdatePersonalWebParts =    0x0000000020000000,

    UNUSED-VALUE =              0x0000000040000000,

    UNUSED-VALUE =              0x0000000080000000,

    UNUSED-VALUE =              0x0000000100000000,

    UNUSED-VALUE =              0x0000000200000000,

    UNUSED-VALUE =              0x0000000400000000,

    UNUSED-VALUE =              0x0000000800000000,

    UseClientIntegration =      0x0000001000000000,

    UseRemoteAPIs =             0x0000002000000000,

    ManageAlerts =              0x0000004000000000,

    CreateAlerts =              0x0000008000000000,

    EditMyUserInfo =            0x0000010000000000,

    UNUSED-VALUE =              0x0000020000000000,

    UNUSED-VALUE =              0x0000040000000000,

    UNUSED-VALUE =              0x0000080000000000,

    UNUSED-VALUE =              0x0000100000000000,

    UNUSED-VALUE =              0x0000200000000000,

    UNUSED-VALUE =              0x0000400000000000,

    UNUSED-VALUE =              0x0000800000000000,

    UNUSED-VALUE =              0x0001000000000000,

    UNUSED-VALUE =              0x0002000000000000,

    UNUSED-VALUE =              0x0004000000000000,

    UNUSED-VALUE =              0x0008000000000000,

    UNUSED-VALUE =              0x0010000000000000,

    UNUSED-VALUE =              0x0020000000000000,

    UNUSED-VALUE =              0x0040000000000000,

    UNUSED-VALUE =              0x0080000000000000,

    UNUSED-VALUE =              0x0100000000000000,

    UNUSED-VALUE =              0x0200000000000000,

    UNUSED-VALUE =              0x0400000000000000,

    UNUSED-VALUE =              0x0800000000000000,

    UNUSED-VALUE =              0x1000000000000000,

    UNUSED-VALUE =              0x2000000000000000,

    EnumeratePermissions =      0x4000000000000000,

    FullMask =                  0x7FFFFFFFFFFFFFFF,

}

We need to pick one unused value and let this be 0x0000000000002000. This 64 bit number is actually the decimal 8192 presented in hex. Let’s call our new user right “My Custom {SharePoint Internals} User Right” and grant it to users that will be allowed to use ‘tricks’ when developing SharePoint applications. The best way to grant our new right would be if we can add it as an extra check box to the editrole.aspx page. Doing this may or may not be possible and will definitely require very serious customizations to this SharePoint page. An easier alternative is to create a new Permission Level and setup its permission mask to contain only our new user right. This way we can grant this new permission level to everyone that we want to allow to use ‘tricks’.

To create our special Permission Level we will need to use code and we can “install” it for example as a feature receiver. One of the reasons for our ‘trick’ to work is because the permission levels are actually created from unmanaged code and the permission masks from managed SharePoint object model code are passed as 64 bit integers rather than as an enum values.  The function that will create any Permission Level of our liking is the AddRoleDef method of the SPRequest unmanaged object associated with every SPWeb. In order to call this method we will need to use Reflection though. In this case we already have the rootWeb SPWeb object and are going to add this new SPRoleDefinition to the root web, which will make it available for the whole site collection if we don’t break the role definitions. The code below does the job:

 

PropertyInfo propRequest =

      typeof(SPWeb).GetProperty(“Request”, BindingFlags.NonPublic | BindingFlags.Instance);

 

object spRequest = propRequest.GetValue(rootWeb, null);

 

MethodInfo mthAddRoleDef = spRequest.GetType().GetMethod(“AddRoleDef”);

 

mthAddRoleDef.Invoke(spRequest, new object[]

{

    rootWeb.Url,

    “My Custom {SharePoint Internals} User Right”,

    “Can use ‘tricks’ when coding”,

    false,

    1000,

    (ulong)0x2000, /* The mask of our custom right */

    (byte)5, /* Use ‘5’ so editing is not allowed */

    0

});

 

Well we completed most of our plan. Now the new Permission Level will show up in the central administration. Its checkbox will be disabled, which is done on purpose, because the editrole.aspx page will not know how to show the custom right. However it will be there and SharePoint will know about it even that you don’t see it from the UI. Next steps would be to actually grant this new Permission Level to some users. This can be done via the SharePoint administration pages or using the object model. Once we have some users that are allowed use ‘tricks’ we will possible want to check in various ocasions whether the curent user is allowed to use tricks in the current web or not i.e. to check whether our custom user right is granted to the user or not. This is done by the standard DoesUserHavePermissions method you usually use, but the permission mask is passed a little differently:

 

(web as ISecurableObject).DoesUserHavePermissions((SPBasePermissions)0x2000)

 

Well that’s it! You have successfully defined a new user right and have integrated it in the SharePoint security model.

What are the risks

Well Microsoft may decide to add a few more user rights themselves and use some of the flags we’ve taken already. Even that this sounds possible actually it will require a bigger change in the object model and such a change is very unlikely to be done in SharePoint 2007 i.e. without a major version release. Even if this happens remember we have 31 extra flags to choose from so that’s plenty.

Another thing that could go wrong is if you are using any third party tools to replicate permissions across servers or site collections and those third party tools use a conservative approach in the replication. Depending on how exactly the replication is implemented there is a chance the special permission masks to be lost after the replication. However this may only happen to the permission mask of the custom Permission Level we created at the root web. Now if you simply don’t replicate this Permission Level (because it will not change) then you won’t have any other issues.

16 September, 2008

The Id attribute of a ListInstance feature is ignored by SharePoint

Filed under: SharePoint — Tags: , , , — hristopavlov @ 7:22 am

A developer asked me today why SharePoint doesn’t use the Id specified in the ListIntance feature manifest for the guid of the list to be created. I checked the MSDN documentation and found out that the Id should be actually an integer number rather than a Guid: http://msdn.microsoft.com/en-us/library/ms476062.aspx

Looking at the SharePoint code with reflector revealed that the class that is responsible for activating a ListInstance feature is Microsoft.SharePoint.SPListInstanceElement which actually completely ignores the Id attribute. It creates the list the “normal” way and the list instances will always come with a different and unique guids.

internal SPList Microsoft.SharePoint.SPListInstanceElement.EnsureListExists(SPWeb web)

{

    SPList list2;

    try

    {

        return web.Lists[this.Title];

    }

    catch (ArgumentException)

    {

        Guid guid;

        int num;

        string str;

        string documentTemplate;

        SPListTemplate.QuickLaunchOptions off = SPListTemplate.QuickLaunchOptions.Off;

        if (string.IsNullOrEmpty(this.TemplateType))

        {

            return null;

        }

 

     

 

        num = int.Parse(this.TemplateType, NumberFormatInfo.InvariantInfo);

        str = base.FeatureDefinition.Id.ToString();

        documentTemplate = null;

      

        guid = web.Lists.Add(this.Title, this.Description, this.Url, str, num, documentTemplate, off);

        if (!(guid != Guid.Empty))

        {

            return null;

        }

        return web.Lists[guid];

 

    }

}

8 September, 2008

SharePoint Coding Best Practices

Filed under: SharePoint — Tags: , — hristopavlov @ 12:59 am

Mark Harison blogged few days ago about the great new Best Practices for SharePoint site coming directly from Microsoft:

http://markharrison.co.uk/blog/2008/09/sharepoint-best-practices-series.htm

Make sure you check the Common Coding Issues When Using the SharePoint Object Model article.

7 August, 2008

Changing the default list content type programatically

This is something I wanted to do today and couldn’t find any obvious way to accomplish it. Checking the code behind of the ChangeContentTypeOrder.aspx page revealed how it can be done. You need to build a SPContentType[] array or List<SPContentType> list that contains the content types in the order you like. The first content type in the list will be used as a default content type for the SPList. Once you have the array/list you need to assign it to the SPFolder.UniqueContentTypeOrder member of the RootFolder of the list and then call SPFolder.Update() to apply your new order.

You cannot use any content types that inherit from one of these two content types:

SPBuiltInContentTypeId.Folder – 0x0120

SPBuiltInContentTypeId.UntypedDocument0x010104

The dummy example below reverses the order of the content types of a list and also uses another cool thing I learned from checking the code behind of the ChangeContentTypeOrder.aspx page – the cool .NET sorting method Array.Sort<TKey, TValue> that sorts two arrays simultaneously based on the first array. Of course I could have also used Array.Reverse() method to do the same.

SPContentType[] newContentTypeOrderArray = new SPContentType[list.RootFolder.ContentTypeOrder.Count];

int[] newContentTypeOrderIndexes = new int[list.RootFolder.ContentTypeOrder.Count];

 

for (int i = 0; i < list.RootFolder.ContentTypeOrder.Count; i++)

{

    newContentTypeOrderArray[i] = list.RootFolder.ContentTypeOrder[i];

    newContentTypeOrderIndexes[i] = -i;

}

 

Array.Sort<int, SPContentType>(newContentTypeOrderIndexes, newContentTypeOrderArray);

 

list.RootFolder.UniqueContentTypeOrder = newContentTypeOrderArray;

list.RootFolder.Update();

As mentioned above you should be also aware that the “Folder” and “Untyped Document” content types are not shown on the “Change New Button Order and Default Content Type” page however they may become a default content type of the list if you delete all other content types or make one of them default content type programmatically. In this situation if the default content type is “Folder” for example and you upload a file to the document library the file’s content type will be “Folder” – probably not something you really wanted to happen 🙂

Finally if you delete the “Folder” content type from a list you may experience some other strange things. See my EditForm Not Shown When Uploading a File post for details.

« Newer PostsOlder Posts »

Create a free website or blog at WordPress.com.