Exception in MergeAspSiteMapFiles for „SharePoint 2010 Products Configuration Wizard“ (PSConfig)

That made my day!

Nick Grattan's Blog

Recently, I’ve found that a SharePoint farm failed to update when running the „SharePoint 2010 Products Configuration Wizard“ or when running PSConfig directly. The PSConfig log file in the 14 hive contained the following exception information:

I suspected that this was caused by a application I had deployed to the SharePoint farm. However, it was not obvious what in the application was causing the problem.

The key to finding the problem was the call to ApplyApplicationContentToLocalServer in the stack trace. Reading up on the documentation for this method indicated that this is responsible for merging all the site map in the 14 hive and placing them in the _app_bin folder for each web application in the appropriate inetpub folders. These sitemap files are XML documents with „.sitemap.“ somewhere in the name.

This post is useful in understanding this process: http://sharepointinterface.com/tag/features/

Using this information I managed to find the sitemap file…

Ursprünglichen Post anzeigen 71 weitere Wörter

Werbung

Was tun wenn das Mac Book Pro nicht mehr bootet…

Moment… Mac als SharePoint-Entwickler? WTF?

Obwohl ich als Softwareentwickler eher in der Windows-Welt unterwegs bin, nenne ich ein 2015er Mac Book Pro Retina mein eigen. Ich mag einfach das Display, die lange Akkulaufzeit und das hervorragende Trackpad. Und Windows bzw. eine SharePoint Developer-Maschine läuft dank schnellem Prozessor und SSD unter VirtualBox ganz hervorragend.

Linux USB-Boot …. fail.

Gestern wollte ich Linux auf der Maschine von einem USB-Stick booten. Einen vermeindlich bootfähigen Stick habe ich nach dieser Anleitung erstellt.

Gesagt gebootet. Um auf einem Mac von einem alternativen Datenträger zu booten muss man die Kiste herunterfahren und beim Einschalten die Alt-Taste gedrückt halten, es erscheint dann eine Auswahl der Bootmedien.

Der Stick meldete sich als Efi-Bootdevice. Leider führte das Booten vom Stick zu nichts weiter als einem schwarzen Bildschirm. Hmm, ok irgendwas war falsch. Was, das habe ich bislang nicht nicht herausbekommen. Egal.

Mac Brick Pro Retina

Was aber schlimmer war: Der Mac bootete überhaupt nicht mehr, auch ohne Stick nicht. Komplett tot, bricked, Briefbeschwerer, shit!

Was würde der n00b tun?

In meiner Not und Unkenntnis über den ziemlich neuen Mac startete ich eine Chat-Session mit dem freundlichen Apple-Support, dieser ließ mich die Maschine mit folgender Prozedur wieder zum Leben erwecken (originalzitat aus dem Chat):

Have your power adapter plugged to the mac and make sure nothing external is connected. Hold down the (left side) Shift, Control, Option(alt) keys and the power button at the same time, then let go of all of the keys together right after. The system won’t respond, except the light on the power cord may possibly change color briefly. If the step was done correctly, the mac will not turn on.

Gesagt, getan.
Die LED am Power-Stecker wechselte von grün auf rot und der Bildschirm blieb schwarz, wie vorhergesagt.

Now turn back on the Mac with the power button, then hold down Command+Option(ALT)+P+R, and you’ll need to be holding the keys before you hear the startup chime. Continue to hold the keys together, until you hear the startup chime a second time, then you can release them and the computer will startup. Let’s see what happens.

Und was soll ich sagen, der Mac startete ganz normal. Yay!
Thank you so much Apple support!

E-Mail Versand in eigener Anwendung ohne Schmerzen testen

Das Problem

Im eigenen Projekt soll irgend ein E-Mail Versand implementiert werden. Natürlich muss die Anwendung getestet werden und es werden meist dutzende Müll-Mails generiert, die einem das Postfach verstopfen. Das ist das kleinere Problem.

Das größere offenbart sich, wenn man auf einer virtuellen Maschine entwickelt, die keinen Zugang zu einem SMTP-Server hat, dann muss man sich aufwändig einen lokalen installieren, und was weiß ich nicht noch alles. Tüddelkram der nur aufhält.

Die Lösung

