SharePoint 2007 to 2010 site upgrade: problems and solutions

Recently, our project was in the process of migrating an existing SharePoint 2007 sub-site with the publishing features enabled, to a separate site collection and convert it to SharePoint 2010 as well, with the use of a sharepoint project management system.

The plan was to use stsadm to create peer to peer texting of the sub-site, which in turn could be imported again on an acceptance environment in a newly created web application with separate content database. I’d make the necessary adjustments to get the site to work as a site collection and then the content database was to be copied to the SharePoint 2010 development environment. We chose this course of action, as the site was part of a much larger site collection, which didn’t need to be migrated to SharePoint 2010 as of this moment.

Now, I already did my fair share of reading up on the topic and it turns out, converting a sub-site into a site collection when the publishing features are enabled, isn’t exactly supported. To add on top of that the migration to SharePoint 2010, things were looking rather grim.

First of all, getting the export to be imported correctly on the acceptance environment took a few attempts (version numbers of SharePoint solutions didn’t match, Terminal Services sessions being terminated in the middle of an import (yes, that actually happened), imports that were successful according to the tool, but when the site content was checked, most of the lists and libraries didn’t actually contain any content). But after a while we had that up and running, so I could replace the missing page layouts and master pages, make the necessary changes to the web.config file, so the site was ready for migration to SharePoint 2010.

The migration process involved copying the content database to SharePoint 2010, attaching it to a newly created web application running the upgrade. Funnily enough, that actually ran without any significant issues. It was when I actually start working on the site, a whole array of bizarre errors was encountered. This blog post’s purpose is to outline these errors and provide the solutions I’ve found during the two days I’ve been banging my head against the desk trying to figure it out:

Invalid SPListItem. The SPListItem provided is not compatible with a Publishing Page.

I seemed to be getting this error after creating a new page and then saving and closing the page; it would then throw this error.

Solution: Deactivate the site “SharePoint Server Publishing” feature and activate it again.

One or more field types are not installed properly. Go to the list settings page to delete these fields.

This error would occur when I opened the “Site Content and Structure” page and also after attempting to deactive the “SharePoint Server Publishing” feature on the site (related to the error above).

Apparently this is caused by a missing field in the hidden Relationships List on the site. This list is created by Publishing Resources and is used to store variations relationships. Note that the error I mentioned didn’t actually have to do with variations, as I’m not even using that on the site.

The upgrade process should have (at least I assume so), created an additional column called GroupGuid in this list, but in my case it hadn’t done so. Unfortunately its field type is supposed to be Guid and this type cannot be selected from the user interface when creating a new column.

Solution:
Use this tool to export the list on a working site and import it again on the site having issues:

http://spdeploymentwizard.codeplex.com

Be sure to add the following entry to the application config, as the Relationships List is hidden by default.

<appSettings>
<add key="ListsNotForExport" value="Cache Profiles" />
</appSettings>

The “Cache Profiles” value is in there because the application seemed to ignore a blank value; it would then still hide the Relationships List:

Also, uncomment the section that mentions using the tool with SharePoint 2010.

Start the tool, and follow these steps:

  1. Select “Export” and enter the URL of the working site on the first page of the wizard and click Next;
  2. Locate “Relationships List” in the tree and right-click the item; select Export -> Include all descendants and click Next;
  3. On the ‘Export Settings’ page, I only filled out the ‘Export folder’ and base filename fields. Use settings appropriate to your situation and click Next;
  4. On the next page, click the ‘Finish’ button to start the export;
  5. Once completed, start the tool again but then with “Import” as action. Enter the URL of the non-working site and click Next;
  6. Select the import file created earlier, leave the other settings to their default values and click Next;
  7. On the next screen, click ‘Finish’ to start the import process.
  8. Go to the site that has problems and access the following URL:
    http://<site URL>/Relationships%20List/AllItems.aspx
  9. Delete the old item that was in the list. This should be the item that doesn’t have a value for the GroupGuid field;
  10. All done, the issue should now be resolved.

Error_pagetitle

An error occurred while getting the items.

List does not exist.

The page you selected contains a list that does not exist. It may have been deleted by another user.

I was getting this error when I opened the “Create list” dialog (the one with the Silverlight control on it). I was actually getting a similar error when deploying a solution from Visual Studio:

Error occurred in deployment step ‘Recycle IIS Application Pool’: The list does not exist.

Solution:
  1. Open up the SharePoint 2010 Management Shell
  2. Run the following cmdlet:
    get-spcontentdatabase
  3. Copy the ID of the database that you’re having problems with
  4. Then run this cmdlet:
    upgrade-spcontentdatabase -id <ID>
  5. That should resolve the issue.
