BizTalk: Checkliste für Empfang von AS2-Nachrichten

by wolfgang@gehirnwindung.de (Wolfgang) Januar 15, 2010 00:06

Um über BizTalk-HTTP-Adapter AS2 Daten empfangen zu können, müssen gleich ein paar Einstellungen passen. Ein kleiner Überblick über die wichtigsten Einstellungen soll hiermit geschaffen werden:

  1. Virtuelles Verzeichnis für den Empfang der Nachrichten über HTTP
    • Local Path: Verzeichnis, in dem die Datei BTSHttpReceive.dll liegt, z.B. C:\Program Files\Microsoft BizTalk Server 2006\HttpReceive\ bzw. C:\Program Files\Microsoft BizTalk Server 20009\HttpReceive\
    • Execute permissions: Scripts and Executables
    • Identität des zugewiesenen Application Pools muss ein Benutzer sein, der Zugriff auf die BizTalk-Datenbank hat, in der Regel wird hier BTSIsolatedHostUser verwendet
    • In welcher Web Site das virtuelle Verzeichnis liegt (es kann natürlich auch die Web Site direkt verwendet werden) ist dabei egal. Allerdings sollte man bei mehreren Web Sites darauf achten, dass der virtuelle Pfad über alle Web Sites hinweg eindeutig ist, da man innerhalb der Receive Location die betroffene Web Site nicht mit angeben kann.
  2. Receive Port anlegen
    • Keine oder asynchrone MDN: One-Way
    • MDN synchron: Request-Response
  3. Receive Location anlegen
    • Type: HTTP-Adapter
      • Virtual directory plus ISAPI extension: z.B. /virtualDirectory/BTSHttpReceive.dll.
        Wie oben erwähnt, können die Web Sites des IIS hier nicht angegeben werden. Nur der virtuelle Pfad (ausgehend vom root-Verzeichnis)
      • Es kann ein beliebiger QueryString an die DLL angehängt werden. Damit können - mit nur einem virtuellen Verzeichnis - für verschiedene Kunden und/oder Dokument verschiedene Receie Locations definiert werden (an sich braucht man damit nur ein virtuelles Verzeichnis im IIS einrichten).
        Dieser Querystring muss exakt genauso auch im sendenden Programm angegeben werden (mit exakt ist Reihenfolge, Groß-Kleinschreibung und Umfang gemeint...).
      • Die Einstellung Suspend failed requests aktivieren
    • Receive Pipeline
      • AS2Receive: Wenn beliebige Daten per AS2 empfangen werden sollen
      • AS2EdiReceive: Wenn EDIFACT-Nachrichten per AS2 empfangen werden sollen
      • oder eine selbst-erstellte Pipeline, die die AS2 decoder-Component verwendet
  4. Sender-Party
    • Party Properties
      • Damit der Firmeneintrag beim Empfang der Datei gefunden werden kann, muss als EDIINT-AS2 From Value (AS2-From) der, zwischen den Partnern ausgemachte Name angegeben werden. Der Name sollte nur aus ASCII-Zeichen bestehen
    • AS2 Properties, Party as AS2 Message Sender
      • Hier kann man eigentlich alles auf Standard lassen (keine Checkbox aktiv). Damit gibt der Sender vor, wie die Datei zu behandeln ist.
      • Man kann den Sender auch zu bestimmten Einstellungen zwingen. Dazu die Checkbox Override inbound message properties aktivieren und die Einstellungen in der Gruppe Incoming AS2 message anpassen
      • Wenn die MDN signiert werden soll, dann kann das erreicht werden, indem die Checkbox Sign requested MDN if Disposition-Notification-Option header is not present or if Signed-Receipt-Protocol header is set to optional aktiviert werden. Der Sender darf das allerdings nicht ausschließen...
  5. Empfänger-Party (aktueller Rechner)
    Beim Empfänger in diesem Fall nicht viel Einstellbar. Hier geht es primär um die MDN, die versendet wird
    • Party Properties
      • Damit die Einstellungen der Firma verwendet werden können, muss als EDIINT-AS2 To Value (AS2-To)-Wert der zwischen den Partnern ausgemachte Name für den Empfänger angegeben werden
  6. Send Port oder Orchestration zum Verarbeiten der ankommenden Daten
    • Damit keine Fehlermeldung angezeigt wird, muss die Datei auch verabeitet werden - und das kann man nur erreichen, indem man entweder
      • den oben erstellen Receive Port einer Orchestration zuweist (Bindings -> Inbound Logical Port), oder
      • einen Send Port erstellt und den Filter so anpasst, dass die ankommenden Nachrichten über diesen Port verarbeitet werden (z.B. über die Eigenshaft BTS.ReceivePortName)

