Revisited - Upgrading SharePoint - Some views not upgraded to XsltListViewWebPart


The old

Back in 2013 when I was a regular blogger I posted about a tool/logic that would assist you in SharePoint migrations where some Views of Lists and Libraries would remain in the ‘old’ display mode. This issue happens with all version upgrades (2007 > 2010 > 2013/2016) and is actually still present.

Link: http://www.sharepointblogs.be/blogs/vandest/archive/2013/01/08/upgrading-sharepoint-some-views-not-upgraded-to-xsltlistviewwebpart.aspx

The tool I wrote back then would recreate the view with exact same settings but I found it still wouldn’t do a 100% conversion of views.

The new

With the release of SharePoint 2016 I’m doing a lot of upgrade projects from 2007/2010 to 2016 and I decided to have a second look at the mechanism behind all this. I inspected the (Xslt)ListViewWebPart with ILSpy/Reflector to see the logic involved. I then converted that logic into a set of Powershell functions that can be called on various levels.

Note that I’m making calls to internal code using Reflection, I’ve used this script with success at all migration projects, but the necessary disclaimers apply since we’re dealing with reflection here.

The details

Here’s the main function that checks an individual Web Part Page for “upgradeable” Web Parts and if applicable will perform the upgrade of ListViewWebParts. It also handles any check-out/check-in that may be required.

function Upgrade-ListViewWebPartsOnPage([Microsoft.SharePoint.SPFile]$page, [bool]$checkOnly)
{
if ($page.Exists) {

if ($checkOnly)
{
$man = $page.GetLimitedWebPartManager('Shared')
$lvWps = @($man.WebParts) | ? { ($_ -is [Microsoft.SharePoint.WebPartPages.ListViewWebPart]) -and (Check-ListViewWebPartNeedsUpgrade -lvwp $_) }

if ($lvWps.Count -gt 0) {
Write-Host ("Page " + $page.Web.Url + "/" + $page.Url + " contains upgradeable ListViewWebParts") -ForeGroundColor Yellow
}
}
else
{
if ($page.RequiresCheckout) {
if ($page.CheckOutStatus -ne 'None') {
try { $page.UndoCheckOut() } catch { $page.CheckIn("SYSTEM"); }
}

$page.CheckOut()
}

$man = $page.GetLimitedWebPartManager('Shared')

if ($man -ne $null) {
try {
$lvWps = @($man.WebParts) | ? { ($_ -is [Microsoft.SharePoint.WebPartPages.ListViewWebPart]) -and (Check-ListViewWebPartNeedsUpgrade -lvwp $_) }

if ($lvWps.Count -gt 0) {
Write-Host ("Processed " + $page.Web.Url + "/" + $page.Url + " for ListViewWebParts") -ForeGroundColor Green

$lvWps | % {
$wp = $_

$view = [Microsoft.SharePoint.WebPartPages.ListViewWebPart].InvokeMember("View", 'GetProperty, Instance, NonPublic', $null, $wp, $null)
$wpType = Get-WebPartTypeId -view $view

[Microsoft.SharePoint.WebPartPages.ListViewWebPart].InvokeMember("NewWebPartTypeId", 'SetProperty, Instance, NonPublic', $null, $wp, $wpType)

$man.SaveChanges($wp)
}

if ($page.RequiresCheckout) {
$page.CheckIn("SYSTEM")

if ($page.Item.ParentList.EnableMinorVersions) {
$page.Publish("SYSTEM")
}
}
} else {
if ($page.RequiresCheckout) {
try { $page.UndoCheckOut() } catch { $page.CheckIn("SYSTEM"); }
}
}
} catch {
Write-Host ("Manually check " + $page.Web.Url + "/" + $page.Url + " for ListViewWebParts WebParts - Error: " + $_) -ForeGroundColor DarkYellow
}
}
}
}
}

Then there are various functions that do the same thing but on View, List or Web scope. They essentially all make calls to the above function in a loop of that particular scope.

