Tracking the usage of controls and Web Parts in SharePoint 2010


When maintaining a SharePoint Farm with multiple sites and solutions, one of the questions that often arises is which versions of which components are used on which sites. Find out how to easily answer this question using PowerShell.

Reusable components

In my previous article I discussed the idea of using shared components across multiple sites to increase the productivity and lower the costs of both the initial development and the maintenance of sites. To lower the test effort involved with introducing new versions of shared component, support for multiple versions of the same Solution Package can be introduced. And although it’s not natively supported by SharePoint it is possible in some scenarios without too much effort.

Tracking the usage of controls and Web Parts

When deploying a new version of a component, one of the things that you have to do in the planning phase is to determine the impact of that upgrade. This is even more important in context of shared components where one component can be used on multiple sites. By finding out which sites they are exactly you can more precisely plan for testing and availability which in result should help you manage the expectations of your customers.

There are many ways and places in which you can make use of controls and Web Parts on the SharePoint platform. In this article I will focus on a scenario that would typically apply to a Publishing Site. Without too much effort you should however be able to extend the code samples to other areas of SharePoint if necessary.

Tracking the usage of controls and Web Part on a Publishing Site

Given the scope of tracking which controls and Web Parts are used where on a Publishing Site we can narrow down the objects that we will query to get the information that we are interested in.

On Publishing Sites controls are used within Master Pages and Page Layouts which are both stored in the Master Page Gallery. By analyzing the contents of all Master Pages and Page Layouts you can find out which controls are being used and which assemblies they belong to. You could optionally narrow the list of Master Pages and Page Layouts only to those that are actually being used on your site, but the chances are that if you narrow the list of controls to custom controls only, only used Master Pages and Page Layouts will be returned.

Web Parts are in general associated with Publishing Pages so that’s the second place that we will be looking in. By retrieving all Publishing Pages from all Publishing Webs we should be able to gather information about all Web Parts that are used within the Publishing Site. In some scenarios Web Part are placed directly on the Page Layout but in that case they will be surfaced in the list of controls that we’ve just discussed.

What controls am I using?

Using the following PowerShell script you can get the list of all controls used in Master Pages and Page Layouts on your site:

param(
    $siteUrl
)

function Get-ControlInformation {
    param (
        $siteUrl
    )
    
    $controlInformation = @()
        
    $site = Get-SPSite $siteUrl
    $rootWeb = $site.RootWeb
    $masterPageGallery = $site.GetCatalog("MasterPageCatalog")
    $masterPageGallery.RootFolder.Files | ? { $_.Name -imatch ".*\.(master|aspx)$" } | % {
        $fileContents = $rootWeb.GetFileAsString($_.Url)
        $controlInformation += Get-Controls $_.Url $fileContents
    }
    
    $controlInformation
}

function Get-ControlPrefixes {
    param(
        $fileContents
    )
    
    $prefixRegex = [regex]"<%[^>]+Register[^>]+>"
    $prefixDetails = [regex]"(?i)(TagPrefix|Namespace|Assembly)=""?'?([^""']+)'?""?"
    
    $prefixes = @(
        New-Object Object |
            Add-Member NoteProperty TagPrefix "asp" -PassThru |
            Add-Member NoteProperty Namespace "System.Web.UI.WebControls" -PassThru |
            Add-Member NoteProperty Assembly "System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" -PassThru
    )
    
    $prefixRegex.Matches($fileContents) | % {
        $prefixInfo = $prefixDetails.Matches($_.Value)
        $tagPrefix = $prefixInfo | ? { $_.Groups[1].Value -ieq "TagPrefix" } | % { $_.Groups[2].Value }
        $namespace = $prefixInfo | ? { $_.Groups[1].Value -ieq "Namespace" } | % { $_.Groups[2].Value }
        $assembly = $prefixInfo | ? { $_.Groups[1].Value -ieq "Assembly" } | % { $_.Groups[2].Value }
        $prefix = New-Object Object |
            Add-Member NoteProperty TagPrefix $tagPrefix -PassThru |
            Add-Member NoteProperty Namespace $namespace -PassThru |
            Add-Member NoteProperty Assembly $assembly -PassThru
        
        if ($assembly -ne $null -and -not($prefixes | ? { $_.TagPrefix -ieq $prefix.TagPrefix -and $_.Namespace -eq $prefix.Namespace -and $_.Assembly -eq $prefix.Assembly } | Select-Object -First 1)) {
            $prefixes += $prefix
        }
    }
    
    $prefixes
}

