Showing posts with label Sharepoint. Show all posts
Showing posts with label Sharepoint. Show all posts

05 December, 2012

Enable CSS3 / HTML5 in SharePoint 2010

Ok i guess everyone that works with SharePoint 2010 has been hunting the holy grail of Sharepoint:
How to enable CSS3 / HTML5.

Or maybe not, but i have, and all the answers i have found so far has failed..

I really can't understand why SharePoint is so bound to IE8.

Latest version of Chrome / FireFox, they all work like a "charm" in SharePoint with CSS3 support.
But IE9 made by Microsoft ?? Nope.

Well it works but in IE8 mode only with no CSS3 support, the document mode is forced by the following meta tag in the master page:

<meta http-equiv='X-UA-Compatible' content='IE=8'/>

If you remove the tag, it renders CSS3 "perfectly" in IE9.
But unfortunately a lot of system functionality starts to fail.
Like saving a page, cancel out of a save, Peoplepicker starts failing etc.

I found one javascript fix for fixing the people picker.
But that was really huge and ugly.
Several other issues still exists, even if you fix that one.

Anyway so far i have noticed that the problems only happens when in edit mode.
So i decided to switch the IE8 document mode on / off dependant of the page mode.
Getting everything to work in IE9 mode seems to be impossible for now.

So when in View mode, then remove the IE8 meta tag and render CSS3.
If not then render the meta tag which will remove CSS3 and make the standard system functionality work.
There is also a second condition we need to check, we need to check if the page is in web part edit mode.
If we don't include the IE8 meta tag in this mode, you are not able to move the webparts within the zones.

Well basically the same situation, both are edits but needs to be handled separately.

I also included a "emergency" fallback, just add ?ie8=1 to the current url if you still need to turn
off IE9 mode in a page.
Or ?ie8=0 to turn on IE9 mode in a edit page or whatever for testing.

somesite.com?ie8=1

UPDATE 21.01.2012
Had to do some changes to the rendering.
Because if you remove the tag completely then the IE compability mode button is displayed.
And some users tends to click on it and disable the css3 rendering.
Which of course causes a lot of problems.
The solution is to always render the tag, but with IE=edge.
Which basically means the latest version of IE.
I also rewrote the fallback so that you can decide which IE mode to use:
somesite.com?IE=8
somesite.com?IE=9
somesite.com?IE=edge

So how can we do this (Updated code):

1. Create a new assembly, sign it, add this class:
namespace MyAssembly.UI.WebControls
{
    using System.Web.UI;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;
    using System.Web.UI.WebControls.WebParts;

    public class LegacyDocumentModeFix : LiteralControl
    {
        protected override void Render(HtmlTextWriter output)
        {
            const string documentMode = "<meta http-equiv='X-UA-Compatible' content='{0}'/>";
            string ieMode = "IE=edge";
            string queryMode = Page.Request.QueryString["IE"];
            if (!string.IsNullOrEmpty(queryMode))
            {
                ieMode = "IE=" + queryMode;
            }
            else
            {
                System.Web.HttpBrowserCapabilities browser = Page.Request.Browser;
                if (browser.Browser == "IE")
                {
                    SPControlMode mode = SPContext.Current.FormContext.FormMode;
                    if (mode != SPControlMode.Display)
                    {
                        ieMode = "IE=8";
                    }
                    else
                    {
                        WebPartManager manager = WebPartManager.GetCurrentWebPartManager(Page);
                        if (manager != null)
                        {
                            if (manager.DisplayMode != WebPartManager.BrowseDisplayMode)
                            {
                                ieMode = "IE=8";
                            }
                        }
                    }
                }
            }

            output.Write(documentMode, ieMode);
            base.Render(output);
        }
    }
}

2. Include the assembly in your sharepoint project:

Package, Advanced, Add, Add assembly from project output.
And make it a safe control.

3. Add a reference in the master page:
<%@ Register Tagprefix="Fix" Namespace="MyAssembly.UI.WebControls" Assembly="MyAssembly.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=yourassemblykeytoken" %>

4. Replace the meta tag inside the <head> tags in your master page:
Replace:
<meta http-equiv='X-UA-Compatible' content='IE=8'/>

With:
<Fix:LegacyDocumentModeFix runat="server"/>

