Monday, December 26, 2011

Purge Workflow history list in Sharepoint

If your workflow history have large amount of data (say in millions), then purging it would be a challenge. If you try to delete the list in UI, you might get "Time out error". If you try to delete the list through Sharepoint designer or through stsadm cmdlet , it might run for hours. 
The best approach to purge the data is deleting them chunk by chunk.
You have to point the running workflow to a new history list, then we can delete the old the history list completely.
In the below sample I'm using processbatchdata for faster deletion. The below code will delete 2000 items in 3 iteration. You can increase the iteration at your will. Also you need to modify the startID accordingly after each execution.

  
private static int iteration = 3;
        private static int Count = 2000;
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                Console.WriteLine("first argument: No of iteration");
                Console.WriteLine("YourprgramName.exe 5");
                Console.WriteLine("******Program terminated : Argument missing! Please give one argument ****");
             
            }
            else
            {
                try
                {
                    iteration = Convert.ToInt32(args[0]);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error : Failed to convert arguments: {0}", ex.Message);
                    
                    return; // exit
                }
                Console.WriteLine("Current System Time (Start) : " + DateTime.Now.ToString());
                try
                {
                    for (int iterate = 0; iterate < iteration; iterate++)
                    {
                        int start =1;
                        Console.WriteLine("Start Index: "+ start.ToString());
                        using (SPSite site = new SPSite("http://siteurl"))
                        {
                            using (SPWeb web = site.OpenWeb())
                            {
                                SPList wflist = web.Lists["Workflow History"]; //workflow history
                                string wflistID = wflist.ID.ToString();
                                Console.WriteLine("No of items before deletion: " + wflist.ItemCount);
                                Console.WriteLine("Building query... ");
                                StringBuilder batchString = new StringBuilder();
                               batchString.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch OnError=\"Continue\">");
                                int end = start + Count - 1;
                                for (int i = start; i <= end; i++)
                                {
                                    batchString.Append("<Method>");
                                    batchString.Append("<SetList Scope=\"Request\">" + wflistID + "</SetList>");
                                    batchString.Append("<SetVar Name=\"ID\">" + Convert.ToString(i) + "</SetVar>");
                                    batchString.Append("<SetVar Name=\"Cmd\">Delete</SetVar>");
                                    batchString.Append("</Method>");
                                }
                                batchString.Append("</Batch>");
                                //Console.WriteLine(batchString.ToString());
                                try
                                {
                                    web.AllowUnsafeUpdates = true;
                                    Console.WriteLine("Executing query... ");
                                    Console.WriteLine("Batch Execution (Start) : " + DateTime.Now.ToString());
                                    string result = web.ProcessBatchData(batchString.ToString());
                                    //Console.WriteLine(result);
                                    web.AllowUnsafeUpdates = false;
                                    Console.WriteLine("Batch Execution (END) : " + DateTime.Now.ToString());
                                }
                                catch (Exception ex)
                                {
                                    Console.WriteLine("Process batch error : " + ex.Message);
                                }
                                Console.WriteLine("No of items before deletion: " + wflist.ItemCount);
                                using (SPSite site1 = new SPSite("http://siteurl/"))
                                {
                                    using (SPWeb web1 = site1.OpenWeb())
                                    {
                                        Console.WriteLine("No of item after deletion: " + web1.Lists["Workflow History"].ItemCount);
                                    }
                                }
                                Console.WriteLine("--------------------------------------------------");
                            }
                        }
                    }
                    Console.WriteLine("Current System Time (End) : " + DateTime.Now.ToString());
                    Console.WriteLine("-----------Program Completed------------");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error : " + ex.Message);
                    Console.WriteLine("******Program terminated due to error **** ");
                }
            }
        }
       
          

Workflow Auto clean job property in Sharepoint

You can check Workflow Auto clean job property by using the following stsadm command 
stsadm –o setproperty –pn job-workflow-autoclean –pv “daily at 6:34:00” –url http://siteurl

Delete workflow history items that has no associations in Sharepoint

Whenever the Out of the box timer job "workflow auto clean up" jobs runs, it will break the associations between the list items and workflow history items for those records that are older than 60 days (default is 60 days. this default value can be chagned). This job will not clear the history data, it just removes the association. Now it is up to us either to keep the history details or remove them. 

For those who want to remove those records can use the below sample code. We have used processbatch data method to delete items which will have better performance.

string WorkflowAssocID="";
            try
            {
                using (SPSite site = new SPSite("http://SiteURL/"))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        SPList list = web.Lists["List_Name"];//Name of the list where Workflow is associated
                        SPQuery qry = new SPQuery();
                        qry.ViewAttributes = "Scope=\"Recursive\"";
                        SPListItemCollection lstCol = list.GetItems(qry);
                        SPWorkflowAssociationCollection WFCol = list.WorkflowAssociations;
                        foreach (SPWorkflowAssociation wa in list.WorkflowAssociations)
                        {
                            if (wa.Name == "Workflow Name")//name of the Workflow
                                WorkflowAssocID = wa.ParentAssociationId.ToString();
                        }
                       
                        List<string> ootbLib = new List<string>();
                        foreach (SPListItem item in lstCol)
                        {
                            if (item.Workflows.Count == 0)
                            {
                                ootbLib.Add(item.ID.ToString());
                            }
                        }
                        SPList wflist = web.Lists["Workflow History"];//History List Name
                        string wflistID = wflist.ID.ToString();
                        SPQuery query = new SPQuery();
                        query.ViewAttributes = "Scope=\"Recursive\"";
                        SPListItemCollection wflstCol = wflist.GetItems(query);
                       
                        StringBuilder batchString = new StringBuilder();
                        batchString.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch OnError=\"Continue\">");
                        for (int i = 0; i < wflstCol.Count; i++)
                        {
                            if (ootbLib.Exists(delegate(string p) { return p.Trim() == wflstCol[i]["Primary Item ID"].ToString(); }))
                            {
                                if (wflstCol[i]["Workflow Association ID"].ToString() == "{" + WorkflowAssocID + "}")
                                {
                                   
                                    batchString.Append("<Method>");
                                    batchString.Append("<SetList Scope=\"Request\">" + wflistID + "</SetList>");
                                    batchString.Append("<SetVar Name=\"ID\">" + Convert.ToString(wflstCol[i]["ID"]) + "</SetVar>");
                                    batchString.Append("<SetVar Name=\"Cmd\">Delete</SetVar>");
                                    batchString.Append("</Method>");
                                }
                            }
                        }
                        batchString.Append("</Batch>");
                        web.AllowUnsafeUpdates = true;
                        string result = web.ProcessBatchData(batchString.ToString());
                        web.AllowUnsafeUpdates = false;
                    }
                }
               
            }
            catch (Exception ex)
            {
                //Some logging can be done
            }