Komfortable Logfileanalyse für Sharepoint


Andreas Rauch

Sicherlich kennen Sie  Sharepoint Fehlermeldung mit Hinweis auf eine KorrelationsID..! T’schulding, war eine rhetorische Frage Dann wissen Sie sicherlich auch, dass Sharepoint diese und viele weitere Ereignisse in seine ULS (Unified Logging Service) Protokolldateien wegschreibt. Sie kennen sicherlich auch das Verzeichnis (C:\Program Files\Common Files\Microsoft Shared\webserver Extensions\14\Logs) in dem standardmäßig die Dateien abgelegt werden und ebenso kennen sie auch das Format der Logfiles: Daraus nun den Fehler zu reproduzieren ist zugegebenermaßen sehr schwerfällig. Leichter wird es , wenn man ein Tool wie den SharepointLogviewer verwendet. Zu finden bei Codeplex: http://sharepointlogviewer.codeplex.com/ Für mich - als Datenbankfuzzi - ist das allerdings noch nicht das Amen in der Kirche. Denn immerhin muss man immer noch am Server sein um die Protokolle lesen zu können, ob lokal am Server oder per Remotesitzung. (Im Falle von mehreren Servern, evtl. auf allen) Da stehe ich schon eher auf bequemere Dinge, wie SQL Server. ULS Tracefiles in den SQL Server schreiben Sharepoint Server hat durchaus alle Mittel an Board, die Protokollierung in den SQL Server schreiben zu lassen. Die Mittel sind lediglich deaktiviert. In diesem Fall ist das Mittel zum Zweck lediglich ein Auftrag: Kontrollieren Sie doch mal in der Zentraladministration die Auftragsdefinitionen. Dort werden Sie einen Auftrag mit folgenden Namen finden: Diagnoseanbieter: Ablaufverfolgunsprotokoll Sobald Sie diese n Auftrag aktivieren und am besten auch gleich starten, werden sie im SQL Server folgendes finden: Eine Menge an Tabellen, darunter auch welche mit folgenden Namen: ULSTraceLog_Partition#  (# für 0 bis 31) und passen dazu eine Sicht ULSTRaceLog, die alle Tabellen abfrägt: Der große Vorteil ist nun, dass einerseits Abfragen auf die Logfiles per TSQL deutlich leichter zu bewerkstelligen sind und andererseits wir nun eine einzige zentralen Stelle haben, an der alle ULS Logfile Einträge aller in der Farm beteiligten Server zusammenkommen. Vollautomatisch! Die Suche nach bspw. KorrelationsID ist nun geradezu lächerlich einfach: Cool, oder? Wird aber noch besser. Im nächsten Artikel werde ich diese Lösung noch in die Zentraladministration einbauen, so dass nicht einmal eine SQL Abfrage gestartet werden muss.. Natürlich ohne Code. 

SharePoint-Apps ohne App-Catalog installieren

Sobald eine App fertig entwickelt wurde, kann diese in einen SharePoint Catalog hochgeladen werden und vom Anwender installiert werden. Evtl. möchte man aber Apps installieren sodaß die Anwender sie verwenden können. Die Anwender sollen aber nicht die App in andern Sites oder Webs installieren dürfen. Hierfür bietet die SharePoint-Powershell Commandlets um eine App zu deployen und zu installieren. Und das ganze auch für Webanwendungen, denen kein App-Catalog zugewiesen wurde. 1. Import-SPAppPackage als erstes muss das App-File hochgeladen werden. Dies wird mit dem Befehl “Import-SPAppPackage” erledigt. Der Parameter “Path” ist die Angabe der .app Datei. mit –Site wird die url der Sitecollection angegeben. Nur in Webs innerhalb der Sitecollection kann die App im nächsten Schritt installiert werden. Der dritte notwendige Parameter ist –Source. Dies ist ein Objekt vom Typ  SPAppSource . Damit wird angegeben woher die App installiert wird. z.b. Marketplace oder CorporateCatalog. Es gibt aber auch ObjectModel als Source. Dies wird genutzt um die App über die Powershell ohne AppCatalog zu installieren. Der erste Teil des Scripts sieht so aus: $app = Import-SPAppPackage -Path "C:\temp\AppToDeploy.app" -Site "http://sp1" -Source ObjectModel .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } 2. Install-SPApp Als nächstes muss die App auch installiert werden.  Dies wird mit “Install-SPApp” erledigt. Der Parameter –web gibt das Ziel an. Als Ziel kann nun nur ein Web dienen welches in der SiteCollection liegt, für die das App-File hochgeladen wurde. -Identity gibt das AppPackage an. Dies ist das Rückgabeobjekt des Import-SPAppPackage Befehls. die zweite Zeile des Scripts sieht nun so aus: Install-SPApp -Web "http://sp1" -Identity $app Der Rückgabewert ist ein AppInstance die auch mit Get-SPAppInstance aufgerufen werden kann: 1: AppPrincipalId : i:0i.t|ms.sp.int|cae02a3d-c65f-43f9-9efe-b1fdbc7c0a92@8be827ec-cd4b-4599-9435-52d93dadee5e 2: InError : False 3: App : Microsoft.SharePoint.Administration.SPApp 4: SiteId : 12a0cd84-1bb6-453a-b81d-29448cb18e9e 5: WebId : ef6dfe74-4c19-494f-af43-fa224d9b2644 6: Title : AppToDeploy 7: Id : 01bfba6c-6125-4460-b9f0-48374793919b 8: LaunchUrl : ~appWebUrl/Pages/Default.aspx?{StandardTokens} 9: Status : Installed 10: RemoteAppUrl : 11: AppWebFullUrl : http://app-daa8dcb3ef9bd6.spapps.grobl.local/SubWeb/AppToDeploy 12: SettingsPageUrl : .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }   Der Status (Zeile 9) wird zunächst auf “Installing” stehen und nach erfolgreicher Installation auf “Installed” wechseln. D.h. Get-SPAppInstance sollte mehrmals ausgeführt werden bis die App installiert ist.

