Friday 2 May 2014

Get Web Part Usage and List Instances [A PowerShell Journey continued]

In my last post I dealt with listing all the features associated with deployed solutions to the farm, and then showing where they were activated. Armed with that information and what I found in the manifest for each feature, I now have what I need to attempt locating all the pesky instances of these custom web parts and lists.

You may be wondering why I'm going to all this trouble. Well, in the absence of source code, these sites will not upgrade. So my intention is to identify all the customisations so that I can either remove (or rebuild) them safely, before deactivating the features they belong to. And also, I just LOVE tinkering with PowerShell. :D

Get WebPart Usage

I wrote this as a function to make it more re-usable. Pass in the webapp URL and webpart name, and it will go through every ASPX document - Publishing, Wiki and Web Part page - and return the page URL, webpart DisplayTitle and webpart Name for each found. The reason I return the name is that I used the -like comparison operator to allow for loose (wildcard) searching. This is extremely handy when dealing with multiple webparts using the same canonical naming syntax.

I went down the webfolder/files collection path, as opposed to hitting the list/item collection, because I needed to recurse through the Forms subfolders to find any list form templates that had been customised.

The function first runs through every folder and subfolder and adds the (ASPX) files to an array called $AllPages. It then proceeds to get the SPLimitedWebPartManager for every file and interrogates the SPLimitedWebPartCollection for a matching webpart name.

The output proved invaluable to me as it showed that not only were some webparts present in customized list forms, but also in master and layout pages. I see a re-branding project in my near future. :\

Get All Lists by TemplateID

Next I wanted to get a list of all the web templates for my root sites. As I already knew where the list template features were activated, and at what scope, I didn't need to iterate through every webapp, site and web again.

The code below grabs the list templates for each root web in the $webs array that I'm interested in. It then outputs a GridView for each web's SPListTemplateCollection.

The grid is sorted by template ID (Type_Client). As you may know, best practice dictates that any custom list template have an ID of 10000 or higher, so this make identifying all custom templates a simple matter. You could even specify a Where clause to return just those templates but I find this list to be a handy reference anyway.

I absolutely love GridViews. In fact I could write several articles on them alone. Few people realise their power. Built-in column sorting and filtering, and selective row selection, can be passed through to the next pipeline object using the new -PassThru function in PowerShell 3. The GridView actually performs all those complex and fidgety sorting and filtering tasks on your behalf. If your next pipeline object is a CSV file then this essentially means you can re-use the same grid to generate custom reports without having to alter your code. [end rant]

So now I've got my list template IDs I want to locate every list instance that's been created using that template in the target webapp(s). The script itself is pretty straightforward. It prompts for a webapp URL and template ID and again it iterates through every site and web.

It took my a little while to discover that the list template ID was stored in List -> Rootfolder -> Properties in a property named vti_listservertemplate. But once I had that the rest was easy.

The output simply provides the list title and URL for each match.

I've modified the code so that both scripts can be both be run consecutively. In this way you can reference your list template ID and then add it at the prompt.

Wrap Up

I hope these last two posts help someone else trying to achieve the same or similar goals. I was fortunate that I only had to deal with list templates and web parts. I can only imagine the headache if custom field types, event receivers and other elements had been included. In my next post I'll likely be detailing the steps taken to eradicate all the deployed customisations found so that I can attempt to deactivate the features and retract the solutions. Unless the source code decides to suddenly materialise...

No comments: