Saturday, March 31, 2007

MOSS/WSS : New view style for Lists "Preview Pane"

From the Sharepoint team blog comes this post how to get a preview pane for viewing listitems in a list (which I've never seen before and is actually quite cool!) :

  • Create a new (contacts) list. (This is a good example since this list type has many columns by default.)
  • On the List default view page, click Settings on the toolbar, and then click on Create View.
  • Give the view a name and add all available columns.
  • Scroll down to the Style section. Expand the section and select Preview Pane.
  • Click OK.
  • Hover over the list of items. Notice that as you hover each item, all info related to that item is displayed on the section to the right. The content of the preview pane appears in a similar fashion to the typical item display form. The end results looks similar to the following:
  • This is also not only handy for a contacts list but also for an Issue list, since you can very quickly scan the latest issues.

    Friday, March 30, 2007

    Good ol' 2003

    It may suprise you that I find a couple of things slightly better in the 2003 version of Sharepoint than the 2007 version. One thing is that consistency (and the lack of ;)) of navigation. In SPS2003, you could alert yourself on almost anything (the link was always there!). For example, you could alert yourself on changes of an area. So if a document was added to a document library that was placed on that area you received an alert. Even better was that when a subarea was created, you received an alert that a new area had been discovered!

     

    Now it could be me but I haven't found this 'feature' yet in 2007 (MOSS/WSS), thing that comes close is the searchresult alertme functionality. So if anyone knows where I can find this piece of functionality in 2007 please let me know!

    Wednesday, March 28, 2007

    SPD Workflow activity : Copying a listItem accros a site

    *Update* Download the solution here

    In addition to my previous post I also created a custom workflow activity for Sharepoint Designer where a listitem can be copied accros a site and/or to a list that is not available during the creation of the workflow but will be when the workflow is executed (using workflow variables).

     

    using System;
    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Collections;
    using System.Diagnostics;
    using System.Drawing;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Workflow.Runtime;
    using System.Workflow.Activities;
    using System.Workflow.Activities.Rules;
    using Microsoft.SharePoint;
     
    namespace CopyListItemExtended
    {
        public partial class List: SequenceActivity
        {
            private EventLog _eventLog;
     
            public static DependencyProperty ListItemIDProperty = 
    DependencyProperty.Register("ListItemID", typeof(string), typeof(List));
            public static DependencyProperty SrcUrlProperty = 
    DependencyProperty.Register("SrcUrl", typeof(string), typeof(List));
            public static DependencyProperty DstUrlProperty = 
    DependencyProperty.Register("DstUrl", typeof(string), typeof(List));
            public static DependencyProperty SrcListNameProperty = 
    DependencyProperty.Register("SrcListName", typeof(string), typeof(List));
            public static DependencyProperty DestListNameProperty = 
    DependencyProperty.Register("DestListName", typeof(string), typeof(List));
     
     
            [ValidationOption(ValidationOption.Required)]
            public string SrcUrl
            {
                get
                {
                    return (string)base.GetValue(SrcUrlProperty);
                }
                set
                {
                    base.SetValue(SrcUrlProperty, value);
                }
            }
     
            [ValidationOption(ValidationOption.Required)]
            public string DstUrl
            {
                get
                {
                    return (string)base.GetValue(DstUrlProperty);
                }
                set
                {
                    base.SetValue(DstUrlProperty, value);
                }
            }
     
            [ValidationOption(ValidationOption.Required)]
            public string SrcListName
            {
                get
                {
                    return (string)base.GetValue(SrcListNameProperty);
                }
                set
                {
                    base.SetValue(SrcListNameProperty, value);
                }
            }
     
            [ValidationOption(ValidationOption.Required)]
            public string DestListName
            {
                get
                {
                    return (string)base.GetValue(DestListNameProperty);
                }
                set
                {
                    base.SetValue(DestListNameProperty, value);
                }
            }
     
            [ValidationOption(ValidationOption.Required)]
            public string ListItemID
            {
                get
                {
                    return (string)base.GetValue(ListItemIDProperty);
                }
                set
                {
                    base.SetValue(ListItemIDProperty, value);
                }
            }
     
            protected override ActivityExecutionStatus 
    Execute(ActivityExecutionContext executionContext)
            {
                //Set up the Event Logging object 
                _eventLog = new EventLog("Workflow");
                _eventLog.Source = "SharePoint Workflow";
     
                try
                {
                    //Send the email 
                    Copy();
                }
                finally
                {
                    //Dispose of the Event Logging object 
                    _eventLog.Dispose();
                }
     
                //Indicate the activity has closed 
                return ActivityExecutionStatus.Closed;
            }
     
     
            private void Copy()
            {
                SPSite srcSite = new SPSite(SrcUrl);
                SPWeb srcWeb = srcSite.OpenWeb();
     
                SPSite dstsite = new SPSite(DstUrl);
                SPWeb dstweb = dstsite.OpenWeb();
     
     
                string error = string.Empty;
     
                try
                {
                    SPList destList = dstweb.Lists[DestListName];
                    SPListItem item = srcWeb.Lists[SrcListName].GetItemById(Convert.ToInt32(ListItemID));
                    item.CopyTo(dstweb.Url.ToString() + "/" + destList.Title.ToString() + 
    "/" + item.File.Name.ToString());
                    _eventLog.WriteEntry("Worklow succes: Item : " + item.Title.ToString() 
    + " has been succesfully copied from : " + srcWeb.Lists[SrcListName].Title.ToString()
    + " to : " + destList.Title.ToString());
     
                }
                catch (System.Exception Ex)
                {
                    //Log exceptions in the Event Log 
                    _eventLog.WriteEntry("Workflow Error :" + Ex.Message.ToString() 
    + error.ToString(), EventLogEntryType.Information);
                }
     
                finally 
                {
                    srcWeb.Close();
                    srcWeb.Dispose();
                    srcSite.Close();
                    srcSite.Dispose();
     
                    dstweb.Close();
                    dstweb.Dispose();
                    dstsite.Close();
                    dstsite.Dispose();
                }
            } 
     
     
            public List()
            {
                InitializeComponent();
            }
     
     
        }
    }


    And the including XML chunk that needs to be added in WSS.ACTIONS:

    <Action Name="Copy List Item Extended"

    ClassName="CopyListItemExtended.List"
    Assembly="CopyListItemExtended, Version=1.0.0.0, Culture=neutral, PublicKeyToken=54d1a6fdf526a294"
    AppliesTo="all"
    Category="Sample">
      <RuleDesigner Sentence="Copy List %1 from this %2 to this %3 and from this %4 to this %5.">
        <FieldBind Field="ListItemID" Text="ItemID" DesignerType="TextArea" Id="1"/>
        <FieldBind Field="SrcUrl" Text="Source Site" DesignerType="TextArea" Id="2"/>
        <FieldBind Field="DstUrl" Text="Destination Site" DesignerType="TextArea" Id="3"/>
        <FieldBind Field="SrcListName" Text="Source List" DesignerType="TextArea" Id="4" />
        <FieldBind Field="DestListName" Text="Destination List" DesignerType="TextArea" Id="5" />
      </RuleDesigner>
      <Parameters>
        <Parameter Name="ListItemID" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="SrcUrl" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="DstUrl" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="SrcListName" Type="System.String, mscorlib" Direction="In" />
        <Parameter Name="DestListName" Type="System.String, mscorlib" Direction="In" />
      </Parameters>
    </Action>

    SPD Workflow activity : Creating a document library

    Using the codesample of Todd Baginski's post of creating a custom activity to email, I modified the code to create a doclib using two variables that an user enters in SPD. These two variables are the url of the site and the title of the document library.

    I replaced the code of Todd with this chunk :

    using System;

    using System.ComponentModel;
    using System.ComponentModel.Design;
    using System.Collections;
    using System.Diagnostics;
    using System.Drawing;
    using System.Workflow.ComponentModel;
    using System.Workflow.ComponentModel.Design;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Workflow.Runtime;
    using System.Workflow.Activities;
    using System.Workflow.Activities.Rules;
    using Microsoft.SharePoint;
     
    namespace CreateDocumentLibrary
    {
        public partial class Doclib: SequenceActivity
        {
     
            private EventLog _eventLog; 
     
            public static DependencyProperty UrlProperty = 
    DependencyProperty.Register("Url", typeof(string), typeof(Doclib));
            public static DependencyProperty ListNameProperty = 
    DependencyProperty.Register("ListName", typeof(string), typeof(Doclib));
     
     
            [ValidationOption(ValidationOption.Required)]
            public string Url
            {
                get
                {
                    return (string)base.GetValue(UrlProperty);
                }
                set
                {
                    base.SetValue(UrlProperty, value);
                }
            }
     
            [ValidationOption(ValidationOption.Required)]
            public string ListName
            {
                get
                {
                    return (string)base.GetValue(ListNameProperty);
                }
                set
                {
                    base.SetValue(ListNameProperty, value);
                }
            }
     
            protected override ActivityExecutionStatus 
    Execute(ActivityExecutionContext executionContext)
            {
                //Set up the Event Logging object 
                _eventLog = new EventLog("Workflow");
                _eventLog.Source = "SharePoint Workflow";
     
                try
                {
                    //Send the email 
                    CreateList();
                }
                finally
                {
                    //Dispose of the Event Logging object 
                    _eventLog.Dispose();
                }
     
                //Indicate the activity has closed 
                return ActivityExecutionStatus.Closed;
            }
     
            private void CreateList()
            {
                try
                {
                SPSite site = new SPSite(Url);
                SPWeb rootweb = site.OpenWeb();
                SPListCollection lists = rootweb.Lists;
     
     
                SPListTemplateType listTemplateType = new SPListTemplateType();
                listTemplateType = SPListTemplateType.DocumentLibrary;
                lists.Add(ListName, "", listTemplateType);
             _eventLog.WriteEntry("Worklow succes: List 
    created at: "
    + rootweb.Title.ToString());
                }
                catch (System.Exception Ex)
                {
                    //Log exceptions in the Event Log 
                    _eventLog.WriteEntry("Workflow Error :" + 
    Ex.Message.ToString(), EventLogEntryType.Information);
                }
            } 
     
            public Doclib()
            {
                InitializeComponent();
            }
        }
    }
     

    And the XML to publish this code in SPD in the WSS.ACTIONS looks like:

     

    <Action Name="Create Document Library"
    ClassName="CreateDocumentLibrary.Doclib"
    Assembly="CreateDocumentLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=54d1a6fdf526a294"
    AppliesTo="all"
    Category="Sample">
    <RuleDesigner Sentence="Create document library %1 on %2.">
    <FieldBind Field="Url" Text="Link to site" DesignerType="TextArea" Id="1"/>
    <FieldBind Field="ListName" Text="Name of the doclib" Id="2" DesignerType="TextArea"/>
    </RuleDesigner>
    <Parameters>
    <Parameter Name="Url" Type="System.String, mscorlib" Direction="In" />
    <Parameter Name="ListName" Type="System.String, mscorlib" Direction="In" />
    </Parameters>
    </Action>

    </Actions>

    So if you want use this code, follow all the steps that are described in the post of Todd and replace his code and xml with mine and you could be a happy man! ;)

    Monday, March 26, 2007

    Commercial: VW Touareg

    This has nothing to-do with Sharepoint or any other IT related subjects but I just find this Volkswagen Touareg commercial so absolutely brilliant I wanted it to share it with you ;) 

    The casting is superb! And erm.. it kinda feels like promoting my own pieces of code ;)

    Sunday, March 25, 2007

    WSS v3 : Visual Studio 2005 Extensions released

    Good news for us developers, version 1.0 of the Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions is now released! Grab your copy from the Microsoft site.

    Things that bother me though are the system requirements :

    System Requirements
    • Supported Operating Systems: Windows Server 2003
    • Windows SharePoint Services 3.0 (Basic installation only), or any product built on Windows SharePoint Services 3.0, such as Microsoft Office SharePoint Server 2007
    • Visual Studio 2005 (Standard Edition, Professional Edition, or Team System)

    It's kinda odd isn't it? The CTP ran perfectly without needing Sharepoint and Win2k3 on my laptop.

    Friday, March 23, 2007

    SharePoint Information Architecture and the Information Architect

    If you are, like me, a mix of a Sharepoint developer as well as a consultant and a bit of an architect you should definitly check out SharePoint Information Architecture and the Information Architect from Joel Oleson. He clearly points out what to think about when you are about to design and implement Sharepoint for customers and how to already deal with things the future may be bring.

    Wednesday, March 21, 2007

    MOSS/WSS 2007: Floating toolpane

    From Vincent Rothwell comes this nice little add-on that enables the toolpane, which you use to edit properties of a particular webpart, to float. Another nice features of this is that is also resizeable and it can be minimized :) Nice work mate!

     

    Vista: Dreamscene

    Have you guys seen Dreamscene yet on Vista? It's one of the most useless features ever created but damn it's cool! Basically, if you don't know what it is, it's a movieclip that runs on your desktop instead of a backgroundpicture. But the movieclips that are included are so subtle (apart from the "rain-on-the-street" clip, that's just depressing :P) you hardly even notice that is 'moving'. And you got to have the Ultimate version of Vista installed to enable it (maybe when Dreamscene is released it will be made available for every copy of Vista?)

    Check out the movieclip below to see what is like : 

    Format: wmv
    Duration: 00:16

    Check out the Dreamscene Team blog at : http://windowsultimate.com/blogs for more information

    Tuesday, March 20, 2007

    SPS/WSS 2003 : Intelligent backup procedure

    Ok, so it's a bad practice to use STSADM to backup your sites (check Keith Richie's post and his implementation of a new backup application Permanent Link to Perform non-intrusive Site Collection level backups with SPLSBackup) The backup procedure of the environment of the customer I'm currently located at, takes up about 12hrs per day (about 200+gb over 450+ sites). This is, ofcourse, unacceptable since the backup runs during working hours.
    As we are facing an upgrade to MOSS, the implementation of the recycle bin for 2003 is not desirable because 1. it takes too much effort of the business and 2. the problem had to be solved real soon

    As a result I created an new backup application using the object model of Sharepoint. Instead of backing every site every day, I only check if a site is changed (ContentLastModified or SecurityLastModified) and only back up that site.
    And to make it even more fancy I store the results of every backup (including: sizing and duration of the backup) in a list on a Sharepoint site that is designed for monitoring (later in MOSS I even can use the KPI capabilities to make it more fancy :))

    btw. The StopWatch function came from http://www.codinghorror.com/blog/archives/000460.html so thanks Jeff Atwood :)

    using System;
    using System.Collections;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;
     
    namespace IncrementBackup
    {
        /// <summary>
        /// Summary description for Class1.
        /// </summary>
        class Class1
        {
            /// <summary>
            /// Application consists of three functions. 
            /// 1. Get all the sites that were modified today. Either content or security wise
            /// 2. Backup all the sites that were collected in the previous function
            /// 3. Push information about the backups into a custom Sharepoint List to 
            /// To store all the relevant data about a site, I created a class called "SiteItem.cs"
            /// To monitor the time of each backup, I found a sample code with acts as a stopwatch "StopWatch.cs"
            /// </summary>
     
            [STAThread]
            static void Main(string[] args)
            {
                Console.WriteLine("Getting sites");
     
                ArrayList sites = GetSites();
     
                foreach (SiteItem item in sites)
                {
                    Backup(item);
                }
                Console.WriteLine("Pushing data into Sharepoint");
                InsertDataIntoList(sites);
            }
     
            //***Function to get all the teamsites***
            //Instead of backupping with this function, 
            //I only collect the urls of the site which have been changed. 
            //The reason is that if I backup immediatly, 
            //the lastmodifieddate and securitymodifieddate are not accurate 
            //anymore since the backup could take more than 12 hours.
            private static ArrayList GetSites()
            {
                ArrayList sites = new ArrayList();
     
                try
                {
     
                    //Get the sitecollection through the Administration model using SPGlobalAdmin and SPVirtualServer
                    SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
                    System.Uri uri = new System.Uri(PortalUrl);
                    SPVirtualServer virtualServer = globalAdmin.OpenVirtualServer(uri);
                    SPSiteCollection siteCollections = virtualServer.Sites;
     
                    //For each site in the sitecollection collect all relevant information from which the content or security is changed today
                    foreach (SPSite site in siteCollections)
                    {
                        if (site.LastContentModifiedDate.Date == DateTime.Today.Date || site.LastSecurityModifiedDate.Date == DateTime.Today.Date)
                        {
     
                            SiteItem item = new SiteItem();
                            item.SiteName = site.RootWeb.Title.ToString();
                            item.Url = site.RootWeb.Url.ToString();
                            item.ModifiedContentDate = site.LastContentModifiedDate;
                            item.ModifiedSecurityDate = site.LastSecurityModifiedDate;
     
                            //We also want to publish information about the sizing of the site
                            SPSite.UsageInfo info = site.Usage;
                            item.Size = Convert.ToString(info.Storage / 1024 / 1024);
     
                            //Here we put the object into the ArrayList
                            sites.Add(item);
                        }
     
                        //Disposing of the objects
                        site.RootWeb.Close();
                        site.RootWeb.Dispose();
                        site.Close();
                        site.Dispose();
                    }
     
                }
                catch (Exception error)
                {
                    Console.WriteLine(error.Message.ToString());
                }
     
                return sites;
     
            }
     
            //***Function to backup every single site
     
            private static void Backup(SiteItem item)
            {
                //To monitor how long each backup takes I found a sample code of using a stopwatch            
                Stopwatch sp = new Stopwatch();
                sp.Start();
     
                //Since it's not possible to declare these functions as a public variable, we have to declare them again
                //to backup each site
                SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
                System.Uri uri = new System.Uri(PortalUrl);
                SPVirtualServer virtualServer = globalAdmin.OpenVirtualServer(uri);
     
                Console.WriteLine(item.Url);
     
                //Open the site using the URL which is stored in the SiteItem object
                SPSite site = virtualServer.Sites[item.Url];
                try
                {
                    //Backup the site
                    Console.WriteLine("Site is currently being backupped");
     
                    //Replacing all the odd characters to ensure that a correct filename can be made
                    string normal = item.SiteName;
                    normal = normal.Replace("/", "");
                    normal = normal.Replace(" ", "_");
                    normal = normal.Replace("@", "_");
                    normal = normal.Replace("&", "_");
                    normal = normal.Replace("(", "");
                    normal = normal.Replace("}", "");
                    normal = normal.Replace(":", "_");
                    normal = normal.Trim();
     
                    item.SiteName = normal;
     
                    //Only backup the teamsites, not the portal
                    if (item.Url.IndexOf("sites", 0, item.Url.Length - 1) > -1)
                    {
                        //The actual backup
                        virtualServer.Sites.Backup(item.Url, @"\\fileshare\teamsites\" + item.SiteName + ".bak", true);
                    }
     
                    Console.WriteLine("Backup is made");
                }
                catch (Exception error)
                {
                    //If an error is raised, we want to see them in the list where we are pushing all the SiteItem objects into
                    item.ErrorMessage = error.Message.ToString();
                    Console.WriteLine(error.Message.ToString());
                }
                finally
                {
                    //Dispose all the objects                
                    site.Close();
                    site.Dispose();
                }
     
                sp.Stop();
     
                item.Duration = sp.Elapsed;
     
            }
     
            //***Function to push all the information about the backups into a custom list
            private static void InsertDataIntoList(ArrayList sites)
            {
                //Open the site where the list belongs to            
                SPSite site = new SPSite(TeamSiteForMonitoring);
                SPWeb web = site.RootWeb;
                try
                {
                    //Open the list
                    SPList list = web.Lists["Sitebackups"];
                    //Foreach backup that was made we push them in the list
                    foreach (SiteItem _item in sites)
                    {
                        SPListItem item = list.Items.Add();
                        item["Title"] = _item.SiteName;
                        item["Duration"] = "Minutes : " + _item.Duration.Minutes.ToString() + "  Seconds: " + _item.Duration.Seconds.ToString() + " Miliseconds: " + _item.Duration.Milliseconds.ToString();
                        item["Last_x0020_modified_x0020_date"] = _item.ModifiedContentDate.ToShortDateString();
                        item["Last_x0020_Security_x0020_modifi"] = _item.ModifiedSecurityDate.ToShortDateString();
                        item["Size"] = _item.Size + "mb";
                        item["Error"] = _item.ErrorMessage.ToString();
                        item["Link"] = _item.Url.ToString();
                        item.Update();
                    }
                }
                catch (Exception error)
                {
                    Console.WriteLine(error.Message.ToString());
                }
     
                finally
                {
                    //Dispose all the objects
                    web.Close();
                    web.Dispose();
                    site.Close();
                    site.Dispose();
                }
            }
     
        }
    }