…heißt Papercut, ist Open Source und auf Codeplex verfügbar.

Papercut ist ein lokaler SMTP-Server und minimaler E-Mail Client in einem, der Mails (unabhängig von der Empfängeradresse) abfängt und übersichtlich darstellt. Auch der Mail-Header kann komfortabel untersucht werden.

Die Installation beschränkt sich auf einen Doppelklick und der obligatorischen Lizenzakzeptanzabfrage, danach ist der SMTP-Server up and running!

Er kann unter 127.0.0.1, localhost oder durch den Computernamen angesprochen werden.

Verwendung mit SharePoint

Um Papercut mit SharePoint zu verheiraten, damit Benachrichtigungs-Mails und solche, die per code mit SPUtility.SendEmail() verschickt werden abgefangen und eingesehen werden können, trägt man in der Zentraladministration einfach folgendes ein:

Central Administration -> System Settings -> Configure outgoing email settings:

2016-08-04_16h58_03

Das ist alles!

Ausgehende Mails werden nun von Papercut abgefangen, es erfolgt eine Meldung und die Mails können eingesehen werden.

Insgesamt ein sehr praktisches Tool.

Mehr…

 

SharePoint: Resource-Strings sicher auslesen

Resource-Files sind eine feine Sache. Sie erlauben bekanntermaßen die Lokalisierung eines Projektes, indem sie Satzfetzen in sprachabhängige XML-Dateien (.resx) auslagern und unter einem gemeinsamen Key zugreifbar machen.

Um Strings per C# (serverseitig) aus Resources/MeinSchnuckeligesResourceFile.resx auszulesen, könnte man z.b. eine einfache Methode schreiben:

public static string GetResString(string key)
   {
      // Return the value of a language resource
      // in the current language.
      string str = "$Resources:"+key;
      return SPUtility.GetLocalizedString(str,
                "MeinSchnuckeligesResourceFile",
                (uint) CultureInfo.CurrentUICulture.LCID).Trim();
    }

So weit so gut.
Manchmal kommt es vor dass Kunden oder andere unbedarfte Personen selbst Hand an die Resource-Files legen dürfen, und es kommt auch vor dass Strings Platzhalter der Form {0}, {1}… enthalten, die später mit Werten ersetzt werden. Wenn ein Platzhalter im String referenziert wird, der nicht übergeben wurde fliegt eine Exception. Es wäre also sinnvoll auf das vorhandensein von solchen nicht abgedeckten Platzhaltern zu testen, dafür dient nachfolgende Methode, die im Prinzip wie String.Format() funktioniert, nur dass sie anstatt des strings den Resource-Key übergeben bekommt:

/// <summary>
/// Retuns a formated string. Unse this when the resource string 
/// contains a {0} placeholder. 
/// Returns the unformatted string if a placeholder refers to 
/// a non existent parameter to avoid an exception
/// </summary>
/// <param name="key">the key in resource file</param>
/// <param name="param">params like in string.format</param>
/// <returns>formatted string. in case of an error, unformatted string.</returns>

public static string GetFormatedResString(string key, params object[] arguments)
{
   string result = String.Empty;
   string fmt = GetResString(key);

   // check if format string does not refer to a non-given argument
   // Because we don't want exceptions just because of wrong resource strings
   var matches = from Match match in Regex.Matches(fmt, @"\{([0-9]+)\}")
   select match.Groups[1].Value;

   bool dirty = false;
   foreach (string mat in matches)
   {
      // if {} placeholder refers to an argument which is not there -> dirty
      if (int.Parse(mat) > arguments.Count() - 1)
      {
         dirty = true;
      }
   }

   if (!dirty)
   {
      // return formatted string
      result = String.Format(fmt, arguments);
   }
   else
   {
      // return unformatted string
      result = fmt;
   }
   return result;
}

Keine Rocket-Science, aber manchmal ganz hilfreich.

Kein Sommertag ohne Zippi

Sommer. Es ist heiß im Büro.

Mir war es bislang leider nicht vergönnt, den Tag in einem adäquat klimatisierten Büro zu verbringen, in keinem Projekt. Bei fast 30° klebt die Haut, das Hirn ist quasi im Standby, die Produktivitätskurve ist nahe der Nulllinie.

