6/26/2012

SharePoint: Search and Replace in Content Editor Web Parts

 

If you are interested in the use of these types of PowerShell scripts to explore and audit SharePoint then you may want to attend my “Exploring and Auditing SharePoint Using PowerShell” session at SharePoint Saturday Dayton this Saturday.

 

 

There’s a question in the MSDN SharePoint forums about how to update Content Editor Web Parts in hundreds of web part pages. These web parts had URLs that pointed to a server that had been renamed, and to fix the URLs meant updating all of these Content Editor Web Parts.

I had recently written a little script to find all of the Content Editor Web Parts in a farm that had a certain piece of text. It only took a small edit to add some code to find and replace text. I have posted the script here instead of the forums as I plan to return here to clean up the code a bit and add more comments.

 

As this script will change, and possibly break, your Content Editor Web Parts… what's below is provided with no warranties and is up to you to test to confirm that it is safe!

 

Steps:

  1. Paste the following into a NotePad file and save as CEWPsearchandreplace.ps1 (or any name you like)
     
  2. Edit these two lines with your before and after text:
        $textToFind = "theBeforeText"
        $newText    = "theAfterText"
     
  3. Start the SharePoint 2010 Management Shell and type: 
          . c:\whereyousavedit\CEWPsearchandreplace.ps1
    Note the dot and the space before the C:

Note that you will need appropriate PowerShell and SharePoint permissions to run this script. See here for more info on permissions: http://techtrainingnotes.blogspot.com/2011/08/searching-and-auditing-sharepoint-with.html

 

Code Notes:

  • The code creates three functions:
    Get-SPWebPartsForWeb  yourURL                (for a single site)"
    Get-SPWebPartsForSite  yourURL                 (for all sites in a site collection)"
    Get-SPWebPartsForApplication  yourURL    (for all site collections in an app)"
     
  • The code only checks for web part pages in selected libraries: "Site Pages","SitePages","Pages"
    You can edit the $librariesToCheck variable to add more locations
     
  • The code also checks the default.aspx page in the site root if $checkrootdefaultaspx = $true
     
  • There are a number of commented out “Write-Host” lines that you can uncomment for debugging purposes.

The code:

"Find and Update Content Editor Web Parts loaded..." 

"Run using:"
"  Get-SPWebPartsForWeb  yourURL         (for a single site)"
"  Get-SPWebPartsForSite  yourURL        (for all sites in a site collection)"
"  Get-SPWebPartsForApplication  yourURL (for all site collections in an app)" 

#  Change these two variables
$textToFind = "theBeforeText"
$newText    = "theAfterText" 

$librariesToCheck = "Site Pages","SitePages","Pages"
$checkrootdefaultaspx = $true 

function Check-Page($page)
{
        # borrow a .NET method GetExtension
        if ([system.io.path]::GetExtension($page.url).ToLower() -ne ".aspx")
        {
          continue #not a webpart page, so back to the foreach
        } 

        # try and get the WebPartManager for the page
        $wpm = $null
        try
        {
          $wpm = $page.GetLimitedWebPartManager([System.Web.UI.WebControls.Webparts.PersonalizationScope]::Shared)
        #"    Webparts: " + $wpm.webparts.count
        }
        catch
        {
          #"   Not a web part page"
        } 

        if ($wpm -ne $null)  # then we have a webpart page
        {
          Write-Host "    Page: " $page.Url 

          # report each web part found
          foreach ($webpart in $wpm.webparts)
          { 

            Write-Host "      Web part: " $webpart.WebBrowsableObject.ToString().Replace("Microsoft.SharePoint.","") -foregroundcolor blue
            Write-Host "         Title: " $webpart.Title
            if ( $webpart.WebBrowsableObject.ToString() -eq "Microsoft.SharePoint.WebPartPages.ContentEditorWebPart" )
            { 

              # if there's linked content then open and search for the script tag
              if  ($webpart.contentlink.length -gt 0 )
              {
                $file = $w.GetFile($webpart.contentlink)
                $b = $file.OpenBinary()
                $encoding=new-object "System.Text.UTF7Encoding"
                if ($encoding.GetString($b).toLower() -match "<script")
                {
                  Write-Host "         contentlink: HAS <SCRIPT>" $webpart.contentlink -foregroundcolor red
                }
                Remove-Variable b
              } 

              # else report what's in the content
              elseif ($webpart.content."#cdata-section".tolower() -match "<script")
                {
                  Write-Host "         content: HAS <SCRIPT>"  -foregroundcolor red 

# write-host $webpart.content."#cdata-section" -foregroundcolor green 

$xmlDoc = New-Object -TypeName xml
$xmlElement = $xmlDoc.CreateElement("MyElement"); 

# THIS IS WHERE THE REPLACE IS DONE.
$xmlElement.InnerText = $webpart.Content.InnerText.Replace($textToFind, $newText); 

$webpart.content = $xmlElement 

$wpm.savechanges($webpart) 

                }
            } 

          }
        } 

} 

