SharePoint: Export Web Parts using PowerShell

We had a requirement to list all the web parts present in publishing pages of a site collection. This is the PowerShell script we used to export all web parts. The main thing to notice here is the need for having context information.
param (
    [string]$spWebAppUrl
 )

$xmlWriter = $null
Add-PSSnapin Microsoft.SharePoint.Powershell | out-null
$currentDir = (Resolve-Path .\).Path

function Ensure-HttpContext([string]$url)
{
    $sw = New-Object System.IO.StringWriter
    $resp = New-Object System.Web.HttpResponse $sw
    $req = New-Object System.Web.HttpRequest "", $url, ""
    $htc = New-Object System.Web.HttpContext $req, $resp
    [System.Web.HttpContext]::Current = $htc
}

function Ensure-SPContext([string]$url)
{
    Ensure-HttpContext $url

    if(![System.Web.HttpContext]::Current.Items["HttpHandlerSPWeb"] -or
        ![System.Web.HttpContext]::Current.Items["HttpHandlerSPSite"])
    {        
        [Microsoft.SharePoint.SPWeb]$web = Get-SPWeb $url
        [System.Web.HttpContext]::Current.Items["HttpHandlerSPWeb"] = $web
        [System.Web.HttpContext]::Current.Items["HttpHandlerSPSite"] = $web.Site
    }
}

function ExportWebPart(
    $webpartMgr,
    $webpart,
    $webpartPath
)
{
    if (!(test-path -path $webpartPath)) {new-item -path $webpartPath -itemtype directory | Out-Null}

    $fileName = Join-Path -Path $webpartPath -ChildPath "$($webpart.Id).webpart"
    $webpartXmlWriter = New-Object System.Xml.XmlTextWriter($fileName,$null)  
    $webpartXmlWriter.Formatting = [System.Xml.Formatting]::Indented

    $webpartMgr.ExportWebpart($webpart, $webpartXmlWriter)    
    $webpartXmlWriter.Dispose()
}

function ProcessSubWebs(    
    [string]$str
)
{
    [System.Web.HttpContext]::Current = $null

    $currentWeb = Get-SPWeb $str 
    write-host -ForegroundColor DarkYellow "Processing $str"  
    $webFolder = $currentWeb.ServerRelativeUrl

    Ensure-SPContext($str)

    if([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($currentWeb))
    {
        $xmlWriter.WriteStartElement("web")
        $xmlWriter.WriteAttributeString("title", $currentWeb.Title)
        $xmlWriter.WriteAttributeString("url", $currentWeb.Url)   

        $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($currentWeb)
        $spQuery = New-Object Microsoft.SharePoint.SPQuery
        $spQuery.ViewAttributes = "Scope='Recursive'"
        $publishingPages = $publishingWeb.GetPublishingPages($spQuery)

        $xmlWriter.WriteStartElement("pages")

        foreach ($publishingPage in $publishingPages)
        {
            $xmlWriter.WriteStartElement("page")

            $xmlWriter.WriteAttributeString("title", $publishingPage.Title)
            $xmlWriter.WriteAttributeString("pagecontent", $publishingPage.ListItem["PublishingPageContent"])            
            $xmlWriter.WriteAttributeString("absoluteurl", $publishingPage.Uri.AbsoluteUri)
            $xmlWriter.WriteAttributeString("pagelayout", $publishingPage.Layout.ServerRelativeUrl)            

            $wpManager = $currentWeb.GetLimitedWebPartManager($publishingPage.Url, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)             
            $webparts = $wpManager.WebParts

            $pageFolder = $wpManager.ServerRelativeUrl.Replace('/', '\')
            $pagePath = join-path -Path $siteFolder -ChildPath $pageFolder

            if($webparts.Count -gt 0)
            {      

                $xmlWriter.WriteStartElement("webparts")
                foreach($webpart in $webparts)
                {
                    $type = $webpart.GetType().Name

                    $xmlWriter.WriteStartElement("webpart")
                    if ($webpart.ExportMode -ne "None")
                    {                        
                        ExportWebPart $wpManager $webpart $pagePath
                    }
                    else
                    {
                        $xmlWriter.WriteAttributeString("info", "webpart not exported")
                    }      
                    if ($webpart.IsClosed)
                    {
                        $xmlWriter.WriteAttributeString("IsClosed", "true")
                    } 
                    $xmlWriter.WriteAttributeString("type", $webpart.GetType().Name)
                    $xmlWriter.WriteAttributeString("title", $webpart.Title)
                    $xmlWriter.WriteAttributeString("ID", $webpart.ID)
                    $xmlWriter.WriteAttributeString("zone", $wpManager.GetZoneID($webpart))
                    $xmlWriter.WriteAttributeString("index", $webpart.ZoneIndex)
                    if($type -eq "ContentEditorWebPart")
                    {
                        $xmlWriter.WriteAttributeString("content", $webpart.Content.InnerText) 
                        $xmlWriter.WriteAttributeString("contentlink", $webpart.ContentLink) 
                    }
                    elseif($type -eq "ScriptEditorWebPart")
                    {
                        $xmlWriter.WriteAttributeString("content", $webpart.Content)                             
                    }

                    $xmlWriter.WriteEndElement()                   
                }

                $xmlWriter.WriteEndElement()
            }

            $xmlWriter.WriteEndElement()
        }

        $xmlWriter.WriteEndElement();

        foreach($sub in $currentWeb.Webs)
        {            
            $xmlWriter.WriteStartElement("webs")
            ProcessSubWebs $sub.Url 
            $xmlWriter.WriteEndElement()            
        }        

        $xmlWriter.WriteEndElement()
    }
    else
    {
        Write-Host -Foregroundcolor Red "$str is not a publishing site"
    }
}

$site = get-spsite $spWebAppUrl


if ($site) 
{
    try
    {
        $siteFolder = (new-object System.Uri $site.Url).Authority

        write-host -ForegroundColor Yellow "Processing site pages..."   

        $xmlFilePath = join-path -Path $currentDir -childpath "$($siteFolder).xml"

        $xmlWriter = New-Object System.Xml.XmlTextWriter($xmlFilePath,$null);   
        $xmlWriter.Formatting = [System.Xml.Formatting]::Indented 

        $xmlWriter.WriteStartDocument()    

        $siteFolder = join-path -path $currentDir  -ChildPath $siteFolder
        ProcessSubWebs $spWebAppUrl 

        $xmlWriter.WriteEndDocument()

        write-host -ForegroundColor Green $xmlFilePath "saved"
    }
    catch
    {
        Write-Host "Error : $($_)"
    }
    finally
    {
        if($xmlWriter -ne $null)
        {
            $xmlWriter.Dispose()
        }
    }     
}