Fixing the publishing page layout URLs when restoring from a subsite to a site collection
Since the site we migrated, originally was a sub-site of a larger site collection, the page layout URLs of existing pages needed to be updated to their respective new locations. After the migration, everything was still pointing to the old site collection’s master page gallery.
Solution:
Create a new console application (remember to set the target framework to .NET 3.5 and the platform target to ‘Any CPU’) and copy in the following code:
static void Main(string[] args)
{
    try
    {
        using (SPSite site = new SPSite(""))
        {
            using (var web = site.OpenWeb())
            {
                FixPageLayoutsUrl(web);
            }
        }
    }
}
 
private static void FixPageLayoutsUrl(SPWeb web)
{
    var spPubWeb = PublishingWeb.GetPublishingWeb(web);
    var pages = spPubWeb.PagesList;
 
    foreach (SPListItem item in pages.Items)
    {
        var pubPage = PublishingPage.GetPublishingPage(item);
 
        if (null == pubPage || null == pubPage.ListItem)
        {
            continue;
        }
 
        var url = new SPFieldUrlValue(Convert.ToString(pubPage.ListItem[FieldId.PageLayout]));
        const string urlPart = "/relative/url/tositecollection";
 
        if (url != null &amp;&amp; !String.IsNullOrEmpty(url.Url) &amp;&amp; url.Url.Contains(urlPart))
        {
            url.Url = url.Url.Replace(urlPart, "");
 
            Console.WriteLine(pubPage.Name);
            pubPage.CheckOut();
            pubPage.ListItem[FieldId.PageLayout] = url;
            pubPage.ListItem.UpdateOverwriteVersion();
 
            pubPage.ListItem.File.CheckIn("Fixed page layout URL.", SPCheckinType.MajorCheckIn);
        }
    }
}

Enter the URL of the site collection on which you’re having problems. Also, replace the urlPart part with a value appropriate to your situation. You may need to rewrite a part of the code there to make it work for your environment. The general idea is to replace the list item’s PageLayout column with the new URL.

Could not create a new page; the process in the dialog would hang and eventually result in a timeout

The same thing would happen when accessing “Page layouts and site templates” from the Site Settings page.

The problem may have been related to the fact that this was a migration from a sub-site to a site collection. The initial ite collection had its available page layouts restricted and this setting may have been included in the export, causing the problem.

Solution:

Run the following piece of code from a commandline utility and do an iisreset after:

static void Main(string[] args)
{
    try
    {
        using (SPSite site = new SPSite(""))
        {
            using (var web = site.OpenWeb())
            {
                var publishingWeb = PublishingWeb.GetPublishingWeb(web);
                publishingWeb.AllowAllPageLayouts(true);
                publishingWeb.Update();
            }
        }
    }
}

Replace the site collection URL with a value appropriate to your environment.

Critical entry in the event log when attempting to open the ‘Create list’ page in SharePoint

Insufficient SQL database permissions for user ‘Name: <UserID> SID: <SID> ImpersonationLevel: Impersonation’ in database ‘<database>’ on SQL Server instance ‘<instance>’. Additional error information from SQL Server is included below.

The EXECUTE permission was denied on the object ‘proc_GetProductVersions’, database ‘<database>’, schema ‘dbo’.

Solution:

  1. Open SQL Server Management Studio and connect to the proper server instance
  2. In the database server, expand the database on which you’re having problems and navigate to Programmability – Stored Procedures – dbo.proc_GetProductVersions.
  3. Open the stored procedure’s properties.
  4. On the new screen, select ‘Permissions’ on the left and click the Search button.
  5. On the new screen, click Search, select the [WSS_Content_Application_Pools] role and click OK.
  6. Click OK again.
  7. On the first popup screen, select the role, check ‘Execute’ permission and click OK.
Thinking about it, perhaps most of the problems might have been fixed after running the Powershell content database upgrade again, but I didn’t get a chance to test that separately.
I’ll try to update this post should I encounter any more issues. Hopefully this helps anyone who’s struggling with migrating a SharePoint 2007 to 2010.

Modify and repackage SharePoint STP files

There have been a few occassions where SharePoint site templates (STP files) we’ve been using in client environments suddenly would stop working after upgrading WSP files, notably after removing certain features from the WSPs. Whenever you’d create a new site based on such a template, you get an error saying “The template you have chosen is invalid or cannot be found”.

Now you’d think you could fix this by just creating a new STP file based on your template site, which then wouldn’t include the feature. However, I’ve encountered cases where this error would still occur, so you need to make sure to use the split pdf. There is a little trick you can use to remove the missing features from the STP file. This is also useful when you receive a non-working STP file from for example a client who has a different environment (extra features) than your own.

