Ein Storyboard zum Anfassen:
Rapid Prototyping in der Softwareentwicklung

MVP. PoC und Prototyp – welche Unterschiede gibt es und worauf ist beim Rapid Prototyping zu achten?

Was haben das Storyboard für einen Zeichentrickfilm und ein Software-Prototyp gemeinsam?

Beides sind Visualisierung einer Idee und häufig auch der „rote Faden“ für die Umsetzung in einem Team.

Ein Grundkonzept des Design Thinking liegt u.a. darin alle Projektbeteiligen durch geeignete Visualisierungen in die Lage zu versetzen, „die gleiche Sprache zu sprechen“ und mögliche Lösungen für ein gegebenes Problem mithilfe verschiedener Techniken zu finden und zu bewerten.
Während dies bei einem Animationsfilm recht einfach dadurch gelingen kann, dass man z.B. einige Bilder des Storyboards zu einem Daumenkino kombiniert, um so z.B. die Bewegung von Personen und Objekten darstellen zu können, ist dies bei Software meist etwas aufwändiger.

Häufig werden im Rahmen der Weiterentwicklung bestehender Softwareprodukte (interaktive) Mockups oder Wireframes verwendet, um z.B. Übergänge von einem View auf einen anderen zu simulieren. Im Grunde ist dies aber auch nichts anderes als eine reine Visualisierungstechnik – und z.B. bei sogenannte Greenfield-Projekten nur bedingt hilfreich. Im Laufe der Zeit haben sich deshalb auch eine ganze Palette verschiedener Versionen des Prototypings entwickelt, die eines gemeinsam haben:

Durch einen überschaubaren Aufwand and (personellen) Ressourcen, Zeit oder Budget soll entweder

  • die Machbarkeit eines Ansatzes überprüft,
  • der zu erwartende Aufwand für die komplette Implementierung eines Projekts abgeschätzt oder
  • schnellstmöglich eine erste Version gebaut werden, mit der man sich Feedback (von Nutzern, Steakholdern, Projektbeteiligten) einholen kann.

Häufig werden die Begriffe Prototyp, Minimum Viable Product (MVP) und Proof of Concept (PoC) synonym verwendet, unterscheiden sich jedoch im Vergleich von einander:

  • Im Rahmen eines Proof of Concept wird in der Regel ein Teilbereich eines kompletten Produkts betrachtet. Dies kann bei einer datenbankgetriebenen Anwendung z.B. die Beispielimplementierung für die Verwendung einer bestimmten Datenbank sein oder für eine Webanwendung der Vergleich zwischen einem LAMP- und einem MEAN-Stack sein.
  • Ein klassicher Prototyp wird meistens dann entwickelt, wenn das Zusammenspiel verschiedener Technologien, Lösungsansätze etc. in einem kontrollierten Umfeld (z.B. einer Testumgebung) – in der Regel ohne echte Testnutzer – demonstriert werden soll.
  • Ein Minimum Viable Product ist einem Prototypen sehr ähnlich, im Regelfall jedoch so weit ausgereift, dass es – in begrenztem Rahmen, ohne dass bereits alle geplanten Funktionen zur Verfügung stehen – auch in einem produktiven Umfeld getestet werden kann.

Wie wird nun aber aus Prototyping Rapid Prototyping?

Die kurze Antwort: Indem man die Entwicklungszeit (für jede Iteration) des Prototypen möglichst kurz hält. Etwas ausführlicher bedeutet dies:

Viel hilft nicht unbedingt viel
Im Regelfall ist die Rechnung dass ein Job in der Hälfte der Zeit erledigt wird, wenn ich doppelt so viel Ressourcen zur Verfügung habe, falsch. Je größer das Team wird, umso mehr Overhead entsteht durch Absprachen und Selbstorganisation. Zudem besteht das Risiko, dass durch ein unterschiedliches Verständnis der Aufgaben-/ Fragestellung sich widersprechende Ansätze verwendet werden.

Wiederverwendbarkeit
In der Regel wird – weder beim Prototyping, noch der Entwicklung einer Softwarelösung – ausschließlich selbstentwickelter Code verwendet. Häufig kommen externe Bibliotheken, Tools und Frameworks zum Einsatz. Dabei sollte bedacht werden, dass

  • Sehr umfangreiche und große Frameworks meist auch eine längere Einarbeitungszeit benötigen. D.h. selbst wenn man schnell einen entsprechend qualifizierten Entwickler findet, ist es meist nicht ohne Weiteres möglich das Framework oder den Entwickler/ Dienstleister in einer späteren Projektphase zu wechseln.
  • Die verwendeten Frameworks und Bibliotheken sollten nicht nur möglichst viele schnell einsetzbare und Standardkomponenten enthalten, sondern die Komponenten sollten auch so gestaltet sein, dass ein Entwickler diese ohne viel Aufwand/ Code entsprechend der aktuellen Bedürfnisse modifizieren kann.
  • Low Code-Systeme erfreuen sich aktuell immer größerer Beliebtheit, da sie versprechen, dass im Grunde jeder Anwender – auch ohne Erfahrung in der Programmierung – seine eigenen Anwendungen entwickeln kann. Unabhängig davon, dass die meisten derartigen Systeme häufig nur Bausteine für von Ihnen selbst unterstützte Funktionen und Drittanwendungen bereitstellen, sollte man sich vor dem Einsatz dieser Systeme aber überlegen ob der Produktiveinsatz der damit einhergehenden Infrastrukturen überhaupt gewünscht oder sinnvoll ist. Ein typisches Beispiel dafür ist Power Automate der Firma Microsoft, das zwar sehr mächtig sein kann, jedoch auch die Nutzung der Azure-Cloudinfrastruktur voraussetzt.

Nichts überstürzen!
Auch wenn die reine Entwicklungszeit eines Prototypen, MVP oder PoC unter Optimalbedingungen recht zügig erfolgen kann, ist es gerade im Agilen Umfeld wichtig, dass damit nicht das Ende der Reise erreicht ist, sondern nicht nur Feedback eingeholt werden sollte, sondern dieses auch entsprechend in die Planung nächster Schritte oder weiterer Prototyping-Zyklen einfließen einfließen muss. Häufig dauert es mindestens genauso lange bis alle am Projekt beteiligten Mitglieder, Testnutzer, Mitglieder von Fokusgruppen die Möglichkeit hatten die entsprechenden Funktionen zu testen, ihre Rückmeldung dazu zu geben und diese auch entsprechend aufzuarbeiten.

WordPress Security: Von Menschen und Maschinen