Zugegeben, wirklich kurz ist die Liste nicht ;) Dennoch sind nur die notwendigsten Einstellungen beschrieben.

Falls die Nachrichten und MDNs signiert und/oder verschlüsselt werden sollen, hab ich im Artikel BizTalk: Wohin mit den AS2-Zertifikaten entsprechende Einstellungen beschrieben.

Update: Punkt 6 hat zwar an sich nichts mehr mit dem Empfang der Daten zu tun, kam aber dennoch dazu, da der Punkt offenbar oft vergessen / übersehen wird (hat man mir berichtet).

Tags: ,

Microsoft BizTalk Server

BizTalk: Wohin mit den AS2-Zertifikaten (Signieren und Verschlüsseln)

by post@example.com (Admin) Januar 14, 2010 00:27

Will man Nachrichten mittels AS2 verschlüsselt oder signiert versenden oder eine verschlüsselte oder signierte Nachricht empfangen, so muss man sich zwangsläufig mit Zertifikaten auseinandersetzen und diese an bestimmte Stellen (Zertifikatsspeicher) ablegen und an ebenso genau definierten Stellen in der BizTalk Administration Console auswählen.

Am Einfachsten macht man sich die Arbeit, wenn man das SDK-Tool "Certificate Wizard" (unter C:\Program Files\Microsoft BizTalk Server 2006\SDK\Utilities\Certificate Wizard\CertWizard.exe bzw. im Verzeichnis BizTalk Server 2009 zu finden) verwendet. Dieses Tool kopiert je nach Einstellung die Zertifikatsdateien in die entsprechenden Zertifikatsspeicher. Wer es manuell machen oder einfach nur überprüfen will, der kann sich an folgender Liste orientieren.

Das Auswählen des Zertifikats innerhalb der BizTalk Server Administration Console muss zudem auch noch an entsprechenden Stellen erfolgen. Auch hier hilft die Liste weiter.

Nachrichten

  Signierung Ver-/Entschlüsselung
Eingehend Speicher:
Typ:
BizTalk:
Other People (Local computer)
Public key (Partner)
Party (Partner) / Certificates
Speicher:
Typ:
BizTalk:
Personal store (InProc User)
Private key (Home)
Isolated Host / Certificates
Ausgehend Speicher:
Typ:
BizTalk:
Personal store (InProc user)
Private key (Home)
Group / Certificates*
Speicher:
Typ:
BizTalk:
Other People (Local computer)
Public key (Partner)
Send Port / Certificates

MDN

  Signierung
Eingehend Speicher:
Typ:
BizTalk:
Other People (Local computer)
Public key (Partner)
Party (Partner) / Certificates
Ausgehend
synchron
Speicher:
Typ:
BizTalk:
Personal store (Isolated host user)
Private key (Home)
Group / Certificates*
Ausgehend
asynchron
Speicher:
Typ:
BizTalk:
Personal store (InProc user)
Private key (Home)
Group / Certificates*

* Für die ausgehende Signierung kann seit BizTalk Server 2006 R2 SP1 das Zertifikat anstatt bei der BizTalk Group auch bei der Party direkt angegeben werden.

Zertifikatsspeicher

Die Zertifikatsspeicher sind nicht ganz einfach zu finden. Man kann aber wie folgt vorgehen. Bestenfalls legt man sich eine eigene Administrationskonsole an.

  • mmc.exe starten
  • Menü "File" -> "Add/Remove Snap-In..."
  • Button "Add..."
  • "Certificates" auswählen und mit "Add" bestätigen
  • "Computer Account" auswählen
  • weiter bis Finish
  • "Certificates" auswählen und mit "Add" bestätigen
  • "My User Account" auswählen

Leider kann man keinen speziellen Benutzer auswählen. Die Datei kann aber als *.msc-Datei gespeichert werden und danach mit der rechten Maustaste mittels "Run as..." mit einem anderen Benutzer gestartet werden. Hier kann dann der BTSHostUser und der BTSIsolatedHostUser eingetragen werden. Nicht schön, aber hilfreich ;)

