Musste erst kürzlich ein PowerShell-Script über eine Batch-Datei aufrufen, welche sich im selben Ordner befindet. Mit folgendem Befehl kann dies gemacht werden:

 @echo off
 powershell.exe -File "%~dp0\<psscript>.ps1"

Innerhalb des PowerShell-Scriptes kann nun wiederum auf den relativen Pfad verwiesen werden:

 $parentPath = Split-Path -Parent $MyInvocation.MyCommand.Definition

Miscellaneous IT Pimpery

The %~dp0 (that’s a zero) variable when referenced within a Windows batch file will expand to the drive letter and path of that batch file.

View original post 129 more words

PowerPivot OData Feeds filtern

Wenn jemand PivotTabellen erstellen möchte, welche sich über mehrere unabhängige Datenquellen verteilen ist PowerPivot ein geniales Werkzeug.

SharePoint kann über die REST-Schnittstelle eingebunden werden, welches das OData Protokoll implementiert. Dazu muss lediglich die WCF Data Services Bibliothek – die Implementierung von OData über WCF – installiert werden. Diese Schnittstelle ist über http://<sharepoint_server>/_vti_bin/listdata.svc erreichbar.

Eigentlich dachte ich, dass dieses Protokoll voll und ganz von PowerPivot unterstützt wird (zumindest wird einem das immer so erklärt). Leider gibt es einen Bereich, welcher nicht unterstützt wird:
Das Vorfiltern der Daten über den QueryString ist leider nicht möglich. Dies ist dann nützlich, wenn man vermeiden möchte, dass die komplette Liste ausgelesen wird (und anschließend in der Excel-Tabelle gespeichert wird). Die Excel Datei kann so gut und gerne 20 bis 50 MB und größer werden, je nach Anzahl an Elementen.

OData bietet für das Filtern, Sortieren und Selektieren QueryString-Parameter an.
Um z.B. alle Buchungen aus dem Jahr 2012 zu filtern kann folgendes geschrieben werden:
…/listdata.svc/Buchungen?$filter=year(Erhalten) eq 2012

Leider mag PowerPivot QueryStrings ganz und gar nicht! Und mit ganz und gar nicht meine ich, dass diese einfach ignoriert werden. Dass ich nicht der Einzige mit diesem Problem ist sieht man an folgendem Bug Report: https://connect.microsoft.com/SQLServer/feedback/details/724823/import-odata-feed-datasource-into-powerpivot-without-filter

Da ich aber nicht so lange warten wollte, bis dieses Feature in PowerPivot Einzug findet habe ich eine Quick & Dirty Lösung zusammengebastelt, welche funktioniert, aber nicht wirklich schön ist.

Eigentlich sind mir zwei Lösungen eingefallen, die Eine greift in die IIS-Konfiguration von SharePoint ein (der geneigte Leser soll selbst entscheiden, ob dies eine gute Lösung ist), die Andere dient als Gateway zum OData-Feed von SharePoint.
Die zweite Lösung ist etwas komplexer – nicht sehr viel komplexer – aber genug, um aus dem Artikel zwei zu machen. Ich habe zurzeit nicht vor auf diese Lösung genauer einzugehen, es sei den ein Leser ist an der zweiten Lösung interessiert, dann werde ich einen weiteren Blog-Eintrag posten.

Quick & Dirty Lösung: Eingriff in die IIS-Konfiguration von SharePoint

Okay, das Ändern der IIS-Konfiguration von SharePoint ist eigentlich ein absolutes NoGo (außer in den von der SharePoint Dokumentation ausdrücklich erlaubten Fällen), die Lösung hat sich aber als recht einfach und wirkungsvoll erwiesen.

