I had a requirement from customer to develop an archival workflow solution using which they can automate the Site Collection archival based on a set of business rules. The features should be, notify users for the key LOB information expiration based on which the sites were created, Maintain the notification count and based on the count take a call to make a particular site read-only, offline or take a back up & delete it permanently from the content database in case of no action taken to update the key LOB information.

And, just to give you a little idea about the size of the farm, it’s in TBs and there are thousands of site collections created automatically across multiple content databases through an automated process. so to do the above steps manually for each site collection based on different notification counter values and repeat it every week, was just impossible and that’s how we decided for an automatic site archival solution.

The purpose of this blog post is to give you an idea about the various features and parts of the solution so in case you come across any of the similar business requirement in future then can make use of some of these ideas or code snippets and make your life little easierSmile

This solution has several features like

  1. Automatic scanning for all Site Collections with expired Key Information(Based on which Site Collections are created)
  2. Check for the expiration date of Key Information with LOB system(The Origin place of the Key Information)
  3. Find out the Site Owners
  4. Validation of the Site Owners with the directory services
  5. Send configurable email notifications to Site Owners about the Key Information expiration
  6. Keep track of notifications count
  7. Automatically extend the Site Collection’s Key Information details in SharePoint if it’s been updated in LOB system
  8. Make the Site Collection offline based on the notification count in case of no response from Owners
  9. Take the Site Collection Backup, transfer the back up to a shared location for the archival & delete it to reclaim the used space based on the notification count
  10. Provision to take an exception for a Site Collection for the Key Information expiration scan & archival

And following are the configuration features of the solution

  1. Configurable LOB pointer & Workflow Admins list
  2. Configurable Email Templates for notifications
  3. Configurable Execution Timing/Schedule
  4. No IISRESET/ App pool recycle required
  5. Logging of each execution in the event viewers for the Admin review

So here is how I designed these configuration features

For Configurable LOB pointer & Workflow Admins list, I kept txt files with solution which would contain the connection and Administrator details respectively so can be modified anytime.

Created XML for the email templates and would read the specific template from the code using template id.

Built whole application as an exe so can be configured with windows task scheduler.

Since no deployment of WSP to SharePoint and solution is just a stand alone exe, no IISReset/ App Pool Recycle required.

Used Event viewer for writing the execution log.

Now following are a few Code Snippets which are used for the above functionalities.

For txt read

try
{
    const string f = "ConnectionString.txt";
    
    using (StreamReader r = new StreamReader(f))
    {
        connectionString = r.ReadLine();
    }
}
catch (Exception ex)
{
    WritetoEventViewer("Exception(ConnectionString Fileread) : " + ex.Message.ToString());
}

To check user validity with directory services

static bool checkValidUser(string UserID)
{
    bool isValid = false;
    try
    {
        using (var ctx = new PrincipalContext(ContextType.Domain)) 
        {     
            var user = UserPrincipal.FindByIdentity(ctx, UserID);      
            if(user != null)     
            {
                isValid = true;
                user.Dispose();     
            } 
        } 
    }
    catch (Exception ex)
    {
        WritetoEventViewer("Exception(Checking AD User Validation) : " + ex.Message.ToString());
    }
 
    return isValid;
}

To get mail templates from XML

static void GetMailTemplates()
{
    mailTemplates = new string[3, 5];
    try
    {
       int i = -1, j = 0;
 
        XmlTextReader reader = new XmlTextReader("EmailTemplates.xml");
        while (reader.Read())
        {
            if ((reader.NodeType == XmlNodeType.Element) && (reader.Name == "template"))
            {
                i++;
                j = 0;
 
                //to save from array overload
                if (i == 2)
                    break;
            }
            if (reader.NodeType == XmlNodeType.Text)
            {
                mailTemplates[i, j] = reader.Value.ToString().Trim('\n', '\r', ' ');
                j++;
            }
        }
    }
    catch (Exception ex)
    {
        WritetoEventViewer("Exception(Getting Mail Templates) : " + ex.Message.ToString());
    }
}

To make NoAccess to the Site Collection

bool operation = false;
try
{
    using (SPSite site = new SPSite(url))
    {
        site.WriteLocked = true;
        site.LockIssue = "Making site collection NoAccess as per the Archival Workflow";
        site.ReadOnly = true;
        site.ReadLocked = true;
        
        operation = true;
    }
}
catch (Exception ex)
{
    operation = false;
    WritetoEventViewer("Exception(Making SiteCollection Offline) : " + ex.Message.ToString());
}

To backup & move the Site Collection in a shared location

bool operation = false;
try
{
    string bkLocation = @"c:\";
    string bkFileName = DateTime.Now.ToString() + ".bak";
 
    bkFileName = bkFileName.Replace("/", "");
    bkFileName = bkFileName.Replace(":", "-");
                   
    using (SPSite site = new SPSite(url))
    {
        SPWebApplication webApp = site.WebApplication;
        SPSiteCollection siteCol = webApp.Sites;
        siteCol.Backup(url, bkFileName, true);
 
        //Move the file to destination after creation
        File.Move(bkFileName, bkLocation + bkFileName);
 
        operation = true;
    }
}
catch (Exception ex)
{
    operation = false;
    WritetoEventViewer("Exception(SiteCollection Backup) : " + ex.Message.ToString());
}

To delete Site Collection

bool operation = false;
try
{
    using (SPSite site = new SPSite(url))
    {
        //Making site accessible if it is made NoAccess
        site.WriteLocked = false;
        site.ReadOnly = false;
        site.ReadLocked = false;
 
        //Delete the site
        site.Delete();
 
        operation = true;
    }
}
catch (Exception ex)
{
    operation = false;
    WritetoEventViewer("Exception(SiteCollection Deletion) : " + ex.Message.ToString());
}