SharePoint Enterprise features provide users the ability to use advanced capabilities not found in standard SharePoint licensing. Once you have licenses for Enterprise features all you do is enable them on the site collections and users are free to consume them.
Once enabled, however, there is no easy way to determine if they are being actually used. Enterprise license are pretty expensive so you want to be sure you are getting your money’s worth.
One, painful way to determine usage is to manually thumb around sites looking for things like Excel or Visio Web Parts, InfoPath forms, Content Organizer Rules, Data Connection Libraries or Power Pivot libraries. That is not practical, nor accurate, nor fun.
In my case I have no fancy commercial tools to manage and administer my farm so I often resort to rolling my own PowerShell scripts. The following are some functions I wrote to detect and document the instances of Enterprise feature usages.
The first thing you need is a loop that iterate down through your farm Site Collections and sites. Something like this usually works fine.
$rootSite = New-Object Microsoft.SharePoint.SPSite("http://my.spdomain.org") ;
$spWebApp = $rootSite.WebApplication;
$allSites = $spWebApp.Sites;
foreach($site in $allSites){
write-host "Site: " $site.Url;
$subWebs = $site | Get-SPWeb -Limit all;
foreach($web in $subWebs){
write-host "Web: " $web.Url;
#--------------------------# Put detection code here#--------------------------$web.Dispose();
$web = $null;
}$subWebs.Dispose;$subWebs = $null;
$site.Dispose();
$site=$null
}$allSites.Dispose;$allSites = $null;
$spWebApp = $null;
Once this main loop is in place we can insert detection functions which analyze SPList objects of each site. With the help of some online research below are the functions I came up with. All of the functions accept a site URL and iterates over the SPList objects to determine if the condition I am looking for is found. If a SPList object contains the feature I seek, it is added to an ArrayList and returned to the calling main loop. The main loop collects all ArryLists and then exports them to CSV or whatever you want to do with them. I export to CSV and put those in MS Access for creating nice reports.
Detecting PowerPivot Libraries
The trick to this function is to analyze the TemplateFeatureId property of a document library and compare it to the feature ID’s for PowerPivot. The PowerPivot feature Id’s are specified in the code.
function Get-PowerPivotLibs
([System.String]$siteUrl = "http://my.spdomain.org/team/Team_Site_Test"
){$powerPivotFeatures = @("1a33a234-b4a4-4fc6-96c2-8bdb56388bd5", "e9c4784b-d453-46f5-8559-3c891d7159dd", "f8c51e81-0b46-4535-a3d5-244f63e1cab9")
# ArrayList to hold any found PowerPivot Libs[System.Collections.ArrayList]$PPLibs = New-Object System.Collections.ArrayList($null)
$libs = Get-SPWeb $siteUrl |
Select -ExpandProperty Lists |
Where { $_.GetType().Name -eq "SPDocumentLibrary" -and -not $_.hidden }
foreach($lib in $libs)
{if(($powerPivotFeatures -contains $lib.TemplateFeatureId))
{$PPLibs.Add($lib) > $null
Write-Host -BackgroundColor Blue -ForegroundColor White "PowerPivot Lib Part Found"
}}return, $PPLibs;
}
Detecting Data Connection Libraries
This method is the same as used for detecting PowerPivot Libraries with the exception of a different feature Id.
function Get-DataConnectionLibs
([System.String]$siteUrl = "http://my.spdomain.org/team/Team_Site_Test/InternalTeam/DocCtr/"
){Write-Host -ForegroundColor Gray "Searching for DataConnection Libraries: " $siteUrl
$dataConnFeatures = @("00bfea71-dbd7-4f72-b8cb-da7ac0440130")
# ArrayList to hold any found DataConn Libs[System.Collections.ArrayList]$DCLibs = New-Object System.Collections.ArrayList($null)
$libs = Get-SPWeb $siteUrl |
Select -ExpandProperty Lists |
Where { $_.GetType().Name -eq "SPDocumentLibrary" -and -not $_.hidden }
foreach($lib in $libs)
{if(($dataConnFeatures -contains $lib.TemplateFeatureId))
{$DCLibs.Add($lib)>$null;
}}return, $DCLibs;
}
Detecting Excel Web Parts
This one is a bit more complicated and requires first determining if the function is dealing with a Publishing site or not. For Publishing sites we have to get something called a “LimitedWebPartManager” from each SPListItem in the Pages library. The “LimitedWebPartManager” allows you to iterate over each web part on the page to detect if the any web part of type “ExcelWebRenderer.”
If we are not dealing with a Publishing site, then function looks through all SPList objects that have a “BaseTemplate” property of “WebPageLibrary.” Again using the “LimitedWebPartManager” we look for the same “ExcelWebRenderer” type web parts.
When we find even one “ExcelWebRenderer” web part in a page we add the SPList to the ArrayList which is returned to the calling loop.
function Get-ExcelWebParts
([System.String]$siteUrl = "http://my.spdomain.org/team/Team_Site_Test/"
# Reference Sites:# http://sharepointpromag.com/sharepoint/windows-powershell-scripts-sharepoint-info-files-pagesweb-parts# http://support.microsoft.com/kb/2261512){Write-Host -ForegroundColor Gray "Searching for Excel Web Parts: " $siteUrl
$ExcelWebRenderer = "ExcelWebRenderer";
# ArrayList to hold any found Exel Web part page s[System.Collections.ArrayList]$ExcelWps = New-Object System.Collections.ArrayList($null)
$web = Get-SPWeb $siteUrl
$lists = $web |
Select -ExpandProperty Lists
Where { -ne $_.hidden }
if([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($web))
{$pWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web)
$pages = $pWeb.PagesList
foreach ($item in $pages.Items) {
try{$manager = $item.file.GetLimitedWebPartManager([System.Web.UI.WebControls.Webparts.PersonalizationScope]::Shared);
$wps = $manager.webparts
$webPartCount = 0;
foreach($wp in $wps)
{if($wp.GetType().Name -eq $ExcelWebRenderer){
$webPartCount++;
Write-Host -BackgroundColor Blue -ForegroundColor White "Excel Web Part Found"
}}if($webPartCount -gt 0)
{$ExcelWps.Add($pages) > $null;}
}catch [system.exception]{
#Write-Host -ForegroundColor Red -BackgroundColor Black "Error"}}} else {
foreach($list in $lists)
{if($list.BaseTemplate -eq "WebPageLibrary")
{foreach ($item in $list.Items) {
try{$manager = $item.file.GetLimitedWebPartManager([System.Web.UI.WebControls.Webparts.PersonalizationScope]::Shared);
$wps = $manager.webparts
$webPartCount = 0;
foreach($wp in $wps)
{if($wp.GetType().Name -eq $ExcelWebRenderer){
$webPartCount++;
Write-Host -BackgroundColor Blue -ForegroundColor White "Excel Web Part Found"
}}if($webPartCount -gt 0)
{$ExcelWps.Add($list) > $null;}
}catch [system.exception]{
#Write-Host -ForegroundColor Red -BackgroundColor Black "Error"}}}}}$web.Dispose();
return, $ExcelWps;
}
Detecting Content Organizer Rules
Content Organizing Rules are Enterprise features usually found in Document or Record Centers. These rules are created by users to automatically route documents to their proper folder based on Meta data on the document. Look it up if you are not sure what they are I think you will find them pretty cool.
Content Organizer Rules are stored in HIDDENs list using a content type named “Rule” so we just iterate over the Hidden SPList objects looking to see if the “Rule” content type is being used. If it is, the SPList is put in my return ArrayList.
function Get-ContentOrganizerRules
([System.String]$siteUrl = "http://my.spdomain.org/team/Team_Site_Test/InternalTeam/DocCtr/"
){Write-Host -ForegroundColor Gray "Searching for Content Organizer Rules: " $siteUrl
# ArrayList to hold any found DataConn Libs[System.Collections.ArrayList]$CORules = New-Object System.Collections.ArrayList($null)
$lists = Get-SPWeb $siteUrl |
Select -ExpandProperty Lists |
Where { $_.GetType().Name -eq "SPList" -and $_.hidden }
foreach($list in $lists)
{#Write-Host $list ;foreach($contenType in $list.ContentTypes){
if($contenType -ne $null){
if($contenType.Id.ToString() -eq "0x0100DC2417D125A4489CA59DCC70E3F152B2000C65439F6CABB14AB9C55083A32BCE9C" -and $contenType.Name -eq "Rule")
{$CORules.Add($list)>$null;
Write-Host -BackgroundColor Green -ForegroundColor White "Content Organizer Rule found: " $list.Url>$null;
}}}}return, $CORules;
}
Detecting InfoPath Forms
There is probably a cleaner way to detect usage of InfoPath forms but this was what I found in a pinch. In my environment it worked for at least a very large portion of InfoPath usages. I noticed that when you examine a list that has a custom InfoPath form instead of the default, SharePoint Designer will display alongside the default NewForm.aspx and EditForm.aspx additional forms called newifs.aspx and editifs.aspx respectively. I assumed that the “ifs” suffix stands for “InfoPath Form Services” so I set out to detect those. In addition, Form Libraries can also use InfoPath forms and those can be detected by inspecting their BaseTemplate property. If “XMLForm” is the BaseTemplate then an InfoPath Form is being used.
function Get-ExcelWebParts
([System.String]$siteUrl = "http://my.spdomain.org/team/Team_Site_Test/"
# Reference Sites:# http://sharepointpromag.com/sharepoint/windows-powershell-scripts-sharepoint-info-files-pagesweb-parts# http://support.microsoft.com/kb/2261512){Write-Host -ForegroundColor Gray "Searching for Excel Web Parts: " $siteUrl
$ExcelWebRenderer = "ExcelWebRenderer";
# ArrayList to hold any found Exel Web part page s[System.Collections.ArrayList]$ExcelWps = New-Object System.Collections.ArrayList($null)
$web = Get-SPWeb $siteUrl
$lists = $web |
Select -ExpandProperty Lists
Where { -ne $_.hidden }
if([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($web))
{$pWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web)
$pages = $pWeb.PagesList
foreach ($item in $pages.Items) {
try{$manager = $item.file.GetLimitedWebPartManager([System.Web.UI.WebControls.Webparts.PersonalizationScope]::Shared);
$wps = $manager.webparts
$webPartCount = 0;
foreach($wp in $wps)
{if($wp.GetType().Name -eq $ExcelWebRenderer){
$webPartCount++;
Write-Host -BackgroundColor Blue -ForegroundColor White "Excel Web Part Found"
}}if($webPartCount -gt 0)
{$ExcelWps.Add($pages) > $null;}
}catch [system.exception]{
#Write-Host -ForegroundColor Red -BackgroundColor Black "Error"}}} else {
foreach($list in $lists)
{if($list.BaseTemplate -eq "WebPageLibrary")
{foreach ($item in $list.Items) {
try{$manager = $item.file.GetLimitedWebPartManager([System.Web.UI.WebControls.Webparts.PersonalizationScope]::Shared);
$wps = $manager.webparts
$webPartCount = 0;
foreach($wp in $wps)
{if($wp.GetType().Name -eq $ExcelWebRenderer){
$webPartCount++;
Write-Host -BackgroundColor Blue -ForegroundColor White "Excel Web Part Found"
}}if($webPartCount -gt 0)
{$ExcelWps.Add($list) > $null;}
}catch [system.exception]{
#Write-Host -ForegroundColor Red -BackgroundColor Black "Error"}}}}}$web.Dispose();
return, $ExcelWps;
}
Exporting to CSV
As mentioned before, the main loop collects all the ArrayLists returned by the detection functions for export to CSV (in my case pipe delimited rather than comma). Just select the properties of the SPList and send them to the Export-CSV commandlet. Since all the CSV’s have the same columns just import them into Excel or Access and make your report.
$InfoPathResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\InfoPathResults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$COResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\COResults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$DCResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\DCResults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$ExcelResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\ExcelResults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$VisioResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\VisioRsults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$PPivotResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\PPivotResults.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
$PPivotResults | Select URL, Title, ItemCount, Author, Created, LastItemModifiedDate | Export-Csv C:\eCalSniffereCalSniffer\All.csv -NoTypeInformation -Encoding UTF8 -Delimiter '|'
Final Code
#References. C:\eCalSniffereCalSniffer\get-InfopathLists.ps1. .\get-InfopathLists.ps1. .\Get-ContentOrganizerRules.ps1. .\Get-DataConnectionLibs.ps1. .\Get-ExcelWebParts.ps1. .\Get-VisioWebParts.ps1. .\Get_PowerPivotLibs.ps1
. .\Results-To-CSV.ps1
$siteUrls = @("http://my.spdomain.org","http://carenet.stjoe.org");
[System.Collections.ArrayList]$InfoPathResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$COResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$DCResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$ExcelResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$VisioResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$PPivotResults = New-Object System.Collections.ArrayList($null)
[System.Collections.ArrayList]$AllResults = New-Object System.Collections.ArrayList($null)
foreach($siteUrl in $siteUrls)
{$rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl)
$spWebApp = $rootSite.WebApplication
$count = 0
write-host -ForegroundColor Gray "Site Collections"
write-host ""
foreach ($site in $spWebApp.Sites)
{$count++
write-host -ForegroundColor Gray "Site Collections URL: " $site.URL
write-host ""
write-host -ForegroundColor Gray "SubSites"
foreach ($web in $site.AllWebs)
{$count++
write-host -ForegroundColor Gray "SubSite URL: " $web.URL
$InfoPathScan = get-InfopathLists $web.URL;
if($InfoPathScan.Count -gt 0){
$InfoPathResults.AddRange($InfoPathScan);
}$CORulesScan = Get-ContentOrganizerRules $web.URL;
if($CORulesScan.Count -gt 0){
$COResults.AddRange($CORulesScan);
}$DataConnScan = Get-DataConnectionLibs $web.URL;
if($DataConnScan.Count -gt 0){
$DCResults.AddRange($DataConnScan);
}$ExcelWpsScan = Get-ExcelWebParts $web.URL;
if($ExcelWpsScan.Count -gt 0){
$ExcelResults.AddRange($ExcelWpsScan);
}$VisioWpsScan = Get-VisioWebParts $web.URL;
if($VisioWpsScan.Count -gt 0){
$VisioResults.AddRange($VisioWpsScan);
}$PowerPivotScan = Get-PowerPivotLibs $web.URL;
if($PowerPivotScan.Count -gt 0){
$PPivotResults.AddRange($PowerPivotScan);
}}$web.Dispose()
}$site.Dispose()
$rootSite.Dispose()
}write-host "Total Count :" $count
$AllResults.AddRange($InfoPathResults);
$AllResults.AddRange($COResults);
$AllResults.AddRange($DCResults);
$AllResults.AddRange($ExcelResults);
$AllResults.AddRange($VisioResults);
$AllResults.AddRange($PPivotResults);
Write-Host "Exporting CSV"
Results-To-CSV