The idea is to extract the STP file, remove the missing features from the manifest and repackage the file. In order to do this, use the following steps.

Since an STP file is just a renamed cabinet file, you can just extract its contents by using a ZIP client. Copy the file to your target environment on which you’re trying to use the template.

Create a temporary folder anywhere on your system. Rename the file to .cab and extract the contents to the folder you just created. Navigate to the folder and open the manifest.xml file in a text editor.

Now, in the manifest file, you should see the following elements:

<WebFeatures>

<SiteFeatures> (I believe this one is optional though)

There should be a list of <Feature /> elements in each of these blocks. You can probably safely skip the features with GUIDs starting with 00bfea as these are SharePoint out-of-the-box features.

For any of the other feature GUIDs, you need to verify that they actually exist in your target environment. I’m not sure if there is any quick way to get an overview of all feature GUIDs in a farm, so here’s the convoluted way. Go to the following folder:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES

For each of the feature GUIDs, search in *.xml files and use the following search phrase:

ID="{GUID}"

Where {GUID} is the GUID of the elements in the manifest file.

If you can’t find a hit for a search phrase, then this is one of the features that’s not available in the environment. Remove any of these feature elements from the manifest file and save the file when done for all of the feature elements.

Repeat this process if you do have a <SiteFeatures> element in your manifest file.

Now, it’s time to repackage the contents of the cabinet file into a new STP file. Windows comes with a utility called makecab that you can use to create a cabinet file from files in a directory. The only thing is, you need a DDF file as an input parameter.

In your temporary folder you created earlier, create a new text file and name it files.ddf and open this file in a text editor. Add the following text to the file:

.OPTION EXPLICIT
.Set CabinetNameTemplate=newtemplate.stp
.set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=.
.Set CabinetFileCountThreshold=0
.Set FolderFileCountThreshold=0
.Set FolderSizeThreshold=0
.Set MaxCabinetSize=0
.Set MaxDiskFileCount=0
.Set MaxDiskSize=0

This will basically instruct makecab to construct a new file called newtemplate.stp with a couple of settings. The threshold and size parameters allow for cabinet files larget than 1.5MB.

Now we need to add the files to be included to the DDF file. Open a command prompt and go to your temporary folder. Execute the following command:

dir /b

This will print a bare list of the files in the directory, very easy to copy and paste into your DDF file. Be sure to only include the following files:

manifest.xml
*.000

Your DDF should now look something like this:

.OPTION EXPLICIT
.Set CabinetNameTemplate=newtemplate.stp
.set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=.
.Set CabinetFileCountThreshold=0
.Set FolderFileCountThreshold=0
.Set FolderSizeThreshold=0
.Set MaxCabinetSize=0
.Set MaxDiskFileCount=0
.Set MaxDiskSize=0

00000000.000
01000000.000
10000000.000
11000000.000
15000000.000
21000000.000
25000000.000
31000000.000
35000000.000
40000000.000
50000000.000
60000000.000
70000000.000
90000000.000
b1000000.000
c0000000.000
c1000000.000
d0000000.000
f0000000.000
g0000000.000
h0000000.000
k0000000.000
p0000000.000
q0000000.000
r0000000.000
s0000000.000
u0000000.000
u4000000.000
v0000000.000
v4000000.000
w4000000.000
x4000000.000
z4000000.000
manifest.xml

Save the file and go back to your command prompt. Execute the following command:

makecab /f files.ddf

When it’s done, go back to the temporary file where you should now see your newtemplate.stp file.

Upload this file to your SharePoint site collection and test whether you can now create a new site based on this template. If all went well, your template should be working now!

Anonymous access settings on a list greyed out

Today I was trying to help out a client to set up one of their existing lists for anonymous access. I followed the usual procedure of editing the list permissions and then navigating to the anonymous access settings. However, all the checkboxes on the page were greyed out.

I checked the anonymous access settings on both the site collection and the website and they all seemed fine. I checked another existing list and that seemed fine as well. Then I created a new list based on the same template as the list I was trying to configure, and that worked as well! Weird…

So I started searching on Google and came across a blog post about anonymous access on surveys here: http://www.teuntostring.net/blog/2007/07/using-surveys-on-anonymous-access.html. It’s mentioned there that in order for anonymous access to work, the setting for ‘Read Access’ needs to be ‘All Responses’, otherwise the checkboxes will be greyed out. Then I remembered that we’re using a similar setting our list, for privacy concerns.

