Background:
So I've apparently committed the cardinal sin in SharePoint. I edited a page layout in SharePoint Designer! This of course injected the special SPD meta attributes into the layout which were still in the page layout at the time I copied it into my farm solution, which was subsequently deployed to our Dev and then Prod environments.
This wouldn't be such a big deal to resolve if it wasn't for the fact that my page layout had web parts embedded within it which we no longer want. Those web parts are now in the AllWebParts table within the content database, so even though I've upgraded my page layout to a version that does not have the SPD attributes and web parts, because the web parts are still in the AllWebParts table, they are still injected into any page using the new version of the page layout.
Question:
Without executing SQL against the content database (unsupported and a big no no), how can I remove the web parts from the AllWebParts table? My farm will soon have thousands of site collections in it, using this page layout, and I can't have the offending web parts injected in every publishing page that anyone uses this page layout on.
After burning a ton of support hours, Microsoft is telling me this is a one-way disaster that cannot be undone without nuking my content databases and starting over. Let's prove them wrong!
Microsoft's Stop-Gap Idea:
As a stop-gap measure, Microsoft have provided the below PowerShell snippit for us to execute on each site collection. It removes the webparts from the pagelayout in the site collection, but any new site collection we create would also have the issue, so we'd have to run the script on those as well. Surely there's a better solution than this!
$site = Get-SPSite http:// SiteURL /sites/bferman/
Write-Host $site.Url
$web = Get-SPWeb http:// SiteURL /sites/bferman/
$pagelayout = "http://SiteURL/sites/bferman/_catalogs/masterpage/gpcNavLeftBLTR.aspx"
$file = $web.GetFile($pagelayout)
Write-Host $file.Name
$file.CheckOut()
$wpm = $web.GetLimitedWebPartManager($pagelayout, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
Write-Host $wpm.WebParts.Count
$webparts = @()
foreach($spwebpart in $wpm.Webparts)
{
#You can have a condition to check for the name of webparts #here
$webparts = $webparts + $spwebpart.ID
}
foreach($webpartId in $webparts)
{
$wpm.DeleteWebPart($wpm.Webparts[$webpartId])
}
$file.CheckIn("Removed webpart",1)
$file.Publish("Removed webpart")
$web.Update();
$web.Dispose();
if($wpm -ne $null)
{
$wpm.Dispose();
}
$site.Dispose()