Quantcast
Channel: SharePoint 2013 – SharePoint Moshpit
Viewing all articles
Browse latest Browse all 5

Referenzen in SharePoint Managed Metadata Feldern mit PowerShell reparieren

$
0
0

Ein aktuelles Kundenproblem im Rahmen einer Migration von SharePoint 2010 zu SharePoint 2016 bereitete die letzten Tage Kopfzerbrechen: Nach dem Migrieren einiger Site Collections, welche diverse Inhaltstypen mit Managed Metadata Taxonomiefeldern aus dem Content Type Hub verwenden, stellt sich heraus, dass diese Felder nicht mehr funktionieren. Sobald die Elemente gespeichert werden sollen, verhindert SharePoint das mit dem Hinweis

„Da zu aktualisierende ‚SPListItem‘ wude nicht mit allen Taxonomiefeldern abgerufen.“ (sic) / „The SPListItem being updated was not retrieved with all taxonomy fields.“

Dieser Artikel zeigt, wie eine mögliche Lösung eines solchen Szenarios aussehen kann.

Es ist schon besonders gemein. Es wird unter Hochdruck migriert, der Zeitplan ist eng und die Todo-Liste noch lang.  Da sind zwar alle Daten schon mit DocAve hinübergewuppt worden, alles sieht gut aus – und dann beim Testen das.

SPLIstItem Taxonomie Fehlermeldung in der Maske
SPLIstItem Taxonomie Fehlermeldung in der Maske

Sucht man im Netz dazu, landet man früher oder später bei diesem älteren Artikel von Tjen, der allerdings das Problem nur im Kontext von Site-Vorlagen und dem Visual Studio beleuchtet. Andere Artikel empfehlen, das Ressource Throttling in der Zentraladministration hochzudrehen, was wohl in einigen Fällen geholfen haben soll. In unserem Fall war die Lösung etwas anders.

Die Fehleranalyse

Der Ursprung der Fehlermeldung lässt sich mit einem kleinen Trick nachvollziehen. Sucht man im ULS Log, findet man im Dickicht nichts wirklich Verwertbares. Keine Unexpectets oder Errors, die einem in die richtige Richtung weisen. Schaut man sich einmal an, was im Browser passiert, wenn das Item gespeichert werden soll, kann man zuerst eine Validierung der Managed Metadaten Felder durch einen ProcessQuery Call sehen.

Da das vom Browser aufgezeichnet werden kann, schaut man einmal im Netzwerkmonitor in den Antworttext dieses Calls. Hier kann man gut nachvollziehen, wie die Fehlermeldungen vom Server zurückgegeben werden.

Fehleranalyse kaputte Managed Metadata Felder
Fehleranalyse kaputte Managed Metadata Felder

Außerdem erhält man in dieser Antwort eine Correlation ID, mit der man ein Merge-SPLogFile füttern kann.

Merge-SPLogFile -Correlation <ID> -Path d:\mergelogs\error.log

 

In unserem Fall enthält das daraus resultierende Logfile neben dem üblichen Beifang folgende recht interessante Passage:

11/23/2017 03:54:05.95     w3wp.exe (SERVER:0x23C8)                 0x1CB8     SharePoint Foundation           Database                        adhpr     Medium     Entering Monitored Scope (VqueryEngine). Parent=SPRequest.GetListItemDataWithCallback2   8c8f2f9e-0f82-a0da-af65-aa9f41dd9940

11/23/2017 03:54:06.04     w3wp.exe (SERVER:0x23C8)                 0x1CB8     SharePoint Foundation           General                         xxpk     Medium     Unable to open Lookup list ‚{39cef420-d51e-4159-aad5-6b60c6a55228}‘.[Error was 0x81020026] 8c8f2f9e-0f82-a0da-af65-aa9f41dd9940

11/23/2017 03:54:06.10     w3wp.exe (SERVER:0x23C8)                 0x1CB8     SharePoint Foundation           General                         xxpk     Medium     Unable to open Lookup list ‚{39cef420-d51e-4159-aad5-6b60c6a55228}‘.[Error was 0x81020026] 8c8f2f9e-0f82-a0da-af65-aa9f41dd9940