Was fehlt ist Luft, Abkühlung, Wind, ach was sage ich, Sturm, Gischt, Regen! Zumindest für den leichten bis mittelstarken Luftzug tut ein profaner Ventilator gute Dienste.

Ich habe mich für einen Zippi von der Firma Vornado entschieden. Gekauft für 22€ bei Amazon.

Warum der?

Ich habe so einen Ventilator vor Jahren mal bei einem Projektkollegen auf dem Schreibtisch stehen sehen, und fand das Teil sofort bemerkenswert. „Der ist ja niedlich!“

Die „Rotorblätter“ sind aus Stoff und damit weich. Man kann sich also nicht daran verletzen. Es tut nicht mal weh wenn man auf höchster Stufe versehentlich dagegen kommt. Dadurch kann man sich den Sicherheitkorb sparen und der Luftzug wird leiser – genial!

Leise ist er wirklich und selbst auf Stufe 1 von 2 macht er genug wind um den Arbeitstag mit voller Hirnleistung zu überstehen. Stufe 2 ist mir persönlich zu windig. Und dabei ist der obere Teil worin sich der Motor befindet nicht größer als eine einförmige Apfelsine.

zippi_black_hero1

Hach, ist er nicht süß?

Wegen der weichen Flügen und der Tatsache, dass sich der Motor nach unten in den Ständer drehen lässt, ist der Kleine auch gut to transportieren, wichtig wenn man im unklimatisierten Budget-Hotelzimmer noch weiter coden möchte und das Gewitter auf sich warten lässt…

Ich bin jetzt jedenfalls auch noch bei 28°C + produktiv 🙂

Hier geht’s zur Produktseite:
Vornado Zippi Tischventilator, schwarz, 701177

 

SharePoint 2013: „Append Changes“ Textfelder auflösen

„Append Changes“-Felder sind praktisch

Textfelder, die mit dem „Append Changes“ Attribut ausgestattet sind, sind prinzipiell eine feine Sache. Jeder Eintrag wird mit Datum und eintragendem Benutzer zu dem bereits bestehenden Text hinzugefügt, der alte Eintrag ist nicht veränderbar. Großartig für Kommentarfelder oder jegliche Form von Journalen.

…aber ungünstig in der Listendarstellung.

In der Listenansicht wird so ein Feld jedoch nur als „View Entries“-Link auf die Leseansicht des Listeneintrags dargestellt. Unschön und vor allem fürchterlich unpraktisch. Das liegt vor allem daran, dass die Änderungen als Versionen gespeichert werden. Um alle Einträge zu bekommen müssen also alle Versionen des betreffenden Feldes gelesen und dargestellt werden. SharePoint liefert solch ein Control leider nur für die View- und Edit-Ansicht eines Items, nicht jedoch für die Listen-Ansicht.

Die Lösung

Doch es gibt Abhilfe. Seit der Version 2013 von SharePoint existiert die Möglichkeit per JavaScript in die Darstellung einzelner Felder in der Listenansicht einzugreifen. JSLink. Das ist nichts weiter als der Link zu einem JavaScript-File die dem ListView Webpart oder dem List-Schema.xml mitgegeben werden kann. In diesem Script lassen sich dann callback-Funktionen definieren für einzelne Felder definieren, die den Inhalt der Spalte liefern. Dieser Inhalt kann alles sein, was der Browser verarbeitet, meistens HTML und Javascript.

Aus diesem Grunde habe ich ein paar sinnvolle JavaScript Funktionen geschrieben, die man für sein JSLink-Script verwenden kann. Es handelt sich im Prinzip um zwei Funktionen, die die Änderungshistorie verschieden darstellen:

Als Popup, welches aufklappt wenn man mit der Maus darüberfährt:

bild1

Oder direkt in der betreffenden Zelle.

Der Code

Hier nun erstmal der Code:


// ------------------------------------------------------------------------------------------
// Shows the content of the current &amp;quot;append only&amp;quot; text field in a popup
function ShowVersionedPopup(ctx) {
    var listUrl = ctx.listUrlDir;
    var listName = ctx.ListTitle;

    var id = &amp;quot;FB_Mo_&amp;quot; + ctx.CurrentFieldSchema.ID + ctx.CurrentItem[&amp;quot;ID&amp;quot;];
    html = &amp;quot;&amp;lt;img id='IMG&amp;quot; + id + &amp;quot;' src='/_layouts/15/images/myproject/moretext.png' onMouseOver=\&amp;quot;$('#&amp;quot; + id            + &amp;quot;').css('visibility','visible')\&amp;quot; onMouseOut=\&amp;quot;$('#&amp;quot; + id + &amp;quot;').css('visibility','hidden')\&amp;quot; /&amp;gt;&amp;quot;
           + &amp;quot;
&amp;lt;div id='&amp;quot; + id + &amp;quot;' style='visibility:hidden; position:absolute; padding:5px;&amp;quot;            + &amp;quot;box-shadow: 10px 10px 29px 0px rgba(184,184,184,1); &amp;quot;            + &amp;quot;border: 2px solid #000000;&amp;quot;            + &amp;quot;background-color: white'&amp;quot;            + &amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;quot;;
    GetVersions(listName, ctx.CurrentItem[&amp;quot;ID&amp;quot;], ctx.CurrentFieldSchema.Name, id);
    return html;
}

// ------------------------------------------------------------------------------------------
// Shows the content of the current &amp;quot;append only&amp;quot; text field directly in the list field
function ShowVersionedDiv(ctx) {
    var listUrl = ctx.listUrlDir;
    var listName = ctx.ListTitle;

    var id = &amp;quot;FB_Mo_&amp;quot; + ctx.CurrentFieldSchema.ID + ctx.CurrentItem[&amp;quot;ID&amp;quot;];
    html = &amp;quot;
&amp;lt;div id='&amp;quot; + id + &amp;quot;'&amp;gt;&amp;lt;/div&amp;gt;
&amp;quot;;
    GetVersions(listName, ctx.CurrentItem[&amp;quot;ID&amp;quot;], ctx.CurrentFieldSchema.Name, id);
    return html;
}
// ------------------------------------------------------------------------------------------
// Retreives all versions of a field of a specifiv List Item
// listName: Name or ID of the List
// itemID: ID of the ListItem
// internalFieldName: The internal Name of the field
// elementId: The ID of the HTML element (usually a div) where the HTML output should be pumped in
function GetVersions(listName, itemId, internalFieldName, elementId)
{
    var xmlData = &amp;quot;&amp;lt;soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' &amp;quot;     xmlData += &amp;quot;xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'&amp;gt;&amp;quot;;
    xmlData += &amp;quot;&amp;lt;soap:Body&amp;gt;&amp;lt;GetVersionCollection xmlns='http://schemas.microsoft.com/sharepoint/soap/'&amp;gt;&amp;quot;;
    xmlData += &amp;quot;&amp;lt;strlistID&amp;gt;&amp;quot; + listName + &amp;quot;&amp;lt;/strlistID&amp;gt;&amp;lt;strlistItemID&amp;gt;&amp;quot; + itemId + &amp;quot;&amp;lt;/strlistItemID&amp;gt;&amp;lt;strFieldName&amp;gt;&amp;quot;
    xmlData += internalFieldName + &amp;quot;&amp;lt;/strFieldName&amp;gt;&amp;lt;/GetVersionCollection&amp;gt;&amp;lt;/soap:Body&amp;gt;&amp;lt;/soap:Envelope&amp;gt;&amp;quot;;

    $.ajax({
        url: &amp;quot;&amp;lt;pfad zum web&amp;gt;/_vti_bin/lists.asmx&amp;quot;, // &amp;lt;---- Hier Anpassen
        type: &amp;quot;POST&amp;quot;,
        dataType: &amp;quot;xml&amp;quot;,
        data: xmlData,
        complete: function (jqXHR) { GetVersionsSuccess(jqXHR, elementId, internalFieldName); },
        error: GetVersionsError,
        contentType: &amp;quot;text/xml; charset=\&amp;quot;utf-8\&amp;quot;&amp;quot;
    });

};

