IE8 Developer tools to find Styles in corev4.css

The easy way to determine what CSS is affecting SharePoint elements on you page. You’ve decided to brand your SharePoint website, you look into corev4.css – 5000 lines, good luck. Hey, there is an easier way, use the IE8 Developer Tools and you can quickly determine which css is affecting the elements on the page. Also you can use the ‘Skewer Click’ function in SharePoint Designer 2010. Here’s how to go about using the IE8 Tools:

IE8 Developer Tools

(These are included in every copy of Internet Explorer 8, to open them click Tools > Developer Tools, or just hit F12)



1. Load up your SharePoint site in IE8.

2. Activate the Developer Tools and use the Select Element by Click feature, Find > Select Element by Click. This allows you to select a section of your page that has css affecting it (virtually everything). Move your mouse around and you’ll see blue rectangles around the areas of design. See the image below where I have selected the top bar:

 

3. After clicking the area, you can see that the coresponding <div> is highlighted HTML side of the Developer Tools like so:

4. Now the cool part, you can see the CSS affecting this element on hte Style side of the Developer Tools window:

 You can see that the CSS from the Body is being overridden by the .ms-cui-ribbon and the .ms-cui-topBar2 styles.

5. Even cooler is that you can make temporary changes to the css in the style window to see the affects in the SharePoint Site. Here I’ve changed the border-bottom to a red color.

 

And here is what you see in IE8 as a result:

 Of course this change will only be temporary and when you reload the page all changes will be gone but you can use this to determine hard to find styles in the corev4.css way easier.

Advertisements

Custom workflow activity to send an email and specify the sendor address.

Just a quick post here to post the code used to add the custom workflow activity that will enable you specify the sendor address in a workflow email. In SharePoint v3.0, I used the spactivities on codeplex but this doesn’t work for SharePoint Foundation or 2010. I followed a number of posts to get this code working.

SendMailActivity.cs:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Linq;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Net.Mail;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;
using System.IO;
namespace SPDActivityDemo
{
    public partial class SendMailActivity : Activity
    {
        public static DependencyProperty ToProperty
            = DependencyProperty.Register(
                "To", typeof(ArrayList), typeof(SendMailActivity));

        public static DependencyProperty FromProperty
            = DependencyProperty.Register(
                "From", typeof(string), typeof(SendMailActivity));

        public static DependencyProperty CCProperty
            = DependencyProperty.Register(
                "CC", typeof(ArrayList), typeof(SendMailActivity));

        public static DependencyProperty SubjectProperty
            = DependencyProperty.Register(
                "Subject", typeof(string), typeof(SendMailActivity));

        public static DependencyProperty BodyProperty
            = DependencyProperty.Register(
                "Body", typeof(string), typeof(SendMailActivity));

        public static DependencyProperty __ContextProperty
            = DependencyProperty.Register(
                "__Context", typeof(WorkflowContext), typeof(SendMailActivity));