function Upgrade-ListViewWebPartsOnView([Microsoft.SharePoint.SPView]$view, [bool]$checkOnly)
{
$web = $view.ParentList.ParentWeb
$page = $web.GetFile($web.Url + "/" + $view.Url)

Upgrade-ListViewWebPartsOnPage -page $page -checkOnly $checkOnly
}

function Upgrade-ListViewWebPartsOnList([Microsoft.SharePoint.SPList]$list, [bool]$checkOnly)
{
@($list.Views) | % {
$view = $_

Upgrade-ListViewWebPartsOnView -view $view -checkOnly $checkOnly
}
}

function Upgrade-ListViewWebPartsOnWeb([Microsoft.SharePoint.SPWeb]$web, [bool]$checkOnly)
{
@($web.Lists) | % {
$list = $_

Upgrade-ListViewWebPartsOnList -list $list -checkOnly $checkOnly
}
}

Finally there are the base functions that handle the checking and upgrading of the Web Parts similar to how the API handles it. They use reflection for methods that are marked internal.

function Check-ListViewWebPartNeedsUpgrade([Microsoft.SharePoint.WebPartPages.ListViewWebPart]$lvwp)
{
$view = [Microsoft.SharePoint.WebPartPages.ListViewWebPart].InvokeMember("View", 'GetProperty, Instance, NonPublic', $null, $lvwp, $null)
$rh = [Microsoft.SharePoint.SPFile].Assembly.GetType("Microsoft.SharePoint.WebPartPages.SPWebPartReflectionHelper")

$g1 = $rh.InvokeMember("GetWebPartTypeID", 'InvokeMethod, Static, NonPublic', $null, $null, ([Microsoft.SharePoint.WebPartPages.XsltListViewWebPart]))
$g2 = Get-WebPartTypeId -view $view

# If ids are identical it needs to upgrade
return ($g1 -eq $g2)
}

function Get-WebPartTypeId([Microsoft.SharePoint.SPView]$view)
{
## Note: Logic taken from Microsoft.SharePoint.WebPartPages.BaseListViewToolPart.ApplyChanges()

$list = $view.ParentList
$rh = [Microsoft.SharePoint.SPFile].Assembly.GetType("Microsoft.SharePoint.WebPartPages.SPWebPartReflectionHelper")
$result1 = $rh.InvokeMember("GetWebPartTypeID", 'InvokeMethod, Static, NonPublic', $null, $null, ([Microsoft.SharePoint.WebPartPages.ListViewWebPart]))
$result2 = $rh.InvokeMember("GetWebPartTypeID", 'InvokeMethod, Static, NonPublic', $null, $null, ([Microsoft.SharePoint.WebPartPages.XsltListViewWebPart]))

if (($view -ne $null) -and ($view.Type -ne "HTML"))
{
if (($view.Type -eq "GRID") -and ([Microsoft.SharePoint.Utilities.SPUtility]::ContextCompatibilityLevel -ge 15))
{
$result = $result2
}
else
{
$result = $result1
}
}
else
{
if ($view -eq $null)
{
$result = $result2
}
else
{
$useDV = [Microsoft.SharePoint.WebPartPages.SPWebPartManager].InvokeMember('UseDataView', 'InvokeMethod, Static, NonPublic', $null, $null, @($list, $view))

if ($useDV)
{
$result = $result2
}
else
{
$result = $result1
}
}
}

return $result
}

The goods

Here’s a nice little download that contains all functions in a single file, ready for you to use – if you dare….

upgrade_lvwp_functions.ps1.txt

If you have any feedback let me know in the comments!

 


Links to this post

Comments

Thursday, 20 Oct 2016 12:18 by Charles Babcock
Steven, I am running into this challenge right now. Any chance of you putting together a script that will look through a web app, site collection, or site and perform the updates on the pages containing the legacy parts? It would save more than the day. Thanks! Charles

Monday, 24 Oct 2016 04:20 by Steven Van de Craen
Hi Charles, there's a function that works on SPWeb scope, so it should be very easy to string that together with some Get-SPWebApplication http://yourUrl | Get-SPSite -Limit ALL | Get-SPWeb -Limit ALL goodness. If you're still stuck contact me directly. Cheers!

CAPTCHA Image Validation