Wednesday, May 16, 2012

Copying Custom SharePoint Publishing Pages with web parts to another site

Ever tried to copy a custom publishing page with web parts in it to another web.. well copying is not the hard part but the page just refuses to work and throws up the famous SharePoint error - An unexpected error has occurred.

 

So what went wrong? The publishing page is quite different than copying documents around sites. The page and its web parts have references and pointers hardcoded based on the page URL, plus sometimes the content type is not present in the destination pages library which the source page uses  and hence these things make the destination page unusable.

 

One of the projects that I worked on had a requirement to copy pages created in the English sites to sites in other languages so that the users could then localise the content if required. There was no out of the box way to do so!

 

Solution? Our friend… PowerShell!

 

The script loops through the pages library and its folders recursively

-          gets all the pages (picks up pages only if they are ready for propagation - which means they are published and version no is major.0 and the page is not checked out by any user).

-          checks if the page exists in the destination equivalent folder

-          if it exists it checks if the page has been modified by any user or not

-          if not it checks it out and updates the destination page

-          adds the content type used in the page to the destination library (very important)

-          assigns the content type and page layout to the destination file (again very important)

-          assigns the field values of the source page to the destination page

-          copies all webparts from the source page to the destination page (first it deletes the webparts from the destination page to avoid multiple instances of the webparts)

-          checks it in and approves the destination page

-          if the page does not exist does not it crates the page and folder (if folder does not exist) and does the same steps as above.

 

The below PowerShell script works with a configuration list that stores the source and destination site urls but you could modify and use it differently. I didn’t get the chance to optimise the script but please feel free to consolidate the conditional repetitive bits to make it a smaller script.

 

 

#Copy pages to language variation sites from source (EN) site

 

if ( (Get-PSSnapin -Name microsoft.sharepoint.powershell -ErrorAction SilentlyContinue) -eq $null )

{

    Add-PSSnapin "microsoft.sharepoint.powershell"

}

 

#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------#

#add web parts from source page to destination page

function CopyWebPartsP2P($strsUrl, $strdUrl, $dWeb)

