Kategorien
IT SharePoint

SharePoint – Tenant-OU wieder freigeben

SharePoint 2016 hat (anders als SharePoint 2019) noch Multi-Tenancy-Unterstützung. Das heisst, man kann einige Service-Applikationen, wie z.B. den Managed Metadata Service oder den User Profile Service für verschiedene Tenants Partitionieren – im Prinzip das, was Microsoft auch in SharePoint Online macht, um verschiedenen Kunden einen eigenen Bereich in den Service-Applikationen zu geben.

Beim erstellen eines Tenants, wird mit New-SPSiteSubscription so eine Partition für den Tenant registriert.

Mann kann dann mit dieser Subscription z.B. den People Picker auf eine OU (Organizational Unit im Active Directory) einschränken:

Set-SPSiteSubscriptionConfig -identity $siteSubscription -FeaturePack $defaultfeaturePackId -UserAccountDirectoryPath "OU=UserAccounts,DC=FABRIKAM,DC=COM"

Beim Erstellen einer Site Collection für den Tenant wird diese Subscription auch mitgegeben:

New-SPSite -Url https://fabrikam.sptest.local" -SiteSubscription $siteSubscription -HostHeaderWebApplication "https://root.sptest.local"

Und mit folgendem Befehl Add-SPSiteSubscriptionProfileConfig wird eingestellt, dass für diese Subscription die AD-Benutzer mit der entsprechenden OU synchronisiert werden (für die User Profile Service Application).

$upaProxy = Get-SPServiceApplicationProxy | where-object { $_.DisplayName -eq "MT User Profile Service Application" }
Add-SPSiteSubscriptionProfileConfig -Identity $siteSubscription -SynchronizationOU "OU=UserAccounts,DC=FABRIKAM,DC=COM" -ProfileServiceApplicationProxy $upaProxy

Wenn man zu einem späteren Zeitpunkt den Tenant löschen möchte, zum Beispiel weil beim Erstellungsprozess etwas schief gelaufen ist, dann muss man schlau vorgehen, damit die OU wiederverwendet werden kann. Eine OU kann nämlich nicht von mehr als einer Subscription verwendet werden.

Mit folgenden Schritten (in der SharePoint Management Shell) kann man eine Subscription und die zugehörige Subscription-Profile-Config löschen. Achtung: es werden dabei alle Site Collections gelöscht, welche zur jeweiligen Subscription gehören!

Zuerst holt man sich die jeweilige Subscription, welche gelöscht werden soll, zum Beispiel über den URL der Site:

# Site holen
$site = Get-SPSite "http://fabrikam.sptest.local"

# subscription der Site holen
$subscription = $site.SiteSubscription

Dann muss man sich die User Profile Service Application holen (darauf achten, dass die richtige Multi-Tenancy-fähige Service-Applikation geholt wird, falls ihr mehere, z.B. auch eine normale, in der Farm habt).

# Namen des UPA Proxys angeben
$upaProxyName = "MT User Profile Service Application"

# UPA Proxy holen
$upaProxy = Get-SPServiceApplicationProxy | where-object { $_.DisplayName -eq $upaProxyName }

Nun kann man als erstes die OU-Synchronisierung entfernen, es darf auf keinen Fall zuerst die Subscription direkt gelöscht werden, denn das wäre möglich und würde das ausführen dieses Befehls verunmöglichen, was dazu führt, dass man die OU nur noch über die manuelle Bearbeitung der Datenbank erledigen kann (was Microsoft nicht gerne hat):

# UPA Subscription Eintrag entfernen (aus UPA Profile DB für Profile Sync.)
Remove-SPSiteSubscriptionProfileConfig -Identity $subscription -ProfileServiceApplicationProxy $upaProxy

Nun kann die Subscription gelöscht werden. Um Sicherzugehen, welche Sites betroffen sind, kann man zuerst $subscription.Sites aufrufen:

# Site subscription und alle assozierten Sites löschen
Remove-SPSiteSubscription -Identity $subscription