SPSecurityTrimmedControl in Abhängigkeit von SharePoint Gruppen verwenden

SharePoint bietet standardmäßig die Möglichkeit, ein Steuerelement in ein sog. Security Trimmed Control einzubetten, um es lediglich für Benutzer mit einer bestimmten Berechtigung/Permission (z.B. ‘ManageLists’) zur Verfügung zu stellen. Alle anderen Benutzer, die nicht mindestens diese Berechtigung besitzen, bekommen somit den Inhalt des Security Trimmes Controls nicht angezeigt. Die Festlegung der Berechtigung geschieht über das Property “PermissionsString”. So weit so gut! Problematisch wird es erst, wenn die Bedingung nicht auf der zugewiesenen Berechtigung, sondern anhand der SharePoint Gruppe, in der sich der Benutzer befindet, basieren soll. Hierfür bietet SharePoint leider keine Out-of-the-Box Lösung an. Abhilfe kann in diesem Fall aber die Erstellung eines Custom Controls schaffen, welches von SPSecurityTrimmedControl abgeleitet wird. Dieses Tutorial soll zeigen, wie man mit Hilfe eines eigenen Controls gezielt Bereiche z.B. aus einem Formular nur für Benutzer einer bestimmten SharePoint Gruppe bereitstellen kann. Für alle anderen Nutzer ist dieser Bereich nicht sichtbar. Konkret soll eine Checkbox im Bearbeiten-Formular (EditForm) nur den Benutzern der Gruppe  “MyCustomGroup” bereitgestellt werden. Dazu sind folgende Schritte notwendig: Custom Control erstellen (abgeleitet von SPSecurityTrimmedControl) Dazu ist ein neues SharePoint Projekt in Visual Studio zu erstellen (Farm Solution) und eine Klasse mit folgendem Code hinzuzufügen: 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Web; 6: using System.Web.UI; 7: using Microsoft.SharePoint.WebControls; 8: using Microsoft.SharePoint; 9:   10: namespace GroupTrimmedControlSolution 11: { 12: public class GroupTrimmedControl : SPSecurityTrimmedControl 13: { 14: private List<string> groups = new List<string>(); 15: public string GroupsString 16: { 17: get 18: { 19: return string.Join(",", groups.ToArray()); 20: } 21: set 22: { 23: groups.AddRange(value.Split(new char[] { ',' }, 24: System.StringSplitOptions.RemoveEmptyEntries) 25: ); 26: } 27: } 28:   29: protected override void Render(HtmlTextWriter output) 30: { 31: if (!string.IsNullOrEmpty(GroupsString) && IsMember()) 32: { 33: base.Render(output); 34: } 35: } 36:   37: private bool IsMember() 38: { 39: using (SPWeb web = new SPSite(SPContext.Current.Web.Url).OpenWeb()) 40: { 41: bool isMember = false; 42: foreach (string group in groups) 43: { 44: isMember = web.IsCurrentUserMemberOfGroup(web.Groups[group].ID); 45: } 46: return isMember; 47: } 48: } 49: } 50: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Dadurch wird beim Deployment der Solution ein Control namens “GroupTrimmedControl” angelegt, welches später verwendet werden soll. Dieses besitzt ein property namens “GroupsString”, in welches später bei der Implementierung des Controls die benötigte Gruppe eingetragen wird. web.config an zwei Stellen modifizieren Damit das Control überhaupt verwendet werden darf, muss es in der web.config als “Safe Control” deklariert werden. Dies geschieht über das einfügen des folgenden Statements. <SafeControl Assembly="GroupTrimmedControlSolution, Version=1.0.0.0, Culture=neutral, PublicKeyToken=779b51b95268bfdd" Namespace="GroupTrimmedControlSolution" TypeName="*" Safe="True" SafeAgainstScript="False" /> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Die Informationen wie Version, Namespace, PublicKeyToken lassen sich nach dem Build der Solution im Global Assembly Cache (GAC) –> Rechtsklick auf den Namen der Assembly –> Eigenschaften auslesen.  Tipp: Alternativ kann Visual Studio auch veranlassen, dass beim Deployment der SafeControl-Eintrag automatisch hinzugefügt wird. Dies geschieht über das Hinzufügen eines neuen Moduls –> Property “SafeControlEntries” –> einen Eintrag hinzufügen (Bild links). Danach taucht der SafeControl-Eintrag direkt in der Manifestdatei der wsp mit auf und man braucht sich nicht mehr um das manuelle Hinzufügen des SafeControl-Eintrags zu kümmern (Bild rechts).   Die zweite Modifikation der web.config ist  nur notwendig, wenn das Control auch innerhalb einer WebParts genutzt werden soll. Im Bsp ist dies der Fall, da das Formular zum Bearbeiten von Listemneinträgen (EditForm) ein sog. DataFormWebpart beinhaltet, welches sämtliche Formularsteuerelemente beinhaltet. <add tagPrefix="GrpTrimCtrl" namespace="GroupTrimmedControlSolution" assembly="GroupTrimmedControlSolution, Version=1.0.0.0, Culture=neutral, PublicKeyToken=779b51b95268bfdd" /> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Dieses Statement sorgt dafür, dass der tag-Prefix auch innerhalb des WebParts verwendbar wird. Referenzen in der Masterpage oder der aspx-Page (Formular) einfügen Nachdem die Solution nun deployed wurde kann das neue Control innerhalb der Page  verwendet werden. Dazu muss es im Header-Bereich zunächst referenziert werden. <%@ Register TagPrefix="GrpTrimCtrl" Namespace="GroupTrimmedControlSolution" Assembly="GroupTrimmedControlSolution, Version=1.0.0.0, Culture=neutral, PublicKeyToken=779b51b95268bfdd" %> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Auch hier gilt es bei der Verwendung innerhalb des WebParts eine zusätzliche Deklaration im Webpartbereich selbst vorzunehmen. Und zwar muss der Namespace erneut mit dem Präfix verknüpft werden, damit das neue Control von der XSL Transformation verarbeitet werden kann. Folgendes Statement muss innerhalb des xsl:stylesheet Tags eingefügt werden: xmlns:GrpTrimCtrl="GroupTrimmedControlSolution" Implemetierung des Custom Controls Da nun alle Vorbereitungen getroffen sind, kann das Control an gewünschter Stelle mittels folgendem Statement eingebunden werden: <GrpTrimCtrl:GroupTrimmedControl id="hideBelegControl" runat="server" GroupsString="MyCustomGroup"> <!-- Controls, die betroffen sein sollen --> </GrpTrimCtrl:GroupTrimmedControl> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Im Bsp. sieht das ganze wie folgt aus: <GrpTrimCtrl:GroupTrimmedControl id="hideBelegControl" runat="server" GroupsString="MyCustomGroup"> <tr> <td width="190px" valign="top" class="ms-formlabel"> <H3 class="ms-standardheader"> <nobr>BelegeFehlen</nobr> </H3> </td> <td width="400px" valign="top" class="ms-formbody"> <SharePoint:FormField runat="server" id="ff6{$Pos}" ControlMode="Edit" FieldName="BelegeFehlen" __designer:bind="{ddwrt:DataBind('u',concat('ff6' $Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@BelegeFehlen')}" /> <SharePoint:FieldDescription runat="server" id="ff6description{$Pos}" FieldName="BelegeFehlen" ControlMode="Edit" /> </td> </tr> </GrpTrimCtrl:GroupTrimmedControl> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Hier wird deutlich, dass eine komplette Zeile mit zwei Spalten und darin enthaltenen Steuerelementen innerhalb der GroupTrimmedControls liegen – und somit für alle Benutzer, die nicht der Gruppe “MyCustomGroup” angehören, ausgeblendet werden. Anmerkung: Das Einfügen des ASP Codes für das Control im SharePoint Designer kann zu Problemen führen (in meinem Fall hat er im Start-Tag eine “to lower” case correction vorgenommen, im End-Tag aber nicht –> Ergebnis: Webpart konnte nicht dargestellt werden). Daher bitte ein anderes Tool (ggf. Notepad etc.) verwenden und die aspx-Datei anschließend über den SharePoint Designer oder WebDAV an entsprechende Stelle hochladen. Schließlich schaut das Ergebnis wie folgt aus. Der Nutzer Max Muster befindet sich in der besagten Gruppe und hat Zugriff auf die im GroupTrimmedControl platzierten Steuerelemente (Bild links). Ein anderer User, der nicht der Gruppe angehört, bekommt das Control samt Inhalt nicht angezeigt (Bild rechts).  

Office Web App Server von SharePoint Farm entfernen

meistens findet man Beschreibung wie eine Farm aufzusetzen ist. Aufgrund eines plötzlichen Verlustes meiner Virtuellen Maschine mit der OWA Installation musste ich mich damit beschäftigen einen OWA Server aus der Farm zu entfernen: Ein simples Powershell Statement reicht: Remove-SPWOPIBinding –All:$true .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

Anpassung des “New Document” Hyperlink zum Aufruf der Dokumentvorlage (z.B. mit Word) einer SharePoint 2013 Bibliothek

Die Standardansicht einer SharePoint 2013 Dokumentbibliothek enthält einen kaum übersehbaren Hyperlink mit einem Plus-Symbol und der Beschriftung “Add Document”. Bei einem Klick auf diesen Link gelangt der Benutzer in einen Upload Dialog, wo ein Dokument hochgeladen werden kann. Oftmals sollen aber direkt aus einer Vorlage (z.B. Word-Template) heraus neue Dokumente erstellt, statt vorhandene hochgeladen werden. So stellt sich die Frage, wie es möglich ist, den Hyperlink “Add Document” so zu bearbeiten, dass beim Klick kein Dokumnt hochgeladen, sondern basierend auf der Dokumentvorlage der Bibliothek erstellt werden kann. Dies wird in folgendem Artikel erklärt und ist in wenigen Minuten direkt mit SharePoint Boardmitteln realisierbar (Inhaltseditor bzw. Content Editor Webpart). Ziel ist es, einen neuen Hyperlink mit modifizierter onclick Methode zu erzeugen. Der neue Hyperlink soll genau wie der standard Link aussehen, weshalb dieser einfach kopiert werden kann. Dies funktioniert mit den Internet Explorer Entwicklertools sehr gut. Taste F12 –> Klick auf das Mauscursor Symbol –> Add Document Link lokalisieren und anklicken –> im Quellcode links wird der relevante Bereich selektiert. Nun mit Rechtsklick auf den <a title> Tag –> Copy outer html und schon befindet sich der html Code des Add Document Links in der Zwichenablage.   Als nächstes muss die Ansicht der Bibliothek im Page Edit Mode bearbeitet werden. Dies er ist erreichbar über das Zahnrad –> Edit page. Hier wird nun ein Content Editor Webpart hinzugefügt. Link “Add Webpart” –> Kategorie Media and Content –> Content Editor.    Nach einem Klick auf “Click here to Add Content” taucht im Ribbon “Format” –> Bereich “Markup” der Button “Edit Source” auf, mit welchem ein Codeeditor geöffnet wird, welcher in der Lage ist, html, css und javascript zu interpretieren. Hier wird nu der kopierte Code des Hyperlinks eingefügt. Damit statt einem Dokumentupload die Vorlage mit der passenden Client Applikation gestartet wird, muss die onclick Methode modifiziert werden. Folgender Javascript Aufruf eignet sich dafür: CoreInvoke('createNewDocumentWithRedirect2', event, '[Template Url]', '[Save Location]', 'SharePoint.OpenDocuments', false, '[DocumentHandler]', true, 1, 'ms-word') .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Kurze Erklärung der drei Platzhalter: [Template Url] steht für die Url der Dokumentvorlage, welche an der Bibliothek hängt. Sie liegt im versteckten Unterordner “Forms” der Bibliothek. [Save Location] ist die Url der Bibliothek. [DocumentHandler] ist eine Applicationpage im Layouts-Verzeichnis (CreateDocument.aspx), welche zur Initiierung aufgerufen wird und als Parameter ebenfalls die Template Url erwartet. Ein Beispielaufruf der CoreInvoke Methode könnte so aussehen: CoreInvoke('createNewDocumentWithRedirect2', event, 'http://server1/Shared%20Documents/Forms/template.dotx', 'http://server1/Shared%20Documents/', 'SharePoint.OpenDocuments', false, 'http://server1/_layouts/15/CreateNewDocument.aspx?id=http://server1/Shared%20Documents/Forms/template.dotx', true, 1, 'ms-word') .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Diese CoreInvoke-Methode ist nun in de Hyperlink in den onClick=”” Eventhandler einzufügen und der alte Aufruf zu ersetzen. Zusätzlich muss noch das href-Attribut entfernt oder geleert werden. Damit der Hyperlink auch die gleiche Schriftgröße wie der standard Add Document Hyperlink besitzt, muss ganz am Ende im <span> Tag noch die CSS Klasse “ms-textXLarge” angegeben werden. Der Code für den neuen Create new document Link lautet im Bsp. wie folgt: <a title="Add a new item to this list or library." class="ms-heroCommandLink" id="idHomePageNewDocument-WPQ2" onclick="CoreInvoke('createNewDocumentWithRedirect2', event, 'http://server1/Shared%20Documents/Forms/template.dotx', 'http://server1/Shared%20Documents/', 'SharePoint.OpenDocuments', false, 'http://server1/_layouts/15/CreateNewDocument.aspx?id=http://server1/Shared%20Documents/Forms/template.dotx', true, 1, 'ms-word')" href="" target="_self" data-viewctr="0"><span class="ms-list-addnew-imgSpan20"><img class="ms-list-addnew-img20" id="idHomePageNewDocument-WPQ2-img" src="/_layouts/15/images/spcommon.png?rev=23"></span><span class="ms-textXLarge">Create new document</span></a> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Nach dem Speichern und Verlassen des PageEditModes befinden sich nun zwei Hyperlinks auf der Page. Der Standard Link zum Hochladen von Dokumenten und der neu eingefügte Link zum Erstellen eines neuen Dokuments auf Basis der Dokumentvorlage. Soll nun noch der Standard Hyperlink entfernt werden, kann dies ganz einfach im PageEditMode über die Webparteigenschaften der Dokumentbibliothek erreicht werden, indem das Attribut ToolbarType auf  “No Toolbar” eingestellt wird. Schlussendlich taucht nur noch der neu erstellte “Create Document Hyperlink” auf, bei dessen Klick sich die Clientapplikation mit der Dokumentvorlage (im Bsp. Microsoft Word) öffnet.

Website Treeview auf SiteCollection Ebene als WebPart bereitstellen

Grundsätzlich bietet die Out-of-the-Box Strukturansicht von SharePoint 2010 eine gute und einfache Möglichkeit, Websiteinhalte als Baumansicht in der Schnellstartleiste/Current Navigation bereitzustellen. Leider sind die Konfigurationsmöglichkeiten relativ begrenzt, so dass z.B. nicht ohne Weiteres die komplette Websitehierarchie abgebildet werden kann, da standardmäßig die aktuelle Website (Current Web Context) als oberster Knotenpunkt für die TreeView dient. Folgende Grafik zeigt dies am Beispiel einer Subwebsite zweiter Ebene, welche NICHT ihre übergeordneten Websites anzeigt:  Daher kann es durchaus sinnvoll sein, die Root Website als Wurzelknotenpunkt der TreeView zu setzen. Dies kann durch serverseitigen Code geschehen. Im folgenden Beitrag wird gezeigt, wie es mit Hilfe eines Visual Webparts mit benutzerdefinierter DataSource möglich ist, eine TreeView auf SiteCollection Ebene für SharePoint 2010 bereit zu stellen, welche dann als WebPart entweder auf eine beliebige aspx-Page oder direkt in die Masterpage eingebunden werden kann. Als erstes ist dazu in Visual Studio ein neues SharePoint Projekt anzulegen (Visual WebPart) und das VisualWebPart im Solution Explorer ggf. entsprechend aussagekräftig zu benennen, z.B. “WebSitesTreeView”. Dadurch ändert sich das “Title” Property innerhalb der .webpart-Datei, welches dem Anzigenamen des WebParts z.B. beim Hinzufügen in der Weboberfläche entspricht.   Im User-Control des WebParts (.ascx-Datei) ist am Ende folgender Code einzufügen: <div id="Div1" class="ms-quicklaunchouter" runat="server"> <div class="ms-treeviewouter"> <SharePoint:SPRememberScroll runat="server" id="RememberScroll1" onscroll="javascript:_spRecordScrollPositions(this);" style="width: 220px;"> <Sharepoint:SPTreeView runat="server" id="TreeView1" ShowLines="false" DataSourceId="CustomTreeViewDataSource" ExpandDepth="0" SelectedNodeStyle-CssClass="ms-tvselected" NodeStyle-CssClass="ms-navitem" NodeStyle-HorizontalPadding="2" SkipLinkText="" NodeIndent="8" ExpandImageUrl="/_layouts/images/tvclosed.png" ExpandImageUrlRtl="/_layouts/images/tvclosedrtl.png" CollapseImageUrl="/_layouts/images/tvopen.png" CollapseImageUrlRtl="/_layouts/images/tvopenrtl.png" NoExpandImageUrl="/_layouts/images/tvblank.gif" NodeWrap="True" > </Sharepoint:SPTreeView> </Sharepoint:SPRememberScroll> </div> </div> <SharePoint:SPHierarchyDataSourceControl runat="server" id="CustomTreeViewDataSource" ShowDocLibChildren="false" ShowListChildren="false" ShowFolderChildren="false" IncludeDiscussionFolders="false" /> Das “SPTreeView” Steuerelement definiert dient zur Ausgabe der im “SPHierarchyDatasourceControl” festgelegten Datenquelle als Baumansicht. Zusätzlich wird im Control der Datenquelle angegeben, dass keine DokumentBibliotheken, Listen, Unterordner und Diskussionsforen in der Baumansicht angezeigt werden. Damit die Datenquelle nun auch mit der Root-Website der aktuellen SiteCollection befüllt werden kann, ist folgende Codezeile im Code behind der User Control (.ascx.cs-Datei) in der Page_Load Methode einzufügen.  CustomTreeViewDataSource.RootWebId = SPContext.Current.Site.RootWeb.ID.ToString(); .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Dadurch wird Beim Laden des WebParts der Kontext der aktuellen SiteCollection ermittelt und dessen Root-Website als Wurzelknoten in die Datenquelle (SPHierarchyDataSourceControl) der TreeView festgelegt. Dies geschieht über die ID der Root-Website. Anschließend kann die Solution Debuggt/Deployt werden. Wenn das WebPart auf einer beliebigen aspx-Page einer SharePoint Website hinzugefügt werden soll, ist dies nun über den Standardweg in der Weboberfläche möglich (zu finden unter der Standardkategorie “Custom”). Das WebPart kann aber auch direkt in der Masterpage implementiert werden. Dazu muss diese im SharePoint Designer geöffnet und das WebPart an gewünschter Stelle, z.B. in der Current Navigation oberhalb des “PlaceHolderQuickLaunchBottom” Controls, hinzugefügt werden. Dies geschieht über das Ribbon –> Insert –> WebPart –> Custom –> Titel des WebParts   Nun muss die Masterpage nur noch gespeichert und ggf. veröffentlicht werden. Die Ergebnisse der beiden Varianten (Einbindung in aspx-Page bzw. Masterpage) entsprechen dann der folgenden Abbildung:

Wochentage ermitteln und berechnen mittels InfoPath und SharePoint

SharePoint erlaubt bekanntermaßen die Verwendung von Formeln zur Spaltenberechnung. InfoPath hingegen ist nicht in der Lage, diesen kompletten Funktionsumfang abzubilden. Benötigt man beispielsweise den Wochentag eines bestimmten Datums, so gibt es standardmäßig keine Möglichkeit, dies ohne Weiteres mit InfoPath Boardmitteln abzubilden. An dieser Stelle kommt wieder der SharePoint ins Spiel, welcher im Gegensatz zu InfoPath die WOCHENTAG()-Funktion kennt, um den Wochentag eines bestimmten Datums zurückzugeben. Im Folgenden wird gezeigt, wie diese Werte auch in InfoPath-Formularen verwendet werden können. Weiterhin beschreibt dieser Artikel die Möglichkeit, Wochentage nach Ablauf einer bestimmten Zeitperiode (z.B. 1 Tag, 3 Tage) zu berechnen und dabei sogar das Wochenende zu berücksichtigen – d.h., zu “überspringen”. Zunächst wird eine SharePoint Liste benötigt, welche die Wochentage berechnet und später von InfoPath aus angesprochen werden kann. Die Liste “Wochentage” erhält zwei neue Spalten: eine namens Datum vom Typ “Datum” sowie eine namens “Wochentag” vom Typ “Berechnete Spalte”. Letztere erhält als Formel den Eintrag: =TEXT(WOCHENTAG(Datum);"dddd") Die Spalte Titel wird im Beispiel auf optional gesetzt und ausgeblendet, da diese nicht benötigt wird. Diese Liste muss nun mit den gewünschten Datumswerten befüllt werden und berechnet für diese automatisch die Wochentage. Das Befüllen kann z.B. mit der Datenblattansicht gelöst werden. Dazu muss einfach das erste zu verwendende Datum (im Bsp. 1.1.2013) eingetragen und die Zelle per Drag&Drop beliebig oft vervielfältigt werden. Nachdem die Listeneinträge erzeugt wurden, können diese per InfoPath über eine sekundäre Datenverbindung ausgelesen werden. Ribboneintrag Daten –> Datenverbindungen –> Hinzufügen –> Empfangen –> von SharePoint –> SP Website eintragen –> Liste mit Wochentagen wählen. Als auszuwählende Felder müssen “Datum” und “Wochentag” ausgewählt werden. Weiterhin ist ein Name der Datenverbindung einzutragen und das automatische Abrufen der Verbindung beim Formularladevorgang zu aktivieren.  Es sei vorausgesetzt, dass bereits ein InfoPath-Formular mit folgenden Feldern und Steuerelementen existiert: - Beginn (Typ: Datum mit Steuerelement Datumsauswahlfeld) - Wochentag Beginn (Typ: Text mit Steuerelement Textfeld) - Dauer (Typ: Zahl mit Steuerelement DropDownListe –> Auswahlmöglichkeiten “1 Tag” (Wert=1), “3 Tage” (Wert=3), “Arbeitswoche” (Wert=7)) - Ende (Typ: Datum mit Steuerelement Textfeld) - Wochentag Ende (Typ: Text mit Steuerelement Textfeld) Nun ist in den Feldern “Wochentag Beginn” und “Wochentag Ende” jeweils ein Standardwert zu hinterlegen, welcher bezogen auf “Beginn” bzw. “Ende” jeweils die Wochentage aus der SharePoint-Liste abruft. Dabei ist zu beachten, dass ein Filter zu setzen ist, damit das Steuerelement weiß, welches der x Elemente aus der SharePoint Liste abzurufen ist. Das Matching geschieht über das Datum. Dies geschieht z.B. für “Wochentag Beginn” wie folgt: Standardwert –> f(x) –> Feld oder Gruppe einfügen –> Sekundäre Datenverbindung –> dataFields –> Wochentag –> Daten filtern –> Hinzufügen –> Filterbedingung: “Datum” “enthält” –> Feld oder Gruppe einfügen –> Primäre Datenquelle –> Beginn Das ganze liest sich dann in SQL Logik etwa so: select “Wochentag” from “Sekundäre Datenverbindung.SharePoint Wochentagsliste” where “Datum” contains “Primäre Datenverbindung.Datum Beginn”. Wichtig ist, dass als Operator “enthält” (contains) und nicht “ist gleich” (equals) verwendet wird. Dies kommt daher, dass der Wert aus der SharePoint Datumsspalte immer auch die Uhrzeit mit enthält, selbst wenn diese nicht angezeigt wird (ISO 8601 Wert mit Nullen als Uhrzeit, z.B. 2013-01-01T00:00:00). Somit würde ein Join über “equals” fehlschlagen (2013-01-01T00:00:00 != 2013-01-01). Damit ausgeschlossen werden kann, dass der Beginn außerhalb der Arbeitswoche liegt, muss dies mit einer Validierungsregel unterbunden werden: DropDownListenfeld –> Regeln –> Neu –> Überprüfung. Eine mit “ODER” verknüpfte Bedingung prüft, ob der Wert von WochentagBeginn gleich den Textwerten “Samstag” bzw. “Sonntag” entspricht. Ist dies der Fall, soll eine Fehlermeldung ausgegeben werden. Um die eigentliche Berechnung durchführen zu können kommt nun die AddDays-Funktion zum Einsatz. Hierfür müssen folgende Regeln auf dem Steuerelement WochentagEnde erstellt werden: Bedingung Aktion (je Feldwert von “Ende” setze mit AddDays Funktion) wenn WochentagEnde = Samstag UND Dauer = 1 Tag addDays(Beginn; 3) wenn WochentagEnde = Samstag UND Dauer = 3 Tage addDays(Beginn; 5) wenn WochentagEnde = Sonntag UND Dauer = 3 Tage addDays(Beginn; 5) wenn WochentagEnde = Montag UND Dauer = 3 Tage addDays(Beginn; 5) Schließlich ist auch die Berechnungslogik implementiert und das Formular sollte entsprechend der Regeln die korrekten Wochentage des EndDatums ausgeben.

SharePoint 2013 Workflow–Lookup Activities

In SharePoint 2013 Workflows werden einige Activities angeboten, die die ListenId benötigen. Im jeweiligen Property Feld kann eine Liste ausgewählt werden. Der tatsächliche Eintrag im Prperty wird zu folgendem Sourcecode: System.Guid.Parse("{$ListId:Lists/urlaubstage;}") Der Platzhalter $ListId…. wird durch die tatsächliche ListenID ersetzt. So weit so gut. Nur leider funktioniert dies nur, wenn der Workflow als Sandboxed Solution ausgeliefert wird. In einer Farm Solution wird dieser Platzhalter nicht ersetzt. Wie zu erwarten ist, kann aus dem Text “{$ListId…..” kein Guid geparsed werden. Die entsprechendene Fehlermeldung sieht dann so aus: RequestorId: 768cedc5-12d4-2419-33f9-ddc55553fe3c. Details: System.FormatException: Expected hex 0x in '{0}'. at System.Guid.GuidResult.SetFailure(ParseFailureKind failure, String failureMessageID, Object failureMessageFormatArgument) at System.Guid.TryParseGuidWithHexPrefix(String guidString, GuidResult& result) at System.Guid.TryParseGuid(String g, GuidStyles flags, GuidResult& result) at System.Guid.Parse(String input) at System.Activities.CodeActivity`1.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager) at … Dies ist ein bekannter Bug und wird hoffentlich demnächst behoben. Da “Demnächst” wohl nicht ausreichend schnell ist, hier ein Workaround: Jeder Workflow wird mittels Elements.xml –Datei ausgeliefert. In dieser können wir weitere Parameter ablegen. Diese Parameter können im Workflow mittels “GetConfigurationValue” ausgelesen werden. 1) Anpassung Elements.xml In die Elements-Datei ist eine weitere Zeile einzufügen um ein Property anzulegen. 1: <Module Name="WFEins" Url="wfsvc/18bfe43f6db7420fa3b0b5999963262b"> 2: <File Url="Workflow.xaml" Type="GhostableInLibrary" Path="WFEins\Workflow.xaml" DoGUIDFixUp="TRUE"> 3: <Property Name="ContentType" Value="WorkflowServiceDefinition" /> 4: <Property Name="isReusable" Value="true" /> 5: <Property Name="RequiresInitiationForm" Value="False" /> 6: <Property Name="RequiresAssociationForm" Value="False" /> 7: <Property Name="WSPublishState" Value="3" /> 8: <Property Name="WSDisplayName" Value="WFEins" /> 9: <Property Name="WSDescription" Value="My 'WFEins' Workflow" /> 10: <!-- If you change the name or Url of your custom initiation or association form, 11: remember to update the corresponding property value (InitiationUrl or AssociationUrl) to match the new web relative url. 12: --> 13: <Property Name="RestrictToType" Value="List" /> 14: <Property Name="RestrictToScope" Value="{$ListId:Lists/antrag;}" /> 15: </File> 16: <File Url="WorkflowStartAssociation" Path="WFEins\WorkflowStartAssociation" Type="GhostableInLibrary"> 17: <Property Name="WSDisplayName" Value="WFEins - Workflow Start" /> 18: <Property Name="ContentType" Value="WorkflowServiceSubscription" /> 19: <Property Name="WSPublishState" Value="3" /> 20: <Property Name="WSEventType" Value="WorkflowStart" /> 21: <Property Name="WSEnabled" Value="true" /> 22: <Property Name="WSGUID" Value="a0f42f40-b453-4120-b205-981009dbc9d8" /> 23: <Property Name="WSEventSourceGUID" Value="{$ListId:Lists/antrag;}" /> 24: <Property Name="Microsoft.SharePoint.ActivationProperties.ListId" Value="{$ListId:Lists/antrag;}" /> 25: <Property Name="HistoryListId" Value="{$ListId:Lists/WorkflowHistoryList;}" /> 26: <Property Name="TaskListId" Value="{$ListId:Lists/WorkflowTaskList;}" /> 27: <Property Name="UrlaubsListeId" Value="{$ListId:Lists/urlaubstage;}" /> 28: </File> 29: </Module> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In diesem Beispiel ist es die Zeile 27. An dieser Stelle funktioniert das Ersetzen der ListId. Somit steht in der Konfiguration ein Property mit dem Namen “UrlaubsListeId” zur Verfügung. 2) Auslesen des Properties im Workflow im Bereich “Runtime” steht die Activity “GetConfigurationValue” zur Verfügung:   Diese Activity kann die Propertywerte der Elements-Datei auslesen und in eine Variable speichern. Die Konfiguration der Activity sieht so aus: Name ist der in der Elementsdatei angegebene Name der Eigenschaft. Und Result ist eine String-Variable in die der  Wert geschrieben werden soll. (in meinen Beispiel “urlaubsTageListeId”). Ab nun kann im Workflow der Ausdruck  Guid.Parse(urlaubsTageListeId) verwendet werden um den Guid der Liste zu erhalten.

SharePoint 2013–aber ohne Skydrive, Newsfeed und Sites

SharePoint 2013 bringt eine neue Topleiste mit, die den Anwender mit Links zu Newsfeed, Skydrive und Sites versorgt. Aber nicht in jeder Installation machen diese Links Sinn. Um sie zu entfernen ist entweder eine Änderung an der MasterPage oder ein JQuery Script notwendig. Eine dritte Möglichkeit ist das Bereitstellen eines DelegateControls. Ein Blick in die Masterpage zeigt die DelegateControls. (Die gezeigte Methode funktionier bei jedem DelegateControl. Nur die ControlId des zu überschreibenden Controls muss später in der Elementsdatei angepasst werden) 1: <div id="suiteLinksBox"> 2: <SharePoint:DelegateControl id="ID_SuiteLinksDelegate" ControlId="SuiteLinksDelegate" runat="server" /> 3: </div> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Die Leiste mit den drei Links, ist das “SuiteLinksDelegate” Control. Für dieses erstellen wir ein eigenes Steuerelement, das den von uns gewünschten Inhalt ausgibt. Der Inhalt kann auch leer sein. Dafür ist im Visual Studio eine neue FarmSolution anzulegen. Als Element wird ein neues UserControls hinzugefügt. Dieses Usercontrol wird in einem SharePoint-Mapped Folder “ControlsTemplates” abgelegt und somit in den 15-er Hive ausgeliefert. Der Code des User-Controls besteht nur aus einer überschriebenen Methode- Render () ist zu überschreiben und die Funktion der Basis Klasse ist nicht aufzurufen. Stattdessen kann hier über den HtmlTextWriter jeder beliebige HTML-Code geschrieben werden. Mein Code sieht so aus: namespace NoSkyDrive.ControlTemplates.NoSkyDrive { public partial class NoSkyDrive : UserControl { protected void Page_Load(object sender, EventArgs e) { }   protected override void Render(HtmlTextWriter writer) { //base.Render(writer); } } } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } Zu guter Letzt ist noch das DelegateControl zu überschreiben. Dies geschieht mittels einer Elements.xml Datei. Einfach ein leeres Element zu der Solution hinzufügen und den Code des Elements anpassen: <?xml version="1.0" encoding="utf-8"?> <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Control Id ="SuiteLinksDelegate" Sequence="100" ControlSrc="/_ControlTemplates/15/NoSkyDrive/NoSkyDrive.ascx"> </Control> </Elements> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } In dem Control Element ist als Id der ControlId des Delegate Controls anzugeben. Diesen haben wir vorher in der Masterpage ermittelt. Die “ControlsSrc” der Pfad zu der zuvor erstellten UserControl Datei. Die fertige Ansicht der SharePoint Seite ohne SkyDrive, Newsfeed und Sites-Links:

High Trust (S2S Trust) SharePoint 2013 App auf Produktivserver ausrollen

Sobald die App fertig programmiert und natürlich getestet wurde, sollte sie auch am SharePoint Server allen Benutzern zur Verfügung gestellt werden. Solange eine Autohosted App verwendet wird, ist das Deployment simpel, da Visual Studio und Azure gut zusammen spielen. Möchte man jedoch die App innerhalb des Unternehmensnetzwerkes betreiben sind mehrere Schritte notwendig. Zunächst sollte der SharePoint Server für Apps konfiguriert werden. In einem früheren Blog habe ich die Vorgangsweise schon beschrieben. (Link) Die Developer Site Collection ist nicht notwendig und bis zum Anlegen des App Catalogs ist alles gleich. Jede App die auf SharePoint hochgeladen werden soll muss zunächst registriert werden. Hierzu liegt im Layouts Verzeichnis eine Seite um Apps zu registrieren. Die URL ist: http://{server}/_Layouts/15/appregnew.aspx. In dieser Seite sind App Id und App Secret zu setzen. Am einfachsten ist, es mit dem Button “Generate” sich neue Werte erstellen zu lassen. Title ist der App Titel. Die App Domain ist jene, die im ersten Schritt bei der Konfiguration des DNS verwendet wurde. Unter Redirect URL ist das virtuelle Verzeichnis des Webservers einzutragen, der dann die App hosten wird. Dieses muss mit https erreichbar sein. http ist hier nicht zulässig. Sobald man mit “Create” abschließt wird die Registrierung erstellt und nachfolgend eine Zusammenfassung angezeigt. Am besten, man kopiert diese Zusammenfassung mit Copy & Paste in eine Textdatei. Die App Solution besteht aus zwei Teilen. Der App und dem Remote Web Projekt. Zunächst behandeln wir das Remote Web Projekt. Im Web Projekt muss die Web.config angepasst werden. Wir müssen die ClientId setzen und das verwendete Zertifikat hinterlegen. Die ClientId, ist jener Wert der oben als “App Id” angelegt wurde . Das Zertifikat haben wir bereits für die Entwicklung erstellt. Es ist nun wichtig, die pfx-Datei des Zertifikates am WebServer zu hinterlegen und in der web.Config den Pfad richtig zu setzen. In meinem Fall verwende ich die selbe Zertifikatordner wie auf der Dev Maschine: C:\certificates\ die Web Config sieht bei mir daher so aus: <appSettings> <add key="ClientId" value="a998bc56-4a5e-4cf1-a8da-5232cb7ed8f2" /> <add key="ClientSigningCertificatePath" value="C:\certificates\AppCert.pfx" /> <add key="ClientSigningCertificatePassword" value="dasSuperGeheimePasswort" /> <add key="IssuerId" value="00000000-0000-0000-0000-000000000000" /> </appSettings> Die IssuerId muss ebenfalls gesetzt werden. In meinem Fall eine Guid mit lauter 0. In der Realität wird natürlich eine neue Guid erstellt. Im Webserver wird nun ein neue Application erstellt. Wichtig ist, einen Application Pool zu verwenden der das –NET 4.0 Framework verwendet. Außerdem muss die Authentication angepasst werden: Anonyme und Windows Authentication müssen aktiviert sein: Achtung: in den Bindings des WebServers muss noch https aktiviert werden und ein Zertifikat für https hinterlegt werden. Das WebProjekt selbst kann nun direkt von Visual Studio auf den WebServer deployed werden. Entweder mittels WebDeploy oder simpel mit einem FileSystem Deployment in das entsprechende Verzeichnis am Web Server. Als nächstes muss die App in ein App-File verpackt und im Catalog hochgeladen werden. Das Verpacken übernimmt das Visual Studio. Im Context-Menü des App-Projektes bringt uns der Menüpunkt “Publish…”  zu folgender Seite im Visual Studio: Mit “Package the app” wird noch die URL und die Client ID abgefragt: Sobald das bestätigt wurde, legt Visual Studio die App-Datei an und startet einen File Explorer in einem Unterverzeichnis des Debug Verzeichnisses der App. Für jede App Version wird hier ein eigener Unterordner erstellt. Im nächsten Schritt müssen wir nun zum App-Catalog auf SharePoint wechseln. Der App Catalog wurde im oben genannten Blog Eintrag angelegt. Die App-Datei wird einfach hochgeladen. Nach dem Hochladen sollte es so aussehen: In diesem Zustand kann der Benutzer nun die App aktivieren und auf seiner Site hinzufügen. Die App wird auch angezeigt, sobald jedoch der Code versucht auf SharePoint zuzugreifen kommt eine Access-Denied Exception. Der letzte Schritt fehlt noch. Das Zertifikat das verwendet wurde muss in SharePoint registriert werden. Das ist pro Zertifikat und damit Issuer ID einmalig durchzuführen. Hier muss der public-Teil des Zertifikates eingespielt werden, der in der .cer Datei zu finden ist. Das Script ist ident mit jenem aus dem Blog zur Developermaschinen-Konfiguration, nur der SharePoint-Server ist anzupassen: Add-PSSnapin "Microsoft.SharePoint.Powershell" $publicCertPath = "C:\certificates\AppCert.cer" $issuerId = [GUID]"00000000-0000-0000-0000-000000000000" $spurl = "http://sp1" $spweb = Get-SPWeb $spurl $realm = Get-SPAuthenticationRealm -ServiceContext $spweb.Site $certificate = Get-PfxCertificate $publicCertPath New-SPTrustedRootAuthority -Name "FarmAppsAuth" -Certificate $certificate $fullIssuerIdentifier = $issuerId.ToString() + "@" +$realm New-SPTrustedSecurityTokenIssuer -Name $issuerId -Certificate $certificate -RegisteredIssuerName $fullIssuerIdentifier -IsTrustBroker .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }     .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }