In my recent post I’ve illustrated, how to display SharePoint lists from other sites via PowerShell. As I told you, that solution does work only if you have direct access to the SharePoint server. Based on my experience that is not always the case. In the current post I introduce you a solution that should work even in such cases. We built this solution on the Managed Client-Object Modell of SharePoint.
Since I knew, that the CreateWebPartFromList method of the Microsoft.SharePoint.WebPartPages.SPWebPartManager is not accessible via the client object model, I first planned to apply the another approach: export the source web part via a LimitedWebPartManager instance (the client-side equivalent of SPLimitedWebPartManager), then use another LimitedWebPartManager instance to import it onto the target page. BUT (there is almost always a but…) it turned out, that although LimitedWebPartManager supports the ImportWebPart method, the ExportWebPart method is not available on the client-side (Note: as Waldek Mastykarz reported, the ExportWebPart method should be available since the March 2016 SharePoint Online CSOM update). So I came up with a fall-back plan and exported the web part by calling the exportwp.aspx page as described here by Anatoly Mironov.
As we export and import the web part from / to another sites, we create to different context objects to access them.
We read the response from the exportwp.aspx page as XML, and set the WebId property according to the ID of the source web site. There is apparently an issue with the ViewGuid property (more about them here), so we have to append it, for example, by cloning an existing XML node, like the one for the WebId property. A bit dirty workaround, but seems to work at me…
Finally, we import the web part to the target page and add it to the web part zone / position we wish.
- $sourceWebUrl = "http://YourSharePoint/Site1/Site2"
- $listTitle = "YourList"
- $viewTitle = "YourView" # name of the view, like "All Items"
- # set the path according the location of the assemblies
- Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
- Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
- $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($sourceWebUrl)
- $sourceWeb = $clientContext.Web
- $list = $sourceWeb.Lists.GetByTitle($listTitle)
- $views = $list.Views
- $clientContext.Load($sourceWeb)
- $clientContext.Load($list)
- $clientContext.Load($views)
- $clientContext.ExecuteQuery()
- $sourceWebId = $sourceWeb.Id
- $view = $views | ? { $_.Title -eq $viewTitle }
- $targetWebUrl = "http://YourSharePoint/Site1"
- # This path should be the site relative URL of the page. If you have the page sub webs, include them in the path
- $targetPageSiteRelUrl = "/Site1/SitePages/SubSiteLisTest.aspx"
- $targetWebPartZoneId = "Bottom" # change the ID of the web part zone to match your needs
- $targetWebPartIndex = 0 # the intended position of the web part in the zone
- if (!$view.ServerObjectIsNull)
- {
- Write-Host View found, exporting WebPart…
- $file = $list.RootFolder.Files.GetByUrl($view.ServerRelativeUrl)
- $clientContext.Load($file)
- $webPartManager = $file.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared)
- $webParts = $webPartManager.WebParts
- $clientContext.Load($webParts)
- $clientContext.ExecuteQuery()
- # I assume there is a single web part on the view page
- # if this assumption is false, you should filter the web parts first
- $webPart = $webParts[0]
- # https://www.red-gate.com/simple-talk/blogs/getting-the-absolute-url-of-a-file-in-csom/
- $viewAbsoluteUrl = (New-Object System.Uri($clientContext.Url)).GetLeftPart([System.UriPartial]::Authority) + $view.ServerRelativeUrl
- $exportWPUrl = $sourceWebUrl + "/_vti_bin/exportwp.aspx?pageurl=" + [System.Web.HttpUtility]::UrlEncode($viewAbsoluteUrl) + "&guidstring=" + $webPart.Id
- $request = [System.Net.WebRequest]::Create($exportWPUrl)
- $request.UseDefaultCredentials = $true
- $response = $request.GetResponse()
- $reader = New-Object System.IO.StreamReader $response.GetResponseStream()
- $wpXml = [Xml]$reader.ReadToEnd()
- $properties = $wpXml.webParts.webPart.data.properties
- $webIdProp = $properties.property | ? { $_.name -eq "WebId" }
- $webIdProp.InnerText = $sourceWebId
- # "For example, setting the ViewGuid or the Toolbar properties doesn't do anything!" see:
- # http://blog.bonzai-intranet.com/analysthq/2014/10/adding-an-xsltlistviewwebpart-with-a-custom-view-using-javascript/
- # as a workaround for the missing ViewGuid property, clone the WebId property and change its name / type / value
- $viewIdProp = $webIdProp.Clone()
- $viewIdProp.name = "ViewGuid"
- $viewIdProp.type = "string"
- $viewIdProp.InnerText = $view.ID.ToString("B").ToUpper() # "convert to a format like {8C1D2A1A-5BE8-469D-806E-2112965D2C1C}"
- [Void]$properties.AppendChild($viewIdProp)
- $wpText = $wpXml.OuterXml
- Write-Host Export completed
- Write-Host Importing WebPart…
- $targetClientContext = New-Object Microsoft.SharePoint.Client.ClientContext($targetWebUrl)
- $targetFile = $targetClientContext.Web.GetFileByServerRelativeUrl($targetPageSiteRelUrl)
- $targetWebPartManager = $targetFile.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared)
- # note the difference:
- # the server-side version of ImportWebPart returns a WebPart
- # the client-side equvalent of ImportWebPart returns a WebPartDefinition
- $targetWebPartDef = $targetWebPartManager.ImportWebPart($wpText)
- # you could set optionally the WebId and ViewGuid properties at this point as well, but be aware of the issue with the ViewGuid property I mentioned above..
- #$targetWebPartDef.WebPart.Properties["WebId"] = $sourceWebId
- #$targetWebPartDef.WebPart.Properties["ViewGuid"] = $view.ID
- # note the difference:
- # the server-side version of AddWebPart returns void
- # the client-side equvalent of AddWebPart returns a WebPartDefinition
- [Void]$targetWebPartManager.AddWebPart($targetWebPartDef.WebPart, $targetWebPartZoneId, $targetWebPartIndex)
- # or if you need the WebPartDefinition later, you can use these lines
- #$targetWebPartDef = $targetWebPartManager.AddWebPart($targetWebPartDef.WebPart, $targetWebPartZoneId, $targetWebPartIndex)
- #$targetClientContext.Load($targetFile)
- $targetClientContext.ExecuteQuery()
- # write code to check in / publish the page here as required
- Write-Host Import completed
- }
- else
- {
- Write-Host View $viewTitle not found
- }
- # http://wvg-epm01e.sv-services.at/Test-Site2/_vti_bin/exportwp.aspx?pageurl=/Test-Site2/Lists/TestList/test.aspx&guidstring=8c1d2a1a-5be8-469d-806e-2112965d2c1c
That’s it, you should now be able to copy list views (XsltListViewWebPart web parts) from one SharePoint site to another from client-side via PowerShell. Of course, that is limited to a site collection scope, and there are still known issues with copying list views (generally, not limited to the PowerShell solutions), for example, copying views for a document library with a folder structure seems not to work if you copy it from a parent site to a sub site. More about that eventually later, as soon as I collect a bit more background information about the problem.