Am Ende kann noch die Inhaltsdatenbank gelöscht werden, in welcher die Site Collection für den Tenant gespeichert war (sinnvoll falls man pro Tenant eine eigene Inhaltsdatenbank verwendet):

# Zum Schluss die Inhaltsdatenbank entfernen. Auch hier sollte man zuerst prüfen, dass keine anderen Sites in der Inhaltsdatenbank sind! (Befehl geht auch, wennd die Site zuvor gelöscht wurde, das Objekt hat noch den Wert der Datenbank und den Datenbanknamen)
$databaseName = $site.ContentDatabase.Name
Remove-SPContentDatabase -Identity $databaseName

Dies war der Weg um einen Tenant sauber zu löschen, so dass die OU die verwendet wurde, auch später wieder neu verwendet werden kann.

Falls man diesen Weg nicht eingehalten hat und z.B. die Site-Subscription schon gelöscht hat, ohne zuerst Remove-SPSiteSubscriptionProfileConfig auszuführen, dann bleibt einem nur noch der Weg, die Subscription manuell aus der Datenbank zu entfernen, dies führt gemäss Microsoft zum Verlust der Garantie!

Es handelt sich dabei um die Profile DB der User Profile Service Application, in welcher in diversen Tabellen die Einträge für unsere Subscription (Partition-ID) gelöscht werden müssen.

Nachfolgend ein SQL-Script, mit welchem man dies machen könnte, Verwendung aber auf eigene Gefahr. Wichtig ist, dass ihr in der ersten Zeile den Wert der Variable SubscriptionId auf die entsprechende Subscription-ID setzt und danach natürlich [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa] mit der ensprechenden Datenbank und dem entsprechenden Schema für euren Fall:

DECLARE @SubscriptionId AS Varchar(255) = '6d947c20-f876-4d4b-b680-1f0cd0a78c6f'

Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ProfileSubtypeList WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].PersonalSite WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Orgle_Stats WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Orgle_OpList WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Orgle_List WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Orgle_ErrorMsg WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].InternalDetail WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].DataTypeList WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].DataServiceList WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].BucketInfo WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].MicrofeedPostDefinitions WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ActivityTemplate WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ActivityApplication WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ActivityType WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ActivityApplication WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Profile_SearchStopwords WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Profile_SearchUserTable WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].OrganizationProfileValue WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].OrganizationProfile WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].UserProfileEventLog WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].UserProfileValue WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].UserProfile_Full WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].UserPrivacyPolicy WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ADImportConstantPropertyMapping WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ADImportPropertyMapping WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ProfileSubtypePropertyAttributes WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].ProfileTypePropertyAttributes WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].PropertyList WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].[Profile_Stats] WHERE PartitionID = @SubscriptionId
Delete
  FROM [SharePoint_MT_User_Profile_Service_Application_ProfileDB].[upa].Tenants WHERE PartitionID = @SubscriptionId

Es empfiehlt sich danach etwas zu warten, da diese Änderungen nicht sofort ziehen. Im Idealfall könnt ihr danach einen IISReset auf allen SharePoint-Servern durchführen und ich würde auch die Instanz auf dem Datenbankserver neu starten.

Kategorien
IT SharePoint

SharePoint (On-Premise) Blob-Cache löschen

Da sich der Markt in den letzten Jahren sehr in Richtung Office 365 bewegt hat, habe ich nur noch selten mit On-Premise SharePoint-Installationen zu tun.

Heute jedoch, musste ich eine (SharePoint 2016) Test-Farm neu aufbauen, zumindest ab Stufe Web-Applikation/Inhaltsdatenbank, welche neu von der Produktion übernommen werden musste und dann als neues Test-System konfiguriert werden sollte, da die alte Test-Farm nach einem missglückten Update korrupt und nicht mehr lauffähig war.

Um zu erkennen, ob man sich auf der produktiven Seite oder auf der Test-Umgebung befindet, hatten wir unteranderem früher via Composed Looks in der Test-Umgebung einen eigenen Look definiert, der im Farbton von der produktiven Seite abwich.