I went back to the list, changed the setting for ‘Read Access’ to ‘All items’, went back to the anonymous access settings page, and voila! The checkboxes were no longer greyed out.

Using the property bag of an SPWeb for storing configuration settings

Each web site in SharePoint 2007 has a property bag that can be accessed from the object model. This property bag is ideal for storing configuration settings for a web. I’m going to show you how to use this in combination with serializing a settings object and by using the Provider design pattern. The Provider pattern is in here so that you can also easily switch to storing the configuration settings in for example a SharePoint list.

I’m first going to outline the requirements for our solution:

  • A mechanism needs to be in place that allows for storing custom site configuration settings for a web;
  • Site collection wide settings should also be stored. These settings are different from the settings on a web site;
  • The storage location of these settings should be easily swappable;
  • The configuration settings should be accessible by using object properties;
  • The consumers of these settings should not have to care about retrieving and storing the settings;

Since the data that is stored for each type is different, a container class for each type is created. To define that they are both configuration setting objects, they both implement a public interface called “IConfigurationSettings”.

1
2
3
4
5
6
7
8
public interface IConfigurationSettings 
{ 
    SPWeb Web 
    { 
       get; 
       set; 
    } 
}

Both types of site settings are stored in a property bag of the corresponding web. Site collection settings are stored on the root web. You can also have any other settings type, just as long as they implement the interface above. In our example we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Serializable()] 
public class SiteConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}       
 
[Serializable()] 
public class SiteCollectionConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}

For the sake of keeping this blog post brief, I’m not going to outline the different properties for each of these classes, I’m sure you can use your imagination. :)

The configuration values are persisted to the underlying data store by using the Provider design pattern. Should the storage location be changed in the future, for example to a custom list, this pattern can easily facilitate such a change. It would only require the development of a provider for the custom list. The container classes and management objects remain unchanged.

Each provider has to implement the interface “IConfigurationProvider”. This interface dictates that any implementing class should at least provide methods for retrieving and storing configuration settings. It uses generics to cope with the different kind of configuration classes. Although an interface could be returned here, generics make more sense since they immediately return the proper type, instead of an interface.

To ease the process of converting the stored data into usable objects and vice versa, the objects are serialized when they are persisted to the data store. Should any new configuration settings be added in the future, then no change to the data storage is required.

1
2
3
4
5
6
7
8
internal interface IConfigurationProvider 
{ 
    void Clear(SPWeb web);        
 
    T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new();        
 
    void SaveConfiguration<T>(SPWeb web, T settings); 
}

The following is the actual implementation used by the property bag provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
internal class PropertyBagConfigurationProvider : IConfigurationProvider 
{ 
    private readonly string _propertyBagSettingsKey = "ConfigurationSettings";     
 
    #region IConfigurationProvider Members     
 
    public void Clear(SPWeb web) 
    { 
        web.Properties[_propertyBagSettingsKey] = null; 
        web.Properties.Update(); 
    }     
 
    public T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new() 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        string propertyData = web.Properties[_propertyBagSettingsKey]; 
        T deserializedConfig = new T(); 
        deserializedConfig.Web = web;     
 
        if (!string.IsNullOrEmpty(propertyData)) 
        { 
            object obj = null;    
 
            using (StringReader sr = new StringReader(propertyData)) 
            { 
                byte[] b = Convert.FromBase64String(propertyData); 
                Stream stream = new MemoryStream(b);    
 
                try 
                { 
                    BinaryFormatter bf = new BinaryFormatter(); 
                    obj = (object)bf.Deserialize(stream); 
                } 
                catch 
                { 
                    throw; 
                }    
 
                deserializedConfig = (T)obj; 
            } 
        }     
 
        return deserializedConfig; 
    }     
 
    public void SaveConfiguration<T>(SPWeb web, T settings) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        if (settings == null) 
        { 
            throw new ArgumentNullException("settings"); 
        }     
 
        string result;    
 
        using (Stream stream = new MemoryStream()) 
        { 
            try 
            { 
                BinaryFormatter bf = new BinaryFormatter(); 
                bf.Serialize(stream, settings); 
            } 
            catch 
            { 
                throw; 
            }    
 
            stream.Position = 0; 
            byte[] b = new byte[stream.Length]; 
            stream.Read(b, 0, (int)stream.Length);    
 
            result = Convert.ToBase64String(b, 0, b.Length); 
        }    
 
        web.Properties[_propertyBagSettingsKey] = result; 
        web.Properties.Update(); 
    }     
 
    #endregion 
}

