Tuesday, March 20, 2007

SPS/WSS 2003 : Intelligent backup procedure

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

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

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

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

1 comment:

Anonymous said...

Please let me know how SiteItem.cs
looks like.Like to get the code plz