Im Prinzip also ein Look, mit der selben Masterpage, aber einem anderen *.spcolor-File, welches für das Test-System andere Farbtöne enthielt.

Nach dem Einspielen der neuen *.spcolor-Datei und aktivieren des gewünschten Composed Looks stellte sich jedoch Ernüchterung ein, die Seite sah genau so aus wie vorher. In der Preview des Composed Looks war schon zu erkennen, dass sich nichts ändern würde.

Gut, an was könnte das liegen? SPColor-Datei eingecheckt und veröffentlicht? Ja. Composed Look nochmals wegwechseln und dann erneut probieren – brachte nichts. IISReset durchführen – brachte auch nichts? Vielleicht den Object-Cache auf allen Servern (hier gab es nur einen) leeren? Hat auch nichts gebracht.

Eine Überprüfung der SPColor-Datei zeigte, dass sie die richtigen Werte enthielt, sie war auch veröffentlicht. Mit den Developer Tools im Browser wurde dann geprüft, durch welche CSS-Regel dieser Style appliziert wurde.

Der (falsche) Style entstammte von einer Themed-CSS-Datei. Beim Anwenden eines Composed Looks, werden die Werte aus der SPColor-Datei in eine zum Theme zugehörige CSS-Datei geschrieben (/Catalog-Bibliothek), die Datei hatte auch den richtigen Namen, aber die falschen Werte die nicht mit denen aus der SPColor-Datei übereinstimmten.

Ich erinnerte ich mich daran, dass ich so ein Problem schon einmal hatte und es daran lag, dass im Blob-Cache noch alte Versionen der CSS-Dateien (und anderer Dateien) vorhanden waren und diese dann an den Browser gesendet wurden, anstelle der aktuellen/korrekten.

Zuerst habe ich versucht, den Blob-Cache mittels PowerShell (SharePoint Management Shell) zu leeren:

$webApp = Get-SPWebApplication "<WebApplicationURL>"
 [Microsoft.SharePoint.Publishing.PublishingCache]::FlushBlobCache($webApp)

Leider hatte auch das nichts gebracht, nach einem erneuten aktivieren des gewünschten Looks, war immer noch alles beim Alten. Auch nach einem erneuten IISReset.

Ich wusste, dass man den Cache auch manuell löschen kann. Um auf Nummer sicher zu gehen, dass es wirklich nicht an diesem Cache liegt, beschloss ich auch dies auszuprobieren.