// ------------------------------------------------------------------------------------------
// Callback func in terms of successful webService operation
// result: the result SOAP envelope
// divId: The ID of the HTML element (usually a div) where the HTML output should be pumped in
// internalFieldName: The internal Name of the SPList field
function GetVersionsSuccess(result, elementId, internalFieldName) {
    var rTable = &amp;quot;
&amp;lt;table&amp;gt;&amp;quot;;
    $()
    //alert(result.esponseText);r
    if ($(result.responseXML).find(&amp;quot;Version&amp;quot;).length &amp;gt; 0) {
        $(result.responseXML).find(&amp;quot;Version&amp;quot;).each(function () {
            if ($(this).attr(internalFieldName) != &amp;quot;
&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;
&amp;quot;) {
                var vDate = $(this).attr(&amp;quot;Modified&amp;quot;);
                rTable += &amp;quot;
&amp;lt;tr&amp;gt;
&amp;lt;td style='vertical-align: top; padding-bottom: 10px; padding-right: 8px;'&amp;gt;&amp;quot; + $(this).attr(&amp;quot;Editor&amp;quot;).split(&amp;quot;#,#&amp;quot;)[1] + &amp;quot; (&amp;quot;
                       + vDate.substr(5, 2) + &amp;quot;/&amp;quot; + vDate.substr(8, 2) + &amp;quot;/&amp;quot; + vDate.substr(0, 4)
                       + &amp;quot;)&amp;lt;/td&amp;gt;
&amp;lt;td style='min-width:200px; vertical-align: top; padding-bottom: 10px;'&amp;gt;&amp;quot;
                       + lineEncode($(this).attr(internalFieldName)) + &amp;quot;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;quot;;
            }
        });
        rTable += &amp;quot;&amp;lt;/table&amp;gt;
&amp;quot;;
        $('#' + elementId).append(rTable);
    }
    else {
        // No entries, hide the icon (if there is one)
        // it is assumed that the corresponding image has the id IMG+divId
        if ($('#IMG' + elementId).length &amp;gt; 0) {
            $('#IMG' + elementId).css(&amp;quot;visibility&amp;quot;, &amp;quot;hidden&amp;quot;);
        }
    }
}

// ------------------------------------------------------------------------------------------
// This fuction is called when an error in the request itself occurs
// (like 404 or timeout) - not when the webService returns an error
function GetVersionsError(result) {
    // just log it
    console.log(result);
}

function lineEncode(value) {
    //create a in-memory div, set it's inner text(which jQuery automatically encodes)
    //then grab the encoded contents back out.  The div never exists on the page.
    return value.replace('\n', '
').replace('\r','
');
}

Wie funktioniert es?

Die Versionen eines Feldes bekommt man mit dem SOAP Webservice lists.asmx und dessen Methode GetVersionsCollection. Dieser Webservice wird in der Funktion GetVersions() asynchron aufgerufen, das Ergebnis als HTML aufbereitet und per jQuery in das Containerelement (in diesem Fall ein <div>) hineingeschrieben.

Wie wird es benutzt?

Der obige Code ist wie gesagt Bestandteil des Scriptes, das per JSLink referenziert wird. An dem Script müssen folgende Dinge nach eigenen Bedürfnissen angepasst werden:

  • Der Pfad zum Icon (Zeile 8)
  • Der Pfad zum Web (Zeile 43)

Vor dem obigen Scriptblock muss noch der Aufruf erfolgen, bzw die Definition welche Felder durch welche Funktion dargestellt werden. Der folgende Block steht am Anfang des Script-Files der Block oben kommt einfach dahinter.

(function () {
    var fbListContext = {};
    fbListContext.Templates = {};

    fbListContext.Templates.Fields = {

        'KommentarAlsPopup' : {          // Interner Feldname
            'View': ShowVersionedPopup   // Als Popup darstellen
        },

        'KommentarInZelle': {            // Interner Feldname
            'View': ShowVersionedDiv     // Direkt in die Zelle schreiben
        }
    };

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(fbListContext);
})();

Dieser Codeblock steht am Anfang des JSLink Scriptes und definiert, welche Felder (interne Feldnamen!) durch welche Funktion dargestellt werden sollen. Die Funktonen ShowVersionedPopup und ShowVersionedDiv sind in dem ersten Codeblock definiert.

Danach muss noch das gesamte Script eingebunden werden, dafür gibt es verschiedene Möglichkeiten, die sich unter [1] nachlesen lassen.

Viel Spaß!

Weitere Infos

[1] JSLink Tutorial