Obwohl grundsätzlich recht sicher, kann auch Wordpress vielen potentieller Bedrohungen ausgesetzt sein. Welche (Gegen-)Maßnahmen können sinnvoll sein?

WordPress ist das meistverwendete Content Management System (CMS) weltweit. Im Rahmen eines Kundenprojekts, das in der Vergangenheit leider mehrfach Ziel von Hackangriffen war, musste ich mich neulich mit der Frage auseinandersetzen:

Wie kann man eine WordPress-Installation bestmöglich absichern?

Zu allererst, die schlechte Nachricht: Bei jedem Gerät, jeder Website und jedem Dienst, den man direkt oder indirekt mit dem Internet verbindet oder über das Internet bereitstellt, muss man sich im Klaren darüber sein, dass es nie einen 100%-igen Schutz gegen Hacker und Eindringlinge geben wird. Egal ob Open Source-Anwendungen oder proprietäre (Closed-Source-)Lösungen, potentielle Sicherheitslücken oder, schlichtweg, Versäumnisse bei der Entwicklung von Softwarelösungen wird es immer geben. Auch wenn man tendenziell meinen sollte, dass potentielle Sicherheitslücken innerhalb einer aktiven Open Source Community schneller identifiziert und ggf. geschlossen werden, gibt es sicher auch immer Individuen, die es gerade wegen der Quelloffenheit auf derartige Systeme abgesehen haben.

Nun die gute Nachricht: Achtet man darauf möglichst mit der aktuellsten Version des WordPress-Cores zu arbeiten, nur vertrauenswürdige Plugins und sichere Themes zu installieren – und auch diese auf dem aktuellsten Softwarestand zu halten -, ist WordPress auch eines der sichersten CM-Systeme.

Was sind nun aber die häufigsten oder wahrscheinlichsten Probleme, mit denen man sich im Alltagsbetrieb konfrontiert sehen könnte?

Identitätsdiebstahl/ Ausspähen von Nutzerdaten

Ähnlich wie am heimischen PC, ist auch der Admin-Bereich von WordPress standardmäßig mit einer Kombination aus Nutzername und Passwort gesichert. Zwar wird sich vermutlich kein Hacker die Mühe einer Phishing-Kampagne zum Ausspähen der Zugangsdaten zu einer WordPress-Installation machen, jedoch kommen sogenannte Brut-Force-Angriffe verhältnismäßig häufig bei. Bei dieser Art des Hackings werden verschiedene Nutzer-Passwort-Kombinationen so lange ausprobiert bis eine gültige Kombination gefunden wurde, so dass sich der Angreifer anschließend in die Website einloggen und Änderungen durchführen kann.

Eine häufig gefundene Empfehlung zur Vorbeugung dieses Problems lautet die Login-Seite zu verstecken, indem man die Standard-Anmelde-URL verändert. Zwar kann dies u.U. sinnvoll sein, jedoch sollte man sich nicht der falschen Sicherheit hingeben, dass dadurch das zugrundeliegende Problem gelöst würde. Vielmehr sollte man:

  • möglichst schwer zu erratende Nutzernamen und möglichst sichere Passworte verwenden
  • eine zusätzliche Sicherheitsüberprüfung mittels Multi-Faktor-Authentifizierung einrichten
  • die Zahl der möglichen Login-Versuche, mithilfe entsprechende Plugins, begrenzen. Dies kann entweder dadurch erreicht werden, dass der zugehörige Nutzeraccount gesperrt wird oder es wird die IP-Adresse eines potentiellen Angreifers als Risiko eingestuft und zu einer sogenannten Blacklist hinzugefügt.

Ein ausführlicher Artikel zum Thema „Sicherheit von Kennwörtern“ findet sich auch in der aktuellen Ausgabe der Zeitschrift Spektrum der Wissenschaft.

Distributed Denial of Service

Als Distributed Denial of Service (DDoS)-Attacke wird ein Angriff bezeichnet, bei dem so viele Anfragen an eine Website gesendet werden, dass diese überlastet wird und der zugehörige Service nicht mehr zur Verfügung steht. Ein Angriff entsprechend der zuvor beschriebenen Brute-Force-Attacke kann u.a. auch zu einem Denial of Service führen, z.B. wenn zu viele Versuche parallele Versuche unternommen werden die Zugangsdaten zu ermitteln.

Bei Verwendung eines Shared Hosters zur Bereistellung einer Website, hat dieser meist ein eigenes Interesse daran ihre Systeme vor einem DDoS-Angriff zu schützen. Auf Installationsebene lässt sich dies aber z.B. durch eine Web Application Firewall unterstützen, bei der die Anzahl der erlaubten Anfragen pro Zeitfenster für eine IP-Adresse begrenzt werden. Dadurch soll verhindert werden, dass einzelne Computersysteme eine Website derart belasten.

Man-in-the-Middle

Bei einem Man-in-the-Middle-Angriff versucht ein potentieller Angreifer die zwischen zwei Computersystemen ausgetauschten Datenpakete abzufangen und deren Inhalt auszulesen.

Gerade beim Versand vertraulicher oder personenbezogener Daten ist es wichtig zu verhindern, dass ein potentieller Eindringling in der Lage ist etwas Sinnvolles mit den Datenpaketen anzufangen. Dies kann daruch erreicht werden, dass die Website mittels SSL/ TLS abgesichert wird. Dabei werden die Datenpakete vor dem Austausch mithilfe eines Zertifikats bzw. kryptographischen Schlüsseln so verschlüsselt, das es nur dem Empfänger möglich ist die Datenpakete wieder lesbar zu machen.

Das Projekt Let’s Encrypt hat sich z.B. der Bereitstellung kostenloser Zertifikate auch für private Webprojekte verschrieben.

Schadcode und -Software

Wie bei nahezu jeder anderen Software bzw. Infrastruktur auch, kann auch eine WordPress-Installation mit Schadsoftware infiziert werden:

SQL-Injection

Als einer der Klassiker für fast jede (Web-)Software, die Textfelder z.B. für die Nutzerregistrierung bereitstellt und Inhalte in einer Datenbank abspeichert, führt eine SQL-Injection dazu, dass z.B. über ein öffentliches Eingabefeld schädlicher Code in die Datenbank eingeschleußt wird. Im einfachsten Fall handelt es sich um JAVA Script-Code, der bei jedem Aufruf der Seite mit ausgeführt wird und z.B. das Ranking einer anderen Website für eine Suchmaschine verbessert; Im schlechtesten Fall, sorgt der der Code z.B. dafür, dass Nutzerdaten ausgelesen werden können oder die zugehörige Datenbank unbrauchbar gemacht wird.