Thats it, deploy.
CSS3 renders nicely in view mode, and degrades to IE8 mode in edit / new mode
which is ok for now.

Until SharePoint 2013 comes along ;)

I haven't tested every functionality in SharePoint with this fix, only the publishing part that i use.
Some of the site settings functionality, all site actions etc.
Haven't found any problems yet..

And i use this control both in my custom masterpage and in my v4 system masterpage.

If you still find something that fails, Please let me know.

29 September, 2012

Renaming a Term in SharePoint 2010 Glitch

I have been working a lot with SharePoint lately, struggling with all kinds of strange problems.
So it's about time to update this blog and share some of my findings.

Let's start with renaming a Term in the Taxanomy Term Store. I found some glitches regarding renaming Terms, so let's look into this in detail.

Prerequisites

Let's say we have a Termset Named Organization, and a Term named Logistics, like this:
Then we have a List with the following Taxaonomy Fields:  

Field            Type    
Organization     TaxanomyFieldType
OrganizationTaxHt0     Note
TaxCatchAll LookupMulti
TaxCatchAllLabel LookupMulti

One document is uploaded and tagged with Logistics like this:



Getting Field Information

Now let's write come code to Display the Taxonomy Fields for a given ListItem:
private void DisplayFields(SPListItem listItem)
{
    foreach (SPField field in listItem.Fields)
    {
        if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
        {
            TaxonomyField taxanomyField = (TaxonomyField)field;
            if (taxanomyField.AllowMultipleValues)
            {
                TaxonomyFieldValueCollection values = listItem[taxanomyField.Id] as TaxonomyFieldValueCollection;
                if (values.Count == 0)
                {
                    WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3}, 
                    TaxonomyFieldValue is null", field.Id, field.TypeAsString, field.Title, field.InternalName));
                }
                else
                {
                    foreach (TaxonomyFieldValue value in values)
                    {
                        DisplayField(field, value);
                    }
                }
            }
            else
            {
                TaxonomyFieldValue value = listItem[taxanomyField.Id] as TaxonomyFieldValue;
                DisplayField(field, value);
            }
        }
        else if (field.TypeAsString == "LookupMulti")
        {
            if (field.InternalName == "TaxCatchAll" || field.InternalName == "TaxCatchAllLabel")
            {
                SPFieldLookupValueCollection lookupValues =(SPFieldLookupValueCollection)listItem[field.Id];
                if (lookupValues.Count == 0)
                {
                    WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
                    LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, 
                    field.Title, field.InternalName, "<empty>", "<empty>"));
                }
                else
                {
                    foreach (SPFieldLookupValue lookupValue in lookupValues)
                    {
                        WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
                        LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName, 
                        lookupValue.LookupValue, lookupValue.LookupId));
                    }
                }
            }
            else
            {
                //Other multi
                SPFieldLookupValueCollection lookupValues =(SPFieldLookupValueCollection)listItem[field.Id];
                if (lookupValues.Count == 0)
                {
                    WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
                    LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName, 
                    "<none>", "<none>"));
                }
                else
                {
                    foreach (SPFieldLookupValue lookupValue in lookupValues)
                    {
                        WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
                        LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName, 
                        lookupValue.LookupValue, lookupValue.LookupId));
                    }
                }
            }
        }
        else if (field.TypeAsString == "Note")
        {
            TaxonomyField taxField = GetTaxonomyTextField(listItem, field);
            if (taxField != null)
            {
                WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3}, Value: {4},
                RelatedTaxFieldInternalName: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName, 
                listItem[field.Id], taxField.InternalName));
            }
            else
            {
                WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3}, Value: {4}", 
                field.Id, field.TypeAsString, field.Title, field.InternalName, listItem[field.Id]));
            }

        }
        else
        {
            WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},Value: {4}", 
            field.Id, field.TypeAsString, field.Title, field.InternalName, listItem[field.Id]));
        }
    }
}

private TaxonomyField GetTaxonomyTextField(SPListItem listItem, SPField noteField)
{
    foreach (SPField field in listItem.Fields)
    {
        if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
        {
            TaxonomyField taxanomyField = (TaxonomyField)field;
            if (taxanomyField.TextField == noteField.Id)
            {
                return taxanomyField;
            }
        }
    }

    return null;
}