Ich bediene mich hier einfach dem URL Rewriting Modul, welches einem im IIS ab Version 7 zur Verfügung steht (muss aber nachinstalliert werden: http://www.iis.net/download/URLRewrite). Eine “User-friendly URL” Rule später funktioniert das Ganze auch schon. Zu beachten ist allerdings, dass der Webserver-Prozess recycelt wird (nicht der App-Pool, geht also etwas schneller), nachdem Änderungen am URL-Rewriting vorgenommen wurden, das Ganze ist in Produktion also mit Vorsicht zu genießen.

Die URL-Regel in meinem Fall sieht beispielsweise so aus:
image

Im Grunde wird nichts Anderes gemacht, als dass die URL
http://portal/projects/buchungsportal/_vti_bin/listdata/BuchungenKundeImJahr/2012
über einen URL Match
^projects/buchungsportal/_vti_bin/listdata/BuchungenKundeImJahr/([^/]+)/?$
umgeleitet wird zu
projects/buchungsportal/_vti_bin/listdata.svc/Buchungen?$filter=InhaltstypID%20eq%20’0x010100F72541062D58004C81F84CFC226B954702007BDDD584C443764C8A87B2F22748C1AF’%20and%20year(Erhalten)%20eq%20{R:1}

Das Jahr ist also variabel und wird in {R:1} eingefügt, der Inhaltstyp ist immer derselbe, muss also nicht über die URL bestimmt werden (könnte aber ohne Weiteres gemacht werden).

Ein rascher Test mit PowerPivot beweist, dass das Rewriting funktioniert:
image

image

Es kommen auch wirklich nur die Buchungen zurück, welche nach der InhaltstypID und dem Jahr gefiltert wurden.

Tagged

HTML5 Boilerplate

HTML5 Boilerplate

Dabei handelt es sich um ein Best Practices Template für HTML5.

Beinhaltet unter Anderem:

  • HTML-Vorlage
  • CSS
    •  Style-Resettierung
    • viele weitere Formatvorlagen, z.B. für den Druck, oder mobile Geräte
  • JavaScript
    • inklusive Modernizr und jQuery
    • enthält einen interessanten Ansatz, wie eigene Scripts eingebunden werden können
  • Webserver Konfiguration
    • z.B. für die empfohlene Konfiguration der Expire Header
    • IIS web.config ist zwar nicht im Projekt enthalten, ist aber in der Doku verlinkt
  • Minification-Projekt
  • Ausführliche Dokumentation zu ALLEN vorgenommenen Anpassungen

Genial ist wirklich die zugehörige Dokumentation, da sie jede Menge Tipps und Ideen enthält. Hab dadurch wirklich eine Menge zu HTML5 gelernt!

Tagged , , , ,

Digest Authentifizierung für System.Net.Http.HttpClient

Wer’s brauch kann sichs hier herunterladen, oder noch besser, weiterentwickeln:

https://bitbucket.org/toburger/digestauthmessagehandler/

Voraussetzung ist das .NET Framework 4.5, da ich stark von den neuen async-Features gebrauch mache. Mit etwas TPL gebastel bekommt man es aber auch auf .NET 4 zum Laufen.

Die Inspiration für die Implementierung habe ich mir von hier geholt: http://blogs.msdn.com/b/henrikn/archive/2012/02/16/extending-httpclient-with-oauth-to-access-twitter.aspx

Features:

  • Authentifizierung mittels Digest
  • Zwischenspeichern des Credential Tickets, sodass bei jedem weiteren Request nicht ein neues Ticket erstellt werden muss (was jeweils zwei Requests zur Folge hätte)
  • Falls das Ticket verfallen ist, wird automatisch ein neues Ticket erstellt
Tagged , , , ,

Tapetenwechsel für die PowerShell Konsole

PowerShell ist schon eine coole Sache! Leider wird diese “Coolness” nicht über ihr Interface widergespiegelt, sondern es sind eher die inneren Werte die die PowerShell zu einem – für mich – absolut unverzichtbaren Werkzeug machen. Das gegenwärtige “Outfit” der PowerShell Konsole sieht folgendermaßen aus: Inspiriert von dem wohl zurzeit schicksten Editor – dicht gefolgt von VS11 natürlich 😉 – “Sublime Text 2“, habe ich mich dazu entschlossen die PowerShell neu einzukleiden: Ob das neue Styling nun den Geschmack des Lesers trifft liegt ehrlich gesagt außerhalb meines subjektiven Relevanzkorridors. Mir gefällts jedenfalls! 😉 Aber keine Sorge, die Farben können natürlich nach eigenem Gutdünken angepasst werden.

Und hier die Schritt für Schritt Anleitung

Zuerst muss man die Konsoleneigenschaften der PowerShell anpassen. Dies kann man mit folgendem Script erledigen.

Ich hab nicht alle Farben angepasst, nur die, die mir in der PowerShell bisher untergekommen sind.

Zu beachten ist, dass die Farben im HEX-Code anzugeben sind. Die ersten zwei Stellen können ignoriert werden (bezieht sich NICHT auf die Transparenz, wie man vielleicht vermuten würde). Allgemein ist das Muster nicht mit den #ARGB-Werten, wie man sie z.B. aus WPF/Silverlight her kennt, zu verwechseln. Die Farben sind nämlich als BGR-Werte zu definieren, dh. 0x00FF0000 ist nicht rot, sondern blau! Hat mich einige Zeit gekostet, bis ich endlich kapiert habe wie die Farbe aufgeschlüsselt ist…

Am Ende des Scripts kopiere ich nun alle Werte, so dass diese Einstellungen auch für die Verknüpfung selbst verwendet werden. Andernfalls greifen diese Einstellungen nur, wenn die PowerShell über ihren vollständigen Namen aufgerufen wird. Hier ist zu beachten, dass der Ordner gleich zu benennen ist, wie der Verknüpfungsname (im Startmenü). Allerdings ist diese Verknüpfung etwas widerspenstig. Sie definiert nämlich ein eigenes Farbschema und ignoriert unsere Einstellungen gnadenlos. Was hilft ist einfach die alte Verknüpfung zu löschen und eine neue Verknüpfung zu erstellen. Eventuell sollte man das Ausführungsverzeichnis auf %HOMEDRIVE%%HOMEPATH% festlegen, wie es vorher definiert war. Wie bereits gesagt, der Name ist wichtig, da sonst die Einstellungen wiederum nicht greifen. In meinem Beispiel verwende ich die Bezeichnung “Windows PowerShell”. Nun sollten die Einstellungen endlich Wirkung zeigen!

Kleine Zugabe

Um der PowerShell zusätzlich noch etwas Farbe zu verleihen habe ich schlussendlich noch den Prompt überschrieben. Dieses Code-Schnipsel kann im PowerShell-Profil hinterlegt werden, sodass es bei jedem Start geladen wird (eventuell kann man statt Green auch Blue hernehmen, was etwas mehr Kontrast zu den Warnmeldungen hat).

Hier nochmal die Ausgabe mit allen Farben:

Tagged ,

Zusammengesetzte Eigenschaften mal anders

Kürzlich wurde ich gefragt wie ich folgende Aufgabe lösen würde:

Ich habe eine Personenklasse, welche Vorname, Nachname, Titel, usw. als Eigenschaften enthält und es soll anschließend eine Eigenschaft erstellt werden, welche diese Eigenschaften als String ausgibt.

Das Ganze soll natürlich so gelöst sein, dass, wenn eine der Eigenschaften leer ist keine überflüssigen Leerzeichen ausgegeben werden.

Der naive Ansatz wäre natürlich der gewesen:

return string.Format("{0} {1} {2} {3}",
    Title, FirstName, MiddleName, LastName);
nur bleiben mit dieser Implementierung eben genau die überflüssigen Leerzeichen stehen, wenn eine der Eigenschaften leer ist.

Dies könnte man lösen, indem man Replace und Trim des String-Objektes verwendet oder etwas Regex-Magie anwenden würde. Ich persönlich finde solche String Manipulationen aber nicht sonderlich elegant.

Andere Ansätze würden eine erquickliche Anzahl an if-Abfragen beinhalten und man müsste darauf achten dass die Leerzeichen an den richtigen Stellen eingefügt werden. Wartbar ist das Ganze auch nur bedingt.

Meine Implementierung würde so aussehen:

public string FullName
{
    get
    {
        var elements = new string[] { Title, FirstName, MiddleName, LastName };
        return string.Join(" ", elements
            .Where(e => !string.IsNullOrWhiteSpace(e)));
    }
}

Das elements-Array beinhaltet die Strings in der Reihenfolge wie sie ausgegeben werden sollen.
Der Join mit der LINQ-Abfrage sorgt dafür, dass nur die Elemente, welche nicht leer sind oder keine Leerzeichen beinhalten, ausgewählt werden und „vereint“ diese mit einem Leerzeichen getrennt.


Kommt nun eine weitere Eigenschaft hinzu, kann man diese einfach in das elements-Array an der gewünschten Position einfügen.

Tagged ,

Atomare farbige Konsolenausgabe (thread safe)

Ich hätte mir nicht gedacht, dass ich mal über so etwas triviales wie die Konsolenausgabe posten werde, aber wieso nicht, fange ich mit etwas an, was in jedem “Hello World!”-Beispiel zu finden ist und reichere es mit einem – hoffentlich – wissenswerten Tipp zum Thema Threading an… 😉

In einem meiner vielen Mockup-Projekte, in welchen ich einfach naiv drauf los programmiere um zu Lernen (jaja, so was mach ich ständig, nennt sich auch “trial and failure” oder “learning by doing”), bin ich auf folgendes Problem gestoßen:

Ich habe in meinem Programm, welches mehrere Threads instanziiert, in den einzelnen Threads Meldungen ausgegeben, was denn gerade passiert.

Nun ist das ja an sich nichts ungewöhnliches, WriteLine ist „thread safe“ und kann ohne Bedenken in mehreren parallel laufenden Threads verwendet werden, ohne dass sich die Threads in die Quere kommen.

Um in meinem Testprogramm nun Fehler besser von den „normalen“ Meldungen zu unterscheiden, habe ich diese rot eingefärbt.

Um die Konsolenausgabe einzufärben und anschließend in den Ursprungszustand wiederherzustellen ist folgender Code vonnöten:

Console.ForegroundColor = consoleColor;
Console.WriteLine("Meldung");
Console.ResetColor();

Wieso hab ich denn ein Problem damit?

Das Ganze hat nun aber einen entscheidenden Haken: der Code ist nicht atomar, das heißt, wird dieser Code von zwei Threads gleichzeitig aufgerufen, dann kann es sein, dass die Ausgabe die falsche Farbe hat!
Am besten ich erkläre es an einem Beispiel.
Folgender Code führt parallel eine Konsolenausgabe aus und weist ihr eine zufällige Farbe zu:

Random rnd = new Random();
Parallel.For(0, Console.WindowHeight - 1, i =>
{
    ConsoleColor consoleColor = (ConsoleColor)rnd.Next(7, 16);

    Console.ForegroundColor = consoleColor;
    Console.WriteLine("Meldung: {0}", consoleColor);
    Console.ResetColor();
});

Das Ergebnis kann man hier bewundern. Wie man sieht stimmen die Farben überhaupt nicht zusammen!

Die Lösung

Was man nun machen muss ist das Ganze atomar zu machen, also alle Threads während dieser Operation zu blockieren, bis die Ausgabe erledigt wurde.

Dies kann in C# mit einem Monitor.Enter oder mit dem C# Schlüsselwort lock (ist nichts anderes als ein Alias auf Monitor.Enter) erledigt werden:

lock (Console.Out)
{
    Console.ForegroundColor = consoleColor;
    Console.WriteLine("Meldung: {0}", consoleColor);
    Console.ResetColor();
}

Alles was innerhalb dieses locks passiert ist atomar, dh. keine anderen Konsolenoperationen kommen meinem Code in der Zwischenzeit in die Quere.

Tagged , ,

TYPO3 unter Windows mit Wincache beschleunigen

Was ist Wincache?

Wincache, oder Windows Cache Extension, ist ein PHP Optimierer, ähnlich wie APC. Er generiert aus den PHP-Quelldateien Bytecode, welcher im Speicher des Servers abgelegt wird. Somit muss dieser Bytecode nur einmal erstellt werden und nicht jedes Mal, wenn ein PHP-Script aufgerufen wird. Im Unterschied zu APC läuft Wincache nur auf Windows-Systemen, hat aber den Vorteil, dass er speziell für diese Plattform optimiert ist.

Wincache und APC bieten außerdem eine Caching API, welche es möglich macht Daten abzulegen und in darauffolgenden Requests wieder zu verwenden. Auf vielen Systemen wird dafür entweder die Datenbank (der Klassiker bei TYPO3) oder memcached verwendet.

Auch wenn die Unterschiede zwischen den beiden Bytecode-Optimierern eher marginal sind, fiel meine Wahl schließlich auf Wincache, einfach aus dem Grund, da der Webserver ein Windows 2008 Server ist und Wincache speziell für die Windows-Plattform entwickelt wurde und es laut verschiedener Benchmarks um einen Tick schneller als APC zu laufen scheint (ich muss zugeben ich habe selbst noch keinen Vergleichstest gemacht, verlasse mich aber mal auf die Angaben anderer).

Warum Wincache und TYPO3?

TYPO3 kannte lange Zeit nur zwei Caching-Methoden. Entweder das Dateisystem oder die Datenbank. Dies sind nicht gerade ideale Orte um Daten mit hohem I/O-Durchsatz abzulegen. Später wurden diese Cache „Backends“, wie sie TYPO3 nennt, konfigurierbar. Unter Anderem kamen ein memcached- und ein APC-Backend dazu.

In meinem konkreten Fall wurde auf der existierenden Webseite memcached unter Windows eingesetzt. Memcached ist wohl der Klassiker auf *nix-Systemen, wenn es um das Zwischenspeichern von Daten im Arbeitsspeicher, auch über mehrere Maschinen hinweg, geht. Es gibt auch Ports für Windows-Systeme, welche einfach einen Windows-Dienst bereitstellen, welcher memcached.exe hostet.

Habe ich schon erwähnt, dass die Seite auf welche ich mich beziehe langsam war? Nicht? Dann hole ich das jetzt nach: die Seite war träge, vor allem die Startseite! Die Startseite brauchte warm (!) – also nachdem die meisten Inhalte im Cache vorhanden waren – teilweise über eine Sekunde, bis sie sich endlich dazu entschlossen hat HTML-Code auszuspucken. Okay, es waren einige dynamische Inhalte vorhanden, wie z.B. Banner, welche zufällig ausgespuckt werden sollten, deshalb wurde auf ein aggresives Caching verzichtet. Trotzdem: es dauerte einfach viel zu lange…

Zeit mir das Ganze auf einem dedizierten System genauer anzusehen. Ich kopierte also die TYPO3-Seite und alle notwendigen Serverkomponenten auf mein Testsystem, hab alles wie auf dem Live-System konfiguriert und führte ein paar Tests durch. Mit mehr oder weniger demselben Ergebnis (sieht man von der unterschiedlichen Prozessoranzahl und verfügbarem Arbeitsspeicher ab): das Ding skalierte einfach nicht!

Ich hab mich daraufhin dazu entschlossen dem System auf den Zahn zu fühlen. Ich muss zugeben, ich habe mich mit TYPO3 seit fast zwei Jahren nicht mehr beschäftigt und das Caching war relativ neu für mich (ich kannte noch den DB-Cache). Aber nach einem Blick in die localconf.php (soweit konnte ich mich noch aus meiner früheren Zeit mit TYPO3 erinnern) ist mir der Konfigurationsabschnitt mit $TYPO3_CONF_VARS['SYS']['caching'] aufgefallen und dass es einige Einträge mit t3lib_cache_backend_MemcachedBackend gab. „Könnte es sein, dass das ein Flaschenhals ist?“ hab ich mir gedacht und mir überlegt, ob man das Ganze nicht mit Wincache beschleunigen könnte.

Dafür muss es doch eine „Backend“-Klasse geben!

Ich wurde enttäuscht. Nachdem ich einen Blick in den Ordner „t3libcachebackend“ geworfen hatte war kein Backend auffindbar, welches Wincache verwendet. Auch im Internet wurde ich nicht fündig.

Da es jedoch ein Backend für APC gibt und ich wusste, dass die beiden Byteoptimierer von ihren Features und der API fast identisch sind, habe ich einfach den bestehenden Code für Wincache portiert. Zu finden ist diese portierte Klasse am Ende des Artikels.

Was noch fehlt, ist dieses Backend in TYPO3 zu registrieren. Dies kann in der localconf.php folgendermaßen vorgenommen werden:

$TYPO3_CONF_VARS['SYS']['caching']['cacheBackends']['t3lib_cache_backend_WincacheBackend'] =
'<path_to_the_class>/class.t3lib_cache_backend_wincachebackend.php:t3lib_cache_backend_WincacheBackend';

Nun muss noch konfiguriert werden, wo das Backend verwendet werden soll. Dies kann über folgenden PHP-Code vorgenommen werden:

$TYPO3_CONF_VARS['SYS']['caching']['cacheConfigurations'] = array(
'cache_hash' => array(
'backend' => 't3lib_cache_backend_WincacheBackend',
'options' => array()
),
'cache_pages' => array(
'backend' => 't3lib_cache_backend_WincacheBackend',
'options' => array()
), 'cache_pagesection' => array(
'backend' => 't3lib_cache_backend_WincacheBackend',
'options' => array()
),
'cache_extbase_reflection' => array(
'backend' => 't3lib_cache_backend_WincacheBackend',
'options' => array()
)
);

Falls es Extensions gibt, welche ebenfalls vom Caching Gebrauch machen, müssen diese ebenfalls mit angegeben werden.

Nachdem alles registriert wurde, sollte die Seite weiterhin ohne Einschränkungen funktionieren, nur dass diesmal der TYPO3-Cache von Wincache verwaltet wird.

Hat’s denn was gebracht?

Ich muss ehrlich gestehen, ich war mir nicht sicher, ob das Ersetzen von memcached durch Wincache allzu viel bringen wird. Ich wurde aber aufs positivste überrascht! Die Seite reagierte plötzlich auf Anfragen viel schneller. Vor allem die Startseite wirkte „frischer“.

Da ich aber diesen subjektiven Eindruck durch einen objektiven Beleg untermauern wollte habe ich “ab” (Apache Benchmark Tool) angeworfen. Dieses kleine aber praktische Tool simuliert Anfragen an einen Server und spuckt anschließend eine Statistik aus, wie schnell die Anfragen abgearbeitet werden konnten.

Das Tool sollte am besten auf derselben Maschine ausgeführt werden, auf welcher die Webseite selbst läuft, damit die Statistik nicht durch Netzwerklatenzen verfälscht wird.

Und hier die Ergebnisse:

Um nun 20 Requests zu simulieren, wobei 4 Requests jeweils parallel ausgeführt werden, kann man folgende Einstellung vornehmen:

.ab.exe -n 20 -c 4 http://%5Bhostname%5D/

memcache aktiviert

25.837 seconds (1291.850 [ms]/Request)

Wincache aktiviert

9.500 seconds (475.005 [ms]/Request)

Obendrein skaliert das Ganze auch noch sehr gut, wie man sehen kann:

60 Requests, wobei 12 parallel ausgeführt werden

.ab.exe -c 12 -n 60 http://%5Bhostname%5D/

memcache aktiviert

71.586 seconds (1193.100 [ms]/Request)

Wincache aktiviert

14.645 seconds (244.083 [ms]/Request)

Zusammenfassung

Zusammenfassend kann man sagen, dass der Einsatz von Wincache wirklich einen merklichen Performanceboost bringt. Und das Ganze ist noch dazu einfach zu erreichen! Eine PHP-Datei und einige Konfigurationseinstellungen genügen, um TYPO3 um 200% zu beschleunigen!

Natürlich hängt der Geschwindigkeitszuwachs von der Komplexität der Seite und der Menge an zwischengespeicherten Daten ab und nicht immer ist ein solches Ergebnis zu erreichen, aber wenn ein solch eklatanter Boost der Lohn für die Mühen ist, sollte man es auf einen Versuch ankommen lassen! 😉

Update

Ich habe den Quellcode inzwischen ins Repository von TYPO3 committed (https://review.typo3.org/#/c/10204/) und wer weiß, mit etwas Glück schafft er es vielleicht in ein offizielles Release… 🙂

Tagged , , , , , ,

Notiz an mich: ‘throw’ ist gut, ‘throw ex’ ist böse

Richtig:

catch (FooException ex)
{
    // mach hier dein Ding
    throw;
}

Falsch:

catch (FooException ex)
{
    // mach hier dein Ding
    throw ex;
}

throw ex ist ein Fehler der von C#-Entwicklern gerne gemacht wird (vor allem von den Programmieren mit Java-Hintergrund), bzw. den man des Öfteren in Beispielen und Foren findet.

Der Unterschied ist, dass bei throw ex im Gegensatz zu throw der StackTrace verloren geht, was eigentlich NIE gewünscht ist.

Tagged , , ,

SharePoint 2010 Management Shell Ausgaben anpassen

Die “SharePoint 2010 Management Shell”, welches im Grunde eine PowerShell-Instanz ist, welche das PSSnapin mit dem Namen Microsoft.SharePoint.PowerShell geladen hat, ist extrem flexibel.

Ich könnte jetzt eine seitenweise Huldigung über die PowerShell schreiben, werde mich aber am Riemen reißen und nur einen der vielen Erweiterungspunkte der PowerShell ansprechen.

Die Erweiterungspunkte welche ich ansprechen möchte sind in der Hilfe von PowerShell dokumentiert.
Die beiden man-pages (wenn man die Linux-kompatible Bezeichnung für die Hilfe verwendet) nennen sich about_Types.ps1xml und about_Format.ps1xml:

man about_Types.ps1xml
man about_Format.ps1xml

Was mit diesen beiden XML-Dateien möglich ist, ist zum Einen das Typsystem der PowerShell zu erweitern (Types.ps1xml) und zum Anderen die Ausgabe auf dem Host zu beeinflussen (Format.ps1xml).

Types.ps1xml

Eines der wohl promintesten Beispiele für eine Erweiterung des Typsystems ist wohl das Count AliasProperty des Array-Objektes. .NET-Programmierer wissen, dass die Länge eines Arrays über das Length-Property ermittelt wird. Andere Collections benutzen dafür aber das Count-Property. Um diese Inkonsistenz zu vermeiden führt die PowerShell das Length AliasProperty ein.

Überall in der PowerShell findet man solche Typerweiterungen. Es gib verschiedenste Möglichkeiten einen Typ zu erweitern, AliasProperty ist die simpelste Möglichkeit und ist einfach ein Verweis auf ein bereits bestehendes Property (im Beispiel des Array.Count ist dies ein Verweis auf Array.Length). Es gibt aber auch komplexere, mit welchen Berechnungen möglich sind, wie z.B. das ScriptProperty. Weitere Informationen bitte about_Types.ps1xml entnehmen.

Format.ps1xml

Diese Datei ermöglicht die Ausgabe der Powershell auf dem Host zu beeinflussen. Ein einfaches Beispiel ist die Ausgabe des Verzeichnissinhaltes mit dir: da es sich im Grunde um .NET-Objekte handelt, welche zurückgegeben werden, muss der PowerShell mitgeteilt werden, wie diese schlussendlich dem Benutzer präsentiert werden sollen, da sonst jede einzelne Eigenschaft in einer Liste ausgegeben wird. Lesbarer (und auch vom DOS-Prompt her gewohnt) ist die Ausgabe mit einer eingeschränkten Anzahl von Eigenschaften und einer tabellarischen Ausgabe. Und genau hierfür sorgt die angesprochene Datei. Weitere Informationen bitte about_Format.ps1xmlentnehmen.

Und was bringt’s mir im Zusammenhang mit SharePoint?

Nun, SharePoint bietet leider eine sehr rudimentäre Implementierung dieser beiden Erweiterungspunkte. Zu finden sind die Formatdateien unter: %PROGRAMFILES%Common FilesMicrosoft SharedWeb Server Extensions14CONFIGPOWERSHELLFormat und die Typedateien unter: %PROGRAMFILES%Common FilesMicrosoft SharedWeb Server Extensions14CONFIGPOWERSHELLTypes.

Wie gesagt, diese Format- und Typdefinitionen sind recht rudimentär. Auf einem Produktivsystem sollten diese Definitionen aber ausreichen. Auf einem Entwicklersystem ist es aber manchmal bequemer, wenn man etwas mehr Informationen aus der PowerShell-Ausgabe erhält, als dies standardmäßig der Fall ist.

Weiter unten habe ich zwei Dateien angehängt, welche nach belieben angepasst werden können. Sie sollen lediglich als Ideengeber dienen.

Geladen werden die Dateien mittels den zwei Kommandos:

Update-TypeData -Prepend SharePoint.types.ps1xml
Update-FormatData -Prepend SharePoint.format.ps1xml

Diese beiden Befehle können im Profil hinterlegt werden, sodass sie beim Aufruf der PowerShell automatisch geladen werden.

Die Reihenfolge sollte wie oben sein (Type, dann Format), da die Ausgabe oftmals auf die definierten Typen verweist.

Falls nachträglich Änderungen an den beiden Dateien durchgeführt wurden, können die Definitionen über Update-TypeData und Update-FormatData neu geladen werden (ohne -Prepend).

Beispielausgabe

Standardausgabe von SharePoint:

Nach dem Laden der neuen Typ- und Ausgabedefinitionen:

Beispieldateien

Die Informationen, welche ausgegeben werden sollen, können für jeden Benutzer/Entwickler unterschiedlich sein, deshalb habe ich jeweils ein Beispiel-XML für die Formate und eines für die Types als Ausgangsdateien erstellt. Diese können dann selbst erweitert und angepasst werden:

SharePoint.format.ps1xml

<Configuration>
  <ViewDefinitions>
    <View>
      <Name>Microsoft.SharePoint.SPWeb</Name>
      <ViewSelectedBy>
        <TypeName>Microsoft.SharePoint.SPWeb</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Width>25</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>20</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>9</Width>
          </TableColumnHeader>
          <TableColumnHeader />
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Title</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>LastItemModifiedDate</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Language2</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Url</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <Name>Microsoft.SharePoint.SPList</Name>
      <ViewSelectedBy>
        <TypeName>Microsoft.SharePoint.SPList</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader />
          <TableColumnHeader>
            <Width>20</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>10</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>9</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>36</Width>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Title</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>LastItemModifiedDate</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>FieldCount</PropertyName>
                <Alignment>Right</Alignment>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ItemCount</PropertyName>
                <Alignment>Right</Alignment>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ID</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
    <View>
      <Name>Microsoft.SharePoint.SPListItem</Name>
      <ViewSelectedBy>
        <TypeName>Microsoft.SharePoint.SPListItem</TypeName>
      </ViewSelectedBy>
      <TableControl>
        <TableHeaders>
          <TableColumnHeader>
            <Width>8</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>19</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>20</Width>
            <Label>ContentType</Label>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>7</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>20</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>19</Width>
          </TableColumnHeader>
          <TableColumnHeader>
            <Width>19</Width>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>ID</PropertyName>
                <Alignment>Right</Alignment>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Title</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>$_.ContentType.Name</ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Version</PropertyName>
                <Alignment>Right</Alignment>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Author</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Created</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Modified</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

SharePoint.types.ps1xml

<Types>
  <Type>
    <Name>Microsoft.SharePoint.SPWeb</Name>
    <Members>
      <AliasProperty>
        <Name>LCID</Name>
        <ReferencedMemberName>Language</ReferencedMemberName>
        <TypeName>System.Int32</TypeName>
      </AliasProperty>
      <ScriptProperty>
        <Name>Language2</Name>
        <GetScriptBlock>
          [System.Globalization.CultureInfo]::GetCultureInfo($this.LCID).Name
        </GetScriptBlock>
      </ScriptProperty>
    </Members>
  </Type>
  <Type>
    <Name>Microsoft.SharePoint.SPList</Name>
    <Members>
      <AliasProperty>
        <Name>Url</Name>
        <ReferencedMemberName>DefaultViewUrl</ReferencedMemberName>
        <TypeName>System.Uri</TypeName>
      </AliasProperty>
      <ScriptProperty>
        <Name>FieldCount</Name>
        <GetScriptBlock>
          $this.Fields.Count
        </GetScriptBlock>
      </ScriptProperty>
    </Members>
  </Type>
  <Type>
    <Name>Microsoft.SharePoint.SPListItem</Name>
    <Members>
      <ScriptProperty>
        <Name>Url</Name>
        <GetScriptBlock>
          $this["ServerUrl"]
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>Author</Name>
        <GetScriptBlock>
          $this.Fields.GetFieldByInternalName("Author").GetFieldValueAsText($this["Author"])
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>Created</Name>
        <GetScriptBlock>
          [DateTime]$this["Created"]
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>Modified</Name>
        <GetScriptBlock>
          [DateTime]$this["Modified"]
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>Version</Name>
        <GetScriptBlock>
          if ($this.Versions -and $this.Versions.Count -gt 0) {
          $this.Versions[0].VersionLabel
          }
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>FieldCount</Name>
        <GetScriptBlock>
          $this.Fields.Count
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>WorkflowCount</Name>
        <GetScriptBlock>
          if ($this.Workflows) { $this.Workflows.Count } else { 0 }
        </GetScriptBlock>
      </ScriptProperty>
      <ScriptProperty>
        <Name>RunningWorkflowCount</Name>
        <GetScriptBlock>
          if ($this.Workflows -and $this.Workflows.Count -gt 0) {
          @($this.Workflows | ? { !$_.IsCompleted }).Count
          } else { 0 }
        </GetScriptBlock>
      </ScriptProperty>
    </Members>
  </Type>
</Types>
Tagged , ,