11/23/2017 03:54:06.38     w3wp.exe (SERVER:0x23C8)                 0x1CB8     SharePoint Foundation           Database                        asutt High     [Forced

11/23/2017 03:54:06.38     w3wp.exe (SERVER:0x23C8)                 0x1CB8     SharePoint Foundation           Database                        ax0rv High     Leaving Monitored Scope: (SPSqlClient(proc_GetListMetaDataAndEventReceivers)) Execution Time=272.248847269695; CPU Milliseconds=0; SQL Query Count=0; Parent=VqueryEngine  8c8f2f9e-0f82-a0da-af65-aa9f41dd9940

Die Validierung der Felder schlägt also fehl, weil eine Liste nicht gefunden werden konnte. Wenn man nun weiß, wie die Anzeige von Taxonomiefeldern in SharePoint funktioniert, kann man sich den Rest zusammenreimen. Denn es existiert in jeder SiteCollection auf der Root-Webseite eine versteckte Liste, welche die Anzeigenamen sowie diverse weitere Informationen enthält. Wenn man im Browser hinter der Adresse einer Site Collection noch ein „/lists/TaxonomyHiddenList“ anfügt, kann man diese sichtbar machen.

TaxonomyHiddenList
TaxonomyHiddenList

Felder mit verwalteten Metadaten bestehen selber aus zwei verschiedenen Feldern: Einmal wird mit dem Anzeigetext in einem Feld eine sichtbare Information gespeichert. Das ist der Text, der einem angezeigt wird, wenn man ein solches Feld angezeigt bekommt. Daneben gibt es in der Liste/Bibliothek noch ein Feld, das sämtliche technischen Informationen enthält, und dieses ist ein Lookup auf die genannte Liste in der Root-Webseite einer Site Collection. Der Verweis findet also nicht direkt auf den Term Store der Managed Metadata Service Application statt sondern nimmt den Umweg über einen lokal geführten Index. Hier werden neben den Verweisen auf den Term Store auch für diverse Sprachen die Anzeigewerte hinterlegt.

TaxonomyHiddenList
TaxonomyHiddenList

Die Liste, die im vorliegenden Fall nun also nicht gefunden wird, ist also genau dieser Lookup. Das bedeutet, dass die kaputten Felder in der Liste eine falsche Listen-Id als Referenz in ihrem Schema gespeichert haben. Wie die in unserem Fall dorthin gekommen ist, ist eine spannende Frage. Offensichtlich hat hier etwas mit dem Content Type Hub und dem Anlegen der Liste bei der Generierung der Site Collection während der Migration nicht funktioniert.

Die Reparatur der Managed Metadata Felder

Letztlich muss man nun sicherstellen, dass die Referenz wieder stimmt. Dazu braucht man die Id der versteckten Liste und die Id des Rootwebs. Vergleicht man diese mit den Werten, die im Schema des Felds stehen und die beiden Paare stimmen nicht überein, kann man das beschriebene Fehlerverhalten sehen.  So ein Unterschied zwischen einer funktionierenden und einer kaputten Felddefinition könnte z.B. so aussehen:

<Field Type="TaxonomyFieldType" DisplayName="Firma" List="{9d4fd7bc-2dfe-4cbf-80a7-b2341350aba6}" WebId="161df174-89bc-405d-a5c4-de49c7e2f5ff" ShowField="Term1031" Required="TRUE" EnforceUniqueValues="FALSE" Group="..." ID="..." ></Field>
<Field Type="TaxonomyFieldType" DisplayName="Firma" List="{68afc1e1-d789-42ea-b74e-b67344cb06d2}" WebId="161df174-89bc-405d-a5c4-de49c7e2f5ff" ShowField="Term1031" Required="TRUE" EnforceUniqueValues="FALSE" Group="..." ID="..." ></Field>

Eine Lösung ist nun, diese Felder neu anzulegen, eine andere, das SchemaXml mit den richtigen Ids neu zu beschreiben. Das könnte man mit PowerShell realisieren:

$web = Get-SPWeb $webUrl
$list = $web.Lists[$listname]
$field = $list.Fields[$fieldname]
                              
$newSchema = $field.SchemaXml
if ($field.LookupList -ne $newListId) {
    $newSchema = $newSchema -replace $currentListId, $newListId                                                           
}
if ($field.LookupWebId -ne $newwebID) {
    $newSchema = $newSchema -replace $currentWebId, $newwebID
}
$field.SchemaXml = $newSchema
$field.Update()

Verpackt man das in ein Skript und legt die Vermutung zugrunde, dass bei allen Feldern, bei denen die Ids nicht mit denen der versteckten Liste übereinstimmen, betroffen sind, kann man ein Analyseskript schreiben. Zum Reparieren spendiert man man eine Autofix-Option. Ruft man es einfach so auf, geht es alle Webs der angegebenen Site Collections rekursiv durch und durchsucht vorhandene Listen und Bibliotheken nach dem beschriebenen Schema. Gefundene Felder werden in einer CSV-Logdatei mit allen notwendigen Informationen angelegt. Setzt man den Autofix-Parameter auf $true, werden die betreffenden Felder repariert.

Damit man das Skript auch für eine größere Anzahl an Site Collections verwenden kann, ist es mit einem CSV-Input ausgestattet. Als Eingabe wird also eine Datei verwendet, in der die vollen URLs stehen und „Url“ als Spaltentitel erwartet wird.

<#
.SYNOPSIS
    Checks the taxonomy fields in lists on webs in given site collections if there
    are faulty entries pointing not to the TaxonomyHiddenLists in the root web.
.DESCRIPTION
    Checks the taxonomy fields in lists on webs in given site collections if there
    are faulty entries pointing not to the TaxonomyHiddenLists in the root web. Can
    autofix these fields by exchanging the IDs in the SchemaXml of these fields.
.PARAMETER csvFile
    The name of the csv file.
.PARAMETER autofix
    If set to $true, the script will repair broken fields by exchanging appropiate ListID and 
    WebId attributes in the SchemaXml.
.EXAMPLE
    .\Check-TaxonomyFieldReferences.ps1 -csvFile webs.csv -autofix $true
.NOTES
    Author: Carsten Büttemeier
    Date:   25.11.2017  
#>
[CmdletBinding()]
Param(
    [Parameter(Mandatory = $True, Position = 1)]
    [string]$csvFile,
    [Parameter(Mandatory = $false, Position = 2)]
    [bool]$autofix = $false
)

#----SnapIns-----------------------------------------

if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell") -eq $null) {
    Add-PSSnapin Microsoft.SharePoint.PowerShell
}