Tags: ,

Microsoft BizTalk Server

BizTalk: unexpected token ';'

by wolfgang@gehirnwindung.de (Wolfgang) November 30, 2009 22:03

Ein Kunde kam heute mit einer Orchestration zu mir, die folgende Fehlermeldung beim kompilieren erzeugte:

Error X2016: unexpected token ';'

Wie so oft führt auch hier eine eigentlich extrem kleine Ursache zu dieser Auswirkung. Die Fehlermeldung sagt leider nicht viel aus. Nach kurzer Suche im .odx-Quelltext findet man dann aber zum Glück schnell ein Konstrukt wie

variableName =  ;

Das sieht nicht nur seltsam aus, sondern ist natürlich auch falsch... Grund war in diesem Fall ein einzelnes Leerzeichen als "Initial Value" der System.String-Variable. Das ist fast schon "fies", da man den Fehler nicht einmal auf den ersten Blick auf die Eigenschaften der Variable erkennen kann (wie im Screenshot zu sehen).

BizTalk Server Orchestration-Fehlermeldung unexpected token

Für string-Variablen muss jeder Initialwert also stets in doppelten Anführungsstrichen stehen.

Tags: ,

Microsoft BizTalk Server

Blog-Parade: Die 3 beliebtesten Fachbücher aus dem .NET-Umfeld (Ergebnis)

by wolfgang@gehirnwindung.de (Wolfgang) November 17, 2009 15:54

Nachdem diese Blog-Parade nicht ganz so erfolgreich verlief, wie ich mir das vorgestellt hatte, wird es recht schwer mit Statistiken - weshalb ich an dieser Stelle auch darauf verzichte. Das beliebteste Fachbuch konnte also leider nicht wirklich gefunden werden. Die Liste der vorgestellten Bücher entspricht dennoch der bewerteten Reihenfolge, wobei die Sprache für die Reihenfolge außer acht gelassen wurde.

  1. C# 3.0 Entwurfsmuster / C# 3.0 Design Pattern
     
    von Judith Bishop.
    ISBN: 978-3-89721-867-3(de) bzw. 978-0-59652-773-0 (en)
  2. Clean Code: A Handbook of Agile Software Craftsmanship

    von Robert C. Martin
    ISBN: 978-0-13235-088-4 (en)
  3. Agile Principles, Patterns and Practices in C#

    von Robert C. Martin und Micah Martin
    ISBN: 978-0-13185-725-4 (en)
  4. ASP.NET MVC 1.0

    von Rob Conery, Scott Hanselman, Phil Haack und Scott Guthrie
    ISBN: 978-0-47038-461-9 (en)
  5. Richtlinien für das Framework-Design

    von Krzysztof Cwalina und Brad Abrams
    ISBN: 978-3-82732-626-3 (de)
  6. The Art of Unit Testing

    von Roy Osherove
    ISBN: 978-1-93398-827-6 (en)
  7. Visual C# 2008

    von Frank Eller
    ISBN: 978-3-82732-641-6 (de)
  8. Grundlagen der Anwendungsentwicklung mit dem .NET Framework 2.0

    von Tony Northrup, Shawn Wildermuth und Bill Ryan
    ISBN: 978-3-86645-907-6 (de)
  9. jQuery in Action

    von Bear Bibeault und Yehuda Katz
    ISBN: 978-1-93398-835-1 (en)
  10. Refactoring

    von Martin Fowler
    ISBN: 978-3-82731-630-1 (de)

 

Gewonnen haben alle Teilnehmer. Da es nur 4 waren hab ich mich kurzerhand entschlossen, noch einen weiteren Gutschein mit in die Liste der Gewinne mit aufzunehmen. Ich hab eine kleine Applikation geschrieben, die die Gewinner zieht - und nach diesem Losverfahren kamen folgende Plätze zustande:

  1. Gordon Breuer / anheledir.NET hat den Gutschein über € 40 gewonnen
  2. Stefan Macke / Stefans Blog hat den Gutschein über € 20 gewonnen
  3. Sven Eiter / Svens Blog hat einen Gutschein über € 10 gewonnen
  4. Mario Noack / .NET und PDF Software hat das Buch gewonnen