private void DisplayField(SPField field, TaxonomyFieldValue fieldValue)
{
    if (fieldValue == null)
    {
        WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
        TaxonomyFieldValue is null", field.Id, field.TypeAsString, field.Title, field.InternalName));
    }
    else
    {
        WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
        TermLabel: {4}, TermId: {5}, WssId: {6}", field.Id, field.TypeAsString, field.Title, field.InternalName, 
        fieldValue.Label, fieldValue.TermGuid, fieldValue.WssId));
    }
}

Now let's view the fields on our listitem:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument

TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization

TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11

TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11

TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword

TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics#?|, LookupId: 11

TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
So far so good, Organization, OrganizationTaxHTField0, TaxCatchAllLabel all says Logistics.

Renaming the Term

Now go into the Taxanomy Term Store in Central Admin and rename Logistics to Logistics1 and hit save.
View the fields again:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument

TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization

TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11

TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11

TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword

TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics#?|, LookupId: 11

TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
No change of course because the list's are not updated immediatly, but through a job named Taxanomy Update Scheduler.

Go into Central Administration, Monitoring, Review Job Definitions, Find the Taxanomy Update Scheduler job, there will be one for each site collection. So be sure to pick the right one.
Now run the job. (The Job is scheduled to run every hour).
Make sure it has executed properly by looking at the Timer Job Status, it should appear in the History.
Ok let's look at our fields again:

Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument

TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization

TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics1, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11

TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11

TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword

TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics1#?|, LookupId: 11

TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
The Note field, OrganizationTaxHTField0 is not updated properly.

But if we look at the list:

Seems like it's updated, viewing and editing the properties of this field also displays Logistics1. If you edit the properties, then hit save, then the Note field is properly updated to Logistics1.

Fixing the Problem

Anyway, i don't like things that are not 100%, so let's see how we can refresh the Note Field from code.
I tried a lot of things, but found one solution that works, here is the code:
private void RefreshTaxanomyFieldsOnListItem(SPListItem listItem, string taxFieldInternalName, string taxFieldNewLabel)
{
    bool changed = false;
    for (int i = 0; i < listItem.Fields.Count; ++i) //Dont use foreach
    {
        SPField field = listItem.Fields[i];
        if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
        {
            if (string.IsNullOrEmpty(taxFieldInternalName) || field.InternalName == taxFieldInternalName)
            {
                TaxonomyField taxanomyField = (TaxonomyField)field;
                if (taxanomyField.AllowMultipleValues)
                {
                    TaxonomyFieldValueCollection values = listItem[taxanomyField.Id] as TaxonomyFieldValueCollection;
                    if (values != null)
                    {
                        foreach (TaxonomyFieldValue value in values)
                        {
                            if (string.IsNullOrEmpty(taxFieldNewLabel) || value.Label == taxFieldNewLabel)
                            {
                                WriteVerboseMessage(string.Format("Refreshing Taxanomy Multi Field: {0}", 
                                taxanomyField.InternalName));
                                taxanomyField.SetFieldValue(listItem, values);
                                changed = true;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    TaxonomyFieldValue value = listItem[taxanomyField.Id] as TaxonomyFieldValue;
                    if (value != null)
                    {
                        if (string.IsNullOrEmpty(taxFieldNewLabel) || value.Label == taxFieldNewLabel)
                        {
                            WriteVerboseMessage(string.Format("Refreshing Taxanomy Single Field: {0}",
                            taxanomyField.InternalName));
                            taxanomyField.SetFieldValue(listItem, value);
                            changed = true;
                        }
                    }
                }
            }
        }
    }

    if (changed)
    {
        WriteInformationMessage(string.Format("Updated File: {0}, Id: {1}, Title: {2}", listItem.Url,
        listItem.UniqueId.ToString(), listItem.Title));
        listItem.SystemUpdate(false);
    }
}

So let's run this code on our list, then look at our fields again:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument

TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics1|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization

TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics1, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11

TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11

TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword

TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics1#?|, LookupId: 11

TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.

Finally everything is correct.

I wrote a Commandlet that did this refresh, but i didn't want to execute the Taxanomy Update Scheduler job manually every time so i found this: TaxonomySession.SyncHiddenList(site);
I thought that this was the command that the Taxanomy Update Scheduler job was executing, but thats not the case.
If i ran this command, no changes where made to the list, if i ran this command after running the Taxanomy Update Scheduler job then the list item was reverted back to the old name in all fields ??
If anyone can tell me where i can find the source for the Taxanomy Update Scheduler job i would be really happy, didn't have time to investigate this any further.

Anyway i decided to run the job before i executed the RefreshTaxanomyFieldsOnListItem like this:
private void RefreshHiddenList(SPSite site, int timeoutSeconds)
{
    foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)
    {
        if (job.Name.Equals("UpdateHiddenListJobDefinition") && job.Status == SPObjectStatus.Online)
        {
            DateTime dateStart = DateTime.Now;
            job.RunNow();

            while (job.LastRunTime < dateStart)
            {
                if (DateTime.Now > dateStart.AddSeconds(timeoutSeconds))
                {
                    Console.WriteLine("Taxanomy Update Scheduler did not finish executing due to a timeout !");
                    break;
                }

                Thread.Sleep(1000);
            }

            break;
        }
    }
}

So the correct sequence are:
1. Rename the Term.
2. Run RefreshHiddenList() or run the Taxaonomy Update Scheduler.
3. Run RefreshTaxanomyFieldsOnListItem().

I'm not sure what the consequences are when the Note field isn't updated as it should. But if you are using the Note field in your custom code, webparts etc. you should be aware of that it could be wrong.

22 March, 2012

VMware image with Sharepoint 2010, MSSQL 2012, SSRS, Performance Point and PowerPivot

This is how you can install a VMware Image with Windows 2008 r2, SharePoint 2010 and Microsoft SQL Server 2012 Denali.
I will also show you how you can configure SSRS, Performance Point and PowerPivot.
At first i tried to install this without a domain, and got everything working except PowerPivot.
At present time there is no workarounds for getting PowerPivot to run in a non domain environment.
Bummer, so i had to scrap it and start over with a domain.
Anyway installing a domain is pretty easy, so just live it and use a domain in your SharePoint playground.
It just makes your life so much easyer.

Sorry no screenshots, if there are parts you don't understand please contact me.

I spent a couple of days doing this, so maybe someone will find this useful.

Install windows

  1. Install VMware Workstation.
  2. Install Windows 2008 R2 With SP1 with username dev.
  3. Change computername to DevServer.
  4. Turn off the Firewall.
  5. Turn off UAC.
  6. Turn off IE ESC (Server manager down on the right).
  7. Add server roles: Web Server (IIS) and Application Server.
  8. Run windows update over and over again, until the green checkmark of delight appears. (Microsoft PLEASE, 2012 and this is still not automated in any way) .
  9. Power off the VM, and take a snapshot. VMware can only clone snapshots taken during power off (just a little tip if you need to clone it in a later life).

Install the domain controller

  1. Click Start, then run.
  2. Type in dcpromo and hit enter.
  3. Don't check use advanced mode installation.
  4. Select create a new domain in a new forest.
  5. FQDN of the forest root domain: xtrm.local or whatever.
  6. Forest functional level, just select windows 2008 r2.
  7. Select additional options for this domain controller, make sure DNS server is selected.
  8. Change to static IP in the IPV4 settings of your network card.
  9. For example IP: 10.10.10.1, Subnet mask 255.255.255.240.
  10. Continue the wizard: Select no, i will assign static IP addresses to all physical network adapters.
  11. A delegation for this DNS server could not be created. Click Yes.
  12. Location for Database, Log Files ans SYSVOL, just leave the defaults.
  13. Directory services restore mode administrator password: Use a strong password.
  14. Summary, click next, click Finish, and reboot.
  15. Switch back to dhcp, obtain ip address and dns automatically because of internet access through the VM.
  16. Now start Active Directory Users and Computers.
  17. Create 3 new users: sqladmin, spadmin, spfarm. Use same password, user cannot change password, password never expires.
  18. Add spadmin to the Domain Admins group.
  19. Reboot, and we are done with setting up the domain.

Install Microsoft Sql Server 2012

  1. When installing MSSQL 2012 Denali, make sure you install these options: Reporting Services - Native, Reporting Services - SharePoint, Reporting Services Add-in for SharePoint Products.
  2. In other words, select all features except the replay controller / client.
  3. Install a sql instance named SharePoint.
  4. Run all services with the xtrm\sqladmin user.
  5. Add dev, sqladmin, spadmin and spfarm user to the sql server / analysis services administrator group.
  6. Install and Reboot.
  7. Microsoft SQL Server 2012 is now up and running.

Install SharePoint 2010

  1. Login as spadmin, IMPORTANT !.
  2. Install SharePoint 2010 Prerequisites.
  3. Start the SharePoint 2010 server install.
  4. Wait until done.
  5. Run the SharePoint Products Configuration Wizard (Started automatically).
  6. Select Create a new server farm.
  7. Database server: DevServer\SharePoint
  8. Database name: SharePoint_Config
  9. Database Access account: xtrm\spfarm
  10. Passphrase: Enter a strong password.
  11. Force port number for SharePoint Central Administration.
  12. Select NTLM.
  13. Finish the wizard.
  14. Start SharePoint Central Administration,, configure your SharePoint farm (If not automatically started).
  15. Service account: Use existing xtrm\spfarm.
  16. Create a top level site, Team Site or whatever.
  17. SharePoint 2010 is now up and running in a farm on Microsoft SQL Server 2012.

Configure Reporting Services in SharePoint 2010

  1. Start SharePoint Central Administration.
  2. Click on General Application Settings.
  3. Under SQL Server Reporting Services, click on Reporting services integration.
  4. DON'T bother, because this is for SQL Server 2008 /2008 R2 only.
  5. This is how you integrate against SQL Server 2012:
  6. Start SharePoint 2010 Management Shell, right click and do a run as administrator. IMPORTANT !
  7. Type in: Install-SPRSService
  8. Type in: Install-SPRSServiceProxy
  9. Type in: get-spserviceinstance -all |where {$_.TypeName -like "SQL Server Reporting*"} | Start-SPServiceInstance
  10. Now go back to SharePoint Central Administration.
  11. Click on System Settings, Manage services on server.
  12. Then you will see a service named: SQL Server Reporting Services Service with status Started.
  13. Goto Application management, Service Applications, Manage Service Applications.
  14. Select New then SQL Server Reporting Services Service Application.
  15. Name: SSRSApp, New app pool: SSRSAppPool, App pool account: xtrm\spfarm.
  16. Select windows authentication, select web application association: SharePoint 80.
  17. And we are done with SSRS integration against SharePoint 2010.

Configure Performance Point in SharePoint 2012

  1. Just make sure PerformancePoint Service is started in System Settings, Manage Services on Server.
  2. And that the Performance Point Service application has been created under Application Management, Manage Service Applications.

Configure PowerPivot in SharePoint 2010

  1. Login as spadmin.
  2. PowerPivot is a separate install, so first we need to install it.
  3. Run Microsoft Sql Server Setup.
  4. Add features to an existing installation.
  5. Next screen, you need to select Perform a New installation of Sql Server 2012.
  6. Then select Sql Server PowerPivot for SharePoint, do not select add database instance.
  7. It will default to named instance PowerPivot.
  8. Run the Sql Server Analysis Services as the xtrm\sqladmin account.
  9. Add dev, spadmin, spfarm and sqladmin as administrators.
  10. Finish the wizard and it will be installed.
  11. Start the PowerPivot Configuration Tool.
  12. Select Configure or Repair PowerPivot For SharePoint.
  13. Default Account: xtrm\spfarm
  14. Database Server: devserver\SharePoint
  15. Enter SharePoint passphrase.
  16. Click on Register Sql Server Analysis Services (PowerPivot) on local server and enter password for sqladmin user.
  17. Click Validate, then Run.
  18. Click yes on the confirm messagebox.
  19. And wait until it succeeds.
  20. Start SharePoint Central Administration
  21. If you look at System, Services On Server, you now have a new service named: SQL Server PowerPivot System Service.
  22. Goto Application Management, Manage Service Applications.
  23. You will have a new Service Application named: Default PowerPivot Service Application.
  24. Now there are 2 farm solutions that has been deployed in the farm.
  25. To view them goto System Settings, Manage farm solutions.
  26. PowerPivotfarm.wsp is globally deployed.
  27. PowerPivotWebApp.wsp is deployed to each web app.
  28. So if you create a new webapp, then you need to deploy this solution to the new webapp.

Great, finally everything is installed and configured.
Later i will write a post about how you actually use what you have installed:
  • SSRS both Native and SharePoint.
  • Performance Point.
  • PowerPivot.

A final tip: ALWAYS run IE and SharePoint Management Console as administrator.
(Right click and run as administrator). If you don't do this there will be a lot of menu options in
SharePoint Central Administration that will be missing.
And the scripts will fail with some cryptic errors.
If you find any errors above, unnecessary steps or other ways to improve the sequence,
please let me know.

07 March, 2011

More readable exception messages in Sharepoint 2010

I have the same trouble everytime i upgrade to a newer sharepoint version,
exception messages that doesn't tell me anything.
And everytime i search the net i find a lot of answers that is not working.

So after investigating this, i found the correct way to turn off custom error
message in sharepoint (This means enabling the display of the actual error message).

This picture shows default error meassages in sharepoint, not
very helpful when developing webparts:

Now to enable more useful error messages, which means turning OFF custom error messages:

Navigate and edit the web.config file located here:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\web.config

Change this line:
From:
<customErrors mode="On" />
To:
<customErrors mode="Off" />
Now when an exception occurs in any sharepoint site, it will look like this:

Much better, but if you are developing web parts you might want to turn on the call stack too.
To do this, navigate to the web.config file located under your web site, like:
C:\inetpub\wwwroot\wss\VirtualDirectories\MyTestSite80\web.config

Change callstack in this line:
From:
<SafeMode MaxControls="200" CallStack="false" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
To:
<SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">

Now when an exception occurs in any sharepoint site, it will look like this:

So just to clarify:
There is NO need to enable debug in any web.config files (debug=true).
There is NO need to change customerrors mode inside the web.config under the site, this can be left On.
There is NO need to change this file: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\web.config.

04 March, 2011

Custom web part pages with navigation, Sharepoint 2010

So even in sharepoint 2010, microsoft has still not added an option to add custom web part pages, just unbelievable :)
So we are still stuck with editing system files manually to get the look that we want.
Another thing that really annoys me is that a web part page has no navigation on the left side , i really can't see
any use of a sharepoint page without navigation.

Anyway let's see how we can make this work a little better.

When creating a web part page in sharepoint there are 8 available types which can be selected:
Web Part PageIndexContentPageImage
Header, Footer, 3 Columns2spstd2.aspxspstd2.gif
Full Page, Vertical1spstd1.aspxspstd1.gif
Header, Left Column, Body3spstd3.aspxspstd3.gif
Header, Right Column, Body4spstd4.aspxspstd4.gif
Header, Footer, 2 Columns, 4 Rows5spstd5.aspxspstd5.gif
Header, Footer, 4 Columns, Top Row6spstd6.aspxspstd6.gif
Left Column, Header, Footer, Top Row, 3 Columns7spstd7.aspxspstd7.gif
Right Column, Header, Footer, Top Row, 3 Columns8spstd8.aspxspstd8.gif

The system content pages can be found here:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\1033\STS\DOCTEMP\SMARTPGS\spstd?.aspx
Graphical view of the types can be found here:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\1033\IMAGES\spstd?.gif
This file links to spcf.aspx
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\1033\STS\DOCTEMP\SMARTPGS\_webpartpage.htm
This is the actual web part page creation form, which will list the 8 different options.
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\spcf.aspx

Which contains:
<select id="onetidWebPartPageTemplate" name="WebPartPageTemplate" size="8" onchange="DoTemplateOptionChange()"> 
   <option value="2" selected="true"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option2%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="1"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option1%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="3"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option3%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="4"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option4%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="5"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option5%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="6"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option6%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="7"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option7%>" EncodeMethod='HtmlEncode'/></option> 
   <option value="8"><SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,webpagecreation_layout_option8%>" EncodeMethod='HtmlEncode'/></option> 
 </select>   
For some reason the different types are listed not in sequence, 1 and 2 are switched, sloppy  :)

