Fix SharePoint Ghost Features After Migration: PowerShell Guide

C
Collab365 TeamAuthorPublished Nov 2, 2016
1

At a Glance

Target Audience
SharePoint Administrators, Migration Engineers
Problem Solved
Orphaned (ghost) features causing random errors after SharePoint farm upgrades or migrations due to missing feature definitions in databases.
Use Case
Cleaning SharePoint environments during farm migrations or version upgrades to ensure error-free operation.

Most teams treat a SharePoint upgrade like a simple copy and paste job. It never works.

You migrate your farm and think the hard part is over. Then the ghost features show up. You left the old custom code behind, but the database remembers. These orphaned features are a well-documented issue after upgrades. They sit in your new environment, invisible but broken, waiting to cause random errors.

You can either deploy the old features to the new farm, or you can cut them out entirely.

I prefer cutting them out.

You need to hunt them down across every site collection and subsite. The secret is detecting orphaned features by checking if $feature.definition -eq $null. If the definition is empty, the feature is a ghost.

Here is the exact script to find them, remove them, and log the cleanup.

$results = @()

foreach($site in Get-SPSite -limit all) {
    foreach ($feature in $site.features) {
        $obj = New-Object PSObject
        
        if ($feature.definition -eq $null) {
            $obj | Add-Member NoteProperty "Site/Web Title" ($site.Title)
            $obj | Add-Member NoteProperty "Site/Web URL" ($site.URL)
            $obj | Add-Member NoteProperty "Feature ID" ($feature.DefinitionId)
            $results += $obj
            
            $site.features.remove($feature.DefinitionId, $true)
        }
    }
    
    $webs = $site | Get-SPWeb -limit all
    foreach ($web in $webs) {
        foreach ($feature in $web.features) {
            $obj = New-Object PSObject
            
            if ($feature.definition -eq $null) {
                $obj | Add-Member NoteProperty "Site/Web Title" ($web.Title)
                $obj | Add-Member NoteProperty "Site/Web URL" ($web.URL)
                $obj | Add-Member NoteProperty "Feature ID" ($feature.DefinitionId)
                $results += $obj
                
                $web.Features.Remove($feature.DefinitionId, $true)
            }
        }
        $web.dispose()
    }
    $site.dispose()
}

$results | Export-Csv "C:\MissingFeatures.csv" -notype

You run the script. You expect a perfectly clean environment.

Instead, you get hit with a wall of red text.

Exception calling "Remove" with "2" argument(s): "Attempted to perform an unauthorized operation."

I see admins panic here. They assume their permissions are broken. They aren't.

The real culprit is usually publishing pages with versioning enabled.

Think about how SharePoint handles versions. When you added that feature originally, it attached itself to that specific iteration of the page. Removing the feature modifies the page and creates a new version. But the old version still exists in the database, and it still holds your ghost feature hostage.

You cannot bypass this with a magic command. You have to do the boring work. To finally remove the feature, you must delete the version history where it was originally applied.

It is not sexy. It is just the reality of database management over a long period of time. Clean your history, run the script again, and watch the ghosts disappear.