Remove Old Versions of Items in the Sitecore ASP.NET CMS

The Best way to delete old versions of an item (SC531) and Delete all versions of a particular language in a multi-site solution threads on the Sitecore Developer Network (SDN) forums prompted me to write this blog post that describes how you can implement a scheduled process to automatically delete excessive versions of items with the Sitecore ASP.NET CMS. For more information about scheduled and processes, see my blog post All About Sitecore Scheduling Agents and Tasks.

To configure a scheduled process to periodically delete old versions:

  1. Take a backup of the dataase. I tested this only in the most limitted manner.
  2. In Visual Studio, implement a solution based on the the prototype code.
  3. Add the /App_Config/Include/TrimVersions.config file to the solution. For more information about web.config include files, see my blog post All About web config Include Files with the Sitecore ASPNET CMS.
  4. Configure the type attribute and the Database, MaxVersions and RootItem parameters as required. There are numerous alternative approaches to determining which versions to delete, such as only those older than a given time span. For more information about passing parameters to an agent, see my blog post The Sitecore ASPNET CMS Configuration Factory.

Readers of this blog post may also find value in the VersionManager Sitecore Shared Source project.

  • Am I correct that the shell UI option "remove all versions" will remove the versions but still retain the item itself?  I'm facing a problem that the Sitecore shell freezes any time the home page item is accessed.  It shows 562 versions and my hope is that by removing the versions altogether I might solve the freezing issue, but I want to be sure first that removing versions will *not* affect the current home page item.

  • @David: That UI command will remove all versions, including the latest version. I would either use the process available in this post or script something.

  • Hi John! During one of your presentations at the symposium, I could swear I heard you say something about working on a better tool for item version house keeping. Was this it or were you planning on expanding the VersionManager SharedSource module you mention here? Or perhaps were you working on something new? Thanks!

  • @Paul: I was going to modify this agent to archive or recycle old versions instead of deleting them. But I ran into a bug that comes up when you add a version through the UI and archive/recycle a version in an OnItemSaved rule such as the one I used to trigger the version archive/recycle logic, and I'm waiting for a resolution for that before posting. Would you rather see something implemented in the version manager shared source thing, or this agent?

  • I prefer your background-task version-archive solution... but I'd like to add the "OlderThan" parameter as well so that versions would be deleted only if they haven't been modified within the "OlderThan" timespan.   We are currently on 6.5 though, and i noticed that version archiving doesn't seem to be available until 6.6. Although it seems like if I just grab the appropriate archive and pass it the itemversion i want to delete in code it would work...         var archive = ArchiveManager.GetArchive("archive", myitemversion.Database);        if (archive != null)             return archive.ArchiveItem(myitemversion);  Also, I assume the OnItemSaved bug is part of a "real-time" archiving solution? For me, this would be a non-issue since I'd rather just have a scheduled task run once a day or so to remove "excess" versions. If you already have something like that working, I would TOTALLY love to steal the code :)

  • @Paul: I think archiving and recycling versions requires 6.6. I think if you try to archive a specific version in 6.5, it will actually archive the entire item.  You are right that the bug I hit should only affect real-time archiving (I used an OnItemSaved rule), and then only when creating a version (which triggers at least one save) and deleting older versions from that rule (or possibly when deleting from other similar event handlers that trigger on version creation).  Would your requirement be: 1. Delete versions older than the specified number of days regardless of the number of versions, 2. Delete versions in excess of some number of versions (AND) that are older than some number of days, or 3. Delete versions except the current version if they are older than some number of days OR if there are more than X versions? I mean, does only the age matter, the number of versions AND the age, or the age OR the number of versions, if that makes sense?  In case it helps at all, this is the (untested) code that performend the archiving:

  • namespace Sitecore.Sharedsource.Mvc.Rules.Actions {   using SC = Sitecore;    public class ArchiveOldVersions<T> :     SC.Rules.Actions.RuleAction<T>     where T : SC.Rules.RuleContext   {     public int KeepMaxVersions { get; set; }      public override void Apply(T ruleContext)     {       SC.Diagnostics.Assert.ArgumentNotNull(ruleContext, "ruleContext");       SC.Diagnostics.Assert.IsNotNull(ruleContext.Item, "ruleContext.Item");       SC.Diagnostics.Assert.IsTrue(this.KeepMaxVersions > 0, "KeepMaxVersions");       SC.Data.Archiving.Archive archive = SC.Data.Archiving.ArchiveManager.GetArchive(         "archive",         ruleContext.Item.Database);       SC.Diagnostics.Assert.IsNotNull(archive, "archive");        foreach (SC.Globalization.Language language         in ruleContext.Item.Database.Languages)       {         SC.Data.Items.Item item = ruleContext.Item.Database.GetItem(           ruleContext.Item.ID,           language);          while (item.Versions.GetVersionNumbers().Length > this.KeepMaxVersions)         {           SC.Data.Items.Item version = item.Database.GetItem(             item.ID,             language,             item.Versions.GetVersionNumbers()[0]);           SC.Diagnostics.Assert.IsNotNull(version, "version");            // don't delete the active version           if (version.Language == item.Language              && version.Version.Number == item.Version.Number)           {             SC.Diagnostics.Log.Warn(               this + " : unable to archive active version " + version.Language.Name + " version "               + version.Version.Number + " and subsequent versions of " + version.Paths.FullPath,               this);             break;           }

  •          // to recycle instead of archive: version.RecycleVersion()?           SC.Diagnostics.Log.Audit(             new object(),             "Archive version: {0}",             new string[] { SC.Diagnostics.AuditFormatter.FormatItem(version) });           archive.ArchiveVersion(version);         }       }     }   } }

  • Regarding my "requirements".. #2 is what i was after. Another thing to consider is workflow. For instance if MaxVersions=10 and my item had 20 versions (1 through 20). Then imagine that the last workflow approved (and thus published) version was #9. In that case, I shouldn't count versions 10-20 in my version total count that we compare with MaxVersions.  Also, just to confirm, the code above would only work for 6.6 right?

  • So it seems like until 6.6 is released/recommended and we upgrade my best bet is to go with a variant of the VersionManager module. I've got it working in DEV. It follows the rules I mentioned above but instead of archiving/recycling it serializes (taken from VersionManager) the item that will have versions deleted.  It would have been really nice to try and archive them instead though. I even went so far as to download the 6.6 preview and compare the SqlArchive classes to 6.5's.. it would require too many changes though.   However, this got me thinking.. what if, in addition to the item serialization approach I'm using, I "directly" inserted the versions I'm going to remove into the archive tables (I'm sure i could steal this code from the 6.6 SqlArchive class)? I could even insert them using a different [Archive Name] like "VersionMainenance" so as not to conflict with the Archive or RecycleBin apps. Then when we finally get 6.6 running I would either create a new Archive app (since they seem to just take a parameter of ArchiveName) or run an update query to change those "VersionMaintenance" records to "archive".  Thoughts?

  • Hi Paul,  I think a scheduled agent actually is more appropriate for this solution, but I've been trying to do everything with rules lately, so maybe I was going the wrong direction. I don't think VersionManager will help you with the issue of archiving or recycling individual versions (I think your only option in 6.5 would be to permenantly delete those versions), but I am not familiar with that component (does it provide the serialization, or did you have to write that). So I don't see the advantage of VersionManager over a scheduled task, but of course if you have it working, go with that. Either way, you can update it when you get 6.6 going.   I always go through supported APIs and try not to mess with the SQL or even tables. That doesn't mean necessarily that your approach wouldn't work (in fact it sounds like it would), I just wouldn't try it, especially if the serialization approach is sufficient for now. But if you do try it, please let me know if it works.  Regards,     -John

  • Ah.. I see now that the first paragraph of my last comment was misleading. I meant to say that I combined serialization code from the VersionManager module with your code to make it run as a scheduled task.  I like the idea of using sitecore rules. I haven't tried them at all but it seems like it would be a slick solution. Could you still use the rules engine from a scheduled task instead?  I did end up getting the "direct" database version archiving to work on 6.5 with stolen api SQL from 6.6. I agree that its a little too much of a hack to put into production. It did work though! Even got the Archive Tool to point to my custom archive name. Ofcourse, I shudder to think what clicking the "Restore" command in that tool would do.. probably overwrite the entire item with the versions from the archive.

  • Yes, you can invoke the rules engine from a scheduled agent. It doesn't take much work or code:  You can also invoke a scheduled agent through the UI (this lets you keep all the configuration in one place but apply it automatically and/or interactively), but that does take a bit more work:  This is really why I like the rules engine so much for admin tasks - I can define an OnItemSaved rule to run whenever a user updates an item, use a scheduled agent to catch anything they haven't saved, and run that agent interactively for testing.

  • Hi guys, I'm new in sitecore, I'm trying to delete versions and shows me the following error :(  Sitecore.Exceptions.AccessDeniedException: DeleteItem - Delete right required  Could somebody help me?, I will apreciate a lot

  • @Majahide:   You need to log in as a user with access rights that allow that operation. I don't think there is a version deletion right, so I think it must be the item deletion right that you need. You can either log in as a Sitecore administrator, or have someone who is an administrator log in and grant you (or one of the groups that you are in) that right.   You can use the Access Viewer application in the desktop to check whether a user has a specific access right.