SharpShell: Leere Ordnerstrukturen markieren
von Philipp Burch
Einleitung
Wer kennt es nicht: Man navigiert manuell durch eine verschachtelte Ordnerstruktur um zu sehen "was es da so hat" und stellt nach der vierten oder fünften Ebene fest, dass alle diese Ordner gar keine echten Dateien enthalten. Ärgerlich. Da wünschte man sich, der Windows Explorer würde direkt anzeigen, wo überhaupt etwas enthalten ist. Nun gibt es natürlich Erweiterungen, welche beispielsweise die Ordnergrösse direkt im Explorer anzeigen können. Das ist allerdings viel mehr Information als nötig, eigentlich sind wir ja nur daran interessiert, ob überhaupt etwas enthalten ist. Eine Diskussion hier auf ActiveVB hat keine wirklich gute fertige Lösung zu Tage gefördert, also kramte ich nach laaanger Zeit wieder einmal Visual Studio hervor.
Hinweis : Ich möchte hier anmerken, dass ich sowohl die Entwicklung des Tools wie auch die Tests unter Windows 7 durchgeführt habe. Neuere Versionen sollten genauso funktionieren, für XP (und früher) müsste jedoch eine ältere Version (4.0 oder früher) des .NET-Frameworks verwendet werden. Wie es dann mit der Unterstützung für SharpShell aussieht, weiss ich nicht. Ich verwendete die IDE Microsoft Visual Studio Express 2012 for Windows Desktop mit dem .NET-Framework 4.5.
Quelle : Der Inhalt dieses Artikels basiert weitgehend auf .NET Shell Extensions - Shell Icon Overlay Handlers von CodeProject.
Allgemeines zu Overlays
Weil es hier um eine Erweiterung des Windows Explorers geht möchte ich erst ein paar Worte dazu verlieren, wie diese Overlays (also kleine Bilder, welche den Ordner- und Dateisymbolen überlagert werden) verwaltet werden. Explorerfenster werden vom Prozess explorer.exe erzeugt und aktualisiert. Beim Start dieses Prozesses (in der Regel beim Einloggen des Benutzers) werden viele Daten aus der Registry geladen, unter anderem auch die Schlüssel im Pfad
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers
Jeder dieser Schlüssel enthält Informationen über einen OverlayHandler. Der Explorer lädt sie in alphabetischer Reihenfolge, jedoch maximal 15 davon. Das heisst, dass unsere Icons nur dann angezeigt werden, wenn der neue Handler innerhalb der ersten 15 Einträge zu liegen kommt.
Die zentrale Information über jeden Handler ist dessen GUID (globally unique identifier), also eine eindeutige Kennung der DLL, welche den Code unseres Handlers enthält:
Abbildung 1: Installierter OverlayHandler
Damit die DLL mit dem Code auch tatsächlich gefunden und geladen wird, muss sich die Datei entweder im GAC (global assembly cache) befinden, oder der Dateipfad muss beim entsprechenden Klasseneintrag in HKEY_CLASSES_ROOT\CLSID\GUID als CodeBase hinterlegt sein. Mit dem GAC hatte ich keinen Erfolg, daher habe ich für das Tool die zweite Variante gewählt.
Dass die DLL vom explorer.exe-Prozess geladen wird, hat für uns nun zwei wichtige Konsequenzen:
-
Ein Neustart von explorer.exe ist notwendig, damit unsere DLL geladen wird, entsprechend ist es für die Installation des Tools erforderlich, dass der Prozess durch eine der folgenden Methoden neu gestartet wird: Rechner neu booten; Benutzer ab- und wieder anmelden; explorer.exe mit dem Task Manager abschiessen und neu starten
-
Während der Prozess läuft (also typischerweise während der ganzen Sitzung des Benutzers) ist die DLL in Verwendung und kann daher nicht überschrieben, verschoben, umbenannt, etc. werden. Das macht die Entwicklung nicht unbedingt sehr angenehm, weil für jeden neuen Build erst der Explorer abgeschossen, dann kompiliert und anschliessend der Explorer wieder gestartet werden muss.
Implementierung
Neues Projekt
Nach diesen Vorinformationen nun also zur eigentlichen Implementierung des Tools. Weil wir keine eigenständige Applikation, sondern eine Klassenbibliothek bauen möchten, erzeugen wir uns also ein neues Class Library-Projekt (ich nutzte C#, aber Visual Basic würde genauso funktionieren):
Abbildung 2: Neues Klassenbibliothek-Projekt
OverlayHandler-Schnittstelle
Damit die DLL als OverlayHandler verwendet werden kann, muss sie eine definierte Schnittstelle aufweisen. Details dazu können der MSDN-Seite How to Implement Icon Overlay Handlers entnommen werden. Grundsätzlich sind drei Methoden erforderlich:
-
GetOverlayInfo: Diese Methode wird während der Initialisierung der DLL aufgerufen, sowie jedes Mal wenn ein Icon angezeigt werden soll. Als Rückgabe wird ein Pfad zur Datei mit dem Icon erwartet, sowie der Index des Icons innerhalb der Datei (wenn es z.B. in einer EXE-Datei liegt).
-
GetPriority: Sollten mehrere OverlayHandlers mehrere verschiedene Icons für eine Datei anzeigen wollen (was nicht unterstützt wird), dann muss die Shell irgendwie entscheiden, welcher Handler "gewinnt". Je tiefer der Rückgabewert dieser Methode ist, desto höher ist die Priorität des entsprechenden Overlays. Der Wertebereich ist 0 .. 100.
-
IsMemberOf: Diese Methode sollte schliesslich eine Information zurückliefern, ob das gegebene Objekt (Datei, Ordner, etc.) mit dem Overlay versehen werden soll. Für unsere Zwecke ist das die einzige interessante Funktion, die anderen beiden können einfach Konstanten zurückliefern.
Typischerweise werden solche COM-DLLs in C oder C++ entwickelt. Seit einiger Zeit gibt es aber das SharpShell-Package für .NET-Anwendungen, welches den Entwurf solcher Explorer-Erweiterungen stark vereinfacht. SharpShell ist nicht standardmässig in Visual Studio integriert, kann aber mittels NuGet einfach installiert werden. Der entsprechende Dialog ist im Menü TOOLS/Library Package Manager/Manage NuGet Package for Solution... zu finden. Mit einer Suche unter Online nach SharpShell werden die beiden Ergebnisse SharpShell und SharpShell Tools gefunden. Wenn beide installiert sind, dann kann es mit dem Coden losgehen.
Abbildung 3: SharpShell-Packages mittels NuGet installieren
Code der DLL
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Drawing; using SharpShell.SharpIconOverlayHandler; using SharpShell.Interop; using System.Runtime.InteropServices; using System.IO; // See: http://www.codeproject.com/Articles/545781/NET-Shell-Extensions-Shell-Icon-Overlay-Handlers [ComVisible(true)] public class EmptyFolderOverlayHandler : SharpIconOverlayHandler { protected override Icon GetOverlayIcon() { return EmptyFolderOverlay.Properties.Resources.OverlayIcon; } protected override int GetPriority() { return 10; } protected override bool CanShowOverlay(string path, SharpShell.Interop.FILE_ATTRIBUTE attributes) { try { if ((attributes & FILE_ATTRIBUTE.FILE_ATTRIBUTE_DIRECTORY) != 0) { DateTime timeLimit = DateTime.Now.AddMilliseconds(1000); // Abort traversal after 1s. return pathIsEmpty(path, timeLimit); } return false; } catch (Exception) { return false; } } protected bool pathIsEmpty(string path, DateTime timeLimit) { foreach (string f in Directory.EnumerateFiles(path)) { if (!f.EndsWith("Thumbs.db")) { // Found a real file, so not empty. return false; } } foreach (string d in Directory.EnumerateDirectories(path)) { if ((DateTime.Now > timeLimit) || !pathIsEmpty(d, timeLimit)) { return false; } } return true; } }
Das ist es auch schon. Das ist der ganze Code für die Erweiterung. Im folgenden ein paar Erklärungen.
Klassendeklaration
Nach den ersten paar using-Anweisungen um Schreibarbeit zu sparen folgt die Klassendeklaration:
[ComVisible(true)] public class EmptyFolderOverlayHandler : SharpIconOverlayHandler {
Hier sind zwei Dinge zu beachten: Erstens muss die Klasse selbst als [ComVisible(true)] markiert sein, weil der Explorer eine COM-DLL benötigt. Zweitens ist das Interface SharpIconOverlayHandler zu implementieren, damit die Schnittstelle der Klasse einen gültigen OverlayHandler bildet.
GetOverlayIcon() und GetPriority()
Diese beiden Methoden sind nicht weiter interessant, wie weiter oben bereits erwähnt. Bemerkenswert ist jedoch die Vereinfachung durch SharpShell für GetOverlayIcon: Anstelle von einem Dateipfad und einem komischen Index kann einfach ein System.Drawing.Icon zurückgegeben werden, was besonders nett ist, wenn man es als Ressource zum Projekt hinzufügt. Dazu öffnet man die Projekteigenschaften (PROJECT/EmptyFolderOverlay Properties...) und wählt den Abschnitt Resources. Anschliessend kann mit Add Resource/Add Existing File... die Icon-Datei ausgewählt werden. Der Name der Ressource ist dann direkt der Bezeichner im Namensbereich EmptyFolderOverlayHandler.Properties.Resources.
Abbildung 4: Icon als Projektressource
CanShowOverlay() und pathIsEmpty()
Diese beiden Methoden sind nun dafür verantwortlich, dass das Tool auch etwas sinnvolles tut. Die Aufgabe ist grundsätzlich ganz einfach: Wird ein Verzeichnis übergeben, dann wird geprüft, ob dieses Verzeichnis mindestens eine reguläre Datei enthält. Ist dies der Fall, dann wird sofort false zurückgegeben, denn dann ist der Ordner offenbar nicht leer. Ansonsten werden rekursiv alle Unterordner überprüft, irgendwo könnte ja eine Datei versteckt sein.
In CanShowOverlay wird nun also als erstes geprüft, ob es sich beim aktuellen Objekt des Explorers um ein Verzeichnis handelt. Wenn nicht, dann wird direkt false zurückgegeben, ebenso falls während der Ausführung eine Ausnahme (Exception) auftreten sollte. Wenn es ein Verzeichnis ist, dann wird in der Variablen timeLimit der Zeitpunkt eine Sekunde in der Zukunft gespeichert. Dies dient als Limite für die Ausführung, falls ein Verzeichnisbaum einmal wirklich riesig sein sollte ohne eine Datei zu enthalten, bzw. falls das Dateisystem ausserordentlich langsam ist.
Der Pfad zusammen mit der Zeitlimite wird dann an die Methode pathIsEmpty übergeben, welche ihrerseits zuerst alle Dateien innerhalb des angegebenen Pfades sucht. Wird eine Datei gefunden, welche nicht auf Thumbs.db endet, dann wird false zurückgegeben. Thumbs.db ist der Name der Vorschaudatei, welche von Windows automatisch erzeugt wird, wenn ein betrachteter Ordner Dateien mit einer Vorschau enthält (bspw. Bilder, PDFs, etc.). Diese Datei ist normalerweise versteckt, was dazu führt, dass ein Ordner nach dem Löschen des Inhalts möglicherweise nicht als leer markiert würde, obwohl keine relevanten Daten mehr darin gespeichert sind.
Nach dem Prüfen der Dateien im aktuellen Ordner wird eine zweite Schleife zum Durchlaufen aller Unterordner gestartet. Dabei wird jeweils auch geprüft, ob die Zeitlimite überschritten wurde, was mit einer Rückgabe von false quittiert wird. Erst wenn diese Schleife ebenfalls ohne gefundene Datei abbricht, wird der Ordner als leer betrachtet und true zurückgegeben.
Kompilierung und Installation
Der Code ist ziemlich einfach, leider ist dafür bei der Kompilierung und Installation etwas mehr zu beachten. Als erstes müssen wir sicherstellen, dass die erzeugte Binärdatei signiert wird. Dazu ist ein Kryptoschlüssel erforderlich, der wie folgt erzeugt wird:
-
Projekteigenschaften öffnen: PROJECT/EmptyFolderOverlay Properties...
-
Im Abschnitt Signing die Kontrollbox Sign the assembly aktivieren.
-
Im Dropdownfeld <New...> auswählen.
-
Im aufpoppenden Dialog einen Dateinamen und, falls gewünscht, ein Passwort eingeben. Hier geht es jedoch nicht wirklich um eine Erhöhung der Sicherheit, daher ist ein Passwort kaum erforderlich. Anschliessend den Dialog mit OK bestätigen.
Damit kann die DLL nun kompiliert werden. Debugging dieser DLL ist mühsam (aber möglich, siehe http://www.codeproject.com/Articles/545781/NET-Shell-Extensions-Shell-Icon-Overlay-Handlers), daher verzichten wir gleich ganz darauf und wählen Release als Konfiguration aus. Anschliessend erzeugen wir die Bibliothek mit BUILD/Build Solution. Das sollte ohne Probleme ausgeführt werden und uns die Datei bin/Release/EmptyFolderOverlay.dll erzeugen.
Damit ist die eigentliche Entwicklung abgeschlossen. Was nun aber noch fehlt ist die Installation, denn bisher weiss der Explorer noch nichts von unserer coolen Erweiterung. Es gäbe nun die Möglichkeit, die entsprechenden Registry-Einträge manuell zu setzen, aber das ist fehleranfällig und mühsam. Glücklicherweise gibt es im Package SharpShell Tools ein Server Manager Tool, genannt srm.exe (nach der Installation des Packages im Projektordner unter packages/SharpShellTools.2.2.0.0/lib zu finden), welches diese Aufgabe vereinfacht. Der Aufruf ist simpel (es bietet sich an, die srm.exe selbst direkt in den bin/Release-Ordner zu kopieren):
srm.exe install EmptyFolderOverlay.dll -codebase
Wenn dieser Aufruf erfolgreich ausgeführt wird, dann sollte in der Registry nun der Schlüssel wie im Screenshot oben gezeigt erzeugt worden sein. Ebenfalls sollte der dort aufgeführte GUID unter HKEY_CLASSES_ROOT\CLSID erscheinen und im Unterschlüssel CodeBase einen Verweis auf die DLL enthalten. Für die Deinstallation kann übrigens wiederum srm.exe eingesetzt werden:
srm.exe uninstall EmptyFolderOverlay.dll
Das war's. Nun die explorer.exe mit einer der erwähnten Methoden neu starten und voilà:
Abbildung 5: Leere Ordnerstruktur mit Overlay
Download der Projektdateien und Kompilate
Ihre Meinung
Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.