Ein Effektiver Schutz gegen diese Art der Attacken besteht nur darin, dass vor dem Absenden einer Eingabe, diese auch auf Gültigkeit geprüft und ggf. zurückgewiesen wird.

Malicious Redirects und Cross-Site-Scripting

Wurde z.B. der FTP-Account, der üblicherweise zum Hochladen und Aktualisieren von Dateien verwendet wird, korrumpiert oder Code-Dateien – wie z.B. hochgeladene PHP-Skripte – ausgeführt, die eigentlich hätten blockiert werden sollen, kann ein Angreifer z.B. die .htaccess-Datei, die im Hauptverzeichnis jeder WordPress-Installation zu finden ist, so manipulieren, dass ein Aufruf ihrer Seite zu einer Weiterleitung auf eine andere Seite führt.

Ein Artikel wie derartige Angriffe bestmöglich verhindert werden können ist im zugehörigen Artikel auf wordfence.com zu finden.

Plugins

Augen auf bei der Plugin-Installation! Auch wenn ich davon überzeugt bin, dass die Mehrheit der Plugin-Entwickler diese mit der größtmöglichen Sorgfalt und nach bestem Wissen und Gewissen entwickelt, führen gerade Plugins von Drittanbietern dazu, dass man sich (potentielle) Sicherheitslücken in sein System aktiv hineininstalliert.

Um dies zu verhindern, empfiehlt es sich vor jeder Installation eine kurze Recherche zu dem jeweiligen Addon durchzuführen und dieses nur dann zu installieren, wenn man zumindest eine Idee von der Funktionsweise und den potentiellen Risiken hat.

Ähnlich wie die Liste möglicher Schadanwendungen für Desktop- und Mobile-Devices täglich zunimmt, könnte ich auch die Liste der möglichen Schadsoftware für Ihr WordPress-System z.B. noch um Cookie Stealthing, Enumeration-Angriffe und viele Weitere Angriffstypen erweitern. Wer wirklich mal eine unruhige Nacht haben möchte, dem sei die Seite hackertarget.com ans Herz gelegt.

Eine generelle Sicherheitsmaßnahme zum Schutz vor unbeabsichtigten oder unberechtigten Änderungen sollte immer auch einen Backup-und-Wiederherstellungsplan beinhalten. D.h. unabhängig von allen anti-Viren-Anwendungen, die es auch WordPress gibt, schützt u.U. nichts so gut wie eine regelmäßige Sicherung mit ggf. entsprechend langen Aufbewahrungsfristen der Sicherungsdateien. Natürlich wird es weh tun ggf. Inhalte und Weiterentwicklungen eines längeren Zeitraums zu verlieren, aber nichts wird so wehtun als die komplette Website von vorn anlegen zu müssen.

Der Mensch

Zuletzt noch ein paar philosophische Gedanken: In der Summe betrachtet, kann man sich als Administrator (einer WordPress-Site) noch so viel Gedanken um die technische Absicherung machen, wenn der oder die Anwender – z.B. aufgrund fehlender Awareness oder Verständnis – Unvorsichtigkeit walten lassen.

Das soll nicht heißen, dass meine Empfehlung lautet, dass niemand anderer als den Admin selbst mit dem System arbeiten soll, denn auch dieser hat bessere und schlechtere Tage. Es soll bedeuten, dass jedes IT-System nur in dem Maße als sicher angesehen werden kann, in dem der Mensch vor dem Computer Vorsicht und Achtsamkeit walten lässt. Es wird immer Personen mit zweifelhaften Absichten geben und im Zweifelsfall rennt man, beim Versuch das Riko potentieller Bedrohungen auf die eigene Website zu eleminieren, Angreifern immer hinterher.

Nichts zu tun und zu hoffen, dass es das eigene System nicht treffen wird, ist tendenziell aber immer falsch – und bereits mit teilweise recht kleinen Maßnahmen kann man beim Thema „Sicherheit“ große Erfolge erzielen.

Gesucht, gefunden:
Ein wiederverwendbarer Extensions-Manager für .net

Das Managed Extensibility Framework – richtig eingesetzt, eine leichtgewichtige Alternative zu Bibliotheken wie Prism & Co.

Die Zeiten, in denen neue Anwendungen in Form monolither Softwaremonster entwickelt worden sind, sind (hoffentlich) inzwischen überall vorbei. Während gerade bei komplexen und/ oder über viele Dienste verteilten Anwendungen häufig eine Microservice-Architektur zum Einsatz kommt, profitieren häufig aber auch zunächst nur für den lokalen Einsatz konzipierte Anwendungen von einem gewissen Grad der Modularisierung:

  • Neue Funktionen können auch ohne ein komplettes Refactoring hinzugefügt werden; häufig auch solche, die im Rahmen eines ersten Konzepts noch gar nicht vorgesehen waren
  • Der Wartungsaufwand bzw. die Fehlersuche kann deutlich reduziert werden indem die Gesamtanwendung in kleine, gut wartbare Codeabschnitte zerlegt wird
  • Mehrere Programmierer oder Teams können parallel an der Entwicklung der Software arbeiten, z.B. indem ein Team sich auf den Anwendungskern und die Schnittstellen konzentriert, während sich andere Entwickler mit der Implementierung der (Fach-)Funktionen befassen

Kernstück dieses Design-Konzepts ist die lose Kopplung der Plugins, wobei eine Hostanwendung die notwendige Schnittstellen und Basis-Datentypen bereitstellt und sich anschließend um das Auffinden und Instanzieren der verschiedenen Schnitstellenimplementierungen kümmert. Sie sorgt ebenfalls dafür, dass die verschiedenen Erweiterungen (engl. Extensions) bei Bedarf auch gut miteinander kommunizieren können.

Häufig kommen zum Aufbau einer solchen Architektur spezielle Bibliotheken von Drittanbietern zum Einsatz. Eine davon ist Prism. Nicht selten ist es dabei notwendig ganz bestimmte Design-Muster oder Namenskonventionen im Quellcode einzuhalten. Dies gilt zwar auch für das Managed Extensibility Framework (MEF), dafür ist dieses aber ohne zusätzliche Installationen, Lizenzen oder Abhängigeiten nutzbare, da es – als Bestandteil des .net-Frameworks (auch .net Core) – standardmäßig mitinstalliert wird.

