in

SharePoint Blogs

The Best Place for SharePoint-related Blogs

Ton Stegeman [MVP] weblog

I have moved my blog to http://www.tonstegeman.com/blog. If you have a blogpost at sharepointblogs that does not display all the content on the right hand side, please go to the new blog. It has all the posts that are on this blog as well. I you have any feedback, please send me a message through the contact form.

July 2007 - Posts

  • Using MOSS to eliminate paper: multifunctionals and scanners

    This is a repost from one of my collegues, Sjoert Ebben:

    Introduction (by Sjoert):

    This is the first post of a planned series of postings on the topic of using SharePoint to get rid of paper. My intention is to share gathered knowledge on the matter, which means that I don't have a clear idea on what to write in the following articles, but that I share knowledge as it comes along. I welcome all input from the community so please let me know your thoughts and experiences.


    This first posting is on the use of multifunctional devices (MFDs) and other scanning devices.

  • Associating the default page in a SharePoint 2007 publishing site with a custom content type and a custom page layout

    In the MOSS 2007 intranets we build for our customers, we try to encourage them to use custom content types to manage the documents and publishing pages. In these projects we always create new page layouts, new content types and associate the page layouts with the new page layouts. The page layout contains the desired layout of the pages and the usercontrols that make it easier for users to edit the metadata while creating content.

    For some reason we had never done this for the default page in a publishing site, which we had to do in one of our current projects. The issue here was that you need to set the content type and the page layout for the default.aspx in the site definition (onet.xml). At that stage our custom content type is not yet associated with the pages library, because that still is the out of the box Pages library. Once again SharePoint features are the solution here. More specific the “ContentTypeBinding” that is part of the “Elements” element helps here. In this post I will describe how I have set this up.

    • Create a site column called “Region” (this is just custom metadata to show the concept; it is a choice field)
    • Create a new content type called “MyArticleContentType”. It inherits from “Article Page” and has the extra Region site column.
    • Create a new Page Layout called “MyArticleLeft.aspx”. In my demo it is a copy of ArticleLeft. aspx and it has the extra user control for the Region field. I have added this control to the placeholder called “PlaceHolderMain” and added the control just between the ArticleStartDate and the ArticleByLine fields.

                          <SharePointWebControls:DropDownChoiceField 
                                FieldName="Region" 
                                runat="server" 
                                id="regionfield"
                          </SharePointWebControls:DropDownChoiceField>

    • Associate the page layout with the content type. Navigate to the Master page gallery in SharePoint find your page layout and select “Edit Properties” from the context menu. In the Associated Content Type section, select your custom content type, in my case “MyArticleContentType”.
      Pages1
    • Publish and approve your page layout.
    • Create a new feature (I called it “PagesContentTypeAssociation”). The feature.xml is just a reference to an elements manifest file:
    • <?xml version="1.0" encoding="utf-8" ?>
        <Feature  Id="9FAA95C2-E455-4a74-B9D5-C3C81DE8824A"
                Title="Association of content types for Pages Libraries"
                Scope="Web"
                Description="This feature associates content types to existing SharePoint lists."
                Version="1.0.0.0"
                xmlns="http://schemas.microsoft.com/sharepoint/">
        <ElementManifests>
          <ElementManifest Location="elements.xml"/>
        </ElementManifests>
      </Feature>
    • Create the elements manifest. In the Elements element, you need to add an ContentTypeBinding like in the example below:
      <?xml version="1.0" encoding="utf-8" ?>
      <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <ContentTypeBinding 
          ContentTypeId="0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D0000951B99779D25428408CD40276C0358" 
          ListUrl="Pages" />
      </Elements>
      The ContentTypeId referenced here is the ID of your custom content type. Because I created the content type manually by using the SharePoint user interface, I have to lookup the ID in SharePoint. You can find it by navigating to the properties of your content type and copying the id from the address bar of your browser:
      Pages2
    • Install the feature on your SharePoint farm
    • Create a new site definition. I just copied the “PUBLISHING” folder in the SiteTemplates folder and created a new registration for my new site definition by creating a new xml file called “webtempmycustomsite.xml” in the folder 12HIVE\TEMPLATE1033\XML.
    • Activate our new feature in the onet.xml of the new site definition. It has to be in WebFeatures, because the feature has the Web scope (as we want to activate is for each subsite)

    •     <WebFeatures>
            <!-- Activate the feature to associate the content type to the Pages library -->
            <Feature ID="9FAA95C2-E455-4a74-B9D5-C3C81DE8824A">
            </Feature>
           </WebFeatures>

    • Change the options for the default.aspx in onet.xml. Set both the ContentType and the PublishingPageLayout properties of the File “default.aspx”:
    •     <Module Name="Home" Url="$Resources:cmscore,List_Pages_UrlName;" Path="">
            <File Url="default.aspx" Type="GhostableInLibrary" Level="Draft" >
              <Property Name="Title" Value="My Welcome Page" />
              <Property Name="PublishingPageLayout" 
                        Value="~SiteCollection/_catalogs/masterpage/MyArticleLeft.aspx" />
              <Property Name="ContentType" Value="MyArticlePage" />
            </File>
          </Module>

    • IISRESET to activate the registration of the new site definition
    • Test your new site definition.
      After creating a new site and putting the default page in edit mode, you will see that you page is based on the correct page layout.
      Pages3
      In the properties of the page you can see that it is now based on the new custom content type.

     

  • SharePoint field objects - classname, name and types

    In one of my SharePoint 2007 projects, I was working with SharePoint fields in code. I have spent quite a bit of time searching for all the classnames and fieldtypes that I needed to identify all different field types. In the table below you can find the most important fields.

    Classname Type TypeAsString TypeDisplayName TypeShortDescription
    Microsoft.SharePoint.SPFieldBoolean Boolean Boolean Yes/No Yes/No (check box)
    Microsoft.SharePoint.SPFieldCalculated Calculated Calculated Calculated Calculated (calculation based on other columns)
    Microsoft.SharePoint.SPFieldChoice Choice Choice Choice Choice (menu to choose from)
    Microsoft.SharePoint.SPFieldComputed Computed Computed Computed Computed
    Microsoft.SharePoint.SPFIeld ContentTypeId ContentTypeId Content Type Id Content Type Id
    Microsoft.SharePoint.SPFieldCurrency Currency Currency Currency Currency ($, ¥, €)
    Microsoft.SharePoint.SPFieldDateTime DateTime DateTime Date and Time Date and Time
    Microsoft.SharePoint.SPFieldFile File File File File
    Microsoft.SharePoint.SPField Guid Guid Guid Guid
    Microsoft.SharePoint.SPFieldNumber Integer Integer Integer Integer
    Microsoft.SharePoint.Portal.WebControls.BusinessDataField Invalid BusinessData Business data Business data
    Microsoft.SharePoint.Publishing.Fields.ContentTypeIdFieldType Invalid ContentTypeIdFieldType Content Type Id Content Type Id
    Microsoft.SharePoint.Publishing.Fields.HtmlField Invalid HTML Publishing HTML Full HTML content with formatting and constraints for publishing
    Microsoft.SharePoint.Publishing.Fields.ImageField Invalid Image Publishing Image Image with formatting and constraints for publishing
    Microsoft.SharePoint.Publishing.Fields.LayoutVariationsField Invalid LayoutVariationsField Variations Page Layout Variations
    Microsoft.SharePoint.Publishing.Fields.LinkField Invalid Link Publishing Hyperlink Hyperlink with formatting and constraints for publishing
    Microsoft.SharePoint.Publishing.Fields.PublishingScheduleEndDateField Invalid PublishingScheduleEndDateFieldType Publishing Schedule End Date Publishing Schedule End Date
    Microsoft.SharePoint.Publishing.Fields.PublishingScheduleStartDateField Invalid PublishingScheduleStartDateFieldType Publishing Schedule Start Date Publishing Schedule Start Date
    Microsoft.SharePoint.Publishing.Fields.SummaryLinkField Invalid SummaryLinks SummaryLinks Summary Links data
    Microsoft.Office.Server.WebControls.FieldTypes.SPFieldTargetTo Invalid TargetTo Audience Targeting Audience Targeting
    Microsoft.SharePoint.SPFieldLookup Lookup Lookup Lookup Lookup (information already on this site)
    Microsoft.SharePoint.SPFieldLookup Lookup LookupMulti Lookup Lookup (information already on this site)
    Microsoft.SharePoint.SPFieldNumber Number Number Number Number (1, 1.0, 100)
    Microsoft.SharePoint.SPFieldRecurrence Recurrence Recurrence Recurrence Recurrence
    Microsoft.SharePoint.SPFieldMultiLineText Note Note Multiple lines of text Multiple lines of text
    Microsoft.SharePoint.SPFieldText Text Text Single line of text Single line of text
    Microsoft.SharePoint.SPFieldUrl URL URL Hyperlink or Picture Hyperlink or Picture
    Microsoft.SharePoint.SPFieldUser User User Person or Group Person or Group

    The second column contains the value of SPField.Type. This is one of the values of the SPFieldType enumeration. All fields that have a Type other that “Invalid” can be created by using the Add method of a SPFieldCollection object and passing Name, FieldType (SPFieldType) and Required. The other fields can be added by instantiating an object of the specified class and adding that SPField object to the SPFieldCollection. Below you will find 2 samples:

        SPSite site = new SPSite("http://office2007");
        SPWeb web = site.OpenWeb();
        SPList list = web.Lists["Documenten"];
     
        // Add a new text field
        list.Fields.Add("NewTextField", SPFieldType.Text, false);
     
        // Add a new HTML field
        HtmlField htmlField = new HtmlField(
            list.Fields, "HTML", "NewHTMLField");
        list.Fields.Add(htmlField);

    The string that is passed as the second parameter in the second sample, is the value in the “TypeAsString” column in the table.

  • Using SharePoint 2007 audiences in combination with MOSS Publishing placeholders

    In one of our projects we had a requirement to target the content of a SharePoint Publishing HTML field to an audience. The content should only be displayed to users that are member of an audience. It was very easy to create this and below you can find the steps to create this yourself:

    • Create a new class that inherits from Microsoft.SharePoint.Publishing.WebControls.RichHtmlField
    • Add a public property that holds the name of the audience
    • Override the Onload and implement this as below:
          protected override void OnLoad(EventArgs e)
          {
              base.OnLoad(e);
              SPSite site = SPControl.GetContextSite(Context);
              ServerContext context = ServerContext.GetContext(site);
              AudienceManager audienceManager = new AudienceManager(context);
              Audience audience = audienceManager.GetAudience(this.Audience);
              SPUser user = SPControl.GetContextWeb(Context).CurrentUser;
              if (audience == null ||
                  !audience.IsMember(user.LoginName))
              {
                  this.Visible = false;
              }
          }
    • Add a new Publishing HTML field to the Pages library called “MyTargetedField”.
    • Edit one of your Page Layouts and add a registration for your assembly :
      <%@ Register Tagprefix="EogWebControls" Namespace="TST.WebParts.AudienceRichHtmlField"
      Assembly="TST.WebParts, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3" %>
    • Add the new webpart to your page layout and set the Audience property to “MyAudience” :
      <EogWebControls:AudienceRichHtmlField id="PageContent4" Audience="MyAudience" FieldName="MyTargetedField" runat="server"/>
    • Create an audience “MyAudience” with some rules and compile it.
    • Create a page based on this page layout and test if the content you entered in the “MyTargetedField” only shows up for members of the “MyAudience” audience.

    As you can see, it is very easy to do with just a few lines of code.

    Erwin, thanks for preparing this!

  • Deploy a SharePoint list template to SharePoint MySites

    A collegue of mine prepared a list in her SharePoint 2007 MySite. Her question was how she could make this list available as a template in everybody’s MySite. The issue with this is that every MySite is a separate Site Collection, and therefore every MySite has its own site columns, content types and template galleries. This makes it difficult to deploy to list template to 1 location from where it can be used.

    There are a number of ways how you can get around this. I choose to create a feature that uploads STP files to the list template gallery when the feature gets activated. The feature gets activated in the site definition, so when a user creates his/her MySite for the first time, the STP files are uploaded to the list template gallery. In our case, this was only part of the solution, because the feature is not activated on existing MySites. I created a STSADM command that activates the feature if it is not yet activated. This is a generic command that you can use to (de)activate any site feature on all sites based on a specific template.

    Step 1 – Create a FeatureReceiver

    The first step is to create a FeatureReceiver that uploads the STP file if it does not exist. If the list template is already available in the list template gallery, the feature deletes the file and uploads the latests version. The feature checks the feature folder for a subfolder called “ListTemplates”. All STP files in this folder are uploaded to the List template gallery. First we get a reference to the site the feature gets activated for and the STP files in the folder:

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite site = properties.Feature.Parent as SPSite;
     
            if (site != null)
            {
                try
                {
                    string directory = properties.Definition.RootDirectory;
                    if (!directory.EndsWith(@"\"))
                        directory += @"\";
                    directory += "ListTemplates";
                    if (System.IO.Directory.Exists(directory))
                    {
                        string[] templates = System.IO.Directory.GetFiles(
                            directory, "*.stp", System.IO.SearchOption.TopDirectoryOnly);
                        SPDocumentLibrary listTemplates =
                            site.GetCatalog(SPListTemplateType.ListTemplateCatalog)
                            as SPDocumentLibrary;
                        UploadTemplates(listTemplates, templates);
                    }
                }
                finally
                {
                    site.Dispose();
                }
            }
        }

    The code snippet also gets reference to the List template gallery. This gallery is a SharePoint document library and can be found using the GetCatalog function of our SPSite object. The method ‘UploadTemplates’ actually takes care of uploading the STP file:

     

        private void UploadTemplates(SPDocumentLibrary templateGallery, string[] templateFiles)
        {
            if (templateGallery != null)
            {
                foreach (string template in templateFiles)
                {
                    System.IO.FileInfo fileInfo = new System.IO.FileInfo(template);
                    SPQuery query = new SPQuery();
                    query.Query = string.Format(
                        "<Where><Eq><FieldRef Name='FileLeafRef'/>" +
                        "<Value Type='Text'>{0}</Value></Eq></Where>", fileInfo.Name);
                    SPListItemCollection existingTemplates = templateGallery.GetItems(query);
     
                    int[] Ids = new int[existingTemplates.Count];
                    for (int i = 0; i < existingTemplates.Count; i++)
                    {
                        Ids[ i ] = existingTemplates[ i ].ID;
                    }
                    for (int j = 0; j < Ids.Length; j++)
                    {
                        templateGallery.Items.DeleteItemById(Ids[j]);
                    }
     
                    byte[] stp = System.IO.File.ReadAllBytes(template);
                    templateGallery.RootFolder.Files.Add(fileInfo.Name, stp);
                }
            }
        }

    This method first checks the template gallery if a template with the same filename exists. If it exists, it is deleted first. Then the STP is uploaded to  the rootfolder of the gallery. This turned out very handy, because just after I installed this to our production environment and uploaded the STP to all MySites, there was a change in the schema of the list….. It was very easy to redeploy the new list template:

    • Deactivate the feature on all MySites using my custom STSADM command (see step 4)
    • Copy the new STP file to the ListTemplates folder in the feature folder
    • Activate the feature on all existing MySites

    The class you create should inherit from Microsoft.SharePoint.SPFeatureReceiver. You will have to compile this into an assembly and install that to your SharePoint server(s).

    Step 2 – Create the feature XML

    The next step is to create the XML for the feature and install the feature using STSADM. The xml for my feature looks like this:

      <?xml version="1.0" encoding="utf-8" ?>
      <Feature  Id="3DB2BE7A-E5DD-400d-B853-EB1F59E57E85"
                Title="Template deployment for list templates"
                Description="This feature uploads list templates to the Template Gallery of a site collection."
                Version="1.0.0.0"
                Scope="Site"
                ReceiverAssembly="TST.FeatureReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=503edd7b21a430b3"
                ReceiverClass="TST.FeatureReceivers.TemplateDeployment"
                xmlns="http://schemas.microsoft.com/sharepoint/">
      </Feature>

    This xml needs to be saved in a file called “feature.xml” that is copied into a subfolder of the TEMPLATE\FEATURES folder in the 12 hive. The feature can be installed using STSADM.

    Step 3 – Create a Stapler for the feature

    After installing the feature we need to activate it. Normally you create a new site definition and activate the feature from the site definition (in onet.xml). For the MySite this is difficult, because it is not a very good idea to modify the existing SharePoint site definitions. SharePoint always uses the SPSPERS site definition and that cannot be changed. This is where feature stapling can help. Read this blog by Chris O’Brian if you want to learn more about stapling, or read this item by Steve Peschka. You simply create a new feature folder with a feature.xml file like below. 

      <?xml version="1.0" encoding="utf-8" ?>
      <Feature  Id="ED23C370-9FC1-4d76-8495-F3BAEFE931CA"
                Title="Stapler for TemplateDeployment feature"
                Scope="Farm"
                xmlns="http://schemas.microsoft.com/sharepoint/">
        <ElementManifests>
          <ElementManifest Location="elements.xml"/>
        </ElementManifests>
      </Feature>

    This is a feature that is “Farm” scoped and adds our TemplateDeployment feature to the site collection features to be activated with the SPSPERS site definition. In the elements.xml that is referenced in this feature file, you add this xml. Please note that the Guid in the Id attribute of the FeatureSiteTemplateAssociation element is the same as the Id of the feature (see xml in step 2).

      <?xml version="1.0" encoding="utf-8" ?>
      <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <FeatureSiteTemplateAssociation 
          Id="3DB2BE7A-E5DD-400d-B853-EB1F59E57E85"
          TemplateName="SPSPERS#0" />
      </Elements>

    Then you install the feature using STSADM.

    Step 4 – Create a STSADM command

    The next step is to create the STSADM command that activate/deactivates site scoped features. To do this, you create a new class that inherits from ISPStsadmCommand. This base class is available in the Microsoft.SharePoint.StsAdmin namespace. In your class you will need to implement GetHelpMessage and a Run method. In the first method, you return the help text for users that ask for help on the command prompt. The Run method has 3 parameters:

    • command – the command that the STSADM user is using (that is entered after “-o”).
    • keyValues – a StringCollection containing the options that the user has entered
    • output – an output variable that you can set to give feedback to the user about the result of your command.

    An example of my STSADM command:

         stsadm" -o activatesitefeature -featurefolder MySiteListTemplates -template SPSPERS

    In this case “activatesitefeature” is the command in the Run method and “featurefolder” and “template” are the settings that I need. They are part of the keyValues. Below you will find my implementation of the Run command. Just below the code you will find some comments on this piece of code.

        public int Run(string command, System.Collections.Specialized.StringDictionary keyValues, out string output)
        {
            if (command != "activatesitefeature" && command != "activatesitefeature")
            {
                throw new ArgumentException("Invalid command");
            }
            string folder = keyValues["featurefolder"];
            if (string.IsNullOrEmpty(folder))
                throw new ArgumentException("Parameter 'featurefolder' must have a value.");
     
            string template = keyValues["template"];
            if (string.IsNullOrEmpty(template))
                throw new ArgumentException("Parameter 'template' must have a value.");
     
            SPFeatureDefinition activateFeature = null;
     
            int errors = 0;
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                foreach (SPFeatureDefinition feature in SPFarm.Local.FeatureDefinitions)
                {
                    if (feature.RootDirectory.ToLower().EndsWith(folder.ToLower()))
                    {
                        activateFeature = feature;
                        break;
                    }
                }
                if (activateFeature == null)
                {
                    throw new ArgumentException(string.Format(
                        "Feature in folder {0} not found. Please install the feature first.", 
                        folder));
                }
                if (activateFeature.Scope != SPFeatureScope.Site)
                {
                    throw new ArgumentException(string.Format(
                        "The scope for feature in folder {0} is not correct; should be Site.", 
                        folder));
                }
     
                SPWebService service = SPFarm.Local.Services.GetValue<SPWebService>("");
                foreach (SPWebApplication webApplication in service.WebApplications)
                {
                    for (int i = 0; i < webApplication.Sites.Count; i++)
                    {
                        using (SPSite site = webApplication.SitesIdea)
                        {
                            using (SPWeb web = site.RootWeb)
                            {
                                if (string.Compare(web.WebTemplate, template, true) == 0)
                                {
                                    bool activate = true;
                                    Guid remove = Guid.Empty;
                                    foreach (SPFeature checkFeature in site.Features)
                                    {
                                        if (checkFeature.DefinitionId == activateFeature.Id)
                                        {
                                            switch (command.ToLower())
                                            {
                                                case "activatesitefeature":
                                                    activate = false;
                                                    break;
                                                case "deactivatesitefeature":
                                                    remove = checkFeature.DefinitionId;
                                                    break;
                                            }
                                        }
                                    }
                                    switch (command.ToLower())
                                    {
                                        case "activatesitefeature":
                                            if (activate)
                                            {
                                                try
                                                {
                                                    site.Features.Add(activateFeature.Id);
                                                }
                                                catch (Exception ex)
                                                {
                                                    errors++;
                                                    LogMessage(string.Format("Error: {0}", ex.Message));
                                                }
                                            }
                                            break;
                                        case "deactivatesitefeature":
                                            if (remove != Guid.Empty)
                                            {
                                                try
                                                {
                                                    site.Features.Remove(remove, true);
                                                }
                                                catch (Exception ex)
                                                {
                                                    errors++;
                                                    LogMessage(string.Format("Error: {0}", ex.Message));
                                                }
                                            }
                                            break;
                                    }
                                }
                            }
                        }
                    }
                }
            });
            output = string.Format("Deactivation completed with {0} errors.", errors);
            return 0;
        }

    First all input parameters (command and options) are validated. After that you will see that I used SPSecurity.RunWithElevatedPrivileges, this is because the STSADM command will run in the context of the user who is logged on to the SharePoint server to run the command. This user typically is a farm administrator, but this person probably does not have the permissions to activate a feature on a MySite. By running the code with elevated privileges, the code runs with Full Control. The next step is to search for the featuredefinition that the user wants to activate. If this feature cannot be found, or the scope of the feature is wrong, an error message is raised. In the next step I start to iterate through all web applications on the SPWebService service. For each SPSite in all web applications, I check if the WebTemplate of the RootWeb is equal to the value of the template parameter passed by the user. If these are equeal, the site feature gets activated (if it is not yet activated) or deactivated, depending on the command. Activating a feature is done by using the Add method of the Features collection of the site and passing the guid of the featuredefinition.

    Step 5 – Register the STSADM command

    After compiling and deploying the assembly, the STSADM command needs to be registered. This can be done by creating a xml file with the xml below. The file should be named “stsadmcommands[myname].xml” where [myname] is your unique name. In this file you register the commands and link them to the assembly:

      <?xml version="1.0" encoding="utf-8" ?>
      <commands>
        <command
          name="activatesitefeature"
          class="TST.STSADM.FeatureActivation, 
              TST.STSADM, Version=1.0.0.0, Culture=Neutral, 
              PublicKeyToken=503edd7b21a430b3"
          />
        &