Soulution 1:
If you want to include the left navigation bar in web part pages, then remove these lines from all of the spstd?.aspx files
...
<style type="text/css">
body #s4-leftpanel {
display:none;
}
.s4-ca {
margin-left:0px;
}
</style>
...
<asp:Content ContentPlaceHolderId="PlaceHolderLeftNavBar" runat="server"></asp:Content>
...
This will enable navigation, removing the style element is something new in sharepoint 2010.

If you want to customize a web part page like adding more web part zones etc you need to edit one of the spstd?.aspx pages.

The part which you need to change is this (located at the bottom in the file):

<table cellpadding="4" cellspacing="0" border="0" width="100%">
        <tr>
            <td id="_invisibleIfEmpty" name="_invisibleIfEmpty" colspan="3" valign="top" width="100%">
                <webpartpages:webpartzone runat="server" title="loc:Header" id="Header" frametype="TitleBarOnly" />
            </td>
        </tr>
        <tr>
            <td id="_invisibleIfEmpty" name="_invisibleIfEmpty" valign="top" height="100%">
                <webpartpages:webpartzone runat="server" title="loc:LeftColumn" id="LeftColumn" frametype="TitleBarOnly" />
            </td>
            <td id="_invisibleIfEmpty" name="_invisibleIfEmpty" valign="top" height="100%">
                <webpartpages:webpartzone runat="server" title="loc:MiddleColumn" id="MiddleColumn"
                    frametype="TitleBarOnly" />
            </td>
            <td id="_invisibleIfEmpty" name="_invisibleIfEmpty" valign="top" height="100%">
                <webpartpages:webpartzone runat="server" title="loc:RightColumn" id="RightColumn"
                    frametype="TitleBarOnly" />
            </td>
        </tr>
        <tr>
            <td id="_invisibleIfEmpty" name="_invisibleIfEmpty" colspan="3" valign="top" width="100%">
                <webpartpages:webpartzone runat="server" title="loc:Footer" id="Footer" frametype="TitleBarOnly" />
            </td>
        </tr>
        <script type="text/javascript" language="javascript">            if (typeof (MSOLayout_MakeInvisibleIfEmpty) == "function") { MSOLayout_MakeInvisibleIfEmpty(); }</script>
    </table>
This is the default layout for spstd2.aspx.
Now you can change the table, adding more webpart zones etc. Just remember to name the zones properly.

If you change the layout it's a good idea to change the spstd?.gif image too so that it will reflect the layout.

When you change the content page and save it, it will immediatly change in sharepoint for all of the pages that are based on the modified page.


Soulution 2:
There is also another option which kinda works but will require sharepoint designer everytime you create a new page.
It's not as smooth and integrated as editing the system files.

In sharepoint desinger 2010:
  • Open the site.
  • Select site pages, select file then new page from master.
  • Inside PlaceHolderMain, add a table, inside the cells add web part zones.
  • Save it as MyTemplate.aspx
  • Close sharepoint designer.
  • Open the site, then site actions, site settings.
  • Under galleries, site content types, Create.
  • Name: SpecialWebPartPage.
  • Select Parent content type from: Document Content Types
  • Parent Content Type: Web Part Page
  • Then press ok to save.
  • Select the SpecialWebPartPage.
  • Advanced settings.
  • Change document Template to: /Sites/YourSite/SitePages/MyTemplate.aspx
  • Save.
  • Create a document library with document template: Web Part Page
  • Go into library settings for the document library.
  • Advanced settings.
  • Enable allow management of content types.
  • Save.
  • Select Add from existing site content types.
  • Select the SpecialWebPartPage.
  • Save.
  • Select  new button order and default content type.
  • Select the new content type to visible position from top = 1 (which makes it default).
  • Select the old document type to visible = false.
  • Now view the document library, select documents, click new document.
  • Sharepoint designer will be started.
  • Now save the file in the appropriate folder in the document library.
  • View the page in the document library, select edit page, now you will see the web part zones as defined in your template.
And this page has the left navigation menu intact.

Works well except that you need sharepoint designer to save the new page.

It's probably possible to make it smoother if the template is located inside the TEMPLATE\LAYOUTS folder like the basic page template bpcf.aspx.
I did a couple of tests, and it certainly treats this template in the same way as basic page template,
but ran into several problems with the masterpage and includes which required a lot of modifications of the template.
Seems like there is a lot of logic in the basic page template that needs to be adopted.
But i haven't investigated this any further.

If anyone has a better solution for this let me know :)