{

$spsWpManager = $sourceWeb.GetLimitedWebPartManager($strsUrl, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)

$spdWpManager = $dWeb.GetLimitedWebPartManager($strdUrl, [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)

 

foreach($spdwebpart in $spdWpManager.Webparts)

      {

           try{

                                $spdWpManager.DeleteWebPart($spdwebpart)

               

            }

            catch

            {

             Write-Host -ForegroundColor Red "An error occurred. Unable to delete webpart " $spdwebpart.Title

            }

      }

 

foreach($spwebpart in $spsWpManager.Webparts)

      {

            Write-Host "web part on " $strsUrl " is " $spwebpart.ID "," $spwebpart.Title ":Zone " $spwebpart.ZoneID ": PartOrder " $spwebpart.PartOrder

         try{

                  $spdWpManager.AddWebPart($spwebpart, $spwebpart.ZoneID, $spwebpart.PartOrder)

            }

            catch

            {

                  Write-Host -ForegroundColor Red "An error occurred. Unable to add webpart " $spwebpart.Title

                  #Write-Host $_.Exception.ToString()

            }

      }

}

#-----------------------------------------------------------------------------------------------------------------------------------------------------------------------#

# Approve the file

function Approve-File($file, $moderated)

{

            Write-Host -ForegroundColor DarkCyan "   Processing file ... Name :" $file.Name

            # Check if the file is checked in

            if($file.Level -eq [Microsoft.SharePoint.SPFileLevel]::Checkout)

            {

                  Write-Host -ForegroundColor DarkGreen "    File is checked out! Checking in ..."

                  $file.CheckIn("checked in by system",[Microsoft.SharePoint.SPCheckInType]::MajorCheckin)

            }

            # If moderation is enabled process the document for approval

            if($moderated)

            {

                  #Approve-Object $file

                  $file.item.ModerationInformation.Status = [Microsoft.SharePoint.SPModerationStatusType]::Approved

                  $file.item.Update()

                                                                      

            }

            Write-Host -ForegroundColor DarkGreen "   Done."

    

}

 

#------------------------------------------------------------------------------------------------------------------------------------------------------------------------#

function IsReadyForPropagation($itm)

{

if($itm.File.MajorVersion -gt 0 -and $itm.File.MinorVersion -eq 0 -and $itm.File.CheckOutType -eq "None")

      {

            return $true

      }

else

      {

            return $false

      }

}

#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------#

function LoopThruFolder($sfldr)

{

            #recursive loop thru source pages library

            $sfldr1 = $cspListItem["Variation Source URL"] + "/" + $sfldr

            if ($cspListItem["Variation Source URL"] -eq "/")

                  {

                        $sfldr1 = $cspListItem["Variation Source URL"] + $sfldr

                  }

            $sfolder = [Microsoft.Sharepoint.SPFolder]$sourceWeb.GetFolder($site + $sfldr1)

        Write-Host "folder = " $sfolder

        $sweb = $sfolder.ParentWeb

            #Write-Host "web = " $sweb

        $slist = $sweb.Lists[$sfolder.ParentListId]

        $squery = New-Object Microsoft.SharePoint.SPQuery

        $squery.Folder = $sfolder

        # Get a collection of items in the specified $folder

       $sitemCollection = $slist.GetItems($squery)

        foreach ($sitem in $sitemCollection)

        {

                  #check if item is file/folder

                  #if folder - recurse

                  if ($sitem.Folder -ne $null)

            {

               Write-Host "Recursive..." $sitem.Folder

               LoopThruFolder $sitem.Folder

            }

            else

            {

                #write-host $sitem.name "*" $sitem.Folder

                        #if file check if current version is major published version

                        # if no - skip

                        if (IsReadyForPropagation $sitem)

                              {

                            

                                    # if yes - check for same page in target site

                                    $destSite = new-object Microsoft.SharePoint.SPSite($site + $cspListItem["Site URL"] + "/" + $LibName)

                                    $destWeb = $destSite.OpenWeb()

                                    $spPubWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($destweb)

                                    $pubpages = $spPubWeb.PagesList

                                    $dLibName = $pubpages.RootFolder.Url

                                    #Write-Host "Dest URL = " $cspListItem["Site URL"]

                                    [Microsoft.SharePoint.SPFile]$dfile = $destWeb.GetFile($sitem.URL.Replace($LibName,$dLibName))

                                    #Write-Host $file.url $file.Exists

                                    if($dfile.Exists)

                                          {

                                          #if is exists

                                          #check if it has been localised. If localised then Modified By will not be SHAREPOINT\system

                                          #check if dest file major version is less than source major version

                                          #so copy only if the file is not already been copied

                                          if ($dfile.ModifiedBy.ToString() -ieq "SHAREPOINT\system" -and $dfile.MajorVersion -lt $sitem.File.MajorVersion)

                                                {

                                                      if ($dfile.CheckOutType -eq "None")

                                                      {

                                                            $dfile.Checkout("Long Term",$null);

                                                      }

                                                      #not localalised yet...hence overwrite the file (else skip)

                                                      #copy file to target pages lib

                                                      write-host "copy " $sitem.URL " to " $sitem.URL.Replace($LibName,$dLibName)

                                                      $dsfldr = $destWeb.GetFolder($sitem.URL.Replace($LibName,$dLibName))

                                                      $spFileCollection = $dsfldr.Files

                                                      $sbytes = $sitem.File.OpenBinary()

$dfile.Delete()

                                                      [Microsoft.SharePoint.SPFile]$fl = $spFileCollection.Add($sitem.URL.Replace($LibName,$dLibName), $sbytes, $true)

                                                      Write-Host "Update content type to " $sitem.ContentType.Name

                                                      $fl.Properties["PublishingPageLayout"] = $sitem.File.Properties["PublishingPageLayout"]

                                                      #[Microsoft.SharePoint.SPContentTypeId]$contenttypeid = $destWeb.ContentTypes[$sitem.ContentType.Name];

                                                                                                                                                                                                                  [Microsoft.SharePoint.SPContentTypeId]$contenttypeid = $sourceweb.Site.RootWeb.ContentTypes[$sitem.ContentType.Name].Id;

                                                     

                                                                                                                                                                                                                if($pubpages.ContentTypes[$sitem.ContentType.Name] -eq $null)

                                                                                                                                                                                                                                                                {

                                                                                                                                                                                                                                                                Write-Host "content type " $sitem.ContentType.Name " does not exists in " $pubpages.Name " hence adding now"

                                                                                                                                                                                                                                                                $pubpages.ContentTypes.Add($sourceweb.Site.RootWeb.ContentTypes[$sitem.ContentType.Name])

                                                                                                                                                                                                                                                                }

                                                                                                                                                                                                                                                               

                                                                                                                                                                                                                if ($fl.CheckOutType -eq "None")

                                                                                                                                                                                                                {

                                                                                                                                                                                                                   Write-Host "Check out..."

                                                                                                                                                                                                                    $fl.CheckOut()

                                                                                                                                                                                                                                                                }

                                                      $fl.Item["ContentTypeId"] = $contenttypeid.ToString()

                                                      $fl.Item.Update

                                                      $fl.Update

                                                     

                                                                                                                                                                                                                  foreach($fld in $sitem.ParentList.Fields)

                                                            {

                                                                  if(!$fld.ReadOnlyField)

                                                                        {

                                                                              Write-Host "field..." $fld.Title

                                                                              $fl.Item[$fld.id] = $sitem[$fld.id]

                                                                        }

                                                            }

                                                                                                                                                                                                                  $fl.Item.Update

                                                      $fl.Update

                                                                                                                                                                                                                  CopyWebPartsP2P $sitem.File.ServerRelativeUrl.ToString() $fl.ServerRelativeUrl.ToString() $destWeb

                                                      Approve-File $fl $pubpages.EnableModeration

                                                      #$destWeb.Update()

                                                      Write-Host "file overwritten to " $dsfldr.Url

                                                }

                                          }

                                    else

                                          {

                                          #if file does not exist

                                          $arrFldrs = $sitem.URL.Replace($LibName,$dLibName).Split("/")

                                          #Write-Host "check folder existance " $sitem.URL.Replace($LibName,$dLibName)

                                          $dsfurl = ""

                                          for($ai=0;$ai -le $arrFldrs.length-1;$ai++)

                                                {

                                                      if ($dsfurl -eq "")

                                                            {

                                                                  $dsfurl  = $dsfurl + $arrFldrs[$ai]  

                                                            }

                                                      else

                                                            {

                                                                  $dsfurl  = $dsfurl + "/" + $arrFldrs[$ai]

                                                            }

                                                      write-host "so far url = " $dsfurl

                                                      if($arrFldrs.length -gt 2 -and ($ai -gt 0 -and $ai -ne $arrFldrs.length-1 ))

                                                            {

                                                            #url has sub folders

                                                            #if folder does not exist

                                                            Write-Host "subfolder = " $arrFldrs[$ai]

                                                            $dsfldr = $destWeb.GetFile($dsfurl)

                                                            #write-host $dsfldr.name "-" $dsfldr.url "-" $dsfldr.Exists

                                                            if (-not $dsfldr.Exists)

                                                                  {

                                                                        #Write-Host "subfolder under" $dsfldr.ParentFolder.Url

                                                                        $pfldr = [Microsoft.SharePoint.SPFolder]$pubpages.ParentWeb.GetFolder($dsfldr.ParentFolder.Url)

                                                                        $newfldr = $pfldr.SubFolders.Add($arrFldrs[$ai]);

                                                                        $newfldr.item.ModerationInformation.Status = [Microsoft.SharePoint.SPModerationStatusType]::Approved

                                                                        $newfldr.item.Update()

                                                                        Write-Host "folder created"

                                                                        $dsfldr = $destWeb.GetFile($dsfurl)

                                                                  }

                                                           

                                                            }

                                                      else

                                                            {

                                                                  Write-Host "Lib/Page=" $arrFldrs[$ai]

                                                                  if($ai -eq $arrFldrs.length-1)   

                                                                  {

                                                                  #copy file to target pages lib

                                                                  write-host "copy " $sitem.File.ServerRelativeUrl " to " $sitem.File.ServerRelativeUrl.Replace($LibName,$dLibName)

                                                                  $dsfldr = $destWeb.GetFolder($dsfurl)

                                                                  $spFileCollection = $dsfldr.Files

                                                                  $sbytes = $sitem.File.OpenBinary()

                                                                  [Microsoft.SharePoint.SPFile]$dfl = $spFileCollection.Add($sitem.URL.Replace($LibName,$dLibName), $sbytes, $true)

                                                                  #$dFileName = $dsfldr.Files.Add($sitem.Name, $sbytes, $true)

                                                                  Write-Host "update content type to " $sitem.ContentType.Name

                                                                  $dfl.Properties["PublishingPageLayout"] = $sitem.File.Properties["PublishingPageLayout"]

                                                                  [Microsoft.SharePoint.SPContentTypeId]$contenttypeid = $sourceweb.Site.RootWeb.ContentTypes[$sitem.ContentType.Name].Id;

                                                                 

                                                                                                                                                                                                                                                if($pubpages.ContentTypes[$sitem.ContentType.Name] -eq $null)

                                                                                                                                                                                                                                                                {

                                                                                                                                                                                                                                                                Write-Host "content type " $sitem.ContentType.Name " does not exists in " $pubpages.Name " hence adding now"

                                                                                                                                                                                                                                                                $pubpages.ContentTypes.Add($sourceweb.Site.RootWeb.ContentTypes[$sitem.ContentType.Name])

                                                                                                                                                                                                                                                                }

                                                                                                                                                                                                                                                if ($dfl.CheckOutType -eq "None")

                                                                                                                                                                                                                {

                                                                                                                                                                                                                   Write-Host "check out..."

                                                                                                                                                                                                                    $dfl.CheckOut()

                                                                                                                                                                                                                                                                }

                                                                

                                                                  $dfl.Item["ContentTypeId"] = $contenttypeid.ToString()

                                                                  $dfl.Item.Update

                                                                  $dfl.Update

                                                                 

                                                                                                                                                                                                                                                                  foreach($fld in $sitem.ParentList.Fields)

                                                                                            {

                                                                                                  if(!$fld.ReadOnlyField)

                                                                                                        {

                                                                                                              Write-Host "field...." $fld.Title

                                                                                                              $dfl.Item[$fld.id] = $sitem[$fld.id]

                                                                                                        }

                                                                                            }

                                                                                                                                                                                                                                                                               

                                                                                                                                                                                                                                                                   $dfl.Item.Update

                                                                  $dfl.Update

                                                                                                                                                                                                                                                                  CopyWebPartsP2P $sitem.File.ServerRelativeUrl.ToString() $dfl.ServerRelativeUrl.ToString() $destWeb

                                                                  Approve-File $dfl $pubpages.EnableModeration

                                                                  #$destWeb.Update()

                                                                  Write-Host "file copied to " $dsfldr.Url

                                                                  }

                                                            }

                                                }

                                          }

                                    $destWeb.Dispose()

                                    $destSite.Dispose()

                              }

            }

        }

}

#parameters to modify

$site = "http://your_site_collection_url"

$LibName = "Pages"

$cListName = "Config List"

#code execution starts here

$spsite = Get-SPSite -identity $site

#Read from site config list to get source and target urls

    $spWeb = $spSite.RootWeb

    $cspList = $spWeb.Lists[$cListName]

    $cspitems = $csplist.items | where-object {($_["Variation Source URL"] -ne $null -and $_["Variation Source URL"] -eq '/')}

    foreach($citem in $cspitems)

      {

    # Casting avoids "Object reference not set to an instance of an object."

    [Microsoft.SharePoint.SPListItem]$cspListItem = $citem

    Write-Host "Dest=" $cspListItem["Site URL"] "; Source=" $cspListItem["Variation Source URL"]

    #for each source-target pair

    #get pages library - append pages to the urls

      if ($cspListItem["Variation Source URL"] -ne "/")

            {

                  $sourceSite = new-object Microsoft.SharePoint.SPSite($site + $cspListItem["Variation Source URL"] + "/" + $LibName)

            }

      else

            {

            $sourceSite = new-object Microsoft.SharePoint.SPSite($site + $cspListItem["Variation Source URL"] + $LibName)

            }

      $sourceWeb = $sourceSite.OpenWeb()

      LoopThruFolder $LibName

      $sourceWeb.Dispose()

      $sourceSite.Dispose()

      }    

 

 

Hope this helps you as much as it did me for my project!

 

1 comment:

My Favorite Athlete said...

Simply awesome. Took a little tweaking to get working how I needed it, but it works. Thanks for sharing your hard work!