function Get-Controls {
    param(
        $url,
        $fileContents
    )
    
    $controlRegex = [regex]"<([^/%@:!>\s]+):([^\s>]+)[^>]+>"
    
    $controls = @()
    
    $prefixes = Get-ControlPrefixes $fileContents
    
    $controlRegex.Matches($fileContents) | % {
        $controlPrefix = $_.Groups[1].Value
        $controlName = $_.Groups[2].Value
        $control = New-Object Object |
            Add-Member NoteProperty Url $url -PassThru |
            Add-Member NoteProperty Prefix $controlPrefix -PassThru |
            Add-Member NoteProperty Name $controlName -PassThru |
            Add-Member NoteProperty FullName ($prefixes | ? { $_.TagPrefix -ieq $controlPrefix } | Select-Object -First 1 | % { "$($_.Namespace).$controlName, $($_.Assembly)" }) -PassThru
        
        if ($control.FullName -ne $null -and -not($controls | ? { $_.Prefix -ieq $control.Prefix -and $_.Name -ieq $control.Name })) {
            $controls += $control
        }
    }
    
    $controls    
}

Get-ControlInformation $siteUrl

SharePoint 2010 Management Console with the output of the Get Control Information command

Using the Site Collection URL we get the Site Collection for which we want to retrieve information about used controls (12). Next we retrieve the Master Page Gallery (14) and from there we iterate through all Master Pages and Page Layouts (15). For each file found we first get its contents (16) and then parse it to retrieve the information about which controls are used (17).

Before we start to build the list of controls that are being used we have to get some additional information about prefixes registered within the file. Without this we would just get the control name but we wouldn’t be able to link it back to the assembly where the control’s code is located. We get the information about registered tag prefixes using the Get-ControlPrefixes function (23). Using regular expressions we parse the file to get all Register directives from the page and from there we build the list of control prefixes registered within the file.

Note: At this moment this piece of the script has two limitations. First there is no support for User Controls which are ignored. The reason for this is the fact that you cannot really version them that easily given the fact that they are deployed to the SharePoint Root folder.

Another thing that the script doesn’t support is having the same prefixes pointing to different assemblies and/or namespaces. While perfectly doable in ASP.NET it’s not recognized by the script which in result would produce unreliable results. The only solution would be to use reflection to check from which of assembly/namespace the particular control is originating.

You might have noticed that all information about used controls is returned by the script as anonymous objects. The big advantage of this approach is that you can easily process the list further. For example, if you wanted to get the list of controls used on the ArticleLeft.aspx Page Layout only you could call the script as follows:

.\Get-ControlInformation.ps1 http://mavention | ? { $_.Url -ieq "_catalogs/masterpage/articleleft.aspx" }

Output of the Get Control Information command narrowed to the Article Left Page Layout only

To get the usage information for a specific control you could modify the call as follows:

.\Get-ControlInformation.ps1 http://mavention | ? { $_.FullName -eq "System.Web.UI.WebControls.SiteMapPath, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" } | ft Url

Output of the Get Control Information command narrowed the Site Map Path control only

What Web Parts am I using?

Using the following PowerShell script you can find out which Web Parts are used where on your site:

param(
    $siteUrl
)

function Get-WebPartInformation {
    param(
        $siteUrl
    )
    
    $webPartInformation = @()
    Get-SPSite $siteUrl | Get-SPWeb | % {
        if ([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($_)) {
            $pweb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($_)
            $pweb.GetPublishingPages() | % {
                $page = $_
                $file = $page.ListItem.File
                $wpmgr = $file.GetLimitedWebPartManager("Shared")
                $wpmgr.WebParts | % {
                    $webPartInformation += New-Object Object |
                        Add-Member NoteProperty PageUrl $page.Uri.ToString() -PassThru |
                        Add-Member NoteProperty Title $_.Title -PassThru |
                        Add-Member NoteProperty WebPart $_.GetType().AssemblyQualifiedName -PassThru
                }
                
                $wpmgr.Dispose()
            }
        }
    }
    
    $webPartInformation
}

Get-WebPartInformation $siteUrl

Once again we start off by retrieving the Site Collection (11). From there we retrieve all Sites and we iterate through them (11). For every Site we check if it’s a Publishing Web (12). If it is, we retrieve all Publishing Pages (14) and for every page we get the list of all Web Parts used on that page (18). Similarly to the previous script the results are returned as anonymous object that you can pass down the PowerShell pipeline for further processing.

To get the list of all Web Parts used on all Publishing Pages within your site all you have to do is to execute the following call from the PowerShell console:

.\Get-WebPartInformation.ps1 http://mavention

SharePoint 2010 Management Shell with the output of the Get Web Part Information command

If you are interested only in Web Parts within a certain part of the site only, you could filter the results as follows:

.\Get-WebPartInformation.ps1 http://mavention | ? { $_.PageUrl -imatch "http://mavention/about*" }

Similarly you could scan your website for using a particular Web Part:

.\Get-WebPartInformation.ps1 http://mavention | ? { $_.WebPart -eq "Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" }

Output of the Get Web Part Information command narrowed to the Content Query Web Part only

Summary

When working with shared components on multiple sites in a SharePoint Farm it’s important to know which components are used where on the Farm. This allows you to determine the impact of an upgrade and with that manage expectations of your customers. Using PowerShell you can easily retrieve the information about which controls and Web Parts are used where on your sites.

Others found also helpful: