Archive for February 9th, 2009

The Server Revolution

Monday, February 9th, 2009

Moving the server from the data-centre to the desktop.

Lifting my gaze from the code for a few seconds, one of the things that has always stuck me with SharePoint is the similarity its impact has on businesses to the PC Revolution. The same arguments for and against central control, the same immaturity of tools, the same gist of ‘user empowerment’, and the same fears and demarcation lines are surfacing.

I like analogies – they provide a framework and a perspective from which to form a critique. I remember way-back-when we had an illuminating student task to analyse and categorise businesses using metaphors, not just their numbers. So let look at this through the lens of that long past but ongoing Desktop Revolution…

In the SharePoint case the thing that stands out to me as very different to the PC revolution is its breadth– there is no one ‘killer app’ or enabling technology driving this. It’s more a confluence of established concepts, some from MS, but many more from the wider web ecosystem.

Hopefully this starts a discussion – where are we headed?

  • Will SharePoint and its successors become a de faco standard on the Server, as the IBM PC and DOS did on the desktop?
  • Will Microsoft mature its tools to better manage SharePoint customisation, deployment and management?
  • Will that help the data centre wrestle control of important definitions, data and processes back from the end users?
  • Will developers get left out in the cold, as users do more and more, or will there be a backlash due to a lack of rigour?
  • Will a ‘killer app’ appear on the SharePoint server, as the spreadsheet did on the desktop?
  • If so, will there be a VisiCalc/Lout123/Excell style progression?
  • Because of its broad scope, will it only really succeed in a few niche areas?
  • Is MOSS simply a glimpse of the possibilities and challenges in the coming ‘Server Revolution’?

Let me know your thoughts, especially any futurists out there!

NB: As a good example of the discussions the future of SharePoint kicks off, here’s Paul’s take on the direction of the nascent external data access API: http://www.cleverworkarounds.com/2008/03/25/sharepoint-external-storage-api-crushing-my-dream

 

The end of another MOSSuMS post

Multiple Projects, one WSPBuilder Solution

Monday, February 9th, 2009

When two (projects) become one (sharepoint solution).

When I originally used WSPBuilder, after a great deal of messing about, I discovered there was a bug that prevented the 'BuildSolution' switch from working. That was a pity, as for everthing else "it did exactly what it said on the can".

I recently revisited it, and I'm pleased to report it now works a treat for multiple projects! You have to place then under the same 'WSPBuilder solution' folder in your file system, but using source control bindings this isn't a big issue.

You can find the WSPBuilder site here – just download and run the latest Extensions for Visual Studio installer from here, which includes the command line version as well.

Below is the config file I use:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!– EXISTING SETTINGS –>
    <!–    As in C:Program FilesWSPToolsWSPBuilderExtensionsWSPBuilder.exe.config  –>
    <add key="BuildDDF" value="true" />
    <!–    As in C:Program FilesWSPToolsWSPBuilderExtensionsWSPBuilder.exe.config, but value changed from 3 to 4 for more feedback –>
    <add key="TraceLevel" value="4" />
    <!– MIKE'S EXTRA SETTINGS –>
    <!–    The following key will ensure we build in debug mode. –>
    <add key="BuildMode" value="Debug" />
    <!–    The following key will override the wsp default of files in dev GAC dir going to the gac, and those in the bin dir going to the iis site bin directory. –>
    <add key="DeploymentTarget" value="GAC" />
    <!–    The following key will create a full 12 hive in your projects, so use only if you need all deployable folders –>
    <!–<add key="Createfolders" value="True" /> –>
    <!–    Don't delete intermediate files such as the manifest.xml (don't add these files to source control) –>
    <add key="Cleanup" value="False" />
    <!–    New name proposed for current BuildSolution key –>
    <!–<add key="BuildMultipleProjects" value="True" /> –>
    <!–    Build all projects into one solution –>
    <add key="BuildSolution" value="True" />
    <!–    Solution path – this tells WSP builder to step back one level for the master (startup/build) project to the main 'wsp solution'
            folder, and build all projects within that folder to one wsp (cab). By default this wsp will take the name of the folder it sits
            in, or use the WSPName key (see below) to give your own name. Unfortunately this dictates a rigid project folder structure, so it
            is advised that projects used in more than one wsp solution solutions are subsequently 'referenced/linked' in source control to
            match this client build structure rather than physically copied. They can then be 'project referenced' in Visual Studio, which
            leaves the option of branching them completely if required. Although this promotes re-use, care should be taken as it often
            indicates a new wsp is required to prevent inadvertent upgrades or clashes with already installed features in other solutions.
            If it is a variation, consider branching or copying files (especially feature.xml to ensure guid is used), dll referencing of base
            feature and inheritance.  –>
    <add key="SolutionPath" value=".." />
    <!–    Override the default of using the parent folder's name as the wsp solution name –>
    <!–<add key="WSPName" value="SomeSolutionName" /> –>
  </appSettings>
</configuration> 

This screen shot shows the place of the config for WSPBuilder in the Visual Studio world (your default project's directory, where you choose to 'build wsp'):

Now you can right click on your main project (where the wsp config file lives) to see the following:

So, hey presto, no more excuses for not putting all those pesky little features into your WSP where they belong! You can also name your solution differently to the containing folder using the WSPName setting. To see all the command line arguments type

C:Program FilesWSPToolsWSPBuilderExtensions>wspbuilder -help:full

This isn't fully aligned with the list of optional settings they give below, so take care to test if it is supported in the config, as well as the command line, before you use it.

Optional settings
    You can set the arguments in this file or use them directly in the console.
    All arguments has a defualt value. See wspbuilder -help
    
    <add key="12Path" value="" />
    <add key="80Path" value="" />
    <add key="BinPath" value="" />
    <add key="BuildCAS" value="" />
    <add key="PermissionSetLevel" value="" />
    <add key="BuildSafeControls" value="" />
    <add key="BuildWSP" value="" />
    <add key="Createfolders" value="" />
    <add key="DeploymentTarget" value="" />
    <add key="Destination" value="" />
    <add key="DLLReferencePath" value="" />
    <add key="Excludefiletypes" value="" />
    <add key="ExpandTypes" value="" />
    <add key="Help" value="" />
    <add key="IncludeAssemblies" value="" />
    <add key="IncludeFeatures" value="" />
    <add key="Includefiletypes" value="" />
    <add key="ManifestEncoding" value="" />
    <add key="Outputpath" value="" />
    <add key="ResetWebServer" value="" />
    <add key="Silence" value="" />
    <add key="SolutionId" value="" />
    <add key="SolutionPath" value="" />
    <add key="WSPName" value="" />
       
    TraveLevel switch controls the general messages. In order to 
         receive general trace messages change the value to the 
         appropriate level. 
         Possible values :
         "0" gives no messages
         "1" gives error messages
         "2" gives errors and warnings
         "3" gives information
         "4" gives verbose information 
       
     The default value in WSPBuilder is Information (3).

All I've got to do now is work out how to get wsp builder to deploy to a specific URL, and my dev time STSADM worries have gone!

The end of another MOSSuMS post

Template that

Monday, February 9th, 2009

Custom Site Definition '3rd way'. 

Do you want a custom site definition, but can't face copying and modifying an ONet? Need to use an OOTB Site Definiton, but also change it? Like features, but hate the issues stapling gives you? Then read on…

The debate about this rages on-and-on (see Microsofts guidelines here, and other developers perspective on upgrade issues here, here and here). Some like features, some like stapling, some like to use the .onet file. Each has its pros and cons. But all agree it would be nice to end up with a deployable sharepoint solution, use features within it, build on the OOTB site definitions (templates), provide a custom site definition the user can easily choose for their new collection, all without hacking a big Onet file. So how to square the circle for us devs?

Raymond Mitchell deserves the credit for this one using web provisioning, as I based my approach on his suggestions and added my preference for Features and WSPBuilder on top of it.

Note: I abandoned my original preference of stapling a master feature within this approach, as I couldn't get the stapled features to activate automatically (stapling happens at farm level, but provisioning is at web level), in the right order or to wait for provisioning to complete. I do think this aspect warrants revisiting as I may have made some mistakes and it would be nice to use more of SharePoint's built-in abilities.

In my implementation I have the following web temp to give us our new site definition:

<?xml version="1.0" encoding="utf-8"?>
<Templates xmlns:ows="Microsoft SharePoint">
 <!– OOTB IDs are listed here: /mossms/archive/2009/02/09/configure-this.aspx –>
 <Template Name="Your.Project.Template" ID="9999999">
   <!– Choose a unique number for this Customisation's ID that is above 10000. –>
        <Configuration
          ID="0"
          Title="Your.Project.Template#0"
          Hidden="FALSE"
          ImageUrl="/_layouts/images/Your.Project/templatePrev.png"
          Description="Custom Your.Project.Template#0 used to provision the OOTB Document Center (BRD#0) before automatic customisation with features."
          DisplayCategory="Custom"
          AllowGlobalFeatureAssociations="TRUE"
          Version="12.0.0.0"
          ProvisionAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ProvisionClass="Your.Project.l033.XML.WebProvisioningProvider">
        </Configuration>
 </Template>
</Templates>

It should be placed in the 12 hive location here:

Have a look at 'Configure this' for a list of existing template IDs, but ensure you use a new unique ID above 10000. This file will have named the custom site definition (template) and points to my provisioning provider shown below:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
 
namespace Your.Project.l033.XML
{
    public class WebProvisioningProvider : SPWebProvisioningProvider
    {
        // Set your existing template and #configuration number here
        public static string WebTemplateConfiguration = "BDR#0";
        // OOTB Configurations are listed here: /mossms/archive/2009/02/09/configure-this.aspx –>
 
        // Set your 'master' site (collection) scope feature here (master feature will activate sub features – can't use feature associations as these don't wait until site is provisioned).
        public static Guid SiteCollectionMasterFeatureId = new Guid("Your-Projects-Site-Master-Feature-Guid");
 
        // Set your 'master' web scope feature here
        public static Guid WebMasterFeatureId = new Guid("Your-Projects-Web-Master-Feature-Guid");
 
        public override void Provision(SPWebProvisioningProperties properties)
        {
            // Provision a site based on the OOTB Document Center site definition – do not dispose the spweb as it it part of the context.
            SPWeb web = properties.Web;
 
            if (WebTemplateConfiguration != null && WebTemplateConfiguration.Trim().Length > 0)
            {
                web.ApplyWebTemplate(WebTemplateConfiguration);
                web.Update();
            }
 
            //Activate the master Site collection scope feature
            if (SiteCollectionMasterFeatureId != null && SiteCollectionMasterFeatureId != Guid.Empty)
            {
                SPSite site = null;  // Must be disposed
                try
                {
                    site = web.Site;
                    site.Features.Add(SiteCollectionMasterFeatureId);
                }
                finally
                {
                    if (site != null)
                    {
                        site.Dispose();
                    }
                }
            }
            //Activate the master Web scope feature
            if (WebMasterFeatureId != null && WebMasterFeatureId != Guid.Empty)
            {
                web.Features.Add(WebMasterFeatureId);
            }
        }
    }
}

I resisted the desire to reference the Stair Master feature, and placed my provisioning provider code here:

 

The provisioning provider will trigger when a new web (when a site collection is created you get the collections root web by default, so it will be triggered then) is created. My example above shows that it optionally provisions an OOTB template (see appendix my previous post for a list of OOTB definitions), activates a master site collection scope feature and a master web scope feature by referencing their Guids. Other scoped features could be added, but there may be some governance/assurance implications in applying features scoped beyond your specific ‘scope’ requirement.

My elements file is empty for now, but the site master feature xml is shown below:

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="Your-Projects-Site-Master-Feature-Guid"
          Title="Your.Project.Features.Site.Master"
          Description="Description for Your.Project.Features.Site.Master MASTER feature"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Site"
          ImageUrl="Your.Project/your.Project.png"
          DefaultResourceFile="core"
          ReceiverAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ReceiverClass="Your.Project.Features.Site.Master.Receiver"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

Remember to replace the Guid and Assembly public key with your own values – this feature lives here in my world:

This is the master site (collection) scope feature receiver code – don' worry about my utils 'using' for a dll reference, it's only used for the logging so comment out that code if you copy from here:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Threading;
//using Your.Project.Features.Site.Utils.Logging; // Comment line out if you copy this code
 
namespace Your.Project.Features.Site.Master
{
    // Single 'master' feature for a site definition. Use this to activate other custom features as stapling does not activate features in order
    public class Receiver : SPFeatureReceiver
    {
        // *** Add your unstapled Site Collection Scope Features here (could be driven from an xml file as this approach matures) ***
        public static Guid[] FeatureGuids = new Guid[] {
            //new Guid("F6924D36-2FA8-4f0b-B16D-06B7250180FA"), // This is the OOTB publishing feature required for style library creation
            new Guid("Your-Projects-Site-Utils-Feature-Guid") // Your.Project.Features.Site.Utils – this is just an example feature
        }; // You can make these features' xml property 'Hidden="TRUE"' once testing is complete
 
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            //Activate Unstapled Site Collection Scope Features (do not wait for root web to be provisioned here, as we will wait forever…)
            SPSite site = properties.Feature.Parent as SPSite;
 
            if (FeatureGuids != null)
            {
                foreach (Guid guid in FeatureGuids)
                {
                    if (guid != null && guid != Guid.Empty)
                    {
                        try
                        {
                            site.Features.Add(guid, true);
                        }
                        catch (Exception ex)
                        {
                            //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                        }
                    }
                }
            }
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            SPSite site = properties.Feature.Parent as SPSite;
 
            //Deactivate the master web scope feature on all subwebs in reverse subweb order
            if (Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId != null && Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId != Guid.Empty)
            {
                for (int i = site.AllWebs.Count -1; i >= 0; –i)
                {
                    SPWeb web = null;
                    try
                    {
                        web = site.AllWebsIdea;
                        web.Features.Remove(Your.Project.l033.XML.WebProvisioningProvider.WebMasterFeatureId, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                    finally
                    {
                        if (web != null)
                        {
                            web.Dispose();
                        }
                    }
                }
            }
 
            //Deactivate unstapled Site collection scope features in reverse order
            if (FeatureGuids != null)
            {
                for (int i = FeatureGuids.Length -1; i >= 0; –i)
                {
                    try
                    {
                        Guid guid = FeatureGuidsIdea;
                        SPFeature feature = site.Features[guid];
                        site.Features.Remove(guid, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                }
            }
        }
 
        public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
    }
}

..and you can find it here – after this I think you'll have the idea of where I locate my code and the xml 12 hive locations, so I'll stop posting my project structure:

 

 

 

 

 

This activates all site collection features in order (otherwise dependencies may cause activation issues) and deactives them in reverse. It also deactivates and web features when you pull the site master by call in the web master feature I'll cover next. That probably isn't necessary, but it seems to prevent issues for me.

So below is the master web scope feature xml and code, activating and deactivating web features in a similar way to the site master, but first waiting for web provisioning to complete. This ensures all OOTB items are present for us to find/modify with our features. NB. If timing issue occur between site collection and web scoped feature activation, it could be done in the single web master feature below, with wider scopes activated first BEFORE we wait for OOTB web provisioning to complete.

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="Your-Projects-Web-Master-Feature-Guid"
          Title="Your.Project.Features.Web.Master"
          Description="Description for Your.Project.Features.Web.Master MASTER feature"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          ImageUrl="Your.Project/your.Project.png"
          DefaultResourceFile="core"
          ReceiverAssembly="Your.Project, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Your.Project.snk"
          ReceiverClass="Your.Project.Features.Web.Master.Receiver"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

…and the receiver code this feature points to:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Threading;
//using Your.Project.Features.Site.Utils.Logging; // Comment line out if you copy this code
 
namespace Your.Project.Features.Web.Master
{
    // Single 'master' feature for a site definition. Use this to activate other custom features as stapling does not activate features in order and doesn't wait for
rovisioning to complete
    public class Receiver : SPFeatureReceiver
    {
        //Add your unstapled Web Scope Features here (could be driven from an xml file as this approach matures) – these are examples of the sorts of features you can add
        public static Guid[] FeatureGuids = new Guid[] {
            new Guid("Your-Projects-Web-Customisation-Feature-Guid"), // Your.Project.Features.Web.Customisation
            new Guid("Your-Projects-Web-ListInstances-Feature-Guid"), // Your.Project.Features.Web.ListInstances
            new Guid("Your-Projects-Web-Branding-Feature-Guid") // Your.Project.Features.Web.Branding
            //new Guid("fc5e2840-0b48-42eb-9ad7-076f5add58ad") // OOTB HIDE the view all site content link feature
        }; // You can make these features' xml property 'Hidden="TRUE"' once testing is complete
 
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            // Ensure Web is provisioned (otherwise provisioning may not complete before feature activation starts)
            SPWeb web = properties.Feature.Parent as SPWeb;
            while (!web.Provisioned)
                Thread.Sleep(1000);     
 
            //Activate Unstapled Web Scope Features
            if( FeatureGuids != null )
            {
                foreach (Guid guid in FeatureGuids)
                {
                    if (guid != null && guid != Guid.Empty)
                    {
                        try
                        {
                            web.Features.Add(guid, true);
                        }
                        catch (Exception ex)
                        {
                            //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                        }
                    }
                }
            }
        }
 
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {   // NB: Do not dispose properties.Feature.Parent, as that is the feature context, just like the Current web context, but other spweb or spsites should be disposed.
 
            //Deactivate Unstapled Web Scope Features in reverse order
            SPWeb web = properties.Feature.Parent as SPWeb;
 
            if (FeatureGuids != null)
            {
                for (int i = FeatureGuids.Length – 1; i >= 0; –i)
                {
                    try
                    {
                        Guid guid = FeatureGuidsIdea;
                        SPFeature feature = web.Features[guid];
                        web.Features.Remove(guid, true);
                    }
                    catch (Exception ex)
                    {
                        //Log.Write(ex); // comment out and replace with own procedures if you copy this code
                    }
                }
            }
        }
 
        public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
    }
}

So once this has been done, each new feature simply requires adding into the correct master sope features code. I would rather maintain the flexibility of reproducing this simple process for each project, but this procedure could easily be extended to provide some automation, resources or configuration file driven facilities (eg. an xml file to choose the OOTB configuration and list each feature, grouped by scope, in their desired order).

The end of another MOSSuMS post