Microsoft SharePoint Developer Documentation Team Blog
The Official Blog of the SharePoint Developer Documentation Team

  • Microsoft SharePoint Developer Documentation Team Blog

    Download: Workflow Development Resources Interactive Map

    • 2 Comments

    (Cross-posted from my individual work blog.)

    For the last several weeks, I’ve been experimenting with using mind maps, both as a way of brainstorming about developer content, and as a possible way of presenting content in a less-structured way than a typical (linear) table of contents. In regards to that last goal, I really liked how mind maps let you graphically lay out a content area, and draw non-linear relationships between various nodes. I could see how this could be a much more intuitive, discoverable way of presenting content, especially for a technology as complex and interrelated as Windows SharePoint Services. I was using an application called MindManager (from Mindjet) to create my mind maps, and was generally pretty happy with the results.

    There was just one problem: in order to share a fully-functioning map with anyone else, they needed to have the same software (or at least a downloadable viewer) installed. Sure, Mind Manager let me save a given mind map as a image map or static PDF, but each of those formats ruled out one of the main selling points on the mind maps themselves: the ability to collapse/expand various nodes on the map, and thereby control the level of information you were looking at. Without that interactive ability, it really seemed like the value of using mind maps as content/resource maps was fairly restricted.

    Perhaps MindJet thought the same thing, because the new version of MindManager now lets you save your mind maps as interactive PDF files. Now anyone who can view PDFs can also interact with your maps, and customize the view by collapsing/expanding the nodes of their choice.

    So I’ve created an interactive mind map as another way of presenting some of the information we currently have on MSDN, and I’ve love to know what people think of it. Basically, I’ve taken some of the content links present on the Workflow Resource Center and restructured them as a mind map. Rather than present the content based on content type or source, as the Resource Center does, I’ve tried to present the content grouped to present a workflow development overview, as well as by developer tool. Icons next to each link display what type of content I’m linking to (SDK topic, blog, multimedia, etc.); the text color denotes whether the link applies just to Windows SharePoint Services (green text) or Office SharePoint Server as well (blue text).

    I’m hoping you’ll take a few minutes and download the map, play with it, and let me know what you think, even if you don’t have any interest in workflow development. More than just does this specific map meet your needs, I’ve love to hear whether you think using such maps in general is a useful/worthwhile way to present developer documentation. Leave a comment after the post or ping me directly through the blog; I’d love to hear from you.

    (One minor drawback I can see is how much saving as an interactive PDF bloats the size of the file: this download, of a fairly small map, is 1.5M.)

    Now if we could just get them to add a ‘Save as Silverlight’ feature…

  • Microsoft SharePoint Developer Documentation Team Blog

    How We Address Developer Documentation Comments

    • 1 Comments

    One of the things I've wanted to do on this blog is give people a clearer view into how we actually produce developer documentation for SharePoint; the processes we use, the decisions we make, and what factors influence those decisions. (The Office client developer docs team have done several great posts over on the Office Client Developer Content blog around similar topics.) With that in mind, when someone asked me the other day what we actually do with those comments people enter for our content on MSDN, I figured it was worth answering here.

     

    The short answer is: we collect, classify, and prioritize them for action, pretty much like a software product team would triage bugs entered against their product.

     

    And here's the long answer:

     

    For each comment, when we initially receive it we assign a status, which is further broken down by a sub-status value as well. This enables us to separate out and focus on the comments for which some action can be taken, and group those comments in terms of what’s being requested. The status and sub-status values we use include:

    ·         New

    This is the status automatically assigned to comments that haven’t been classified yet.

    ·         In progress

    Comments that contain actionable content. The comment has been initially triaged and prioritized.

    o   Code sample

    User wants a code sample to illustrate what the topic covers.

    o   Incorrect/Incomplete info

    User points out that there is incorrect or incomplete information in the topic. (Not surprisingly, these are usually our first priority to fix.)

    o   More Info

    User is asking for more information to be added to the topic.

    o   More Research Needed

    We need to do more research on the comment before we can accurately categorize it.

    o   New Topic Requested

    User is asking for a new topic, separate from the topic where they added the comment.

    ·         No Action

    o   Negative

    Comments that are negative but not specific enough to be actionable.

    o   Noise

    Unintelligible comments, such as random typing.

    o   Positive

    Positive comment for which no action is necessary.

    ·         Off-topic

    Comments that don't apply to the topic itself, but might be actionable at another level.

    o   MSDN Issue

    Comment refers to a wider MSDN issue.

    o   Noise

    Comments that are intelligible, but have nothing to do with the topic ("I like cheese, do you like cheese?")

    o   Product Issue

    Comment refers to a product issue, such as whether the user likes the feature the topic describes, rather than the documentation itself.

    o   SDK Issue

    Comment refers to a larger issue concerning our content set, that might be present in the topic.

    ·         Spam

    Exactly what you'd expect: foreign financial scams, discounted prescriptions, offers to refinance our property at 1 Microsoft Way.

    ·         Fixed

    The actionable content of the comment has been addressed. For sub-status, we leave the value at what is was when the comment was In progress.

     

    Once we’ve got them classified, we can properly triage those comments that require action, prioritizing them along with the other content feedback we get from newsgroups, blogs, MVPs, MSDN Community Content comments, internal and external partner groups, and various other channels.

     

    Now that I’ve outlined what we do with the comments users enter, let’s take a quick look at what those comments actually say. So what are people telling us about the WSS SDK? Taking a look at the comments entered for the WSS 3.0 SDK since its publication online, here's how they currently break out, according to status:

    WSS SDK all comments by status

    As you can see, 46% of the comments actually require no action beyond the initial triage. Of the actionable comments, about two-thirds are still in some state of being worked on. Like I said, these comments are prioritized in with all the content suggestions we receive from other channels as well, so that our documentation team is working on the highest priority work items, regardless of the path it took to get to us.

     

    Looking a little closer at the comments still in progress, here's what you get when you break down those comments by sub-status:

     

    WSS SDK In Progress comments by sub-status 

     

    Users are overwhelmingly (78% of the total) asking for more information (52%) or code samples (26%). This is in line with feedback we’ve been getting through other channels, and is something we’ve been actively working to address.

     

    When you include the comments we've fixed, here are the classifications for all actionable comments we've received to date:

     

    WSS SDK all comments by sub-status 

     

    One other metric we periodically look at is the trend of the positive and negative comments over time. I'm really happy to see that as we periodically republish the SDK with new, expanded, and revised material, positive comments have tended to increase at a faster rate than negative ones:

     

    WSS SDK no action comments over time 

     

    We do some additional data analysis on comments, but that’s the basics. So next time you’re looking at developer documentation on MSDN, take a moment to enter a comment to let us know how we’re doing. I guarantee we’re on the other end, listening.

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Add Virtual Earth Interactive Maps to your Search Server Site

    • 2 Comments

    (cross-posted from Enterprise Search Blog

    Are you trying to think of ways to make your Search Server site more interactive and graphical? You can use Federation as a way to enhance the functionality of your search result pages.

    The Federated Search Web Part makes it possible to display more than results from OpenSearch (1.0/1.1) sites on your search results page. The Search Server 2008 SDK explains how to include results from SQL Server database queries and search sites that do not expose XML feeds (such as Atom or RSS). In both scenarios, you do this by means of a "connector," a light-weight interface that sends queries to a given location or database, places the results into a structured XML document, and sends that XML to a Federated Search Web Part.


    This sample demonstrates how you can extend the connector concept to Web service requests other than basic search queries. It shows how to use a connector to pass an address string to Microsoft's MapPoint Web service in order to obtain the latitude and longitude coordinates for that address. Once you have those coordinates, displaying a Microsoft Virtual Earth map requires only the addition of some Javascript to your Federated Location definition file. See this Codeplex project for the sample code (along with a sample Federated location definition file) and an explanation of how to implement it on your own site.

    This sample is part of the Search Community Toolkit.

     

     Jim Crowley
    Programming Writer
    Microsoft Corp

  • Microsoft SharePoint Developer Documentation Team Blog

    How to: Create a Content Class That Can Be Backed Up and Restored

    • 1 Comments

    Introduction

    If you have a custom content component that you want to be included in Windows SharePoint Server 3.0 backups and restores, you must represent the component with a class that implements the IBackupRestore interface. This post explains how to do that. There is a complete example following the procedures. This post assumes that you are familiar with my earlier post: Programming with the Windows SharePoint Services Backup/Restore Object Model.

    Note: Unless explicitly stated otherwise, all classes and interfaces referred to in this post are in the Microsoft.SharePoint.Administration.Backup namespace. Classes that you will create are in bold pink. Also, this post refers at times to the “reference topic” for this or that method/property for supplementary information. As of the date of this posting these reference topics in the most recent versions of the WSS SDK (1.3), in either download or MSDN form, did not yet contain this new material. Look for another update soon. The additional information is not necessary for using this post as a learning exercise or implementing the example it contains.

    Your class does not have to derive from Microsoft.SharePoint.Administration.SPPersistedObject but if your content is a database, we recommend that you derive your class from either Microsoft.SharePoint.Administration.SPDatabase or from Microsoft.SharePoint.Administration.SPContentDatabase. Both of the latter classes are derived from SPPersistedObject and both implement IBackupRestore. Therefore, you will have default implementations of members of IBackupRestore that you can use when appropriate.

    You can create as many types of IBackupRestore classes you want and, if you want, they can be nested as a tree of component classes. But the highest class in any such tree must derive (directly or indirectly) from SPPersistedObject object and must be a child of Microsoft.SharePoint.Administration.SPFarm. If your content class is not a child of any other custom content class, it must derive (directly or indirectly) from SPPersistedObject object and must be a child of SPFarm.

    If your class derives from a class that already implements IBackupRestore object (whether or not it derives from SPPersistedObject), and you want to replace an inherited implementation of an IBackupRestore member, your class declaration should explicitly reference IBackupRestore like this:

    [C#]

    public class MyClass : SPPersistedObject, IBackupRestore

    Your "override" of any IBackupRestore member should explicitly include "IBackupRestore" in the member name and it should not include the public keyword. The following is an example:

    [C#]

    UInt64 IBackupRestore.DiskSizeRequired { ... }

    Alternatively, if the implementation of the member in the parent class used the virtual or override keywords, you can use the override keyword in your implementation like this:

    [C#]

    public override UInt64 DiskSizeRequired { ... }

    Warning: Do not hide the inherited member implementation by redeclaring the member either with or without the new keyword ([new] public UInt64 DiskSizeRequired { ... }). In the procedure below, the member signatures are written as they would be for a class that does not derive from a class that already implements IBackupRestore. Be sure to change them to the required pattern if your class does derive from such a parent.

    If your class derives from SPPersistedObject, let the Id and Name properties of that class serve as the implementation of the IBackupRestore.Id and IBackupRestore.Name properties. You may override the properties, but do not create a second implementation of either of them. Your class should have just one Name and one Id property.

    Procedures

     

    To Implement the Members of IBackupRestore

    1.    Begin a new Class project in Visual Studio.

    2.    Add a reference to Windows SharePoint Services to your Visual Studio project and add using statements for the Microsoft.SharePoint.Administration and Microsoft.SharePoint.Administration.Backup namespaces to your class file.

    3.    If your class does not derive from SPPersistedObject, implement the IBackupRestore.Name property. This will serve as the name of the content component in the UI of stsadm.exe, the Central Administration application and the UI of any custom backup and restore application. In most cases you implement the property by creating a private field for the name value and implement the public property as a wrapper around the field. For information on possible variant implementations, see the reference topic for the property.

    [C#]

    private String name;

    public String Name

    {

    get {return name;}

    set {name = value;}

    }

    4.    If your class does not derive from SPPersistedObject, implement the IBackupRestore.Id property. In most cases, you implement the property by creating a private field for the name value and implement the public property as a wrapper around the field. For information on possible variant implementations, see the reference topic for the property.

    [C#]

    private Guid id;

    public Guid Id

    {

    get {return id;}

    set {id = value;}

    }

    5.    Implement the IBackupRestore.DiskSizeRequired property. If your class is just a container for some child IBackupRestore classes, the property should return 0. Otherwise, the property should calculate the size of the content. (Include the size of any non-IBackupRestore child objects, but do not include the size of any child IBackupRestore objects. They each have their own IBackupRestore.DiskSizeRequired property and Windows SharePoint Server 3.0 will add those values in automatically.) The following example sums the sizes of all the files whose paths are contained in a collection called FrontEndFilePaths.

    [C#]

    public UInt64 DiskSizeRequired

    {

        get

        {

            UInt64 total = 0;

            List<FileInfo> FrontEndFiles = new List<FileInfo>(NUMBER_OF_FILES_TO_BACK_UP);

           

            foreach (String path in FrontEndFilePaths)

            {

                FileInfo file = new FileInfo(path);

                FrontEndFiles.Add(file);

            }

           

            foreach (FileInfo file in FrontEndFiles)

            {

                total = total + (UInt64)file.Length;

            }

            

            return total;

        }

    }

    6.    Implement the IBackupRestore.CanSelectForBackup property. If users should never be able to backup objects of your class independently of a backup of the parent object, the get accessor should return false. If users should always be able to select any object of your class for independent backup, the get accessor should return true. In either case, the set accessor should be an empty pair of braces "{ }". If some objects of your class can be backed up independently of their parent, but some cannot be, implement the property as a wrapper around a private System.Boolean field.

    7.    Implement the IBackupRestore.CanSelectForRestore property. If users should never be able to restore objects of your custom component class independently of a restoration of the parent object, the get accessor should return false. If users should always be able to select any object of your class for independent restoration, the get accessor should return true. In either case, the set accessor should be an empty pair of braces "{ }". If some objects of your class can be restored independently of their parent, but some cannot be, implement the property as a wrapper around a private System.Boolean field.

    8.    Implement the IBackupRestore.CanRenameOnRestore property. If users should never be able to restore objects of your custom component class to a new location, the get accessor should return false. If users should be able to migrate any object of your class, the get accessor should return true. If objects of your class can sometimes be migrated, but not always, implement the property as a wrapper around a private System.Boolean field.

    9.    Implement the IBackupRestore.AddBackupObjects method.

    a.    Your implementation code should begin by throwing an exception if there is no valid parent to which the component can be added.

    b.    Use the SPBackupRestoreObject.AddChild method to add your component to the tree of objects that the backup or restore operation will process.

    c.    Use the SPBackupRestoreInformation.SetParameter method to specify a type name and description of the component that can be used by the UI of backup/restore applications.

    d.    If the component has child IBackupRestore objects, your implementation should iterate through them and recursively call the IBackupRestore.AddBackupObjects method of each child. 

    e.    See the reference topic for the IBackupRestore.AddBackupObjects method for more ideas about implementations of it.

    The following example code assumes that your content class has a ChildContentCollection of child IBackupRestore objects. If your class has more than one type of child component, you may have separate collections for each type and iterate through each collection.

    [C#]

    public void AddBackupObjects(SPBackupRestoreObject parent)

    {

        if (parent == null)

        {

            throw new ArgumentNullException("parent");

        }

     

        SPBackupRestoreObject self = parent.AddChild(this);

        self.Information.SetParameter(SPBackupRestoreObject.SPTypeName, this.GetType());

        self.Information.SetParameter(SPBackupRestoreObject.SPDescription,

        "Description of custom content component");

     

        foreach (ChildContent child in ChildContentCollection)

        {

            IBackupRestore childIBR = child as IBackupRestore;

            childIBR.AddBackupObjects(self);

        }

    }

    10. Implement the IBackupRestore.OnAbort method. It should always return true. In most cases it should do nothing more, but see the reference topic for IBackupRestore.OnAbort for information about exceptions to this general rule.

    11. Implement the IBackupRestore.OnPrepareBackup method. At a minimum, you should use the SPBackupRestoreInformation.SetParameter method to specify a name for the content object. Beyond that, few generalizations can be made. See the reference topic for IBackupRestore.OnPrepareBackup for more information. The following example shows a minimal implementation of the method, which is often all that is needed.

    [C#]

    public Boolean OnPrepareBackup(Object sender, SPBackupInformation args)

    {

        if (args == null)

        }

            throw new ArgumentNullException("args");

        }

        args.SetParameter(SPBackupRestoreObject.SPName, this.Name);

        return true;

    }

    12. Implement the IBackupRestore.OnBackup method. If your content class has no content outside of any IBackupRestore child objects it may have, your implementation should simply set the SPBackupRestoreInformation.CurrentProgess to a value that approximately represents the percentage of the total backup operation time that is consumed by the IBackupRestore.OnBackup and IBackupRestore.OnPrepareBackup methods. It should then return true as seen in the following example. Do not call the IBackupRestore.OnBackup method of any IBackupRestore child objects.

    [C#]

    public Boolean OnBackup(Object sender, SPBackupInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        args.CurrentProgress = 50;

        return true;

    }

    If your class does have content outside of any IBackupRestore child objects it may have, your implementation must copy this content to args.SPBackupRestoreInformation.Location and return false if the copy fails. You should include logic to backup any child objects that do not implement IBackupRestore, but you should not explicitly backup any child objects that do implement IBackupRestore. They will be backed up by their own IBackupRestore.OnBackup method, which the runtime will call. You should not call the IBackupRestore.OnBackup methods of the child objects in your own code. The following example shows the overall structure of a substantive implementation of IBackupRestore.OnBackup.

    [C#]

    public Boolean OnBackup(Object sender, SPBackupInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        args.CurrentProgress = 50;

        Boolean successSignal = true;

     

        // TODO: Implement copying your content to args.Location

        //       If the copy fails, set successSignal to false.

     

        return successSignal;

    }

    13. Implement the IBackupRestore.OnBackupComplete method. At a minimum, your implementation should set SPBackupRestoreInformation.CurrentProgess to 100 percent and return true as shown in the following example. This is typically all that is required. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnBackupComplete.

    [C#]

    public Boolean OnBackupComplete(Object sender, SPBackupInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        args.CurrentProgress = 100;

        return true;

    }

    14. Implement the IBackupRestore.OnPreRestore method. In most situations, a restoration operation requires no preparation and your implementation of IBackupRestore.OnPreRestore should just return true. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnPreRestore.

    15. Implement the IBackupRestore.OnRestore method.

    ·         If your content class can be migrated, your code should check to see what the restore method is and call SPBackupRestoreInformation.Rename if the method is New.

    ·         If your content class has no content outside of any IBackupRestore child objects it may have, your implementation should simply set the SPBackupRestoreInformation.CurrentProgess to a value that approximately represents the percentage of the total restore operation time that is consumed by the IBackupRestore.OnRestore and the IBackupRestore.OnPreRestore methods. It should then return true as seen in the following example. Do not call the IBackupRestore.OnRestore method of any IBackupRestore child objects.

    [C#]

    public Boolean OnRestore(Object sender, SPRestoreInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        if (args.RestoreMethod == SPRestoreMethodType.New)

        {

            args.Rename();

        }

        args.CurrentProgress = 50;

        return true;

    }

    ·         If your class does have content outside of any IBackupRestore child objects it may have, your implementation must copy this content to the restoration destination. Return false, if for any reason the copy of content fails.

    The following example shows the overall structure of a substantive implementation of IBackupRestore.OnRestore:

    [C#]

    public Boolean OnRestore(Object sender, SPRestoreInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        if (args.RestoreMethod == SPRestoreMethodType.New)

        {

            args.Rename();

        }

        args.CurrentProgress = 50;

        Boolean successSignal = true;

     

        // TODO: Implement copying your content to the destination.

        //       If the copy fails, set successSignal to false.

     

        return successSignal;

    }

    16. Implement the IBackupRestore.OnPostRestore method. At a minimum, your implementation should set SPBackupRestoreInformation.CurrentProgess to 100 percent and return true as shown in the following example. This is typically all that is required. For information about other work your implementation may need to perform, see the reference topic for IBackupRestore.OnPostRestore.

    [C#]

    public Boolean OnPostRestore(Object sender, SPRestoreInformation args)

    {

        if (args == null)

        {

            throw new ArgumentNullException("args");

        }

        args.CurrentProgress = 100;

        return true;

    }

     

     

    Add other Members to Your Class As Needed

    17. Add fields, properties, and helper methods as needed to complete your class. As you work, keep these points in mind:

    ·         Use fields and properties to hold child content objects.

    ·         If your class derives from Microsoft.SharePoint.Administration.SPPersistedObject, then the declaration of fields that you want to persist in the configuration database must be preceded with the [Persisted] attribute. However, you can only mark the following types of fields in this way: primitive types such as strings, integers, and GUIDs; other Microsoft.SharePoint.Administration.SPPersistedObject objects or Microsoft.SharePoint.Administration.SPAutoserializingObject objects; or collections of any of the above. For example, the class cannot have a System.IO.FileInfo field marked with the [Persisted] attribute. If the data you would like to persist is not of a persistable class, use a persistable substitute. The sample implementation above of the IBackupRestore.DiskSizeRequired property envisions a class that persists a collection of file names and uses them to create a temporary collection of System.IO.FileInfo objects at runtime.

    ·         If your class can have multiple children of the same type, create a property or field of a collection type or other enumerable type to hold a collection of all children of a given type. This is particularly important if the child type itself implements IBackupRestore, because your implementation of the IBackupRestore.AddBackupObjects method should iterate through such children and call the IBackupRestore.AddBackupObjects method of each child. See the procedure step for implementing the IBackupRestore.AddBackupObjects method above, for more information.

    18. Add constructors to your class to initialize its fields and properties as needed. If the class derives from SPPersistedObject, there must be at least one constructor that names the object and assigns it to a parent. Typically, such a constructor takes at least these two arguments:

    ·         A String argument that will be the name of the content object.

    ·         An SPPersistedObject argument that represents the parent of the content object.

    This constructor must call the base constructor that takes the same two arguments. The following is an example:

    [C#]

    public MyContentComponent(String componentName, SPPersistedObject parent, SomeType someOtherArgument, ... )

                       : base(componentName, parent)

    {

        somePrivateField = someOtherArgument;

        ...

    }

    You must pass Microsoft.SharePoint.Administration.SPFarm.Local as the parent when the content object is the top most object in a tree of custom IBackupRestore objects. If your custom component type is always the top most object, then leave out the SPPersistedObject argument and hard code a reference to SPFarm.Local in the call to the base constructor. The following is an example:

    [C#]

    public MyContentComponent(String componentName, SomeType someOtherArgument, ... )

                       : base(componentName, SPFarm.Local)

    {

        somePrivateField = someOtherArgument;

        ...

    }

    If objects of your class always have the same name, you can leave out the String argument and hard code the name in the call to the base constructor. (If all objects of a given type have the same name, there should never be more than one child of that type for a given parent and; thus, no more than one object of that type on the entire farm if the object is a child of the farm.)

    19. Compile your class project.

    Warning: You must give the assembly a strong name and put the assembly in the General Assembly Cache (GAC).

     

     

    To Create an Object of Your Class and Make it a Child of the Farm

    20. Start a new console application project in Visual Studio.

    21. Add a reference to the DLL of your custom component class to the project.

    22. Add a using statement for Microsoft.SharePoint.Administration.

    23. Add a using statement for the namespace that you used in your custom component class (or just use the same namespace in your console application).

    24. Add to the Main method of your project a call to the constructor of your custom component class. If you created a hierarchy of custom types, call the constructor of the top most class.

    25. If needed, precede the call to the constructor of the component with code that creates parameters for the constructor.

    26. After the call to the constructor of your component, your code should call the component object's SPPersistedObject.Update method. The following is an example of what you should have in the Main method:

    [C#]

    MyContentComponent myContentObject = new MyContentComponent("component name", SPFarm.Local);

    myContentObject.Update();

    27. Compile and run the application.

    28. In the Central Administration application navigate to Operations | Perform a Backup. Your object should appear as child of the farm on the Perform a Backup page.

    Note: There is a sample console application for creating and deleting custom content objects in the Example section below.

     

    Development Advice

    The following tips may be helpful as you develop your custom content classes particularly because you will probably be creating objects and adding them to the farm multiple times as you work.

     

    Points to Keep in Mind

    1.    If you need to delete your object from the configuration database, use SPPersistedObject.Delete.

    2.    An exception is thrown if you call obj.Update() and there is already an object of the same class as obj with the same SPPersistedObject.Name property value and the same parent in the configuration database. There is an overloaded version of SPPersistedObject.Update that may be preferable.

    3.    There is an example console application in the second Example section below that can be used to add or delete your custom objects from the configuration database.

    4.    Run iisreset at the command line after every recompile of your IBackupRestore class. You may need to reboot the server as well.

    5.    The various IBackupRestore.On* methods take either a SPBackupInformation parameter or a SPRestoreInformation parameter. You can use their members for debugging purposes. Particularly helpful is the SPBackupRestoreInformation.Log method.

     

    Example

    The following code implements a custom content component that represents a single Web.config file on a front end server. Replace the TestSite part of the file path in the constructor implementation with a directory name from your test server. The compiled assembly must be strong-named and installed in the GAC.

    In the example following the class implementation, there is the code for a simple console application that will register the component as a child of the farm or delete it from the farm.

    using System;

    using System.IO;

    using System.Collections.Generic;

    using Microsoft.SharePoint.Administration;

    using Microsoft.SharePoint.Administration.Backup;

     

    namespace MyCompany.SharePoint.Administration

    {

        public class CriticalFiles : SPPersistedObject, IBackupRestore

        {

     

            public CriticalFiles() { }

     

            public CriticalFiles(String componentName, SPPersistedObject parent)

                       : base(componentName, parent)

            {

                String pathOfFile = @"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\TestSite\Web.config";

                FrontEndFilePaths.Add(pathOfFile);

            }

     

            [Persisted]

            private const Int32 NUMBER_OF_FILES_TO_BACK_UP = 1;

     

            [Persisted]

            private List<String> FrontEndFilePaths = new List<String>(NUMBER_OF_FILES_TO_BACK_UP);

     

            public Boolean CanSelectForBackup

            {

                get { return true; }

                set { }

            }

     

            public Boolean CanSelectForRestore

            {

                get { return true; }

                set { }

            }

     

            public Boolean CanRenameOnRestore

            {

                get { return false; }

            }

     

            public UInt64 DiskSizeRequired

            {

                get

                {

                    UInt64 total = 0;

                    List<FileInfo> FrontEndFiles = new List<FileInfo>(NUMBER_OF_FILES_TO_BACK_UP);

                   

                    foreach (String path in FrontEndFilePaths)

                    {

                        FileInfo file = new FileInfo(path);

                        FrontEndFiles.Add(file);

                    }

                   

                    foreach (FileInfo file in FrontEndFiles)

                    {

                        total = total + (UInt64)file.Length;

                    }

                   

                    return total;

                }

            }

     

            public void AddBackupObjects(SPBackupRestoreObject parent)

            {

                if (parent == null)

                {

                    throw new ArgumentNullException("parent");

                }

     

                SPBackupRestoreObject self = parent.AddChild(this);

                self.Information.SetParameter(SPBackupRestoreObject.SPTypeName, this.GetType());

                self.Information.SetParameter(SPBackupRestoreObject.SPDescription, "The critical files on all front end servers.");

            }

     

            public Boolean OnAbort(Object sender, SPBackupRestoreInformation args)

            {

                return true;

            }

     

            public Boolean OnPrepareBackup(Object sender, SPBackupInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

                args.SetParameter(SPBackupRestoreObject.SPName, this.Name);

                return true;

            }

     

            public Boolean OnBackup(Object sender, SPBackupInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

               

                Boolean successSignal = true;

     

                foreach (String path in FrontEndFilePaths)

                {

                    FileInfo file = new FileInfo(path);

                    try

                    {

                        String mappedFileName = args.GenerateFileMapping(file.Name);

                        file.CopyTo(args.Location + @"\" + mappedFileName, true);

                        args.Log(SPBackupRestoreLogSeverity.Verbose, "Backed up " + file.Name + " in (" + mappedFileName + ")");

                    }

                    catch (Exception e)

                    {

                        args.Log(SPBackupRestoreLogSeverity.Verbose, file.Name + " not backed u " + e.Message);

                        successSignal = false;

                    }

                }

     

                args.CurrentProgress = 50;

                return successSignal;

            }

     

            public Boolean OnBackupComplete(Object sender, SPBackupInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

                args.CurrentProgress = 100;

                return true;

            }

     

            public Boolean OnPreRestore(Object sender, SPRestoreInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

                return true;

            }

     

            public Boolean OnRestore(Object sender, SPRestoreInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

     

                // If the CriticalFiles object was deleted from the farm after it was

                // backed up, restore it to the configuration database.

                CriticalFiles cf = SPFarm.Local.GetChild<CriticalFiles>(this.Name);

                if (cf == null)

                {

                    this.Update();

                    args.Log(SPBackupRestoreLogSeverity.Verbose, this.Name + " added back to configuration database.");

                }

     

                Boolean successSignal = true;

     

                // TODO: The following loop restores files to the local server. If there are

                //       multiple front end servers, your code must iterate through all of

                //       SPFarm.Local.Servers and restore the same files to every server whose

                //       Role property is SPServerRole.WebFrontEnd

     

                foreach (String path in FrontEndFilePaths)

                {

                    FileInfo backupCopy = new FileInfo(path);

                    String mappedFileName = args.ReverseFileMapping(backupCopy.Name);

                    FileInfo file = new FileInfo(args.Location + @"\" + mappedFileName);

     

                    try

                    {

                        file.CopyTo(path, true);

                        args.Log(SPBackupRestoreLogSeverity.Verbose, "Restored " + backupCopy.Name);

                    }

                    catch (Exception e)

                    {

                        args.Log(SPBackupRestoreLogSeverity.Verbose, file.Name + " not restored: " + e.Message);

                        successSignal = false;

                    }

                }

               

                args.CurrentProgress = 50;

                return successSignal;

            }

           

            public Boolean OnPostRestore(Object sender, SPRestoreInformation args)

            {

                if (args == null)

                {

                    throw new ArgumentNullException("args");

                }

     

                args.CurrentProgress = 100;

                return true;

            }

     

        }

    }

    Description

    The following is a console application that will add or delete your content object from the configuration database.

    using System;

    using System.Collections.Generic;

    using System.Text;

    using Microsoft.SharePoint.Administration;

    using Microsoft.SharePoint.Administration.Backup;

     

    namespace MyCompany.SharePoint.Administration

    {

        class Program

        {

            static void Main(string[] args)

            {

                CriticalFiles cf = SPFarm.Local.GetChild<CriticalFiles>("Critical Front End Files");

                if (cf == null)

                {

                    Console.WriteLine("There is no CriticalFiles object in the configuration database.");

                    Console.Write("Enter 'A' to add it. Press Return to do nothing:");

                    String response = Console.ReadLine();

                    if (response == "A")

                    {

                        CriticalFiles myCriticalFiles = new CriticalFiles("Critical Front End Files", SPFarm.Local);

                        myCriticalFiles.Update();

                    }

                }

                else

                {

                    Console.WriteLine("There is a CriticalFiles object in the configuration database.");

                    Console.Write("Enter 'D' to delete it. Press Return to do nothing:");

                    String response = Console.ReadLine();

                    if (response == "D")

                    {

                        cf.Delete();

                    }

                }

            }// end Main

        }// end Program

    }

     

    See Also

    Programming with the Windows SharePoint Services Backup/Restore Object Model

    How to: Programmatically Back Up Content

    How to: Programmatically Restore Content

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    How to: Programmatically Back Up and Restore a Single Site Collection

    • 2 Comments

    Introduction

    This post describes how to back up and restore individual site collections programmatically.

    Note: Unless explicitly stated otherwise, all classes referred to in this post are in the Microsoft.SharePoint.Administration or Microsoft.SharePoint namespaces (not Microsoft.SharePoint.Administration.Backup).

    Procedures                         

     

    To Back Up or Restore a Site Collection

    1.    Add to your Visual Studio project a reference to Windows SharePoint Services.

    2.    Add using statements for Microsoft.SharePoint and Microsoft.SharePoint.Administration.   

    3.    Add the following lines to obtain a reference to the farm and its collection of services.

    [C#]

    SPFarm myFarm = SPFarm.Local;

    SPServiceCollection myServices = myFarm.Services;

    4.    Obtain a reference to the Web service that publishes the Web application that hosts your site collection by using the service's System.Guid which is the value of its SPWebService.Id property.

    [C#]

    Guid serviceID = new Guid("21d91b29-5c5b-4893-9264-4e9c758618b4");

    SPWebService webPubService = (SPWebService)myServices[serviceID];

    If you do not know the SPWebService.Id of the application publishing Web service, you can iterate through all the services and report their SPWebService.Name, SPWebService.TypeName, and SPWebService.Id. The following is an example:

    [C#]

    foreach (SPService service in myServices)

    {

        if (service is SPWebService)

        {

        Console.WriteLine("Web service name:" + webService.Name);

        Console.WriteLine("Web service type:" + webService.TypeName);

        Console.WriteLine("Web service ID:" + webService.Id);

        Console.WriteLine();

        Console.Readline();

        }

    }

    5.    Obtain a reference to the Web application that hosts your site collection. If you know the URL of the Web application you can obtain a reference with the static SPWebApplication.Lookup method. Alternatively, you can use the application's System.Guid which is the value of its SPWebApplication.Id property. The following code shows the second method.

    [C#]

    SPWebApplicationCollection myApps = webPubService.WebApplications;

    Guid appID = new Guid("10ea4e6f-ae37-4909-b04f-f516c066bc37");

    SPWebApplication myApp = myApps[appID];

    If you do not know the SPWebApplication.Id of the Web application that hosts your site collection, you can iterate through all the Web applications and report their SPWebApplication.Name, SPWebApplication.TypeName, and SPWebApplication.Id. The following is an example:

    [C#]

    foreach (SPWebApplication app in webApps)

    {

        Console.WriteLine("Web application name:" + app.Name);

        Console.WriteLine("Web application type:" + app.TypeName);

        Console.WriteLine("Web application ID:" + app.Id);

        Console.WriteLine();

        Console.Readline();

    }

    6.    Get a reference to the Web application's collection of site collections.

    [C#]

    SPSiteCollection mySiteCols = myApp.Sites;

    7.    To back up a site collection, call the SPSiteCollection.Backup method. As parameters pass the following:

    ·         The full URL of the site collection; that is, the full URL of its Top Level Web site.

    ·         The full path and file name of the file that will hold the compressed content of the site collection.

    ·         True, if the operation should overwrite an existing backup file of the same name; false, if it should not.

    [C#]

    mySiteCols.Backup(@"htt//Server/sites/MySiteCollection", @"\\OtherServer\WSSBackups\SiteCollections\BackupOfMySiteCollection", true);

    8.    To restore a site collection, call the SPSiteCollection.Restore method. It takes the same parameters as the SPSiteCollection.Backup method. The Boolean parameter indicates whether the site collection should be overwritten if it already exists at the specified URL.

    [C#]

    mySiteCols.Restore(@"htt//Server/sites/MySiteCollection", @"\\OtherServer\WSSBackups\SiteCollections\BackupOfMySiteCollection", true);

     

    Example

    The following example shows a simple way to programmatically back up or restore a site collection. You will need to replace all the System.Guid values with actual values from your deployment and replace the placeholder values in the Backup and Restore methods with actual URLs and paths from your deployment.

     

    // Get a reference to the Web application publishing

    // Web service.

    SPFarm myFarm = SPFarm.Local;

    SPServiceCollection myServices = myFarm.Services;

    Guid serviceID = new Guid("21d91b29-5c5b-4893-9264-4e9c758618b4");

    SPWebService webPubService = (SPWebService)myServices[serviceID];

     

    // Get a reference to the Web application that hosts the

    // site collection.

    SPWebApplicationCollection myApps = webPubService.WebApplications;

    Guid appID = new Guid("10ea4e6f-ae37-4909-b04f-f516c066bc37");

    SPWebApplication myApp = myApps[appID];

     

    // As alternative to the preceding three lines, you can use

    // the following when you know the URL of the Web application:

    //     SPWebApplication myApp = SPWebApplication.Lookup(url_of_Web_app)

     

    // Get a reference to the Web application's collection of

    // site collections.

    SPSiteCollection mySiteCols = myApp.Sites;

     

    // Back up a specified site collection.

    mySiteCols.Backup(@"htt//Server/sites/MySiteCollection", @"\\OtherServer\WSSBackups\SiteCollections\BackupOfMySiteCollection", true);

     

    // Restoring the site collection is identical to the preceding

    // code except that the "Restore" is used in place of "Backup".

    //

    // mySiteCols.Restore(@"htt//Server/sites/MySiteCollection", @"\\OtherServer\WSSBackups\SiteCollections\BackupOfMySiteCollection", true);

    Comments

    The Microsoft.SharePoint.SPSite class does not implement Backup.IBackupRestore and the SPSiteCollection.Backup and SPSiteCollection.Restore methods do not use the facilities of the Microsoft.SharePoint.Administration.Backup namespace. This means that records of backups and restorations of site collections are not kept in a history file (spbrtoc.xml) in the backup directory. Similarly, backup and restoration data is not stored in spbackup.xml or sprestore.xml files, neither are these site collection operations logged in spbackup.log or sprestore.log files.

    If you want to do any kind of logging of backups and restorations of site collection operations, you will have to program your own system. Writing to the system-created spbrtoc.xml, spbackup.xml, sprestore.xml, spbackup.log, and sprestore.log files is not supported in Windows SharePoint Services 3.0. Neither is moving them, deleting them, or renaming them. However, you can create files that merge data from the system-created files with data from your site collection backups and restorations.

    See Also

    Programming with the Windows SharePoint Services Backup/Restore Object Model

    How to: Programmatically Back Up Content

    How to: Programmatically Restore Content

    How to: Create a Content Class That Can Be Backed Up and Restored

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    How to: Programmatically Restore Content

    • 2 Comments

     

    Introduction

    This post explains how to create an application that restores from a backup a content component, including a custom content component, of a Windows SharePoint Services 3.0 farm. The post assumes that you are familiar with my previous posts Overview of Backing Up and Restoring Data in Windows SharePoint Services and Programming with the Windows SharePoint Services Backup/Restore Object Model.

    Note: Unless specified otherwise, all classes referred to in this post are in the Microsoft.SharePoint.Administration.Backup namespace. Classes that you create are in bold pink.

    Procedures

     

    To Restore a Content Component

    1.    Add a reference to Windows SharePoint Services to your Visual Studio project and add using statements for the Microsoft.SharePoint.Administration and Microsoft.SharePoint.Administration.Backup namespaces to you code file.

    2.    Inside the Main method, create a SPRestoreSettings object by using the static SPBackupRestoreSettings.GetRestoreSettings method. For the first parameter pass the path where the backup is stored. For the second parameter pass a string version of one of the values of SPRestoreMethodType.

    [C#]

    SPRestoreSettings settings = SPBackupRestoreSettings.GetRestoreSettings((@"\\Server\WSSBackups", "Overwrite");

    3.    Prompt the user to specify the content component that is to be restored and assign its name to the SPBackupRestoreSettings.IndividualItem property. To see an itemization of the names of the components on your farm that were included in the last full backup and that can be the objects of restore operations, you can either run the command stsadm -o restore -showtree at the server command line. To specify a different full backup package, use the -backupid parameter. Alternatively, you can visit Operations > Perform a Restore in the Central Administration application. To specify the whole farm, use "Farm" as the name. (Setting the property to null also selects the whole farm for backup assuming that you use SPBackupRestoreSettings.IndividualItem in all subsequent code to identify by name the component to be restored, as you should. For an example, see the use of the SPBackupRestoreConsole.FindItems method in step 9.)

    [C#]

    Console.Write("Enter name of component to restore (default is whole farm):");

    settings.IndividualItem = Console.ReadLine();

    4.    If you want to restore from a backup other than the most recent, identify the backup package by assigning its GUID to the SPRestoreSettings.BackupId property. A record of each backup operation for a particular backup location is stored in spbrtoc.xml in the root of the location. Each backup and restore operation is represented in the file by an <SPHistoryObject> element. If the operation is a backup, the <IsBackup> child of the <SPHistoryObject> element is "True". The <SPId> element of the <SPHistoryObject> element contains the GUID of the backup.

    Note: To programmatically obtain the list of all backup and restore operations, use the SPBackupRestoreConsole.GetHistory method. This method returns an SPBackupRestoreHistoryList object that contains SPBackupRestoreHistoryObject objects. Each of the latter represents an operation and holds its GUID in the SPBackupRestoreHistoryObject.SelfId property. 

    [C#]

    settings.BackupId = new Guid("GUID");

    5.    Optionally, set one or both of the SPBackupRestoreSettings.IsVerbose and SPBackupRestoreSettings.UpdateProgress properties. (For details about these properties, see the reference topics for them.)

    [C#]

    settings.IsVerbose = true;

    settings.UpdateProgress = 10;

    6.    If necessary, set the SPRestoreSettings.FarmAdminLoginName and SPRestoreSettings.FarmAdminLoginPassword properties.

    [C#]

    settings.FarmAdminLoginName = "Bob";

    settings.FarmAdminPassword = "7*j2U";

    7.    Create the restore operation with the SPBackupRestoreConsole.CreateBackupRestore method. (A history object for the operation is also created.)

    [C#]

    Guid restore = SPBackupRestoreConsole.CreateBackupRestore(settings);

    8.    If your UI has users type a component name instead of pick one from a list, you must make sure that the name entered matches exactly one component. Add the following line to your Main method.

    [C#]

    SPBackupRestoreObject node = EnsureUniqueValidComponentName(settings, ref restore);

    9.    Add the following declaration and implementation of your EnsureUniqueValidComponentName method. Use the SPBackupRestoreConsole.FindItems method to retrieve a collection of content objects whose names match the user-entered name. If there is no match, prompt the user to try again. If there is more than one, prompt the user to be more specific. If the component name that the user entered is valid and not ambiguous, get a reference to the SPBackupRestoreObject object that represents the component that the user wants to restore.

    [C#]

    private static SPBackupRestoreObject EnsureUniqueValidComponentName(SPBackupRestoreSettings settings, ref Guid operationGUID)

    {

        SPBackupRestoreObjectCollection list = SPBackupRestoreConsole.FindItems(operationGUID, settings.IndividualItem);

        SPBackupRestoreObject component = null;

     

        if (list.Count <= 0)

        {

            Console.WriteLine("There is no component with that name. Run again with a new name.");

            Console.WriteLine("Press Enter to continue.");

            Console.ReadLine();

        }

        else if (list.Count > 1)  // The component name specified is ambiguous. Prompt user to be more specific.

        {

            Console.WriteLine("More than one component matches the name you entered.");

            Console.WriteLine("Run again with one of the following:");

            for (int i = 0; i < list.Count; i++)

            {

                Console.WriteLine("\t{0}", list[i].ToString());

            }

            Console.WriteLine("Press Enter to continue.");

            Console.ReadLine();

        }

        else

        {

            component = list[0];

        }

     

        return component;

     

    }

    10. In the Main method, create a conditional structure that will run only if your EnsureUniqueValidComponentName method has returned a valid node.

    [C#]

    if (node != null)

    {

        // TODO: Set the restore operation as the active operation

        // and run it.

    }

    11. Replace the "TODO" line in the previous step with the following code. This sets the operation to be the active operation with the SPBackupRestoreConsole.SetActive method and tests to verify that it succeeded. If it fails, which it will if another backup or restore operation is already underway, report an error to the UI of your application.

    [C#]

    if (SPBackupRestoreConsole.SetActive(restore) == true)

    {

        // TODO: Run the operation. See next step.

    }

    else

    {

        // Report through your UI that another backup

        // or restore operation is underway.

        Console.WriteLine("Another backup or restore operation is already underway. Try again when it ends.");

    }

    12. In the code branch that runs if the SPBackupRestoreConsole.SetActive call succeeds, run the operation with the SPBackupRestoreConsole.Run method. Test that the operation succeeds. If it fails, report the operation's failure message to your UI. The following code replaces the "TODO" line in the previous step.

    [C#]

    if (SPBackupRestoreConsole.Run(restore, node) == false)

    {

        // Report "error" through your UI.

        String error = SPBackupRestoreConsole.Get(restore).FailureMessage;

        Console.WriteLine(error);

    }

    13. Clean up the restore with the SPBackupRestoreConsole.Remove method. Add the following code just before the closing brace you inserted in step 10.

    [C#]

    // Clean up the operation.

    SPBackupRestoreConsole.Remove(restore);

     

    Console.WriteLine("Restore attempt complete. Press Enter to continue.");

    Console.ReadLine();

     

     

     

    Example

    The following code shows how to program a restoration of a content component. Replace the placeholder \\Server\WSSBackups with the path of your backup location. The runtime will automatically find the most recent backup at that location.

     

    using System;

    using Microsoft.SharePoint.Administration;

    using Microsoft.SharePoint.Administration.Backup;

     

    namespace MyCompany.SharePoint.Administration.Backup

    {

        class Restore

        {

            static void Main(string[] args)

            {

                // Create the restore settings.

                SPRestoreSettings settings = SPBackupRestoreSettings.GetRestoreSettings(@"\\Server\WSSBackups", "Overwrite");

     

                // Identify the content component to restore.

                Console.Write("Enter name of component to restore (default is whole farm):");

                settings.IndividualItem = Console.ReadLine();

               

                // Set optional operation parameters.

                settings.IsVerbose = true;

                settings.UpdateProgress = 10;

               

                // Create the restore operation and return its ID.

                Guid restore = SPBackupRestoreConsole.CreateBackupRestore(settings);

     

                SPBackupRestoreObject node = EnsureUniqueValidComponentName(settings, ref restore);

     

                if (node != null)

                {

                    // Set the restore as the active job and run it.

                    if (SPBackupRestoreConsole.SetActive(restore) == true)

                    {

                        if (SPBackupRestoreConsole.Run(restore, node) == false)

                        {

                            // Report "error" through your UI.

                            String error = SPBackupRestoreConsole.Get(restore).FailureMessage;

                            Console.WriteLine(error);

                        }

                    }

                    else

                    {

                        // Report through your UI that another backup

                        // or restore operation is underway.

                        Console.WriteLine("Another backup or restore operation is already underway. Try again when it ends.");

                    }

     

                    // Clean up the operation.

                    SPBackupRestoreConsole.Remove(restore);

     

                    Console.WriteLine("Restore attempt complete. Press Enter to continue.");

                    Console.ReadLine();

                }

            }// end Main

     

            private static SPBackupRestoreObject EnsureUniqueValidComponentName(SPBackupRestoreSettings settings, ref Guid operationGUID)

            {

                SPBackupRestoreObjectCollection list = SPBackupRestoreConsole.FindItems(operationGUID, settings.IndividualItem);

                SPBackupRestoreObject component = null;

     

                if (list.Count <= 0)

                {

                    Console.WriteLine("There is no component with that name. Run again with a new name.");

                    Console.WriteLine("Press Enter to continue.");

                    Console.ReadLine();

                }

                else if (list.Count > 1)  // The component name specified is ambiguous. Prompt user to be more specific.

                {

                    Console.WriteLine("More than one component matches the name you entered.");

                    Console.WriteLine("Run again with one of the following:");

                    for (int i = 0; i < list.Count; i++)

                    {

                        Console.WriteLine("\t{0}", list[i].ToString());

                    }

                    Console.WriteLine("Press Enter to continue.");

                    Console.ReadLine();

                }

                else

                {

                    component = list[0];

                }

     

                return component;

     

            }// end EnsureUniqueValidComponentName

     

        }// end Restore class

    }// end namespace

     See Also

    Programming with the Windows SharePoint Services Backup/Restore Object Model

    How to: Programmatically Back Up Content

    How to: Create a Content Class That Can Be Backed Up and Restored

     

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    How to: Programmatically Back Up Content

    • 2 Comments

    Introduction

    This post explains how to program a backup of a content component, including custom components, in a Windows SharePoint Services 3.0 farm. The post assumes that you are familiar with my previous posts Overview of Backing Up and Restoring Data in Windows SharePoint Services and Programming with the Windows SharePoint Services Backup/Restore Object Model.

    Note: Unless specified otherwise, all classes referred to in this post are in the Microsoft.SharePoint.Administration.Backup namespace. Classes that you create are in bold pink.

    Procedures

     

    To Back Up a Content Component

    1.    Add a reference to Windows SharePoint Services to your Visual Studio project and add using statements for the Microsoft.SharePoint.Administration and Microsoft.SharePoint.Administration.Backup namespaces to you code file.

    2.    Inside the Main method, prompt the user to specify where the backup should be stored.

    [C#]

    Console.Write("Enter full UNC path to the directory where the backup will be stored:");

    String backupLocation = Console.ReadLine();

    3.    Inside the Main method, create a SPBackupSettings object by using the static SPBackupRestoreSettings.GetBackupSettings method. For the first parameter pass the path where the backup should be stored. For the second parameter pass a string version of one of the values of SPBackupMethodType.

    [C#]

    SPBackupSettings settings = SPBackupRestoreSettings.GetBackupSettings(backupLocation, "Full");

    4.    Prompt the user to specify the content component to back up and assign its name to the SPBackupRestoreSettings.IndividualItem property. To see an itemization of the names of the components on your farm that can be the objects of backup operations, you can either run the command stsadm -o backup -showtree at the server command line or visit Operations > Perform a Backup in the Central Administration application. To specify the whole farm, use "Farm" as the name. (Setting the property to null also selects the whole farm for backup assuming that you use SPBackupRestoreSettings.IndividualItem in all subsequent code to identify by name the component to be backed up, as you should. For an example, see the use of the SPBackupRestoreConsole.FindItems method in step 8.)

    [C#]

    Console.Write("Enter name of component to backup (default is whole farm):");

    settings.IndividualItem = Console.ReadLine();

    5.    Optionally, set one or more of the SPBackupRestoreSettings.IsVerbose, SPBackupRestoreSettings.UpdateProgress, and SPBackupSettings.BackupTheads properties. (For details about these properties, see the reference topics for them.)

    [C#]

    settings.IsVerbose = true;

    settings.UpdateProgress = 10;

    settings.BackupThreads = 2;

    6.    Create the backup operation with the SPBackupRestoreConsole.CreateBackupRestore method. (A history object for the operation is also created. For more information, see SPBackupRestoreHistoryObject and SPBackupRestoreHistoryList.)

    [C#]

    Guid backup = SPBackupRestoreConsole.CreateBackupRestore(settings);

    7.    If your UI has users type a component name instead of pick one from a list, you must make sure that the name entered matches exactly one component. Add the following line to your Main method.

    [C#]

    SPBackupRestoreObject node = EnsureUniqueValidComponentName(settings, ref backup);

    8.    Add the following declaration and implementation of your EnsureUniqueValidComponentName method. Use the SPBackupRestoreConsole.FindItems method to retrieve a collection of content objects whose names match the user-entered name. If there is no match, prompt the user to try again. If there is more than one, prompt the user to be more specific. If the component name that the user entered is valid and not ambiguous, get a reference to the SPBackupRestoreObject object that represents the component that the user wants to restore.

    [C#]

    private static SPBackupRestoreObject EnsureUniqueValidComponentName(SPBackupRestoreSettings settings, ref Guid operationGUID)

    {

        SPBackupRestoreObjectCollection list = SPBackupRestoreConsole.FindItems(operationGUID, settings.IndividualItem);

        SPBackupRestoreObject component = null;

     

        if (list.Count <= 0)

        {

            Console.WriteLine("There is no component with that name. Run again with a new name.");

            Console.WriteLine("Press Enter to continue.");

            Console.ReadLine();

        }

        else if (list.Count > 1)  // The component name specified is ambiguous. Prompt user to be more specific.

        {

            Console.WriteLine("More than one component matches the name you entered.");

            Console.WriteLine("Run again with one of the following:");

            for (int i = 0; i < list.Count; i++)

            {

                Console.WriteLine("\t{0}", list[i].ToString());

            }

            Console.WriteLine("Press Enter to continue.");

            Console.ReadLine();

        }

        else

        {

            component = list[0];

        }

     

        return component;

     

    }

    9.    In the Main method, create a Boolean flag that will signal whether there is sufficient space for the backup, and a conditional structure that will run only if your EnsureUniqueValidComponentName method has returned a valid node.

    [C#]

    Boolean targetHasEnoughSpace = false;

    if (node != null)

    {

        targetHasEnoughSpace = EnsureEnoughDiskSpace(backupLocation, backup, node);

    }

    10. Add the following declaration and implementation of your EnsureEnoughDiskSpace method. Use the SPBackupRestoreConsole.DiskSizeRequired method to obtain the amount of space that is needed, and the SPBackupRestoreConsole.DiskSize method to determine how much free space is available on the destination disk.

    [C#]

    private static Boolean EnsureEnoughDiskSpace(String location, Guid backup, SPBackupRestoreObject node)

    {

        UInt64 backupSize = SPBackupRestoreConsole.DiskSizeRequired(backup, node);

        UInt64 diskFreeSize = 0;

        UInt64 diskSize = 0;

        Boolean hasEnoughSpace = true;

     

        try

        {

            SPBackupRestoreConsole.DiskSize(location, out diskFreeSize, out diskSize);

        }

        catch

        {

            diskFreeSize = diskSize = UInt64.MaxValue;

        }

     

        if (backupSize > diskFreeSize)

        {

            // Report through your UI that there is not enough disk space.

            Console.WriteLine("{0} bytes of space is needed but the disk hosting {1} has only {2}.", backupSize, location, diskFreeSize);

            Console.WriteLine("Please try again with a different backup location or a smaller component.");

            hasEnoughSpace = false;

        }

        else if (backupSize == UInt64.MaxValue || diskFreeSize == 0)

        {

            // Report through your UI that it cannot be determined whether there is enough disk space.

            Console.WriteLine("Cannot determine if that location has enough disk space.");

            Console.WriteLine("Please try again with a different backup location or a smaller component.");

            hasEnoughSpace = false;

        }

        return hasEnoughSpace;

     

    }

    11. In the Main method, create a conditional structure that will run only if your EnsureEnoughDiskSpace returns true.

    [C#]

    if (targetHasEnoughSpace)

    {

        // TODO: Set the backup operation as the active operation

        // and run it.

    }

    12. Replace the "TODO" line in the previous step with the following code. This sets the operation to be the active operation with the SPBackupRestoreConsole.SetActive method and tests to verify that it succeeded. If it fails, which it will if another backup or restore operation is already underway, report an error to the UI of your application.

    [C#]

    if (SPBackupRestoreConsole.SetActive(backup) == true)

    {

        // TODO: Run the operation. See next step.

    }

    else

    {

        // Report through your UI that another backup

        // or restore operation is underway.

        Console.WriteLine("Another backup or restore operation is already underway. Try again when it ends.");

    }

    13. In the code branch that runs if the SPBackupRestoreConsole.SetActive call succeeds, run the operation with the SPBackupRestoreConsole.Run method. Test that the operation succeeds. If it fails, report the operation's failure message to your UI. The following code replaces the "TODO" line in the previous step.

    [C#]

    if (SPBackupRestoreConsole.Run(backup, node) == false)

    {

        // Report "error" through your UI.

        String error = SPBackupRestoreConsole.Get(backup).FailureMessage;

        Console.WriteLine(error);

    }

    14. Clean up the restore with the SPBackupRestoreConsole.Remove method. Add the following code just before the closing brace you inserted in step 11.

    [C#]

    // Clean up the operation.

    SPBackupRestoreConsole.Remove(backup);

     

    Console.WriteLine("Backup attempt complete. Press Enter to continue.");

    Console.ReadLine();

     

     

     

    Example

    The following code shows how to program a backup of a content component.

     

    using System;

    using Microsoft.SharePoint.Administration;

    using Microsoft.SharePoint.Administration.Backup;

     

    namespace MyCompany.SharePoint.Administration.Backup

    {

        class Backup

        {

            static void Main(string[] args)

            {

                // Identify the location for the backup storage.

                Console.Write("Enter full UNC path to the directory where the backup will be stored:");

                String backupLocation = Console.ReadLine();

               

                // Create the backup settings.

                SPBackupSettings settings = SPBackupRestoreSettings.GetBackupSettings(backupLocation, "Full");

     

                // Identify the content component to backup.

                Console.Write("Enter name of component to backup (default is whole farm):");

                settings.IndividualItem = Console.ReadLine();

               

                // Set optional operation parameters.

                settings.IsVerbose = true;

                settings.UpdateProgress = 10;

                settings.BackupThreads = 10;

     

                // Create the backup operation and return its ID.

                Guid backup = SPBackupRestoreConsole.CreateBackupRestore(settings);

     

                // Ensure that user has identified a valid and unique component.

                SPBackupRestoreObject node = EnsureUniqueValidComponentName(settings, ref backup);

     

                // Ensure that there is enough space.

                Boolean targetHasEnoughSpace = false;

                if (node != null)

                {

                    targetHasEnoughSpace = EnsureEnoughDiskSpace(backupLocation, backup, node);

                }

     

                // If there is enough space, attempt to run the backup.

                if (targetHasEnoughSpace)

                {

                    // Set the backup as the active job and run it.

                    if (SPBackupRestoreConsole.SetActive(backup) == true)

                    {

                        if (SPBackupRestoreConsole.Run(backup, node) == false)

                        {

                            // Report "error" through your UI.

                            String error = SPBackupRestoreConsole.Get(backup).FailureMessage;

                            Console.WriteLine(error);

                        }

                    }

                    else

                    {

                        // Report through your UI that another backup

                        // or restore operation is underway.

                        Console.WriteLine("Another backup or restore operation is already underway. Try again when it ends.");

                    }

     

                    // Clean up the operation.

                    SPBackupRestoreConsole.Remove(backup);

     

                    Console.WriteLine("Backup attempt complete. Press Enter to continue.");

                    Console.ReadLine();

                }

            }// end Main

     

            private static SPBackupRestoreObject EnsureUniqueValidComponentName(SPBackupRestoreSettings settings, ref Guid operationGUID)

            {

                SPBackupRestoreObjectCollection list = SPBackupRestoreConsole.FindItems(operationGUID, settings.IndividualItem);

                SPBackupRestoreObject component = null;

     

                if (list.Count <= 0)

                {

                    Console.WriteLine("There is no component with that name. Run again with a new name.");

                    Console.WriteLine("Press Enter to continue.");

                    Console.ReadLine();

                }

                else if (list.Count > 1)  // The component name specified is ambiguous. Prompt user to be more specific.

                {

                    Console.WriteLine("More than one component matches the name you entered.");

                    Console.WriteLine("Run again with one of the following:");

                    for (int i = 0; i < list.Count; i++)

                    {

                        Console.WriteLine("\t{0}", list[i].ToString());

                    }

                    Console.WriteLine("Press Enter to continue.");

                    Console.ReadLine();

                }

                else

                {

                    component = list[0];

                }

     

                return component;

     

            }// end EnsureUniqueValidComponentName

     

            private static Boolean EnsureEnoughDiskSpace(String location, Guid backup, SPBackupRestoreObject node)

            {

                UInt64 backupSize = SPBackupRestoreConsole.DiskSizeRequired(backup, node);

                UInt64 diskFreeSize = 0;

                UInt64 diskSize = 0;

                Boolean hasEnoughSpace = true;

     

                try

                {

                    SPBackupRestoreConsole.DiskSize(location, out diskFreeSize, out diskSize);

                }

                catch

                {

                    diskFreeSize = diskSize = UInt64.MaxValue;

                }

     

                if (backupSize > diskFreeSize)

                {

                    // Report through your UI that there is not enough disk space.

                    Console.WriteLine("{0} bytes of space is needed but the disk hosting {1} has only {2}.", backupSize, location, diskFreeSize);

                    Console.WriteLine("Please try again with a different backup location or a smaller component.");

                    hasEnoughSpace = false;

                }

                else if (backupSize == UInt64.MaxValue || diskFreeSize == 0)

                {

                    // Report through your UI that it cannot be determined whether there is enough disk space.

                    Console.WriteLine("Cannot determine if that location has enough disk space.");

                    Console.WriteLine("Please try again with a different backup location or a smaller component.");

                    hasEnoughSpace = false;

                }

                return hasEnoughSpace;

     

            }// end EnsureEnoughDiskSpace

     

        }// end Backup class

    }// end namespace

    See Also

    Programming with the Windows SharePoint Services Backup/Restore Object Model

    How to: Programmatically Restore Content

    How to: Create a Content Class That Can Be Backed Up and Restored

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Programming with the Windows SharePoint Services Backup/Restore Object Model

    • 1 Comments

    Introduction

    This post describes the architecture of the backup and restore object model in Windows SharePoint Services 3.0 and provides some advice about how to program against it. Your backup application can be an independent application or an extension of the stsadm.exe command-line tool. For more information about how to extend stsadm, see How to: Extend the STSADM Utility.

    Note: All classes in this post are in the Microsoft.SharePoint.Administration.Backup namespace unless specified otherwise.

    The Backup and Restore Object Model

    At the top of the object model is the SPBackupRestoreConsole class. (See , in Figure 1.) It provides an umbrella operations manager responsible for queuing backup and restore jobs, starting jobs, and persisting a history of jobs in the form of xml files. An object of this class is "just underneath the surface" of the UI of a backup application. Its members are mainly static methods and properties that provide hooks for the UI of the backup application. Both the Central Administration application and the stsadm.exe command-line tool use these hooks.

    There is always just one object of this class and it is created the first time one of its members is called. It remains in memory until the next time Internet Information Server (IIS) is reset; however, its memory footprint is small.

    Figure 1: The primary objects in the backup/restore hierarchy.

    Primary Objects in WSS Backup Restore OM 

     

    Each particular backup or restore operation is represented by an object of the class SPBackupRestoreConsoleObject that is created by the SPBackupRestoreConsole.CreateBackupRestore method of the console object. (See , in Figure 1, the queued backup and restore operations.) The properties of one of these lightweight objects hold information about the operation such as whether it is a backup or a restore, the location of the backup files, the backup or restore method, the current stage of the operation, and the tree of content components that his being backed up or restored. These objects also have an SPBackupRestoreConsoleObject.Id property of type System.Guid that serves as a handle for the object that can be passed to the methods of SPBackupRestoreConsole. For example, SPBackupRestoreConsole.Run is passed the ID of the operation (that is; the SPBackupRestoreConsoleObject object) that the console is to run next. Finally, each SPBackupRestoreConsoleObject has a SPBackupRestoreConsoleObject.Settings property that holds a persisting object that contains a reusable pattern of backup (or restore) settings. This object also identifies the content component that the operation is to back up or restore. The settings objects are discussed in more detail near the end of this section.

    While the SPBackupRestoreConsoleObject objects represent operations, the content components themselves are represented by SPBackupRestoreObject objects. (See , in Figure 1.) These objects can be nested with the SPBackupRestoreObject.Children property. Therefore, each one represents a tree of one or more content components. For example, a Web application would be represented by a by SPBackupRestoreObject object that would have each of the Web application's content databases as child SPBackupRestoreObject objects. The tree of components that are the subject a particular backup or restore operation is internally linked to the SPBackupRestoreConsoleObject object that represents the operation. You can get a reference to the topmost SPBackupRestoreObject object in the tree by passing the ID of the SPBackupRestoreConsoleObject object to the SPBackupRestoreConsole.GetRoot method.

    A SPBackupRestoreObject object is a container for two critical types of objects:

    ·         In its SPBackupRestoreObject.Information property the SPBackupRestoreObject object holds either a SPBackupInformation or a SPRestoreInformation object. These kinds of objects hold information about how to back up or restore a particular component and methods that can be called internally by SPBackupRestoreConsoleObject and SPBackupRestoreObject as part of an operation. More specifically, these classes hold methods and properties whose implementation should never change regardless of what kind of content component is being represented. Hence, they are sealed and deriving a new class from their parent, SPBackupRestoreInformation, is not supported.

    ·         In its SPBackupRestoreObject.IBackupRestore property the SPBackupRestoreObject object holds an object that implements the IBackupRestore interface (and might also inherit from Microsoft.SharePoint.Administration.SPPersistedObject). (See , in Figure 1.) Implementation of the latter interface turns an object into something that can potentially be backed up and restored. Like an SPBackupRestoreInformation object, an IBackupRestore object contains members that provide information and methods needed for backups and restorations. But the members of IBackupRestore must be given different implementations depending on the kind of content component that is being represented. Most importantly, the implementation includes event handlers for an operation's events, such as IBackupRestore.OnBackup and IBackupRestore.OnRestore.

    Besides acting as a container, SPBackupRestoreObject is a helper class that provides easier interaction between the operation objects (SPBackupRestoreConsoleObject), on the one hand, and the component information objects (SPBackupRestoreInformation and IBackupRestore), on the other.

    The final major classes are SPBackupSettings and SPRestoreSettings. An object of one of these two types is passed to the SPBackupRestoreConsole.CreateBackupRestore method when an operation is created. (See , in Figure 1.) Each of these objects holds settings for use in operations; most importantly, they identify, in their SPBackupRestoreSettings.IndividualItem property, the content component that is the subject of the backup or restore operation. (See , in Figure 1.)

    Programming Advice

    Ways of Customization

    Most of the critical classes in the backup restore object model are sealed (NotInheritable in Visual Basic). Moreover, although the following classes are not sealed, deriving from them is not supported. In each case, you must use the existing derived classes:

    ·         SPBackupRestoreInformation. (Existing derived classes: SPBackupInformation and SPRestoreInformation.)

    ·         SPBackupRestoreSettings. (Existing derived classes: SPBackupSettings and SPRestoreSettings.)

    Accordingly, there are just two primary points of customization:

    ·         You can create your own high level application, and UI, that will use the static methods of the operations console — an SPBackupRestoreConsole object — to manage and run backup and restore jobs. For more information about how to do this see How to: Programmatically Back Up Content and How to: Programmatically Restore Content.

    ·         You can create a class that implements the IBackupRestore interface and might or might not also derive the Microsoft.SharePoint.Administration.SPPersistedObject class. For more information about how to create a custom content class, see How to: Create a Content Class That Can Be Backed Up and Restored.

    Permissions

    Code that backs up content must run in the user context of a farm administrator. Code that restores must run in the context of a user who is both a farm administrator and an administrator on all the front-end servers. The user should also have read and write permissions for the backup location.

    Failure Logging

    If a backup or restore operation fails, details about the failure will be logged in spbackup.log or sprestore.log in the backup location.

    Backups and Restores of Site Collections

    Aside from custom content types that you create by implementing IBackupRestore, the smallest content object that you can back up and restore with the classes in the Microsoft.SharePoint.Administration.Backup namespace is a content database. To programmatically backup or restore individual site collections, use Microsoft.SharePoint.Administration.SPSiteCollection.Backup(System.String,System.String,System.Boolean) and Microsoft.SharePoint.Administration.SPSiteCollection.Restore(System.String,System.String,System.Boolean). For more information on backing up and restoring site collections, see How to: Programmatically Backup and Restore a Single Site Collection.

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Extending the Connect To menu item

    • 2 Comments

    Introduction

    In my last two posts I discussed how third party clients can most efficiently synchronize with Windows SharePoint Services 3.0 In this article I look into extending the “Connect To…” menu item. The “Connect To…” menu item is extensible so that any client can make use of it.

    Extending the “Connect To” menu item

    Client applications must do two things in order for the Connect To menu item to be displayed:

    1.      Register the stssync:// protocol handler

    2.      Install an ActiveX control that returns the application name and an icon filename

     

    More information about registering protocol handlers is available from http://msdn.microsoft.com/workshop/networking/pluggable/overview/appendix_a.asp.

    The ActiveX control must have a ProgID of SharePoint.Stssynchandler.3 and implement the following methods:

     

    HRESULT GetStssyncAppNameForType([in] BSTR pbstrType,  [out, retval] BSTR *pbstrAppName);

    HRESULT GetStssyncIconName([out, retval] BSTR *pbstrIconName);

     

    Before displaying the menu item, SharePoint calls the GetStssyncAppNameForType() method in the ActiveX control and passes in the current list’s sync type.  Each list has a default sync type (accessed in the object model at SPList.SyncType) based on its base type, base template, and whether it allows content types. This type can be overridden as an attribute of the list template definition in a feature.

    Typically, the ActiveX control checks the sync type against a list of supported types and returns the name of the application to display in the menu item, or null if the type is not supported – in which case the menu item is not displayed.

     

    If a non-null application name is returned, SharePoint then calls the control’s GetStssyncIconName() method to get the filename of an icon to display in the menu item.  The filename returned is used in the context menu for document library folders, while the “menu” prefix is added to the filename to get the image used in the toolbar menu item.  Thus, clients should install two images in the images path of each SharePoint web front end – foo.gif and menufoo.gif.

     

    If the browser does not support Active X, (Windows Internet Explorer 5 and later), we display the “Connect To…” menu with a default icon and assume that the stssync protocol is supported.

     

    When the button is clicked, JavaScript redirects the browser to an stssync:// URL with the right context parameters.  The URL is formatted as follows:

     

    stssync://sts/?ver=1.1&type=Type&cmd=add-folder&base-url=WebUrl&list-url=ListUrl&guid=ListId&site-name=SiteName&list-name=ListName

     

    Parameter Descriptions

    Type

    The sync type for this list

    WebUrl

    an absolute, encoded URL to the web on which the list is found

    ViewUrl

    a web-relative, encoded URL (starting with /) to the list’s default view page

    ListUrl

    a web-relative, encoded URL (starting and ending with /) to the list

    ListId

    the list’s GUID

    SiteName

    the title of the web on which the list is found, up to 20 characters long

    ListName

    the title of the list, up to 20 characters long

     

    Document libraries may add two parameters to the end of the stssync:// URL when syncing a folder:

     

    &folder-url=FolderUrl&folder-id=FolderId

     

    Type

    The sync type for this list

    FolderUrl

    a web-relative, unencoded URL (starting with /) to the context folder

    FolderId

    the item ID (an integer) of the context folder

     

    Acknowledgements

    Once again, I would like to acknowledge Matt Swann (Microsoft Corporation) and Bill Snead (Microsoft Corporation) for their gracious help in technical reviews of this material.

     See Also

    http://blogs.msdn.com/sharepointdeveloperdocs/archive/2008/01/21/synchronizing-with-windows-sharepoint-services-part-1.aspx

    http://blogs.msdn.com/sharepointdeveloperdocs/archive/2008/01/22/synchronizing-with-windows-sharepoint-services-part-2.aspx

  • Microsoft SharePoint Developer Documentation Team Blog

    Overview of Backing Up and Restoring Data in Windows SharePoint Services

    • 5 Comments

     

    Introduction

    This post provides some basic facts about backing up and restoring data in Windows SharePoint Services 3.0. It will serve as background information for several developer-oriented posts that I will create in the next couple of weeks.

    Note: Unless explicitly stated otherwise, all classes and members referenced in this post are in the Microsoft.SharePoint.Administration.Backup namespace.

    What Can Be Backed Up and What Can Be Restored

    There are six types of content components built-in to Windows SharePoint Services 3.0 that can be backed up and restored through either the Central Administration application's UI, the Stsadm.exe Command-line Tool or a custom application that uses the Windows SharePoint Services 3.0 backup and restore object model.

    ·         Site collections, each of which might contain multiple Web sites.

    ·         Content databases, each of which might contain multiple site collections.

    ·         Web applications, each of which might contain multiple content databases.

    ·         Content publishing "Web services," each of which might contain multiple Web applications.

    Note: This refers to content publishing "Web services" (which are really partitions of content) that are represented in the object model by Microsoft.SharePoint.Administration.SPWebService objects. It does not refer to the functional Web services in the more common sense of "Web service," such as the Alerts (websvcAlerts) service or the Meetings (websvcMeetings) service. For more information about content publishing "Web services," see Server and Site Architecture: Object Model Overview and The High Level Object Model of Windows SharePoint Sevices 3.0 (to be published on MSDN).

    ·         Search Windows service including its databases and indexes.

    Note: This does not refer to the Search Web service websvcSPSearch.

    ·         A whole Windows SharePoint Services farm.

    If an enhanced functionality product such as Office SharePoint Server 2007 has been installed in addition to Windows SharePoint Services 3.0 and that product includes Shared Service Providers, then they can also be backed up and restored with Windows SharePoint Services 3.0.

    In addition, you can create new types of content objects that can be backed up and restored by implementing the IBackupRestore interface.

    Limitations

    There are some limitations on what can be backed up and restored through either the Central Administration application's UI, the stsadm command line utility or a custom application that uses the Windows SharePoint Services 3.0 backup and restore object model.

    ·         You cannot backup (or restore) an individual Web site, list, or list item except by backing up (or restoring) the entire site collection to which it belongs.

    ·         You cannot back up a Windows SharePoint Services farm's configuration database or the content database of the Central Administration application except by backing up the whole farm.

    ·         You cannot restore a farm's configuration database or the content database of the Central Administration application. The backups of these components that are included in a backup of a farm provide a snapshot of these components at the time of the backup. Such snapshots might be useful for troubleshooting because they can be used to compare with the present state of the components using SQL Server tools. (There is an exception to this point: you can restore the configuration database and the content database of the Central Administration application if you are using the Volume Shadow Copy Service – VSS - of Windows Server 2003/2008).

    ·         You cannot back up the Internet Information Server (IIS) metabase.

    The following kinds of content cannot be backed up with either the Central Administration application's UI or the stsadm command line utility, but you can create custom backup solutions with the Windows SharePoint Services SDK that include these types of content.

    ·         Registry keys.

    ·         Files that live on the front-end servers; that is, outside any content database, such as certain master pages, .ascx files, web.config files, and other configuration files.

    Types of Backups and Restorations

    Backups of a given component can be either full or incremental. In the latter case, only parts of the component that have changed since the last full backup are backed up.

    Note: A Windows SharePoint Services Search index cannot be incrementally backed up. If a search index is included in an incremental backup job, the index will get a full backup.

    Restorations can either overwrite the original backup source or they can be to a new location. This means that the backup and restore functionality in Windows SharePoint Services 3.0 can also be used as a method of migrating content components.

    Two Kinds of Custom Backup Applications

    There are two ways to use the Windows SharePoint Services object model to create custom backup applications.

    The Main Backup and Restore Object Model

    You can create a backup and restore application by using the main backup and restore object model, sometimes called "catastrophic" backup/restore. This is located mainly in the Microsoft.SharePoint.Administration.Backup namespace; but backups and restores of individual site collections are performed with the Microsoft.SharePoint.Administration.SPSiteCollection.Backup() and Microsoft.SharePoint.Administration.SPSiteCollection.Restore() methods.

    Interface to the Volume Shadow Copy Service

    Windows SharePoint Services 3.0 deployments can also take advantage of the Volume Shadow Copy Service (VSS) in Windows Server 2003/2008. Windows SharePoint Services 3.0 includes a Windows SharePoint Services VSS Writer service that will create shadow copies of native and custom content in the deployment. The service contains a VSS writer that will write shadow copies of all native Windows SharePoint Services 3.0 databases and all custom databases. Non-database custom components can also be registered with the service by using the SPVssComponentDefinition and SPVssDiscoveryHelper classes. It is also necessary that you create a VSS writer for any such non-database custom components.

    Note: With the VSS service, you can target only the whole farm or individual content databases for database shadow copy. Individual Web applications and individual content publishing "Web services" cannot be set for shadow copying independently of making a shadow copy of the whole farm. (For information about the meaning of "content publishing Web service" see the first note of this post.)

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Synchronizing with Windows SharePoint Services, Part 2

    • 7 Comments

    Introduction

    In my last blog post I introduced GetListItemChangesSinceToken and discussed how using GetListItemChangesSinceToken can make synchronization more efficient. In this post I'll talk some more about synchronization; take a quick look at GetList and UpdateListItems, and property bags. I'll finish up by discussing conflict detection, and performance best practices.

    Other Web Services

    In addition to GetListItemChangesSinceToken, Lists.asmx defines several other web services which allow clients to query for changes and make updates. There are several good examples in the Lists web service topic in the Windows SharePoint Services SDK.

    GetList

    GetList returns field schemas and other list properties.  Clients typically call this before syncing a new list, and then parse the response to match WSS fields to their client-side representation. 

    If the field exists as an out-of-box site column, it can be matched by its field ID.  In other cases, the field's internal name can be referenced.

    UpdateListItems

    UpdateListItems adds, modifies, or deletes list items.  Clients typically call this to keep server items in sync with changes made on the client.

    Request

    public SoapXml.SoapXmlElement UpdateListItems(string listName, SoapXml.SoapXmlElement updates)

    This is the format of the updates parameter:

     

    <Batch [update options]>

       <Method ID="X" Cmd="CMD">

          <Field Name="InternalName">VALUE</Field>

       </Method>

    </Batch>

     

    A batch is a collection of methods, each of which specifies the following value for the Cmd attribute:

     

    List of batch methods

    Method

    Description

    New

    Create a new item with the specified field values.

    Update

    Update the specified field values for an item.

    Moderate

    Change the moderation status for an item (used in the same manner as Update). The ModerationStatus field can only be changed by a Cmd=Moderate call. 

    Delete

    Delete the item with the following field values

    Note   Setting ModerationStatus must be done using a Cmd=Moderate call. Attempting to set ModerationStatus using Cmd=Update or Cmd=New has no effect. For more information about ListItem, refer to the SDK.

     

    The ID attribute of the Method tag is only used to correlate the method in the batch with the right item in the result.

     

    Update Options

    Option

    Description

    OnError="Continue"

    Continue processing the batch if errors are encountered

    LockSchema="TRUE"

    ListVersion="N"

    Only process the update if the list version matches

    ViewName="VIEW'

    Return columns present in this view with the item's data

    RootFolder="FOLDERURL"

    Perform the update in the context of this folder

    Properties="TRUE"

    Return the properties in the item property bag as separate fields

    DateInUtc="TRUE"

    The dates updated and returned are in UTC

     

    Fields

    In updates and deletes, the ID field needs to be supplied in order to identify the item.  If the update is being made on a document library, the FileRef field is also required to identify the document being updated.

    When updating an item, only the changed fields need to be supplied.

    When adding or changing certain types of items, certain fields may be required. 

     

    Clients may update the property bag in two ways:

    Update

    Description

    <Field Name="MetaInfo">

    XXX

    </Field>

    Add or update the specified name/value pairs in the property bag

    <Field Name=

    "MetaInfo" Property=

    "Name">

    VALUE</Field>

    Add or update this specific name/value pair in the property bag

     

    Note   There is no way for a client to delete individual name/value pairs in the property bag. See Property Bag below for more information.

     

    Response

    The return value is:

    <Results>.

       <Result ID="X,CMD">

          <ErrorCode>HR</ErrorCode>

          <ID>ID</ID>

          <z:row ... />

       </Result>

    </Results>

     

    Tag

    Description

    Error Code

    0x00000000 if no error. A hex HR if there was an error.

    ID

    Not sure if, when and why this shows. Maybe only for deletes?

    Z:row

    For all commands except Delete, the updated item with all its fields is returned.

    Property Bag

    Property bags are mechanisms for developers to add their custom data to corresponding objects inside of SharePoint.  Property bags let developers transform simple lists into rich data stores, and webs into full applications. The property bag is a virtual container that can store almost any typed of value. The SPListItem Properties property returns a property bag for the specified object.

    Note   If you use anything besides a String, int, or DateTime for the value, you will get a SPUnsupportedPropertyDataTypeException, with the message of "Only String, int, and DateTime datatypes can be used as the value in Properties."

    A call to the Update method on the object persists the values set in the property bag. All values can be stored and retried by using Web service methods.

    You must send the entire property bag, you cannot update just part of it.

    For more information about property bags in Windows SharePoint Services, refer to http://msdn2.microsoft.com/en-us/library/ms480101.aspx.

    Fields and Properties

    SharePoint has very extensible list schemas.  A client that has fixed content types (calendars, tasks, etc) needs to be able to relate its own item properties to specific fields on the list.  Typically this is done by calling GetList to get the field schema before a client syncs to a SharePoint list for the first time.

    Item properties in a client are best matched to fields on the server by the field GUID in the list schema.  The internal name of SharePoint fields can also be used to associate client-side properties with their server-side counterparts.  If a client cannot find an appropriate field on the server, it should store the data in the property bag for other clients to consume.  The client should cache these associations so that it doesn't have to fetch the list schema on every call.

    GetListItemChangesSinceToken is a very fast call when no changes were done on the list. GetList is not quite as fast.

    When a client calls GetListItemChangesSinceToken, it will receive a new list schema if the schema has changed. Unfortunately, if the schema change indicates a change in the internal name of one of the fields which are requested by name, the results of the call must be discarded. This is because the field you requested by name may not be included in the result set.

    There are a few snags with respect to client use of the property bag.  If, when making an update, a client doesn't know which particular property has changed, it must update every property (even the ones not set) so that it is sure to clear the ones recently emptied.

    Note   A field with an empty value is equivalent to an absent field; while an empty-value property is not.

    If a new field is added to the list, values stored by the client in an equivalent property are not automatically promoted into the field value. The client must decide whether to respect the value in the property and the value in the field, possibly losing user data in the process.

    Document Sync

    Clients should use HTTP/DAV to sync document content. Although some SharePoint web methods support document content fetch and update (for attachments), this is not the preferred way of transferring binary document content. Because SOAP is based on XML, binary documents need to be encoded into an XML-compliant form. This is generally accomplished using hex encoding, which roughly doubles the bandwidth used.

    A core field in document libraries (and also in generic lists in wssversion3short) is FileRef, which is basically a combination of two other fields associated with columns in the SQL table: DirName (server path to the containing folder) and LeafName (name of the document).

    Document libraries also have two other fields computed from FileRef - ServerUrl and EncodedAbsUrl.

    One of these can be used to reference a document.

    On a generic list, another field (Attachments) is used to determine if the item has attachments.

    Although there is a separate method to determine what these attachments are, by default there is no way to quickly determine if any attachments of an item have changed. This is what the IncludeAttachmentUrls and IncludeAttachmentVersion query options are for.

    When these options are used, the value of this field should contain; #[AttachmentUrl];#[AttachmentGuid],[AttachmentVersion] for each attachment. The client can compare these values with what they stored to determine which attachments need to be re-fetched.

    Because document contents can be quite big, a client can support a header-only mode that lets the user decide which document contents should be off-lined. MaxBulkDocumentSyncSize is a property that can be set on the server to guide the client when to automatically sync all contents.

    Note    Some generic lists have content that can be quite large. Discussion Boards are an example. The content of a discussion item is stored in a couple of rich text fields. The header-only concept could also be used here by separate the call for the generic fields from a separate GetListItems or call for the contents. We have no examples of the best way doing this and we have not analyzed it for performance.

    Conflict Detection

    Besides performance considerations, discussed below, this is arguably the most important sync topic: How to detect and deal with an object that was modified on both sides.

    Fetch-Send-Refresh

    It is better to have conflicts detected and resolved by the client. The client can better detect that the changes didn't actually conflict, it can raise an alert for the user to manually correct the conflict, and it can store a copy of the changes applied by the local user in the user's storage.

    Thus it is best if a sync operation consists first of fetching the changed data from the server, then detecting any conflicts with changes in the client copy, and finally uploading those changes if there is no conflict.

    SharePoint objects may have some business logic applied at the point of the update. Because of that, the UpdateListItems method returns the updated values of all the fields and properties of the updated items.

    owshiddenversion Field

    wssversion3short uses this field to detect conflicts. If the field value is not supplied on update, the server will overwrite any changes. A client should always supply it on update to prevent data loss. This number should be whatever the server last sent.

    This is used so that the server can tell if you're updating a stale copy of the item.  For example, a client syncs an item and gets a value of '2' for this attribute.  Someone changes the title of the item on the server, so the value increments to become 3.  When the client sends a change with value '2', the server complains because the item has been modified since the client last requested it.

    When there is a conflict, the server will return a TP_E_VERSIONCONFLICT (0x81020015) error and the current contents of the item.

    vti_versionhistory Property

    The hidden version field is sufficient for simple conflict detection when all clients are synchronizing with a central server however; peer to peer synchronization presents further challenges. You want to avoid raising unnecessary conflicts when the change was synchronized by a peer client.

    This situation may also happen in a non-peer-to-peer scenario. If a client successfully uploads a change to the server but does not receive an acknowledgment (response from UpdateListItems), the client needs a way to know that its changes were uploaded on next sync.

    ETag DAV Header

    Document and Attachment fetches and updates are done through HTTP/DAV. For that protocol, we have a separate mechanism for conflict detection. In every http get of a file (be it a document in a list, an attachment or a page outside a list) we return an ETag, which is supposed to be another blob that contains a guid and a version number. When uploading a document with http put, a client should request that the ETag matches the one supplied.

    Note   Version history is not supported for this protocol.

    Attachments

    Although updating attachments can be done through HTTP/DAV, adding attachments requires using the AddAttachment method from the lists.asmx web service which takes a binary array and returns the URL of the attachment.

    For more information about AddAttachment , refer to lists.asmx web service.

    Performance

    There are some performance issues to keep in mind when you are dealing with syncing with a server.

    Latency

    The amount of time it takes for an action to complete is what is important to a user. Also, there are usually limits on the amount of time allowed to process a request on a database, on a front end and by the entire request, so extremely long requests can turn into denied requests. Even despite this, you want to be able to give the users feedback on the action.  This is why paging is required.

    Using the row limit property on GetListItemChangesSinceToken to limit the amount of data requested each time is crucial for the above reasons, but it should be clear that it will also increase the total amount of time to complete a sync process.

    Throughput

    Obviously, reducing the total amount of cycles required to process a request helps performance by reducing latency. However, with multiple clients, it is more important to reduce the adverse effects one client has on the others. Most of the time, it is easier, cleaner, safer and more effective to make the server do some processing than to implement the same processing on a multiple number of clients. However, to increase throughput, it is almost always better to do that work on the client. Although the server will likely be a lot more powerful, the client will likely have more available CPU time. A sync client should make the data request to the server be as little and simple as possible.

    Bandwidth

    We target high-bandwidth scenarios, but even then, it is important to try to minimize the amount of data sent across the wire.

    It a client is not going to require a piece of information, it should avoid requesting it.

    Paging

    When performing a full sync (no change token), the client should request a maximum number of items returned per page using the rowLimit parameter. If the filtered number of items in the list is greater than that number, the server will return a ListItemCollectionPositionNext attribute to be used to request the next page.

    We will only return the current change token of the list on the first page to prevent the loss of any changes being made to the first page. The client should store the change token from the first page for a subsequent incremental sync.

    Secondary pages will also not include list and global properties like permissions, alternate urls and TTL.

     

    rowLimit is also supported on incremental syncs (change token supplied), but on an incremental sync this will limit the processing of our internal change log, and we have an internal limit of 100. Although the client can be sure the number of items returned will never be greater than that limit, in certain circumstances not all changes may have been synchronized even if the number of items returned is smaller than the limit. This is because we will stop processing the change log as soon as we reach a number of updates equal to the limit. When that is the case, we return the MoreChanges attribute to indicate there are more changes in the change log. Instead of waiting for the next sync period, the client should request more changes immediately using the returned change token.

     

    The limit works this way on incremental sync for a few reasons:

    • We have an internal limit of 100 because we don't have a modified time index we can use to filter the items returned and SQL has a limit of 160 on the number of ors in a query and starts performing badly close to that number. 100 gives us a potential extra 60 as part of the filter requested by the client.
    • We could have made several separate SQL queries, but that would imply supporting all ordering and filtering on the middle tier.

    Because of this, we needed to return a change token that is not current, so that extra changes could be processed on a separate call. We could have still looked at the entire change log to better determine the latest point at which the number of items returned would be smaller than the limit, but even this wouldn't be accurate without filtering on the middle tier.

    Filtering and Ordering

    Filtering is a way to allow the user to only get a certain set of items in a list. The two most common usages of this are for folder sync, where the user only gets the items inside a folder, and for certain Group Board scenarios where the user only gets the items associated with him/her.

    Filtering can be done using the contains parameter or the query parameter. Contains is more restrictive since it is basically the Where clause of a SharePoint CAML query, while query is the full query. Contains is safer to use because we can optimize certain scenarios. Query is more powerful and flexible, but the caller must understand its performance effects.

    A client should avoid filtering by a non-indexed column. Otherwise, fetching a page will require a scan of the entire list until it finds the number of items requested.

    A client should also avoid requesting an order unless the column of the order is indexed. Otherwise, fetching a page will at a minimum require a sort on the entire filtered dataset.

    Finally, if the filter is not on the same indexed column of the order, then SQL may still scan the entire list to avoid sorting the filtered dataset.

    An incremental sync has an implicit filter. We will request items with a certain ID. In this case, the client should never order by something other than ID. Filtering by something else is OK, for the dataset is restricted to a maximum of 100.

    Filtering by folder can be done using the Folder query option, but the list should be ordered by the FileLeafRef. For a recursive query, it should first be ordered by FileDirRef as well.

    There is also a way to filter by multiple folders using something like

    "<Or><BeginsWith><FieldRef Name="FileRef"/><Value Type="Note">Shared Documents/folder1/</Value></BeginsWith><BeginsWith><FieldRef Name="FileRef"/><Value Type="Note">Shared Documents/folder2/</Value></BeginsWith></Or>".

    This will synchronize the full contents of folder1 and folder2.

    The client should use this in the contains parameter and add the following query option:

    "<OptimizeFor>FolderUrls</OptimizeFor> "

    This will make sure the SQL query is optimized appropriately by ordering it by FileDirRef, FileLeafRef and constraining the right columns.

     

    Conclusion

    The most efficient way to synchronize with Windows SharePoint Services is to download only those items that have changed since the last synchronization occurred. In wssversion3short this can be done by calling the GetListItemChangesSinceToken Web method.

    GetListItemChangesSinceToken allows clients to track changes on a list.  Changes, including deleted items, are returned along with a token that represents the moment in time when those changes were requested. By including this token the next time you call GetListItemChangesSinceToken, the server looks for only those changes that have occurred since the token was generated.

    We discussed considerations that the developer must keep in mind to obtain the best possible performance.

    Acknowledgements

    I would like to acknowledge the following persons for their gracious help in technical reviews for this article: Matt Swann (Microsoft Corporation), Bill Snead (Microsoft Corporation).

    See Also

    http://blogs.msdn.com/sharepointdeveloperdocs/archive/2008/01/21/synchronizing-with-windows-sharepoint-services-part-1.aspx

    See Also

  • Microsoft SharePoint Developer Documentation Team Blog

    Synchronizing with Windows SharePoint Services, Part 1

    • 8 Comments

    Introduction

    There may be occasions where you will need to synchronize third party programs with Windows SharePoint Services. GetListItemChangesSinceToken, introduced in Windows SharePoint Services version 3.0, makes this synchronization much more efficient.

    This post explores using GetListItemChangesSinceToken to synchronize with Windows SharePoint Services version 3.0. The article details the queryOptions used by GetListItemChangesSinceToken.

    The follow on post will discuss conflict detection issues; and document best practices for clients who want to synchronize with calendar items, documents, and tasks in Windows SharePoint Services version 3.0.

    Please forgive the length of this entry, the various tables and descriptions I've included will be in future updates of the SDK.

    Synchronization Using GetListItemChangesSinceToken

    You can use GetList to download a list; however, the most efficient way to synchronize information is to download only those items that have changed since the last synchronization occurred. In Windows SharePoint Services version 3.0 this can be done by calling the GetListItemChangesSinceToken Web method.

    Sending a GetListItemChangesSinceToken request without including a token returns the list schema, the full list contents and a token. The token represents the moment when those changes were requested. By including this token the next time you call GetListItemChangesSinceToken, the server returns only those changes that have occurred since the token was generated.

    If the list schema itself has changed, GetListItemChangesSinceToken returns the entire list schema, the full list contents, and a token.

    #GetListItemChangesSinceToken Request and Response

    Here is a listing of the GetListItemChangesSinceToken Request and Response and an explanation of the very useful queryOptions parameter.

    Request

    public SoapXml.SoapXmlElement GetListItemChangesSinceToken(

                string listName,

                string viewName,

                SoapXml.SoapXmlElement query,

                SoapXml.SoapXmlElement viewFields,

                string rowLimit,

                SoapXml.SoapXmlElement queryOptions,

                string changeToken,

                SoapXml.SoapXmlElement contains)

     

    Name

    Description

    listName

    This can be the list title, but using the list id (GUID) results in better performance.

    viewName

    Not supported.

    query

    A CAML query similar to the CAML query used in GetListItems and SPQuery. Refer to GetListItems and SPQuery in the SDK for more information.

    Note   Not intended to be used with the contains parameter.

    viewFields

    Set of field references for each field required in the response. <ViewFields /> returns all fields in the list. A Properties='true' attribute will separate the MetaInfo field into its separate decoded properties.

    rowLimit

    The maximum number of values to return.  Used for paging.

    queryOptions

    Set of options related to the query (see details below).

    changeToken

    An opaque token used to determine the changes since the last call.  This token should never be parsed or constructed, as its format may change in the future.

    contains

    A CAML filter applied to the query results.  This is the Where clause in a SPQuery.

    Note   Not intended to be used with the query parameter.

     

    Query Options Parameter

    The queryOptions element can contain a variety of tags which modify the query.  For Boolean options the default is FALSE, only a value of TRUE (must be uppercase) enables them.

     

    Available Query Option Tags

    Query Option Tag

    Description

    <Paging ListItemCollection

    PositionNext="X" />

    X is an opaque token used to determine the page of items to return.  Like the changeToken value, this should never be parsed or constructed.

    <IncludeMandatory

    Columns>

    Ensures that fields defined as required are included, even if not specified in the viewFields. This option may be misleading because Windows SharePoint Services actually has a separate set of mandatory fields that are always returned independently of this option.

    <RecurrenceOrderBy>

    This is a requirement for some calendar programs. For each recurring series, the master item is returned first and then all exceptions. This is a special internal ordering that is applied ahead of any other ordering.

    Note   This should not be used unless your program explicitly requires it.

    If the view has a field of type Recurrence, the list will be ordered by fields of reference type UID, EventType and StartDate in the definition of the recurrence field.

    <ExpandUserField>

    Special rendering for the user field values that makes them include the login name, email, SipAddress, and the title when present. This causes a user field to behave as a multi lookup field.

    The lookup fields used in the expansion are "Name", "EMail", "SipAddress" and "Title". The values are separated by ,#. Any commas in the lookup field name are encoded as ,,.

    These values occur in the normal field data for each item. 

     

    Examples of ExpandUserField

    <ExpandUserField>FALSE</ExpandUserField> looks like:

    ows_Author="1;#Admin AdminName"

     

    <ExpandUserField>TRUE</ExpandUserField> looks like:

     ows_Author="1;#Admin AdminName,#login\name,#email@address,#sip@address,#Admin AdminName "

     

    More Query Option Tags

    Description

    < DateInUtc>

    Date fields are returned in UTC format and zone.

    UTC is in GMT time zone an ISO6801 format: 2006-10-04T10:00:00Z

    Normal is in local (server) time zone and the same as above with the T and the Z replaced by spaces.

    <ViewAttributes

    foo=bar />

    All attributes of this option will be used as view attributes of the view schema. The most commonly used is Scope="RecursiveAll" that defines the view as flat instead of scoped to a particular folder.

    <Folder>

     

    Set the root folder scope of the view. This is a server relative URL.

    <Folder>Shared Documents/foldername</Folder>

    <MeetingInstanceId>

     

    Defines which meeting instance to sync with if this list is part of a meeting workspace. -1 should be used for all instances unless the client wants to filter for a particular instance.

    <IncludePermissions>

     

    One way a client can request individual item permissions.

    <IncludeAttachmentUrls>

     

    Changes the value returned for the Attachments field from a Boolean to a list of full urls separated by ;#

    <IncludeAttachment

    Version>

    Used in conjunction with IncludeAttachmentUrls, IncludeAttachmentVersion also returns the GUID and version number used for conflict detection on update.

    <RecurrencePattern

    XMLVersion>

    v3

    </RecurrencePattern

    XMLVersion>

     

    Used to maintain backwards compatibility, RecurrencePatternXMLVersion changes the value of a RecurrenceData field to NOT return <V3RecurrencePattern /> when it contains elements only present on a version 3 pattern.

    Without this tag, recurrence patterns that were not present in Windows SharePoint Services version 2 are sent as <V3RecurrencePattern />. Including this tag means that recurrence patterns new to Windows SharePoint Services are sent correctly.

    <ExtraIds>

    1,4,23

    </ExtraIds>

    Request extra items to be included on the returned set regardless of whether they changed or not. The common use of ExtraIds is to specify the IDs of the folders you're syncing if you were in a doclib and chose "connect to..." on a folder rather than on the entire doclib.  This way you get the folder name and can tell when it is renamed.

    Note   This should only be used with a change token.

    This allows a client to sync to one or more folders and detect if any folder above the hierarchy was deleted or renamed.

    Folder names are not returned unless some changes are done to the list and the query to fetch changed items also uses IDs.

    <OptimizeFor>

    The two values supported are:

    • ItemIds
    • FolderUrls

    ItemIds is the default as long as a query or recurrence order is not requested and optimizes our SQL query with an ID order.

    FolderUrls optimizes a sync filtered to the flat contents of one or more folders by optimizing the SQL query with a DirName, LeafName order.

    <OptimizeFor>ItemIds</OptimizeFor>

     

    Default Query Options

    If the queryOptions parameter is absent, the following default options are used:

     

    <RecurrenceOrderBy>TRUE</RecurrenceOrderBy>

    <ViewAttributes Scope="Recursive" />

    <DateInUtc>TRUE</DateInUtc>

    <IncludePermissions>TRUE</IncludePermissions>

    <IncludeAttachmentUrls>TRUE</IncludeAttachmentUrls>

    <IncludeAttachmentVersion>TRUE</IncludeAttachmentVersion>

    <RecurrencePatternXMLVersion>v3</RecurrencePatternXMLVersion>

    <ExpandUserField>TRUE</ExpandUserField>

    <MeetingInstanceID>-1</MeetingInstanceID>

     

    Note   Clients should always specify a query option; failing to do so will negatively impact scale performance.

    Response

    A GetListItemChangesSinceToken response is delivered in the following format:

     

    <listitems [list and global properties] [namespace declarations]>

      <Changes [MoreChanges="TRUE"] [LastChangeToken="X"]>

        [Changes]

      </Changes>

          <rs:data ItemCount="N" [ListItemCollectionPositionNext="X"]>

      [Data]

      </rs:data>

    </listitems>

    Note   X is an opaque token used to determine the page of items to return.  Like the changeToken value, this should never be parsed or constructed.

     

    Namespace declarations

    Any XML namespaces used below are declared here.

    List and global properties

    These include configuration properties, alternate URL information, and list permissions.

    List and global properties table

    Property

    Definition

    MinTimeBetweenSyncs

    A server parameter that represents the minimum amount of time between user-initiated or automatic synchronization. The value represents a time in minutes.

    Note   Clients should respect this value even if the user initiates synchronization manually.  That is, if this is set to 5 minutes, clients should only send one request per 5 minutes even if the user repeatedly clicks "send/receive".

    RecommendedTime

    BetweenSyncs

    The recommended minimum amount of time between synchronizations. This should specifically be respected for automatic syncs. Clients should never automatically synchronize more often than this.  User-initiated synchronizations can override this interval.

    MaxBulkDocumentSyncSize

    The total size of content to be synchronized to the client. The default is 500 MB. You get the URL and metadata for each document when you call GetListItemChangesSinceToken, but you then need to do an HTTP GET to retrieve the actual document contents. Setting this value to high may degrade performance.

    AlternateUrls

    Alternate URLs are listed in the following zone order, delimited by commas: Intranet,Default,Extranet,Internet,Custom

    EffectiveBasePermissions

    The permissions on the list as returned by SPList.EffectiveBasePermissions.ToString().

    Changes

    Changes consist of a list of change events that need to be specially handled by the client.

    These are taken from our internal change log when a change token is supplied. For a full synchronization (no change token) we still return the Changes tag with the current change token.

    The limit on the number of updates returned from a change token is 100. The MoreChanges attribute indicates that the last change token is not current. More changes were done on the list and the client should call this method again with the new change token.

    Change

    Description

    <List></List>

    If the list schema has changed, or if no change token was provided, we return the list here. The format is the same as returned by GetList.

    <Id ChangeType=

    "InvalidToken" />

    The token is either invalid or old.  You must do a full sync.

    <Id ChangeType="Restore" />

    The list has been restored from the recycle bin or from a backup.  You should do a full sync.

     

    In both of the cases above, the client should ignore other changes and do a full reconciliation of the list.

     

    Change Type

    Description

    <Id ChangeType=

    "Delete">ID</Id>

    This item is no longer present.  Note that Delete changes are sent even if the item was filtered out by the query.

    <Id ChangeType="MoveAway"

     AfterListId="ID"

    AfterItemId="ID">ID</Id>

    Treat in the same manner as a delete.

    <Id ChangeType="Restore">

    ID

    </Id>

    This item and any items beneath it were restored.

    <Id ChangeType=

    "SystemUpdate">ID</Id>

    Some clients may use hidden version, version history or modified time to determine whether to update an item. A SystemUpdate means Windows SharePoint Services has made changes and that you need to update all properties on that particular item.

    <Id ChangeType="Rename">ID</Id>

    Just like SystemUpdate, renamed items may retain hidden version information.

    Data

    The ItemCount attribute contains the number of items returned. Each Item is returned as a <z:row> tag.

    A ListItemCollectionPositionNext attribute is returned only for a full sync (no change token) when a row limit parameter was used to limit the number of items returned in one call. This type of paging is blocked on an incremental sync by restricting the number of updates processed from the change log to the row limit if smaller than our internal maximum.

    The contents of the items are returned as attributes with an "ows_" prefix and the internal name of the field. The values are encoded to be valid XML attribute values. A set of fields is always returned.

    Some fields are marked as required and returned if the IncludeMandatoryColumns option is set. If <ViewFields /> is sent, all fields are returned.

    When Properties="TRUE" is set in the ViewFields tag, the property bag (MetaInfo field) is separated into an attribute per property. These have a prefix of "ows_MetaInfo_".

    Most of the column values are simply converted from their internal representation, while others are constructed specially for a client. Several attributes are not represented by a field in the list schema.

     

    Table: List of attributes not represented by a field in the list schema.

    Attribute

    Description

    MetaInfo

    This is the property bag container, SPListItem.Properties. For more information, refer to the property bag in the SPListItem object model.

    Fields of type "Attachments"

    This is a bit column in the database, but query options modify it to return attachment data.

    Recurrence

    Data

    This is the XML definition of a recurrence.

     

    See http://blogs.msdn.com/sharepoint/archive/2007/05/14/understanding-the-sharepoint-calendar-and-how-to-export-it-to-ical-format.aspx for more details on this XML

    Acknowledgements

    I would like to acknowledge the following persons for their gracious help in technical reviews for this article: Matt Swann (Microsoft Corporation), Bill Snead (Microsoft Corporation).

    See Also

    I will be continuing this discussion in part 2.

  • Microsoft SharePoint Developer Documentation Team Blog

    How To: Customizing alert emails using IAlertNotifyHandler

    • 22 Comments

    In my post last week I talked about customizing alert notifications and alert templates. Sometimes you need to go further and create an alert handler.  This week I’d like to share a code sample from Rashid Aga, one of our escalation engineers.  His sample demonstrates how to intercept and modify alert mails using the IAlertNotifyHandler interface.

     

    Problem:

    ==============

    There are issues with particular field names like ItemName which will be truncated in an alert email at 70 characters. There can also be situations where you want to embed additional content in the email or change the layout and email appearance altogether.

     

    Solution:

    ==============

    Make use of the IAlertNotifyHandler interface to intercept the email and modify it.

     

    We can create our own class that inherits from the IAlertNotifyHandler interface and uses the OnNotification method. This will allow you to intercept the outgoing alert emails and modify them. We can access most of the properties for the alert and with some xml parsing and SharePoint object model code, we can extract all the information we need to build up the email. We can then construct the HTML stub to display the email based on your requirements and send the email out using SharePoint’s SendMail functionality.

     

    Steps:

    I have included the sample code below along with the steps to set up the scenario. I have formatted the output of my code to resemble the default alert template emails as close as possible, you can customize it further to suit your needs.

     

    1      Create a class project that inhertits from the IAlertNotifyHandler interface. Include the Microsoft.SharePoint and Microsoft.SharePoint.Utilities namespaces in the project.

                        This is the code for the class:

        public class Class1:IAlertNotifyHandler

        {

     

            #region IAlertNotifyHandler Members

     

            public bool OnNotification(SPAlertHandlerParams ahp)

            {

                try

                {

                    SPSite site = new SPSite(ahp.siteUrl+ahp.webUrl);

                    SPWeb web = site.OpenWeb();

                    SPList list=web.Lists[ahp.a.ListID];

                    SPListItem item = list.GetItemById(ahp.eventData[0].itemId) ;

                   

                    string FullPath=HttpUtility.UrlPathEncode(ahp.siteUrl+"/"+ahp.webUrl+"/"+list.Title+"/"+item.Name);

                    string ListPath = HttpUtility.UrlPathEncode(ahp.siteUrl + "/" + ahp.webUrl + "/" + list.Title);

                    string webPath=HttpUtility.UrlPathEncode(ahp.siteUrl+"/"+ahp.webUrl);

                    

                    string build = "";

                     if (ahp.eventData[0].eventType==1)

                     eventType="Added";

                     else if(ahp.eventData[0].eventType==2)

                     eventType="Changed";

                     else if(ahp.eventData[0].eventType==3)

                     eventType="Deleted";

     

                    build = "<style type=\"text/css\">.style1 {              font-size: small; border: 1px solid #000000;"+

                        "background-color: #DEE7FE;}.style2 {               border: 1px solid #000000;}</style></head>"+                    

                        "<p><strong>"+ item.Name.ToString() +"</strong> has been "+eventType +"</p>"+

                        "<table style=\"width: 100%\" class=\"style2\"><tr><td style=\"width: 25%\" class=\"style1\">"+

                        "<a href="+ webPath +"/_layouts/mysubs.aspx>Modify my Settings</a></td>"+

                        "<td style=\"width: 25%\" class=\"style1\"> <a href="+ FullPath +">View "+item.Name+"</a></td>"+

                        "<td style=\"width: 25%\" class=\"style1\"><a href=" + ListPath + ">View " + list.Title + "</a></td>" +

                        "        </tr></table>";

                    string subject=list.Title.ToString() ;              

                    SPUtility.SendEmail(web,true , false, ahp.headers["to"].ToString(), subject,build);

                    return false;

                }

                catch (System.Exception ex)

                {

                    return false;

                }

            }

     

            #endregion

        }

     

    2.            GAC the dll. 

    3.            Make a copy of the alertTemplates.xml file found at this location: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\XML. Always work with a copy of AlertTemplates.xml, not the original.

    4.            Call this new file CustomAlertTemplates and save the file. Edit the file and search for the keyword properties:

    Include these additional lines into the properties block:

     

              <NotificationHandlerAssembly>AlertHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d59ecf2a3bd66904</NotificationHandlerAssembly>

              <NotificationHandlerClassName>AlertHandler.Class1</NotificationHandlerClassName>

        <NotificationHandlerProperties></NotificationHandlerProperties>

     

    The entire stub should look like this now:

            <Properties>

                <ImmediateNotificationExcludedFields>ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;</ImmediateNotificationExcludedFields>

                <DigestNotificationExcludedFields>ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;</DigestNotificationExcludedFields>

                <NotificationHandlerAssembly>AlertHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d59ecf2a3bd66904</NotificationHandlerAssembly>

                    <NotificationHandlerClassName>AlertHandler.Class1</NotificationHandlerClassName>

                    <NotificationHandlerProperties></NotificationHandlerProperties>

      </Properties>

     

    Include this xml stub in each alert template section you want in the alert template file.

     

    5.            Run this command from C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN:  stsadm -o updatealerttemplates -filename "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\XML\customalerttemplates.xml" -url <your sharepoint site url>

    6.            Run this command:  stsadm -o setproperty -pn job-immediate-alerts -pv "every 1 minutes" so that you can see the log file come back in one minute. Be sure to set the time back after testing.

    7.            Make sure you have SharePoint already configured for outgoing emails.

    8.            Make sure that you have alerts for the document library turned on if you are testing with the document library. 

    9.            Run this command from the command prompt: iisreset

    10.         Run this command from the command prompt: services.msc

    11.         From the services window, restart the Windows SharePoint Services Timer.

     

    Your custom email alert handler should be configured at this point. Create a new alert and you should get the updated custom email.

     

    Once again, my thanks to Rashid for this sample.

     

  • Microsoft SharePoint Developer Documentation Team Blog

    SharePoint SDKs (and Other Documentation Resources) Now Updated for SP1

    • 15 Comments

    By now you’ve no doubt heard that SP1 for Windows SharePoint Services 3.0, Office SharePoint Server 2007, and Office SharePoint Designer are now available. (And if you haven’t heard, go here to get all the important details before you download and install the Service Packs.)

    For a complete listing of the documentation available for SharePoint SP1, check out the What’s New in SharePoint Server 2007 Service Pack 1 page on MSDN, which includes information resources for both WSS and MOSS SP1.

    I’m happy to announce that as part of the SP1 push, we’ve again updated the WSS and MOSS SDKs on MSDN:

    ·         Windows SharePoint Services 3.0 SDK (December 2007 Refresh)

    ·         Office SharePoint Server 2007 SDK (December 2007 Refresh)

    Here’s a quick run-down of additions and updates we’ve made to the WSS SDK:

    New and Updated Material in the Windows SharePoint Services 3.0 SP1 SDK

    New conceptual sections and topics that detail new features in Windows SharePoint Services 3.0 SP1 include:

    ·      ASP.NET AJAX in Windows SharePoint Services

    This section provides overview information, installation instructions, and programming examples that introduce you to how Microsoft ASP.NET AJAX interacts with Web Parts. Topics include:

    ·         Overview: ASP.NET AJAX and Web Parts in Windows SharePoint Services 3.0

    ·         Installing ASP.NET 2.0 AJAX Extensions 1.0 in Windows SharePoint Services Version 3.0

    ·         Walkthrough: Creating a Basic ASP.NET AJAX-enabled Web Part

    Other new conceptual sections and topics include:

    ·      Workflow Actions Schema Overview

    The section discusses how to use the Workflow Actions schema to create and deploy custom workflow actions. The section includes reference topics for each of the 14 elements in the schema, as well as an example .ACTIONS file:

    ·         .ACTIONS File Example (WorkflowActions)

    ·      Alerts in Windows SharePoint Services

    This section provides 8 new topics of overview information and programming tasks to help you work with Windows SharePoint Services 3.0 Alerts.

    Significantly revised sections and topics include:

    ·      Custom Field Types

    This section has been expanded with nine new topics, including the following new walkthrough:

    ·         Walkthrough: Creating a Custom Field Type

    In addition, reference material for all base field-rendering control types, and most derived types, in Microsoft.SharePoint.WebControls has been expanded.

    ·      Mobile Development

    This section has been expanded with 6 new conceptual topics, including the following new procedural and walkthrough topics:

    ·         How to: Customize Mobile Home Pages

    ·         How to: Customize Mobile List View and Form Pages

    ·         How to: Customize Field Rendering on Mobile Pages

    ·         Walkthrough: Customizing Item Titles on a Mobile Display Form

    ·         Walkthrough: Creating a Custom Field Rendering Control for Mobile Pages

    In addition, reference material for all types in the Microsoft.SharePoint.MobileControls namespace has been added or expanded. The types in this namespace are now fully documented.

    ·      Working with Site Templates and Definitions

    Several topics have been expanded.

    ·      All types in the Microsoft.SharePoint namespace that connected to auditing are now fully documented.

    ·      More than 80 code samples rewritten to adhere to the best coding practices as described in the following articles:

    ·         Best Practices: Common Coding Issues When Using the SharePoint Object Model

    ·         Best Practices: Using Disposable Windows SharePoint Services Objects

    ·      Greatly expanded reference material for 39 types in the Microsoft.SharePoint.Utilities namespace.

    This update also includes numerous other updates and revisions to existing SDK content.

    And as for the SharePoint Server 2007 SDK, we’ve added and revised quite a bit of material there as well:

    New and Updated Material in the SharePoint Server 2007 SP1 SDK

    New and Improved Features in Office SharePoint Server 2007 SP1

    ·         Support for Custom HTTP and SOAP Headers

    New Conceptual Topics and How Tos

    ·         SharePoint Products and Technologies Customization Best Practices

    ·         Modeling Web Service Systems

    ·         Modeling Database Systems

    ·         Document Converter Framework Sample

    ·         Working with Menus and Navigation Objects

    ·         Modifying Navigation Settings Through the UI

    ·         Customizing Navigation Controls and Providers

    New Web Services and Class Library Reference Topics

    ·         Microsoft.SharePoint.UserProfiles (56 classes) 

    ·         User Profile Web Service (14 classes) 

    ·         User Profile Change Web Service (2 classes) 

    ·         Exception classes (multiple classes in various namespaces)

    ·         Internal only classes (multiple classes in various namespaces)

    New Tools/Samples

    ·         WCM Document Converters Code sample

    ·         Workflow Collect Feedback ASPX sample

    Check it out, and as always, let us know what you think—either through rating our content, entering a comment on the topic online, or pinging us with an email through our blog.

  • Microsoft SharePoint Developer Documentation Team Blog

    Customizing Alert Notifications and Alert Templates in Windows SharePoint Services 3.0

    • 41 Comments

    Hello.  My name is Albert Meerscheidt and I’m one of the Programming Writers on the Windows SharePoint Services SDK team.

    In the coming months I will be posting about the content that’s on the publishing horizon.  The first topic that I'd like to discuss is how to customize alert notifications and alert templates in Windows SharePoint Services 3.0. The following is a somewhat abridged version of what you will see in the upcoming SDK update.

       Overview

    Alerts are an e-mail subscription notification service in Windows SharePoint Services 3.0. Users can create alerts to be notified of changes to list items (item level alert), documents, lists (list level alert), or document libraries. Alerts in Windows SharePoint Services 3.0 are quite flexible; when you create an alert you have options such as when to be notified and what kind of changes will trigger the alert. Alerts can be managed by users and administrators and customized by developers.

    After an alert has been created, changes that fit the criteria specified in the alert are recorded in an event log. Alerts are generated by a timer job that reads the event log, formats the alert email using an alert template, and then sends that email out to the user.

    Alert templates are stored in Alerttemplates.xml. Alerttemplates.xml defines the format, contents, and properties used to create alert messages from each list type in Windows SharePoint Services 3.0. Developers can customize alerts by modifying a copy of Alerttemplates.xml and then loading the customized alert templates using the stsadm -o updatealerttemplates command.

    Note   Do not modify Alerttemplates.xml itself. Doing so may result in loss of functionality when upgrading or installing service packs.

    The <filters> element in each template allows you to create custom triggers for events by using Collaborative Application Markup Language (CAML) queries.

    The default timer interval can be changed to adjust the latency of 'immediate' alerts.

       Default Timer Interval

    Alert emails can be sent immediately or in a daily or weekly summary. Immediate alerts are not actually immediate; they are sent out after a delay determined by the timer interval. This is done for performance reasons.

    The default timer interval is 5 minutes. Thus, if this interval has not been changed you could notice a delay of up to 5 minutes (the default timer interval) before an alert message is generated. Changes are recorded as they occur in an event log (discussed later in this article) and then alert messages are rendered from that log when the next timer job runs.

    The default timer interval can be determined using the stsadm command.

    stsadm -o getproperty -pn job-immediate-alerts -url<url> command.

    The timer interval property can be set using stsadm. In this example I set the interval to 3 minutes.

    stsadm -o setproperty -pn job-immediate-alerts -url<url> -pv "every 3 minutes"

    For more information about STSADM, see http://technet2.microsoft.com/Office/en-us/library/188f006d-aa66-4784-a65b-a31822aa13f71033.mspx?mfr=true.

       Alert templates

    Alert templates are stored in the configuration database and are available at the web application level. Alert templates are defined in Alerttemplates.xml.

    Windows SharePoint Services 3.0 ships with the following templates:

    Table 1. Predefined alert templates

    Alert Template Name

    Description

    SPAlertTemplateType.GenericList

    The first alert template in Alerttemplates.xml. GenericList is used unless there is a match to one of other event types.

    SPAlertTemplateType.DocumentLibrary

    Notification of changes in document libraries

    SPAlertTemplateType.Survey

    Notification of changes in surveys

    SPAlertTemplateType.Links

    Notification of changes in links

    SPAlertTemplateType.Announcements

    Notification of changes in announcements

    SPAlertTemplateType.Contacts

    Notification of changes in contacts

    SPAlertTemplateType.Events

    Notification of changes in events

    SPAlertTemplateType.Tasks

    Notification of changes in tasks

    SPAlertTemplateType.DiscussionBoard

    Notification of changes in discussion boards

    SPAlertTemplateType.PictureLibrary

    Notification of changes in picture libraries

    SPAlertTemplateType.XMLForm

    Notification of changes in XML form

    SPAlertTemplateType.DataConnectionLibrary

    Notification of changes in data connection libraries

    SPAlertTemplateType.AssignedtoNotification

    Assigned to task / issue list notifications

     

    Alerttemplates.xml is located at: Local_Drive\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\XML directory.

    Note   Do not modify Alerttemplates.xml itself. Make changes to a working copy.

     

       Alert Template Format

    Alert templates have the following format:

    <AlertTemplates>

     <AlertTemplate Type="List" Default="True" Name ="Default Template">

     </AlertTemplate>

    <AlertTemplate Type="List" Name="SPAlertTemplateType.GenericList">

      <EventTypes IsVisible="True"/>

      <Format>

      </Format>

      <Filters>

        <FilterDefinition>

        </FilterDefinition>

      </Filters>

      </AlertTemplate>

    </AlertTemplates>

     

    Table 2. Alert Template Attributes

    Attribute

    Description

    Name

    The name of the template. Searching on the template names provides an easy way to locate a particular template.

    Type

    The type attribute can be set to List, Item, or Custom.

    Default

    Default="True" sets the default alert template.

    Filters

    If IsVisible = "True", then the filter fields are displayed when creating the alert.

    Filter Definitions

    Define a filter (query).

    Format

    Formatting of the email is defined in the <Format> element.

    Fields

    Specify which fields to render in the alert email.

     

       Filters and Filter Definitions Elements

    Filters enable you to create new triggers for an event such as 'the priority of a task has changed'. To modify existing filters or create additional filters modify the <FilterDefinition> element of the appropriate template. Define the query element using inside the filter using Collaborative Application Markup Language (CAML).  This query will create an alert event if the event date, end date, or location changes.

     

    <FilterDefinition>

       <FriendlyName>$Resources:Alerts_4_filter;</FriendlyName>

       <ShortName>$Resources:Alerts_4_filter_shortname;</ShortName>

       <Query>

       <Or>

          <Or>

          <Neq><FieldRef name="EventDate/New"/>

          <FieldRef name="EventDate/Old"/>

          </Neq>

          <Neq>

             <FieldRef name="EndDate/New"/>

             <FieldRef name="EndDate/Old"/>

          </Neq>

       </Or>

          <Neq>

             <FieldRef name="Location/New"/>

             <FieldRef name="Location/Old"/>

          </Neq>

       </Or>

       </Query>

    </FilterDefinition>

     

    Resource variables listed in AlertTemplates.xml, such as $Resources:Alerts_anything_filter_shortname, can be found in core.resx; located in the Local_Drive\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Resources directory.

    Note   The Query element in the Alerts schema contains no Where element.

     

    For more information about CAML query schema, see http://msdn2.microsoft.com/en-us/library/ms467521.aspx.

       

       Format Element

    Use the format element of an alert template to modify the look of the alert email. You can also customize the formatting of the alert email by using embedded styles.

    <Format>

    <Digest> - digest format

        <Subject>

        </Subject>

        <Header>

        </Header>

        <HeaderFieldsHeader>

        </HeaderFieldsHeader>

        <HeaderFields>

        </HeaderFields>

        <HeaderFieldsFooter>

        </HeaderFieldsFooter>

        <RowHeader> - each row’s header (row refers to each item level change in the digest notification)

        </RowHeader>

        <RowFields> - each row’s fields

        </RowFields>

        <RowFooter> - each row’s footer

        </RowFooter>

        <Footer>

        </Footer>

    </Digest>

    <Immediate> - immediate notification format

        <Subject> - email subject

        </Subject>

        <Header>

        </Header>

        <Fields> - the html for the fields. CAML will go over all the fields and render this part of the template. The field itself should be referenced using <GetVar Name=”OldValue#{Field}” /> or <GetVar Name=”NewValue#{Field}” />

        </Fields>

        <Footer>

      </Footer>

    </Immediate>

    </Format>

     

        Fields Element

    Specify the fields to be rendered in the email.

    For example you can specify <GetVar Name="NewValue#{Field}"> inside the <Fields> section. More examples can be seen in AlertTemplates.xml.

    To exclude some fields from being rendered in the email, you should include them in the <DigestNotificationExcludedFields> and <ImmediateNotificationExcludedFields> section. DigestNotificationExcludedFields and ImmediateNotificationExcludedFields give you the flexibilty to render different fields for immediate alerts and summary (digest) alerts.

     

    In addition to these, CAML is supported. For example you can specify <GetVar Name="NewValue#{Field}"> inside the <Fields> section. More examples can be seen in AlertTemplates.xml.

     

    To exclude some fields from being rendered in the email, you should include them in the <DigestNotificationExcludedFields> and <ImmediateNotificationExcludedFields> section.

       Properties Element

    The properties element allows you to include or exclude fields from the list that would be shown in the email notification. The example below demonstrates how to exclude fields for both the immediate and summary (digest) alerts.

    <Properties>

    <ImmediateNotificationExcludedFields>ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;</ImmediateNotificationExcludedFields>

                <DigestNotificationExcludedFields>ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;</DigestNotificationExcludedFields>

            </Properties>

       Customizing the Alert Email

    Alert UI elements

    In addition to the formatting styles defined in the <format> element, alert templates include formatting instructions for user interface elements, such as the Modify my alert settings, Mobile View, and View <list> buttons, which can be modified or removed.

    Modify my alert settings button

    The following code displays the Modify my alert settings button:

    <GetVar Name="MySubsUrl" URLEncodeAsURL="TRUE"/>

                <HTML><![CDATA[">$Resources:Alerts_link_modify;</a></td>

            <td class="toolbarsep">|</td>]]></HTML>

    View list URL button

    The following code displays the List URL button:

    <GetVar Name="ListUrl" URLEncodeAsURL="TRUE" />

                    <HTML><![CDATA[">$Resources:Alerts_link_view; ]]></HTML><GetVar Name="ListName" HTMLEncode="TRUE" />

    Mobile View button

    The following code displays the Mobile View button:

    <Switch>

            <Expr><GetVar Name="MobileUrl"/></Expr>

              <Case Value = "NULL"/>

              <Default>

                  <HTML><![CDATA[

            <td class="toolbarsep">|</td>

            <td class="toolbarbutton"><a href="]]></HTML><GetVar Name="MobileUrl" />

         <HTML><![CDATA[">$Resources:Alerts_mobile_view;</a></td>]]></HTML>

             </Default>

    </Switch>

    Steps to Modify Alerts

    You can use any XML editing tool to edit the templates.

    1.      Create a working copy of AlertTemplates.xml.

    Note   Remember to always modify a copy of Alerttemplates.xml, not Alerttemplates.xml itself.

    2.      Edit the working copy that you just created.

    3.      Use the stsadm command to read the changed templates into the database.
    STSADM -o updatealerttemplates -url <http://urlname> -filename <your working copy filename>.

    4.      Restart IIS.

    Note   It may be necessary to restart SharePoint Timer Services.

    Creating your own alert template

    If you want, you can create your own template, and then use the stsadm command to load that template. Pattern your template after one of the templates in Alerttemplates.xml.

     

    A particular instance of a list can use a specific alert template by setting the SPList.AlertTemplate property with the desired template. Any new alert created for this list through the subnew.aspx or through the object model using SPAlerts.Add(SPList / SPItem, SPEventType, SPAletFrequency) will pick up the alert template defined for the list.

     

    To assign a custom alert template to a specific list

    1.      Create a new custom template with a unique Name attribute in your working copy of AlertTemplates.xml (You can copy paste the entire section with Name SPAlertTemplateType.GenericList, change the name and modify the sections you want to). Modify the template as necessary. After restarting IIS, write object model code to set the list alert template

    SPAlertTemplateCollection ats = new SPAlertTemplateCollection((SPWebService)(WebApplication.Parent)); //give appropriate values for WebApplication

    list.AlertTemplate = ats[“name from above”];

    list.Update();

     

       Event Logs

    Rendering of alert emails occurs some time after the change that generated the alert. Data in the EventCache and EventLog tables is used for rendering the alert since the changed item may no longer exist, or exist in the same form, at the time the email is sent.

    The event log uses a SQL table called the EventCache to record SQL level events as they occur.  Each row in the table corresponds to an event.  The events can be read using the WSS object model.  In general, the number of rows written to the table is the minimum necessary to capture the semantics of the event.  The assumption is that the reader of the event knows how to use the event row to calculate all the implications of a given event.  For example, when a folder is deleted, the folder delete event is the only event written to the change log.  No events are written for the files and sub-folders that are contained in the folder.  The reader of the folder delete event needs to understand that it implies the deletion of the entire contents of the folder.

    By default, the change log retains 15 days worth of data.  You can configure this using the “web application general settings” page.  There is a timer job that runs daily to delete all expired change log entries.

    Change Events

    Change events are returned as objects in an SPChangeCollection.  Each change object is a subclass of the SPChange object.  Depending on the type of SPChange object, different properties are available.  The change collection is created by calling SPSite.GetChanges(), SPWeb.GetChanges(), SPList.GetChanges(), or SPContentDatabase.GetChanges().  An SPChangeToken object is passed into GetChanges() to specify where in the log to begin reading.  An SPChangeToken can be serialized to a string format by calling the ToString() method. 

    Use the SPList.CurrentChangeToken property to get the current change token for that list object.  There is a similar property on the SPSite, SPWeb, and SPContentDatabase objects.  In general, the SPChangeToken from one object cannot be used in the GetChanges() method of another object.  The only exception is that the SPChangeToken from an SPContentDatabase can be used in any object contained in that content database.  There is also an object called an SPChangeTokenCollection which can be used to store multiple change tokens together, that can then be serialized to one single string.

    Change Log Event Types

    The following is a description of the event types recorded in the change log.

    Table 3. Event Types

    Event Type

    Description

    Add

    An object was added.  For items, files and folders, the TimeLastModified in the log should be the same as the Created datetime of the object.

    Modify

    An object was modified.  For items, files and folders, the TimeLastModified in the log should be the same as the modified date time of the object.

    Delete

    An object was deleted.

    Rename

    An object was renamed.  This means that the file name portion of the URL was changed.

    MoveInto/MoveAway

    An object was renamed.  This means that the path portion of the URL was changed.  The leaf portion may or may not have been changed also.

    Restore

    An object was restored from the recycle bin or from a backup. This event signals to a sync client change log reader that it needs to re-sync the object and all its children.

    Role Add

    A role was added at the scope of the object.

    Assignment Add

    A role assignment was added at the scope of the object.

    System Modify

    An object was modified without its Modified date time or Modified By property changing.  The TimeLastModified in the log should be the time that the update took place, not the Modified date time property.

    Member Add

    A member is added to a SharePoint group.

    Member Delete

    A member is deleted from a SharePoint group.

    Role Delete

    A role was deleted at the scope of the object.

    Role Update

    A role was updated at the scope of the object.

    Assignment Delete

    A role assignment was deleted at the scope of the object.

    Navigation

    The navigation nodes were updated at the scope of the object.

     

    Object Types

    The following is a list of the object types that may have changes recorded in the change log.  Not all event types are possible on all object types.

    Table 4. Object Types

    Object Type

    Description

    Item

    This object type applies to all objects that exist in a list: list items, files and folders

    List

    List object type

    Web

    Web object type

    Site

    Site object type

    File

    This object type applies to files that exist outside of a list and don’t have a corresponding item.

    Folder

    This object type applies to folders that exist outside of a list and don’t have a corresponding item.

    Alert

    Alert object type

    User

    User object type

    Group

    Group object type

    ContentType

    Content object type

    Field

    Field object type

    SecurityPolicy

    A security policy change has been made at the web application level that affects the entire content database.  This event was added late in the development cycle, so it and the content database level restore event are logged as a site level event with a SITEID of “00000000-0000-0000-000000000000”

     

       Conclusion

    Alerts are quite flexible; when you create an alert you have options such as when to be notified and what kind of changes will trigger the alert. Alerts can be managed by users and administrators and customized by developers.

    Alerts are generated by a timer job that reads the event log, formats the alert email using an alert template, and then sends that email out to the user.Developers can customize these alert emails by modifying alert templates. Alert templates are stored in Alerttemplates.xml. Alerttemplates.xml defines the format, contents, and properties used to create alert messages from each list type in wssversion3. The <filters> element in each template allows you to create custom triggers for events by using Collaborative Application Markup Language (CAML) queries. Developers can customize alerts by modifying a copy of Alerttemplates.xml and then loading the customized alert templates using the stsadm -o updatealerttemplates command.

     

    In my next post I'll talk about how to use the IAlertNotifyHandler interface to customize alerts. 

     

     

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Content Migration in SharePoint (Post 2):

    • 0 Comments

    Key Concepts in Selective Migration 

    Picking up where the first post left off, today's foray explores the basic concepts of SharePoint content migration. Most of these concepts come into play in nearly every migration scenario where you use the SharePoint.Deployment namespace APIs. This post covers the following concents:

    1. Content selection (also known as "cherry picking")
    2. The content migration package (.cmp) file
    3. Object identity (that is, retaining object identity on import)
    4. Reparenting objects
    5. Handling content types when migrating a site collection
    6. Handling workflows when migrating

     

         Content selection for export

    Once the initial full migration has occurred and the source and destination sites are established, selective migration relies on logic in your custom code for selection criteria that determines which items are selected for migration, and when. Also known as "cherry picking," this is the process through which specific items on the source site collection are migrated to the destination site collection based on selection criteria that is specified in the custom code.

    In the basic scenario, logic in a code module associated with a timer job runs on a recurring schedule (for example, every 24 hours at 3:00 a.m.); the module examines date stamps or change logs (or both), then selects and exports only those files that have changed since the last migration.

    The operation relies on the identity of objects in the source and destination site collections. At import time, the destination recognizes objects by GUID identifiers and then updates their corresponding files accordingly.

         Content migration package (.cmp) file

    The content migration package is a compressed cabinet (.cab) file that uses the .cmp file extension. The migration package (.cmp) file aggregates data for export from the source site, along with site structure and dependency data, and stores it as compressed, serialized XML files.

    While the export operation compresses site data by default, you can override this behavior and export uncompressed files by changing the SPDeploymentSettings.FileCompression property to false. This property is on the SPDeploymentSettings object.

    When using file compression, you must must specify the name of the compressed content migration (.cmp) file using the BaseFileName property on the SPExportSettings object. The FileLocation property specifies where the compressed file is located on the source server.

    By default, the .cmp files are limited to 24 MB in size, although you can change this using the FileMaxSize property. When site data exceeds the 24 MB limit, your site data is separated in to two or more migration files. However, where a single file exceeds the maximum file size, the operation resizes the .cmp file to accommodate the file. You can have any number of .cmp files.

         Object identity

    SharePoint objects are identified by GUID on the source server. Upon import, by default, the objects are assigned new GUIDs on import to the destination. To support selective migration scenarios, however, you must change the default and retain the identity of the object that you are exporting. This is very important, because the only way that files can successfully update their corresponding versions on the destination site is for the destination to recognize the object by its identifying GUID.

    You can retain object identity by setting the RetainObjectIdentity property on the SPImportSettings object to true.

    Note   To use the RetainObjectIdentity property to support selective migrations, you must also set the ExportMethod property to the value ExportChanges.

    Retaining object identity should be used carefully, and in limited scenarios. It most commonly supports a publishing scenario in which source and one or more destination servers are mirror images — for example, where a development or test server is migrating updates to one or more production servers.

    If you are creating a new site that you know in advance will receive file updates on a regular basis (for example, from a development or test server), you can enable a smooth link between the two by first doing a full migration from the source to a completely blank site, but with RetainObjectIdentity property set to true. It is very important, then, that you never modifythe destination. The initial migration established the mirrored hierarchy and mappings; any modifications to the destination can break the linkage. If this relationship gets broken, your only recourse is to delete the destination and then reestablish the linkage as originally done.

    Note   In the scenario described above, you can use Stsadm.exe to complete the export portion of the migration, since it does not support the retention of object identity. However, you must use the migration APIs to complete the import portion of the migration in order to retain object identity.

    SharePoint content databases do not permit duplicate GUIDs or file names, so you must be careful when implementing this property. You should not retain object identity when you are simply updating files on the destination server, because the import operation cannot determine whether a file is new or an update, and will therefore place a duplicate copy of the file on the destination.

    This happens because the migration process uses GUID identifiers, but only down to the level of the item collection. Beneath that level, that is, for individual files, the system uses the file name. Consequently, the import simply copies over the updated version of the existing file and leaves the original intact, potentially creating duplicates. When the destination server then encounters duplicate file names, it cannot resolve the ambiguity and becomes unstable.

    Important   When deleting files, it is very important to delete the files on both the source and destination servers. Not doing so can cause duplicate files names and an unstable site.

    To support field deletion (a feature that deletes previous versions of items on the source server when the migration updates and deletes the same file on the destination), the RetainObjectIdentity property must be set to true.

     

         Reparenting

    Now let's consider a much different scenario. Instead of a publishing scenario, in which objects on the source server have to map to their equivalent files on the destination server, consider instead a scenario in which a specific subweb or list item is migrated and you want to place it at a different place in the destination hierarchy. This is a very common requirement and you handle this by reparenting the object (or objects) on import.

    It's important to note that, as discussed in the section on retaining object identity, any time that you do not retain object identity, a migrated object will need a new parent specified on import. It doesn't matter if the destination site collection is the same site collection as the source or even if it is in the same database as the source site collection. It also doesn't matter if the destination site collection is in the same or a different farm as the source site collection.

    If you export an item from a database without exporting its parent, then the item that you've exported will become orphaned in the content migration package, but that's not necessarily a problem. A package can contain multiple orphaned objects.

    Having orphaned objects in the migration package is not a problem because the import method allows us to define a new parent for each of these orphaned objects. This is what is meant by "reparenting." However, objects that are exported with their parent objects keep their relationships during migration. So, for example, if you export Web A and Web B is a sub web of web A, then you can only change the parent of web A. The parent of Web B will remain as Web A, because Web B.

    There are two ways to reparent orphaned objects when importing. In the first, you import all of your orphaned objects into the same subweb, while in the other method you assign parent objects individually to each orphaned object.

          Reparent by importing all orphans to the same subweb

    You can reparent your orphan objects by simply importing all of them into the same subweb. However, this only works if all of the orphan objects are destined for the same subweb. For example, this is the preferred method if you have exported two or more list or document library objects and you want to import them all into the same subweb. This works even if the export objects are from different source sites. Here is some sample code for doing this. Of particular note are the WebUrl and RetainObjectIdentity properties.

     

    SPImportSettings settings = new SPImportSettings();

    settings.SiteUrl = "http://localhost:2001";

    settings.WebUrl = "http://localhost:2001/MyDestinationWeb";

    settings.FileLocation = @"c:\export";

    settings.FileCompression = false;

    settings.RetainObjectIdentity = false;

     

    SPImport import = new SPImport(settings);

    import.Run();

     

    In the above example, the WebUrl property defines the site (the SPWeb) that becomes the new parent for all orphaned objects in the export package.

    Note   You cannot use this method to reparent orphaned objects if the migration package contains orphaned documents; SharePoint site cannot become the parent of a document document object (only list or folder objects can parent documents).

    Note, too, that the RetainObjectIdentity property is set to false. This is important because keeping this property as false (which is the default) cases the operation to assign a new GUID to the orphaned objects, which is essential for reparenting those objects.

          Reparent by assigning new parent objects individually

    Assigning new parents to orphaned objects individually is much more flexible than the previous method, but also requires more coding. In this approch, you must intercept the import operation to assign a new parent to each orphaned object after the operation has accumulated a list of all orphaned objects in the migration package. You can do this by implementing a custom event handler, as in the following example:

     

    static void OnImportStarted(object sender, SPDeploymentEventArgs args)

    {

       SPSite site = new SPSite("http://localhost:2001");

       SPWeb web = site.RootWeb;

     

       SPImportObjectCollection rootObjects = args.RootObjects;

       foreach (SPImportObject io in rootObjects)

       {

          io.TargetParentUrl = web.Url;

       }

     

       web.dispose();

       site.dispose();

    } 

     

    This approach uses the RootObject collection of the event arguments to aggregate all and then reparent all of the orphan objects. Actually, we do much the same thing that we did when we imported all of the objects to the same subweb, that is, we defined a specific site (SPWeb) as the new parent of all included orphaned objects. However, notice how you can extend the logic in the event handler to, for example, assign different parents to different orphans based on object type, as shown here:

     

    static void OnImportStarted(object sender, SPDeploymentEventArgs args)

    {

       SPSite site = new SPSite("http://localhost:2001");

       SPWeb web = site.RootWeb;

       SPList list = web.Lists["MyDocLib"];

     

       SPImportObjectCollection rootObjects = args.RootObjects;

       foreach (SPImportObject io in rootObjects)

       {

          if (io.Type == SPDeploymentObjectType.ListItem)

          {

             io.TargetParentUrl = list.RootFolder.ServerRelativeUrl;

          }

          if (io.Type == SPDeploymentObjectType.List)

          {

             io.TargetParentUrl = web.Url;

          }

          ...

       }

     

       web.dispose();

       site.dispose();

    } 

     

    There is a great deal of flexibility here. In addition to reparenting based on the object type, you could instead look at the original TargetParentUrl, for example, to obtain the location of the source and then include that as part of your reparenting logic.

    The code example below shows us how to hook up the event handler to the import operation. You can do this as follows:

     

     

    static void ImportDocLibItem()

    {

       SPImportSettings settings = new SPImportSettings();

       settings.SiteUrl = "http://localhost:2001";

       settings.FileLocation = @"c:\deployment5";

       settings.FileCompression = false;

       settings.RetainObjectIdentity = false;

     

       SPImport import = new SPImport(settings);

     

       EventHandler<SPDeploymentEventArgs> eventHandler = new EventHandler<SPDeploymentEventArgs>(OnImportStarted);

       import.Started += eventHandler;

     

       import.Run();

    }

     

    Notice that you need to register the event handler with the import class before you start the import operation.

     

    There are several more event handlers that you can register and use during import; these are explained in more details at help topics for SPImportEvents and for SPExportEvents. Similar events can also be registered for export, if required:

     

     

         Content types (handling)

    Migration operations that use the export and import commands include your content type definition schema files in the export package. However, in cases where content type definitions hold references to custom SharePoint features, you must manually install and configure on the destination compute the feature for which the content type holds a reference. Features that are native to the SharePoint server environment will have their type definition references restored automatically.

    On starting the import operation, you will receive a warning message that alerts you with the identity of custom features for which references must be recreated. You must resolve these references after completing the import.

    For more information about content types, see Content Types. For more information about SharePoint features, see Working with Features.

     

         Workflows

    Workflows are not included in export packages so when you initially set up a migration target, you must copy any custom workflows that you have onto the destination server and reconfigure the workflow association table. This includes creating (or restoring on the destination) the workflow definitions files.

    Of course, manually restoring your custom workflows on the migration destination is only necessary when you initially set up your destination server. Subsequently, when doing selective migrations in a publishing scenario, it is advantageous that workflows are excluded from the migration package.

    For more information about managing workflows, see the following:

    ·         Introduction to Workflows in Windows SharePoint Services

    ·         Workflows in Windows SharePoint Services

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Content Migration in SharePoint

    • 3 Comments

    Around the time work started on a long whitepaper about migrating content files from SharePoint server "A" to SharePoint server "B" using the migration and deployment APIs in the Microsoft.SharePoint.Deployment namespace, I came across a series of blog posts by Stefan Goβner that covered the subject matter in detail. Many of you have probably seen Stefan's work:  Deep Dive into the SharePoint Content Deployment and Migration API, Parts 1-5.

    I contacted Stefan and he agreed to collaborate. He's allowed me to use some of his code examples, and to expand on some of his points. Additionally, I've collaborated with the feature PM and lead developer on the migration/deployment APIs here at Microsoft, Patrick Simek, so the whitepaper treats the subject quite a bit more broadly than does Stefan's blog posts.

    I'll chunk this article up into segments for posting to this blog.  Comments are DEFINITELY welcome.  I'll be converting much of the content of this article (and these posts) into Help content in the SharePoint SDK documentation, so anything that helps to improve the quality of the content is highly desired.  Here's the first section:

    The ABCs of Content Migration (Post 1)

    Many scenarios require moving content from one SharePoint site to another. When the scenario calls for a full migration (that is, moving the entire contents of a SharePoint site or site collection), the task is relatively simple. Typically, you use either of two main approaches:

    •  Use the export and import operations in the Stsadm.exe utility to migrate data from one site to another.
    • Alternatively, use SOAP calls to the  Sites.ExportWeb and  Sites.ImportWeb methods that are implemented on the Sites Web service to migrate data.

    However, both of these methods have limitations. Both limit you to migrating only a full SharePoint site, or site collection. In addition, neither allows you to retain object identity during the migration operation. Retaining object identity is an essential feature of selective migration. Using Stsadm.exe and Sites Web service have other limitations as well.

    Consequently, content migration scenarios that require you to export only selected content, or that require you to automate or customize migration operations, there is only one approach: you must write a custom solution that uses the APIs in the Microsoft.SharePoint.Deployment namespace.

    Note   Selective migration operates when there is an established source and a recognized destination for a known quantity of content. That is, selective migration requires that a full migration was initially performed so that the destination is a mirror image of the source.

    Selective migration applies, typically, to content that needs to be migrated from server to server based on factors such as content version (current vs. future), time stamp, and content state (approved vs. in review, for example). Selection criteria provides a high degree of granularity from the scope of the site collection down; that is, you have selection control at the scope of the Web, the list, folder, and list item.

    You can have any number of .cmp files in a migration operation, and you can also have multiple destinations. However, the objects contained in a given content migration package (.cmp) file must originate from a single site collection.

    Typical Migration Scenarios

    The APIs in the Microsoft.SharePoint.Deployment namespace provide a rich migration toolbox that gives you an enormous degree of flexibility to support wide ranging migration scenarios. Following is a list of migration and deployment features that are supported in Windows SharePoint Services 3.0. This list is only a high-level, generalized summary of supported migration scenarios. The Deployment APIs are sufficiently rich to support any number of special circumstances that you might face.

    • Export an entire site collection (that is, do a full migration).
    • Export a specific site inside a site collection, including or excluding content in subsites as needed. The deployment APIs allow you to include or exclude dependencies.
    • Export a SharePoint list or document library, or even of a specific folder inside a document library.
    • Export a single list item or document from a document library. This provides the extreme migration granularity that enables so much flexibility.
    • Export object dependencies like images or attached files.
    • Generate your export file as a compressed export (.cmp) package, or as uncompressed files.
    • Throttle exports by specifying a maximum size for the compressed export package file (multiple export files will be created if required).
    • Support incremental exports of items that are selected for export based on change tokens. This allows you to automate exporting all items that have been created, changed, or deleted after the timestamp specified in the change token.
    • Import the exported content with or without preserving object identity—that is, you can configure objects in the export package to retain their object GUID. This is a requirement for selective migrations.
    • Import the migration content under the same or a different parent in the destination content database. Moving items to a different position in the site hierarchy is an operation is known as reparenting.
    • Do link fixup during import.

    In my next post we'll continue with this high-altitude look at the subject of content migration. There, we'll talk extensively about key concepts in the migration/deployment feature area.

  • Microsoft SharePoint Developer Documentation Team Blog

    Creating new .ACTIONS files for code-free workflow editors

    • 2 Comments

    .ACTIONS files are used by Windows SharePoint Services 3.0 to compile a list of code-free workflow actions and conditions.  The default file provided by Windows SharePoint Services 3.0 is called the WSS.ACTIONS file found under C:\Program Files\Common Files\Microsoft Shared\web server extensions\12. 

    By creating additional .ACTIONS files and placing them into the same directory, Windows SharePoint Services will read all of the files and create a single list of available workflow actions and conditions and present them to SharePoint Designer 2007 in the form of a cohesive, categorized list that can be used to build more complex workflows.

    The following is an example of how to construct this file.

     <?xml version="1.0" encoding="utf-8"?>
    <WorkflowInfo Language="en-us">
       <Conditions And="and"
                   Else="Else If"
                   Not="not"
                   Or="or"
                   When="If">
          <Condition AppliesTo="list"
                     Assembly="Assembly.Name,
                               Version=0.0.0.0,
                               Culture=neutral,
                               PublicKeyToken=GUID"
                     ClassName="Fully qualified
                                class name"
                     FunctionName="Boolean method name
                                   implemented in
                                   class"
                     Name="Name to be displayed in
                           workflow editor"
                     Type="Advanced"
                     UsesCurrentItem="true">
             <RuleDesigner Sentence="Sentence to be
                                     displayed to the
                                     workflow editor">
                <FieldBind DesignerType="Date"
                           Field="Parameter that
                                  FieldBind maps to"
                           Function="true"
                           Id="Unique positive Integer"
                           Text="Text to be displayed
                                 as a hyperlink"
                           TypeFrom="Parameter that a
                                     non-Operator derives
                                     its type from"
                           Value="Reserved for future use">
                   <Option Name="Option1" Value="Value1" />
                </FieldBind>
             </RuleDesigner>
             <Parameters>
                <Parameter Direction="In"
                           InitialValue=""
                           Name="MyParameter"
                           Type="System.String, mscorlib" />
             </Parameters>
          </Condition>
       </Conditions>
       <Actions>
          <Action Name="Action name displayed in editor">
             <RuleDesigner Sentence="Sentence to be
                                     displayed to the
                                     workflow editor">
                <FieldBind DesignerType="CreateListItem"
                           Field="Parameter that FieldBind
                                  maps to"
                           Function="true"
                           Id="Unique positive Integer"
                           OperatorTypeFrom="Parameter
                                             Operator derives
                                             its type from"
                           Text="Text to be displayed
                                 as a hyperlink"
                           TypeFrom="Parameter non-Operator
                                     derives its type from"
                           Value="Reserved for future use">
                </FieldBind>
             </RuleDesigner>
          </Default>
       </Actions>
    </WorkflowInfo>

    In my next post I will start going into details of each of the elements, so stay tuned.

     Rodney

  • Microsoft SharePoint Developer Documentation Team Blog

    SharePoint SDKs Machine Translation Pilot

    • 1 Comments

    Erika Ehrli has the scoop on Office’s participation in the MSDN machine translation pilot project. As part of the pilot, you can try out beta versions of the machine-translated Windows SharePoint Services 3.0 and Office SharePoint Server 2007 SDKs in Portuguese. You can also test out beta versions of the machine-translated Office SharePoint Server TechNet materials in Spanish and Portuguese, side-by-side view with the original English content.

    Erika has all the details here.

  • Microsoft SharePoint Developer Documentation Team Blog

    SharePoint Resource Center Pages Now Live

    • 3 Comments

    Last week we added five new Resource Center pages to the Windows SharePoint Services developer portal on MSDN. Each Resource Center page focuses on a specific area of developer functionality in SharePoint, and contains links to information and resources from around the web, be it any source (Microsoft sites, sure, but also partners, consultants, MVPs, or anyone else who’s got useful information to share) and in any format (technical articles, blogs, Web casts, video downloads, you name it). Think of them as one-stop-shops for developer information around specific areas of interest.

    Here are the five areas for which we added Resource Center pages:

    ·         Administration 

    ·         Pages and User Interface 

    ·         Provisioning 

    ·         Security 

    ·         Web Parts 

    These are in addition to the Resource Center pages already up on the Office SharePoint Server developer portal:

    ·         Business Data Catalog 

    ·         Enterprise Content Management 

    ·         Enterprise Search 

    ·         Excel Services 

    ·         Web Content Management  

    ·         Workflow 

    In the near future, we plan on adding more Resource Centers for areas such as upgrade and migration, deployment, email and alerts, and content management.

    So what would you like to see in our Resource Centers?

    If you’ve know of resources you think should be added to our existing Resource Center pages, or have areas you think warrant Resource Centers of their own, let us know! Just add a comment to this post, or email the blog directly. We’d love to hear from you.

  • Microsoft SharePoint Developer Documentation Team Blog

    Extending Workflow Actions for SharePoint Designer

    • 10 Comments

    Extending Workflow Actions for SharePoint Designer

    Hi there.  My name is Rodney Farris and I’m one of the newest Programming Writers on the Windows SharePoint Services SDK team.  I’ve been assigned to cover SharePoint Workflows as well as the developer aspects of SharePoint Designer 2007.

    For the last 8 years I’ve been with Microsoft supporting customers, 4 of those being with the SharePoint Developer Support team.  I have a good deal of experience talking to customers and dealing with the problems that they encountered in developing SharePoint applications.  Hopefully this experience will help me to focus my writing on helping you to find answers to your questions about SharePoint workflows.

    In the coming months I will be posting about the content that’s on the publishing horizon.  The first of which is rounding out the documentation for declarative, code-free, rules-based workflows.  This is a powerful means of developing workflows without needing to know the architecture of workflow or how to code. 

    In my first few months in this new position, I’ve been doing a lot of research on the topic by talking to the product team, customers, and product support as well as reviewing existing documentation and reading blog posts.  I’ve seen a lot of good information written about extending the list of workflow activities provided by Windows SharePoint Services 3.0, but I’ve also seen just as many questions.

    A lot of those questions are around how to extend the list of Actions, or workflow activities, and Conditions for use in SharePoint Designer.  This being the case, my first duty was to research and work with the product team to describe the schema that governs their development. 

    The following is a somewhat abridged version of what you will see in the upcoming SDK update, in that I have removed all of the in-line syntax and examples.  Not to worry, I will be posting those shortly along with a developer’s version of the schema in an XSD format. 

     

    Workflow Actions Schema Overview

    You can build powerful and robust workflows to automate most of their common business processes by using a declarative, rules-based, code-free workflow editor such as Microsoft Office SharePoint Designer 2007. Windows SharePoint Services provides 23 built-in workflow actions and 9 workflow conditions that can be incorporated in these workflows.

    However, sometimes it is necessary to build workflows around very complex and unique business requirements that cannot be accommodated by the default lists provided by Windows SharePoint Services.

    In order to allow a code-free editor to work with more complex business logic, you must create customized workflow libraries and deploy them to the server that is running Windows SharePoint Services. After you have deployed your customized workflow objects, the new actions and conditions will be visible to the workflow editor.

    XML schema definition files (XSD) are commonly used to validate XML structure and syntax. However, in the case of Action and Condition elements, the information that is normally contained in an XSD file, and is easily readable, is contained within Windows SharePoint Services internal code.

    Schema Elements
    I placed these in alphabetical order for easier reference, not in order of hierarchy.  I will post the hierarchy the next time.

    Action
    Actions
    Condition
    Conditions
    Default
    FieldBind
    Option
    Parameter
    Parameters
    RuleDesigner
    WorkflowInfo

    Action Element

    Contains the information needed for the workflow engine to process a workflow activity, which is called an action in Windows SharePoint Services 3.0. A workflow Action element represents a workflow activity, such as sending e-mail notifications, updating Windows SharePoint Services 3.0 list items, creating and assigning tasks, as well as many other activities.

    By default, Windows SharePoint Services 3.0 provides 23 built-in workflow actions. These are defined in the WSS.ACTIONS file.

    Attributes

     

    Attribute

    Description

    Name

    Required text. Represents the description of the workflow action that is displayed to the workflow editor.

    ClassName

    Required text. Fully qualified name of the class that implements the workflow action. For example: Microsoft.SharePoint.

    WorkflowActions.EmailActivity.

    Assembly

    Required text. The .NET assembly name that contains instructions to implement the Action element. The text should include the PublicKeyToken, Version and Culture.

    Category

    Optional text. Provides a category for the workflow action. This text is used to filter the list of available actions.

    CreatesTask

    Optional Boolean. If set to true, a task list item is created in the workflow. If left blank, the assumption is false and no task list items are created.

    CreatesInList

    Optional text. If a value is set for this attribute, the workflow creates an item in a list. Values must map to a parameter name that contains the ID of the list or document library.

    AppliesTo

    Required text. Indicates whether this workflow action should be available for lists, document libraries, or both. Valid values include list, doclib, and all.

    ListModeration

    Optional Boolean. If set to true, this Action element applies to a list or document library that has content approval enabled. If left blank, the assumption is false.

    UsesCurrentItem

    Optional Boolean. If set to true, indicates that the current item should be used or modified. If set to false or left blank, this Action element uses only the SharePoint list or document library item that is specified.

     

    Actions Element

    Windows SharePoint Services 3.0 provides a number of default actions to a declarative, code-free workflow editor, such as Microsoft Office SharePoint Designer 2007, that can be used to build workflows that address common business needs. However, complex business rules can sometimes require customized actions. You can use the Actions element to add custom workflow activities and expand the workflow actions available to you beyond those that are included in the default list.

    Attributes

     

    Attribute

    Description

    Parallel

    Required text. If the user creating the workflow indicates that all workflow actions should be executed in parallel, the string defined in this attribute is used to join the Actions elements in the RuleDesigner sentence.

    The default value for this attribute is and (which is defined in the WSS.ACTIONS file) and applies only to the English language version of Windows SharePoint Services . This value cannot be overridden in a custom .ACTIONS file.

    Sequential

    Required text. If the user creating the workflow indicates that all workflow actions should be executed in sequence, the string defined in this attribute is used to join the Actions elements in the RuleDesigner sentence.

    The default value is then (which is defined in the WSS.ACTIONS file) and applies only to the English language version of Windows SharePoint Services .This value cannot be overridden in a custom .ACTIONS file.

     

    Condition Element

    Represents a Condition statement, which is part of a rule sentence that can be displayed in a declarative, rules-based, code-free workflow editor, such as Microsoft Office SharePoint Designer 2007.

    When a workflow is triggered by an event corresponding to a SharePoint list or document library item in Windows SharePoint Services 3.0, it is often necessary to evaluate what workflow action should be taken or if an action is required. A Condition element allows the workflow to perform this evaluation with the values and arguments provided to it by the workflow editor.

    Each Condition element also corresponds to a Boolean method inside a specified Windows SharePoint Services 3.0 workflow library. These methods are used to evaluate values passed by their parameters and return either true or false.

    A Condition element contains information about the .NET assembly where the Condition code is implemented, as well as the parameters that are required to make the function call. It also contains information about how the Condition statement should be displayed to the workflow editor.

    Attributes

     

    Attribute

    Description

    AppliesTo

    Required text. Specifies that the conditional statement being evaluated is applied to a SharePoint list or document library. By changing the value, you can show or hide a specific condition statement in the workflow editor, depending on the type of SharePoint list that the workflow is associated with.

    The following values are not case sensitive.

     

    Value

    Description

    all

    Specifies that a condition statement is available to all list and document library types.

    doclib

    Specifies that a condition statement is visible to the workflow editor only when the workflow is associated with a document library. If the workflow is associated with any other type of list, the condition statement is hidden from the workflow editor.

    list

    Specifies that a condition statement is visible to the workflow editor only when the workflow is associated with a SharePoint list. If the workflow is associated with any type other than a list type, the condition statement is hidden from the workflow editor.

    none

    Specifies that a condition statement is hidden from the workflow editor.

     

     

    Assembly

    Required text. Specifies the .NET assembly that contains the implementation code for the Condition element.

     

    Value Type

    Description

    String

    Specifies the .NET assembly that contains the workflow code. The format should be as follows:

    Assembly name, Version, Culture, PublicKeyToken

    Example:
    Assembly="Microsoft.SharePoint.
                                 WorkflowActions,
                            Version=12.0.0.0,
                            Culture=neutral,
                            PublicKeyToken=  
                                71e9bce111e9429c"

    ClassName

    Required text. Contains the fully qualified class name in which the Condition element code is implemented.

     

    Value Type

    Description

    String

    Fully qualified class name in which the custom Condition element code is implemented. Example:

    XML:
    ClassName=
    "Microsoft.
                               SharePoint.                           
                               WorkflowActions.
                               Helper
    "

    FunctionName

    Required text. Name of the Boolean method in the class that implements the Condition code.

     

    Value Type

    Description

    String

    Represents the method name in the class in which the Condition element code is implemented.

    Example method:

    Bool myCondition(WorkflowContext context, string ListGUIDorName, int ItemWorkflowAttachedTo)

    XML:

    FunctionName="myCondition"

    Type

    Optional text. Specifies whether the Condition element is Custom or Advanced.

    The following values are not case sensitive.

     

    Value

    Description

    Custom

    Used to compare a value found in the current SharePoint list or document library item to a value specified by the workflow designer.

    Advanced

    Used to indicate that a Condition can be used to compare two values of any type (for example, text, integers, and dates).

    UsesCurrentItem

    Optional Boolean. Specifies that the item currently selected is associated with the workflow.

     

    Value Type

    Description

    Boolean

    If set to true, the workflow binds to the SharePoint list item or document library item that started the workflow instance. When using a declarative, code-free workflow editor, this value always returns true and cannot be changed.

     

    Conditions Element

    Conditions are used by declarative, rules-based code-free workflow editors, such as Microsoft Office SharePoint Designer 2007, to build workflows. Conditions are simply functions, in code, that return a Boolean value when called by Windows SharePoint Services 3.0.

    When using a code-free workflow editor to develop workflows, conditions are presented to the workflow designer in the form of a list of phrases. Each of the conditions in this list has a corresponding function in code that is used to evaluate values provided either by the user or by Windows SharePoint Services 3.0.

    The Conditions element is the parent element for all Condition elements.

    Note   The attributes listed below are only read from the default WSS.ACTIONS file and cannot be overridden in any custom .ACTIONS files.

    Attributes

     

    Attribute

    Description

    And

    Required text. The text defined in this attribute is displayed in the rule designer sentence when two or more conditions are used in the same conditional branch, and when all conditions must be satisfied before workflow actions can execute. The value is not case sensitive.

    The default value is and (applies only to the English language version of Windows SharePoint Services 3.0.

    Example:

    <Conditions And="and">

    Else

    Required text. The text defined in this attribute is displayed in the rule designer sentence when a conditional branch activity is added to the workflow. The value is not case sensitive.

    The default value is Else if (applies only to the English language version of Windows SharePoint Services 3.0).

    Example:

    <Conditions Else="Else if">

    Not

    Required text. The text defined in this attribute is displayed in the rule designer sentence when the condition must not contain a specified value or range of values. This value is not case sensitive.

    The default value is Not.

    Example:

    <Conditions Not="not">

    Or

    Required text. The text defined in this attribute is displayed in the rule designer sentence when there are two or more conditions in the same conditional branch and any value will satisfy the conditions, allowing the workflow actions to execute. The value is not case sensitive.

    The default value is or (applies only to the English language version of Windows SharePoint Services 3.0).

    Example:

    <Conditions Or="or">

    When

    Required text. The text defined in this attribute is displayed in the rule designer sentence when a conditional branch is added that requires the values or conditions that follow it to return true in order for the workflow actions to execute. The value is not case sensitive.

    The default value is If (applies only to the English language version of Windows SharePoint Services 3.0).

    Example:

    <Conditions When="If">

     

     

    Remarks

    Each Conditions element can occur only once in an .ACTIONS file.

    Default Element

    The Default element is a container for other elements and has no definable attributes.

    Note   The Default element is only read from the default WSS.ACTIONS file and cannot be overridden with a custom .ACTIONS file.

    Attributes

     

    None

     

    Remarks

    When you create workflows using a declarative, code-free workflow editor, such as Microsoft Office SharePoint Designer 2007, the .ACTIONS file that is installed on the server is combined into a single list of items and displayed to the workflow editor. Windows SharePoint Services 3.0 then searches for existing workflow conditions. If Windows SharePoint Services 3.0 finds a condition that is not represented by an entry in an .ACTIONS file, the Default element sentence is displayed for that condition.

    FieldBind Element

    The FieldBind element is a child of the RuleDesigner element. These elements are used together to create a readable sentence that describes a condition that needs to be evaluated or an activity that must be executed. When constructed properly, these elements can also be used to insert variables (such as hyperlinks) within the sentence, so that the code-free workflow editor can substitute dynamic values into the workflow while it is running. The FieldBind element maps the inputs from the workflow creator to parameters that are then passed to Windows SharePoint Services 3.0.

    Attributes

     

    Attribute

    Description

    DesignerType

    Required text. Specifies the type of control or user input that is presented to the workflow creator when building sentences in the workflow editor.

    Note   If you do not specify a DesignerType, the default DesignerType attribute is used. The default DesignerType is a text box followed by an ellipses button and a lookup button.

    Note   A code-free workflow editor should treat the values returned to it from the server as case-insensitive.

    Field

    Required text. Represents a Parameter element used to build workflows. The Field attribute maps directly to one or more Parameter elements when a parameter type and direction are defined.

    Note   If you use more than one parameter for a Field attribute, the parameter names should be separated by commas (for example, Field="Variable,ValueType").

    Function

    Optional Boolean. When set to true, this attribute inserts the name of the Action method into the sentence.

    Id

    Required Integer (non-negative). Id is used as the relational key between a FieldBind element and the Sentence property of the parent RuleDesigner element, much like a primary key is used in a database.

    OperatorTypeFrom

    Required text. Used only when DesignerType attribute is set to Operator. This attribute determines the types of operators that are available to the user, based on the .NET data type listed in the corresponding Parameter element. The parameter specified for the OperatorTypeFrom attribute can be different from the parameter listed in the Field attribute.

    Text

    Required text. Text displayed to the user as a hyperlink in the condition sentence.

    TypeFrom

    Optional text. Specifies the .NET data types that are valid for use with an instance of the FieldBind element. The TypeFrom attribute is associated with a Parameter element that contains the type definition.

    Value

    Reserved for future use.

     

    DesignerType Attribute

     

    Value

    Data Bound

    Default Builder

    Show Drop-down

    Adv-
    anced Builder

    Control Description

    Boolean

    Yes

    No

    Yes

    No

    Drop-down list box with the choices true and false populated.

    ChooseDoclibItem

    No

    No

    No

    Yes

    Document library item selector.

    ChooseListItem

    No

    No

    No

    Yes

    Default.

    CreateListItem

    No

    No

    No

    Yes

    Default.

    Date

    Yes

    Yes

    No

    No

    Date/Time selector.

    Dropdown

    No

    No

    Yes

    No

    Drop-down list box control. Static items can be populated by adding Option elements.

    Email

    No

    No

    No

    Yes

    E-mail advanced control. The form displays standard e-mail fields including To, From, CC, Subject and Body.

    FieldNames

    No

    No

    Yes

    No

    Drop-down list box control populated with all field names in the current list or document library.

    Float

    Yes

    No

    No

    No

    Text box. Allows entry of floating point values.

    Hyperlink

    Yes

    Yes

    No

    No

    URL browser. Select local or remote resources using a standard link builder.

    Integer

    Yes

    No

    No

    No

    Text box. Accepts non-negative integer values.

    ListNames

    No

    No

    Yes

    No

    Drop-down list box control populated with all lists in the current Web site.

    Operator

    No

    No

    Yes

    No

    Drop-down list box control that includes operators used to evaluate each side of the RuleDesigner sentence. Operators are static and must be added in Options elements.

    ParameterNames

    No

    No

    Yes

    No

    Drop-down list box populated with all local variables that have been entered for use by the workflow.

    Person

    Yes

    Yes

    No

    No

    Person or Group selector. You can choose only one person or group from either built-in, local users or groups or users and groups from a domain.

    SinglePerson

    Yes

    Yes

    No

    No

    Person or Group selector. You choose only one person or group from either built-in, local users or groups or users and groups from a domain.

    Stringbuilder

    No

    No

    No

    Yes

    Inline text box editor. Use to create simple strings.

    Survey

    No

    No

    No

    Yes

    Default.

    Text

    Yes

    No

    No

    No

    Default.

    TextArea

    Yes

    Yes

    No

    No

    Default.

    UpdateListItem

    No

    No

    No

    Yes

    Default.

    writablefieldNames

    No

    No

    Yes

    No

    Drop-down list box populated either with a list of fields in the current list or a list of document libraries that are editable. All other fields are hidden.

     

    Properties of DesignerTypes

    Property

    Description

    Data Bound

    Control that can be data bound to a SharePoint list or document library item.

    Default Builder

    Displays a button with ellipses. Depending on the DesignerType, this property opens either a Date/Time or Text Editor dialog box.

    Show Dropdown

    Displays a control for a drop-down list box. Depending on the DesignerType, the values may be populated. You can add Options elements to any Show DropDown controls that are not already populated.

    Advanced Builder

    Displays advanced builders based on the DesignerType. Advanced DesignerTypes can have multiple properties. For example, the e-mail advanced builder allows entry of To, From, CC, Subject and Body fields.

     

    Option Element

    Used to populate DesignerType drop-down list box controls that are not data bound. Option elements contain text and value pairs that can be used to build a workflow sentence. They also contain information about their .NET data types.

    Attributes

     

    Attribute

    Description

    Name

    String. The value displayed in the drop-down list box control.

    TypeFilter

    String. Used only if the parent FieldBind DesignerType is Operator. The TypeFilter attribute allows options to be hidden or displayed in the workflow editor, based on the data type of the parent element.

    You can define multiple types for the TypeFilter attribute but they must be separated by commas.

    UnaryHides

    String. Used only if the parent FieldBind RuleDesigner type is Operator. The value specified in this attribute should be synchronized with the Field attribute of a FieldBind element. If this option is selected, then the FieldBind specified here will be hidden from the workflow editor.

    Value

    String. Represents the value of the selected drop-down list item.

     

    Value Attribute

    The following table contains attribute values that are used with a TypeFilter attribute of Operator that performs conditional comparisons. Custom values can be substituted.

     

    Value

    Description

    Equal

    Returns true if queried values are equal. Case sensitivity must match.

    EqualNoCase

    Returns true if queried values are equal. Case sensitivity does not need to match.

    NotEqual

    Returns true if queried values are not equal. Case sensitivity must match.

    NotEqualNoCase

    Returns true if queried values are not equal. Case sensitivity does not need to match.

    StartsWith

    Returns true if queried values start with a specific pattern.

    NotStartsWith

    Returns true if queried values do not start with a specific pattern.

    EndsWith

    Returns true if queried values end with a specific pattern.

    NotEndsWith

    Returns true if queried values do not end with a specific pattern.

    Contains

    Returns true if queried values contain the specified pattern.

    NotContains

    Returns true if queried values do not contain the specified pattern.

    ContainsNoCase

    Returns true if queried values contain the specified pattern.

    Matches

    Returns true if queried values match a specified regular expression.

    IsEmpty

    Specifies empty string.

    Parameter Element

    Used to describe the input and output parameters for a custom Actions or Conditions method call.

    Attributes

     

    Attribute

    Description

    Type

    Required String. Partially qualified .NET data type. Values are not case sensitive.

    Example:

    System.Object, mscorlib

    Direction

    Optional text. Specifies an input or output parameter. Valid values are In and Out. Values are not case sensitive.

    Name

    Required text. Used to associate the FieldBind element with the parameter. Values are not case sensitive.

    InitialValue

    Optional text. Used to specify the default initial value that is passed to the parameter. Values are not case sensitive.

     

    Parameters Element

    Container for all Parameter elements and contains no definable attributes. Includes the descriptions of the parameters in a condition or action method signature.

    The Parameters element is a complex element type and can be used with both Actions and Conditions elements to define their parameters.

    Attributes

     

    None

     

     

    RuleDesigner Element

    Complex type element. The RuleDesigner element contains information needed to render a workflow sentence in a declarative, code-free workflow editor such as Microsoft Office SharePoint Designer 2007.

    Attributes

     

    Attribute

    Description

    Sentence

    Required text. Text displayed in the workflow editor that represents the workflow rule. The rule sentence can contain static text as well as text dynamically generated at run time.

    Variables can be embedded into the sentence using the notation %1, %2, and so on. Each variable maps to a FieldBind element Id. Then, during workflow design, the text displayed for these variables is the Text attribute of the FieldBind element.

     

    WorkflowInfo Element

    WorkflowInfo is the root element of the Actions schema. This element must be included in any .ACTIONS file that is installed on the server.

    Attributes

     

    Attribute

    Description

    Language

    Refers to the language of the server, not the client. This is notated as a language/culture pair. For example "en-us" is used to specify "English-United States" (See Locale Identifier Constants and Strings).

     

     

  • Microsoft SharePoint Developer Documentation Team Blog

    Welcome

    • 2 Comments

    Welcome to the official blog of the SharePoint Products and Technologies developer documentation team. My name is Andrew May; I’m a Content Publishing Manager for Windows SharePoint Services. My team produces the Windows SharePoint Services SDK, along with the developer help for SharePoint Designer. We also manage the Windows SharePoint Services and SharePoint Designer developer portals. My cohort, Randall Isenhour, manages the team responsible for the Office SharePoint Server and Project SDKs, along with the Office SharePoint Server and Project developer portals.

    We created this blog in order to be able to:

    ·         Present “draft” versions of SDK content before it’s available in the SDKs themselves. These drafts are tech-reviewed content that will end up in the SDKS; this is just our way of getting the information out to you as quick as we can.

    ·         Promote content that we publish on MSDN. This includes giving you the latest news on new technical articles, SDK updates, code samples or tools downloads, and anything else new and noteworthy we think you’ll want to take a look at.

    ·         Blog about the developer documentation we produce, and why. Ever wonder why we document what we do, in the way we do? In future posts we’ll explore the decisions we make when planning SharePoint developer documentation, and the data we base those decisions on.

    ·         Engage with users, and find out just how developers are using (or would like to use) the documentation we produce.

    ·         Experiment with presenting developer documentation in new and different formats.

    So stay tuned. Over the next few days we plan introduce some of the writer on our teams, and to give you a first look at some documentation that’ll be included in the next updates of the WSS and MOSS SDKs.

    And if you’ve got anything you want to talk about regarding SharePoint developer documentation, post a comment or drop us an email. We’d love to hear from you.

Page 2 of 2 (47 items) 12