Ein komplettes MEF-Tutorial würde wohl den Rahmen dieses Beitrags sprengen. Zudem gibt es bereits gute Artikel zu diesem Thema, so viel aber trotzdem dazu: MEF basiert auf Reflection, was bedeutet, dass, basierend auf einer Liste von Assemblies oder Datei- und Verzeichnispfaden, ein Katalog von Schnittstellenimplementierungen erstellt wird, der als Auflistung von Lazy-Objekten zur Verfügung steht. Mithilfe von Metadaten können die Erweiterungen beschrieben werden, so dass z.B. ein bestimmtes Plugin gefunden und ausgeführt werden kann. Stefan Henneken hat in dieses Konzept in einem entsprechenden Artikel ausführlich beschrieben.

Auch wenn MEF-Features mit wenigen Zeilen Code genutzt werden können, empfiehltsich die Erstellung einer wiederverwendbaren Bibliothek, um ggf. zusätzliche Funktionen nutzen oder bereitstellen zu können. In der von mir öffentlich bereitgestellten BYTES.NET-Bibliothek findet sich eine derartige Manager-Klasse.

ExtensionsManager stellt eine Update-Funktion zur Verfügung, die, basierend auf einem Array von Dateisystempfaden, die Plugins enumeriert und als Extensions-Eigenschaft bereitstellt. Um die Klasse möglichst flexibel zu halten, wurde sie mit generischen Typen dekoriert, so dass sie z.B. wie folgt instanziiert werden kann:

Dim _manager as ExtensionsManager(of MyInterface, MyMetadata) = new ExtensionsManager(of MyInterface, MyMetadata)

Damit dies möglich ist, musste die private Variable _allExtensions, die das Ergebnis der MEF-Katalogerstellung beinhaltet, mit ImportMany ohne Angabe eines einschränkenden Datentyps dekoriert werden. Leider unterstützt MEF aktuell keine generischen Datentypen.

<ImportMany()>
Private _allExtensions As ObservableCollection(Of Lazy(Of TInterface, TMetadata)) = New ObservableCollection(Of Lazy(Of TInterface, TMetadata))

Das bedeutet aber auch, dass MEF zunächst alle gefundenen Plugins, unabhängig vom jeweiligen Datentyp/ Interface, zurückgibt. Solange die Anwendung lediglich über eine einzelne Schnittstellendefinition verfügt, ist dies unproblematisch. Schwieriger wird es bei mehreren Interfaces.

Um dennoch auch mit multi-Interface-Anwendungen kompatibel zu sein, wurde Update mit dem Aufruf der ValidateExtensions-Methode erweitert:

Private Sub ValidateExtensions()
    _validExtensions = New ObservableCollection(Of Lazy(Of TInterface, TMetadata))

        For Each extension In _allExtensions
            Try
                If ImplementsInterface(extension) Then
                    _validExtensions.Add(extension)
                End If
             Catch ex As Exception
             End Try
         Next

End Sub

Diese Methode durchläuft alle gefunden Extensions und prüft ob das als (generischer) Datentyp definierte Interface implementiert wird. Ist dies der Fall, wird das Plugin zur Liste valider Plugins (für diesen Manager) hinzugefügt.

Neben dem (Standard-)ExtensionsManager, enthält die BYTES.NET auch noch eine ExtendedExtensionsManager-Klasse, die es ermöglicht Plugins über Pfade zu finden, die mithilfe von Wildcards definiert wurden bzw. Erweiterung mithilfe einer ID in die Ausgabeliste hinzuzunehmen oder davon auszuschließen. Ein entsprechendes Beispiel ist in der Beispiel-Anwendung im zugehörigen Github-Repository zu finden.

Das Kinderzimmer im Code:
Polymorphismen in der Programmierung

Was sind Überschreibungen, Überladungen und Polyfills – und wozu benötigt man diese überhaupt?

Wer (mindestens) ein kleines Kind zu Hause hat, kennt sicher das folgende Phänomen: An einem Tag steht die Spielküche noch in der Ecke hinten links, aber schon am Folgetag hat der Nachwuchs das gesamte Zimmer umgeräumt, so dass nicht nur die Spielküche in eine andere Ecke bugsiert wurde, sondern nahezu jedes nur irgendwie bewegliche Möbelstück seine Position verändert hat.

Auch wenn mir wohl jeder zusatimmen wird, dass sich das Erscheinungsbild des Zimmers eindeutig verändert hat, ist es immer noch als Kinderzimmer zu erkennen. Damit unterliegt Das Zimmer einem Effekt, den man in der Programmierung Polymorphismus nennt.

Zugegeben, dieser Vergleich hinkt ein Wenig (wie wohl die meisten Vergleiche), denn eigentlich müsste ergänzt werden „während Sie das Zimmer am ersten Tag noch durch die Tür betreten haben, mussten Sie am Folgetag durch das Fenster hereinklettern“ oder „obwohl mithilfe einer Trockenbauwand das Zimmer um einige Quadratmeter verkleinert wurde, hat die Spielküche noch hineingepasst“, doch das Grundkonzept sollte klar sein: Polymorphismen treten in der objektorientierten Programmierung immer dort auf wo zur Laufzeit ähnliche Funktionen unter leicht varrierenden Bedingungen benötigt werden oder Methoden an Laufzeitbedingungen angepasst werden müssen. Dazu gehören z.B.:

  • Unterschiedliche Interpreter oder Laufzeitumgebungen, in denen der gleiche Code (z.B. ein Skript) ausgeführt werden soll – z.B. unterschiedliche Webbrowser, in denen ein-und-dieselbe HTML-Website gerendert werden soll
  • Aufruf von Methoden mit Übergabe unterschiedlicher Argumente, deren vorherige Umwandlung zu aufwändig oder schlicht nicht möglich wären
  • Methoden, die zur Laufzeit unterschiedliche Werte(-Typen) zurückgeben müssen, die evtl. auch nicht ohne Mehraufwände ineinander umgewandelt werden können
  • Vererbung von Klassen, bei denen z.B. eine Tochterklasse eine in der Elternklasse definierte Operation mit geänderten Parametern aufrufen muss

Polymorphismen können mithilfe einer Vielzahl verschiedener Techniken erreicht werden. Gerade bei der Softwareentwicklung mittels .net ist wohl die Überladung eine der am häufigsten eingesetzten Technik.

//version 1 
public int GetSum(int val1, int val2)
{ 
    return val1 + val2; 
} 

//version 2 
public int GetSum(int[] values)
{ 
    int result = 0; 
    
    foreach(int val in values)
    { 
        result += val; 
    } 
    
    return result; 
} 

//version 3 
public string GetSum(string val1, string val2)
{ 
    int result = int.Parse(val1) + int.Parse(val2); 
    return result.ToString(); 
}