Dazu mus man wie folgt vorgehen:

  • web.config Datei der Web-Applikation ermitteln, öffnen und darin den Blob-Cache deaktivieren (Tag „BlobCache“ suchen und das Attribut „enabled“ auf „false“ stellen – dies muss auf jedem SharePoint-Server der Farm durchgeführt werden
  • Den Ordner des Blob-Caches im Dateisystem auf jedem Server löschen (den Pfad zum Ordner findet ihr auch in der web.config, im BlobCache-Tag)
  • IISReset durchführen
  • Nun den Blob-Cache wieder auf jedem Server der Farm aktivieren, dazu die web.config-Dateien erneut anpassen (BlobCache-Tag Attribut „enabled“ nun auf „true“ setzen)
  • Erneut einen IISReset durchführen (sollte durch das Speichern der web.config eigentlich automatisch geschehen)

Nachdem ich diese Schritte durchgeführt hatte, ging ich in den Websiteeinstellungen erneut auf „Aussehen ändern“ (Composed Looks) und wählte den gewünschten Look aus. Im Preview erschien immer noch der falsche Farbton. Ich habe den Look trotzdem angewendet und siehe da, auf der Webseite war dann nun doch der richtige Farbton endlich vorhanden.

Die (themed) CSS-Datei wurde nun korrekt neu erstellt, mit dem gewünschten, angepassten Farbton.

Hier noch weitere Informationen zum Thema Blob Cache, auf dem Blog von Karin Bosch, dort musste ich auch wieder einige der manuellen Schritte nachschlagen, da ich sie selber nicht mehr alle wusste 🙂 :
https://karinebosch.wordpress.com/my-articles/improving-performance-of-sharepoint-sites/part-3-blob-caching-in-sharepoint-2010/

Kategorien
IT SharePoint

SharePoint: client-seitiger Datei-Upload mit Metadaten

Vor ein paar Monaten bekam ich eine Anfrage, ein Skript zu entwickeln, um eine Datei mit Metadaten in eine SharePoint-Dokumentbibliothek hochzuladen.

Das Skript würde durch eine Workflow-Anwendung angestossen werden, welche unter anderem PowerShell-Skripts starten und an diese Parameter übergeben kann.

Diese Anwendung lief jedoch nicht auf einem SharePoint-Server, so war es mir nicht möglich das Server Side Object Model zu verwenden, welches ich im Normalfall für ein solches Szenario gewählt hätte und welches ich auch heute noch bevorzuge, da es noch immer mächtiger ist (als das Client Side Object Model) und man auf das ganze Async-„Gemurks“ verzichten kann.

Der eigentliche Datei-Upload gestalltet sich mit CSOM auch äusserst einfach. Was mir aber grosse Probleme bereitete, war die Übergabe von Benutzer-Logins an das Skript, respektive, dass diese dann korrekt verwendet wurden.

In den ersten Anläufen hatte ich das Phänomen, dass wenn ich Benutzer-Logins (via String-Array) übermittelte, nur die ungeraden übernommen wurden.

Initial hatte ich das sehr dirty (Schande über mich) gelöst, indem ich einfach alle Einträge im Array jeweils verdoppelte.
Da ich aber solche Hacks nicht sehr sauber finde und diese ja nicht gerade sehr wartungsfreundlich sind, da wenn jemand der in einem Jahr den Code anschaut sich denkt „Wtf?! Warum?“, beschloss ich der Sache etwas tiefer auf den Grund zu gehen und eine bessere Lösung zu finden.

Leider schien ich mit dieser Problematik alleine zu sein. Ich fand im Internet zwar einige Beispiele für das setzen von Metadaten via CSOM bei Datei-Uploads, jedoch nie eine Beispiel für das setzen eines „People Picker“-Feldes, welches mehrere Werte erlaubte (FieldUserValueCollection).

Nach etwas Debugging und Ausprobieren konnte ich das Problem aber dann doch noch eingrenzen und schlusssendlich lösen.
Das Problem lag daran, dass bei den Einträgen für die Benutzer auch ein Wert (LookupValue) vorhanden sein muss (welcher, wenn ich mich richtig erinnere, nicht gesetzt werden konnte, da der Setter read-only ist).
Der Feldwert darf dann nicht einfach als FieldUserValue-Array gesetzt werden, sondern muss folgendes Format haben, welches ihr vielleicht schon kennt, wenn ihr auf einem Lookup-Wert schon einmal „.ToString()“ ausgeführt habt („[User-ID];#[User-Anzeigename]“):

"32;#Peter Muster;#22;#Petra Meier;#31;#Hans Meister;#"

Hier der Auszug der relevanten Stelle, mit der Erstellung eines „FieldUserValue“-Objektes (gut könnte man strenggenommen auch weglassen und den $valueUsers-String direkt aufbauen) und der anschliessenden Umwandlung der Collection in einen String:

{
    ...
    $user = $web.EnsureUser($userLogin)
    # User must be loaded to pass a valid user ID as lookup ID
    $ctx.Load($user)
    $ctx.ExecuteQuery()
    if ($user.Id -ne $null)
    {
        $fieldUserValue = New-Object Microsoft.SharePoint.Client.FieldUserValue
        $fieldUserValue.LookupId = $user.Id
        $userFieldValueCollection += $fieldUserValue
    }
    else
    {
        throw "User for login '$userLogin' not found!"
    }
}

$valueUsers = ($userFieldValueCollection | % { $_.LookupId.ToString() + ";#DUMMY;#" }) -join "" 

Hier noch das komplette Beispiel-Skript:

param(
    [Parameter(mandatory=$true)]
    [string] $SourceFilePath, 
    [Parameter(mandatory=$true)]
    [string] $ValueTitle,
    [Parameter(mandatory=$true)]
    [string] $ValueChoice,
    [Parameter(mandatory=$false)]
    [string] $ValueComments,
    [Parameter(mandatory=$false)]
    [bool] $ValueSendNotification = $true,
    [Parameter(mandatory=$true)]
    [string[]] $ValueUserLogins
)

# Set target site collection URL
$siteUrl = "http://server.name"
$targetLibraryInternalName = "Documents"

# Get script directory
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path

# Load SharePoint Client Dlls
Add-Type -Path (Resolve-Path (Join-Path -Path $ScriptDir -ChildPath "Microsoft.SharePoint.Client.dll"))
Add-Type -Path (Resolve-Path (Join-Path -Path $ScriptDir -ChildPath "Microsoft.SharePoint.Client.Runtime.dll"))

# Create Client Context (connect to target site)
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$web = $ctx.Web 
$ctx.Load($web) 
$ctx.ExecuteQuery()
Write-Host "Connected to web: $($web.Url)" -ForegroundColor Yellow

# Get target library
$targetLibrary = $ctx.Web.GetList($web.ServerRelativeUrl + "/" + $targetLibraryInternalName)

# Prepare 'Viewers' value
[Microsoft.SharePoint.Client.FieldUserValue[]]$userFieldValueCollection = @()
foreach ($userLogin in $ValueUserLogins)
{
    $user = $web.EnsureUser($userLogin)
    # User must be loaded to pass a valid user ID as lookup ID
    $ctx.Load($user)
    $ctx.ExecuteQuery()
    if ($user.Id -ne $null)
    {
        $fieldUserValue = New-Object Microsoft.SharePoint.Client.FieldUserValue
        $fieldUserValue.LookupId = $user.Id
        $userFieldValueCollection += $fieldUserValue
    }
    else
    {
        throw "User for login '$userLogin' not found!"
    }
}
# Needs beside the LookupId also a dummy LookupValue, otherwise only odd entries are stored!
$valueUsers = ($userFieldValueCollection | % { $_.LookupId.ToString() + ";#DUMMY;#" }) -join "" 

# Upload file
$fci = New-Object Microsoft.SharePoint.Client.FileCreationInformation
$fci.Overwrite = $true
$fci.Content = [System.IO.File]::ReadAllBytes($SourceFilePath)
$fci.URL = [System.IO.Path]::GetFileName($SourceFilePath)
$uploadFile = $targetLibrary.RootFolder.Files.Add($fci)
Write-Host "File uploaded to target library"

# Set metadata properties on uploaded file (must be done on item of file)
Write-Host "Writing metadata to fields"
$listItem = $uploadFile.ListItemAllFields
$listItem["Title"] = $ValueTitle
$listItem["MyChoice"] = $ValueChoice
$listItem["Comments"] = $ValueComments
$listItem["SendNotification"] = $ValueSendNotification
$listItem["Readers"] = $valueUsers
$listItem.Update()
Write-Host "Executing pending queries (write metadata to file)"

$ctx.Load($uploadFile)
$ctx.Load($listItem)
$ctx.ExecuteQuery()

Write-Host "Uploaded file '$($uploadFile.Name)', received ID: $($listItem.Id)" -ForegroundColor Yellow

Das Skript kann wie folgt aufgerufen werden:

.\Skript.ps1 -SourceFilePath "X:\SourceFiles\Test Document.docx" -ValueTitle "Title Value" -ValueChoice "My Choice Value" -ValueComments "Test Comment" -ValueUserLogins @("Domain\username1", "Domain\username2") –ValueSendNotification $true 

Wichtig: damit ihr via PowerShell CSOM verwenden könnt, müsst ihr noch die SharePoint-Client-DLLs zur Verfügung haben (findet ihr im SharePoint-Installationsverzeichnis) und diese im Skript Verzeichnis ablegen, darum die beiden Aufrufe im Skript:

# Load SharePoint Client Dlls
Add-Type -Path (Resolve-Path (Join-Path -Path $ScriptDir -ChildPath "Microsoft.SharePoint.Client.dll"))
Add-Type -Path (Resolve-Path (Join-Path -Path $ScriptDir -ChildPath "Microsoft.SharePoint.Client.Runtime.dll"))

Kategorien
IT SharePoint

SharePoint: Degradierte Search-Index-Komponente korrigieren

Es kann manchmal vorkommen, dass eine Index-Partition der SharePoint Search Service Application degradiert, sich also in einem nicht mehr funktionstüchtigen Zustand befindet. Versucht man auf einer SharePoint-Seite dann eine Suche zu starten, wird man von einer Fehlermeldung begrüsst.

Der Administrator sieht dann in der Search Service Administration in der Central Administration ungefähr folgendes Bild:

Degraded SharePoint Index ComponentDas Ausrufezeichen signalisiert direkt, dass etwas nicht in Ordnung ist.

Um das Problem zu lösen, kann es genügen, die Index Component neu zu erstellen. Falls es daran liegt, dass die Disk, auf welcher die Index-Partition liegt, vollgelaufen ist, kann man auch eine zusätzliche Index-Komponente für die Partition auf einer anderen Disk erstellen.

Um eine neue Komponente zu erstellen, muss man PowerShell verwenden. Wie bei allen Änderungen an der Search Topology ist das Vorgehen so, dass man zuerst sich die aktuelle Topologie holt, diese dann klont, am Klon Anpassungen vornimmt und dann den Klon zur aktiven Topologie erklärt. Es ist ausserdem nötig, dass für das Vorgehen eine Index-Partition ohne Fehler vorhanden ist, das heisst, wenn nur eine vorhanden ist und diese einen Fehler hat, müsste man zuerst einen Index Reset machen. Die bestehende Index Component kann nicht entfernt werden, wenn sie die Letzte ist – das heisst, dass man zuerst in einem Durchgang eine neue Index Component hinzufügen muss, dann die Änderungen aktivieren muss und erst in einem zweiten Durchgang kann dann die problematische Komponente entfernt werden.

Hier der PowerShell-Code dazu:

# ----- Teil 1, neue Index-Komponente hinzufügen

# Aktive Topologie holen
$ssa = Get-SPEnterpriseSearchServiceApplication
$active = Get-SPEnterpriseSearchTopology -Active -SearchApplication $ssa 

# Einen Klon der aktiven Topologie erstellen
$clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone -SearchTopology $active

# Search Service Instance des Servers "SERVER1" holen (Namen muss natürlich angepasst werden)
$hostServer1 = Get-SPEnterpriseSearchServiceInstance -Identity "SERVER1"

# Neue Index-Komponente erstellen und zum Klon hinzufügen, Ziel-Instanz auf den gewünschten Server konfigurieren
$newIndexComponent = New-SPEnterpriseSearchIndexComponent -SearchTopology $clone -SearchServiceInstance $hostServer1 -IndexPartition 0

# Verzeichnis für Index (auf der vorhin mitgegebenen Instanz (SearchServiceInstance)) einrichten
$newIndexComponent.RootDirectory = "I:\Index"

# Überprüfung der Konfiguration der Komponenten im Klon (muss nun die neue Komponente und auch die alte anzeigen)
Get-SPEnterpriseSearchComponent -SearchTopology $clone

# Den Klon als neue, aktive Topologie aktivieren (Änderungen werden erst jetzt aktiv!)
Set-SPEnterpriseSearchTopology -Identity $clone

# Überprüfung der aktiven Topologie
Get-SPEnterpriseSearchTopology -Active -SearchApplication $ssa

# ----- Teil 2, alte Index-Komponente entfernen

# Aktive Topologie neu holen
$active = Get-SPEnterpriseSearchTopology -Active -SearchApplication $ssa

# Einen Klon der aktiven Topologie erstellen
$clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone -SearchTopology $active

# Search Components für die Topologie anzeigen (Component ID der degradierten Komponente kopieren)
Get-SPEnterpriseSearchComponent -SearchTopology $clone

# Index-Komponente (ID hier ersetzen/eintragen) aus der Topologie (Klon) entfernen
Remove-SPEnterpriseSearchComponent -Identity <Component ID> -SearchTopology $clone

# Den Klon als neue, aktive Topologie aktivieren
Set-SPEnterpriseSearchTopology -Identity $clone

# Überprüfung der aktiven Topologie
Get-SPEnterpriseSearchTopology -Active -SearchApplication $ssa

# ----- Teil 3, alte Topologie (da nun inaktiv) löschen (wird aktuell noch mit dem Namen '$active' referenziert)
Remove-SPEnterpriseSearchTopology -Identity $active<span style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" data-mce-type="bookmark" class="mce_SELRES_start"></span>

Nun sollte die Search Service Administration wieder eine saubere Topologie ohne Fehlermeldungen anzeigen.

Kategorien
IT SharePoint

Alle SharePoint-Solutions mit einem einzigen PowerShell-Befehl exportieren

Wenn man alle installierten Farm-Solutions/WSPs einer SharePoint-Farm mit einem simplen, einzeiligen PowerShell-Befehl exportieren möchte (vielleicht weil man sie sonst nirgends mehr hat oder nicht mehr sicher ist welche Version installiert ist – beides sollte man natürlich vermeiden), dann kann man in der SharePoint Management Shell einfach folgenden Befehl absetzen:

(Get-SPFarm).Solutions | ForEach-Object{$var = (Get-Location).Path + "\" + $_.Name; $_.SolutionFile.SaveAs($var)}

Die WSP-Datein befinden sich danach im aktuellen Working-Directory, welches man in der PowerShell „offen“ hatte, z.B. im Verzeichnis des aktuellen Benutzer-Kontos.

Kategorien
IT SharePoint

SharePoint: Inhaltsdatenbank-Version an SharePoint-Version angleichen

Wenn man in einer SharePoint-Installation ein neues Cumulatives Update installiert um Programm-Fehler zu beheben, so wird die gesamte SharePoint-Version hochgehoben, also die Assemblies/Dlls etc. aktualisiert und haben dadurch höhere Versionsnummern.

Die Inhaltsdatenbank (Content Database) wird grundsätzlich aber nicht automatisch aktualisiert und behält das alte Schema und auch die alte Versionsnummer bei.

In der Regel ist dies nicht weiter tragisch, aber unter Umständen kann es trotzdem nötig werden, dass man die Inhaltsdatenbank aktualisiert und damit ihre Version an diejenige des SharePoint-Serves angleicht, so zum Beispiel, wenn man ein Backup einer Site-Collection einspielen möchte (Restore-SPSite), welches von einer Site/Datenbank mit der höheren Versionsnummer, die des Servers, entstammt.

Um manuell die Inhaltsdatenbank zu aktualisieren, kann man sich der SharePoint Management Shell bedienen.

Zuerst sollte man die gewünschte Content Database in eine Variable laden (wobei man „SQLServerName\WSS_Content_Database_Name“ durch den entsprechenden Datenbank-Server und Namen der Inhaltsdatenbank ersetzen muss):

$contentdb = Get-SPContentDatabase | Where-Object {$_.Name -match "SQLServerName\WSS_Content_Database_Name"}

Ist unklar, wie die Inhaltsdatenbank heisst, so kann man zuerst einfach nur Get-SPContentDatabase eingeben um eine Auflistung aller Inhaltsdatenbanken zu erhalten. Sollte die gewünschte Datenbank nicht aufgeführt sein, sollte sicher gestellt werden, dass diese in der Central Administration im Status „Ready“ ist.

Danach kann man mit folgendem PowerShell-Befehl die Aktualisierung starten:

Upgrade-SPContentDatabase -Identity $contentdb

In der PowerShell wird der Fortschritt in Prozent angezeigt. Bei grösseren Datenbanken kann die Aktualsierung mehrere Minute bis hin zu Stunden dauern.

Weitere Informationen findet ihr in diesem Technet-Artikel: http://technet.microsoft.com/en-us/library/ff607813.aspx

Kategorien
IT SharePoint

SharePoint: Alle Elemente einer Liste mittels PowerShell löschen

Wenn man alle Listen-Elemente einer SharePoint-Liste löschen möchte, dann kann das manuelle Löschen mittels Browser sehr umständlich sein. Selbst in der Datasheet-View ist es sehr mühsam alle Elemente auf einen Schlag zu löschen, da bei grosser Anzahl an Elementen Timeouts auftreten können. Leider fehlt im GUI eine benutzerfreundliche Option im Sinne von „Lösche alle Elemente dieser Liste“.

Mit einem PowerShell-Skript kann man dies Vorhaben jedoch ziemlich einfach und nerven-schonend durchführen:

[System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")

# "Enter the site URL here"
$SITEURL = "http://siteCollectionUrl"

$site = new-object Microsoft.SharePoint.SPSite ( $SITEURL )
$web = $site.OpenWeb()
"Web is : " + $web.Title

# Enter name of the List below
$oList = $web.Lists["InternalListName"];

"List is :" + $oList.Title + " with item count " + $oList.ItemCount

$collListItems = $oList.Items;
$count = $collListItems.Count - 1

for($intIndex = $count; $intIndex -gt -1; $intIndex--)
{
        "Deleting : " + $intIndex
        $collListItems.Delete($intIndex);
}

Das Skript in einer „ps1“-Datei speichern (z.B. „DeleteAllListElements.ps1“), darin den URL der „Site Collection“ den internen Listennamen anpassen, danach direkt ausführen um die Elemente zu löschen.

Möchte man alle Elemente, einschliesslich aller Ordner und aller Elemente in Ordnern/Unterordnern löschen, so kann man das folgende PowerShell-Skript verwenden (auch hier muss man den URL und den Listennamen anpassen):

Add-PSSnapin Microsoft.SharePoint.PowerShell -erroraction SilentlyContinue

$web = Get-SPWeb "https://siteCollectionUrl"
$list= $web.GetList("/Lists/InternalListName")


foreach ($item in $list.items)
{
	$deleteItem = $list.GetItemById($item.ID)
	$deleteItem.Delete()
}

foreach ($folder in $list.Folders)
{
	$deleteFolder = $list.GetItemById($folder.ID)
	$deleteFolder.Delete()
}

 

Kategorien
IT SharePoint

SharePoint: TaxonomyHiddenList manuell aktualisieren

Unter gewissen Umständen und mit gewissen Cumulativen Updates für den SharePoint 2010 kann es vorkommen, dass die TaxonomyHiddenList nicht mehr auf dem aktuellsten Stand ist, da die Synchronisation mit dem Term-Store (Terminologiespeicher) nicht mehr fehlerfrei durchgeführt werden kann.

Abhilfe (wenn auch eventuell nur temporäre?)  schafft folgendes PowerShell-Skript:

$Assem = (
	"Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" ,
	"Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
)

$Source = @"
using Microsoft.SharePoint;
using Microsoft.SharePoint.Taxonomy;
using System;

namespace TaxonomyUpdater
{
	public static class TaxonomyHiddenList
	{
		public static void Update(string SiteUrl)
		{
			SPSite Site2Update = new SPSite(SiteUrl);
			TaxonomySession.SyncHiddenList(Site2Update);
			Site2Update.Dispose();
		}
	}
}
"@

Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp

Das Skript muss man in einen Text-Editor einfügen und als „UpdateTaxonomyHiddenList.ps1“ abspeichern.

Danach das Skript in einer PowerShell einmal ausführen, damit die Klasse verfügbar wird. Zum Schluss mit folgendem Befehl die Synchronisation starten:

[TaxonomyUpdater.TaxonomyHiddenList]::Update(“http://SharePointServer”)

Siehe auch: http://blogs.msdn.com/b/joerg_sinemus/archive/2011/03/03/terms-and-how-to-update-taxonomyhiddenlist-when-the-timer-job-was-not-able-to-update.aspx