Which files were deployed by a feature
October 18, 2011
Inspired by an apparent simple question from SharePoint StackExchange, decided to write a post about deletion of site pages on feature deactivation and about a way to determine, which files were deployed by the feature.
So the problem is, that on feature deactivation, previously deployed aspx pages aren't removed from the server. That's why, if you want to re-activate such feature, be prepared that all the webparts, which were deployed to these pages, will be duplicated.
As you probably know, this problem never happens, when you deploy through Visual Studio. Visual Studio resolves the conflicts and removes previously deployed pages before activating the feature. You might want to have the same functionality for your feature in production environment, so that administrators are able to re-activate it with no trouble.
But at this point, it is very important to understand, that users generally have an ability to customize pages. Hence if you delete pages, all users' customizations will be lost. Also, if users have created some navigation menus pointed to those files, the menus will be lost too.
You must have very strong reasons to decide deleting site pages in feature deactivating receiver!
And if you do have, the code itself is very simple:
var url = "SitePages/test.aspx";
SPFile file = web.GetFile(url);
// this will delete test.aspx from Site Pages document library
But assuming you don't actually want to maintain the file urls to delete, there is another interesting option here.
The point is to get Feature element definitions, parse them using Linq-to-XML, and generate list of files to delete.
var modules =
(from SPElementDefinition element in
where element.ElementType == "Module"
let xmlns = XNamespace.Get(element.XmlDefinition.NamespaceURI)
let module = XElement.Parse(element.XmlDefinition.OuterXml)
Url = module.Attribute("Url") == null ? String.Empty : module.Attribute("Url").Value,
Path = Path.Combine(element.FeatureDefinition.RootDirectory, module.Attribute("Path") == null ? String.Empty : module.Attribute("Path").Value),
Files = (from file in module.Elements(xmlns.GetName("File"))
Url = file.Attribute("Url") == null ? String.Empty : file.Attribute("Url").Value,
Properties = file.Elements(xmlns.GetName("Property")).ToDictionary(n => n.Attribute("Name").Value, v => v.Attribute("Value").Value)
So you fetched list of modules, with a list of files in each module. Flatten them to files using following two loops:
foreach (var module in modules)
foreach (var file in module.Files)
if (Path.GetExtension(file.Path) == ".aspx")
// do what you need to do with a site page
And so you can perform the deletion inside the inner loop, using following construction to determine the particular file url:
var url = SPUtility.ConcatUrls(SPUtility.ConcatUrls(web.Url, module.Url), file.Url);
Actually, the list of files can be used in some other circumstances, for example, for some upgrade purposes, when you can not delete file itself, but remove some webparts from it, or add missed webparts, etc.