Ein paar Fragen an das Active Directory

Beim Betrieb einer SharePoint-Farm ist man auf die korrekte Pflege von Accounts im Active Directory angewiesen. Gerade im Zusammenhang mit Kerberos ist es wichtig, dass alle Einstellungen im Active Directory richtig eingetragen werden, damit die Authentifizierung auch ohne Probleme funktioniert.

Als SharePoint-Farm Administrator hat man dazu allerdings nicht immer ohne weiteres Zugriff auf das Active Directory, zumindest nicht auf die Management-Tools. Nun stellt sich also die Frage, wie kann ich feststellen, welche Service-Principles alle für einen Account im Active Directory registriert wurden. Wenn ich Zugriff auf einen Domain-Controller hätte, dann würde ich ganz einfach SETSPN verwenden.

C:\>SETSPN spIntranetWA
Registered ServicePrincipalNames for CN=spIntranetWA,OU=Dienstkonten,OU=HeadQuarter,DC=acme,DC=local:
    HTTP/sp01.acme.local
    HTTP/intranet.acme.local

Aber SETSPN kann ich nur direkt auf einem Domain Controller ausführen, da komme ich als SharePoint Farm-Admin also nicht drauf. Was nun?

Wie bei allen Administrativen gibt es eine Ultimative Antwort: PowerShell!

Die Lösung ist so einfach, ich traue mich die ja schon kaum zu posten. Mit folgendem Befehl kann ich alle Einträge im Active Directory suchen, deren Account-Name “spIntranetWA” ist:

C:\> ([adsisearcher]"samaccountname=spIntranetWA").FindAll()

Path                                                        Properties
----                                                        ----------
LDAP://CN=spIntranetWA,OU=Dienstkonten,OU=HeadQuarter,... {logoncount, codepage, objectcategory, usnchanged...}

Damit habe ich schon mal ein paar Informationen.

Das funktioniert natürlich von jedem Arbeitsplatz mit PowerShell. Im Gegensatz zu den Active-Directory cmdlets verwende ich hier den ADSISearcher (dabei handelt es sich um ein Alias für die DirectorySearcher-Klasse aus dem .Net Framework). Das ist eine der genialen Dinge der PowerShell – ich kann mal eben so was aus dem .Net Framework verwenden. Um nun Objekte im Active-Directory zu suchen kann man dem ADSISearcher LDAP-Abfragen übergeben.

Insbesondere die Properties sind hierbei interessant. Die kann man sich auch etwas genauer ansehen.

C:\> $accounts = ([adsisearcher]"samaccountname=spIntranetWA").FindAll()
C:\> $accounts.properties

Name                           Value
----                           -----
logoncount                     {323}
codepage                       {0}
objectcategory                 {CN=Person,CN=Schema,CN=Configuration,DC=acme,DC=local}
usnchanged                     {15699245}
instancetype                   {4}
name                           {spIntranetWA}
pwdlastset                     {128843629222870406}
serviceprincipalname           {HTTP/sp01.acme.local, HTTP/intranet.acme.local}
objectclass                    {top, person, organizationalPerson, user}
samaccounttype                 {805306368}
lastlogontimestamp             {130265658248173942}
usncreated                     {19523}
dscorepropagationdata          {21.06.2013 13:56:40, 21.06.2013 12:28:23, 20.07.2012 09:54:16, 01.01.1601 18:16:33}
whencreated                    {16.04.2009 13:42:02}
adspath                        {LDAP://CN=spIntranetWA,OU=Dienstkonten,OU=HeadQuarter,DC=acme,DC=local}
useraccountcontrol             {590336}
cn                             {spIntranetWA}
countrycode                    {0}
primarygroupid                 {513}
whenchanged                    {18.10.2013 10:30:24}
lastlogon                      {130270479005600192}
distinguishedname              {CN=spIntranetWA,OU=Dienstkonten,OU=HeadQuarter,DC=acme,DC=local}
samaccountname                 {spIntranetWA}
objectsid                      {1 5 0 0 0 0 0 5 21 0 0 0 130 139 166 40 17 195 95 115 138 167 50 63 245 22 0 0}
displayname                    {spIntranetWA}
objectguid                     {217 8 220 57 70 168 118 76 178 20 180 160 198 219 146 67}
accountexpires                 {9223372036854775807}
userprincipalname              {spIntranetWA@acme.local}

Hier kann man auch den ServicePrincipleName ablesen. Der Rest ist dann nur noch ein wenig  PowerShell gefummel, um das ganze dann auch tabellarisch angezeigt zu bekommen.

C:\> ([adsisearcher]"samaccountname=sp*").FindAll()|select @{name='cn'; expression = {$_.properties['cn']}}, @{name="spn"; expression={$_.properties["ServicePrincipalName"]}}

cn                      spn
--                      ---
spCrawl
spDBAccount
spExtranetWA
spFarm
spInternetWA
spIntranetWA            {HTTP/sp01.acme.local, HTTP/intranet.acme.local}
spMySites
spSearch
spSPService

Lokalisierung in Office365

Und mal wieder eine (Erfolgs)Geschichte aus Office365 … also ich habe da so eine Mysite unter busitec-my.sharepoint.com. Die Mysite wurde ursprünglich in Englisch angelegt, und anschließend habe ich die Sprache auf „Deutsch“ umgestellt.

site_setting_locale

Wenn ich nun einen Blog anlege, dann ist der zunächst auf Englisch; kein Problem. Also Sprache auf Deutsch umstellen. Sieht ja alles Super aus – alles läuft; oder etwa nicht? Wenn ich nun in “Beiträge verwalten” gehe und mir alle Beiträge der Liste anzeigen lasse und dann einen einzelnen Beitrag anklicke …

post_error

Wow! Was ist denn das? Eine Fehlermeldung, und das bei O365? Wie kann denn das … und viel Interessanter ist ja noch, wenn ich den Beitrag direkt von der Startseite aus aufrufe, dann geht’s?!

OK – die Erklärung ist dann doch ganz schnell gefunden. Wenn ich den Beitrag von der Startseite aus aufrufe, dann sieht die URL wie folgt aus:

https://busitec-my.sharepoint.com/personal/eiben_busitec_de/Blog/Lists/Posts/Post.aspx?ID=1

Wenn ich aber über die Liste der Beiträge gehe und dann auf den Titel klicke wird diese URL aufgerufen:

https://busitec-my.sharepoint.com/personal/eiben_busitec_de/Blog/_layouts/15/listform.aspx?PageType=4&ListId=%7B36A2AAFB%2D5143%2D403F%2DA325%2DA7610CDE8EA6%7D&ID=1&ContentTypeID=0x01100007A0E26C347C5F4899398346B0DAF58B

…die dann einen Redirect auslöst auf:

https://busitec-my.sharepoint.com/personal/eiben_busitec_de/Blog/Lists/Beitraege/Post.aspx?List=36a2aafb%2D5143%2D403f%2Da325%2Da7610cde8ea6&ID=1&Source=https%3A%2F%2Fbusitec%2Dmy%2Esharepoint%2Ecom%2Fpersonal%2Feiben%5Fbusitec%5Fde%2FBlog%2FLists%2FPosts%2FAllPosts%2Easpx&ContentTypeId=0x01100007A0E26C347C5F4899398346B0DAF58B

Hier wird also die URL lokalisiert Smile ist zwar nett von SharePoint, dass er so viel wie möglich lokalisieren will – aber bitte doch nicht die URL. Denn eine Liste Beitraege gibt es nicht. Die würde es nur dann geben, wenn beim Anlegen der Site auch “Deutsch” ausgewählt gewesen wäre. Dann würde SharePoint die Namen der Listen an die Sprache anpassen.

Interessant ist IMHO, dass das listform.aspx (welches für den Redirect zuständig ist) nicht erkennt wie die Liste mit der ID 36A2AAFB-5143-403F-A325-A7610CDE8EA6 wirklich heißt.

Nachdem ich ja nun schon vor 3 Wochen einen Fehler in Nintex beim Auflösen von Gruppennamen und letzte und diese Woche einen Fehler in ShareGate beim kopieren von HTML-Inhalten gefunden habe, werde ich diesen Fehler auch mal versuchen an Microsoft zu melden – immer muss man alles selber machen.

Migration (m)eines SharePoint-Blogs nach O365

Es begab sich zu der Zeit als ich meinen Blog von meiner SharePoint 2007 Mysite auf meine neue SharePoint 2013 (in Office365 gehostete) MySite migrieren wollte.

Das Vorgehen

Also schnell ShareGate geladen und loslegen. Als erstes also die Kategorien migrieren, damit die BlogPosts auf meiner neuen MySite dann auch darauf korrekt verweisen können; check!

Dann sind die Beiträge dran. Das sind schon ein paar mehr (>600) aber jetzt auch nicht so die Masse. Auch hier ist das Migrieren zunächst kein Problem. Bis ich mir dann die BlogPosts einmal angesehen habe. Dabei ist mir aufgefallen, dass HTML-Text, der von einem Pre-Tag umschlossen ist und der dazu noch Zeilenumbrüche beinhaltet nicht korrekt übernommen wird. OK, also mit den Jungs von ShareGate eine Lösung gebaut (Anmerkung: das war ein Bug in der Version 4.0 von ShareGate, der mit der Version 4.1 behoben wurde). Continue reading

Ändern des Edit-Formulars für eine SharePoint Liste

Vor kurzem kam ein Kunde mit einem Problem auf mich zu. Er hatte mit dem SharePoint-Designer ein eigenes Edit-Form für eine Liste erstellt und dieses als Standard-Editor-Formular der Liste zugeordnet. Mit der Zeit wurde die Liste angepasst und es sind neue Feld in die Liste dazugekommen. Leider werden diese neuen Felder von dem angepassten Formular nicht angezeigt.

Nun wollte der Kunde als das Standard-Formular wieder zurücksetzen. Leider wurde in der Zwischenzeit die Verwendung des SharePoint Designers in der Farm deaktiviert. Nun war also die Frage: wie kann ich das Standard Edit-Formular wieder zurücksetzen?

Klare Antwort: PowerShell!

$url = Read-Host "Enter URL" 
$listname = Read-Host "Enter List Name"
$editformurl = Read-Host "Enter Edit Form Url"

$web = Get-SPWeb $url 
$list = $web.Lists[$listname]

$list.DefaultEditFormUrl = $editformurl
$list.update()

So weit, so gut. Aber leider hatte der Kunde keinen Zugriff auf den Server. Somit hilft PowerShell an dieser Stelle leider einmal nicht weiter.

Was bleibt dann noch? JavaScript! Mit dem Client-Object-Model von SharePoint 2010 geht halt doch schon einiges.

<script type="text/javascript">
(function() {
    "use strict";

    var context;
    var list;

    SP.SOD.executeOrDelayUntilScriptLoaded(function () {
        context = new SP.ClientContext.get_current();
        list = context.get_web().get_lists().getByTitle("Test");
        context.load(list);
        context.executeQueryAsync(onSuccessLoadList, onFailure);
    }, "sp.js");

    function onSuccessLoadList() {
        list.set_defaultEditFormUrl("/users/eiben/Lists/Test/NewEditForm.aspx");
        list.update();
        context.executeQueryAsync(onSuccessUpdateList, onFailure);
    }

    function onSuccessUpdateList() {
        alert("done");
    }

    function onFailure(sender, args) {
        SP.UI.Notify.addNotification("Es ist ein Fehler aufgetreten ... " + args.get_message(), true, "", null);
        console.debug(args.get_stackTrace());
    }

})();
</script>

Das mal eben auf der Site z.B. in einem Content-Editor-WebPart einbinden – und voila!

Telefonnummern im SharePoint

SharePoint hilft wo er kann, z.B. bei Links. Wenn ich als Anwender einen “ungültigen” Link in einer SharePoint-Seite einbette, dann entfernt SharePoint den für mich.

Leider ist SharePoint dabei schon mal ein wenig voreilig. Nun wollte ich auf einer Seite im SharePoint einen Link auf eine Telefonnummer wie folgt einbetten:

<a href="tel:+49251123456">+43 (0)251 123456</a>

SharePoint – hilfsbereit wie immer – macht daraus aber:

<a>+49 (0)251 123456</a>

Denn bei dem Protokoll tel: muss es sich um einen Fehler handeln. Das kennt SharePoint (offenbar) nicht!

Was nun? Der Retter kommt in der Form von JavaScript. Ich habe dem Anchor-Tag eine Klasse tel zugewiesen, damit ich die Links auf eine Telefonnummer einfacher finden kann. Somit habe ich nun auf meiner Seite also folgendes eingegeben:

<a class="tel">+49 (0)251 123456</a>

Nun nur noch ein bisserl Code:

jQuery.noConflict();
jQuery(function() {
    jQuery("a.tel").each(function() {
        var number = jQuery(this).text().replace(/\(0\)/g, "");
        jQuery(this).attr("href", "tel:" + number);
    });
});

Und schon werden die Links korrekt eingebettet!

Quick Edit im SharePoint 2013

In SharePoint 2013 hat Microsoft das Quick Edit ja deutlich überarbeitet. Während das Datasheet-Edit bis SharePoint 2010 nur im Internet Explorer möglich war (weil ActiveX Control), geht das nun in jedem Browser.

Ebenfalls neu ist, dass ich direkt aus dem Quick Edit auch neue Spalten anlegen kann.

quick_edit_col

Wenn ich hier also einen neue Spalte NewColumn hinzufüge, dann ist der interne Namen dieser Spalte nicht etwa NewColumn (was ich jetzt naiv erwartet hätte), sondern in diesem Fall war es zz1h.

Das ist gut zu wissen. Besonders wenn man denkt, dass man so mal eben Schnell eine Liste aufbauen kann und dann sich wundert, warum die CAML-Abfrage oder das XSL nicht mehr so ganz wollen.

Kein ULS Logging in SharePoint

Es kommt immer mal wieder vor, dass auf einem SharePoint-Server die ULS Logs leer sind. SharePoint erstellt zwar alle 30 Minuten ein neue Logdatei, aber die Dateien haben immer eine Größe von 0 Byte. Wie kann das sein?

Zuerst dachte ich, dass das Loglevel einfach falsch eingestellt ist, und dass keine Ereignisse in das Log geschrieben werden – das war aber in meinem aktuellen Fall nicht so. Was kann es dann sein?

Aus Verzweiflung habe ich einfach mal den SharePoint 2010 Tracing Service neu gestartet – und da kamen auch schon wieder Einträge ins Log. Aber meine Freude war nicht von langer Dauer – nach ein paar Sekunden wurde keine neuen Einträge mehr geschrieben.

Aber da – auf einmal stand die Lösung heraus:

Not enough free disk space available. The tracing service has temporarily stopped outputting usage entries to the usage log file. Usage logging will resume when more than 1124 MB of disk space becomes available.

Mal eben schnell auf die C: Platte geschaut – und da waren nur 600MB freier Speicher; offenbar nicht genug für SharePoint. Nachdem ich also ein paar hundert MB gelöscht hatte ging es auch mit dem Logging wieder weiter:

The tracing service has resumed outputting trace messages to the log file.
The usage logging has resumed.

Cool!