Friday, 21 October 2011

Unexpected Error on SharePoint Web Analytics Page

We have recently deployed another new branded look and feel to our SharePoint 2010 intranet.  It comes with heavy customization and of course a custom master page.  Everything works fine until we hit the web analytics report under site settings – the famous SharePoint unexpected error. 

We went to the ULS log and found the following error:

System.ArgumentException: Could not find the sitemap node with URL '/_layouts/WebAnalytics/WebAppSelection.aspx'.  

This seems strange to me as we used to have an old customized master page and it didn’t have the same problem.  It’s quite a common problem after some Google, but none of the mentioned solutions (http://social.technet.microsoft.com/Forums/en/sharepoint2010setup/thread/2022cff0-e2cf-4702-bb2a-17dc91dd4247) really works for us.

Just a quick run down of our custom master page: we have 3 heavy customized navigation control: Global navigation, side navigation, as well as a footer (doormat) navigation. 

I start to remove these navigation one by one from the master page – and eventually I found that the footer navigation is the one that is causing the problem. And what’s more, it is the SiteMapDataSource object that is causing the problem.  This is strange, because the global navigation and the side navigation is using the same site map provider. 

After bit of playing around, I found the solution – by moving the footer data source control before the QuickLaunchDataSource delegate control (or the side side navigation).  I don’t have any explanation, but it looks like you cannot declare any SiteMapDataSource after the Quick Launch (or Side Nav) data source.

Friday, 10 June 2011

SharePoint 2010 Ribbon stuck at “Loading…”

Do you experience the following:
  • The ribbon is stuck at “Loading…” and never comes up, even you are using out of the box SharePoint site template and master page. 
  • Your site is setup with a DNS/host header, rather than the actual machine name.
  • There is a “.” in your DNS host (e.g. sp2010.dev). 
  • The ribbon works if you access the site using FQDN (e.g. sp2010.dev.domain.com)
  • Your SharePoint environment is running on either Windows 7 or Windows 2008 R2. 

If you answer “Yes” for all of the questions above, you are in the right place. 

image
This problem is actually caused by a new concept called DNS Devolution that Microsoft introduced in Windows 7 and Windows 2008 R2.  This concept give a finer controls over how far you search up the tree with an unqualified name.  For example, if you host name is sp2010, Windows will resolve it to sp2010.domain.com.  However, if you have a “.” in your hostname, e.g. sp2010.dev, Windows 7 and Windows 2008 R2 will not append the DNS search suffix to it. 

To solve the ribbon “Loading…” problem, you need to allow DNS suffix append to unqualified multi-label name.  To do that, run gpedit.msc on the server, Browse Local Computer Policy > Computer Configuration > Administrative Templates > Network > DNS Client.  Enable “Allow DNS Suffix Appending to Unqualified Multi-Label Name Queries”.  Then flush the DNS by running a ipconfig /flushdns in command prompt.  Refresh your site and your ribbon should work!


image

Monday, 31 January 2011

Reverting Claim Based Authentication to Classic Mode Authentication

Let me start with my story first: I have 3 different SharePoint environments:

  • Development with Classic Mode (Windows)
  • Testing with Claim Based
  • Production with Classic Mode (Windows)

Sometimes I will need to move a site from development to the testing environment, and since they are using different types of authentication, I will need to execute the $webApp.MigrateUsers($true) to convert the stored username in the content database from the “Windows” format (DOMAIN/username) to Claim based format (i:0#.w|DOMAIN/username). 

And now here’s a very stupid things that I have done last week.  I tried to move a site from Testing to the Production environment.  I went to the site permission and noticed the most of the username are stored in the Claim Based format, and the user is getting access denied error.  Immediately I think of the MigrateUsers($true) command that I mentioned above and I executed it (Yes, a real stupidity as a SharePoint guy).  Ops, other sites sit under the same Web Application starts to bound users off with an “Access Denied” error.  I can’t even login to the site with the farm administrator account. 

I immediately search for any information about MigrateUsers and found the Microsoft Official description here.  It says “true to move users to claims authentication; false to move users from claims authentication”.  Cool, this is a life saver and all I need to do is execute this command passing in false.  Run this and I am getting “Operation is not valid due to the current state of the object”. 

image

No good again.  My colleague opened up reflector and tried to look at the MigrateUsers method, and what we have noticed is the method intentionally throws an exception when we pass in false as the parameter.  Why??? I did more searching and found out that Claims to Classic mode is not currently supported by Microsoft. 

I went into the content database and checked the UserInfo table and have noticed that all logins had been converted to claim based format.  I tried to change one of the login back to Windows format (by the way, modification directly to SQL is not supported), but still not success. 

I did another test by creating a new site collection in a new content database under the same web application, and setting my Windows account as the site collection administrator.  In theory, this should work as everything is clean, however I am still getting “Access Denied”. 

Based on the above testing and investigation, we can prove that:

  • MigrateUsers($true) convert all username to “Claim-Based” format and this change applies to every content database under the same web application. 
  • Since the site is still using Windows Classic mode, when a user access the site, it will take the username in Windows format.  However, when it compares the “Windows” username with the converted claim based format username in the content database.  Since they are different, it returns an access denied error. 
  • Based on the result of the new site collection testing mentioned above, the MigratedUsers command will also do something to the web application, making it no longer accept “Windows” username.  Hence the web application is corrupted. 

After spending whole night in the office, finally I found a solution to roll back the changes: A command already existed in SharePoint 2007 fixed the problem, and it is MigrateUser.  Basically this command will convert one login to a new login. However this only works on single user every time. 

So here is my strategy for the problem:

  1. As mentioned, the web application is corrupted.  We will need to delete the corrupted web application and recreate a new one.  Make sure you keep an backup of the database
  2. After you recreate the web application, mount the content database(s). 
  3. Reset the Site Collection Administrator in Central Administration. 

Up to this point, you should be able to login to the site using the site collection administrator account that you defined in 3.).  And the next/final step that we need to do is to run the MigrateUser command to every users in the site collection.  To do that, I have implemented the following console application:

Code Snippet
  1. public Program(string url)
  2. {
  3.     using (SPSite site = new SPSite(url))
  4.     {
  5.         using (SPWeb web = site.RootWeb)
  6.         {
  7.             foreach (SPUser user in web.AllUsers)
  8.             {
  9.                 string username = GetClaimBasedUserName(user);
  10.                 if (!username.Equals(string.Empty))
  11.                 {
  12.                     Console.Write("Migrating {0} to {1}...", user.LoginName, username);
  13.                     try
  14.                     {
  15.                         SPFarm Farm = SPFarm.Local;
  16.                         Farm.MigrateUserAccount(user.LoginName, username, false);
  17.                         Console.WriteLine("Done");
  18.                     }
  19.                     catch (Exception ex)
  20.                     {
  21.                         Console.WriteLine(ex.Message);
  22.                     }
  23.                 }
  24.             }
  25.         }
  26.     }
  27. }
  28.  
  29. private string GetClaimBasedUserName(SPUser user)
  30. {
  31.     string username = string.Empty;
  32.     try
  33.     {
  34.         if (user.IsDomainGroup)
  35.         {
  36.             if (user.LoginName.StartsWith("c:0+.w|"))
  37.             {
  38.                 username = user.Name;
  39.             }
  40.         }
  41.         else
  42.         {
  43.             if (user.LoginName.StartsWith("i:0#.w|"))
  44.             {
  45.                 username = user.LoginName.Substring(7);
  46.             }
  47.         }
  48.     }
  49.     catch
  50.     {
  51.  
  52.     }
  53.     return username;
  54. }
  1. Basically the above code does the following:
  2. Grab all the users in the site collections,
  3. Get the current username which is in the claim based format
  4. Extract the “Windows” user name from the claim based username (Just trim the first 7 characters)
  5. Run the MigrateUser method

And after running this console application, your site is back up and running with Classic mode.

Thursday, 6 January 2011

Error Creating User Profile Connection - Unable to process Create Message

I have managed to setup User Profile Synchronisation in a few different environments without any problem - Connection created, FIM services are running, user profiles are imported successfully. 

Recently I was trying to setup a new server farm in the production environment.  I followed the exact same procedure as I always do, but this time I am getting a new error message when I tried to create a new User Profile Connection - Unable to process Create Message:


And I was getting these errors in the error log:


Microsoft.ResourceManagement.Service: System.InvalidOperationException: Retrieve schema failed
   at Microsoft.ResourceManagement.ActionProcessor.SyncConfigActionProcessor.Create(String typeName, IList`1 createParameters, Guid creator, Guid cause)
   at Microsoft.ResourceManagement.ActionProcessor.SyncConfigActionProcessor.ProcessInputRequest(RequestType request)
   at Microsoft.ResourceManagement.ActionProcessor.ActionDispatcher.ProcessInputRequest(RequestType request)
   at Microsoft.ResourceManagement.WebServices.RequestDispatcher.ExecuteAction(RequestType request)
   at Microsoft.ResourceManagement.WebServices.RequestDispatcher.ExecuteAction[ResponseBodyType](RequestType request)
   at Microsoft.ResourceManagement.WebServices.RequestDispatcher.DispatchRequest[ResponseBodyType](RequestType request, Guid requestIdentifier, Object redispatchSingleInstanceKey)
   at Microsoft.ResourceManagement.WebServices.RequestDispatcher.DispatchRequest[ResponseBodyType](RequestType request)
   at Microsoft.ResourceManagement.WebServices.ResourceManagementService.Create(Message request)

This is weird because I followed the exactly same procedure as the other environments.  FIM services are running, service accounts are setup properly, Net Bios Name flag enabled for the UPS, what was going wrong? Someone got the same error as me here and they fixed the problem by pointing the Sync to a Windows 2008 Domain Controller:
http://social.technet.microsoft.com/Forums/en-US/sharepoint2010setup/thread/6c68c067-5b2a-482f-925b-5a0a25759344

However I have confirmed both the domain controller of production environment and other environments Windows 2003.  So what was wrong?  I spent 2 overnights, and one premium support called was raised and I was still going no where, where I was 4 days away from the production launch. 

I didn't install the August cumulative updates due to an error I posted earlier (link).  Then I thought....well, worth a try as I have tried out every thing already.  And you know what? That fixed the problem! And this also fixed the August CU problem that I had earlier (it broke the User Profile Service Application). 

So if you are getting this error when you create an user profile synchronisation connection, here are the two things that you should do:
  1. Check your domain controller
  2. Install the August (or later) Cumulative Updates

SharePoint 2010 August Cumulative Updates breaks ForeFront Identity Manager

I have recently came across a bug in the Newsfeed Settings. I have gone into my newsfeed setting and unchecked some of the activity types. The first time I done it, it was fine. However when I try to do it again, I noticed that my changes doesn't get saved.

Found nothing out there, so I decided to apply to SharePoint 2010 August Cumulative Updates:
SharePoint Foundation 2010: kb2266423
SharePoint Server 2010: kb2352342

After I applied the updates, my problems gone! I can now save my newsfeed settings.

BUT!!!! (You know that I am going to say this, right?) It produces another problem.

As usual, after the reboot required by the updates, my two ForeFront Identity Manager services have stopped. The Forefront Identity Manager Service has started successfully, but I couldn't get the Synchronization Service start.

I am getting the following error:


And when I checked my error log, I am getting a WorkflowManagerException: Forefront Identity Management Service does not support workflows of type 'Microsoft.ResourceManagement.Workflow.Activities.SequentialWorkflow, Microsoft.ResourceManagement, Version=2.0.2450.5" error:


To fix this problem, simply delete your User Profile Service Application, and recreate a new one.  Somehow the cumulative update does not work with the service application that was created using the old binary?

Fixing SharePoint 2010 Lookup Drop Down 20 Items Limit

As you might have noticed already, SharePoint (both 2007 and 2010) will render your lookup drop down column differently depends on the number of item in the drop down list. If you have less than 20 items, SharePoint will just render the drop down using the normal drop down list control.

However SharePoint will change the rendering style when there is more than 20 items. Instead of the simple drop down list control, it will be changed to a textbox + ajax + filtering list, like the following:



So when you start typing in the textbox, it filters the options, allows the user to select an item from a large list. There are pros, and of course there are cons:
- If you decide to select from the list, you need to do a double click in order to pick an option
- The free-text box gives an impression that you can free-text, which ends up saving empty string
- Inconsistent user experience if you have other drop down list in the form that has less than 20 items.
- There are some rendering issue for the Ajax Drop Down list in SharePoint 2010

To get around this, I have implemented a JQuery solution. Basically the script does the following if the drop down has more than 20 items:
- Hide the text box
- Hide the drop down arrow
- Provision the options in a simple drop down list, register an OnChange event to set the lookup hidden field that SharePoint is using.
- Add the drop down to the page.

Insert the following script to the New/Edit form (by using a content editor web part or custom ribbon javascript). I am assuming that you have a reference to the JQuery library in your master page.

Code Snippet
  1. <script>
  2. $(document).ready(function () {

  3. // Name of the column (Display Name)
  4. var columnName = "Lookup";

  5. // Override the Drop Down List
  6. OverrideDropDownList(columnName);

  7. // Main Function
  8. function OverrideDropDownList(columnName) {

  9. // Construct a drop down list object
  10. var lookupDDL = new DropDownList(columnName);

  11. // Do this only in complex mode...
  12. if (lookupDDL.Type == "C") {

  13. // Hide the text box and drop down arrow
  14. lookupDDL.Obj.css('display', 'none');
  15. lookupDDL.Obj.next("img").css('display', 'none');

  16. // Construct the simple drop down field with change trigger
  17. var tempDDLName = "tempDDLName_" + columnName;
  18. if (lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").length == 0) {
  19. lookupDDL.Obj.parent().append("<select name='" + tempDDLName + "' id='" + tempDDLName + "' title='" + tempDDLName + "'></select>");

  20. lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").bind("change", function () {
  21. updateOriginalField(columnName, tempDDLName);
  22. });
  23. }

  24. // Get all the options
  25. var splittedChoices = lookupDDL.Obj.attr('choices').split("|");

  26. // get selected value
  27. var hiddenVal = $('input[name=' + lookupDDL.Obj.attr("optHid") + ']').val()
  28. if (hiddenVal == "0") {
  29. hiddenVal = lookupDDL.Obj.attr("value")
  30. }

  31. // Replacing the drop down object with the simple drop down list
  32. lookupDDL = new DropDownList(tempDDLName);

  33. // Populate the drop down list
  34. for (var i = 0; i < splittedChoices.length; i++) {
  35. var optionVal = splittedChoices[i];
  36. i++;
  37. var optionId = splittedChoices[i];

  38. var selected = (optionId == hiddenVal) ? " selected='selected'" : "";
  39. lookupDDL.Obj.append("<option" + selected + " value='" + optionId + "'>" + optionVal + "</option>");
  40. }
  41. }
  42. }

  43. // method to update the original and hidden field.
  44. function updateOriginalField(child, temp) {
  45. var childSelect = new DropDownList(child);
  46. var tempSelect = new DropDownList(temp);

  47. // Set the text box
  48. childSelect.Obj.attr("value", tempSelect.Obj.find("option:selected").val());

  49. // Get Hidden ID
  50. var hiddenId = childSelect.Obj.attr("optHid");

  51. // Update the hidden variable
  52. $('input[name=' + hiddenId + ']').val(tempSelect.Obj.find("option:selected").val());
  53. }

  54. // just to construct a drop down box object. Idea token from SPServces
  55. function DropDownList(colName) {
  56. // Simple - when they are less than 20 items
  57. if ((this.Obj = $("select[Title='" + colName + "']")).html() != null) {
  58. this.Type = "S";
  59. // Compound - when they are more than 20 items
  60. } else if ((this.Obj = $("input[Title='" + colName + "']")).html() != null) {
  61. this.Type = "C";
  62. // Multi-select: This will find the multi-select column control on English and most other languages sites where the Title looks like 'Column Name possible values'
  63. } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).html() != null) {
  64. this.Type = "M";
  65. // Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like 'Выбранных значений: Column Name'
  66. } else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).html() != null) {
  67. this.Type = "M";
  68. } else
  69. this.Type = null;
  70. } // End of function dropdownCtl
  71. });
  72. </script>
The script will then turn the complex drop down list (if it has more than 20 items) to the simple drop down list:



Originally I extended the SPServices JQuery Library to fix the drop down issue. So you may find that my scripts contains little portion of the library. I have integrated the above solution with the Cascading Drop Down list function provided by the SPServices library. Let me know if you want to obtain the extended script.
Update:
Since there are request for the Extended SPServices, I have make it available for download. You can download it from here. (Please rename the extension from .txt to .js)
Few notes before you use this:

1. This extended version is based of SPServices version 0.5.7

2. I have extended SPCascadeDropDowns and called the new function SPCascadeDropDownsEx

3. The original SPServices expects you to have the parent-child list in the following format:
Parent1 - Child1a
Parent1 - Child1b
Parent1 - Child1c
Parent2 - Child2a
Parent3 - Child3a
Parent3 - Child3b

However I have changed this so that it works with a child list and a parent list. For example, I have a child list with list item Child1a, Child1b, Child1c, Child2a, Child3a, Child3b. And then I create a parent list with a multi-valued lookup child colume like this:

Parent1 - Child1a, Child1b, Child1c
Parent2 - Child2a
Parent3 - Child3a, Child3b

(Sorry for the rough explanation, I will include screenshots when I have the system around)

And this is how I invoked the function:
// Enable Cascading Drop down for Department
$().SPServices.SPCascadeDropdownsEx({
relationshipList: "Department",
relationshipListParentColumn: "Title",
relationshipListChildColumn: "AreaOfImpact",
parentColumn: "Department",
childColumn: "Area Of Impact",
relationshipListType: "LookupMultiValue",
promptText: "--- Please select ---",
debug: false
});
So in the above script:
- relationshipList is the parent list
- relationshipListParentColumn is the name of the parent column in the parent list
- relationshipListChildColumn is the name of the childLookup column in the parent list
- parentColumn is the display name of the parent drop down list
- childColumn is the display name of the child drop down list
- relationshipListType is the type of the relationshipListChildColumn in the parent list