Wie in diesem Beispiel, besitzen sich überladende Methoden zwar den gleichen Namen, unterscheiden sich aber in den Argumenten und/ oder Rückgabetypen.

Das Überschreiben ist eine Methode, die gern im Zusammenhang mit Klassenvererbung eingesetzt wird. Dabei haben die Methoden in der Eltern- und auch der Kinderklasse den gleichen Namen, jedoch wird die Methode in der abgeleiteten Klasse aufgerufen – anstatt die in der Elternklasse. Voraussetzung dafür ist es (in C#), die Methode in der Elternklasse als virtual zu definieren und die Methode in der Kinderklasse mit override zu dekorieren.

//die Eltern-Klasse 
public class ParentClass
{ 
    public virtual void DoSomething()
    { 
        MessageBox.Show(DateTime.Now.ToString()); 
    } 
} 

//die Kind-Klasse 
public class ChildClass : ParentClass
{ 
    public override void DoSomething()
    { 
        MessageBox.Show("Hello World!"); 
    }
}

Eine besondere Form des Polymorphismus hat sich in JavaScript herausgebildet: Häufig müssen Eigenheiten der verschiedenen Browser bei der Erstellung von Methoden berücksichtigt werden. Häufig unterstützen einzelne Browser Variablen oder Methoden, die ein anderer gar nicht kennt und umgekehrt. Ein Beispiel dafür, ist document.documentMode in Microsofts Internet Explorer:

if (document.documentMode == 7) { 
     doSomethingIn.IE7_only; 
} else { 
     doSomething.everwhereElse; 
}

Eine Prüfung auf eine dieser Eigenschaften kann in der Praxis dafür verwendet werden um einen bestimmten Browser(-Typ) zur Laufzeit zu identifizieren und spezifische Operationen auszuführen.

Mithilfe sogenannter Polyfills ist es möglich in JavaScript Methoden für einen Browser zur Verfügung zu stellen, die dieser nativ gar nicht zur Verfügung stellt – ggf. auch um Code kompatibel zu älteren Browser-Versionen zu machen. So wird die string.startsWith()-Methode erst ab Version 12 nativ durch den Internet Explorer unterstützt, mithilfe eines Polyfills ist es aber möglich ältere Systeme damit „nachzurüsten“. Damit agieren Polyfills ähnlich den Extension-Methoden in z.B. VB.net oder C#.

//add the 'string.startsWith()' function (polyfill) (e.g. for the Internet Explorer) 
if (!String.prototype.startsWith) { 
    String.prototype.startsWith = function(searchString, position) { 
        position = position || 0; 
        return this.indexOf(searchString, position) === position; 
    }; 
}

Neben der Vererbung von Klassen selbst, gehören Polymorphismen zu den im Rahmen von Clean Code-Initiativen am stärksten diskutierten und kritisierten Themen. Als Hauptargumente gegen derartige Techniken wird häufig eine erhöhe Komplexität bzw. Unübersichtlichkeit des Quellcodes aufgeführt. Ich gebe diesen Kritikern recht, dass zumindest Überladungen und Überschreibungen zu Verwirrung und Code-Wiederholungen führen können, jedoch bin ich mir auch nicht ganz sicher wie wartbar ein Quellcode wird, in dem ich z.B. ständig vermeidbare Typen-Konversionen durchführen muss oder während der Laufzeit nicht in der Lage bin flexibel auf Abweichungen reagieren zu können.

Sofern notwendig, würde ich lieber für einige Tage durch das Fenster in das Kinderzimmer klettern, als die Wand zu durchbrechen um eine zweite Tür einzubauen, die u.U. die Statik des Gesamtgebäudes gefährdet.

Schlangenbeschwörer:
Externe Skripte in .net-Anwendungen

Wie lassen sich externe Skripte in eine .net-Anwendung integrieren?

Nicht immer ist es möglich oder sinnvoll Anwendungen „aus einem Guss“ bzw. in einer Programmiersprache zu entwickeln oder zu betreiben. Unabhängig vom Konzept der Modularisierung, sollte, meiner Meinung nach, auch immer der Ansatz der „Best Available Technology (BAT)“ (im Deutschen wohl am besten zu übersetzen mit „Stand der Technik“) berücksichtigt werden. Viel zu häufig wird – gerade bei Konzeptionierung neuer Anwendungen – die Wahl der verwendeten Sprachen, Frameworks und Techniken darauf basierend getroffen, welche Kenntnisse der jeweilige Dienstleister anbieten kann und nicht welche Auswahl für die Lösung einer bestimmten Fragestellung am sinnvollsten wäre. Auch kann die Kombination relativ junger Technologien mit bereits etablierten Frameworks eine Herausforderung darstellen. Dazu ein konkretes Beispiel:

Eine bereits existierende, auf .net basierende Anwendung soll um Features aus dem Bereich Deep Learning erweitert werden. Neben den .net-Entwicklern, arbeiten an diesem Projekt natürlich auch Data Scientists mit, die u.a. für die Entwicklung der künstlichen neuronalen Netzwerke zuständig sind – dies aber natürlich in Python tun. Die Aufgabe besteht nun darin diese beiden Technologien zu kombinieren. Was also tun?

Option 1: Warum (überhaupt) Python?

Mit ML.Net steht ein Framework aus dem Hause Microsoft zur Verfügung, mit dessen Hilfe sich verhältnismäßig einfach machine learning-Lösungen in C# erstellen lassen. Die Daten-Wissenschaftler würden also (versuchen) ihre Konzepte in einer Sprache zu realisieren, die eine Integration unnötig macht und ggf. im Rahmen eines kompilierten Assemblies recht einfach in die bestehende Anwendung integriert werden kann.

Unabhängig davon ob sich die betroffene Fachgruppe darüber freuen würde bei der Wahl der zu verwendenden Tools eingeschränkt zu werden, gibt es einige Gründe warum sich Python und damit verbundene Frameworks im Bereich machine learning durchgesetzt haben. Mit dieser Lösung würde man sich ggf. einen Sonderfall ins Haus holen, bei dem man nur mit deutlich erhöhtem Aufwand auf dem aktuellen Stand der Technik bleiben und somit gegen den BAT-Grundsatz verstoßen.

Option 2: Python in .net interpretieren

Es gibt eine Reihe recht guter Projekte, wie z.B. Python.net oder IronPython, die Bibliotheken bereitstellen um Python-Skripe in .net-Anwendungen auszuführen und zu interpretieren. Mithilfe verschiedener Injection-Szenarien ist es sogar möglich Variablen und Funktionen bereitzustellen, die üblicherweise nicht Bestandteil der (Core-)Python-Umgebung sind.

Leider kommen diese Interpreter gerade bei wissenschaftlichen Bibliotheken an ihre Grenzen. Um z.B. aufwändige mathematische Operationen oder (wissenschaftliche) Berechnungen durchführen zu können, stellen Frameworks wie Anaconda zusätzliche Funktionen und Bibliotheken bereit, die bei den genannten Interpretern nicht zur Verfügung stehen. Somit stellt dies nur eine engschränkt nutzbare Lösung dar.

Option 3: Die Python-Skripte als externe Prozesse ausführen

In einem Artikel in der dotnetpro vom März 2019 wird – neben einem Beispiel für die Verwendung von IronPython – genau dieses Szenario beschrieben, mit dem Lösungsansatz das Python-Skript mithilfe der .net-eigenen Process-Klasse aufzurufen, die Ausgabe zu überwachen und in der Core-Anwendung abzuwarten bis das Skript vollständig durchlaufen wurde bis die entstandenen Daten weiterverarbeitet werden.

Natürlich funktioniert dies nicht nur für Python-Skripte, sondern für alle Arten externer Skripte und Anwendungen – und das ganz ohne z.B. tiefgreifende Anpassungen an der Original-Anwendung durchführen zu müssen. Möchte man jedoch nicht jedes mal aufs Neue

  • die Process-Parameter, inkl. ggf. notwendiger für das externe Skript oder die Anwendung notwendige Kommandozeilenparameter, konfigurieren
  • den Ablauf überwachen und auf auftretende Output- oder Fehler-Events reagieren

empfiehlt sich die Kapselung in eine dafür optimierte Klasse.

Mit der ManagedProcess-Klasse stellt das aktuelle Release (1.4.1) des BYTES.NET-Frameworks genau eine derartige Kapselung bereit.

Private Async Sub RunScript()

    'create the process class instance
    Dim process As ManagedProcess = New ManagedProcess

    With process
        .Executable = "C:\Windows\System32\cmd.exe"
        .Arguments = {"/c " & """" & "%InstallationDir%..\..\..\..\samples\Scripts\LongRunningSampleScript.cmd" & """" & " TestValue"}
        .ShowUI = Me.ShowUI
    End With

    'handle the events
    AddHandler _process.OutputReceived, Sub(data As LogEntry)
                                            _log.Add(data)
                                        End Sub

    AddHandler _process.Exited, Sub(code As Integer)
                                    MsgBox("Process finished with: " & code)
                                End Sub

    Await _process.RunAsync()

End Function

Mithilfe weniger Zeilen Code ist es z.B. möglich ein Skript (mithilfe eines beliebigen externen Interpreters) auszuführen und sowohl auf Rückgaben zu reagieren als auch auf den Abschluss des Runs zu warten. Zudem bietet die Klasse die Vorteile, dass

  • mit RunAsync bereits eine Methode bereitgestellt wird, die die Nutzeroberfläche während (eines beliebig langen) Durchlaufs nicht blockiert
  • ein einheitliches Event (OutputReceived) zur Verfügung steht, dass die Verarbeitung der Rückgaben ermöglicht; Fehler können mithilfe des InformationLevel des zurückgegebenen LogEntry-Objekts identifiziert werden
  • relative Pfade – z.B. mithilfe der generischen %InstallationDir%-Variable – für den Interpreter-Pfad unterstützt werden, so dass auch Interpreter wie z.B. Anaconda in einer portable-Version (z.B. als Bestandteil eines Installationspakets) genutzt werden können

Ein detailliertes Beispiel für den Einsatz mit Windows-Batch (*.cmd) und Python (*.py) ist im zugehörigen Repository auf Github verfügbar.

Der Geist in der Grafikkarte:
NVIDIAs GPC2020

Im Rahmen der GPC 2020 kommen Spezialisten aus allen Fachbereichen zusammen – in diesem Jahr online

Aufgrund der durch das COVID-19 hervorgerufenen, geänderten Rahmenbedingungen, findet NVIDIAs diesjährige GPU Technology Conference als digitales Online-Event statt, anstatt wie ursprünlich geplant in San Jose. Da die Anmeldung kostenfrei ist, kommt man auf diese Weise in den Genuss teils hochspezialisierter Fachvorträge, ganz ohne Ausgaben für Reise oder Hotel.

Neben einer Vielzahl von Kursen, Workshops und Postern zu den verschiedensten Anwendungsfällen und Teilaspekten von High Performance Computing (HPC) und Artifical Intelligence (AI), werden auf der GTC Tools und Lösungen präsentiert, die die Entwicklung und Integration von Deep Learning (DL)-Ansätzen weiter vereinfachen und somit noch massentauglicher machen sollen. Um dieses Ziel zu erreichen, findet man im Session-Katalog auch immer wieder Vorträge, wie der von Will Ramey, in dem Grundlagen zum Thema künstliche Intelligenz erklärt werden

Doch weshalb betreibt eine Firma, die die meisten Endanwender lediglich als Hersteller von Grafikkarten kennen, einen derartigen Aufwand?

Die Antwort ist recht einfach: Um Grafikkarten zu verkaufen.

Zugegeben, diese Darstellung ist sehr stark vereinfacht, jedoch verdiente NVIDIA, nach Zahlen vom März 2020, etwa 9,5 Milliarden US-Dollar mit dem Verkauf von Graphics Processing Units (GPUs) und lag damit im Q4 2019 deutlich vor dem Mitbewerber AMD.

Warum aber profitiert gerade ein Hersteller von Grafikkarten vom Trendthema AI – und nicht Firmen wie Intel oder AMD, deren Prozessor-Chips (CPU) in praktisch jedem Heimcomputer und Server verbaut sind?

Technisch gesehen ist auch diese Antwort recht einfach: Grundsätzlich basieren die meisten DL-Modelle auf der gewichteten, stufenweisen Verarbeitung von Parametern. Tim Dettmers hat das Grundkonzept hinter Deep Learning in einem Artikel im NVIDIA Developer Blog zusammengefasst.

Mathematisch wird dies über sogenannte Matrix-Multiplikationen abgebildet – und genau da kommt der Vorteil der GPU gegenüber der CPU zum Tragen: Grafikkarten wurden ursprünglich genau dazu entwickelt diese mathematischen Operationen möglichst performant durchzuführen. Im direkten Vergleich kann bereits eine GPU deutlich mehr Bilder pro Sekunde verarbeiten als ein Verbund aus 5 CPUs – und die Leistungsfähigkeit der GPUs hat sich in den letzten Jahren immer weiter erhöht.

Auch wenn die prominentesten Beispiele für Deep Learning oder AI aus den Bereichen der Bildverarbeitung oder autonomen Fahren kommen, sorgt die Zunahme sogenannter Edge Devices als Bestandteil des IoT dafür, dass immer größere Datenmengen zu verarbeiten, (vor) zu klassifizieren und für (de-)zentrale Erfassungs- und Verarbeitungssysteme aufzubereiten sind. Um dies zu realisieren, genügen traditionelle, regelbasierte Systeme of nicht mehr. Ein Ansatz zur Lösung dieser Herausforderungen is Deep Learning oder AI – dazu bedarf es jedoch neben der passenden Hard- und Software auch Wissenschaftler und Spezialisten die in der Lage sind mit diesen Systemen zu arbeiten.

Richtig gebunden:
Emby im Active Directory

Der Medienserver Emby lässt sich ohne großen Aufwand an ein LDAP anbinden. Doch was ist bei der Verwendung von Active Directory zu beachten?

Ich bin ein ziemlicher Fan von Emby. Einmal eingerichtet, stellt der Emby Server eine recht unkomplizierte und elegante Möglichkeit zur Verfügung seine privaten Medien – wie z.B. Musikdateien – in Bibliotheken zu organisieren und auf nahezubeliebigen Geräten wiederzugeben. Mithilfe verschiedener Plugins lassen sich zudem externe Quellen, wie z.B. IP TV, einbinden und der Funktionsumfang erweitern.

Eines dieser Plugins erlaubt es auch den Medienserver an das lokale Active Directory und damit auch das zentrale Nutzermanagement anzubinden. Folgt man der zugehörigen Anleitung, ist die Installation selbst schnell erledigt – leider ist die Konfiguration nicht ganz so intuitiv wie ich es erwartet hätte. Obwohl nur aus einer Seite mit ein paar Textfeldern bestehend, hat es mich einigen Aufwand gekostet die richtigen Konfigurationsparameter zu setzen:

Während LDAP server url und LDAP server Port number im Grunde selbsterklärend sind, muss ich zugeben, dass ich bereits für Bind DN auf einen Thread aus dem Community-Forum zurückgreifen musste. Es ist zwar nicht sonderlich überraschend, Authentifizierungsdaten für eine LDAP-Abfrage bereitstellen zu müssen und ich behaupte einfach mal, dass ich kein absoluter Newbe bei der Integration mit LDAP bin, aber den Distinguished Name komplett angeben zu müssen, kommt bei den meisten Anwendungen nicht so häfig vor. Nchdem man diesen dann jedoch ausgefüllt und noch das zugehörige Passwort in Bind credentials hinterlegt hat, kommt der Konfigurationsparameter, der mich einige Stunden an Suche und Nerven gekostet hat: User search filter.

Verwendet man ein Windows Active Directory als Backend und belässt gleichzeitig den standardmäßigen Filter (uid={0}), kann man noch so verzweifelt versuchen sich mit einem AD-Nuterkonto anzumelden, es wird immer die Fehlermeldung auftauchen „Nutzerdaten falsch; Anmeldung verweigert“. Im Emby Server Log sieht das Problem in etwa so oder so ähnlich aus:

Die Ursache: Relativ häufig ist das uid-Attribut standardmäßig nicht mit dem Nutzernamen belegt, so dass der Versuch den Nutzer bei der Anmeldung zu intentifizieren ins Leere läuft. Die Lösung: Verwendung des sAMAccountName-Attributes mit dem Filter (sAMAccountName={0}). Da dieses Attribut in einem AD als einmaliger Schlüssel verwendet wird, ist er immer mit dem Nutzernamen belegt. Eine vollständige Konfiguration würde dann wie folgt aussehen:

Nachgefragt. Nicht-blockende Dialoge in WPF

Ein nicht-blockender Dialog, in .net, basierend auf dem MVVM-Designmuster. Geht das? Ja, klar!

Genau genommen, ist dies ein Post aus dem letzten Jahr – jedoch aktualisiert im Rahmen des Release meines BYTES.NET-Projekts. Der Quellcode der Beispielanwendung ist im zugehörigen Github-Repository zu finden und dient als Basis für diesen Artikel:

Jeder Entwickler kommt irgendwann an den Punkt, an dem er den Anwender nach weiteren Angaben oder Eingaben fragen muss. Sofern es sich nicht um eine „Single Page“- oder „inline“- Anwendung handelt, gehört es zum üblichen Vorgehen, einen Dialog zu öffnen, auf die Eingabe (und deren Bestätigung) zu warten und die Informationen anschließend (weiter) zu verarbeiten.

In einer Windows Forms-Anwendung gibt es den recht komfortablen OpenFileDialog um z.B. eine Datei zu öffnen. In Windows Presentation Foundation (WPF) gibt es diesen und ähnliche Dialoge standardmäßig nicht. Der übliche Tipp für WPF und den soeben genannten Anwendungsfall lautet daher häufig „bitte auf das System.Windows.Forms-Assembly referenzieren und dann den OpenFileDialog verwenden“.

Wie geht man aber damit um, wenn man eine Eingabe benötigt, für die es keinen vorgefertigten Dialog gibt oder wenn die Standarddialoge nicht ausreichen?

Entsprechend des MVVM-Designmusters, besteht ein Dialog aus

  • Einem Window, das als View verwendet wird und in dem die notwendigen Controls per XAML definiert werden.
  • Die Eigenschaften der Controls werden an die entsprechenden Eigenschaften eines View Models gebunden.
  • Anschließend wird der Dialog über Window.Show() anzeigen.

So weit so gut, jedoch liegt auch hier – wie allzu häufig – die Herausforderung im Detail. Was ist zum Beispiel…

  • … wenn der Dialog bzw. das Warten auf die Eingabe die übrigen Anwendungsfunktionen nicht blockieren darf (weil beispielsweise asynchrone Prozesse im Hintergrund ausgeführt werden)?
  • … wenn man häufig verschiedene Dialoge benötigt und die Codebase möglichst schlank halten möchte (z.B. auch vor dem Hintergrund des Bugfixings und der Wartbarkeit)?

Gleichzeitig ist es (aus meiner Sicht) deutlich eleganter – ähnlich wie im OpenFileDialog eine ShowDialog()-Methode über das View Model aufzurufen, anstatt eine neue Instanz der Window-Klasse zu erstellen und diese dann aufzurufen.

Die Lösung dafür bietet eine eigene DialogViewModel-Basisklasse, die von BYTES.NET.WPF.MVVM.ViewModel abgeleitet wird und somit auch INotifyPropertyChanged implementiert. Diese Klasse stellt eine Methode namens ShowDialog() bereit, in der gleich drei Dinge passieren:

  • Zum Ersten wird das zugehörige View (= Window) über die Methode ShowView() angezeigt.
  • Es wird darauf gewartet, dass der Anwender den View wieder schließt – entweder in Form einer Eingabebestätigung oder eines Abbruchs
  • Nach dem Schließen des View wird ein Boolean zurückgegeben, der in der aufrufenden Klasse dann ausgewertet werden kann:
Public Function ShowDialog() As Boolean

    'show the view
    ShowView()

    'wait for user input
    _myBlock = New EventWaitHandle(False, EventResetMode.ManualReset)
    WaitForEvent(_myBlock, New TimeSpan(24, 0, 0))

    'remove the wait handle
    _myBlock.Dispose()

    'return the dialog closing indicator
    Return Me.DialogResult

End Function

Ein Beispiel für eine daraus abgeleitete View Model-Klasse ist BYTES.NET.Sample.ViewModels.DialogVM auf Github. Durch die beiden überschreibbaren Methoden ShowView() und CloseView() ist es möglich nicht nur Windows als Views zu verwenden, sondern ggf. auch eigene GUI-Elemente wie z.B. Benutzersteuerelemente. Zudem lässt sich bei Bedarf noch während des Schließens der Wert von DialogResult manipulieren.

Kommen wir nun zum Verhindern des Blockens der Anwendung, beim Warten auf die Eingabebestätigung, WaitForEvent(). Nachdem ich einige (mehr oder minder erfolgreiche) Lösungsansätze probiert habe, bin ich in diesem Artikel auf eine sehr elegante Lösung zu dieser Problematik gestoßen: Die Grundidee beruht darauf einen zusätzlichen Thread zu verwenden um asynchron auf ein Ereignis zu warten – wodurch der Hauptthread (und mit ihm auch die Oberfläche) nicht blockiert wird. Zeitlich achgelagerte Operationen werden erst dann ausgeführt, wenn ShowDialog() eine Antwort zurückgegeben hat.

Private Function WaitForEvent(eventHandle As EventWaitHandle, Optional timeout As TimeSpan = Nothing) As Boolean

    Dim didWait As Boolean = False
    Dim frame = New DispatcherFrame()
    Dim start As New ParameterizedThreadStart(Sub()

                                                didWait = eventHandle.WaitOne(timeout)
                                                frame.[Continue] = False

                                              End Sub)

    Dim thread = New Thread(start)
    thread.Start()
    Dispatcher.PushFrame(frame)
    Return didWait

End Function

Schön und gut, was passiert nun aber, wenn der Anwender – statt auf z.B. einen Button zu drücken – das Dialogfenster über das kleine Kreuzchen rechts oben im Header schließt? Das Fenster wird zwar geschlossen, aber der „Warte“-Thread wird in der Regel nicht automatisch beendet und die Anwendung wird nicht sauber geschlossen.

Um auch dieses Problem zu lösen, gibt es einen recht einfachen Trick, den ich wiederum in diesem Artikel gefunden habe: Mithilfe eines Behaviors wird ein delegated Command (in meinem Fall ViewModelRelayCommand) an das ClosedEvent des Window gebunden, so dass – wie bei jedem anderen Command auch – die Close()-Methode (mit einem Negativwert für das DialogResult) aufgerufen werden kann. Als Teil eines XAML-definierten Window sieht das Ganze dann so aus:

<Window x:Class="Views.MVVM.DialogView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BYTES.NET.SAMPLE.Views.MVVM"
        
        xmlns:Behaviors="clr-namespace:BYTES.NET.WPF.Behaviors;assembly=BYTES.NET"
        Behaviors:WindowClosingBehavior.Closed="{Binding Path=Commands[CancelCmd]}"
        
        mc:Ignorable="d"
        Title="{Binding Title, Mode=OneWay}" Height="300" Width="500" ResizeMode="NoResize">

    ...

</Window>

Wie war das gleich nochmal?
Passwortschutz aus Word-Dokumenten entfernen

Die Word-Datei ist passwortgeschützt, aber niemand erinnert sich an das Passwrot? Kein Problem!

Ich bin mir ziemlich sicher, dass jeder von uns bereits schon einmal in diesem oder einem ähnlichen Dilemma war: Sie versuchen auf ein geschütztes Dokument oder einen passwortverschlüsselten Datensatz zuzugreifen, den Sie seit Jahren nicht mehr benötigt haben – und Sie haben das Kennwort vergessen. So aktuell mir geschehen mit einem vor mehr als vier Jahren für einen Kunden erstellen und gegen (versehentliche) Bearbeitung geschützten Word-Formular, das nun doch bearbeitet werden soll.

Trotz intensivster Suche im Passwortmanager und/ oder diversen Readme-Dateien lässt sich natürlich genau dieses Kennwort nicht finden. Was tun?

Eine Schnellsuche beim Internetsuchdienst meines Vertrauens rät:

  • Mittels STRG+A den kompletten Inhalt aus dem Dokument markieren, kopieren und in ein neues Dokument einfügen – funktioniert zumindest in meiner Word-Version (Office365, Januar 2020) nicht
  • Die Datei als .odt oder .rtf neu abspeichern und anschließend öffnen – erzeugt zwar eine bearbeitbare Kopie, entfernt aber auch die Formularfeldfunktionen; also nur eine Notlösung

Ein Artikel aus dem Jahr 2012 hat mich dann aber doch auf die richtige Lösung gebracht:

Im Grunde ist eine .docx-Datei ein ZIP-Container, der u.a. verschiedene XML-Dateien mit dem Dokumententext und den -Eigenschaften beinhaltet. Ändert man die Dateiendung in .zip, lassen sich die einzelnen Parts einer .docx-Datei mithilfe eines üblichen Archivmanager durchsuchen und bearbeiten.

Zwar hat sich inzwischen das Dateiformat gegenüber dem Artikel aus dem Jahr 2012 verändert, innerhalb des word-Verzeichnisses gibt es aber eine Datei namens settings.xml, in der ein XML-Knoten namens w:documentProtection zu finden ist.

Löscht man nun diesen gesamten Knoten, speichert die settings.xml und ändert anschließend die Dateierweiterung des Dokuments wieder in .docx um, erhält man eine ungeschützte und passwortfreie Datei, die sich nicht nur problemlos in Word öffnen lässt, sondern auch noch alle (Formular-)Features aufweist.