filter Get-WebPartPages
{
  # this filter accepts a SPWeb and returns SPPages from target libraries 

# write-host "getting webparts"
  $w = $_
  #Write-Host ""
  #Write-Host "Web: "  $w.ServerRelativeUrl #-foregroundcolor yellow 

  if ($checkrootdefaultaspx)
  {
    #Write-Host "  Root: default.aspx"
    foreach ($file in $w.files)
    {
      if ($file.url -eq "default.aspx")
      {
        $file
      } 
    }
  }
  # Write-Host "now for lists..................." $w.lists.count
  foreach ($list in $w.Lists)
  {
      #Write-Host "  List: " $list.title $librariesToCheck $("'" + $list.title + "'")
    if ( $librariesToCheck -like "*'$($list.title)'*" )
    {
      #Write-Host "File name " -ForegroundColor red
      foreach ($item in $list.items)
      {
        #Write-Host "File name " $item.file.url -ForegroundColor red
        $page = $item.file 

        $page
      }
    }
  }
} 

function Check-WebParts($w)
{ 

  Write-Host ""
  Write-Host "Web: "  $w.ServerRelativeUrl -foregroundcolor yellow 

  if ($checkrootdefaultaspx)
  {
    Write-Host "  Root: "
    foreach ($file in $w.files)
    {
      if ($file.url -eq "default.aspx")
      {
        Check-Page($file)
      } 
    }
  } 

  foreach ($list in $w.Lists)
  {
  Write-Host ("'" + $list.title + "'")
    if ( $librariesToCheck -contains $list.title )
    {
      write-host $list.title
      foreach ($item in $list.items)
      {
        $page = $item.file 

        Check-Page($page)
      }
    }
  } 

} 

Function Get-SPWebPartsForWeb($url)
{
  $web = Get-SPWeb($url)
  Check-WebParts($web)
  $web.Dispose()
} 

Function Get-SPWebPartsForSite($url)
{
  $site = Get-SPSite($url)
  foreach($web in $site.AllWebs)
  {
    Check-WebParts($web)
    $web.Dispose()
  }
  $site.Dispose()
} 

Function Get-SPWebPartsForApplication($url)
{
  $app = Get-SPWebApplication($url)
  foreach ($site in $app.sites)
  {
    foreach($web in $site.AllWebs)
    {
      Check-WebParts($web)
      $web.Dispose()
    }
    $site.Dispose()
  }
}

 

.

15 comments:

wyefye said...

i'm in process of migrating sp2010 to sp2013 in a side-by-side config. i have ~800 site collections with over 5k subsites. Huge, right? Anyway, with that many users, lots of people have used hard coded links on everything from the title picture, to content editor webparts, nav links, the link webpart... etc... etc... etc... when i copy content DB's over to the new farm(different URL), i'm getting prompted to login to the original farm because the title picture is attempting to be found. I'm wondering if you know of a script that i could use to update a site collection and parse through it's subsites, and replace these hardcoded links with relative ones. I was also looking at URL rewrites, but i'm not having much success there.

Any ideas, or any way i can mod your powershel script would be greatly appreciated.

Mike Smith said...

wyefye,

The only "best way" that I know of is to create a several several PowerShell scripts to hunt down and change the URLs. For example, here's one to change all of the absolute URLs in Quick Launch to relative URLs.