#----Variables----------------------------------------

#set logging files
$x = (Split-Path -Parent $MyInvocation.MyCommand.Definition) + "\CheckTaxonomyReferences.log"
ac $x ("WebUrl, ListId, ExpectedListId, WebId, ExpectedWebId")
#----Execute-----------------------------------------

function FixField() {
    Param(
        [Parameter (Mandatory = $true)]
        [string]$webUrl,
        [Parameter (Mandatory = $true)]
        [string]$listname,
        [Parameter (Mandatory = $true)]
        [string]$fieldname,
        [Parameter (Mandatory = $true)]
        [string]$currentListId,
        [Parameter (Mandatory = $true)]
        [string]$newListId,
        [Parameter (Mandatory = $true)]
        [string]$currentWebId,
        [Parameter (Mandatory = $true)]
        [string]$newwebID
    )

    # Fixing the taxonomy list lookup 
    if ($autofix -eq $true) {
        Write-Host "Fixing column.." -ForegroundColor Yellow  -NoNewline                      

        $web = Get-SPWeb $webUrl
        $list = $web.Lists[$listname]
        $field = $list.Fields[$fieldname]
                              
        $newSchema = $field.SchemaXml
        if ($field.LookupList -ne $newListId) {
            $newSchema = $newSchema -replace $currentListId, $newListId
                                                           
        }
        if ($field.LookupWebId -ne $newwebID) {
            $newSchema = $newSchema -replace $currentWebId, $newwebID
        }
        $field.SchemaXml = $newSchema
        $field.Update()
        Write-Host "..done." -ForegroundColor Green  -NoNewline   
    }
}

