Test-SPContentDatabase : Automating missing feature removal

Recently I've been working with a customer to help them with a SharePoint migration project.
One of the steps that we've worked on is to run the Test-SPContentDatabase cmdlet to check for missing files, features, web parts, assemblies, etc.

One of the content databases that was tested had somewhere in the region of 200 missing features. The customer confirmed that they didn't need to keep the feature references so we looked at ways of automating the removal of features (shockingly we never wanted to remove these manually!).

Test-SPContentDatabase return errors in a really nice format (Microsoft.SharePoint.Upgrade.SPContentDatabaseTestResult). As each error is returned as an object, we can check the 'Category' property in our script and do something if the category equals 'MissingFeature', which is what we do in the script. An example of the type of error that we want to check for:

[MissingFeature] Feature Id [fe5408f9-d9d4-a317-6c7f-122032c54475] is referenced [12] times in the database [WSS_Content_ContentDatabase1], but is not installed on the current farm.

We can check each error by storing all errors that are detected:

$testResults = Test-SPContentDatabase -Name -WebApplication

And then looping through each error individually:

foreach ($result in $testResults) { <# We want to check the type of error #> }

Once we've matched the category:

if ($result.Category -eq "MissingFeature") { <# We've found a missing feature #> }

We then extract the feature Guid from the 'message' property using a regular expression:

$regularExpression = "([a-f0-9]{8}[-][a-f0-9]{4}[-][a-f0-9]{4}[-][a-f0-9]{4}[-][a-f0-9]{12})" $match = [regex]::Matches($result.Message, $regularExpression) $guid = $match[0].Value

And then try to delete the feature reference by calling a function that we've created (see the entire script below):

Write-Host "Deleting feature id:" $guid Delete-SPFeatureReference -contentDatabase -guid $guid

Hopefully that all makes sense, the entire script is below:

$testResults = Test-SPContentDatabase -Name -WebApplication function Delete-SPFeatureReference() { Param ( [string]$contentDatabase, [string]$guid ) $db = Get-SPDatabase | Where-Object {$_.Name -eq $contentDatabase} foreach ($site in $db.Sites) { Start-SPAssignment -Global $siteFeature = $site.Features[$guid] if ($siteFeature) { $site.Features.Remove($siteFeature.DefinitionId, $true) } else { foreach ($web in $site.AllWebs) { $webFeature = $web.Features[$guid] if ($webFeature) { $web.Features.Remove($webFeature.DefinitionId, $true) } } } Stop-SPAssignment -Global } } foreach ($result in $testResults) { if ($result.Category -eq "MissingFeature") { $regularExpression = "([a-f0-9]{8}[-][a-f0-9]{4}[-][a-f0-9]{4}[-][a-f0-9]{4}[-][a-f0-9]{12})" $match = [regex]::Matches($result.Message, $regularExpression) $guid = $match[0].Value Write-Host "Deleting feature id:" $guid Delete-SPFeatureReference -contentDatabase -guid $guid } }

Hopefully this helps, you could extend the script to remove other kinds of errors based on the category.