        public SendMailActivity()
        {
            InitializeComponent();
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        [Description("Enter any e-mail recipients, separated by semicolons")]
        public ArrayList To
        {
            get { return ((ArrayList)(base.GetValue(ToProperty))); }
            set { base.SetValue(ToProperty, value); }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        [Description("Enter the e-mail sender")]
        public string From
        {
            get { return ((string)(base.GetValue(FromProperty))); }
            set { base.SetValue(FromProperty, value); }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        [Description("Enter any carbon copy recipients, separated by semicolons")]
        public ArrayList CC
        {
            get { return ((ArrayList)(base.GetValue(CCProperty))); }
            set { base.SetValue(CCProperty, value); }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        [Description("Enter the e-mail subject")]
        public string Subject
        {
            get { return ((string)(base.GetValue(SubjectProperty))); }
            set { base.SetValue(SubjectProperty, value); }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        [Description("Enter the body text of the e-mail")]
        public string Body
        {
            get { return ((string)(base.GetValue(BodyProperty))); }
            set { base.SetValue(BodyProperty, value); }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        [Browsable(true)]
        public WorkflowContext __Context
        {
            get { return ((WorkflowContext)(base.GetValue(__ContextProperty))); }
            set { base.SetValue(__ContextProperty, value); }
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

        {

            try
            {
                //Send the email
                //grab the Activity for the Lookup Fields to be based on
                Activity myActivity = executionContext.Activity;
                while (myActivity.Parent != null)
                {
                    myActivity = myActivity.Parent;
                }
  
                //Send the email with attachments!!
                SendEmail(myActivity);
            }
            finally
            {
                //nada
            }
           
            //Indicate the activity has closed
            return ActivityExecutionStatus.Closed;

        }

        private void SendEmail(Activity parent)
        {

        //Create a new object that will send the email

        System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage();

        //Create the fromAddress variable and then determine the from email address
        //Create an object to specify the from address for the email
        System.Net.Mail.MailAddress fromAddress;
        try
            {
                //set the email address
                fromAddress = new System.Net.Mail.MailAddress(From);
            }
        catch (System.Exception Ex)
            {
                fromAddress = new System.Net.Mail.MailAddress("<a href="mailto:administrator@domain.com">administrator@domain.com</a>");
            }
        //Set the from email address on the message object
        msg.From = fromAddress;

        //Determine who the email will be sent to, To and CC, and add those email addresses to the msg object created in STEP 1.
        try
            {
            //Add the email addresses stored in the To property to the email message object
            for (int i = 0; i < To.Count; i++)
                {
                    msg.To.Add(To[i].ToString());
                }
            }
        catch (System.Exception Ex)
            {
            //errored out on the To list
            msg.To.Add("<a href="mailto:administrator@domain.com">administrator@domain.com</a>");
            }
        try
            {
            //Add the email addresses stored in the CC property to the email message object
            if (CC != null)
                {
                for (int i = 0; i < CC.Count; i++)
                    {
                        msg.CC.Add(CC[i].ToString());
                    }
                }
            }
        catch (System.Exception Ex)
            {
                //errored out on the CC list… odd
                msg.CC.Add("<a href="mailto:administrator@domain.com">administrator@domain.com</a>");
            }

        // Create the subject line for this Email
        String mySubject = "";
        try
            {
                //To include fields in the subject line from the List, uncomment the lines below
                //mySubject += Subject;
                //mySubject += ” (” + Helper.LookupString(this.__Context, this.ListId, this.ListItem, “My Field 1″) + “)”;
                mySubject = Subject;
            }
        catch (System.Exception Ex)
            {
                mySubject = "ERR: Invalid Subject Line";
            }
        msg.Subject = mySubject;

        //Generate the message body
        //start the handling of the message body.
        msg.IsBodyHtml = true;
        String myBody = Body;
        //make SharePoint replace the Lookup Strings in the field
        try
            {
                //process the Add Lookup strings [%...%] from the Workflow configuration
                myBody = Helper.ProcessStringField(myBody, parent, this.__Context);
            }
        catch (System.Exception Ex)
            {
                //create a writer and open the file
                TextWriter tw = new StreamWriter("E:\\error.log");
                //write a line of text to the file
                tw.WriteLine(DateTime.Now +" – " + Ex.Message + Ex.StackTrace);
                //close the stream
                tw.Close();
            }
        msg.Body = myBody;
        //Set SmtpServer to default SharePoint SMTP Server **Change to your smtp ip address
        String SMTPServerName = "smtp server ip address";

        //Create an object to represent the mail server and SEND the message
        try
            {
                System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient(SMTPServerName);
                //Set the credentials used to authenticate to the email server
                client.UseDefaultCredentials = true;
                //Send the message
                client.Send(msg);
            }
        catch (System.Exception Ex)
            {
                //create a writer and open the file
                TextWriter tw = new StreamWriter("E:\\error.log");
                //write a line of text to the file
                tw.WriteLine(DateTime.Now +" – " + Ex.Message + Ex.StackTrace + Ex.Data);
                //close the stream
                tw.Close();
            }     
        }
    }
}

.ACTIONS file:

<?xml version="1.0" encoding="utf-8" ?>
<WorkflowInfo>
  <Actions Sequential="then" Parallel="and">
    <Action Name="Send Email Extended"
      ClassName="SPDActivityDemo.SendMailActivity"
      Assembly="SPDActivityDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d1763733beaa0777"
      AppliesTo="all"
      Category="Extended Activities">
      <RuleDesigner
        Sentence="Send email from [%1] to [%2]">
        <FieldBind Field="From" DesignerType="TextArea" Text="this person" Id="1"/>
        <FieldBind Field="To,CC,Subject,Body" DesignerType="Email" Text="this person" Id="2"/>
      </RuleDesigner>
      <Parameters>
        <Parameter Name="To" Type="System.Collections.ArrayList, mscorlib" Direction="In" Description="Recipients of the email." />
        <Parameter Name="CC" Type="System.Collections.ArrayList, mscorlib" Direction="Optional" Description="Carbon copy recipients of the email." />
        <Parameter Name="Subject" Type="System.String, mscorlib" Direction="In" Description="Subject line of the email." />
        <Parameter Name="Body" Type="System.String, mscorlib" Direction="Optional" Description="Body text of the email." />
        <Parameter Name="From" Type="System.String, mscorlib" Direction="In" Description="Sender of the email." />
        <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In"/>
      </Parameters>
    </Action>
  </Actions>
</WorkflowInfo>

References used:
http://www.devtrends.com/index.php/custom-workflow-actions-part-1-of-3/
http://msdn.microsoft.com/en-us/library/cc627284.aspx
http://sharepointbloggin.com/2010/02/10/walkthrough-3-sharepoint-designer-workflows-imported-to-visual-studio-2010/

SharePoint Visifire Charting Extended – Rollup Charting

Having followed Doug Perkes’ post on creating charts from SharePoint Custom Lists, I had a need to extend the scripts functionality to create charts that rolled up content from numerous lists of the same content type that were dispersed around a site collection. If you haven’t looked into Visifire charting, it’s a great way to add some visual content to dashboard pages using Silverlight, JavaScript, and JQuery.

Creating rollups with the content from multiple lists with the same content type is relatively straight forward using the spdatasource control and DataSourceMode=”CrossList”, see the msdn blog post for help. This will give your users access to all the of list items in lists across your site collection with the same content type. For example, you could create a roll up of all the ‘tasks due today’ from all tasks lists across your sites and subsites. But what about putting in a nice chart to add some visual appeal? The spdatasource didn’t work with the visifire charting requirements so that’s why I looked into Doug’s post and modifying Doug’s scripts to suit.

Here are the steps to set this up:

1. Create a custom list with the following fields: urlValue(I just renamed the title field for this one), listID, and listView all of type ‘Single line of text’. I use this list to store all of the lists that I want to include in the chart(s). So if I want to include four lists I would have four list items in this list like the following:

 

Site ListID ViewID
SiteA {E824AFE9-4431-4969-A39E-A902C42F7F2C} {A8319615-60B3-40A5-929A-308BED8D954A}
SiteB {5C4C127D-0A68-43CA-94AC-8D452D9B4DD5} {B77AC8B2-44E9-486D-B9A7-345E7D6534FA}
SiteC {0BD78163-9EFD-4278-BB21-F15768E9D235} {8F0C08CD-E227-4388-A071-59DDE78132AA}
SiteD {226C61C6-6EF5-4F20-98FB-F783417E1EC3} {99DB0A21-4DBF-455B-B233-E277955E9798}


The main idea here is that we will use JQuery to retrieve this list (master list) from our site and use it to retrieve the content lists for our charts. This allows you to add/remove lists to the charts without having to modify the script in the future, we can do that in SharePoint or even have users do that for you.

2. Set up a Web Part page with the javascript references and placeholders for the charts just like in Doug’s post. You would post the following code in the Content Editor Web Part:

<script type="text/javascript" src="/ssrc/visifire.js"></script>
<script type="text/javascript" src="/ssrc/My.Custom.Chart.js"></script>
<h3>Visifire Chart</h3>
<table>
  <tr>
   
<td> </td>
<div id="<span class=">VisifireChart1"></div>
   
<td> </td>
<div id="<span class=">VisifireChart2"></div>
  </tr>
  <tr>
   
<td> </td>
<div id="<span class=">VisifireChart3"></div>
   
<td> </td>
<div id="<span class=">VisifireChart4"></div>
  </tr>
</table>

Even better would be to have this saved in a html file located in a Script library somewhere on your site and have the CEWP reference it by url.  Also, I have the JQuery reference in my masterpage, but if you don’t you will need to add the reference to the JQuery library. Something like:

<script type="text/javascript" src="/ssrc/jquery-1.4.2.min.js"></script>

 

3. Next we’ll copy the following code into a text file and name it ‘My.Custom.Chart.js’ and save it to your script library (see comments in the code to understand what’s happening):

$(document).ready(
function() {
 /*Initialize some arrays that we'll use to hold the url, listID,
  and listView values and the resulting xml after the ajax call*/
 
 var listNameArray = new Array();
 var listViewArray = new Array();
 var urlArray = new Array();
 var xDataArray = new Array();
 
 /*This sets the master list. This is the SharePoint list that contains
 list items with the properties of url, listID, and listView.*/
 
 var masterList = '{FD7DBAF7-C111-401F-97DE-5C9991CAE62E}';
 var masterView = '{E8BEE90D-D97D-4A76-8836-1D2ADEDFC306}';
 var masterUrl = '';
 
 /*Here we make our first ajax call to retrieve the SharePoint list data
 from our master list. We also read each line and store the resulting properties
 in our arrays.*/
 
 var xData1 = soapCall(masterList, masterView, masterUrl);
    i=0;
    listData = $(xData1.responseXML);
 $(listData).find("z\\:row").each(function() {
      urlArray[i] = $(this).attr('ows_LinkTitle');
      listNameArray[i] = $(this).attr('ows_ListID');
   listViewArray[i] = $(this).attr('ows_ViewID');
      i++;
 });
 
 /*Next we loop through our listNameArray array to make ajax calls for all of the
 lists in our SharePoint master list. These objects are stored in the xDataArray.*/
 
 for(var i=0; i
  xDataArray[i] = soapCall(listNameArray[i],listViewArray[i],urlArray[i]);
 }
 
 /*Now that we have all of the list data in our xDataArray, we process the results
 and generate the charts by calling the processResult function.*/
 
 processResult(xDataArray);
 
    }
);
function soapCall(list, view, urlValue) {
  var listData = '';
  var listStatus = '';
  
  /*This SOAP request is almost the same as in Doug's Post, but since we're making
  several SOAP requests to retrieve several SharePoint Lists, it follows to make it a
  function. We pass in the listID, viewID, and url values for our master list on its
  first call. Then we repeatedly call this function with our arrays containing the
  parameters for our data lists. Each time we return the data object containing the xml.*/
  
  var soapEnv =
            "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> \
                <soapenv:Body> \
                     <GetListItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> \
                        <listName>" + list + "</listName> \
                        <viewName>" + view + "</viewName> \
                        <viewFields /> \
                    </GetListItems> \
                soapenv:Body> \
            soapenv:Envelope>";
        $.ajax({
            url: urlValue + "/_vti_bin/lists.asmx",
            type: "POST",
            dataType: "xml",
            data: soapEnv,
      async: false,
      complete: function(data) {
       listData = data;
    listStatus = status;
            },
       error: function(request,error) {
      },
         contentType: "text/xml; charset=\"utf-8\""
        });
        return listData;
}
 
function processResult(xDataArray) {
  /*This function receives an array of objects containing our SharePoint lists in xml
  and we generate our charts with it. Instead of passing in an object, as in Doug's Post,
  we are passing in an array of objects so we need to iterate through it and generate another
  array with the list xml data. */
        var listArray = new Array();
        for(var i=0; i
          listArray[i]= $(xDataArray[i].responseXML);
  }
        var dataPoints = '';
        var chartXmlString = '';
  /*We use the same functions to Group the datapoints, generate the xml for visifire, and then
  render the chart(s). Again each have been modified slightly to recieve an array instead of a
  single listData element. Some additional fields are added which are explained in the functions.*/
  dataPoints = getGroupedDataPoints(listArray, 'ows_Integration_x0020_Site', 'Count', null, false, '#YValue');
        chartXmlString = getXaml('Site Summary', 'Pie', dataPoints, 280, 280, 'Center', 'Bottom');
        renderChart(chartXmlString, 'VisifireChart1', 280, 280);
        dataPoints = getGroupedDataPoints(listArray, 'ows_Category', 'Count', null, false, '#YValue');
        chartXmlString = getXaml('Issue Summary', 'Pie', dataPoints, 280, 280, 'Center', 'Bottom');
        renderChart(chartXmlString, 'VisifireChart2', 280, 280);
        dataPoints = getGroupedDataPoints(listArray, 'ows_IssueStatus', 'Count', null, false, '#Percentage%');
        chartXmlString = getXaml('Status Summary', 'Pie', dataPoints, 280, 280, 'Center', 'Bottom');
        renderChart(chartXmlString, 'VisifireChart3', 280, 280);
 
  dataPoints = getGroupedDataPoints(listArray, 'ows_Assigned', 'Count', 'ows_Assigned', false, '#YValue');
        chartXmlString = getXaml('Issue Assignment', 'Pie', dataPoints, 280, 280, 'Center', 'Bottom');
        renderChart(chartXmlString, 'VisifireChart4', 280, 280);  
    }
function renderChart(xaml, divId, sizeWidth, sizeHeight) {
  /*Here we additionally take in the width and height of the chare to be generated. This allows us
  to generate charts of different size.*/
        var vChart = new Visifire("/Scripts/SL.Visifire.Charts.xap", sizeWidth, sizeHeight );
  vChart.setWindowlessState(true);
        vChart.setDataXml(xaml);
        vChart.render(divId);
    }
function getGroupedDataPoints(listArray, groupByField, groupType, valueField, insertTotal, labelType) {
 var valueArray = new Array();
 var keyArray = new Array();
 
 /*Here we loop through our array of SharePoint list xml and group the information for the charts.*/
 
 for(var i=0; i
  $(listArray[i]).find("z\\:row").each(function() {
      var myKey = $(this).attr(groupByField);
      
      /*If one of our data list items fields contain a null value we can just skip to the next xml list item.
      This was a problem if you don't require a field to contain information is SharePoint and you want to
      chart that field.*/
      
      if (myKey != null) {
      if (jQuery.inArray(myKey, keyArray) == -1) {keyArray[keyArray.length] = myKey; valueArray[keyArray.length] = 0;}
      switch(groupType) {
       case 'Count':
       if (valueArray[jQuery.inArray(myKey, keyArray)] == null || isNaN(valueArray[jQuery.inArray(myKey, keyArray)])) {valueArray[jQuery.inArray(myKey, keyArray)] = 0;}
     valueArray[jQuery.inArray(myKey, keyArray)]++;
       break;
        case 'Sum':
     var val = $(this).attr(valueField);
     if (val != null && !isNaN(val)) {
     if (valueArray[jQuery.inArray(myKey, keyArray)] == null) { valueArray[jQuery.inArray(myKey, keyArray)] = 0; }
     valueArray[jQuery.inArray(myKey, keyArray)]+= parseFloat(val);}
       break;
  
      break;
      }}
  });
 } 
 
 var dataPoints = "";
 var totalValue = 0;
 
 /*Here we are looping through to create a the datapoints for visifire. We've also passed into the function the labelType.*/
 
 for(var i=0; i<keyArray.length; i++) {
  var label = keyArray[i].replace("&", "&amp");
  dataPoints += '';
  totalValue += valueArray[i];
 }
 if (insertTotal) {
  dataPoints = '' + dataPoints;
 }
 return dataPoints;
}
function getXaml(title, chartType, dataPoints, sizeWidth, sizeHeight, legHAlign, legVAlign) {
 /*Additionally we passed in the width and height of the charts and legend alignment values.*/
 
      var chartXmlString = ''
      +'xmlns:vc="clr-namespace:Visifire.Charts;assembly=SLVisifire.Charts" Width="' + sizeWidth + '" Height="' + sizeHeight + '" BorderThickness="1" BorderBrush="Silver" Theme="Theme1" Watermark="True" SmartLabelEnabled="True">'
           +'<vc:Chart.Titles>'
               +'<vc:Title Text="' + title + '" HorizontalAlignment="Left" FontSize="16" FontFamily="Trebuchet MS" FontWeight="Bold" />'
           +'</vc:Chart.Titles>'
     +'<vc:Chart.Legends>'
         +'HorizontalAlignment="' + legHAlign + '" VerticalAlignment="' + legVAlign + '" />'
    +'</vc:Chart.Legends>'
        +'<vc:Chart.AxesY>'
            +'<vc:Axis AxisType="Primary" />'
        +'</vc:Chart.AxesY>'
        +'<vc:Chart.Series>'
            +'DataSeries ShowInLegend="True" RenderAs="' + chartType + '" AxisYType="Primary" >'
                +'<vc:DataSeries.DataPoints>'
                    + dataPoints
                +'</vc:DataSeries.DataPoints>'
            +'</vc:DataSeries>'
        +'</vc:Chart.Series>'
      +'</vc:Chart>';
        return chartXmlString;
    }

4. You’ll have to follow Doug’s post to determine your field names that you’ll use in the charts with Fiddler. Also pay close attention to the urlValue you store in the Sharepoint master list, it’s used in the SOAP request and has “/_vti_bin/lists.asmx” appended to it to request the SharePoint lists web service for each of your data lists.

A couple errors that came up in the SharePoint 2010 Developer Training Kit

I’ve started to go through the SharePoint 2010 Developer Training Kit after setting up a development environment and I received a couple error messages while doing the Labs.

While trying to add a custom webpart to a webpart zone:

“Web Part Error: A Web Part or Web Form Control on this Page cannot be displayed or imported. The type could not be found or it is not registered as safe.”

While attempting to debug a project:

“Could not load the Web.config configuration file. check the file for any malformed XML elements, and try again. The following error occurred: The given key was not present in the dictionary.”

Both of the errors were resolved by ensuring  the URL assigned to the Default zone for the Web Application’s Alternate Access Mappings matches the Site URL of the SharePoint project in Visual Studio 2010. Using one of the other zones (like Intranet) for the URL will not work. The Default Zone and the project’s Site URL need to match. Thanks to Steve!

Customizing SharePoint workflow emails in 2010/Foundation

Part of the migration process to SharePoint 2010 or Foundation is ensuring that everything is working as perscribed. One thing that I had a little trouble with was customizing the format of workflow emails from the plain or basic one to something more stylish like the out of the box alert format. With Wss 3.0 and 2007 you could write html code and paste it into the body of the ‘Define E-Mail Message’ dialog, the issue there was that you had to remove all html whitespace from the code. See Create Custom E-mail Alert Templates for SharePoint Workflows for a handy tool that will help you create the html.

I had hoped that there would be a better way to define the email template in Foundation / 2010 but after some google’n nothing came up. So I attempted to do the same as with Wss 3.0. Created my html with the handy tool above, pasted it into the body and updated the Lookup fields. Tested. No Good, no formatting took and the email just contained the code. Then I noticed the properties dialog of the email step in the workflow:

Selecting the Properties in the dropdown give you this:

I noticed here that something was up with the body of the email, it had tons of additional html along with the code I pasted into the body:

After re-pasting the html code into the String Builder the workflow email had the correct formatting. Also any further changes to the body I made in the normal email dialog worked too. Now if there was a way to have the whitespaces?

Installing MSS2010 Express on SharePoint Foundation

If you’ve gone as far as to install SharePoint Foundation and your environment can handle the additional load, why not extend the search capabilities with Microsoft Search Server Express? It’s free and will provide powerful, web-style search across your corporate intranet sites quickly, and easily. You can find additional information on it here: http://technet.microsoft.com/en-us/enterprisesearch/ee263912.aspx#tab=1.

I’m convinced, so I went ahead and installed it only to run into a few snags along the way. I’m running a standalone installation of Foundation with a couple of site collections. Mainly to test new functionality and upgrade my masterpages before permanently migrating my content. During the Foundation install a site collection was created on port 80 without a host header, I can’t remember if I was prompted to enter one or not. But I later added one in Central Administration and made sure in IIS that the Web Application had the correct settings. So when the MSS 2010 Express initiated the SharePoint 2010 Products Configuration Wizard I thought I was gold. Only to be prompted with the following error:

Failed to create sample data.
An exception of type System.ArgumentException was thrown. Additional exception information: The directory C:\inetpub\wwwroot\wss\VirtualDirectories\80 is already being used by another IIS Web Site. Choose a different root directory for your new Web application.

 

Searching online didn’t result in much information, but some people have experienced similiar errors when web applications were created without host headers. Even after ensuring that the default site had its header the Configuration Wizard would not complete. Solution – backup the Site Collection, delete the Web Application and re-run the wizard. Simple enough. Except that when deleting a Site Collection from Central Admin doesn’t immediately delete everything necessary to run the wizard and complete the configuration. After deleting it in Central Admin, and removing the IIS site manually(also removing the folder C:\inetpub\wwwroot\wss\virtualdirectories\80) I got the following error when running the wizard:

Failed to create sample data.
An exception of type System.ArgumentException was thrown. Additional exception information: The IIS Web Site you have selected is in use by SharePoint. You must select another port or hostname.

 
 

 

In the warning dialog when deleting the site collection there is a note specifying “To perform an immediate deletion, you need to run a set of commands using, for example, Power Shell or the Object Model. Find more information, see the SharePoint Help system” which didn’t provide much help as to actual commands to delete the web application.

Fortunately, Adam Preston posted List All SharePoint 2010 PowerShell Commands that provided the necessary information to complete the deletion. Namely, run get-spwebapplication to list the web applications configured in SharePoint Foundation. Then run Remove-SPWebApplication -identity:http://webappname/ -removeContentDatabases -deleteiissite -verbose -confirm. This command will immediately delete the Web Application, delete the content database, and the IIS site. After this I was successfully able to complete the Configuration Wizard. Up popped the Search Administration page in Central Administration. Of to configuring it!

Maybe this post should be titled ‘Trials of installing MSS2010 Express on SharePoint Foundation’?

SharePoint Foundation Master Page CSS

I recently had to take a WSS 3.o masterpage and upgrade it for use in SharePoint Foundation. Simple enough, following the helpful post here Upgrading an Existing Master Page to the SharePoint Foundation Master Page to ensure that I have all the necessary controls on my page. This post from Tom Wilson was also very helpful determining the elements that make up the new ribbon, Ribbon Customization: Changing Placement, Look and Behavior. The following is just a table of the CSS overrides that you may like to use to modify some of the styles, backgrounds, or borders to suit your brand.

SharePoint 2010 CSS Chart (Well at least a start anyway!)