function CheckWeb() {
    Param(
        [Parameter (Mandatory = $true)]
        [string]$webUrl
    )

    $brokenFields = @() 
        
    Write-Host "Checking web with URL " -ForegroundColor Cyan -NoNewline
    Write-Host $webUrl  -ForegroundColor Gray 
    $web = Get-SPWeb -Identity $webUrl -ErrorAction SilentlyContinue
    if ($web) {
        Write-Host "..Web Id  " -ForegroundColor Cyan -NoNewline
        Write-Host $web.ID  -ForegroundColor Gray 
    
        $taxonomyHiddenList = $web.Site.RootWeb.Lists["TaxonomyHiddenList"]
        Write-Host "..TaxonomyHiddenList Id " -ForegroundColor Cyan -NoNewline
        Write-Host $taxonomyHiddenList.ID  -ForegroundColor Gray 
        Write-Host "..Checking Lists and Libraries " -ForegroundColor Cyan 
        
        foreach ($list in $web.Lists) {
            $listUrl = $list.RootFolder.ServerRelativeUrl
            Write-Host "..checking list $listUrl " -ForegroundColor Gray -NoNewline
        
            $fields = $list.Fields | Where {$_.TypeAsString -eq "TaxonomyFieldType" -or $_.TypeAsString -eq "TaxonomyFieldTypeMulti"}
            if (!$fields) {
                Write-Host "..no taxonomy fields in list." -ForegroundColor Cyan 
            }
            else {
                foreach ($f in $fields) {
                    $broken = $false
                    $expectedListId = "{$($taxonomyHiddenList.ID)}" 
                    $expectedWebId = $web.Site.RootWeb.Id
                    Write-Host "..checking field $($f.Title) " -ForegroundColor Gray -NoNewline
                    if ($f.LookupList -ne $expectedListId) {
                        $broken = $true
                        Write-Host "..ListId mismatch.." -ForegroundColor Yellow -NoNewline
                    }
                    else {
                        Write-Host "..ListId ok.." -ForegroundColor Green -NoNewline                
                    }
                    if ($f.LookupWebId -ne $expectedWebId) {
                        $broken = $true
                        Write-Host "..WebId mismatch.." -ForegroundColor Yellow
                    }
                    else {
                        Write-Host "..WebId ok.." -ForegroundColor Green               
                    }

                    if ($broken) {
                        $listId = $f.LookupList
                        $webId = $f.LookupWebId
                        ac $x ("$listUrl, $listId, $expectedListId, $webId, $expectedWebId")
                            
                        if ($autofix -eq $true) {
                            # collect data to fix this outside the foreach loop.
                            $fieldObject = New-Object PSCustomObject        
                            $fieldObject | Add-Member -type NoteProperty -name WebUrl -Value $webUrl    
                            $fieldObject | Add-Member -type NoteProperty -name ListName -Value $list.Title     
                            $fieldObject | Add-Member -type NoteProperty -name FieldName -Value $f.Title
                            $fieldObject | Add-Member -type NoteProperty -name CurrentListId -Value $listId
                            $fieldObject | Add-Member -type NoteProperty -name NewListId -Value $expectedListId
                            $fieldObject | Add-Member -type NoteProperty -name CurrentWebId -Value $webId
                            $fieldObject | Add-Member -type NoteProperty -name NewWebId -Value $expectedWebId
                            $brokenFields += $fieldObject
                        }                          
                    }
                }
            }
        }

        foreach ($broken in $brokenFields) {
            FixField -webUrl $broken.WebUrl -listname $broken.ListName -fieldname $broken.FieldName -newListId $broken.NewListId -currentListId $broken.CurrentListId -currentWebId $broken.CurrentWebId -newwebID $broken.NewWebId
        }

    }

    foreach ($subWeb in $web.Webs) {
        $url = $subWeb.Url
        CheckWeb -webUrl $url
    }
}

try {
    #get the input csv
    $csvImport = Import-Csv ((Split-Path -Parent $MyInvocation.MyCommand.Definition) + "\$csvFile")
    $numberOfWebs = $csvImport.count 
    Write-Host "$numberOfWebs webs found in file $csvFile" -ForegroundColor Gray
}
catch {
    Write-Host "File $csvFile could not be found or error while importing csv" -ForegroundColor Yellow
}

# Check all given site collections
for ($i = 0; $i -lt $csvImport.count; $i++) {
    Write-Host "Checking broken Taxonomy lookups in list definitions in site " -ForegroundColor Cyan -NoNewline
    Write-Host $csvImport[$i].URL  -ForegroundColor Yellow 
    $saWeb = Get-SPWeb -Identity $csvImport[$i].URL -ErrorAction SilentlyContinue
    if ($saWeb) {
        CheckWeb -webUrl $csvImport[$i].URL
    }
    else {
        Write-Host "Site doesn't exist"
    }
    $saWeb.Dispose()
}

Wie immer gilt dabei, Skripte aus dem Internet nicht ohne Prüfung zu verwenden und erst recht nicht auf Livedaten ungetestet anzuwenden! Ich übernehme keine Gewähr, die Verwendung erfolgt auf eigene Gefahr.

Nichtsdestotrotz hoffe ich, dass das Skript bei ähnlichen Fällen helfen kann und würde mich über Rückmeldungen dazu freuen.

Der Beitrag Referenzen in SharePoint Managed Metadata Feldern mit PowerShell reparieren erschien zuerst auf SharePoint Moshpit.


Viewing all articles
Browse latest Browse all 5