The provider objects are not exposed through the public API, since the calling client doesn’t necessarily have to be aware of how data storage and retrieval is done. To abstract this concept, a generic “Configuration” object is created. When an instance of this object is created, it also creates an instance of the configured provider. Any calls to the storage and retrieval methods are redirected to the provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class Configuration<T> where T : IConfigurationSettings, new() 
{ 
    #region Fields   
 
    private static Configuration<T> _instance; 
    private SPWeb _web = null; 
    private IConfigurationProvider _provider = null; 
    private T _settings = default(T);   
 
    #endregion   
 
    #region Properties   
 
    public T Settings 
    { 
        get 
        { 
            if (_provider != null &amp;&amp; _web != null) 
            { 
                _settings = _provider.GetConfiguration<T>(_web); 
            } 
            else 
            { 
                _settings = new T(); 
            }   
 
            return _settings; 
        } 
    }   
 
    #endregion   
 
    #region Constructors   
 
    internal Configuration() 
    {   
 
    }   
 
    internal Configuration(SPWeb web) 
    { 
        _web = web;   
 
        // Note that this can also be changed to read a setting from a configuration file so that it can be changed without touching the code. 
        _provider = Activator.CreateInstance(typeof(PropertyBagConfigurationProvider)) as IConfigurationProvider; 
    }   
 
    #endregion   
 
    #region Public Methods   
 
    public void Clear() 
    { 
        _provider.Clear(_web); 
    }   
 
    public void SaveSettings() 
    { 
        _provider.SaveConfiguration<T>(_web, _settings); 
    }   
 
    #endregion   
 
    #region Public Static Methods   
 
    internal static Configuration<T> GetConfigurationSettings(SPWeb web) 
    { 
        _instance = new Configuration<T>(web);   
 
        return _instance; 
    }   
 
    #endregion 
}

By using this Configuration class, we can abstract the behaviour of the provider and hide it from the caller. This way the caller doesn’t have to care about what’s going on with the data store, he only sees the methods to retrieve and save the configuration settings.

In order to improve usability of the application, a final wrapper class is created to provide easy access to each of the different settings. The wrapper class exposes properties for SiteConfigurationSettings and SiteCollectionConfigurationSettings. These properties will look for settings in the web SPContext.Current.Web. The exposed methods can be used to retrieve settings from a different web, or when you’re calling the code from a console application for example, where the SPContext does not exist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public static class ConfigurationManager 
{ 
    public static SiteConfigurationSettings WebSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
	    return ConfigurationManager.OpenSiteConfiguration(SPContext.Current.Web).Settings; 
            }   
 
             return null; 
        } 
    }   
 
    public static SiteCollectionConfigurationSettings SiteCollectionSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Site != null) 
            { 
	    return ConfigurationManager.OpenSiteCollectionConfiguration(SPContext.Current.Site).Settings; 
            }   
 
            return null; 
        } 
    }   
 
    public static string CustomCssUrl 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
                return StyleConfiguration.CustomCssLocation + String.Format(StyleConfiguration.CustomCssFileFormat, SPContext.Current.Web.ID.ToString()); 
            }   
 
            return String.Empty; 
        } 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPSite siteCollection) 
    { 
        if (siteCollection == null) 
        { 
            throw new ArgumentNullException("siteCollection"); 
        }   
 
        return OpenSiteCollectionConfiguration(siteCollection.RootWeb); 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPWeb rootWeb) 
    { 
        if (rootWeb == null) 
        { 
            throw new ArgumentNullException("rootWeb"); 
        }   
 
        return Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings>.GetConfigurationSettings(rootWeb); 
    }   
 
    public static Configuration<siteconfigurationsettings></siteconfigurationsettings> OpenSiteConfiguration(SPWeb web) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }   
 
        if (web.IsRootWeb) 
        { 
            // Return an empty settings object, since this method cannot be used on a root web; the type conversion would fail. 
            return new Configuration<siteconfigurationsettings></siteconfigurationsettings>(); 
        }   
 
        return Configuration<siteconfigurationsettings></siteconfigurationsettings>.GetConfigurationSettings(web); 
    } 
}

Invalide parameter: ContentType. Expect a number. ‘SomeContentType’ is given instead.

So, you’ve created your custom advanced search box for your SharePoint 2007 site, but now you’re running into an error:

Invalide parameter: ContentType. Expect a number. ‘SomeContentType’ is given instead.

There is a good chance you forgot to include two imporant hidden fields in your control:

  • ASB_TextDT_Props
  • ASB_DateTimeDT_Props

I’m not entirely sure about the purpose of these fields, but I suspect that the entries in these fields define the data type of the property fields in the advanced search box. You can include them in your control like this:

protected override void OnPreRender(EventArgs e) 
{ 
  base.OnPreRender(e);     
 
  this.Page.ClientScript.RegisterHiddenField("ASB_TextDT_Props", "ContentType#;#Path#;#FileName#;#Description#;#Title#;#Author#;#DocSubject#;#DocKeywords#;#DocComments#;#Manager#;#Company#;#CreatedBy#;#ModifiedBy");
  this.Page.ClientScript.RegisterHiddenField("ASB_DateTimeDT_Props", "Write#;#Created#"); 
}

Also, be sure to include any custom managed properties you might have and wish to search on.

WebPartPageUserException: This page has encountered a critical error.

Did you ever try using a console application to programmatically activate a feature on a web and ran into the following error?

Microsoft.SharePoint.WebPartPages.WebPartPageUserException: This page has encountered a critical error. Contact your system administrator if this problem persists.

This doesn’t really give you much to go on. There is no InnerException object that gives you more information about what exactly is going on. I’ll first briefly outline what exactly I am trying to do here.

I have a page layout that contains several web part zones. This page layout is contained in a feature with scope “Site” and is deployed to the master page gallery upon activation. Now, I have another feature with scope “Web” that is stapled to the custom site definition we’re using. This feature takes care of creating an instance of that page layout and is supposed to add several web parts specified in a config file. The code for this is contained in a feature receiver. The web parts I’m adding are stored in *.webpart and *.dwp files, which are part of my SharePoint solution.

Now, this is where I’m running into the error mentioned in the beginning of this post. I am using the SPLimitedWebPartManager.ImportWebPart() method with an XmlReader to load the web part definition that should be imported. Whenever this method was hit (I was using the debugger to see what’s going on), it would throw an exception. Funny detail here is that the ImportWebPart() method has an out parameter that should contain the error message when something goes wrong. Strangely enough the exception I was running into, was not contained in the parameter, but thrown as an actual exception.

For those of you not interested in the outline of what took me an entire day to figure out, I will first describe what solved the error in my case. Thinking about it, I should have probably noticed this in the first place, but at first I didn’t connect the dots. Checking the event viewer, I noticed an error there:

Safe mode did not start successfully. Could not load file or assembly ‘Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c’ or one of its dependencies. The system cannot find the file specified.

Initially I figured this to be related to the weird way my virtual machine with the development environment was behaving, but it turned out that this was exactly what was causing the problem. Since I am running the code from a console application and not within one of the SharePoint web application pools, I had of course also no access to the web application "_app_bin" folder. Apparently the DLL mentioned in the error is not added to the Global Assembly Cache, but is stored in the aforementioned folder. So, when I would run my console application, it would of course not be able to load the file, because it doesn’t know that it should look in that folder. I copied the DLL to my console application’s bin folder and it worked!

For anyone interested in how I figured this out without using the error message in the event log. The stack trace of the exception looked like this:

"Microsoft.SharePoint.WebPartPages.WebPartPageUserException: This page has encountered a critical error. Contact your system administrator if this problem persists. 
at Microsoft.SharePoint.ApplicationRuntime.SafeControls.RethrowExceptionIfNeeded() 
at Microsoft.SharePoint.ApplicationRuntime.SafeControls.IsSafeControl(Type type, String&amp; unsafeErrorMessage) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.CreateWebPart(Boolean clearConnections) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.Import(SPWebPartManager manager, XmlReader reader, Boolean clearConnections, Uri webPartPageUri, SPWeb spWeb) 
at Microsoft.SharePoint.WebPartPages.WebPartImporter.Import(SPWebPartManager manager, XmlReader reader, Boolean clearConnections, SPWeb spWeb) 
at Microsoft.SharePoint.WebPartPages.SPWebPartManager.ImportWebPart(XmlReader reader, String&amp; errorMessage) 
at Microsoft.SharePoint.WebPartPages.SPLimitedWebPartManager.ImportWebPart(XmlReader reader, String&amp; errorMessage) 
  at ConsoleTest.WebPartConfigurator.ConfigureWebPartsForPage(SPWeb web, String pageUrl, String webPartsLocation) in e:\Development\Sandbox\ConsoleTest\ConsoleTest\WebPartConfigurator.cs:line 68"

Since I wasn’t getting anything specific from the exception, I figured I’d start .NET Reflector to disassemble the WebPartImporter.CreateWebPart() method to see where it would crash. Somewhere in that method the imported web part is being validated to see whether it’s actually a safe control, hence the call to SafeControls.IsSafeControl() The SafeControls object is an internal property of the SPWeb object. When I opened the SafeControls.IsSafeControls() method, I noticed that the last method in the call stack (where the exception is thrown) is actually SafeControls.RethrowExceptionIfNeeded(). This appeared to be the first method that’s called in SafeControls.IsSafeControls(). In this method, it checks to see if a private field called _configException is not null. If not, it would throw the exception.

So I figured that since an exception already occured before it could actually check to see if the control is safe, I decided to see if I could find the place in the code where the exception was be thrown. Since SharePoint uses resource files for all of its text, I could not just search on the exception text. With Reflector, I opened Microsoft.SharePoint.intl.dll and found the resource entry with my exception text, with key “SafeModeFailedInitialize”.

Since I had already concluded that the exception already occurred before doing validation, I tried to figure out where the actual list of safe controls is being built. Opening the constructor of the SafeControls class showed the following:

this._configException = new WebPartPageUserException(SPResource.GetString("SafeModeFailedInitialize", new object[0]));

It appeared that I had found the source of the problem! Now only to figure out why it would do that…

The SafeControlsList object that’s being initialized there is trying to load the web application config file in order to parse the SafeControls section. At first I thought: “Maybe it cannot find the config file because I’m running the code from a console application”. However, there is already some code in place that looks for the config file if it’s not executed in the web application context, so it had to be something else.

Further down in the constructor, the method InitSafeControlsInfoFromConfig() is called. Scrolling through the code I noticed the following line:

throw new WebPartPageUserException(SPResource.GetString("SafeModeFailedInitialize", new object[0]));

Interesting, that’s exactly the same error message as I discovered earlier! I searched the code and found out that this exception is thrown when one of the assemblies in the SafeControls section is null. Then I remembered the error in the event log of which I didn’t think anything in the beginning. Adding things up, I quickly drew the conclusion that the missing DLL must be the source of my problem. So, I checked the Global Assembly Cache and couldn’t find the assembly there, which made sense, otherwise I wouldn’t be getting the error. Since the code was working when I activated the feature through the Site Settings page, the DLL had to be in the _app_bin folder of the web application.

And indeed, the file was indeed in that folder. I copied the file to the bin folder of my console application and tried to run it again. This time it completed without any exceptions!

It took me pretty much the entire day to figure out what was going on, that’s why I’m writing this blog post. Two reasons: I hope it helps anyone who’s running into the same problem and a brain dump for myself, should I ever run into the same issue again.

Change the welcome page of your SharePoint 2007 site programmatically

Today I was trying to figure out how to change the welcome page of a SharePoint 2007 site programmatically. I couldn’t really find useful links on Google, so I figured I’d go and play around a bit with the object model.

Apparently the PublishingWeb object has a property called DefaultPage, which takes an SPFile object as a value. I ended up with the following code to change the welcome page:

using(SPSite siteCollection = new SPSite("http://yourserver")) 
{ 
    SPWeb targetWeb = siteCollection.AllWebs["YourWebName"]; 
    SPFile newWelcomePage = null; // Change the code here to set the SPFile object to your new welcome page
 
    if(PublishingWeb.IsPublishingWeb(targetWeb)) 
    { 
        PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(targetWeb); 
        publishingWeb.DefaultPage = newWelcomePage; // this sets the new welcome page 
        publishingWeb.Update(); 
    } 
}

That should do the trick :) . I hope this post is useful for anyone who’s also trying to achieve the same thing!

Can’t find that newly saved list template when creating a new list?

Ever came across that situation where you just saved an existing list as a template? And then you tried creating a new list based on that template? No matter how hard you looked, you just couldn’t find the template saved earlier when trying to create a new list?

Chances are that your list was provisioned using a feature and that the Hidden property of the <ListTemplate> element was set to True. That property prevents the list template from showing up on the page where you create a new list. Unfortunately, this also makes it a little bit more difficult to create that list through the SharePoint user interface.

Yes, I said a little more difficult, not impossible. There happens to be a work-around that allows you to still perform that action through the UI. This little trick was inspired on something that worked in SharePoint Portal Server 2003 as well. Sometimes the UI doesn’t allow you to perform a certain action (for example, it hides the action). However, the page that performs the actual action, could be fooled in performing the hidden action, by passing the correct query string parameters.

In this case, we will do exactly the same thing to create our list. Navigate to the site where you want to create your list and go to View All Site Content. On that page, click the Create button. As you already found out, the template you just saved isn’t here. Now, click the link to create a new Custom List.

Notice the address bar in your browser. The new.aspx page takes a FeatureId and ListTemplate as a parameter. The FeatureId parameter matches with the feature which installed this list on the SharePoint environment. The ListTemplate parameter probably speaks for itself; it specifies the template ID of the list.