Glückwunsch nochmal...

Tags:

Blog-Parade

BizTalk Server Scripting-Functoid wider Erwarten...

by wolfgang@gehirnwindung.de (Wolfgang) September 23, 2009 20:00

Mit dem Scripting Functoid des BizTalk-Mappers kann man bekanntlich eigene Scripts in verschiedenen Sprachen (z.b. C#) während des Mappings ausführen lassen. Man definiert damit eine Funktion deren Parameter mit Hilfer der Eingangselemente gefüllt werden können. Der Rückgabewert der Funktion ist das, was das Functoid als Ausgangsparameter weitergibt. Bis hierhin ist alles noch selbsterklärend.

Falls nun mehrere Scripting Functoide existieren, die alle das Gleiche tun, so ist man gut beraten, auch immer den gleichen Methodennamen zu verwenden. Diese werden beim Kompilieren in XSLT zusammengeführt (aus dem *.btm-Dateien wird immer xslt generiert). Innerhalb dieser XSLT-Datei, wird dann auch immer nur eine Funktion aufgerufen.

Allerdings scheint den Entwicklern ein kleiner Fehler unterlaufen zu sein. Denn nicht der Methodenname und der dazugehörige Inhalt (MethodBody), sondern lediglich der Methodenname und die Anzahl der Parameter wird verwendet, um die "Gleichheit" zweier Funktionen festzustellen.

vollständigen Artikel anzeigen...

Tags: ,

Microsoft BizTalk Server

Blog-Parade: Die 3 beliebtesten Fachbücher aus dem .NET-Umfeld

by wolfgang@gehirnwindung.de (Wolfgang) September 01, 2009 20:32

Hallo zusammen,

mir hat die Idee der Blog-Parade sehr gut gefallen. Und nach kurzer Überlegung ist mir sogar eine sinnvolle Verwendung eingefallen... Ich suche die beliebtesten Fachbücher, die irgendwas mit Programmierung im .NET-Umfeld zu tun haben.

Wer teilnehmen möchte, schreibt einen Beitrag mit seinen 3 beliebtesten Büchern (inklusiver kurzer Begründung) und mit Trackback auf diesen Artikel in seinem Blog. Sollte die Trackback-Funktion mal nicht funktionieren, so schreibt bitte einen kleinen Kommentar mit entsprechender URL zu eurem Artikel. Am Ende der Blog-Parade werden die Teilnehmer aus den Kommentaren ermittelt. Bitte prüft also, ob der Trackback funktioniert hat. Es dürfen gerne auch mehr als 3 Bücher beschrieben werden.

Alle Artikelautoren nehmen einmal (ungeachtet der Anzahl der Beiträge oder der genannten Bücher) an der Verlosung folgender Preise teil:

  1. Ein Amazon, buecher.de oder BOL - Gutschein in Höhe von € 40
  2. Ein Amazon, buecher.de, oder BOL - Gutschein in Höhe von € 20
  3. Das Buch Microsoft ASP.NET AJAX Programmierung von Dino Esposito aus dem Microsoft Press Verlag (wie unten beschrieben)

Und bevor es zu irgendwelchen Fragen kommt (Teilnahmebedingungen)

  • Teilnahmeberechtigt ist jeder, der einen Artikel in einem Blog veröffentlichen kann.
    Es dürfen gerne mehrere Artikel eingereicht werden, dies erhöht die Gewinnchancen für den Teilnehmer jedoch nicht.
  • Die 3 Gewinner werden nach Abschluß der Blog-Parade per Los ermittelt und daraufhin per Email benachrichtig.
    Im jeweiligen Blog muss daher ein Kontaktformular oder eine Email-Adresse zu finden sein!
  • Die Gewinner werden nach Rücksprache in diesem Blog bekannt gegeben (mit Vor- und Nachname sowie Link auf das jeweilige Blog)
  • Die Zustellung des Gewinns erfolgt per Email oder im Falle des Buches per Post ohne Versandversicherung. Eine Ausbezahlung in Bar ist nicht möglich.
  • Der Rechtsweg ist ausgeschlossen.
  • Stichtag ist der 30.09.2009 (Teilnahmeschluss). Autoren der Beiträge, die später (also genau um oder nach dem 01.10.2009 00:00 Uhr) in der Liste der Kommentare bekannt gegeben wurden, nehmen nicht an der Verlosung teil.

Am Ende der Blog-Parade wird die Liste aller Bücher mit (exemplarischen) Links zu den jeweiligen Beiträgen veröffentlicht. Bin gespannt, welche Buch-Perlen diese Blog-Parade zum Vorschein bringt...

Was ist das für ein Buch auf dem 3. Platz

Dino Esposito: Microsoft ASP.NET AJAX-Programmierung - Eine Einführung in die Programmierung interaktiver Webanwendungen

Verlost wird neben den Gutscheinen das für Grundlagen durchaus immer noch sehr gut geeignete Buch "Microsoft ASP.NET AJAX Programmierung - Eine Einführung in die Programmierung schneller und interaktiver Webanwendungen" von Dino Esposito aus dem Microsoft Press Verlag. Ich hab heute beim entstauben entdeckt, dass ich dieses Buch gleich 2x besitze.

Es weißt leichte Gebrauchsspuren auf (wobei dieses Exemplar nicht gelesen wurde) und hat mir sehr gute Dienste geleistet.

 

Tags:

Blog-Parade

XQuery-Singleton oder warum ein einzelnes Element nicht unbedingt als solches erkannt wird

by wolfgang@gehirnwindung.de (Wolfgang) August 23, 2009 16:52

Bei der Arbeit mit dem XML-Datentyp ab SQL Server 2005 kann es zu einer Fehlermeldung kommen, die - zumindest auf den ersten Blick - ein wenig unverständlich erscheint.

Msg 2389, Level 16, State 1, Line #
XQuery [value()]: 'value()' erfordert ein Singleton (oder eine leere Sequenz). Ein Operand vom 'xdt:untypedAtomic *'-Typ wurde gefunden.

bzw.

Msg 2389, Level 16, Status 1, Line #
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'

Dabei ist nicht die Aussage der Fehlermeldung verwirrend, sondern vielmehr die Tatsache, dass alles korrekt zu sein scheint. Dazu folgendes Beispiel:

DECLARE @test xml

SET @test = '
<test>
    <name>Wolfgang Kluge</name>
    <links>
        <link>http://gehirnwindung.de/</link>
        <link>http://klugesoftware.de/</link>
        <link>http://vbwelt.de/</link>
    </links>
</test>'


SELECT @test.value('/test/links/link[2]/text()', 'varchar(200)')

Die Meldung weißt ja bereits darauf hin. Die value()-Funktion erfordert einen einzelnen Wert oder eine leere Sequenz - also eine Abfrage, die keinen oder genau einen Wert zurückgibt. Eben das macht die Abfrage aber eigentlich bereits - sie gibt einen einzelnen Wert zurück. Deswegen scheint die Fehlermeldung nicht wirklich viel Sinn machen.

vollständigen Artikel anzeigen...

Tags: , , ,

Microsoft SQL Server

BizTalk Eloquent: You must specify at least one already-initialized correlation set...

by wolfgang@gehirnwindung.de (Wolfgang) August 08, 2009 10:24

Die Lösung ist unglaublich einfach - die Frage danach klingt aber alles andere als das *g*. Folgende Fehlermeldung kann beim kompilieren eines BizTalk-Projekts erscheinen.

Error 3: you must specify at least one already-initialized correlation set for a non-activation receive that is on a non-selfcorrelating port.

Die Macher dieser wortgewaltigen Fehlermeldung haben durchaus recht. Damit eine Nicht-aktivierende Receive-Shape (wie übersetzte man das *g*?) auf einem nicht-Selbst-Korrelierenden Port (Anschluss) funktioniert, muss mindestens ein initialisierter Korrelationssatz vorgegeben werden... Das klingt kompliziert - und das ist es auch.

Die andere Seite hätte man - meiner Meinung nach - aber auch dazuschreiben können. Will man keine Korrelationssätze definieren oder braucht man diese erst gar nicht, reicht es auch aus, von einem Nicht-aktivierenden auf einen aktivierenden Port umzustellen. D.h. dass man die Eigenschaft "Activate" des ersten Receive Ports auf "True" stellen muss. Dies dürfte in vielen Fällen auch das gewünschte Verhalten sein...

 Einstellung Activate einer Receive-Shape in Biztalk-Orchestation

Was das ist? Ein aktivierender Receive Port initialisiert die Orchestration, sobald eine Nachricht diesem Anschluss zugeordnet wird. D.h. die Orchestration wird nach dem Empfang auf diesem Port ausgeführt. Daher darf es nur einen solchen Receive Port geben (Ausnahmen gibt's mittels Listen- und Parallel-Shape auch hier) und dieser aktivierende Receive Port muss ganz am Anfang stehen (sonst würde man die Orchestration ja zu einem Zeitpunkt aktivieren wollen, zu dem sie bereits läuft).

Tags: ,

Microsoft BizTalk Server

Der geography-Datentyp und warum die Länge einer Linie nicht unbedingt auch die Distanz zwischen deren Endpunkten ist

by wolfgang@gehirnwindung.de (Wolfgang) Juli 28, 2009 23:52

Seltsamer Titel? Stimmt. Das war aber heute eine Frage in einem Microsoft-Forum. Warum ist die Länge nicht gleich der Distanz zwischen 2 Punkten?

Nun, das stimmt natürlich nicht - zum Glück. Bei der Fehlersuche ist allerdings eine Besonderheit des SQL Servers aufgefallen. Zuerst aber mal ein Beispiel

DECLARE @line geography, @pointStart geography, @pointEnd geography
SELECT @line = geography::STGeomFromText('LINESTRING(1 2, 3 4)', 4326),
       @pointStart = geography::Point(1, 2, 4326),
       @pointEnd = geography::Point(3, 4, 4326)

SELECT @line.STLength() AS Length,
       @pointEnd.STDistance(@pointStart) AS Distance

Heraus kommen die Werte

Length Distance
313588,386985478 313705,435222058

Doch warum sollte hier ein Unterschied bestehen... STLength() gibt die Länge des LINESTRINGs zurück und STDistance() berechnet die Distanz zwischen 2 Punkten. Wie bereits vorweggenommen ist das Ergebnis so an sich also falsch - trotzdem ist es so, dass der SQL Server korrekt rechnet. Bleibt als einzige Fehlerquelle also nur noch der Code selbst übrig...

Laut MSDN-Referenz zu geography::Point werden 3 Parameter erwartet: Latitude, Longitude und die SRID. Die Funktion geography::STGeomFromText interpretiert die Angaben aber anders herum. Um genauer zu sein in der Reihenfolge Longitude (geographischer Länge) und danach Latitude (geographischer Breite).

Und das war auch schon das ganze Geheimnis ;) Warum geography::Point hier die Werte anders herum erwartet ist mir ein Rätsel und wird es wohl auch bleiben - die Reihenfolge wie sie geography::STGeomFromText interpretiert entspricht den Vorgaben der OGC und erscheinen damit in allen möglichen Bereichen eben in dieser Reihenfolge (und damit u.a. auch in GML).

Wie dem auch sei. Es ist alles noch korrekt - man muss nur drank denken ;)

Tags: ,

Microsoft SQL Server

Warning LNK4075: /EDITANDCONTINUE wird aufgrund der Angabe von /INCREMENTAL:NO ignoriert

by wolfgang@gehirnwindung.de (Wolfgang) Juli 07, 2009 13:00

Wenn der C++ Linker die Warnung LNK4075 ausgibt, so kann man entweder die Einstellung /INCREMENTAL von NO auf YES stellen, oder - so kann man meinen - /EDITANDCONTINUE deaktivieren.

 Die Einstellung Enable Incremental Linking

Während man die erstgenannte Einstellung in den Projekteigenschaften unter "Linker" -> "General" -> "Enable Incremental Linking" findet, ist die Einstellung für "Edit And Continue" allerding nicht auffindbar.

Diese ist über den Visual Studio Dialog nicht direkt einstellbar. Stattdessen gibt es eine Einstellung unter "C/C++" -> "General" -> "Debug Information Format". Ist hier "/ZI" (also Program Database for Edit & Continue) eingestellt, so impliziert das die (interne) Linkeroption /EDITANDCONTINUE.

Die Einstellung Debug Information Format

Tags: ,

Microsoft Visual Studio | C++

Powered by BlogEngine.NET 1.6.1.6
Theme by Mads Kristensen | Modified by Mooglegiant and me ;)