http://gallery.technet.microsoft.com/projectserver/Update-specified-59552f37

That is not general enough, but shows the idea.

You would also need scripts for:
- wiki pages (home and other pages)
- top link bar
- the Link to a Document content type links
- Links in any Hyperlink column in lists

Links in documents would be the biggest problem to deal with.

Give me a list of where you have found URLs that need to be fixed and I'll see what I can find or create.

Mike

wyefye said...

Also, doesn't seem that you above script actually created the functions Get-SPWebPartsFor/web/site/app.

Perhaps i'm missing something?

Mike Smith said...

wyefye,

The functions are there... just scroll to the end of the script.

Mike

wyefye said...

Yeah. I see the functions. If I follow your instructions, who his yo mod the script then run, it tells me to use one of the commands it spits out(the function). Almost seems like it doesn't register the function with PS. I'm also running my prompt with admin rights, and with the so plugins loaded... So there should be no issue, but yet.. There is.

donkos said...

Superb posting - thank you for this, saved me a ton of work!

Dolph said...

Hi everyone,
Anybody can help me to use this script? From the beginning I want to say that I’m newbie in using PowerShell. So… my scenario is:
I have a SharePoint site http://sharepoint.mydomain.com/site1 I want to replace some text in all CEWP from this site. Can you please tell me in a few steps how to run this script.
Thanks in advance,

Mike Smith said...

Dolph,

Are you a SharePoint server administrator? PowerShell scripts must be run from one of the SharePoint servers, and you have to have the correct permissions to run PowerShell there.

Mike

Dolph said...

Hi Mike,

Yes I am FarmAdmin and I try to run this from Windows PoweShell ISE including "Add-PSSnapin microsoft.sharepoint.powershell -ErrorAction SilentlyContinue"

PowerShell run as administrator on WFE server.

Thanh Ngo said...

Thank you for the great post.

However, I don't see how "filter Get-WebPartPages" is applied. Nothing in the code calls or uses it. Please help understand

Mike Smith said...

Thanh,

The filter code is not called by the other functions, but it could be used in a pipeline to list potential web part pages. It could be deleted, but was there for some original testing.

Something like: Get-SPWeb http://urlToYourSite | Get-WebPartPages

Mike

Thanh Ngo said...

Thanks, Mike. It's clear now.

Thanh

Anonymous said...

Hi Mike, I am using your script to replace text in CEWP but its not working for me. Below are the statements I am using to test where exactly the issue coming:

$xmlElement.InnerText = $webpart.Content.InnerText.Replace($textToFind, $newText);
Write-host $xmlElement.InnerText
$webpart.content = $xmlElement
Write-host $webpart.content.InnerText
$wpm.savechanges($webpart)
Write-host $webpart.content.InnerText


So in above script, I am getting the replaced content above $wpm.savechanges($webpart) but in below write-host statement, I am getting the older text which was there originally.
PLease suggest why its not saving the changes in wpm and what I am missing here.

Mike Smith said...

I don't see any obvious issues. Do you have permissions to edit that page? Is PowerShell displaying any error at all?

Anonymous said...

Yes, I am having all the required permissions but identified that it requires page checkout, checkin to save the changes. But when I am using like below:

$page.CheckOut()
Write-Host "Checkout done :" $page.CheckOutType
$xmlelement.innertext = $webpart.content.innertext.replace($texttofind, $newtext);
$webpart.content = $xmlelement
$wpm.savechanges($webpart)
write-host $webpart.content.innertext
$page.CheckIn("Ok")
$page.Publish("Ok")


Above statements for checkout, checkin are working fine, I checked in version history of the page but still in $wpm.savechanges($webpart), getting error as
Exception calling "SaveChanges" with "1" argument(s): "The file is not checked out. You must first check out this document before making changes."
+ $wpm.savechanges($webpart)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebPartPageUserException


When I checked for the same error on google, found that the page must be checked out before the SPLimitedWebPartManager is retrieved. But in your script, its not possible to get the WebPartManager later because we are checking all webparts on each page. Please suggest on this.

Note to spammers!

Spammers, don't waste your time... all posts are moderated. If your comment includes unrelated links, is advertising, or just pure spam, it will never be seen.