To create a list based on a saved template, you need to specify an additional parameter to new.aspx. This parameter is called NewPageFilename and its value should be the name of your STP file.

Now you should look up the feature that installed the list you’re trying to create and copy its ID. Take this value and replace the value for FeatureId in the query string. Also, look up the <ListTemplate> element that is defined in the feature and copy its Type attribute. This value corresponds with the ListTemplate parameter in the URL, so replace the value in the query string with the value you just copied.

When you’re done, your URL should look something like this (without the square brackets):

/_layouts/new.aspx?NewPageFilename=[YourTemplateName]%2Estp&FeatureId={735DE88F-B6C1-4571-8D5F-08BF22D6CF40}&ListTemplate=130185

There you go! This URL should allow you to create your list. Navigate to the URL above and fill out the form like you normally do and you’re done :) This trick probably also works with hidden lists that don’t have a saved template. In that case, just omit the NewPageFilename parameter.

SharePoint hotfix 939077 partially breaks Advanced Search

Microsoft released a hotfix package for SharePoint Server 2007 and for SharePoint Server 2007 for Search. This hotfix package fixes an issue where sites that use forms-based or cookie-based authentication are not being crawled in SharePoint. It also fixes an issue where slow performance of the BLOB cache is experienced. The latter is the reason why we installed this hotfix on the production environment of a client I’m currently working at.

The hotfix addresses the issues just fine. However, on our environment it appears to be introducing a new issue: It breaks the out of the box SharePoint Advanced Search Box web part with the following dreaded error: “Object reference not set to an instance of an object.” 

This is all the information you’re getting, no stack trace, nothing in the logs. This pretty much left me clueless. At first I thought I screwed something up with my own search settings. That feeling got stronger after I created a default Search Center site: the Advanced Search web part was still working there.

After some puzzling I concluded that it had to be a difference in the web part properties, since it appeared to be working on the default Search Center site. So I exported both web parts, and examined the differences…and indeed, the only differences where property settings.

Right, back to my non-working version of the web part. In my version I was hiding the Properties section which is visible in the default web part. After making it visible on my web part again, it worked! So much for testing your hotfixes before rolling them out…

The problem is definitely the hotfix. I also installed it on my development environment and it broke the Advanced Search web part as well…

The hotfix cannot be uninstalled, so now we’re pretty much waiting for the hotfix-hotfix from Microsoft. For now, I guess I’ll just have to remove the Advanced Search web part from the search results page.

SPWeb.AvailableContentTypes or SPWeb.ContentTypes?

There are two ways to get a collection of content types for a web site. One is the SPWeb.AvailableContentTypes collection and the other one is the SPWeb.ContentTypes collection. Right, what’s the difference then, I hear you say. A quote from the MSDN documentation:

Gets the collection of all content type templates for the current scope, including those of the current Web site, as well as of any parent sites.

Use the ContentTypes property to return only the content types of the current Web site.

The documentation is not very extensive here. The difference really is a bit vague. For you programmers out there it is good to realize that an important difference between the two exists. The AvailableContentTypes collection is read-only!

Allow me to ellaborate a bit more on that. A while ago I tried to programmatically add an information management policy to a content type (I’ll write something on that later). So, I retrieved the content type like this:

SPWeb web = SPContext.Current.Web;
SPContentType contentType = web.AvailableContentTypes[new SPContentTypeId(myContentTypeId)];

When trying to add the policy to the content type, I just kept getting an error: “The collection cannot be modified”. This seemed very strange to me, and I couldn’t figure it out at first. Google and MSDN both were not very helpful in trying to find out what went wrong.

The stack trace informed me that the error occurred when the SPContentType.Update() method was called. So I started up Reflector (this really is a must-have tool for working with MOSS 2007, sad but true) to see what was happening in that method. After a while I found out that when you access the AvailableContentTypes property, a read-only collection of SPContentType objects is returned. Everything worked fine when I used the SPWeb.ContentTypes collection.

Right, that pretty much made me feel like banging my head against the wall. I spent a couple of hours trying to figure out why it wouldn’t work and it turns out to be so easy. Those are the moments when you wish that the MSDN documentation would contain more information than just a basic description of a property.

Of course, thinking about it, it probably makes sense that the AvailableContentTypes property is read-only. After all, it does return content types that belong to the parent site and you probably won’t want to go around editing those directly from a child site. Oh well, let us just be grateful that Reflector exists.

For all of those who have been living in a cave lately, here is a link to the site where Reflector can be downloaded:

http://www.red-gate.com/products/reflector/